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..53ae057
--- /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 开关
+VITE_APP_WEBSOCKET = true
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..e74db40
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,17 @@
+*.sh
+node_modules
+*.md
+*.woff
+*.ttf
+.vscode
+.idea
+dist
+/public
+/docs
+.husky
+.local
+/bin
+.eslintrc.cjs
+prettier.config.js
+src/assets
+tailwind.config.js
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.cjs b/.eslintrc.cjs
new file mode 100644
index 0000000..6042c39
--- /dev/null
+++ b/.eslintrc.cjs
@@ -0,0 +1,51 @@
+module.exports = {
+ env: {
+ browser: true,
+ node: true,
+ es6: true
+ },
+ parser: 'vue-eslint-parser',
+ extends: [
+ 'plugin:vue/vue3-recommended',
+ './.eslintrc-auto-import.json',
+ 'plugin:@typescript-eslint/recommended',
+ 'prettier',
+ 'plugin:prettier/recommended'
+ ],
+ parserOptions: {
+ ecmaVersion: '2020',
+ sourceType: 'module',
+ project: './tsconfig.*?.json',
+ parser: '@typescript-eslint/parser'
+ },
+ plugins: ['vue', '@typescript-eslint', 'import', 'promise', 'node', 'prettier'],
+ rules: {
+ '@typescript-eslint/no-empty-function': 'off',
+ '@typescript-eslint/no-explicit-any': 'off',
+ '@typescript-eslint/no-unused-vars': 'off',
+ '@typescript-eslint/no-this-alias': 'off',
+
+ // vue
+ 'vue/multi-word-component-names': 'off',
+ 'vue/valid-define-props': 'off',
+ 'vue/no-v-model-argument': 'off',
+ 'prefer-rest-params': 'off',
+ // prettier
+ 'prettier/prettier': 'error',
+ '@typescript-eslint/ban-types': [
+ 'error',
+ {
+ // 关闭空类型检查 {}
+ extendDefaults: true,
+ types: {
+ '{}': false,
+ Function: false
+ }
+ }
+ ]
+ },
+ globals: {
+ DialogOption: 'readonly',
+ OptionType: 'readonly'
+ }
+};
diff --git a/.gitignore b/.gitignore
index 5d947ca..1fd56f0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,18 +1,29 @@
-# Build and Release Folders
-bin-debug/
-bin-release/
-[Oo]bj/
-[Bb]in/
+.DS_Store
+.history
+node_modules/
+dist/
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+**/*.log
-# Other files and folders
-.settings/
+tests/**/coverage/
+tests/e2e/reports
+selenium-debug.log
-# Executables
-*.swf
-*.air
-*.ipa
-*.apk
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.local
-# Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties`
-# should NOT be excluded as they contain compiler settings and other important
-# information for Eclipse / Flash Builder.
+package-lock.json
+yarn.lock
+pnpm-lock.yaml
+
+# 编译生成的文件
+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 b/.prettierrc
new file mode 100644
index 0000000..6ca3ce5
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,20 @@
+{
+ "printWidth": 150,
+ "tabWidth": 2,
+ "useTabs": false,
+ "semi": true,
+ "singleQuote": true,
+ "quoteProps": "preserve",
+ "jsxSingleQuote": false,
+ "bracketSameLine": false,
+ "trailingComma": "none",
+ "bracketSpacing": true,
+ "embeddedLanguageFormatting": "auto",
+ "arrowParens": "always",
+ "requirePragma": false,
+ "insertPragma": false,
+ "proseWrap": "preserve",
+ "htmlWhitespaceSensitivity": "css",
+ "vueIndentScriptAndStyle": false,
+ "endOfLine": "auto"
+}
diff --git a/README.en.md b/README.en.md
deleted file mode 100644
index c2974c6..0000000
--- a/README.en.md
+++ /dev/null
@@ -1,36 +0,0 @@
-# ruoyiflex-elementplus-ts
-
-#### Description
-ruoyiflex-elementplus-ts是Ruoyi-Flex的前端UI ,采用Vue3、Element-Plus、TypeScript、vite、Pinia等技术构建。
-
-#### Software Architecture
-Software architecture description
-
-#### Installation
-
-1. xxxx
-2. xxxx
-3. xxxx
-
-#### Instructions
-
-1. xxxx
-2. xxxx
-3. xxxx
-
-#### Contribution
-
-1. Fork the repository
-2. Create Feat_xxx branch
-3. Commit your code
-4. Create Pull Request
-
-
-#### Gitee Feature
-
-1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
-2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
-3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
-4. The most valuable open source project [GVP](https://gitee.com/gvp)
-5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
-6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
diff --git a/README.md b/README.md
index a658afc..4dba2c2 100644
--- a/README.md
+++ b/README.md
@@ -1,37 +1,149 @@
-# ruoyiflex-elementplus-ts
+
+
+
+Ruoyi-Flex V5.0.0
+Ruoyi-Flex是基于JDK21、Spring Boot V3.2.X+平台 前后端分离的未来8年更快的Java开发框架
-#### 介绍
+
+## 1、项目介绍
ruoyiflex-elementplus-ts是Ruoyi-Flex的前端UI ,采用Vue3、Element-Plus、TypeScript、vite、Pinia等技术构建。
-#### 软件架构
-软件架构说明
+## 2、系统特色
+Ruoyi-Flex秉承“写的更少、性能更好、出错更低、交流通畅、快速入门” 的理念,为您带来全方位的赋能与提升:
+
+### (1)写的更少
+借助MyBatis-Flex,Ruoyi-Flex显著降低了代码输入工作量,最高降低了25.85%,参考“演示模块”中的同一功能演示程序源码对比分析(排除相同代码量的控制器、前端代码):
+
+
+
+除了那些复杂的遗留项目中的统计报表,在绝大部分情况下 Ruoyi-Flex 不需要手写 SQL 语句。
+
+### (2)性能更好
+除了集成的JDK21、SpringBootV3.2、MyBatis-Flex的性能提升,系统“代码生成”模块生成的代码,凡是涉及到后台数据库的多表查询,没有采用数据库的LeftJoin、InnerJoin等SQL方式,而是使用WithRelation编程装配来取代数据库LeftJoin SQL关联查询,数据库不用维护表间外键关系,将多表关联SQL语句拆分为对各个单表的主键查询,关联无 SQL,性能提高10倍。
+
+### (3)出错更低
+原来用mybatis开发需要手写SQL语句,开发后期需要增加字段,修改xml文件是一种灾难,一不留神就犯错了;而Ruoyi-Flex借助MyBatis-Flex则很好地规避了此问题,如果字段输入错误,开发环境IDEA就会自动标红报警,避免犯错。
+
+### (4)交流通畅
+“非我族类,其心必异”。Ruoyi-Flex集成了一大波国产开源软件:MyBatis-Flex、Sa-Token、Hutool、PowerJob、Element-Plus等,同根同源,交流自然顺畅,开发中遇到问题可联系作者快速得到解决。例如,同一个领域的安全框架,一个中国人只需半天就可学会Sa-Token干活,如果是学Spring Security的话,七天也不一定能学会。
+
+### (5)多端同步
+Ruoyi-Flex提供“1+3”端,1个后台端、3个前台端,熟悉js的可使用flex-elementplus-ui前端,熟悉ts的可使用ruoyiflex-elementplus-ts前端,既熟悉ts有熟悉antdesign的请使用ruoyiflex-antdesign-vben前端,总有一款适合您的前端供您选择!
+
+### (6)入门快速
+Ruoyi-Flex已集成各种开源开发框架,扫平了技术障碍,可直接上手干活。使用者只需要设计好数据库表结构,系统能可视化生成前后端本地代码,单表、树表、主子表任你选,10分钟就能开发一个模块,快速入门,开发高效。
+
+## 3、后端项目
+Ruoyi-Flex实行前后端分离仓库,本项目是Element-Plus+TypeScript前端部分,java后端项目是Ruoyi-Flex,后端项目地址: [Ruoyi-Flex](https://gitee.com/dataprince/ruoyi-flex)
+
+## 4、内置功能
+
+1. 租户管理:系统内租户的管理 如:租户套餐、过期时间、用户数量、企业信息等。
+2. 租户套餐管理:系统内租户所能使用的套餐管理 如:套餐内所包含的菜单等。
+3. 客户端管理:系统内对接的所有客户端管理 如: pc端、小程序端等支持动态授权登录方式 如: 短信登录、密码登录等 支持动态控制token时效。
+4. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
+5. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。
+6. 岗位管理:配置系统用户所属担任职务。
+7. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。
+8. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
+9. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。
+10. 参数管理:对系统动态配置常用参数。
+11. 通知公告:系统通知公告信息发布维护。
+12. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
+13. 登录日志:系统登录日志记录查询包含登录异常。
+14. 文件管理:引入云存储服务,将文件存储到MinIO、七牛、阿里、腾讯等OSS服务器上,支持上传、下载。
+15. 在线用户:当前系统中活跃用户状态监控。
+16. 调度中心:集成PowerJob全新一代分布式任务调度与计算框架。
+17. 代码生成:前后端代码的生成(java、html、vue、js),支持单表、树表、主子表,减少70%以上的开发工作量。
+18. 系统接口:集成springdoc,根据文档注释自动生成相关的api接口文档。
+19. 监控中心:集成Spring Boot Admin,监视集群系统CPU、内存、磁盘、堆栈、在线日志、Spring相关配置等。
+20. 缓存监控:对系统的缓存信息查询,命令统计等。
+21. 后台数据库:支持PostgreSQL、MySQL开源数据库及其衍生分布式数据库。
+22. 演示模块:mybatis、mybatis-flex两种格式代码的单表、树表、主子表三种类型的演示程序。
+23. 实现多租户功能。
+24. 实现乐观锁功能。
+25. 实现逻辑删除功能。
+26. 启用JAVA21虚拟线程、分代ZGC功能。
+27. 实现API接口加密功能,密码使用密文传输。
-#### 安装教程
+## 5、演示图
-1. xxxx
-2. xxxx
-3. xxxx
+
+
+  |
+  |
+
+
+  |
+  |
+
+
+  |
+  |
+
+
+  |
+  |
+
+
+  |
+  |
+
+
-#### 使用说明
+## 6、安装教程
-1. xxxx
-2. xxxx
-3. xxxx
+### 开发
-#### 参与贡献
+
+#### 克隆项目
+git clone https://gitee.com/dataprince/flex-elementplus-ui.git
+
+#### 进入项目目录
+cd flex-elementplus-ui
+
+#### 安装依赖
+npm install --registry=https://registry.npmmirror.com
+
+#### 启动服务
+npm run dev
+
+前端浏览器访问 http://localhost:80
+
+### 发布
+
+#### 构建生产环境
+npm run build:prod
+
+## 7、Ruoyi-Flex交流群
+
+本软件完全开源,作者很忙,如果您在使用过程中遇到问题,请付点小费(扫码微信支付99元)后申请加入微信群寻求帮助:
+
+
+ 1、免费QQ交流群: |
+ 762217712[交流1群] |
+
+
+ 2、付费微信VIP群(微信扫码支付99元加好友入群): |
+  |
+
+
+
+## 8、开源协议
+
+**为什么推荐使用本项目?**
+
+① 本项目采用比 Apache 2.0 更宽松的 [MIT License](https://gitee.com/dataprince/ruoyi-flex/blob/master/LICENSE) 开源协议,个人与企业可 100% 免费使用,不用保留类作者、Copyright 信息。
+
+② 代码全部开源,不会像其它项目一样,只开源部分代码,让你无法了解整个项目的架构设计。
+
+如果这个项目让您有所收获,记得 Star 关注哦,这对我是非常不错的鼓励与支持。
+
+
+## 9、参与贡献
1. Fork 本仓库
2. 新建 Feat_xxx 分支
3. 提交代码
-4. 新建 Pull Request
-
-
-#### 特技
-
-1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
-2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
-3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
-4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
-5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
-6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
+4. 新建 Pull Request
\ No newline at end of file
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..4d2773d
--- /dev/null
+++ b/html/ie.html
@@ -0,0 +1,242 @@
+
+
+
+
+ 请升级您的浏览器
+
+
+
+
+
+
+ 请升级您的浏览器,以便我们更好的为您提供服务!
+ 您正在使用 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..aa1c86d
--- /dev/null
+++ b/index.html
@@ -0,0 +1,214 @@
+
+
+
+
+
+
+
+
+ RuoYi-Vue-Plus多租户管理系统
+
+
+
+
+
+
+
+
+
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..e406e22
--- /dev/null
+++ b/package.json
@@ -0,0 +1,90 @@
+{
+ "name": "ruoyiflex-elementplus-ts",
+ "version": "5.1.2",
+ "description": "Ruoyi-Flex多租户管理系统",
+ "author": "数据小王子",
+ "license": "MIT",
+ "type": "module",
+ "scripts": {
+ "dev": "vite serve --mode development",
+ "build:prod": "vite build --mode production",
+ "build:dev": "vite build --mode development",
+ "preview": "vite preview",
+ "lint:eslint": "eslint --fix --ext .ts,.js,.vue ./src ",
+ "prepare": "husky install",
+ "prettier": "prettier --write ."
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://gitee.com/dataprince/ruoyiflex-elementplus-ts.git"
+ },
+ "dependencies": {
+ "@element-plus/icons-vue": "2.3.1",
+ "@vueup/vue-quill": "1.2.0",
+ "@vueuse/core": "10.7.2",
+ "animate.css": "4.1.1",
+ "await-to-js": "3.0.0",
+ "axios": "1.6.5",
+ "crypto-js": "4.2.0",
+ "echarts": "5.4.3",
+ "element-plus": "2.4.4",
+ "file-saver": "2.0.5",
+ "fuse.js": "7.0.0",
+ "js-cookie": "3.0.5",
+ "jsencrypt": "3.3.2",
+ "nprogress": "0.2.0",
+ "path-browserify": "1.0.1",
+ "path-to-regexp": "6.2.1",
+ "pinia": "2.1.7",
+ "screenfull": "6.0.2",
+ "vform3-builds": "3.0.10",
+ "vue": "3.4.13",
+ "vue-cropper": "1.1.1",
+ "vue-i18n": "9.9.0",
+ "vue-router": "4.2.5",
+ "vue-types": "5.1.1"
+ },
+ "devDependencies": {
+ "@iconify/json": "2.2.168",
+ "@intlify/unplugin-vue-i18n": "2.0.0",
+ "@types/crypto-js": "4.2.1",
+ "@types/file-saver": "2.0.7",
+ "@types/js-cookie": "3.0.6",
+ "@types/node": "18.14.2",
+ "@types/nprogress": "0.2.3",
+ "@types/path-browserify": "1.0.2",
+ "@typescript-eslint/eslint-plugin": "6.18.1",
+ "@typescript-eslint/parser": "6.18.1",
+ "@unocss/preset-attributify": "0.58.3",
+ "@unocss/preset-icons": "0.58.3",
+ "@unocss/preset-uno": "0.58.3",
+ "@vue/compiler-sfc": "3.4.13",
+ "@vitejs/plugin-vue": "5.0.3",
+ "autoprefixer": "10.4.16",
+ "eslint": "8.56.0",
+ "eslint-config-prettier": "9.1.0",
+ "eslint-define-config": "2.1.0",
+ "eslint-plugin-prettier": "5.1.3",
+ "eslint-plugin-promise": "6.1.1",
+ "eslint-plugin-node": "11.1.0",
+ "eslint-plugin-import": "2.29.1",
+ "eslint-plugin-vue": "9.20.1",
+ "fast-glob": "3.3.2",
+ "husky": "8.0.3",
+ "postcss": "8.4.33",
+ "prettier": "3.2.2",
+ "sass": "1.69.7",
+ "typescript": "5.3.3",
+ "unocss": "0.58.3",
+ "unplugin-auto-import": "0.17.3",
+ "unplugin-icons": "0.18.2",
+ "unplugin-vue-components": "0.26.0",
+ "unplugin-vue-setup-extend-plus": "1.0.0",
+ "vite-plugin-compression": "0.5.1",
+ "vite-plugin-svg-icons": "2.0.1",
+ "vitest": "1.2.0",
+ "vue-eslint-parser": "9.4.0",
+ "vue-tsc": "1.8.27",
+ "vite": "5.0.11"
+ }
+}
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..94147be
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..66dbed6
--- /dev/null
+++ b/src/App.vue
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
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/mf/product/index.ts b/src/api/mf/product/index.ts
new file mode 100644
index 0000000..c1ed97d
--- /dev/null
+++ b/src/api/mf/product/index.ts
@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { ProductVO, ProductForm, ProductQuery } from '@/api/mf/product/types';
+
+/**
+ * 查询产品树列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listProduct = (query?: ProductQuery): AxiosPromise => {
+ return request({
+ url: '/mf/product/list',
+ method: 'get',
+ params: query
+ });
+};
+
+/**
+ * 查询产品树详细
+ * @param productId
+ */
+export const getProduct = (productId: string | number): AxiosPromise => {
+ return request({
+ url: '/mf/product/' + productId,
+ method: 'get'
+ });
+};
+
+/**
+ * 新增产品树
+ * @param data
+ */
+export const addProduct = (data: ProductForm) => {
+ return request({
+ url: '/mf/product',
+ method: 'post',
+ data: data
+ });
+};
+
+/**
+ * 修改产品树
+ * @param data
+ */
+export const updateProduct = (data: ProductForm) => {
+ return request({
+ url: '/mf/product',
+ method: 'put',
+ data: data
+ });
+};
+
+/**
+ * 删除产品树
+ * @param productId
+ */
+export const delProduct = (productId: string | number | Array) => {
+ return request({
+ url: '/mf/product/' + productId,
+ method: 'delete'
+ });
+};
diff --git a/src/api/mf/product/types.ts b/src/api/mf/product/types.ts
new file mode 100644
index 0000000..447fef6
--- /dev/null
+++ b/src/api/mf/product/types.ts
@@ -0,0 +1,76 @@
+export interface ProductVO extends BaseEntity {
+ /**
+ * 父产品id
+ */
+ parentId: string | number;
+
+ /**
+ * 产品名称
+ */
+ productName: string;
+
+ /**
+ * 显示顺序
+ */
+ orderNum: number;
+
+ /**
+ * 产品状态(0正常 1停用)
+ */
+ status: string;
+
+ /**
+ * 子对象
+ */
+ children: ProductVO[];
+}
+
+export interface ProductForm {
+ /**
+ * 产品id
+ */
+ productId?: string | number;
+
+ /**
+ * 父产品id
+ */
+ parentId?: string | number;
+
+ /**
+ * 产品名称
+ */
+ productName?: string;
+
+ /**
+ * 显示顺序
+ */
+ orderNum?: number;
+
+ /**
+ * 产品状态(0正常 1停用)
+ */
+ status?: string;
+
+ /**
+ * 乐观锁
+ */
+ version?: number;
+
+}
+
+export interface ProductQuery {
+ /**
+ * 产品名称
+ */
+ productName?: string;
+
+ /**
+ * 产品状态(0正常 1停用)
+ */
+ status?: string;
+
+ /**
+ * 日期范围参数
+ */
+ params?: any;
+}
diff --git a/src/api/mf/student/index.ts b/src/api/mf/student/index.ts
new file mode 100644
index 0000000..9bfa7a7
--- /dev/null
+++ b/src/api/mf/student/index.ts
@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { StudentVO, StudentForm, StudentQuery } from '@/api/mf/student/types';
+
+/**
+ * 查询学生信息表列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listStudent = (query?: StudentQuery): AxiosPromise => {
+ return request({
+ url: '/mf/student/list',
+ method: 'get',
+ params: query
+ });
+};
+
+/**
+ * 查询学生信息表详细
+ * @param studentId
+ */
+export const getStudent = (studentId: string | number): AxiosPromise => {
+ return request({
+ url: '/mf/student/' + studentId,
+ method: 'get'
+ });
+};
+
+/**
+ * 新增学生信息表
+ * @param data
+ */
+export const addStudent = (data: StudentForm) => {
+ return request({
+ url: '/mf/student',
+ method: 'post',
+ data: data
+ });
+};
+
+/**
+ * 修改学生信息表
+ * @param data
+ */
+export const updateStudent = (data: StudentForm) => {
+ return request({
+ url: '/mf/student',
+ method: 'put',
+ data: data
+ });
+};
+
+/**
+ * 删除学生信息表
+ * @param studentId
+ */
+export const delStudent = (studentId: string | number | Array) => {
+ return request({
+ url: '/mf/student/' + studentId,
+ method: 'delete'
+ });
+};
diff --git a/src/api/mf/student/types.ts b/src/api/mf/student/types.ts
new file mode 100644
index 0000000..517af02
--- /dev/null
+++ b/src/api/mf/student/types.ts
@@ -0,0 +1,96 @@
+export interface StudentVO extends BaseEntity {
+ /**
+ * 学生名称
+ */
+ studentName: string;
+
+ /**
+ * 年龄
+ */
+ studentAge: number;
+
+ /**
+ * 爱好(0代码 1音乐 2电影)
+ */
+ studentHobby: string;
+
+ /**
+ * 性别(1男 2女 3未知)
+ */
+ studentGender: string;
+
+ /**
+ * 状态(0正常 1停用)
+ */
+ studentStatus: string;
+
+ /**
+ * 生日
+ */
+ studentBirthday: string;
+
+}
+
+export interface StudentForm {
+ /**
+ * 编号
+ */
+ studentId?: string | number;
+
+ /**
+ * 学生名称
+ */
+ studentName?: string;
+
+ /**
+ * 年龄
+ */
+ studentAge?: number;
+
+ /**
+ * 爱好(0代码 1音乐 2电影)
+ */
+ studentHobby?: string;
+
+ /**
+ * 性别(1男 2女 3未知)
+ */
+ studentGender?: string;
+
+ /**
+ * 状态(0正常 1停用)
+ */
+ studentStatus?: string;
+
+ /**
+ * 生日
+ */
+ studentBirthday?: string;
+
+ /**
+ * 乐观锁
+ */
+ version?: number;
+
+}
+
+export interface StudentQuery extends PageQuery {
+
+ /**
+ * 学生名称
+ */
+ studentName?: string;
+
+ /**
+ * 状态(0正常 1停用)
+ */
+ studentStatus?: string;
+
+ /**
+ * 日期范围参数
+ */
+ params?: any;
+}
+
+
+
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..23047a7
--- /dev/null
+++ b/src/api/system/client/index.ts
@@ -0,0 +1,82 @@
+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 version 版本号
+ * @param status 状态
+ */
+export function changeStatus(id: number, version: number, status: string) {
+ const data = {
+ id,
+ version,
+ 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..89154af
--- /dev/null
+++ b/src/api/system/client/types.ts
@@ -0,0 +1,148 @@
+export interface ClientVO {
+ /**
+ * id
+ */
+ id: number;
+
+ /**
+ * 客户端id
+ */
+ clientId: string;
+
+ /**
+ * 客户端key
+ */
+ clientKey: string;
+
+ /**
+ * 客户端秘钥
+ */
+ clientSecret: string;
+
+ /**
+ * 授权类型
+ */
+ grantTypeList: string[];
+
+ /**
+ * 设备类型
+ */
+ deviceType: string;
+
+ /**
+ * token活跃超时时间
+ */
+ activeTimeout: number;
+
+ /**
+ * token固定超时
+ */
+ timeout: number;
+
+ /**
+ * 状态(0正常 1停用)
+ */
+ status: string;
+
+ /** 乐观锁 */
+ version: number;
+
+ /**
+ * 删除标志(0就代表存在 1就代表删除)
+ */
+ delFlag: number;
+}
+
+export interface ClientForm extends BaseEntity {
+ /**
+ * id
+ */
+ id?: number;
+
+ /**
+ * 客户端id
+ */
+ clientId?: string | number;
+
+ /**
+ * 客户端key
+ */
+ clientKey?: string;
+
+ /**
+ * 客户端秘钥
+ */
+ clientSecret?: string;
+
+ /**
+ * 授权类型
+ */
+ grantTypeList?: string[];
+
+ /**
+ * 设备类型
+ */
+ deviceType?: string;
+
+ /**
+ * token活跃超时时间
+ */
+ activeTimeout?: number;
+
+ /**
+ * token固定超时
+ */
+ timeout?: number;
+
+ /**
+ * 状态(0正常 1停用)
+ */
+ status?: string;
+
+ /**
+ * 状态(0正常 1停用)
+ */
+ delFlag?: number;
+}
+
+export interface ClientQuery extends PageQuery {
+ /**
+ * 客户端id
+ */
+ clientId?: 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..50400e2
--- /dev/null
+++ b/src/api/system/config/index.ts
@@ -0,0 +1,75 @@
+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, version: number) {
+ return request({
+ url: '/system/config/updateByKey',
+ method: 'put',
+ data: {
+ configKey: key,
+ configValue: value,
+ version: version
+ }
+ });
+}
+
+// 删除参数配置
+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..1e0dc7d
--- /dev/null
+++ b/src/api/system/config/types.ts
@@ -0,0 +1,24 @@
+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;
+ version: number;
+ 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..0d4e215
--- /dev/null
+++ b/src/api/system/dept/types.ts
@@ -0,0 +1,46 @@
+/**
+ * 部门查询参数
+ */
+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;
+ version?: number;
+ 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..b09976f
--- /dev/null
+++ b/src/api/system/dict/data/types.ts
@@ -0,0 +1,27 @@
+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;
+ version: 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..7b6097c
--- /dev/null
+++ b/src/api/system/dict/type/types.ts
@@ -0,0 +1,19 @@
+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;
+ version: number;
+ 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..8818265
--- /dev/null
+++ b/src/api/system/menu/types.ts
@@ -0,0 +1,70 @@
+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;
+ version?: number;
+}
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..6d5feb2
--- /dev/null
+++ b/src/api/system/notice/types.ts
@@ -0,0 +1,27 @@
+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;
+ version: number;
+ 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..bc14416
--- /dev/null
+++ b/src/api/system/ossConfig/index.ts
@@ -0,0 +1,61 @@
+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, version: number, status: string, configKey: string) {
+ const data = {
+ ossConfigId,
+ version,
+ 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..31ed3cb
--- /dev/null
+++ b/src/api/system/ossConfig/types.ts
@@ -0,0 +1,39 @@
+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;
+ version: number;
+ 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..8845e08
--- /dev/null
+++ b/src/api/system/post/types.ts
@@ -0,0 +1,24 @@
+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;
+ version: number;
+ 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..c679ca6
--- /dev/null
+++ b/src/api/system/role/index.ts
@@ -0,0 +1,145 @@
+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, version: number, status: string) => {
+ const data = {
+ roleId,
+ version,
+ 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..27e057a
--- /dev/null
+++ b/src/api/system/role/types.ts
@@ -0,0 +1,53 @@
+/**
+ * 菜单树形结构类型
+ */
+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;
+ version?: number;
+}
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..8118d82
--- /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(tenantId: string | number): AxiosPromise {
+ return request({
+ url: '/system/tenant/' + tenantId,
+ 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(tenantId: string | number, version: number, status: string) {
+ const data = {
+ tenantId,
+ version,
+ status
+ };
+ return request({
+ url: '/system/tenant/changeStatus',
+ method: 'put',
+ data: data
+ });
+}
+
+// 删除租户
+export function delTenant(tenantId: string | number | Array) {
+ return request({
+ url: '/system/tenant/' + tenantId,
+ 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..a310aa8
--- /dev/null
+++ b/src/api/system/tenant/types.ts
@@ -0,0 +1,45 @@
+export interface TenantVO extends BaseEntity {
+ 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 {
+ 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;
+ version?: 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..a5bc7e1
--- /dev/null
+++ b/src/api/system/tenantPackage/index.ts
@@ -0,0 +1,68 @@
+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, version: number, status: string) {
+ const data = {
+ packageId,
+ version,
+ 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..9aaa07c
--- /dev/null
+++ b/src/api/system/tenantPackage/types.ts
@@ -0,0 +1,21 @@
+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;
+ version?: number;
+ 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..6303b46
--- /dev/null
+++ b/src/api/system/user/index.ts
@@ -0,0 +1,217 @@
+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, version: number, password: string) => {
+ const data = {
+ userId,
+ version,
+ 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, version: number, status: string) => {
+ const data = {
+ userId,
+ version,
+ 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..92f1066
--- /dev/null
+++ b/src/api/system/user/types.ts
@@ -0,0 +1,84 @@
+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;
+ gender: string;
+ avatar: string;
+ status: string;
+ delFlag: string;
+ loginIp: string;
+ loginDate: string;
+ remark: string;
+ deptName: string;
+ 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;
+ gender?: string;
+ status: string;
+ remark?: string;
+ postIds: string[];
+ roleIds: string[];
+ version?: number;
+}
+
+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..57f6d86
--- /dev/null
+++ b/src/api/tool/gen/index.ts
@@ -0,0 +1,88 @@
+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): AxiosPromise => {
+ return request({
+ url: '/tool/gen',
+ method: 'put',
+ data: data
+ });
+};
+
+// 导入表
+export const importTable = (data: { tables: string }): AxiosPromise => {
+ return request({
+ url: '/tool/gen/importTable',
+ method: 'post',
+ params: data
+ });
+};
+
+// 预览生成代码
+export const previewTable = (tableId: string | number, frontType: number) => {
+ const data = {
+ tableId,
+ frontType
+ };
+ return request({
+ url: '/tool/gen/preview',
+ method: 'get',
+ params: data
+ });
+};
+
+// 删除表数据
+export const delTable = (tableId: string | number | Array) => {
+ return request({
+ url: '/tool/gen/' + tableId,
+ method: 'delete'
+ });
+};
+
+// 生成代码(自定义路径)
+export const genCode = (tableId: string | number, frontType: number) => {
+ const data = {
+ tableId,
+ frontType
+ };
+ return request({
+ url: '/tool/gen/genCode',
+ method: 'get',
+ params: data
+ });
+};
+
+// 同步数据库
+export const synchDb = (tableId: string | number) => {
+ return request({
+ url: '/tool/gen/synchDb/' + tableId,
+ method: 'get'
+ });
+};
diff --git a/src/api/tool/gen/types.ts b/src/api/tool/gen/types.ts
new file mode 100644
index 0000000..5a4c41a
--- /dev/null
+++ b/src/api/tool/gen/types.ts
@@ -0,0 +1,173 @@
+export interface TableVO extends BaseEntity {
+ tableId: string | number;
+ 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;
+}
+
+export interface DbColumnVO extends BaseEntity {
+ 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 {
+ 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;
+ version?: number;
+}
+
+export interface DbTableQuery extends PageQuery {
+ tableName: string;
+ tableComment: string;
+}
+
+export interface GenTableVO {
+ info: DbTableVO;
+ rows: DbColumnVO[];
+ tables: DbTableVO[];
+}
+
+export interface DbColumnForm extends BaseEntity {
+ 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 {
+ 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..9798f5a
--- /dev/null
+++ b/src/api/types.ts
@@ -0,0 +1,59 @@
+/**
+ * 注册
+ */
+export type RegisterForm = {
+ tenantId: number;
+ username: string;
+ password: string;
+ confirmPassword?: string;
+ code?: string;
+ uuid?: string;
+ userType?: string;
+};
+
+/**
+ * 登录请求
+ */
+export interface LoginData {
+ tenantId?: number;
+ 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/topiam.svg b/src/assets/icons/svg/topiam.svg
new file mode 100644
index 0000000..e7ea057
--- /dev/null
+++ b/src/assets/icons/svg/topiam.svg
@@ -0,0 +1,29 @@
+
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/dataprince.jpg b/src/assets/images/dataprince.jpg
new file mode 100644
index 0000000..fc3c11f
Binary files /dev/null and b/src/assets/images/dataprince.jpg differ
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..89d2e8d
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..b3a940b
Binary files /dev/null and b/src/assets/images/profile.jpg differ
diff --git a/src/assets/images/ruoyi-flex-logo.png b/src/assets/images/ruoyi-flex-logo.png
new file mode 100644
index 0000000..49dfd2a
Binary files /dev/null and b/src/assets/images/ruoyi-flex-logo.png differ
diff --git a/src/assets/logo/logo.png b/src/assets/logo/logo.png
new file mode 100644
index 0000000..49dfd2a
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..bdf9b03
--- /dev/null
+++ b/src/assets/styles/index.scss
@@ -0,0 +1,215 @@
+@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..d85da55
--- /dev/null
+++ b/src/assets/styles/sidebar.scss
@@ -0,0 +1,232 @@
+#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..9502e60
--- /dev/null
+++ b/src/components/Breadcrumb/index.vue
@@ -0,0 +1,63 @@
+
+
+
+
+ {{ item.meta?.title }}
+ {{ item.meta?.title }}
+
+
+
+
+
+
+
+
diff --git a/src/components/BuildCode/index.vue b/src/components/BuildCode/index.vue
new file mode 100644
index 0000000..0b8e3f4
--- /dev/null
+++ b/src/components/BuildCode/index.vue
@@ -0,0 +1,61 @@
+
+
+
+
+
+ 保存
+
+
+
+
+
+
+
+
diff --git a/src/components/BuildCode/render.vue b/src/components/BuildCode/render.vue
new file mode 100644
index 0000000..aeb9312
--- /dev/null
+++ b/src/components/BuildCode/render.vue
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
diff --git a/src/components/DictTag/index.vue b/src/components/DictTag/index.vue
new file mode 100644
index 0000000..b355a42
--- /dev/null
+++ b/src/components/DictTag/index.vue
@@ -0,0 +1,87 @@
+
+
+
+
+
+ {{ item.label + ' ' }}
+
+
+ {{ item.label + ' ' }}
+
+
+
+
+ {{ unmatchArray }}
+
+
+
+
+
+
+
diff --git a/src/components/Editor/index.vue b/src/components/Editor/index.vue
new file mode 100644
index 0000000..fc30c85
--- /dev/null
+++ b/src/components/Editor/index.vue
@@ -0,0 +1,244 @@
+
+
+
+
+
+
+
+ $emit('update:modelValue', content)"
+ />
+
+
+
+
+
+
diff --git a/src/components/FileUpload/index.vue b/src/components/FileUpload/index.vue
new file mode 100644
index 0000000..aaaa45c
--- /dev/null
+++ b/src/components/FileUpload/index.vue
@@ -0,0 +1,229 @@
+
+
+
+
+ 选取文件
+
+
+
+ 请上传
+
+ 大小不超过 {{ fileSize }}MB
+
+
+ 格式为 {{ fileType.join('/') }}
+
+ 的文件
+
+
+
+
+
+ {{ getFileName(file.name) }}
+
+
+ 删除
+
+
+
+
+
+
+
+
+
diff --git a/src/components/Hamburger/index.vue b/src/components/Hamburger/index.vue
new file mode 100644
index 0000000..f9f5e62
--- /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..a785958
--- /dev/null
+++ b/src/components/HeaderSearch/index.vue
@@ -0,0 +1,195 @@
+
+
+
+
+
+
+
diff --git a/src/components/IconSelect/index.vue b/src/components/IconSelect/index.vue
new file mode 100644
index 0000000..3bfde4d
--- /dev/null
+++ b/src/components/IconSelect/index.vue
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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..5543923
--- /dev/null
+++ b/src/components/ImagePreview/index.vue
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/ImageUpload/index.vue b/src/components/ImageUpload/index.vue
new file mode 100644
index 0000000..ae5cd45
--- /dev/null
+++ b/src/components/ImageUpload/index.vue
@@ -0,0 +1,218 @@
+
+
+
+
+
+
+
+
+
+ 请上传
+
+ 大小不超过 {{ fileSize }}MB
+
+
+ 格式为 {{ fileType.join('/') }}
+
+ 的文件
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/LangSelect/index.vue b/src/components/LangSelect/index.vue
new file mode 100644
index 0000000..b5fafd3
--- /dev/null
+++ b/src/components/LangSelect/index.vue
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+ 中文
+ English
+
+
+
+
+
+
+
+
diff --git a/src/components/Pagination/index.vue b/src/components/Pagination/index.vue
new file mode 100644
index 0000000..ac02193
--- /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..e8c9d65
--- /dev/null
+++ b/src/components/RightToolbar/index.vue
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/RuoYiDoc/index.vue b/src/components/RuoYiDoc/index.vue
new file mode 100644
index 0000000..08c47b5
--- /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..3762358
--- /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..abf72cc
--- /dev/null
+++ b/src/components/SizeSelect/index.vue
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+ {{ item.label }}
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/SvgIcon/index.vue b/src/components/SvgIcon/index.vue
new file mode 100644
index 0000000..05dfe87
--- /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..c41177e
--- /dev/null
+++ b/src/components/TopNav/index.vue
@@ -0,0 +1,197 @@
+
+
+
+
+ {{ item.meta?.title }}
+
+
+
+
+ 更多菜单
+
+ {{ item.meta?.title }}
+
+
+
+
+
+
+
+
diff --git a/src/components/TreeSelect/index.vue b/src/components/TreeSelect/index.vue
new file mode 100644
index 0000000..7accd6b
--- /dev/null
+++ b/src/components/TreeSelect/index.vue
@@ -0,0 +1,147 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/iFrame/index.vue b/src/components/iFrame/index.vue
new file mode 100644
index 0000000..98f2224
--- /dev/null
+++ b/src/components/iFrame/index.vue
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
diff --git a/src/directive/common/copyText.ts b/src/directive/common/copyText.ts
new file mode 100644
index 0000000..b6805f8
--- /dev/null
+++ b/src/directive/common/copyText.ts
@@ -0,0 +1,66 @@
+/**
+ * v-copyText 复制文本内容
+ * Copyright (c) 2022 ruoyi
+ */
+
+export default {
+ beforeMount(el: any, { value, arg }: any) {
+ if (arg === 'callback') {
+ el.$copyCallback = value;
+ } else {
+ el.$copyValue = value;
+ const handler = () => {
+ copyTextToClipboard(el.$copyValue);
+ if (el.$copyCallback) {
+ el.$copyCallback(el.$copyValue);
+ }
+ };
+ el.addEventListener('click', handler);
+ el.$destroyCopy = () => el.removeEventListener('click', handler);
+ }
+ }
+};
+
+function copyTextToClipboard(input: string, { target = document.body } = {}) {
+ const element = document.createElement('textarea');
+ const previouslyFocusedElement = document.activeElement as HTMLInputElement;
+ element.value = input;
+ // Prevent keyboard from showing on mobile
+ element.setAttribute('readonly', '');
+
+ element.style.contain = 'strict';
+ element.style.position = 'absolute';
+ element.style.left = '-9999px';
+ element.style.fontSize = '12pt'; // Prevent zooming on iOS
+
+ const selection = document.getSelection();
+ let originalRange;
+ if (selection) {
+ originalRange = selection?.rangeCount > 0 && selection.getRangeAt(0);
+ }
+ target.append(element);
+ element.select();
+
+ // Explicit selection workaround for iOS
+ element.selectionStart = 0;
+ element.selectionEnd = input.length;
+
+ let isSuccess = false;
+ try {
+ isSuccess = document.execCommand('copy');
+ } catch (err) {
+ console.error(err);
+ }
+ element.remove();
+
+ if (originalRange) {
+ selection?.removeAllRanges();
+ selection?.addRange(originalRange);
+ }
+
+ // Get the focus back on the previously focused element, if any
+ if (previouslyFocusedElement) {
+ previouslyFocusedElement.focus();
+ }
+ return isSuccess;
+}
diff --git a/src/directive/index.ts b/src/directive/index.ts
new file mode 100644
index 0000000..ef25ee8
--- /dev/null
+++ b/src/directive/index.ts
@@ -0,0 +1,9 @@
+import copyText from './common/copyText';
+import { hasPermi, hasRoles } from './permission';
+import { App } from 'vue';
+
+export default (app: App) => {
+ app.directive('copyText', copyText);
+ app.directive('hasPermi', hasPermi);
+ app.directive('hasRoles', hasRoles);
+};
diff --git a/src/directive/permission/index.ts b/src/directive/permission/index.ts
new file mode 100644
index 0000000..afde332
--- /dev/null
+++ b/src/directive/permission/index.ts
@@ -0,0 +1,44 @@
+import { Directive, DirectiveBinding } from 'vue';
+import useUserStore from '@/store/modules/user';
+/**
+ * 操作权限处理
+ */
+export const hasPermi: Directive = {
+ mounted(el: HTMLElement, binding: DirectiveBinding) {
+ const { permissions } = useUserStore();
+ // 「其他角色」按钮权限校验
+ const { value } = binding;
+ if (value && value instanceof Array && value.length > 0) {
+ const hasPermission = permissions.some((permi: string) => {
+ return permi === '*:*:*' || value.includes(permi);
+ });
+ if (!hasPermission) {
+ el.parentNode && el.parentNode.removeChild(el);
+ return false;
+ }
+ } else {
+ throw new Error("check perms! Like v-has-permi=\"['system:user:add','system:user:edit']\"");
+ }
+ }
+};
+
+/**
+ * 角色权限处理
+ */
+export const hasRoles: Directive = {
+ mounted(el: HTMLElement, binding: DirectiveBinding) {
+ const { value } = binding;
+ const { roles } = useUserStore();
+ if (value && value instanceof Array && value.length > 0) {
+ const hasRole = roles.some((role: string) => {
+ return role === 'admin' || value.includes(role);
+ });
+ if (!hasRole) {
+ el.parentNode && el.parentNode.removeChild(el);
+ return false;
+ }
+ } else {
+ throw new Error("check roles! Like v-has-roles=\"['admin','test']\"");
+ }
+ }
+};
diff --git a/src/enums/LanguageEnum.ts b/src/enums/LanguageEnum.ts
new file mode 100644
index 0000000..c857c43
--- /dev/null
+++ b/src/enums/LanguageEnum.ts
@@ -0,0 +1,5 @@
+export enum LanguageEnum {
+ zh_CN = 'zh_CN',
+
+ en_US = 'en_US'
+}
diff --git a/src/enums/MenuTypeEnum.ts b/src/enums/MenuTypeEnum.ts
new file mode 100644
index 0000000..cecebd2
--- /dev/null
+++ b/src/enums/MenuTypeEnum.ts
@@ -0,0 +1,15 @@
+export enum MenuTypeEnum {
+ /**
+ * 目录
+ */
+ M = 'M',
+ /**
+ * 菜单
+ */
+ C = 'C',
+
+ /**
+ * 按钮
+ */
+ F = 'F'
+}
diff --git a/src/enums/RespEnum.ts b/src/enums/RespEnum.ts
new file mode 100644
index 0000000..ce60a51
--- /dev/null
+++ b/src/enums/RespEnum.ts
@@ -0,0 +1,90 @@
+export enum HttpStatus {
+ /**
+ * 操作成功
+ */
+ SUCCESS = 200,
+ /**
+ * 对象创建成功
+ */
+ CREATED = 201,
+ /**
+ * 请求已经被接受
+ */
+ ACCEPTED = 202,
+ /**
+ * 操作已经执行成功,但是没有返回数据
+ */
+ NO_CONTENT = 204,
+ /**
+ * 资源已经被移除
+ */
+ MOVED_PERM = 301,
+ /**
+ * 重定向
+ */
+ SEE_OTHER = 303,
+ /**
+ * 资源没有被修改
+ */
+ NOT_MODIFIED = 304,
+ /**
+ * 参数列表错误(缺少,格式不匹配)
+ */
+ PARAM_ERROR = 400,
+ /**
+ * 未授权
+ */
+ UNAUTHORIZED = 401,
+ /**
+ * 访问受限,授权过期
+ */
+ FORBIDDEN = 403,
+ /**
+ * 资源,服务未找到
+ */
+ NOT_FOUND = 404,
+ /**
+ * 不允许的http方法
+ */
+ BAD_METHOD = 405,
+ /**
+ * 资源冲突,或者资源被锁
+ */
+ CONFLICT = 409,
+ /**
+ * 不支持的数据,媒体类型
+ */
+ UNSUPPORTED_TYPE = 415,
+ /**
+ * 系统内部错误
+ */
+ SERVER_ERROR = 500,
+ /**
+ * 接口未实现
+ */
+ NOT_IMPLEMENTED = 501,
+ /**
+ * 服务不可用,过载或者维护
+ */
+ BAD_GATEWAY = 502,
+ /**
+ * 网关超时
+ */
+ GATEWAY_TIMEOUT = 504,
+ /**
+ * 未知错误
+ */
+ UNKNOWN_ERROR = 520,
+ /**
+ * 服务未知错误
+ */
+ SERVICE_ERROR = 521,
+ /**
+ * 数据库未知错误
+ */
+ DATABASE_ERROR = 522,
+ /**
+ * 系统警告消息
+ */
+ WARN = 601
+}
diff --git a/src/enums/SettingTypeEnum.ts b/src/enums/SettingTypeEnum.ts
new file mode 100644
index 0000000..bf4ec43
--- /dev/null
+++ b/src/enums/SettingTypeEnum.ts
@@ -0,0 +1,16 @@
+export enum SettingTypeEnum {
+ TITLE = 'title',
+ THEME = 'theme',
+ SIDE_THEME = 'sideTheme',
+ SHOW_SETTINGS = 'showSettings',
+ TOP_NAV = 'topNav',
+ TAGS_VIEW = 'tagsView',
+ FIXED_HEADER = 'fixedHeader',
+ SIDEBAR_LOGO = 'sidebarLogo',
+ DYNAMIC_TITLE = 'dynamicTitle',
+ ANIMATION_ENABLE = 'animationEnable',
+ LAYOUT = 'layout',
+ DARK = 'dark',
+
+ LAYOUT_SETTING = 'layout-setting'
+}
diff --git a/src/enums/SideThemeEnum.ts b/src/enums/SideThemeEnum.ts
new file mode 100644
index 0000000..f172858
--- /dev/null
+++ b/src/enums/SideThemeEnum.ts
@@ -0,0 +1,4 @@
+export enum SideThemeEnum {
+ DARK = 'theme-dark',
+ LIGHT = 'theme-light'
+}
diff --git a/src/enums/layout/LayoutEnum.ts b/src/enums/layout/LayoutEnum.ts
new file mode 100644
index 0000000..0aa601b
--- /dev/null
+++ b/src/enums/layout/LayoutEnum.ts
@@ -0,0 +1,4 @@
+export enum ThemeEnum {
+ DARK = 'theme-dark',
+ LIGHT = 'theme-light'
+}
diff --git a/src/lang/en_US.json b/src/lang/en_US.json
new file mode 100644
index 0000000..17b472e
--- /dev/null
+++ b/src/lang/en_US.json
@@ -0,0 +1,25 @@
+{
+ "route": {
+ "dashboard": "Dashboard",
+ "document": "Document"
+ },
+ "login": {
+ "username": "Username",
+ "password": "Password",
+ "login": "Login",
+ "code": "Verification Code",
+ "copyright": ""
+ },
+ "navbar": {
+ "full": "Full Screen",
+ "language": "Language",
+ "dashboard": "Dashboard",
+ "document": "Document",
+ "message": "Message",
+ "layoutSize": "Layout Size",
+ "selectTenant": "Select Tenant",
+ "layoutSetting": "Layout Setting",
+ "personalCenter": "Personal Center",
+ "logout": "Logout"
+ }
+}
diff --git a/src/lang/en_US.ts b/src/lang/en_US.ts
new file mode 100644
index 0000000..034ea91
--- /dev/null
+++ b/src/lang/en_US.ts
@@ -0,0 +1,28 @@
+export default {
+ // 路由国际化
+ route: {
+ dashboard: 'Dashboard',
+ document: 'Document'
+ },
+ // 登录页面国际化
+ login: {
+ username: 'Username',
+ password: 'Password',
+ login: 'Login',
+ code: 'Verification Code',
+ copyright: ''
+ },
+ // 导航栏国际化
+ navbar: {
+ full: 'Full Screen',
+ language: 'Language',
+ dashboard: 'Dashboard',
+ document: 'Document',
+ message: 'Message',
+ layoutSize: 'Layout Size',
+ selectTenant: 'Select Tenant',
+ layoutSetting: 'Layout Setting',
+ personalCenter: 'Personal Center',
+ logout: 'Logout'
+ }
+};
diff --git a/src/lang/index.ts b/src/lang/index.ts
new file mode 100644
index 0000000..e4e1d25
--- /dev/null
+++ b/src/lang/index.ts
@@ -0,0 +1,27 @@
+// 自定义国际化配置
+import { createI18n } from 'vue-i18n';
+
+import { LanguageEnum } from '@/enums/LanguageEnum';
+import messages from '@intlify/unplugin-vue-i18n/messages';
+
+/**
+ * 获取当前语言
+ * @returns zh-cn|en ...
+ */
+export const getLanguage = (): LanguageEnum => {
+ const language = useStorage('language', LanguageEnum.zh_CN);
+ if (language.value) {
+ return language.value;
+ }
+ return LanguageEnum.zh_CN;
+};
+
+const i18n = createI18n({
+ globalInjection: true,
+ allowComposition: true,
+ legacy: false,
+ locale: getLanguage(),
+ messages
+});
+
+export default i18n;
diff --git a/src/lang/zh_CN.json b/src/lang/zh_CN.json
new file mode 100644
index 0000000..071598d
--- /dev/null
+++ b/src/lang/zh_CN.json
@@ -0,0 +1,25 @@
+{
+ "route": {
+ "dashboard": "首页",
+ "document": "项目文档"
+ },
+ "login": {
+ "username": "用户名",
+ "password": "密码",
+ "login": "登 录",
+ "code": "请输入验证码",
+ "copyright": ""
+ },
+ "navbar": {
+ "full": "全屏",
+ "language": "语言",
+ "dashboard": "首页",
+ "document": "项目文档",
+ "message": "消息",
+ "layoutSize": "布局大小",
+ "selectTenant": "选择租户",
+ "layoutSetting": "布局设置",
+ "personalCenter": "个人中心",
+ "logout": "退出登录"
+ }
+}
diff --git a/src/lang/zh_CN.ts b/src/lang/zh_CN.ts
new file mode 100644
index 0000000..666a400
--- /dev/null
+++ b/src/lang/zh_CN.ts
@@ -0,0 +1,27 @@
+export default {
+ // 路由国际化
+ route: {
+ dashboard: '首页',
+ document: '项目文档'
+ },
+ // 登录页面国际化
+ login: {
+ username: '用户名',
+ password: '密码',
+ login: '登 录',
+ code: '请输入验证码',
+ copyright: ''
+ },
+ navbar: {
+ full: '全屏',
+ language: '语言',
+ dashboard: '首页',
+ document: '项目文档',
+ message: '消息',
+ layoutSize: '布局大小',
+ selectTenant: '选择租户',
+ layoutSetting: '布局设置',
+ personalCenter: '个人中心',
+ logout: '退出登录'
+ }
+};
diff --git a/src/layout/components/AppMain.vue b/src/layout/components/AppMain.vue
new file mode 100644
index 0000000..2b38a4e
--- /dev/null
+++ b/src/layout/components/AppMain.vue
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
diff --git a/src/layout/components/IframeToggle/index.vue b/src/layout/components/IframeToggle/index.vue
new file mode 100644
index 0000000..2de529d
--- /dev/null
+++ b/src/layout/components/IframeToggle/index.vue
@@ -0,0 +1,28 @@
+
+
+
+
+
diff --git a/src/layout/components/InnerLink/index.vue b/src/layout/components/InnerLink/index.vue
new file mode 100644
index 0000000..bb6b89b
--- /dev/null
+++ b/src/layout/components/InnerLink/index.vue
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
diff --git a/src/layout/components/Navbar.vue b/src/layout/components/Navbar.vue
new file mode 100644
index 0000000..aec0566
--- /dev/null
+++ b/src/layout/components/Navbar.vue
@@ -0,0 +1,297 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/layout/components/Settings/index.vue b/src/layout/components/Settings/index.vue
new file mode 100644
index 0000000..3082beb
--- /dev/null
+++ b/src/layout/components/Settings/index.vue
@@ -0,0 +1,238 @@
+
+
+ 主题风格设置
+
+
+
+

+
+
+
+

+
+
+
+
+ 主题颜色
+
+
+
+
+
+ 深色模式
+
+
+
+
+
+
+
+ 系统布局配置
+
+
+ 开启 TopNav
+
+
+
+
+
+
+ 开启 Tags-Views
+
+
+
+
+
+
+ 固定 Header
+
+
+
+
+
+
+ 显示 Logo
+
+
+
+
+
+
+ 动态标题
+
+
+
+
+
+
+
+ 保存配置
+ 重置配置
+
+
+
+
+
+
diff --git a/src/layout/components/Sidebar/Link.vue b/src/layout/components/Sidebar/Link.vue
new file mode 100644
index 0000000..fd75f35
--- /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..49cfdf2
--- /dev/null
+++ b/src/layout/components/Sidebar/Logo.vue
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
diff --git a/src/layout/components/Sidebar/SidebarItem.vue b/src/layout/components/Sidebar/SidebarItem.vue
new file mode 100644
index 0000000..3720062
--- /dev/null
+++ b/src/layout/components/Sidebar/SidebarItem.vue
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/layout/components/Sidebar/index.vue b/src/layout/components/Sidebar/index.vue
new file mode 100644
index 0000000..a4f20d0
--- /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..eac66bc
--- /dev/null
+++ b/src/layout/components/SocialCallback/index.vue
@@ -0,0 +1,84 @@
+
+
+
+
+
diff --git a/src/layout/components/TagsView/ScrollPane.vue b/src/layout/components/TagsView/ScrollPane.vue
new file mode 100644
index 0000000..3b30043
--- /dev/null
+++ b/src/layout/components/TagsView/ScrollPane.vue
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/layout/components/TagsView/index.vue b/src/layout/components/TagsView/index.vue
new file mode 100644
index 0000000..1e12ca4
--- /dev/null
+++ b/src/layout/components/TagsView/index.vue
@@ -0,0 +1,341 @@
+
+
+
+
+ {{ tag.title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/layout/components/TopBar/search.vue b/src/layout/components/TopBar/search.vue
new file mode 100644
index 0000000..440b88d
--- /dev/null
+++ b/src/layout/components/TopBar/search.vue
@@ -0,0 +1,157 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.title }}
+
+
+
+
+
+
+
+
+
+
+
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..e12d33f
--- /dev/null
+++ b/src/layout/components/notice/index.vue
@@ -0,0 +1,131 @@
+
+
+
+
+
+
+
+
{{ v.message }}
+
+
{{ v.time }}
+
+
+
已读
+
未读
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/layout/index.vue b/src/layout/index.vue
new file mode 100644
index 0000000..8fe5554
--- /dev/null
+++ b/src/layout/index.vue
@@ -0,0 +1,130 @@
+
+
+
+
+
+
+
diff --git a/src/main.ts b/src/main.ts
new file mode 100644
index 0000000..439d82c
--- /dev/null
+++ b/src/main.ts
@@ -0,0 +1,38 @@
+import { createApp } from 'vue';
+// global css
+import 'virtual: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
+
+// svg图标
+import 'virtual:svg-icons-register';
+import ElementIcons from '@/plugins/svgicon';
+
+// permission control
+import './permission';
+
+// 国际化
+import i18n from '@/lang/index';
+
+const app = createApp(App);
+
+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..ef9976a
--- /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);
+ /* has token*/
+ if (to.path === '/login') {
+ next({ path: '/' });
+ NProgress.done();
+ } else if (whiteList.indexOf(to.path as string) !== -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({ path: to.path, replace: true, params: to.params, query: to.query, hash: to.hash, name: to.name as string }); // hack方法 确保addRoutes已完成
+ }
+ } else {
+ next();
+ }
+ }
+ } else {
+ // 没有token
+ if (whiteList.indexOf(to.path as string) !== -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..6c5e0c3
--- /dev/null
+++ b/src/plugins/index.ts
@@ -0,0 +1,43 @@
+import modal from './modal';
+import tab from './tab';
+import download from './download';
+import cache from './cache';
+import auth from './auth';
+// 预设动画
+import animate from '@/animate';
+
+import { download as dl } from '@/utils/request';
+import { useDict } from '@/utils/dict';
+import { getConfigKey, updateConfigByKey } from '@/api/system/config';
+import { parseTime, addDateRange, handleTree, selectDictLabel, selectDictLabels } from '@/utils/ruoyi';
+
+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;
+
+ // 全局方法挂载
+ app.config.globalProperties.useDict = useDict;
+ app.config.globalProperties.getConfigKey = getConfigKey;
+ app.config.globalProperties.updateConfigByKey = updateConfigByKey;
+ app.config.globalProperties.download = dl;
+ 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;
+}
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..dd240cd
--- /dev/null
+++ b/src/plugins/tab.ts
@@ -0,0 +1,96 @@
+import router from '@/router';
+import { RouteLocationMatched, RouteLocationNormalized } from 'vue-router';
+import useTagsViewStore from '@/store/modules/tagsView';
+
+export default {
+ /**
+ * 刷新当前tab页签
+ * @param obj 标签对象
+ */
+ async refreshPage(obj?: RouteLocationNormalized): Promise {
+ const { path, query, matched } = router.currentRoute.value;
+ if (obj === undefined) {
+ matched.forEach((m: RouteLocationMatched) => {
+ 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,
+ matched: undefined,
+ fullPath: undefined,
+ hash: undefined,
+ params: undefined,
+ redirectedFrom: undefined,
+ meta: undefined
+ };
+ }
+ }
+ });
+ }
+ 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: RouteLocationNormalized): void {
+ useTagsViewStore().delView(router.currentRoute.value);
+ if (obj !== undefined) {
+ router.push(obj);
+ }
+ },
+ // 关闭指定tab页签
+ async closePage(obj?: RouteLocationNormalized): Promise<{ visitedViews: RouteLocationNormalized[]; cachedViews: string[] } | any> {
+ if (obj === undefined) {
+ // prettier-ignore
+ const { visitedViews } = await useTagsViewStore().delView(router.currentRoute.value)
+ 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?: RouteLocationNormalized) {
+ return useTagsViewStore().delLeftTags(obj || router.currentRoute.value);
+ },
+ // 关闭右侧tab页签
+ closeRightPage(obj?: RouteLocationNormalized) {
+ return useTagsViewStore().delRightTags(obj || router.currentRoute.value);
+ },
+ // 关闭其他tab页签
+ closeOtherPage(obj?: RouteLocationNormalized) {
+ 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: RouteLocationNormalized) {
+ return useTagsViewStore().updateVisitedView(obj);
+ }
+};
diff --git a/src/router/index.ts b/src/router/index.ts
new file mode 100644
index 0000000..0792d8e
--- /dev/null
+++ b/src/router/index.ts
@@ -0,0 +1,184 @@
+import { createWebHistory, createRouter, RouteRecordRaw } 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: RouteRecordRaw[] = [
+ {
+ 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: RouteRecordRaw[] = [
+ {
+ 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..4b62c75
--- /dev/null
+++ b/src/settings.ts
@@ -0,0 +1,62 @@
+import { LanguageEnum } from '@/enums/LanguageEnum';
+
+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,
+ language: LanguageEnum.zh_CN,
+
+ size: 'default',
+
+ layout: ''
+};
+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..0205fab
--- /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<'large' | 'default' | 'small'>('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: 'large' | 'default' | 'small'): 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..de980b6
--- /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: any) => {
+ 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..cf12a73
--- /dev/null
+++ b/src/store/modules/permission.ts
@@ -0,0 +1,170 @@
+import { defineStore } from 'pinia';
+import router, { constantRoutes, dynamicRoutes } from '@/router';
+import store from '@/store';
+import { getRouters } from '@/api/menu';
+import auth from '@/plugins/auth';
+import { RouteRecordRaw } from 'vue-router';
+
+import Layout from '@/layout/index.vue';
+import ParentView from '@/components/ParentView/index.vue';
+import InnerLink from '@/layout/components/InnerLink/index.vue';
+
+// 匹配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 getRoutes = (): RouteRecordRaw[] => {
+ return routes.value;
+ };
+ const getSidebarRoutes = (): RouteRecordRaw[] => {
+ return sidebarRouters.value;
+ };
+ const getTopbarRoutes = (): RouteRecordRaw[] => {
+ return topbarRouters.value;
+ };
+
+ const setRoutes = (newRoutes: RouteRecordRaw[]): void => {
+ addRoutes.value = newRoutes;
+ routes.value = constantRoutes.concat(newRoutes);
+ };
+ const setDefaultRoutes = (routes: RouteRecordRaw[]): void => {
+ defaultRoutes.value = constantRoutes.concat(routes);
+ };
+ const setTopbarRoutes = (routes: RouteRecordRaw[]): void => {
+ topbarRouters.value = routes;
+ };
+ const setSidebarRouters = (routes: RouteRecordRaw[]): 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: RouteRecordRaw[], lastRouter?: RouteRecordRaw, type = false): RouteRecordRaw[] => {
+ return asyncRouterMap.filter((route) => {
+ if (type && route.children) {
+ route.children = filterChildren(route.children, undefined);
+ }
+ // Layout ParentView 组件特殊处理
+ if (route.component?.toString() === 'Layout') {
+ route.component = Layout;
+ } else if (route.component?.toString() === 'ParentView') {
+ route.component = ParentView;
+ } else if (route.component?.toString() === '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: RouteRecordRaw[], lastRouter?: RouteRecordRaw): RouteRecordRaw[] => {
+ let children: RouteRecordRaw[] = [];
+ childrenMap.forEach((el) => {
+ if (el.children && el.children.length) {
+ if (el.component?.toString() === '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,
+ topbarRouters,
+ sidebarRouters,
+ defaultRoutes,
+
+ getRoutes,
+ getSidebarRoutes,
+ getTopbarRoutes,
+
+ setRoutes,
+ generateRoutes,
+ setSidebarRouters
+ };
+});
+
+// 动态路由遍历,验证是否具备权限
+export const filterDynamicRoutes = (routes: RouteRecordRaw[]) => {
+ const res: RouteRecordRaw[] = [];
+ 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..ef49b3c
--- /dev/null
+++ b/src/store/modules/settings.ts
@@ -0,0 +1,47 @@
+import { defineStore } from 'pinia';
+import defaultSettings from '@/settings';
+import { useDynamicTitle } from '@/utils/dynamicTitle';
+
+export const useSettingsStore = defineStore('setting', () => {
+ const storageSetting = useStorage('layout-setting', {
+ topNav: defaultSettings.topNav,
+ tagsView: defaultSettings.tagsView,
+ fixedHeader: defaultSettings.fixedHeader,
+ sidebarLogo: defaultSettings.sidebarLogo,
+ dynamicTitle: defaultSettings.dynamicTitle,
+ sideTheme: defaultSettings.sideTheme,
+ theme: defaultSettings.theme
+ });
+ const title = ref(defaultSettings.title);
+ const theme = ref(storageSetting.value.theme);
+ const sideTheme = ref(storageSetting.value.sideTheme);
+ const showSettings = ref(defaultSettings.showSettings);
+ const topNav = ref(storageSetting.value.topNav);
+ const tagsView = ref(storageSetting.value.tagsView);
+ const fixedHeader = ref(storageSetting.value.fixedHeader);
+ const sidebarLogo = ref(storageSetting.value.sidebarLogo);
+ const dynamicTitle = ref(storageSetting.value.dynamicTitle);
+ const animationEnable = ref(defaultSettings.animationEnable);
+ const dark = ref(defaultSettings.dark);
+
+ const setTitle = (value: string) => {
+ title.value = value;
+ useDynamicTitle();
+ };
+ return {
+ title,
+ theme,
+ sideTheme,
+ showSettings,
+ topNav,
+ tagsView,
+ fixedHeader,
+ sidebarLogo,
+ dynamicTitle,
+ animationEnable,
+ dark,
+ setTitle
+ };
+});
+
+export default useSettingsStore;
diff --git a/src/store/modules/tagsView.ts b/src/store/modules/tagsView.ts
new file mode 100644
index 0000000..b9502eb
--- /dev/null
+++ b/src/store/modules/tagsView.ts
@@ -0,0 +1,233 @@
+import { RouteLocationNormalized } from 'vue-router';
+
+export const useTagsViewStore = defineStore('tagsView', () => {
+ const visitedViews = ref([]);
+ const cachedViews = ref([]);
+ const iframeViews = ref([]);
+
+ const getVisitedViews = (): RouteLocationNormalized[] => {
+ return visitedViews.value;
+ };
+ const getIframeViews = (): RouteLocationNormalized[] => {
+ return iframeViews.value;
+ };
+ const getCachedViews = (): string[] => {
+ return cachedViews.value;
+ };
+
+ const addView = (view: RouteLocationNormalized) => {
+ addVisitedView(view);
+ addCachedView(view);
+ };
+
+ const addIframeView = (view: RouteLocationNormalized): void => {
+ if (iframeViews.value.some((v: RouteLocationNormalized) => v.path === view.path)) return;
+ iframeViews.value.push(
+ Object.assign({}, view, {
+ title: view.meta?.title || 'no-name'
+ })
+ );
+ };
+ const delIframeView = (view: RouteLocationNormalized): Promise => {
+ return new Promise((resolve) => {
+ iframeViews.value = iframeViews.value.filter((item: RouteLocationNormalized) => item.path !== view.path);
+ resolve([...iframeViews.value]);
+ });
+ };
+ const addVisitedView = (view: RouteLocationNormalized): void => {
+ if (visitedViews.value.some((v: RouteLocationNormalized) => v.path === view.path)) return;
+ visitedViews.value.push(
+ Object.assign({}, view, {
+ title: view.meta?.title || 'no-name'
+ })
+ );
+ };
+ const delView = (
+ view: RouteLocationNormalized
+ ): Promise<{
+ visitedViews: RouteLocationNormalized[];
+ cachedViews: string[];
+ }> => {
+ return new Promise((resolve) => {
+ delVisitedView(view);
+ if (!isDynamicRoute(view)) {
+ delCachedView(view);
+ }
+ resolve({
+ visitedViews: [...visitedViews.value],
+ cachedViews: [...cachedViews.value]
+ });
+ });
+ };
+
+ const delVisitedView = (view: RouteLocationNormalized): 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?: RouteLocationNormalized): 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: RouteLocationNormalized
+ ): Promise<{
+ visitedViews: RouteLocationNormalized[];
+ cachedViews: string[];
+ }> => {
+ return new Promise((resolve) => {
+ delOthersVisitedViews(view);
+ delOthersCachedViews(view);
+ resolve({
+ visitedViews: [...visitedViews.value],
+ cachedViews: [...cachedViews.value]
+ });
+ });
+ };
+
+ const delOthersVisitedViews = (view: RouteLocationNormalized): Promise => {
+ return new Promise((resolve) => {
+ visitedViews.value = visitedViews.value.filter((v: RouteLocationNormalized) => {
+ return v.meta?.affix || v.path === view.path;
+ });
+ resolve([...visitedViews.value]);
+ });
+ };
+ const delOthersCachedViews = (view: RouteLocationNormalized): 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: RouteLocationNormalized[]; 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: RouteLocationNormalized) => tag.meta?.affix);
+ resolve([...visitedViews.value]);
+ });
+ };
+
+ const delAllCachedViews = (): Promise => {
+ return new Promise((resolve) => {
+ cachedViews.value = [];
+ resolve([...cachedViews.value]);
+ });
+ };
+
+ const updateVisitedView = (view: RouteLocationNormalized): void => {
+ for (let v of visitedViews.value) {
+ if (v.path === view.path) {
+ v = Object.assign(v, view);
+ break;
+ }
+ }
+ };
+ const delRightTags = (view: RouteLocationNormalized): Promise => {
+ return new Promise((resolve) => {
+ const index = visitedViews.value.findIndex((v: RouteLocationNormalized) => v.path === view.path);
+ if (index === -1) {
+ return;
+ }
+ visitedViews.value = visitedViews.value.filter((item: RouteLocationNormalized, idx: number) => {
+ 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: RouteLocationNormalized): Promise => {
+ return new Promise((resolve) => {
+ const index = visitedViews.value.findIndex((v: RouteLocationNormalized) => v.path === view.path);
+ if (index === -1) {
+ return;
+ }
+ visitedViews.value = visitedViews.value.filter((item: RouteLocationNormalized, idx: number) => {
+ 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: RouteLocationNormalized): 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: RouteLocationNormalized): boolean => {
+ // 检查匹配的路由记录中是否有动态段
+ return view.matched.some((m) => m.path.includes(':'));
+ };
+
+ return {
+ visitedViews,
+ cachedViews,
+ iframeViews,
+
+ getVisitedViews,
+ getIframeViews,
+ getCachedViews,
+
+ 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..4122294
--- /dev/null
+++ b/src/store/modules/user.ts
@@ -0,0 +1,88 @@
+import { to } from 'await-to-js';
+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';
+import defAva from '@/assets/images/profile.jpg';
+import store from '@/store';
+
+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..9f2c6d2
--- /dev/null
+++ b/src/types/axios.d.ts
@@ -0,0 +1,9 @@
+export {};
+declare module 'axios' {
+ 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..9c544e4
--- /dev/null
+++ b/src/types/element.d.ts
@@ -0,0 +1,35 @@
+import type * as ep from 'element-plus';
+declare global {
+ declare type ElTagType = 'success' | 'info' | 'warning' | 'danger' | '';
+ declare type ElFormInstance = ep.FormInstance;
+ declare type ElTableInstance = ep.TableInstance;
+ declare type ElUploadInstance = ep.UploadInstance;
+ declare type ElScrollbarInstance = ep.ScrollbarInstance;
+ declare type ElInputInstance = ep.InputInstance;
+ declare type ElInputNumberInstance = ep.InputNumberInstance;
+ declare type ElRadioInstance = ep.RadioInstance;
+ declare type ElRadioGroupInstance = ep.RadioGroupInstance;
+ declare type ElRadioButtonInstance = ep.RadioButtonInstance;
+ declare type ElCheckboxInstance = ep.CheckboxInstance;
+ declare type ElSwitchInstance = ep.SwitchInstance;
+ declare type ElCascaderInstance = ep.CascaderInstance;
+ declare type ElColorPickerInstance = ep.ColorPickerInstance;
+ declare type ElRateInstance = ep.RateInstance;
+ declare type ElSliderInstance = ep.SliderInstance;
+
+ declare type ElTreeInstance = InstanceType;
+ declare type ElTreeSelectInstance = InstanceType;
+ declare type ElSelectInstance = InstanceType;
+ declare type ElCardInstance = InstanceType;
+ declare type ElDialogInstance = InstanceType;
+ declare type ElCheckboxGroupInstance = InstanceType;
+ declare type ElDatePickerInstance = InstanceType;
+ declare type ElTimePickerInstance = InstanceType;
+ declare type ElTimeSelectInstance = InstanceType;
+
+ declare type TransferKey = ep.TransferKey;
+ declare type CheckboxValueType = ep.CheckboxValueType;
+ declare type ElFormRules = ep.FormRules;
+ declare type DateModelType = ep.DateModelType;
+ declare type UploadFile = ep.UploadFile;
+}
diff --git a/src/types/env.d.ts b/src/types/env.d.ts
new file mode 100644
index 0000000..6667d05
--- /dev/null
+++ b/src/types/env.d.ts
@@ -0,0 +1,25 @@
+declare module '*.vue' {
+ import { DefineComponent } from 'vue';
+ const Component: DefineComponent<{}, {}, any>;
+ export default Component;
+}
+
+// 环境变量
+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..7c1cb3b
--- /dev/null
+++ b/src/types/global.d.ts
@@ -0,0 +1,167 @@
+import type { PropType as VuePropType, ComponentInternalInstance as ComponentInstance } from 'vue';
+import { LanguageEnum } from '@/enums/LanguageEnum';
+
+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;
+
+ updateSupport: number;
+
+ /** 其他参数 */
+ [key: string]: any;
+ }
+ /**
+ * 字典数据 数据配置
+ */
+ declare interface DictDataOption {
+ label: string;
+ value: string;
+ elTagType?: ElTagType;
+ elTagClass?: string;
+ }
+
+ declare interface BaseEntity {
+ /** 乐观锁 */
+ version?: number;
+ createBy?: 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;
+ }
+ declare interface LayoutSetting {
+ /**
+ * 是否显示顶部导航
+ */
+ topNav: boolean;
+
+ /**
+ * 是否显示多标签导航
+ */
+ tagsView: boolean;
+ /**
+ * 是否固定头部
+ */
+ fixedHeader: boolean;
+ /**
+ * 是否显示侧边栏Logo
+ */
+ sidebarLogo: boolean;
+ /**
+ * 是否显示动态标题
+ */
+ dynamicTitle: boolean;
+ /**
+ * 侧边栏主题 theme-dark | theme-light
+ */
+ sideTheme: string;
+ /**
+ * 主题模式
+ */
+ theme: string;
+ }
+
+ declare interface DefaultSettings extends LayoutSetting {
+ /**
+ * 网页标题
+ */
+ title: string;
+
+ /**
+ * 是否显示系统布局设置
+ */
+ showSettings: boolean;
+
+ /**
+ * 导航栏布局
+ */
+ layout: string;
+
+ /**
+ * 布局大小
+ */
+ size: 'large' | 'default' | 'small';
+
+ /**
+ * 语言
+ */
+ language: LanguageEnum;
+
+ /**
+ * 是否启用动画效果
+ */
+ animationEnable: boolean;
+ /**
+ * 是否启用暗黑模式
+ *
+ * true:暗黑模式
+ * false: 明亮模式
+ */
+ dark: boolean;
+
+ errorLog: string;
+ }
+}
+export {};
diff --git a/src/types/module.d.ts b/src/types/module.d.ts
new file mode 100644
index 0000000..984df9f
--- /dev/null
+++ b/src/types/module.d.ts
@@ -0,0 +1,39 @@
+import modal from '@/plugins/modal';
+import tab from '@/plugins/tab';
+import download from '@/plugins/download';
+import auth from '@/plugins/auth';
+import cache from '@/plugins/cache';
+import animate from '@/animate';
+import { useDict } from '@/utils/dict';
+import { handleTree, addDateRange, selectDictLabel, selectDictLabels, parseTime } from '@/utils/ruoyi';
+import { getConfigKey, updateConfigByKey } from '@/api/system/config';
+import { download as rd } from '@/utils/request';
+
+export {};
+
+declare module 'vue' {
+ 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;
+ }
+}
+
+declare module 'vform3-builds' {
+ const content: any;
+ export = content;
+}
diff --git a/src/types/router.d.ts b/src/types/router.d.ts
new file mode 100644
index 0000000..11a60a0
--- /dev/null
+++ b/src/types/router.d.ts
@@ -0,0 +1,38 @@
+import { LocationQuery, type RouteMeta as VRouteMeta } from 'vue-router';
+declare module 'vue-router' {
+ interface RouteMeta extends VRouteMeta {
+ link?: string;
+ title?: string;
+ affix?: boolean;
+ noCache?: boolean;
+ activeMenu?: string;
+ icon?: string;
+ breadcrumb?: boolean;
+ }
+
+ interface _RouteRecordBase {
+ hidden?: boolean | string | number;
+ permissions?: string[];
+ roles?: string[];
+ alwaysShow?: boolean;
+ query?: string;
+ parentPath?: string;
+ }
+
+ interface _RouteLocationBase {
+ children?: _RouteRecordBase[];
+ path?: string;
+ title?: string;
+ }
+
+ interface TagView {
+ fullPath?: string;
+ name?: string;
+ path?: string;
+ title?: string;
+ meta?: RouteMeta;
+ query?: LocationQuery;
+ }
+}
+
+export {};
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..2b0aad5
--- /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: any[] = [];
+ 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..24d861d
--- /dev/null
+++ b/src/utils/propTypes.ts
@@ -0,0 +1,26 @@
+import { CSSProperties } from 'vue';
+import VueTypes, { createTypes, toValidableType, VueTypeValidableDef, VueTypesInterface } from 'vue-types';
+
+type PropTypes = VueTypesInterface & {
+ readonly style: VueTypeValidableDef;
+ readonly fieldOption: 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..3de2076
--- /dev/null
+++ b/src/utils/request.ts
@@ -0,0 +1,197 @@
+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) => {
+ 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) {
+ 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..8efd12c
--- /dev/null
+++ b/src/utils/ruoyi.ts
@@ -0,0 +1,251 @@
+// 日期格式化
+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: Array = [];
+ 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';
+};
+
+export default {
+ handleTree
+};
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..3abf3d0
--- /dev/null
+++ b/src/utils/websocket.ts
@@ -0,0 +1,139 @@
+/**
+ * @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 { ElNotification } from 'element-plus';
+import useNoticeStore from '@/store/modules/notice';
+
+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;
+ }
+ useNoticeStore().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..1430a54
--- /dev/null
+++ b/src/views/demo/demo/index.vue
@@ -0,0 +1,254 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+ 新增
+
+
+ 修改
+
+
+ 删除
+
+
+ 导出
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/demo/tree/index.vue b/src/views/demo/tree/index.vue
new file mode 100644
index 0000000..5dfda6c
--- /dev/null
+++ b/src/views/demo/tree/index.vue
@@ -0,0 +1,258 @@
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+ 新增
+
+
+ 展开/折叠
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/error/401.vue b/src/views/error/401.vue
new file mode 100644
index 0000000..968550c
--- /dev/null
+++ b/src/views/error/401.vue
@@ -0,0 +1,76 @@
+
+
+
返回
+
+
+ 401错误!
+ 您没有访问权限!
+ 对不起,您没有访问权限,请不要进行非法操作!您可以返回主页面
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/error/404.vue b/src/views/error/404.vue
new file mode 100644
index 0000000..b9a9ea3
--- /dev/null
+++ b/src/views/error/404.vue
@@ -0,0 +1,223 @@
+
+
+
+
+
+
404错误!
+
+ {{ message }}
+
+
+ 对不起,您正在寻找的页面不存在。尝试检查URL的错误,然后按浏览器上的刷新按钮或尝试在我们的应用程序中找到其他内容。
+
+
返回首页
+
+
+
+
+
+
+
+
diff --git a/src/views/index.vue b/src/views/index.vue
new file mode 100644
index 0000000..446fd62
--- /dev/null
+++ b/src/views/index.vue
@@ -0,0 +1,357 @@
+
+
+
+
+
+
+
+ Ruoyi-Flex V5.0.0
+ Ruoyi-Flex是基于JDK21、Spring Boot V3.2.X+平台 前后端分离的Java快速开发框架
+
+
+
+
+ Ruoyi-Flex后台管理框架
+
+ Ruoyi-Flex是基于RuoYi-Vue、RuoYi-Vue-Plus进行的扩展,集成MyBatis-Flex、JDK21、SpringBootV3.2.X+、Lombok、Sa-Token、Hutool、SpringBoot Admin、PowerJob、Vue3、element-plus、MinIO等优秀开源软件,准备作为未来5年软件开发的底座。本系统可以用于所有的Web应用程序,如网站管理后台、网站会员中心、CMS、CRM、OA、ERP等等,当然,您也可以对她进行深度定制,以做出更强系统。所有前端后台代码封装过后十分精简易上手,出错概率低。同时支持移动客户端访问。系统会陆续更新一些实用功能。
+
+
+ 当前版本: v{{ version }}
+
+
+ ¥0.00 免费开源
+
+
+ 访问码云
+
+
+
+
+
+
+
+ 技术选型
+
+
+
+
+ 后端技术
+
+ - JDK21、SpringBoot V3
+ - Sa-Token
+ - JWT
+ - MyBatis-Flex、MyBatis
+ - Lombok
+ - ...
+
+
+
+ 前端技术
+
+ - Vue3
+ - Vuex
+ - Element-Plus
+ - Axios
+ - Sass
+ - Quill
+ - ...
+
+
+
+
+
+
+
+
+
+
+
+ 联系信息
+
+
+
+
+ 官网:https://gitee.com/dataprince/ruoyi-flex
+
+
+ QQ群:100956531
+
+
+ 微信:数据小王子
+
+
+ 支付宝:数据小王子
+
+
+
+
+
+
+
+
+ 更新日志
+
+
+
+
+
+ - 增加“租户套餐管理”、“租户管理”模块
+ - 实现多租户功能
+ - 实现乐观锁功能
+ - 实现逻辑删除功能
+ - 启用JAVA21虚拟线程功能
+ - 启用JAVA21分代ZGC功能
+ - Ruoyi-FlexV5的PosgtgreSQL、MySQL数据库完整脚本
+ - 修正部门排序字段错误
+ - 升级spring-boot依赖到V3.2.1
+ - 升级mybatis-flex依赖到1.7.7,去掉mybatis-spring依赖
+ - 代码生成模块支持多租户、乐观锁、逻辑删除
+ - 增加"客户端管理"模块
+ - 完善“ruoyi-common-tenant“模块
+ - 增加“ruoyi-common-encrypt”加密模块
+ - postgresql创建与mysql等效的的find_in_set函数
+ - 同步ruoyi-vue-plus的2023-11-17至2023-12-23的更新
+ - 简化代码,对于QueryWrapper的操作,不再重复判断条件
+ - 修改yml文件,大部分配置代码放到参数文件中
+
+
+
+
+ - 更新jdk到21
+ - 升级springboot到V3.2.0
+ - 前后端仓库分离
+ - 修正文件管理两个bug:不显示“上传人”的问题、图片无法预览问题
+ - 修改数据库名称:由ry-vue修改为ruoyi-flex
+ - sys_menu菜单表结构修改、去掉主键自增
+ - 重构menu菜单模块,去掉xml中的sql语句
+ - 代码生成更新:生成的sql文件去掉主键自增
+ - 代码生成模块数据库表去掉主键自增
+ - 代码生成模块重构
+ - 升级依赖Redisson到V3.25.1,改进 JDK21 虚拟线程兼容性
+ - 修改mysql数据库表的del_flag字段为smallint类型
+ - 修改delFlag属性为Integer类型
+ - 为方便入门,数据库登录用户、密码不再加密!
+ - 增加多数据源演示(学生信息表的服务selectPage方法),默认关闭
+ - 支持PostgreSQL数据库
+
+
+
+
+ - 注册全局数据填充监听器
+ - 使用mybatis-flex重构ruoyi-vue的各模块mybatis代码
+ - 新增保姆级开发文档:《Ruoyi-Flex-Guide.docx》
+ - 新增基础服务类IBaseService及实现
+ - 升级依赖版本:spring boot升级到V3.1.5,mybatis-flex升级到V1.7.5,sa-token升级到V1.37.0,powerjob升级到V4.3.6
+ - 数据库表结构取消自增主键,使用雪花算法
+ - 升级用户导入:加入部门名称
+ - 数据权限重构,去掉ruoyi-vue的注解写法
+ - 升级前端,状态管理由vuex切换到pinia
+ - 添加“文件管理”模块:将文件存储到MinIO、七牛、阿里、腾讯等OSS服务器上。
+ - 重构“代码生成”模块,支持mybatis-flex,能够生成单表、树表、主子表的代码
+ - 演示模块添加mybatis、mybatis-flex两种格式代码的单表、树表、主子表三种类型的演示程序
+ - 同步ruoyi-vue-plus的2023-10-25至2023-11-14的更新
+ 1)update 优化 排除powerjob无用的依赖 减少打包30M体积
+ 2)fix 修复 代码生成 是否必填与数据库不匹配问题
+ 3)update 优化 补全操作日志部门数据
+ 4)update 优化 AddressUtils 兼容linux系统本地ip
+ 5)fix 修复 普通角色编辑使用内置管理员code越权问题
+ 6)update 优化 补全代码生成 columnList 接口参数注解缺失
+ 7)fix 修复 外链带端口出现的异常
+ 8)update 优化 更改默认日志等级为info 避免日志过多(按需开启debug)
+
+ - 同步RuoYi-Vue的2023-10-24到2023-12-5的更新:
+ 1)update ruoyi-ui/src/permission.js
+ 2)优化数字金额大写转换精度丢失问题
+ 3)修复字典表详情页面搜索bug
+ 4)修复五级路由缓存无效问题
+ 5)优化缓存监控图表支持跟随屏幕大小自适应调整
+ 6)update fastjson2
+ 7)升级oshi到最新版本6.4.8
+
+
+
+
+
+ - 升级mybatis-flex到V1.6.0版本
+ - 删除不再使用的quartz前端代码
+ - 添加微信VIP交流群
+ - 修改bug
+ - 前端代码从vue2升级到vue3
+ - 升级Element UI到Element-Plus
+
+
+
+
+ - 移除ruoyi-quartz模块
+ - 整合PowerJob分布式任务调度与计算框架
+ - PowerJob接入SpringBoot Admin监控
+
+
+
+
+ - 删除原服务监控代码文件
+ - 集成SpringBoot Admin监控软件
+ - SpringBoot Admin集成undertow
+
+
+
+
+ - 优化登录提示信息
+ - 调整项目结构
+ - 集成Sa-Token、Lombok、Hutool等软件
+ - SpringDoc模块与Sa-Token集成
+
+
+
+
+ - 集成SpringDoc,代替springfox
+ - 将“系统接口”菜单设置为外链
+ - 升级MyBatis-Flex到V1.5.3
+
+
+
+
+ - web容器使用undertow来代替tomcat
+ - 用hikariCP数据库连接池取代druid、用MyBaits-Flex内置多数据源取代dynamic-datasource动态数据源
+ - 统一删除标记:del_flag设置为0代表存在、1代表删除,涉及部门、用户、角色三个模块
+ - 重构ruoyi-common模块为ruoyi-common-core
+ - 优化代码
+ - 升级MyBatis-Flex到V1.5.0
+
+
+
+
+ - 优化代码
+ - 完美兼容RuoYi-Vue代码生成,生成的单表、树表、主子表mybatis代码运行正常
+ - 解决generator.yml中文乱码问题
+ - 使用flatten-maven-plugin插件集中管理软件版本号
+ - 同步RuoYi-Vue 2023-07-06更新
+ - 参考RuoYi-Cloud,增加ruoyi-modules模块,调整项目结构
+
+
+
+
+ - 全局设置数据库表主键为雪花算法
+ - 全局设置数据库逻辑删除数值:0代表正常,1代表删除
+ - 从jdk8升级到jdk17、SpringBootV2升级到V3.0.8
+
+
+
+
+ - Ruoyi-Flex前后端分离系统v4正式发布
+ - 基于RuoYi-Vue v3.8.6进行了扩展
+ - 引入mybatis-flex框架,与mybatis和平共处
+ - maven仓库从阿里切换到华为
+
+
+
+
+
+
+
+
+ 捐赠支持
+
+
+

+
你可以请作者喝杯咖啡表示鼓励
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/login.vue b/src/views/login.vue
new file mode 100644
index 0000000..cbdc97c
--- /dev/null
+++ b/src/views/login.vue
@@ -0,0 +1,289 @@
+
+
+
+ RuoYi-Flex多租户管理系统
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
![]()
+
+
+ 记住密码
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 登 录
+ 登 录 中...
+
+
+ 立即注册
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/mf/product/index.vue b/src/views/mf/product/index.vue
new file mode 100644
index 0000000..25f251f
--- /dev/null
+++ b/src/views/mf/product/index.vue
@@ -0,0 +1,289 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+ 新增
+
+
+ 展开/折叠
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{dict.label}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/mf/student/index.vue b/src/views/mf/student/index.vue
new file mode 100644
index 0000000..94b7aec
--- /dev/null
+++ b/src/views/mf/student/index.vue
@@ -0,0 +1,312 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+ 新增
+
+
+ 修改
+
+
+ 删除
+
+
+ 导出
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.studentBirthday, '{y}-{m}-{d}') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{dict.label}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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..81bbf2d
--- /dev/null
+++ b/src/views/monitor/cache/index.vue
@@ -0,0 +1,192 @@
+
+
+
+
+
+
+
+ 基本信息
+
+
+
+
+
+
+
+ Redis版本
+ |
+
+ {{ cache.info.redis_version }}
+ |
+
+ 运行模式
+ |
+
+ {{ cache.info.redis_mode === 'standalone' ? '单机' : '集群' }}
+ |
+
+ 端口
+ |
+
+ {{ cache.info.tcp_port }}
+ |
+
+ 客户端数
+ |
+
+ {{ cache.info.connected_clients }}
+ |
+
+
+
+ 运行时间(天)
+ |
+
+ {{ cache.info.uptime_in_days }}
+ |
+
+ 使用内存
+ |
+
+ {{ cache.info.used_memory_human }}
+ |
+
+ 使用CPU
+ |
+
+ {{ parseFloat(cache.info.used_cpu_user_children).toFixed(2) }}
+ |
+
+ 内存配置
+ |
+
+ {{ cache.info.maxmemory_human }}
+ |
+
+
+
+ AOF是否开启
+ |
+
+ {{ cache.info.aof_enabled === '0' ? '否' : '是' }}
+ |
+
+ RDB是否成功
+ |
+
+ {{ cache.info.rdb_last_bgsave_status }}
+ |
+
+ Key数量
+ |
+
+ {{ cache.dbSize }}
+ |
+
+ 网络入口/出口
+ |
+
+
+ {{ cache.info.instantaneous_input_kbps }}kps/{{ cache.info.instantaneous_output_kbps }}kps
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+ 命令统计
+
+
+
+
+
+
+
+
+ 内存信息
+
+
+
+
+
+
+
+
+
diff --git a/src/views/monitor/logininfor/index.vue b/src/views/monitor/logininfor/index.vue
new file mode 100644
index 0000000..656a7bc
--- /dev/null
+++ b/src/views/monitor/logininfor/index.vue
@@ -0,0 +1,208 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+ 删除
+
+
+
+ 清空
+
+
+
+ 解锁
+
+
+
+ 导出
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.loginTime) }}
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/monitor/online/index.vue b/src/views/monitor/online/index.vue
new file mode 100644
index 0000000..27de613
--- /dev/null
+++ b/src/views/monitor/online/index.vue
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+ {{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.loginTime) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/monitor/operlog/index.vue b/src/views/monitor/operlog/index.vue
new file mode 100644
index 0000000..4316fbf
--- /dev/null
+++ b/src/views/monitor/operlog/index.vue
@@ -0,0 +1,305 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+ 删除
+
+
+
+ 清空
+
+
+ 导出
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.operTime) }}
+
+
+
+
+ {{ scope.row.costTime }}毫秒
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ form.operName }} / {{ form.deptName }} / {{ form.operIp }} / {{ form.operLocation }}
+
+
+ {{ form.requestMethod }} {{ form.operUrl }}
+
+
+ {{ form.title }} / {{ typeFormat(form) }}
+
+
+ {{ form.method }}
+
+
+ {{ form.operParam }}
+
+
+ {{ form.jsonResult }}
+
+
+
+ 正常
+ 失败
+
+
+
+ {{ form.costTime }}毫秒
+
+
+ {{ parseTime(form.operTime) }}
+
+
+ {{ form.errorMsg }}
+
+
+
+
+
+
+
+
+
+
+
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..97a6556
--- /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..7f078c1
--- /dev/null
+++ b/src/views/register.vue
@@ -0,0 +1,229 @@
+
+
+
+ RuoYi-Vue-Plus多租户管理系统
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
![]()
+
+
+
+
+ 注 册
+ 注 册 中...
+
+
+ 使用已有账户登录
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/client/index.vue b/src/views/system/client/index.vue
new file mode 100644
index 0000000..c94f673
--- /dev/null
+++ b/src/views/system/client/index.vue
@@ -0,0 +1,336 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+ 新增
+
+
+
+
+ 修改
+
+
+
+
+ 删除
+
+
+
+
+ 导出
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Token活跃超时时间
+
+
+
+
+
+
+
+
+
+
+ Token固定超时时间
+
+
+
+
+
+
+
+ {{ dict.label }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/config/index.vue b/src/views/system/config/index.vue
new file mode 100644
index 0000000..dbab774
--- /dev/null
+++ b/src/views/system/config/index.vue
@@ -0,0 +1,262 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+ 新增
+
+
+
+ 修改
+
+
+
+
+ 删除
+
+
+
+ 导出
+
+
+ 刷新缓存
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.createTime) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ dict.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/dept/index.vue b/src/views/system/dept/index.vue
new file mode 100644
index 0000000..7c5ef5f
--- /dev/null
+++ b/src/views/system/dept/index.vue
@@ -0,0 +1,309 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+ 新增
+
+
+ 展开/折叠
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.createTime) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ dict.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/dict/data.vue b/src/views/system/dict/data.vue
new file mode 100644
index 0000000..0307019
--- /dev/null
+++ b/src/views/system/dict/data.vue
@@ -0,0 +1,310 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+ 新增
+
+
+ 修改
+
+
+
+ 删除
+
+
+
+ 导出
+
+
+ 关闭
+
+
+
+
+
+
+
+
+
+
+ {{ scope.row.dictLabel }}
+ {{ scope.row.dictLabel }}
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.createTime) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/dict/index.vue b/src/views/system/dict/index.vue
new file mode 100644
index 0000000..cb703b8
--- /dev/null
+++ b/src/views/system/dict/index.vue
@@ -0,0 +1,247 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+ 新增
+
+
+ 修改
+
+
+
+ 删除
+
+
+
+ 导出
+
+
+ 刷新缓存
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ scope.row.dictType }}
+
+
+
+
+
+
+ {{ parseTime(scope.row.createTime) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/menu/index.vue b/src/views/system/menu/index.vue
new file mode 100644
index 0000000..fd2cfcc
--- /dev/null
+++ b/src/views/system/menu/index.vue
@@ -0,0 +1,410 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+ 新增
+
+
+ 展开/折叠
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ scope.row.createTime }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 目录
+ 菜单
+ 按钮
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 是否外链
+
+
+
+ 是
+ 否
+
+
+
+
+
+
+
+
+
+
+
+
+ 路由地址
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 组件路径
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 权限字符
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 路由参数
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 是否缓存
+
+
+
+ 缓存
+ 不缓存
+
+
+
+
+
+
+
+
+
+
+
+
+ 显示状态
+
+
+
+ {{ dict.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+ 菜单状态
+
+
+
+
+ {{ dict.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/notice/index.vue b/src/views/system/notice/index.vue
new file mode 100644
index 0000000..638d3f8
--- /dev/null
+++ b/src/views/system/notice/index.vue
@@ -0,0 +1,244 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+ 新增
+
+
+ 修改
+
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ dict.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/oss/config.vue b/src/views/system/oss/config.vue
new file mode 100644
index 0000000..e4fb8b4
--- /dev/null
+++ b/src/views/system/oss/config.vue
@@ -0,0 +1,339 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+ 新增
+
+
+ 修改
+
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ private
+ public
+ custom
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ dict.label }}
+
+
+
+
+ private
+ public
+ custom
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/oss/index.vue b/src/views/system/oss/index.vue
new file mode 100644
index 0000000..8a67a50
--- /dev/null
+++ b/src/views/system/oss/index.vue
@@ -0,0 +1,357 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+ 上传文件
+
+
+
+
+ 上传图片
+
+
+
+
+ 删除
+
+
+
+ 预览开关 : {{ previewListResource ? '禁用' : '启用' }}
+
+
+
+
+ 配置管理
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/post/index.vue b/src/views/system/post/index.vue
new file mode 100644
index 0000000..a039b8d
--- /dev/null
+++ b/src/views/system/post/index.vue
@@ -0,0 +1,242 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+ 新增
+
+
+ 修改
+
+
+
+ 删除
+
+
+
+ 导出
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.createTime) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ dict.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/role/authUser.vue b/src/views/system/role/authUser.vue
new file mode 100644
index 0000000..9ab11a4
--- /dev/null
+++ b/src/views/system/role/authUser.vue
@@ -0,0 +1,160 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+ 添加用户
+
+
+
+ 批量取消授权
+
+
+
+ 关闭
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ scope.row.createTime }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/role/index.vue b/src/views/system/role/index.vue
new file mode 100644
index 0000000..e65c041
--- /dev/null
+++ b/src/views/system/role/index.vue
@@ -0,0 +1,508 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+ 新增
+
+
+ 修改
+
+
+ 删除
+
+
+ 导出
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.createTime) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 权限字符
+
+
+
+
+
+
+
+
+
+ {{ dict.label }}
+
+
+
+ 展开/折叠
+ 全选/全不选
+ 父子联动
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 展开/折叠
+ 全选/全不选
+ 父子联动
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/role/selectUser.vue b/src/views/system/role/selectUser.vue
new file mode 100644
index 0000000..ec7a257
--- /dev/null
+++ b/src/views/system/role/selectUser.vue
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.createTime) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/tenant/index.vue b/src/views/system/tenant/index.vue
new file mode 100644
index 0000000..a04ac9a
--- /dev/null
+++ b/src/views/system/tenant/index.vue
@@ -0,0 +1,352 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+ 新增
+
+
+ 修改
+
+
+
+ 删除
+
+
+
+ 导出
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.expireTime, '{y}-{m}-{d}') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/tenantPackage/index.vue b/src/views/system/tenantPackage/index.vue
new file mode 100644
index 0000000..d06eff0
--- /dev/null
+++ b/src/views/system/tenantPackage/index.vue
@@ -0,0 +1,341 @@
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+ 新增
+
+
+
+ 修改
+
+
+
+
+ 删除
+
+
+
+ 导出
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 展开/折叠
+ 全选/全不选
+ 父子联动
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/user/authRole.vue b/src/views/system/user/authRole.vue
new file mode 100644
index 0000000..cf71609
--- /dev/null
+++ b/src/views/system/user/authRole.vue
@@ -0,0 +1,139 @@
+
+
+
+
基本信息
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
角色信息
+
+
+
+
+ {{ (pageNum - 1) * pageSize + scope.$index + 1 }}
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.createTime) }}
+
+
+
+
+
+ 提交
+ 返回
+
+
+
+
+
+
+
+
diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue
new file mode 100644
index 0000000..0cff2ae
--- /dev/null
+++ b/src/views/system/user/index.vue
@@ -0,0 +1,701 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+ 新增
+
+
+
+
+ 修改
+
+
+
+
+ 删除
+
+
+
+
+
+ 更多
+
+
+
+
+
+
+ 下载模板
+ 导入数据
+ 导出数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ scope.row.createTime }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ dict.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 将文件拖到此处,或点击上传
+
+
+
+
+ 是否更新已经存在的用户数据
+
+
仅允许导入xls、xlsx格式文件。
+
下载模板
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/user/profile/index.vue b/src/views/system/user/profile/index.vue
new file mode 100644
index 0000000..0c8b527
--- /dev/null
+++ b/src/views/system/user/profile/index.vue
@@ -0,0 +1,110 @@
+
+
+
+
+
+
+
+ 个人信息
+
+
+
+
+
+
+
+ -
+ 用户名称
+
{{ state.user.userName }}
+
+ -
+ 手机号码
+
{{ state.user.phonenumber }}
+
+ -
+ 用户邮箱
+
{{ state.user.email }}
+
+ -
+ 所属部门
+
{{ state.user.deptName }} / {{ state.postGroup }}
+
+ -
+ 所属角色
+
{{ state.roleGroup }}
+
+ -
+ 创建日期
+
{{ state.user.createTime }}
+
+
+
+
+
+
+
+
+
+ 基本资料
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/user/profile/resetPwd.vue b/src/views/system/user/profile/resetPwd.vue
new file mode 100644
index 0000000..0a39fb1
--- /dev/null
+++ b/src/views/system/user/profile/resetPwd.vue
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ 保存
+ 关闭
+
+
+
+
+
diff --git a/src/views/system/user/profile/thirdParty.vue b/src/views/system/user/profile/thirdParty.vue
new file mode 100644
index 0000000..4a77d65
--- /dev/null
+++ b/src/views/system/user/profile/thirdParty.vue
@@ -0,0 +1,143 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 解绑
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/user/profile/userAvatar.vue b/src/views/system/user/profile/userAvatar.vue
new file mode 100644
index 0000000..32b6f5c
--- /dev/null
+++ b/src/views/system/user/profile/userAvatar.vue
@@ -0,0 +1,182 @@
+
+
+
![点击上传头像]()
+
+
+
+
+
+
+
+
![]()
+
+
+
+
+
+
+
+
+ 选择
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 提 交
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/user/profile/userInfo.vue b/src/views/system/user/profile/userInfo.vue
new file mode 100644
index 0000000..289f8d4
--- /dev/null
+++ b/src/views/system/user/profile/userInfo.vue
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 男
+ 女
+
+
+
+ 保存
+ 关闭
+
+
+
+
+
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 @@
+
+ 表单构建(由于此功能的开源组件不支持 VUE3+TS 故暂时无法使用)
+
diff --git a/src/views/tool/gen/basicInfoForm.vue b/src/views/tool/gen/basicInfoForm.vue
new file mode 100644
index 0000000..5412088
--- /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..2175a9c
--- /dev/null
+++ b/src/views/tool/gen/editTable.vue
@@ -0,0 +1,198 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ dict.dictName }}
+ {{ dict.dictType }}
+
+
+
+
+
+
+
+
+
+
+
+
+ 提交
+ 返回
+
+
+
+
+
+
diff --git a/src/views/tool/gen/genInfoForm.vue b/src/views/tool/gen/genInfoForm.vue
new file mode 100644
index 0000000..a8d7e13
--- /dev/null
+++ b/src/views/tool/gen/genInfoForm.vue
@@ -0,0 +1,295 @@
+
+
+
+
+
+ 生成模板
+
+
+
+
+
+
+
+
+
+
+ 生成包路径
+
+
+
+
+
+
+
+
+
+
+
+ 生成模块名
+
+
+
+
+
+
+
+
+
+
+
+ 生成业务名
+
+
+
+
+
+
+
+
+
+
+
+ 生成功能名
+
+
+
+
+
+
+
+
+
+
+
+ 上级菜单
+
+
+
+
+
+
+
+
+
+
+
+ 生成代码方式
+
+
+
+
+ zip压缩包
+ 自定义路径
+
+
+
+
+
+
+ 自定义路径
+
+
+
+
+
+
+
+
+ 最近路径快速选择
+
+
+
+
+ 恢复默认的生成基础路径
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 树编码字段
+
+
+
+
+
+
+
+
+
+
+
+
+ 树父编码字段
+
+
+
+
+
+
+
+
+
+
+
+
+ 树名称字段
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 关联子表的表名
+
+
+
+
+
+
+
+
+
+
+
+
+ 子表关联的外键名
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/tool/gen/importTable.vue b/src/views/tool/gen/importTable.vue
new file mode 100644
index 0000000..8bf7c8e
--- /dev/null
+++ b/src/views/tool/gen/importTable.vue
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/tool/gen/index.vue b/src/views/tool/gen/index.vue
new file mode 100644
index 0000000..ac2cded
--- /dev/null
+++ b/src/views/tool/gen/index.vue
@@ -0,0 +1,238 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+ 生成
+
+
+ 导入
+
+
+ 修改
+
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+ {{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 复制
+
+ {{ value }}
+
+
+
+
+
+
+
+
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..e4bf3ac
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,34 @@
+{
+ "compilerOptions": {
+ "target": "esnext",
+ "module": "esnext",
+// "useDefineForClassFields": true,
+ "moduleResolution": "bundler",
+ "strict": true,
+ "jsx": "preserve",
+ "strictNullChecks": false,
+ "sourceMap": true,
+ "resolveJsonModule": true,
+ "esModuleInterop": true,
+ "strictFunctionTypes": false,
+ "lib": ["esnext", "dom"],
+ "noImplicitAny": false,
+ "baseUrl": ".",
+ "allowJs": true,
+ "experimentalDecorators": true,
+ "paths": {
+ "@/*": ["src/*"]
+ },
+ "compilerOptions": {
+ "types": ["element-plus/global"]
+ },
+ "types": ["vite/client"],
+ "skipLibCheck": true,
+ "removeComments": true,
+ // 允许默认导入
+ "allowSyntheticDefaultImports": true,
+ "forceConsistentCasingInFileNames": true
+ },
+ "include": ["src/**/*.ts", "src/**/*.vue", "src/types/**/*.d.ts", "vite.config.ts"],
+ "exclude": ["node_modules", "dist", "**/*.js", "**/*.md", "src/**/*.md"]
+}
diff --git a/uno.config.ts b/uno.config.ts
new file mode 100644
index 0000000..0c60a22
--- /dev/null
+++ b/uno.config.ts
@@ -0,0 +1,33 @@
+import {
+ defineConfig,
+ presetAttributify,
+ presetIcons,
+ presetTypography,
+ presetUno,
+ presetWebFonts,
+ transformerDirectives,
+ transformerVariantGroup
+} from 'unocss';
+
+export default defineConfig({
+ 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'
+ },
+ theme: {
+ colors: {
+ primary: 'var(--el-color-primary)',
+ primary_dark: 'var(--el-color-primary-light-5)'
+ }
+ },
+ presets: [
+ presetUno(),
+ presetAttributify(),
+ presetIcons(),
+ presetTypography(),
+ presetWebFonts({
+ fonts: {}
+ })
+ ],
+ transformers: [transformerDirectives(), transformerVariantGroup()]
+});
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..01da668
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,115 @@
+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')
+ },
+ 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',
+ 'vue-i18n',
+ '@vueup/vue-quill',
+
+ '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..aa8c779
--- /dev/null
+++ b/vite/plugins/compression.ts
@@ -0,0 +1,28 @@
+import compression from 'vite-plugin-compression';
+
+export default (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/i18n.ts b/vite/plugins/i18n.ts
new file mode 100644
index 0000000..8777d1a
--- /dev/null
+++ b/vite/plugins/i18n.ts
@@ -0,0 +1,6 @@
+import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite';
+export default (path: any) => {
+ return VueI18nPlugin({
+ include: [path.resolve(__dirname, '../../src/lang/**.json')]
+ });
+};
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..0ec5b8d
--- /dev/null
+++ b/vite/plugins/index.ts
@@ -0,0 +1,24 @@
+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 createI18n from './i18n';
+import path from 'path';
+
+export default (viteEnv: any, isBuild = false): [] => {
+ const vitePlugins: any = [];
+ vitePlugins.push(vue());
+ vitePlugins.push(createUnoCss());
+ vitePlugins.push(createAutoImport(path));
+ vitePlugins.push(createComponents(path));
+ vitePlugins.push(createCompression(viteEnv));
+ vitePlugins.push(createIcons());
+ vitePlugins.push(createSvgIconsPlugin(path, isBuild));
+ vitePlugins.push(createSetupExtend());
+ vitePlugins.push(createI18n(path));
+ return vitePlugins;
+};
diff --git a/vite/plugins/setup-extend.ts b/vite/plugins/setup-extend.ts
new file mode 100644
index 0000000..ed3423f
--- /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..08e186b
--- /dev/null
+++ b/vite/plugins/unocss.ts
@@ -0,0 +1,7 @@
+import UnoCss from 'unocss/vite';
+
+export default () => {
+ return UnoCss({
+ hmrTopLevelAwait: false // unocss默认是true,低版本浏览器是不支持的,启动后会报错
+ });
+};