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..14e1335
--- /dev/null
+++ b/.env.development
@@ -0,0 +1,35 @@
+# 页面标题
+VITE_APP_TITLE = RuoYi-Vue-Plus多租户管理系统
+
+# 开发环境配置
+VITE_APP_ENV = 'development'
+
+# 开发环境
+VITE_APP_BASE_API = '/dev-api'
+
+# 应用访问路径 例如使用前缀 /admin/
+VITE_APP_CONTEXT_PATH = '/'
+
+# 监控地址
+VITE_APP_MONITOR_ADMIN = 'http://localhost:9090/admin/applications'
+
+# SnailJob 控制台地址
+VITE_APP_SNAILJOB_ADMIN = 'http://localhost:8800/snail-job'
+
+VITE_APP_PORT = 80
+
+# 接口加密功能开关(如需关闭 后端也必须对应关闭)
+VITE_APP_ENCRYPT = true
+# 接口加密传输 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 开关 默认使用sse推送
+VITE_APP_WEBSOCKET = false
+
+# sse 开关
+VITE_APP_SSE = true
diff --git a/.env.production b/.env.production
new file mode 100644
index 0000000..1109bc6
--- /dev/null
+++ b/.env.production
@@ -0,0 +1,38 @@
+# 页面标题
+VITE_APP_TITLE = RuoYi-Vue-Plus多租户管理系统
+
+# 生产环境配置
+VITE_APP_ENV = 'production'
+
+# 应用访问路径 例如使用前缀 /admin/
+VITE_APP_CONTEXT_PATH = '/'
+
+# 监控地址
+VITE_APP_MONITOR_ADMIN = '/admin/applications'
+
+# SnailJob 控制台地址
+VITE_APP_SNAILJOB_ADMIN = '/snail-job'
+
+# 生产环境
+VITE_APP_BASE_API = '/prod-api'
+
+# 是否在打包时开启压缩,支持 gzip 和 brotli
+VITE_BUILD_COMPRESS = gzip
+
+VITE_APP_PORT = 80
+
+# 接口加密功能开关(如需关闭 后端也必须对应关闭)
+VITE_APP_ENCRYPT = true
+# 接口加密传输 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 开关 默认使用sse推送
+VITE_APP_WEBSOCKET = false
+
+# sse 开关
+VITE_APP_SSE = true
diff --git a/.eslintrc-auto-import.json b/.eslintrc-auto-import.json
new file mode 100644
index 0000000..cb7058a
--- /dev/null
+++ b/.eslintrc-auto-import.json
@@ -0,0 +1,323 @@
+{
+ "globals": {
+ "Component": true,
+ "ComponentPublicInstance": true,
+ "ComputedRef": true,
+ "DirectiveBinding": true,
+ "EffectScope": true,
+ "ElLoading": true,
+ "ElMessage": true,
+ "ElMessageBox": true,
+ "ElNotification": true,
+ "ExtractDefaultPropTypes": true,
+ "ExtractPropTypes": true,
+ "ExtractPublicPropTypes": true,
+ "InjectionKey": true,
+ "MaybeRef": true,
+ "MaybeRefOrGetter": true,
+ "PropType": true,
+ "Ref": true,
+ "VNode": true,
+ "WritableComputedRef": true,
+ "acceptHMRUpdate": 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,
+ "createPinia": true,
+ "createReactiveFn": true,
+ "createReusableTemplate": true,
+ "createSharedComposable": true,
+ "createTemplatePromise": true,
+ "createUnrefFn": true,
+ "customRef": true,
+ "debouncedRef": true,
+ "debouncedWatch": true,
+ "defineAsyncComponent": true,
+ "defineComponent": true,
+ "defineStore": true,
+ "eagerComputed": true,
+ "effectScope": true,
+ "extendRef": true,
+ "getActivePinia": true,
+ "getCurrentInstance": true,
+ "getCurrentScope": true,
+ "h": true,
+ "ignorableWatch": true,
+ "inject": true,
+ "injectLocal": true,
+ "isDefined": true,
+ "isProxy": true,
+ "isReactive": true,
+ "isReadonly": true,
+ "isRef": true,
+ "makeDestructurable": true,
+ "mapActions": true,
+ "mapGetters": true,
+ "mapState": true,
+ "mapStores": true,
+ "mapWritableState": true,
+ "markRaw": true,
+ "nextTick": true,
+ "onActivated": true,
+ "onBeforeMount": true,
+ "onBeforeRouteLeave": true,
+ "onBeforeRouteUpdate": true,
+ "onBeforeUnmount": true,
+ "onBeforeUpdate": true,
+ "onClickOutside": true,
+ "onDeactivated": true,
+ "onElementRemoval": true,
+ "onErrorCaptured": true,
+ "onKeyStroke": true,
+ "onLongPress": true,
+ "onMounted": true,
+ "onRenderTracked": true,
+ "onRenderTriggered": true,
+ "onScopeDispose": true,
+ "onServerPrefetch": true,
+ "onStartTyping": true,
+ "onUnmounted": true,
+ "onUpdated": true,
+ "onWatcherCleanup": true,
+ "pausableWatch": true,
+ "provide": true,
+ "provideLocal": true,
+ "reactify": true,
+ "reactifyObject": true,
+ "reactive": true,
+ "reactiveComputed": true,
+ "reactiveOmit": true,
+ "reactivePick": true,
+ "readonly": true,
+ "ref": true,
+ "refAutoReset": true,
+ "refDebounced": true,
+ "refDefault": true,
+ "refThrottled": true,
+ "refWithControl": true,
+ "resolveComponent": true,
+ "resolveRef": true,
+ "resolveUnref": true,
+ "setActivePinia": true,
+ "setMapStoreSuffix": true,
+ "shallowReactive": true,
+ "shallowReadonly": true,
+ "shallowRef": true,
+ "storeToRefs": true,
+ "syncRef": true,
+ "syncRefs": true,
+ "templateRef": true,
+ "throttledRef": true,
+ "throttledWatch": true,
+ "toRaw": true,
+ "toReactive": true,
+ "toRef": true,
+ "toRefs": true,
+ "toValue": true,
+ "triggerRef": true,
+ "tryOnBeforeMount": true,
+ "tryOnBeforeUnmount": true,
+ "tryOnMounted": true,
+ "tryOnScopeDispose": true,
+ "tryOnUnmounted": true,
+ "unref": true,
+ "unrefElement": true,
+ "until": true,
+ "useActiveElement": true,
+ "useAnimate": true,
+ "useArrayDifference": true,
+ "useArrayEvery": true,
+ "useArrayFilter": true,
+ "useArrayFind": true,
+ "useArrayFindIndex": true,
+ "useArrayFindLast": true,
+ "useArrayIncludes": true,
+ "useArrayJoin": true,
+ "useArrayMap": true,
+ "useArrayReduce": true,
+ "useArraySome": true,
+ "useArrayUnique": true,
+ "useAsyncQueue": true,
+ "useAsyncState": true,
+ "useAttrs": true,
+ "useBase64": true,
+ "useBattery": true,
+ "useBluetooth": true,
+ "useBreakpoints": true,
+ "useBroadcastChannel": true,
+ "useBrowserLocation": true,
+ "useCached": true,
+ "useClipboard": true,
+ "useClipboardItems": true,
+ "useCloned": true,
+ "useColorMode": true,
+ "useConfirmDialog": true,
+ "useCountdown": 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,
+ "useId": true,
+ "useIdle": true,
+ "useImage": true,
+ "useInfiniteScroll": true,
+ "useIntersectionObserver": true,
+ "useInterval": true,
+ "useIntervalFn": true,
+ "useKeyModifier": true,
+ "useLastChanged": true,
+ "useLink": true,
+ "useLocalStorage": true,
+ "useMagicKeys": true,
+ "useManualRefHistory": true,
+ "useMediaControls": true,
+ "useMediaQuery": true,
+ "useMemoize": true,
+ "useMemory": true,
+ "useModel": true,
+ "useMounted": true,
+ "useMouse": true,
+ "useMouseInElement": true,
+ "useMousePressed": true,
+ "useMutationObserver": true,
+ "useNavigatorLanguage": true,
+ "useNetwork": true,
+ "useNow": true,
+ "useObjectUrl": true,
+ "useOffsetPagination": true,
+ "useOnline": true,
+ "usePageLeave": true,
+ "useParallax": true,
+ "useParentElement": true,
+ "usePerformanceObserver": true,
+ "usePermission": true,
+ "usePointer": true,
+ "usePointerLock": true,
+ "usePointerSwipe": true,
+ "usePreferredColorScheme": true,
+ "usePreferredContrast": true,
+ "usePreferredDark": true,
+ "usePreferredLanguages": true,
+ "usePreferredReducedMotion": true,
+ "usePreferredReducedTransparency": true,
+ "usePrevious": true,
+ "useRafFn": true,
+ "useRefHistory": true,
+ "useResizeObserver": true,
+ "useRoute": true,
+ "useRouter": true,
+ "useSSRWidth": 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,
+ "useTemplateRef": true,
+ "useTemplateRefsList": true,
+ "useTextDirection": true,
+ "useTextSelection": true,
+ "useTextareaAutosize": true,
+ "useThrottle": true,
+ "useThrottleFn": true,
+ "useThrottledRefHistory": true,
+ "useTimeAgo": true,
+ "useTimeout": true,
+ "useTimeoutFn": true,
+ "useTimeoutPoll": true,
+ "useTimestamp": true,
+ "useTitle": true,
+ "useToNumber": true,
+ "useToString": true,
+ "useToggle": true,
+ "useTransition": true,
+ "useUrlSearchParams": true,
+ "useUserMedia": true,
+ "useVModel": true,
+ "useVModels": true,
+ "useVibrate": true,
+ "useVirtualList": true,
+ "useWakeLock": true,
+ "useWebNotification": true,
+ "useWebSocket": true,
+ "useWebWorker": true,
+ "useWebWorkerFn": true,
+ "useWindowFocus": true,
+ "useWindowScroll": true,
+ "useWindowSize": true,
+ "watch": true,
+ "watchArray": true,
+ "watchAtMost": true,
+ "watchDebounced": true,
+ "watchDeep": true,
+ "watchEffect": true,
+ "watchIgnorable": true,
+ "watchImmediate": true,
+ "watchOnce": true,
+ "watchPausable": true,
+ "watchPostEffect": true,
+ "watchSyncEffect": true,
+ "watchThrottled": true,
+ "watchTriggerable": true,
+ "watchWithFilter": true,
+ "whenever": true,
+ "Slot": true,
+ "Slots": true,
+ "createRef": true
+ }
+}
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..b6b1ecf
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,10 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
+# 已忽略包含查询文件的默认文件夹
+/queries/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
+# 基于编辑器的 HTTP 客户端请求
+/httpRequests/
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..5d4eddb
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..79ee123
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..03d9549
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/intc-single-ultra-web.iml b/.idea/intc-single-ultra-web.iml
new file mode 100644
index 0000000..c956989
--- /dev/null
+++ b/.idea/intc-single-ultra-web.iml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..1c3e895
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/prettier.xml b/.idea/prettier.xml
new file mode 100644
index 0000000..b0c1c68
--- /dev/null
+++ b/.idea/prettier.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 0000000..1ac928b
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,4 @@
+
+
+ {}
+
\ No newline at end of file
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/LICENSE b/LICENSE
index 0a512a6..32b3071 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,73 +1,20 @@
-Apache License
-Version 2.0, January 2004
-http://www.apache.org/licenses/
+The MIT License (MIT)
-TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+Copyright (c) 2019 RuoYi-Vue-Plus
-1. Definitions.
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
-"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
-"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
-
-"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
-
-"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
-
-"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
-
-"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
-
-"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
-
-"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
-
-"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
-
-"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
-
-2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
-
-3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
-
-4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
-
- (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
-
- You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
-
-5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
-
-6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
-
-7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
-
-8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
-
-9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
-
-END OF TERMS AND CONDITIONS
-
-APPENDIX: How to apply the Apache License to your work.
-
-To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.
-
-Copyright 2025 qdintc
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
index aa3501e..1c89bf9 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,83 @@
-# intc-single-ultra-web
+## 平台简介
+- 本仓库为前端技术栈 [Vue3](https://v3.cn.vuejs.org) + [TS](https://www.typescriptlang.org/) + [Element Plus](https://element-plus.org/zh-CN) + [Vite](https://cn.vitejs.dev) 版本。
+- 成员项目: 基于 vben5(ant-design-vue) 的前端项目 [ruoyi-plus-vben5](https://gitee.com/dapppp/ruoyi-plus-vben5)
+- 成员项目: 基于soybean 的前端项目 [ruoyi-plus-soybean](https://gitee.com/xlsea/ruoyi-plus-soybean)
+
+## 配套后端代码仓库地址
+
+| 介绍 | 项目名 | 项目地址 |
+|------------|:-----------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| 🔥 分布式集群框架 | RuoYi-Vue-Plus | - [Gitee](https://gitee.com/dromara/RuoYi-Vue-Plus) - [GitHub](https://github.com/dromara/RuoYi-Vue-Plus) - [GitCode](https://gitcode.com/dromara/RuoYi-Vue-Plus) |
+| 🔥 微服务框架 | RuoYi-Cloud-Plus | - [Gitee](https://gitee.com/dromara/RuoYi-Cloud-Plus) - [GitHub](https://github.com/dromara/RuoYi-Cloud-Plus) - [GitCode](https://gitcode.com/dromara/RuoYi-Cloud-Plus) |
+
+## 分支说明
+
+- ts分支(稳定发布主分支 生产可用)
+- dev分支(开发分支 开发过程中使用)
+
+## 前端运行
+
+```bash
+# 安装依赖
+npm install --registry=https://registry.npmmirror.com
+
+# 启动服务
+npm run dev
+
+# 构建生产环境
+npm run build:prod
+
+# 前端访问地址 http://localhost:80
+```
+
+## 本框架与RuoYi的业务差异
+
+| 业务 | 功能说明 | 本框架 | RuoYi |
+| ------------ | ------------------------------------------------------------- | ------ | ----------------------------- |
+| 租户管理 | 系统内租户的管理 如:租户套餐、过期时间、用户数量、企业信息等 | 支持 | 无 |
+| 租户套餐管理 | 系统内租户所能使用的套餐管理 如:套餐内所包含的菜单等 | 支持 | 无 |
+| 用户管理 | 用户的管理配置 如:新增用户、分配用户所属部门、角色、岗位等 | 支持 | 支持 |
+| 部门管理 | 配置系统组织机构(公司、部门、小组) 树结构展现支持数据权限 | 支持 | 支持 |
+| 岗位管理 | 配置系统用户所属担任职务 | 支持 | 支持 |
+| 菜单管理 | 配置系统菜单、操作权限、按钮权限标识等 | 支持 | 支持 |
+| 角色管理 | 角色菜单权限分配、设置角色按机构进行数据范围权限划分 | 支持 | 支持 |
+| 字典管理 | 对系统中经常使用的一些较为固定的数据进行维护 | 支持 | 支持 |
+| 参数管理 | 对系统动态配置常用参数 | 支持 | 支持 |
+| 通知公告 | 系统通知公告信息发布维护 | 支持 | 支持 |
+| 操作日志 | 系统正常操作日志记录和查询 系统异常信息日志记录和查询 | 支持 | 支持 |
+| 登录日志 | 系统登录日志记录查询包含登录异常 | 支持 | 支持 |
+| 文件管理 | 系统文件展示、上传、下载、删除等管理 | 支持 | 无 |
+| 文件配置管理 | 系统文件上传、下载所需要的配置信息动态添加、修改、删除等管理 | 支持 | 无 |
+| 在线用户管理 | 已登录系统的在线用户信息监控与强制踢出操作 | 支持 | 支持 |
+| 定时任务 | 运行报表、任务管理(添加、修改、删除)、日志管理、执行器管理等 | 支持 | 仅支持任务与日志管理 |
+| 代码生成 | 多数据源前后端代码的生成(java、html、xml、sql)支持CRUD下载 | 支持 | 仅支持单数据源 |
+| 系统接口 | 根据业务代码自动生成相关的api接口文档 | 支持 | 支持 |
+| 服务监控 | 监视集群系统CPU、内存、磁盘、堆栈、在线日志、Spring相关配置等 | 支持 | 仅支持单机CPU、内存、磁盘监控 |
+| 缓存监控 | 对系统的缓存信息查询,命令统计等。 | 支持 | 支持 |
+| 在线构建器 | 拖动表单元素生成相应的HTML代码。 | 支持 | 支持 |
+| 使用案例 | 系统的一些功能案例 | 支持 | 不支持 |
+
+## 演示图例
+
+| | |
+| ---------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
+|  |  |
+|  |  |
+|  |  |
+|  |  |
+|  |  |
+|  |  |
+|  |  |
+|  |  |
+|  |  |
+|  |  |
+|  |  |
+|  |  |
+|  |  |
+|  |  |
+|  |  |
+|  |  |
+|  |  |
+|  |  |
+|  |  |
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/eslint.config.ts b/eslint.config.ts
new file mode 100644
index 0000000..50458d1
--- /dev/null
+++ b/eslint.config.ts
@@ -0,0 +1,44 @@
+import pluginVue from 'eslint-plugin-vue';
+import globals from 'globals';
+import prettier from 'eslint-plugin-prettier';
+import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript';
+import skipFormatting from '@vue/eslint-config-prettier/skip-formatting';
+
+export default defineConfigWithVueTs(
+ {
+ name: 'app/files-to-lint',
+ files: ['**/*.{js,cjs,ts,mts,tsx,vue}']
+ },
+
+ {
+ name: 'app/files-to-ignore',
+ ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**']
+ },
+ {
+ languageOptions: {
+ globals: globals.browser
+ }
+ },
+ pluginVue.configs['flat/essential'],
+ vueTsConfigs.recommended,
+ skipFormatting,
+ {
+ plugins: { 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',
+ // 允许使用空Object类型 {}
+ '@typescript-eslint/no-empty-object-type': 'off',
+ '@typescript-eslint/no-unused-expressions': 'off'
+ }
+ }
+);
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..7d4b418
--- /dev/null
+++ b/package.json
@@ -0,0 +1,96 @@
+{
+ "$schema": "https://json.schemastore.org/package",
+ "name": "ruoyi-vue-plus",
+ "version": "5.5.0-2.5.0",
+ "description": "RuoYi-Vue-Plus多租户管理系统",
+ "author": "LionLi",
+ "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",
+ "lint:eslint:fix": "eslint --fix",
+ "prettier": "prettier --write ."
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://gitee.com/JavaLionLi/plus-ui.git"
+ },
+ "dependencies": {
+ "@element-plus/icons-vue": "2.3.1",
+ "@highlightjs/vue-plugin": "2.1.0",
+ "@vueup/vue-quill": "1.2.0",
+ "@vueuse/core": "13.1.0",
+ "animate.css": "4.1.1",
+ "await-to-js": "3.0.0",
+ "axios": "1.8.4",
+ "crypto-js": "4.2.0",
+ "echarts": "5.6.0",
+ "element-plus": "2.9.8",
+ "file-saver": "2.0.5",
+ "highlight.js": "11.9.0",
+ "image-conversion": "2.1.1",
+ "js-cookie": "3.0.5",
+ "jsencrypt": "3.3.2",
+ "nprogress": "0.2.0",
+ "pinia": "3.0.2",
+ "screenfull": "6.0.2",
+ "vue": "3.5.13",
+ "vue-cropper": "1.1.1",
+ "vue-i18n": "11.1.3",
+ "vue-json-pretty": "2.4.0",
+ "vue-router": "4.5.0",
+ "vue-types": "6.0.0",
+ "vxe-table": "4.13.7"
+ },
+ "devDependencies": {
+ "@iconify/json": "^2.2.276",
+ "@types/crypto-js": "4.2.2",
+ "@types/file-saver": "2.0.7",
+ "@types/js-cookie": "3.0.6",
+ "@types/node": "^22.13.4",
+ "@types/nprogress": "0.2.3",
+ "@unocss/preset-attributify": "66.0.0",
+ "@unocss/preset-icons": "66.0.0",
+ "@unocss/preset-uno": "66.0.0",
+ "@vitejs/plugin-vue": "5.2.3",
+ "@vue/compiler-sfc": "3.5.13",
+ "@vue/eslint-config-prettier": "10.2.0",
+ "@vue/eslint-config-typescript": "14.4.0",
+ "autoprefixer": "10.4.20",
+ "eslint": "9.21.0",
+ "eslint-plugin-prettier": "5.2.3",
+ "eslint-plugin-vue": "9.32.0",
+ "globals": "16.0.0",
+ "prettier": "3.5.2",
+ "sass": "1.87.0",
+ "typescript": "~5.8.3",
+ "unocss": "66.0.0",
+ "unplugin-auto-import": "19.1.2",
+ "unplugin-icons": "22.1.0",
+ "unplugin-vue-components": "28.5.0",
+ "unplugin-vue-setup-extend-plus": "1.0.1",
+ "vite": "6.3.2",
+ "vite-plugin-compression": "0.5.1",
+ "vite-plugin-svg-icons-ng": "^1.4.0",
+ "vite-plugin-vue-devtools": "7.7.5",
+ "vitest": "3.1.2",
+ "vue-tsc": "^2.2.8"
+ },
+ "overrides": {
+ "quill": "2.0.2"
+ },
+ "engines": {
+ "node": ">=18.18.0",
+ "npm": ">=8.9.0"
+ },
+ "browserslist": [
+ "Chrome >= 87",
+ "Edge >= 88",
+ "Safari >= 14",
+ "Firefox >= 78"
+ ]
+}
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..3f919d8
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/src/App.vue b/src/App.vue
new file mode 100644
index 0000000..617a10e
--- /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..35e6a4e
--- /dev/null
+++ b/src/api/login.ts
@@ -0,0 +1,113 @@
+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,
+ repeatSubmit: false
+ },
+ 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,
+ repeatSubmit: false
+ },
+ method: 'post',
+ data: params
+ });
+}
+
+/**
+ * 注销
+ */
+export function logout() {
+ if (import.meta.env.VITE_APP_SSE === 'true') {
+ request({
+ url: '/resource/sse/close',
+ method: 'get'
+ });
+ }
+ 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(isToken: boolean): AxiosPromise {
+ return request({
+ url: '/auth/tenant/list',
+ headers: {
+ isToken: isToken
+ },
+ method: 'get'
+ });
+}
diff --git a/src/api/menu.ts b/src/api/menu.ts
new file mode 100644
index 0000000..a3ae80e
--- /dev/null
+++ b/src/api/menu.ts
@@ -0,0 +1,11 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { RouteRecordRaw } from 'vue-router';
+
+// 获取路由
+export function getRouters(): AxiosPromise {
+ return request({
+ url: '/system/menu/getRouters',
+ method: 'get'
+ });
+}
diff --git a/src/api/monitor/cache/index.ts b/src/api/monitor/cache/index.ts
new file mode 100644
index 0000000..e45d6fb
--- /dev/null
+++ b/src/api/monitor/cache/index.ts
@@ -0,0 +1,59 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { CacheVO } from './types';
+
+// 查询缓存详细
+export function getCache(): AxiosPromise {
+ return request({
+ url: '/monitor/cache',
+ method: 'get'
+ });
+}
+
+// 查询缓存名称列表
+export function listCacheName() {
+ return request({
+ url: '/monitor/cache/getNames',
+ method: 'get'
+ });
+}
+
+// 查询缓存键名列表
+export function listCacheKey(cacheName: string) {
+ return request({
+ url: '/monitor/cache/getKeys/' + cacheName,
+ method: 'get'
+ });
+}
+
+// 查询缓存内容
+export function getCacheValue(cacheName: string, cacheKey: string) {
+ return request({
+ url: '/monitor/cache/getValue/' + cacheName + '/' + cacheKey,
+ method: 'get'
+ });
+}
+
+// 清理指定名称缓存
+export function clearCacheName(cacheName: string) {
+ return request({
+ url: '/monitor/cache/clearCacheName/' + cacheName,
+ method: 'delete'
+ });
+}
+
+// 清理指定键名缓存
+export function clearCacheKey(cacheName: string, cacheKey: string) {
+ return request({
+ url: '/monitor/cache/clearCacheKey/' + cacheName + '/' + cacheKey,
+ method: 'delete'
+ });
+}
+
+// 清理全部缓存
+export function clearCacheAll() {
+ return request({
+ url: '/monitor/cache/clearCacheAll',
+ method: 'delete'
+ });
+}
diff --git a/src/api/monitor/cache/types.ts b/src/api/monitor/cache/types.ts
new file mode 100644
index 0000000..4017b65
--- /dev/null
+++ b/src/api/monitor/cache/types.ts
@@ -0,0 +1,7 @@
+export interface CacheVO {
+ commandStats: Array<{ name: string; value: string }>;
+
+ dbSize: number;
+
+ info: { [key: string]: string };
+}
diff --git a/src/api/monitor/loginInfo/index.ts b/src/api/monitor/loginInfo/index.ts
new file mode 100644
index 0000000..f8877c9
--- /dev/null
+++ b/src/api/monitor/loginInfo/index.ts
@@ -0,0 +1,36 @@
+import request from '@/utils/request';
+import { LoginInfoQuery, LoginInfoVO } from './types';
+import { AxiosPromise } from 'axios';
+
+// 查询登录日志列表
+export function list(query: LoginInfoQuery): AxiosPromise {
+ return request({
+ url: '/monitor/logininfor/list',
+ method: 'get',
+ params: query
+ });
+}
+
+// 删除登录日志
+export function delLoginInfo(infoId: string | number | Array) {
+ return request({
+ url: '/monitor/logininfor/' + infoId,
+ method: 'delete'
+ });
+}
+
+// 解锁用户登录状态
+export function unlockLoginInfo(userName: string | Array) {
+ return request({
+ url: '/monitor/logininfor/unlock/' + userName,
+ method: 'get'
+ });
+}
+
+// 清空登录日志
+export function cleanLoginInfo() {
+ return request({
+ url: '/monitor/logininfor/clean',
+ method: 'delete'
+ });
+}
diff --git a/src/api/monitor/loginInfo/types.ts b/src/api/monitor/loginInfo/types.ts
new file mode 100644
index 0000000..202c779
--- /dev/null
+++ b/src/api/monitor/loginInfo/types.ts
@@ -0,0 +1,20 @@
+export interface LoginInfoVO {
+ infoId: string | number;
+ tenantId: string | number;
+ userName: string;
+ status: string;
+ ipaddr: string;
+ loginLocation: string;
+ browser: string;
+ os: string;
+ msg: string;
+ loginTime: string;
+}
+
+export interface LoginInfoQuery extends PageQuery {
+ ipaddr: string;
+ userName: string;
+ status: string;
+ orderByColumn: string;
+ isAsc: string;
+}
diff --git a/src/api/monitor/online/index.ts b/src/api/monitor/online/index.ts
new file mode 100644
index 0000000..5b3221c
--- /dev/null
+++ b/src/api/monitor/online/index.ts
@@ -0,0 +1,36 @@
+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'
+ });
+}
+
+// 获取当前用户登录在线设备
+export function getOnline() {
+ return request({
+ url: '/monitor/online',
+ method: 'get'
+ });
+}
+
+// 删除当前在线设备
+export function delOnline(tokenId: string) {
+ return request({
+ url: '/monitor/online/myself/' + 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..6b302b0
--- /dev/null
+++ b/src/api/system/client/index.ts
@@ -0,0 +1,80 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { ClientVO, ClientForm, ClientQuery } from '@/api/system/client/types';
+
+/**
+ * 查询客户端管理列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listClient = (query?: ClientQuery): AxiosPromise => {
+ return request({
+ url: '/system/client/list',
+ method: 'get',
+ params: query
+ });
+};
+
+/**
+ * 查询客户端管理详细
+ * @param id
+ */
+export const getClient = (id: string | number): AxiosPromise => {
+ return request({
+ url: '/system/client/' + id,
+ method: 'get'
+ });
+};
+
+/**
+ * 新增客户端管理
+ * @param data
+ */
+export const addClient = (data: ClientForm) => {
+ return request({
+ url: '/system/client',
+ method: 'post',
+ data: data
+ });
+};
+
+/**
+ * 修改客户端管理
+ * @param data
+ */
+export const updateClient = (data: ClientForm) => {
+ return request({
+ url: '/system/client',
+ method: 'put',
+ data: data
+ });
+};
+
+/**
+ * 删除客户端管理
+ * @param id
+ */
+export const delClient = (id: string | number | Array) => {
+ return request({
+ url: '/system/client/' + id,
+ method: 'delete'
+ });
+};
+
+/**
+ * 状态修改
+ * @param clientId 客户端id
+ * @param status 状态
+ */
+export function changeStatus(clientId: string, status: string) {
+ const data = {
+ clientId,
+ 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..142118d
--- /dev/null
+++ b/src/api/system/client/types.ts
@@ -0,0 +1,135 @@
+export interface ClientVO {
+ /**
+ * id
+ */
+ id: string | number;
+
+ /**
+ * 客户端id
+ */
+ clientId: string;
+
+ /**
+ * 客户端key
+ */
+ clientKey: string;
+
+ /**
+ * 客户端秘钥
+ */
+ clientSecret: string;
+
+ /**
+ * 授权类型
+ */
+ grantTypeList: string[];
+
+ /**
+ * 设备类型
+ */
+ deviceType: string;
+
+ /**
+ * token活跃超时时间
+ */
+ activeTimeout: number;
+
+ /**
+ * token固定超时
+ */
+ timeout: number;
+
+ /**
+ * 状态(0正常 1停用)
+ */
+ status: string;
+}
+
+export interface ClientForm extends BaseEntity {
+ /**
+ * id
+ */
+ id?: string | number;
+
+ /**
+ * 客户端id
+ */
+ clientId?: string | number;
+
+ /**
+ * 客户端key
+ */
+ clientKey?: string;
+
+ /**
+ * 客户端秘钥
+ */
+ clientSecret?: string;
+
+ /**
+ * 授权类型
+ */
+ grantTypeList?: string[];
+
+ /**
+ * 设备类型
+ */
+ deviceType?: string;
+
+ /**
+ * token活跃超时时间
+ */
+ activeTimeout?: number;
+
+ /**
+ * token固定超时
+ */
+ timeout?: number;
+
+ /**
+ * 状态(0正常 1停用)
+ */
+ status?: string;
+}
+
+export interface ClientQuery extends PageQuery {
+ /**
+ * 客户端id
+ */
+ clientId?: string | number;
+
+ /**
+ * 客户端key
+ */
+ clientKey?: string;
+
+ /**
+ * 客户端秘钥
+ */
+ clientSecret?: string;
+
+ /**
+ * 授权类型
+ */
+ grantType?: string;
+
+ /**
+ * 设备类型
+ */
+ deviceType?: string;
+
+ /**
+ * token活跃超时时间
+ */
+ activeTimeout?: number;
+
+ /**
+ * token固定超时
+ */
+ timeout?: number;
+
+ /**
+ * 状态(0正常 1停用)
+ */
+ status?: string;
+}
diff --git a/src/api/system/config/index.ts b/src/api/system/config/index.ts
new file mode 100644
index 0000000..1e4842d
--- /dev/null
+++ b/src/api/system/config/index.ts
@@ -0,0 +1,74 @@
+import request from '@/utils/request';
+import { ConfigForm, ConfigQuery, ConfigVO } from './types';
+import { AxiosPromise } from 'axios';
+
+// 查询参数列表
+export function listConfig(query: ConfigQuery): AxiosPromise {
+ return request({
+ url: '/system/config/list',
+ method: 'get',
+ params: query
+ });
+}
+
+// 查询参数详细
+export function getConfig(configId: string | number): AxiosPromise {
+ return request({
+ url: '/system/config/' + configId,
+ method: 'get'
+ });
+}
+
+// 根据参数键名查询参数值
+export function getConfigKey(configKey: string): AxiosPromise {
+ return request({
+ url: '/system/config/configKey/' + configKey,
+ method: 'get'
+ });
+}
+
+// 新增参数配置
+export function addConfig(data: ConfigForm) {
+ return request({
+ url: '/system/config',
+ method: 'post',
+ data: data
+ });
+}
+
+// 修改参数配置
+export function updateConfig(data: ConfigForm) {
+ return request({
+ url: '/system/config',
+ method: 'put',
+ data: data
+ });
+}
+
+// 修改参数配置
+export function updateConfigByKey(key: string, value: any) {
+ return request({
+ url: '/system/config/updateByKey',
+ method: 'put',
+ data: {
+ configKey: key,
+ configValue: value
+ }
+ });
+}
+
+// 删除参数配置
+export function delConfig(configId: string | number | Array) {
+ return request({
+ url: '/system/config/' + configId,
+ method: 'delete'
+ });
+}
+
+// 刷新参数缓存
+export function refreshCache() {
+ return request({
+ url: '/system/config/refreshCache',
+ method: 'delete'
+ });
+}
diff --git a/src/api/system/config/types.ts b/src/api/system/config/types.ts
new file mode 100644
index 0000000..b68f073
--- /dev/null
+++ b/src/api/system/config/types.ts
@@ -0,0 +1,23 @@
+export interface ConfigVO extends BaseEntity {
+ configId: number | string;
+ configName: string;
+ configKey: string;
+ configValue: string;
+ configType: string;
+ remark: string;
+}
+
+export interface ConfigForm {
+ configId: number | string | undefined;
+ configName: string;
+ configKey: string;
+ configValue: string;
+ configType: string;
+ remark: string;
+}
+
+export interface ConfigQuery extends PageQuery {
+ configName: string;
+ configKey: string;
+ configType: string;
+}
diff --git a/src/api/system/dept/index.ts b/src/api/system/dept/index.ts
new file mode 100644
index 0000000..03c54d5
--- /dev/null
+++ b/src/api/system/dept/index.ts
@@ -0,0 +1,65 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { DeptForm, DeptQuery, DeptTreeVO, DeptVO } from './types';
+
+// 查询部门列表
+export const listDept = (query?: DeptQuery) => {
+ return request({
+ url: '/system/dept/list',
+ method: 'get',
+ params: query
+ });
+};
+
+/**
+ * 通过deptIds查询部门
+ * @param deptIds
+ */
+export const optionSelect = (deptIds: (number | string)[]): AxiosPromise => {
+ return request({
+ url: '/system/dept/optionselect?deptIds=' + deptIds,
+ method: 'get'
+ });
+};
+
+// 查询部门列表(排除节点)
+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 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..adaefd2
--- /dev/null
+++ b/src/api/system/dept/types.ts
@@ -0,0 +1,60 @@
+/**
+ * 部门查询参数
+ */
+export interface DeptQuery extends PageQuery {
+ deptName?: string;
+ deptCategory?: string;
+ status?: number;
+}
+
+/**
+ * 部门类型
+ */
+export interface DeptVO extends BaseEntity {
+ id: number | string;
+ parentName: string;
+ parentId: number | string;
+ children: DeptVO[];
+ deptId: number | string;
+ deptName: string;
+ deptCategory: string;
+ orderNum: number;
+ leader: string;
+ phone: string;
+ email: string;
+ status: string;
+ delFlag: string;
+ ancestors: string;
+ menuId: string | number;
+}
+
+/**
+ * 部门类型
+ */
+export interface DeptTreeVO extends BaseEntity {
+ id: number | string;
+ label: string;
+ parentId: number | string;
+ weight: number;
+ children: DeptTreeVO[];
+ disabled: boolean;
+}
+
+/**
+ * 部门表单类型
+ */
+export interface DeptForm {
+ parentName?: string;
+ parentId?: number | string;
+ children?: DeptForm[];
+ deptId?: number | string;
+ deptName?: string;
+ deptCategory?: string;
+ orderNum?: number;
+ leader?: string;
+ phone?: string;
+ email?: string;
+ status?: string;
+ delFlag?: string;
+ ancestors?: string;
+}
diff --git a/src/api/system/dict/data/index.ts b/src/api/system/dict/data/index.ts
new file mode 100644
index 0000000..7692abc
--- /dev/null
+++ b/src/api/system/dict/data/index.ts
@@ -0,0 +1,53 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { DictDataForm, DictDataQuery, DictDataVO } from './types';
+// 根据字典类型查询字典数据信息
+export function getDicts(dictType: string): AxiosPromise {
+ return request({
+ url: '/system/dict/data/type/' + dictType,
+ method: 'get'
+ });
+}
+
+// 查询字典数据列表
+export function listData(query: DictDataQuery): AxiosPromise {
+ return request({
+ url: '/system/dict/data/list',
+ method: 'get',
+ params: query
+ });
+}
+
+// 查询字典数据详细
+export function getData(dictCode: string | number): AxiosPromise {
+ return request({
+ url: '/system/dict/data/' + dictCode,
+ method: 'get'
+ });
+}
+
+// 新增字典数据
+export function addData(data: DictDataForm) {
+ return request({
+ url: '/system/dict/data',
+ method: 'post',
+ data: data
+ });
+}
+
+// 修改字典数据
+export function updateData(data: DictDataForm) {
+ return request({
+ url: '/system/dict/data',
+ method: 'put',
+ data: data
+ });
+}
+
+// 删除字典数据
+export function delData(dictCode: string | number | Array) {
+ return request({
+ url: '/system/dict/data/' + dictCode,
+ method: 'delete'
+ });
+}
diff --git a/src/api/system/dict/data/types.ts b/src/api/system/dict/data/types.ts
new file mode 100644
index 0000000..e4abb9b
--- /dev/null
+++ b/src/api/system/dict/data/types.ts
@@ -0,0 +1,26 @@
+export interface DictDataQuery extends PageQuery {
+ dictName: string;
+ dictType: string;
+ dictLabel: string;
+}
+
+export interface DictDataVO extends BaseEntity {
+ dictCode: string;
+ dictLabel: string;
+ dictValue: string;
+ cssClass: string;
+ listClass: ElTagType;
+ dictSort: number;
+ remark: string;
+}
+
+export interface DictDataForm {
+ dictType?: string;
+ dictCode: string | undefined;
+ dictLabel: string;
+ dictValue: string;
+ cssClass: string;
+ listClass: ElTagType;
+ dictSort: number;
+ remark: string;
+}
diff --git a/src/api/system/dict/type/index.ts b/src/api/system/dict/type/index.ts
new file mode 100644
index 0000000..7dc3d66
--- /dev/null
+++ b/src/api/system/dict/type/index.ts
@@ -0,0 +1,62 @@
+import request from '@/utils/request';
+import { DictTypeForm, DictTypeVO, DictTypeQuery } from './types';
+import { AxiosPromise } from 'axios';
+
+// 查询字典类型列表
+export function listType(query: DictTypeQuery): AxiosPromise {
+ return request({
+ url: '/system/dict/type/list',
+ method: 'get',
+ params: query
+ });
+}
+
+// 查询字典类型详细
+export function getType(dictId: number | string): AxiosPromise {
+ return request({
+ url: '/system/dict/type/' + dictId,
+ method: 'get'
+ });
+}
+
+// 新增字典类型
+export function addType(data: DictTypeForm) {
+ return request({
+ url: '/system/dict/type',
+ method: 'post',
+ data: data
+ });
+}
+
+// 修改字典类型
+export function updateType(data: DictTypeForm) {
+ return request({
+ url: '/system/dict/type',
+ method: 'put',
+ data: data
+ });
+}
+
+// 删除字典类型
+export function delType(dictId: string | number | Array) {
+ return request({
+ url: '/system/dict/type/' + dictId,
+ method: 'delete'
+ });
+}
+
+// 刷新字典缓存
+export function refreshCache() {
+ return request({
+ url: '/system/dict/type/refreshCache',
+ method: 'delete'
+ });
+}
+
+// 获取字典选择框列表
+export function optionselect(): AxiosPromise {
+ return request({
+ url: '/system/dict/type/optionselect',
+ method: 'get'
+ });
+}
diff --git a/src/api/system/dict/type/types.ts b/src/api/system/dict/type/types.ts
new file mode 100644
index 0000000..4987dbf
--- /dev/null
+++ b/src/api/system/dict/type/types.ts
@@ -0,0 +1,18 @@
+export interface DictTypeVO extends BaseEntity {
+ dictId: number | string;
+ dictName: string;
+ dictType: string;
+ remark: string;
+}
+
+export interface DictTypeForm {
+ dictId: number | string | undefined;
+ dictName: string;
+ dictType: string;
+ remark: string;
+}
+
+export interface DictTypeQuery extends PageQuery {
+ dictName: string;
+ dictType: string;
+}
diff --git a/src/api/system/menu/index.ts b/src/api/system/menu/index.ts
new file mode 100644
index 0000000..81461fd
--- /dev/null
+++ b/src/api/system/menu/index.ts
@@ -0,0 +1,78 @@
+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'
+ });
+};
+
+// 级联删除菜单
+export const cascadeDelMenu = (menuIds: Array) => {
+ return request({
+ url: '/system/menu/cascade/' + menuIds,
+ method: 'delete'
+ });
+};
diff --git a/src/api/system/menu/types.ts b/src/api/system/menu/types.ts
new file mode 100644
index 0000000..ca19840
--- /dev/null
+++ b/src/api/system/menu/types.ts
@@ -0,0 +1,69 @@
+import { MenuTypeEnum } from '@/enums/MenuTypeEnum';
+
+/**
+ * 菜单树形结构类型
+ */
+export interface MenuTreeOption {
+ id: string | number;
+ label: string;
+ parentId: string | number;
+ weight: number;
+ children?: MenuTreeOption[];
+}
+
+export interface RoleMenuTree {
+ menus: MenuTreeOption[];
+ checkedKeys: string[];
+}
+
+/**
+ * 菜单查询参数类型
+ */
+export interface MenuQuery {
+ keywords?: string;
+ menuName?: string;
+ status?: string;
+}
+
+/**
+ * 菜单视图对象类型
+ */
+export interface MenuVO extends BaseEntity {
+ parentName: string;
+ parentId: string | number;
+ children: MenuVO[];
+ menuId: string | number;
+ menuName: string;
+ orderNum: number;
+ path: string;
+ component: string;
+ queryParam: string;
+ isFrame: string;
+ isCache: string;
+ menuType: MenuTypeEnum;
+ visible: string;
+ status: string;
+ icon: string;
+ remark: string;
+}
+
+export interface MenuForm {
+ parentName?: string;
+ parentId?: string | number;
+ children?: MenuForm[];
+ menuId?: string | number;
+ menuName: string;
+ orderNum: number;
+ path: string;
+ component?: string;
+ queryParam?: string;
+ isFrame?: string;
+ isCache?: string;
+ menuType?: MenuTypeEnum;
+ visible?: string;
+ status?: string;
+ icon?: string;
+ remark?: string;
+ query?: string;
+ perms?: string;
+}
diff --git a/src/api/system/notice/index.ts b/src/api/system/notice/index.ts
new file mode 100644
index 0000000..285d1f4
--- /dev/null
+++ b/src/api/system/notice/index.ts
@@ -0,0 +1,45 @@
+import request from '@/utils/request';
+import { NoticeForm, NoticeQuery, NoticeVO } from './types';
+import { AxiosPromise } from 'axios';
+// 查询公告列表
+export function listNotice(query: NoticeQuery): AxiosPromise {
+ return request({
+ url: '/system/notice/list',
+ method: 'get',
+ params: query
+ });
+}
+
+// 查询公告详细
+export function getNotice(noticeId: string | number): AxiosPromise {
+ return request({
+ url: '/system/notice/' + noticeId,
+ method: 'get'
+ });
+}
+
+// 新增公告
+export function addNotice(data: NoticeForm) {
+ return request({
+ url: '/system/notice',
+ method: 'post',
+ data: data
+ });
+}
+
+// 修改公告
+export function updateNotice(data: NoticeForm) {
+ return request({
+ url: '/system/notice',
+ method: 'put',
+ data: data
+ });
+}
+
+// 删除公告
+export function delNotice(noticeId: string | number | Array) {
+ return request({
+ url: '/system/notice/' + noticeId,
+ method: 'delete'
+ });
+}
diff --git a/src/api/system/notice/types.ts b/src/api/system/notice/types.ts
new file mode 100644
index 0000000..abfd5b2
--- /dev/null
+++ b/src/api/system/notice/types.ts
@@ -0,0 +1,26 @@
+export interface NoticeVO extends BaseEntity {
+ noticeId: number;
+ noticeTitle: string;
+ noticeType: string;
+ noticeContent: string;
+ status: string;
+ remark: string;
+ createByName: string;
+}
+
+export interface NoticeQuery extends PageQuery {
+ noticeTitle: string;
+ createByName: string;
+ status: string;
+ noticeType: string;
+}
+
+export interface NoticeForm {
+ noticeId: number | string | undefined;
+ noticeTitle: string;
+ noticeType: string;
+ noticeContent: string;
+ status: string;
+ remark: string;
+ createByName: string;
+}
diff --git a/src/api/system/oss/index.ts b/src/api/system/oss/index.ts
new file mode 100644
index 0000000..4472112
--- /dev/null
+++ b/src/api/system/oss/index.ts
@@ -0,0 +1,28 @@
+import request from '@/utils/request';
+import { OssQuery, OssVO } from './types';
+import { AxiosPromise } from 'axios';
+
+// 查询OSS对象存储列表
+export function listOss(query: OssQuery): AxiosPromise {
+ return request({
+ url: '/resource/oss/list',
+ method: 'get',
+ params: query
+ });
+}
+
+// 查询OSS对象基于id串
+export function listByIds(ossId: string | number): AxiosPromise {
+ return request({
+ url: '/resource/oss/listByIds/' + ossId,
+ method: 'get'
+ });
+}
+
+// 删除OSS对象存储
+export function delOss(ossId: string | number | Array) {
+ return request({
+ url: '/resource/oss/' + ossId,
+ method: 'delete'
+ });
+}
diff --git a/src/api/system/oss/types.ts b/src/api/system/oss/types.ts
new file mode 100644
index 0000000..bc0bc1f
--- /dev/null
+++ b/src/api/system/oss/types.ts
@@ -0,0 +1,22 @@
+export interface OssVO extends BaseEntity {
+ ossId: string | number;
+ fileName: string;
+ originalName: string;
+ fileSuffix: string;
+ url: string;
+ createByName: string;
+ service: string;
+}
+
+export interface OssQuery extends PageQuery {
+ fileName: string;
+ originalName: string;
+ fileSuffix: string;
+ createTime: string;
+ service: string;
+ orderByColumn: string;
+ isAsc: string;
+}
+export interface OssForm {
+ file: undefined | string;
+}
diff --git a/src/api/system/ossConfig/index.ts b/src/api/system/ossConfig/index.ts
new file mode 100644
index 0000000..d0faefe
--- /dev/null
+++ b/src/api/system/ossConfig/index.ts
@@ -0,0 +1,60 @@
+import request from '@/utils/request';
+import { OssConfigForm, OssConfigQuery, OssConfigVO } from './types';
+import { AxiosPromise } from 'axios';
+
+// 查询对象存储配置列表
+export function listOssConfig(query: OssConfigQuery): AxiosPromise {
+ return request({
+ url: '/resource/oss/config/list',
+ method: 'get',
+ params: query
+ });
+}
+
+// 查询对象存储配置详细
+export function getOssConfig(ossConfigId: string | number): AxiosPromise {
+ return request({
+ url: '/resource/oss/config/' + ossConfigId,
+ method: 'get'
+ });
+}
+
+// 新增对象存储配置
+export function addOssConfig(data: OssConfigForm) {
+ return request({
+ url: '/resource/oss/config',
+ method: 'post',
+ data: data
+ });
+}
+
+// 修改对象存储配置
+export function updateOssConfig(data: OssConfigForm) {
+ return request({
+ url: '/resource/oss/config',
+ method: 'put',
+ data: data
+ });
+}
+
+// 删除对象存储配置
+export function delOssConfig(ossConfigId: string | number | Array) {
+ return request({
+ url: '/resource/oss/config/' + ossConfigId,
+ method: 'delete'
+ });
+}
+
+// 对象存储状态修改
+export function changeOssConfigStatus(ossConfigId: string | number, status: string, configKey: string) {
+ const data = {
+ ossConfigId,
+ status,
+ configKey
+ };
+ return request({
+ url: '/resource/oss/config/changeStatus',
+ method: 'put',
+ data: data
+ });
+}
diff --git a/src/api/system/ossConfig/types.ts b/src/api/system/ossConfig/types.ts
new file mode 100644
index 0000000..d227bd0
--- /dev/null
+++ b/src/api/system/ossConfig/types.ts
@@ -0,0 +1,38 @@
+export interface OssConfigVO extends BaseEntity {
+ ossConfigId: number | string;
+ configKey: string;
+ accessKey: string;
+ secretKey: string;
+ bucketName: string;
+ prefix: string;
+ endpoint: string;
+ domain: string;
+ isHttps: string;
+ region: string;
+ status: string;
+ ext1: string;
+ remark: string;
+ accessPolicy: string;
+}
+
+export interface OssConfigQuery extends PageQuery {
+ configKey: string;
+ bucketName: string;
+ status: string;
+}
+
+export interface OssConfigForm {
+ ossConfigId: string | number | undefined;
+ configKey: string;
+ accessKey: string;
+ secretKey: string;
+ bucketName: string;
+ prefix: string;
+ endpoint: string;
+ domain: string;
+ isHttps: string;
+ accessPolicy: string;
+ region: string;
+ status: string;
+ remark: string;
+}
diff --git a/src/api/system/post/index.ts b/src/api/system/post/index.ts
new file mode 100644
index 0000000..aba1ec1
--- /dev/null
+++ b/src/api/system/post/index.ts
@@ -0,0 +1,69 @@
+import request from '@/utils/request';
+import { PostForm, PostQuery, PostVO } from './types';
+import { AxiosPromise } from 'axios';
+import { DeptTreeVO } from '../dept/types';
+
+// 查询岗位列表
+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 optionselect(deptId?: number | string, postIds?: (number | string)[]): AxiosPromise {
+ return request({
+ url: '/system/post/optionselect',
+ method: 'get',
+ params: {
+ postIds: postIds,
+ deptId: deptId
+ }
+ });
+}
+
+// 新增岗位
+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'
+ });
+}
+
+/**
+ * 查询部门下拉树结构
+ */
+export const deptTreeSelect = (): AxiosPromise => {
+ return request({
+ url: '/system/post/deptTree',
+ method: 'get'
+ });
+};
diff --git a/src/api/system/post/types.ts b/src/api/system/post/types.ts
new file mode 100644
index 0000000..45a0540
--- /dev/null
+++ b/src/api/system/post/types.ts
@@ -0,0 +1,31 @@
+export interface PostVO extends BaseEntity {
+ postId: number | string;
+ deptId: number | string;
+ postCode: string;
+ postName: string;
+ postCategory: string;
+ deptName: string;
+ postSort: number;
+ status: string;
+ remark: string;
+}
+
+export interface PostForm {
+ postId: number | string | undefined;
+ deptId: number | string | undefined;
+ postCode: string;
+ postName: string;
+ postCategory: string;
+ postSort: number;
+ status: string;
+ remark: string;
+}
+
+export interface PostQuery extends PageQuery {
+ deptId: number | string;
+ belongDeptId: number | string;
+ postCode: string;
+ postName: string;
+ postCategory: 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..fb0fcab
--- /dev/null
+++ b/src/api/system/role/index.ts
@@ -0,0 +1,160 @@
+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
+ });
+};
+
+/**
+ * 通过roleIds查询角色
+ * @param roleIds
+ */
+export const optionSelect = (roleIds: (number | string)[]): AxiosPromise => {
+ return request({
+ url: '/system/role/optionselect?roleIds=' + roleIds,
+ method: 'get'
+ });
+};
+
+/**
+ * 查询角色详细
+ */
+export const getRole = (roleId: string | number): AxiosPromise => {
+ return request({
+ url: '/system/role/' + roleId,
+ method: 'get'
+ });
+};
+
+/**
+ * 新增角色
+ */
+export const addRole = (data: any) => {
+ return request({
+ url: '/system/role',
+ method: 'post',
+ data: data
+ });
+};
+
+/**
+ * 修改角色
+ * @param data
+ */
+export const updateRole = (data: any) => {
+ return request({
+ url: '/system/role',
+ method: 'put',
+ data: data
+ });
+};
+
+/**
+ * 角色数据权限
+ */
+export const dataScope = (data: any) => {
+ return request({
+ url: '/system/role/dataScope',
+ method: 'put',
+ data: data
+ });
+};
+
+/**
+ * 角色状态修改
+ */
+export const changeRoleStatus = (roleId: string | number, status: string) => {
+ const data = {
+ roleId,
+ status
+ };
+ return request({
+ url: '/system/role/changeStatus',
+ method: 'put',
+ data: data
+ });
+};
+
+/**
+ * 删除角色
+ */
+export const delRole = (roleId: Array | string | number) => {
+ return request({
+ url: '/system/role/' + roleId,
+ method: 'delete'
+ });
+};
+
+/**
+ * 查询角色已授权用户列表
+ */
+export const allocatedUserList = (query: UserQuery): AxiosPromise => {
+ return request({
+ url: '/system/role/authUser/allocatedList',
+ method: 'get',
+ params: query
+ });
+};
+
+/**
+ * 查询角色未授权用户列表
+ */
+export const unallocatedUserList = (query: UserQuery): AxiosPromise => {
+ return request({
+ url: '/system/role/authUser/unallocatedList',
+ method: 'get',
+ params: query
+ });
+};
+
+/**
+ * 取消用户授权角色
+ */
+export const authUserCancel = (data: any) => {
+ return request({
+ url: '/system/role/authUser/cancel',
+ method: 'put',
+ data: data
+ });
+};
+
+/**
+ * 批量取消用户授权角色
+ */
+export const authUserCancelAll = (data: any) => {
+ return request({
+ url: '/system/role/authUser/cancelAll',
+ method: 'put',
+ params: data
+ });
+};
+
+/**
+ * 授权用户选择
+ */
+export const authUserSelectAll = (data: any) => {
+ return request({
+ url: '/system/role/authUser/selectAll',
+ method: 'put',
+ params: data
+ });
+};
+// 根据角色ID查询部门树结构
+export const deptTreeSelect = (roleId: string | number): AxiosPromise => {
+ return request({
+ url: '/system/role/deptTree/' + roleId,
+ method: 'get'
+ });
+};
+
+export default {
+ optionSelect,
+ listRole
+};
diff --git a/src/api/system/role/types.ts b/src/api/system/role/types.ts
new file mode 100644
index 0000000..7dbb6ff
--- /dev/null
+++ b/src/api/system/role/types.ts
@@ -0,0 +1,52 @@
+/**
+ * 菜单树形结构类型
+ */
+export interface DeptTreeOption {
+ id: string;
+ label: string;
+ parentId: string;
+ weight: number;
+ children?: DeptTreeOption[];
+}
+
+export interface RoleDeptTree {
+ checkedKeys: string[];
+ depts: DeptTreeOption[];
+}
+
+export interface RoleVO extends BaseEntity {
+ roleId: string | number;
+ roleName: string;
+ roleKey: string;
+ roleSort: number;
+ dataScope: string;
+ menuCheckStrictly: boolean;
+ deptCheckStrictly: boolean;
+ status: string;
+ delFlag: string;
+ remark?: any;
+ flag: boolean;
+ menuIds?: Array;
+ deptIds?: Array;
+ admin: boolean;
+}
+
+export interface RoleQuery extends PageQuery {
+ roleName: string;
+ roleKey: string;
+ status: string;
+}
+
+export interface RoleForm {
+ roleName: string;
+ roleKey: string;
+ roleSort: number;
+ status: string;
+ menuCheckStrictly: boolean;
+ deptCheckStrictly: boolean;
+ remark: string;
+ dataScope?: string;
+ roleId: string | undefined;
+ menuIds: Array;
+ deptIds: Array;
+}
diff --git a/src/api/system/social/auth.ts b/src/api/system/social/auth.ts
new file mode 100644
index 0000000..69f0d7e
--- /dev/null
+++ b/src/api/system/social/auth.ts
@@ -0,0 +1,28 @@
+import request from '@/utils/request';
+
+// 绑定账号
+export function authBinding(source: string, tenantId: string) {
+ return request({
+ url: '/auth/binding/' + source,
+ method: 'get',
+ params: {
+ tenantId: tenantId,
+ domain: window.location.host
+ }
+ });
+}
+
+// 解绑账号
+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..d1d8ff8
--- /dev/null
+++ b/src/api/system/tenant/index.ts
@@ -0,0 +1,101 @@
+import request from '@/utils/request';
+import { TenantForm, TenantQuery, TenantVO } from './types';
+import { AxiosPromise } from 'axios';
+
+// 查询租户列表
+export function listTenant(query: TenantQuery): AxiosPromise {
+ return request({
+ url: '/system/tenant/list',
+ method: 'get',
+ params: query
+ });
+}
+
+// 查询租户详细
+export function getTenant(id: string | number): AxiosPromise {
+ return request({
+ url: '/system/tenant/' + id,
+ method: 'get'
+ });
+}
+
+// 新增租户
+export function addTenant(data: TenantForm) {
+ return request({
+ url: '/system/tenant',
+ method: 'post',
+ headers: {
+ isEncrypt: true,
+ repeatSubmit: false
+ },
+ data: data
+ });
+}
+
+// 修改租户
+export function updateTenant(data: TenantForm) {
+ return request({
+ url: '/system/tenant',
+ method: 'put',
+ data: data
+ });
+}
+
+// 租户状态修改
+export function changeTenantStatus(id: string | number, tenantId: string | number, status: string) {
+ const data = {
+ id,
+ tenantId,
+ status
+ };
+ return request({
+ url: '/system/tenant/changeStatus',
+ method: 'put',
+ data: data
+ });
+}
+
+// 删除租户
+export function delTenant(id: string | number | Array) {
+ return request({
+ url: '/system/tenant/' + id,
+ method: 'delete'
+ });
+}
+
+// 动态切换租户
+export function dynamicTenant(tenantId: string | number) {
+ return request({
+ url: '/system/tenant/dynamic/' + tenantId,
+ method: 'get'
+ });
+}
+
+// 清除动态租户
+export function dynamicClear() {
+ return request({
+ url: '/system/tenant/dynamic/clear',
+ method: 'get'
+ });
+}
+
+// 同步租户套餐
+export function syncTenantPackage(tenantId: string | number, packageId: string | number) {
+ const data = {
+ tenantId,
+ packageId
+ };
+ return request({
+ url: '/system/tenant/syncTenantPackage',
+ method: 'get',
+ params: data
+ });
+}
+
+// 同步租户字典
+export function syncTenantDict() {
+ return request({
+ url: '/system/tenant/syncTenantDict',
+ method: 'get'
+ });
+}
diff --git a/src/api/system/tenant/types.ts b/src/api/system/tenant/types.ts
new file mode 100644
index 0000000..25395fa
--- /dev/null
+++ b/src/api/system/tenant/types.ts
@@ -0,0 +1,46 @@
+export interface TenantVO extends BaseEntity {
+ id: number | string;
+ tenantId: number | string;
+ username: string;
+ contactUserName: string;
+ contactPhone: string;
+ companyName: string;
+ licenseNumber: string;
+ address: string;
+ domain: string;
+ intro: string;
+ remark: string;
+ packageId: string | number;
+ expireTime: string;
+ accountCount: number;
+ status: string;
+}
+
+export interface TenantQuery extends PageQuery {
+ tenantId: string | number;
+
+ contactUserName: string;
+
+ contactPhone: string;
+
+ companyName: string;
+}
+
+export interface TenantForm {
+ id: number | string | undefined;
+ tenantId: number | string | undefined;
+ username: string;
+ password: string;
+ contactUserName: string;
+ contactPhone: string;
+ companyName: string;
+ licenseNumber: string;
+ domain: string;
+ address: string;
+ intro: string;
+ remark: string;
+ packageId: string | number;
+ expireTime: string;
+ accountCount: number;
+ status: string;
+}
diff --git a/src/api/system/tenantPackage/index.ts b/src/api/system/tenantPackage/index.ts
new file mode 100644
index 0000000..70d0dab
--- /dev/null
+++ b/src/api/system/tenantPackage/index.ts
@@ -0,0 +1,67 @@
+import request from '@/utils/request';
+import { TenantPkgForm, TenantPkgQuery, TenantPkgVO } from './types';
+import { AxiosPromise } from 'axios';
+
+// 查询租户套餐列表
+export function listTenantPackage(query?: TenantPkgQuery): AxiosPromise {
+ return request({
+ url: '/system/tenant/package/list',
+ method: 'get',
+ params: query
+ });
+}
+
+// 查询租户套餐下拉选列表
+export function selectTenantPackage(): AxiosPromise {
+ return request({
+ url: '/system/tenant/package/selectList',
+ method: 'get'
+ });
+}
+
+// 查询租户套餐详细
+export function getTenantPackage(packageId: string | number): AxiosPromise {
+ return request({
+ url: '/system/tenant/package/' + packageId,
+ method: 'get'
+ });
+}
+
+// 新增租户套餐
+export function addTenantPackage(data: TenantPkgForm) {
+ return request({
+ url: '/system/tenant/package',
+ method: 'post',
+ data: data
+ });
+}
+
+// 修改租户套餐
+export function updateTenantPackage(data: TenantPkgForm) {
+ return request({
+ url: '/system/tenant/package',
+ method: 'put',
+ data: data
+ });
+}
+
+// 租户套餐状态修改
+export function changePackageStatus(packageId: number | string, status: string) {
+ const data = {
+ packageId,
+ status
+ };
+ return request({
+ url: '/system/tenant/package/changeStatus',
+ method: 'put',
+ data: data
+ });
+}
+
+// 删除租户套餐
+export function delTenantPackage(packageId: string | number | Array) {
+ return request({
+ url: '/system/tenant/package/' + packageId,
+ method: 'delete'
+ });
+}
diff --git a/src/api/system/tenantPackage/types.ts b/src/api/system/tenantPackage/types.ts
new file mode 100644
index 0000000..c24b8fc
--- /dev/null
+++ b/src/api/system/tenantPackage/types.ts
@@ -0,0 +1,20 @@
+export interface TenantPkgVO extends BaseEntity {
+ packageId: string | number;
+ packageName: string;
+ menuIds: string;
+ remark: string;
+ menuCheckStrictly: boolean;
+ status: string;
+}
+
+export interface TenantPkgQuery extends PageQuery {
+ packageName: string;
+}
+
+export interface TenantPkgForm {
+ packageId: string | number | undefined;
+ packageName: string;
+ menuIds: string;
+ remark: string;
+ menuCheckStrictly: boolean;
+}
diff --git a/src/api/system/user/index.ts b/src/api/system/user/index.ts
new file mode 100644
index 0000000..caffecc
--- /dev/null
+++ b/src/api/system/user/index.ts
@@ -0,0 +1,229 @@
+import { DeptTreeVO } 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
+ });
+};
+
+/**
+ * 通过用户ids查询用户
+ * @param userIds
+ */
+export const optionSelect = (userIds: (number | string)[]): AxiosPromise => {
+ return request({
+ url: '/system/user/optionselect?userIds=' + userIds,
+ method: 'get'
+ });
+};
+
+/**
+ * 获取用户详情
+ * @param userId
+ */
+export const getUser = (userId?: string | number): AxiosPromise => {
+ return request({
+ url: '/system/user/' + parseStrEmpty(userId),
+ method: 'get'
+ });
+};
+
+/**
+ * 新增用户
+ */
+export const addUser = (data: UserForm) => {
+ return request({
+ url: '/system/user',
+ method: 'post',
+ data: data
+ });
+};
+
+/**
+ * 修改用户
+ */
+export const updateUser = (data: UserForm) => {
+ return request({
+ url: '/system/user',
+ method: 'put',
+ data: data
+ });
+};
+
+/**
+ * 删除用户
+ * @param userId 用户ID
+ */
+export const delUser = (userId: Array | string | number) => {
+ return request({
+ url: '/system/user/' + userId,
+ method: 'delete'
+ });
+};
+
+/**
+ * 用户密码重置
+ * @param userId 用户ID
+ * @param password 密码
+ */
+export const resetUserPwd = (userId: string | number, password: string) => {
+ const data = {
+ userId,
+ password
+ };
+ return request({
+ url: '/system/user/resetPwd',
+ method: 'put',
+ headers: {
+ isEncrypt: true,
+ repeatSubmit: false
+ },
+ data: data
+ });
+};
+
+/**
+ * 用户状态修改
+ * @param userId 用户ID
+ * @param status 用户状态
+ */
+export const changeUserStatus = (userId: number | string, status: string) => {
+ const data = {
+ userId,
+ status
+ };
+ return request({
+ url: '/system/user/changeStatus',
+ method: 'put',
+ data: data
+ });
+};
+
+/**
+ * 查询用户个人信息
+ */
+export const getUserProfile = (): AxiosPromise => {
+ return request({
+ url: '/system/user/profile',
+ method: 'get'
+ });
+};
+
+/**
+ * 修改用户个人信息
+ * @param data 用户信息
+ */
+export const updateUserProfile = (data: UserForm) => {
+ return request({
+ url: '/system/user/profile',
+ method: 'put',
+ data: data
+ });
+};
+
+/**
+ * 用户密码重置
+ * @param oldPassword 旧密码
+ * @param newPassword 新密码
+ */
+export const updateUserPwd = (oldPassword: string, newPassword: string) => {
+ const data = {
+ oldPassword,
+ newPassword
+ };
+ return request({
+ url: '/system/user/profile/updatePwd',
+ method: 'put',
+ headers: {
+ isEncrypt: true,
+ repeatSubmit: false
+ },
+ 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,
+ optionSelect,
+ 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..8b6924e
--- /dev/null
+++ b/src/api/system/user/types.ts
@@ -0,0 +1,86 @@
+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;
+ nickName?: string;
+ phonenumber?: string;
+ status?: string;
+ deptId?: string | number;
+ roleId?: string | number;
+ userIds?: string | number | (string | number)[] | undefined;
+}
+
+/**
+ * 用户返回对象
+ */
+export interface UserVO extends BaseEntity {
+ userId: string | number;
+ tenantId: string;
+ deptId: number;
+ userName: string;
+ nickName: string;
+ userType: string;
+ email: string;
+ phonenumber: string;
+ sex: string;
+ avatar: string;
+ status: string;
+ delFlag: string;
+ loginIp: string;
+ loginDate: string;
+ remark: string;
+ 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;
+ sex?: string;
+ status: string;
+ remark?: string;
+ postIds: string[];
+ roleIds: string[];
+}
+
+export interface UserInfoVO {
+ user: UserVO;
+ roles: RoleVO[];
+ roleIds: string[];
+ posts: PostVO[];
+ postIds: string[];
+ roleGroup: string;
+ postGroup: string;
+}
+
+export interface ResetPwdForm {
+ oldPassword: string;
+ newPassword: string;
+ confirmPassword: string;
+}
diff --git a/src/api/tool/gen/index.ts b/src/api/tool/gen/index.ts
new file mode 100644
index 0000000..efe079c
--- /dev/null
+++ b/src/api/tool/gen/index.ts
@@ -0,0 +1,86 @@
+import request from '@/utils/request';
+import { DbTableQuery, DbTableVO, TableQuery, TableVO, GenTableVO, DbTableForm } from './types';
+import { AxiosPromise } from 'axios';
+
+// 查询生成表数据
+export const listTable = (query: TableQuery): AxiosPromise => {
+ return request({
+ url: '/tool/gen/list',
+ method: 'get',
+ params: query
+ });
+};
+// 查询db数据库列表
+export const listDbTable = (query: DbTableQuery): AxiosPromise => {
+ return request({
+ url: '/tool/gen/db/list',
+ method: 'get',
+ params: query
+ });
+};
+
+// 查询表详细信息
+export const getGenTable = (tableId: string | number): AxiosPromise => {
+ return request({
+ url: '/tool/gen/' + tableId,
+ method: 'get'
+ });
+};
+
+// 修改代码生成信息
+export const updateGenTable = (data: DbTableForm): AxiosPromise => {
+ return request({
+ url: '/tool/gen',
+ method: 'put',
+ data: data
+ });
+};
+
+// 导入表
+export const importTable = (data: { tables: string; dataName: string }): AxiosPromise => {
+ return request({
+ url: '/tool/gen/importTable',
+ method: 'post',
+ params: data
+ });
+};
+
+// 预览生成代码
+export const previewTable = (tableId: string | number) => {
+ return request({
+ url: '/tool/gen/preview/' + tableId,
+ method: 'get'
+ });
+};
+
+// 删除表数据
+export const delTable = (tableId: string | number | Array) => {
+ return request({
+ url: '/tool/gen/' + tableId,
+ method: 'delete'
+ });
+};
+
+// 生成代码(自定义路径)
+export const genCode = (tableId: string | number) => {
+ return request({
+ url: '/tool/gen/genCode/' + tableId,
+ method: 'get'
+ });
+};
+
+// 同步数据库
+export const synchDb = (tableId: string | number) => {
+ return request({
+ url: '/tool/gen/synchDb/' + tableId,
+ method: 'get'
+ });
+};
+
+// 获取数据源名称
+export const getDataNames = () => {
+ return request({
+ url: '/tool/gen/getDataNames',
+ method: 'get'
+ });
+};
diff --git a/src/api/tool/gen/types.ts b/src/api/tool/gen/types.ts
new file mode 100644
index 0000000..7f7b62f
--- /dev/null
+++ b/src/api/tool/gen/types.ts
@@ -0,0 +1,180 @@
+export interface TableVO extends BaseEntity {
+ createDept: number | string;
+ tableId: string | number;
+ dataName: string;
+ tableName: string;
+ tableComment: string;
+ subTableName?: any;
+ subTableFkName?: any;
+ className: string;
+ tplCategory: string;
+ packageName: string;
+ moduleName: string;
+ businessName: string;
+ functionName: string;
+ functionAuthor: string;
+ genType: string;
+ genPath: string;
+ pkColumn?: any;
+ columns?: any;
+ options?: any;
+ remark?: any;
+ treeCode?: any;
+ treeParentCode?: any;
+ treeName?: any;
+ menuIds?: any;
+ parentMenuId?: any;
+ parentMenuName?: any;
+ tree: boolean;
+ crud: boolean;
+}
+
+export interface TableQuery extends PageQuery {
+ tableName: string;
+ tableComment: string;
+ dataName: string;
+}
+
+export interface DbColumnVO extends BaseEntity {
+ createDept?: any;
+ columnId?: any;
+ tableId?: any;
+ columnName?: any;
+ columnComment?: any;
+ columnType?: any;
+ javaType?: any;
+ javaField?: any;
+ isPk?: any;
+ isIncrement?: any;
+ isRequired?: any;
+ isInsert?: any;
+ isEdit?: any;
+ isList?: any;
+ isQuery?: any;
+ queryType?: any;
+ htmlType?: any;
+ dictType?: any;
+ sort?: any;
+ increment: boolean;
+ capJavaField?: any;
+ usableColumn: boolean;
+ superColumn: boolean;
+ list: boolean;
+ pk: boolean;
+ insert: boolean;
+ edit: boolean;
+ query: boolean;
+ required: boolean;
+}
+
+export interface DbTableVO {
+ createDept?: any;
+ tableId?: any;
+ tableName: string;
+ tableComment: string;
+ subTableName?: any;
+ subTableFkName?: any;
+ className?: any;
+ tplCategory?: any;
+ packageName?: any;
+ moduleName?: any;
+ businessName?: any;
+ functionName?: any;
+ functionAuthor?: any;
+ genType?: any;
+ genPath?: any;
+ pkColumn?: any;
+ columns: DbColumnVO[];
+ options?: any;
+ remark?: any;
+ treeCode?: any;
+ treeParentCode?: any;
+ treeName?: any;
+ menuIds?: any;
+ parentMenuId?: any;
+ parentMenuName?: any;
+ tree: boolean;
+ crud: boolean;
+}
+
+export interface DbTableQuery extends PageQuery {
+ dataName: string;
+ tableName: string;
+ tableComment: string;
+}
+
+export interface GenTableVO {
+ info: DbTableVO;
+ rows: DbColumnVO[];
+ tables: DbTableVO[];
+}
+
+export interface DbColumnForm extends BaseEntity {
+ createDept: number;
+ columnId: string;
+ tableId: string;
+ columnName: string;
+ columnComment: string;
+ columnType: string;
+ javaType: string;
+ javaField: string;
+ isPk: string;
+ isIncrement: string;
+ isRequired: string;
+ isInsert?: any;
+ isEdit: string;
+ isList: string;
+ isQuery?: any;
+ queryType: string;
+ htmlType: string;
+ dictType: string;
+ sort: number;
+ increment: boolean;
+ capJavaField: string;
+ usableColumn: boolean;
+ superColumn: boolean;
+ list: boolean;
+ pk: boolean;
+ insert: boolean;
+ edit: boolean;
+ query: boolean;
+ required: boolean;
+}
+
+export interface DbParamForm {
+ treeCode?: any;
+ treeName?: any;
+ treeParentCode?: any;
+ parentMenuId: string;
+}
+
+export interface DbTableForm extends BaseEntity {
+ createDept?: any;
+ tableId: string | string;
+ tableName: string;
+ tableComment: string;
+ subTableName?: any;
+ subTableFkName?: any;
+ className: string;
+ tplCategory: string;
+ packageName: string;
+ moduleName: string;
+ businessName: string;
+ functionName: string;
+ functionAuthor: string;
+ genType: string;
+ genPath: string;
+ pkColumn?: any;
+ columns: DbColumnForm[];
+ options: string;
+ remark?: any;
+ treeCode?: any;
+ treeParentCode?: any;
+ treeName?: any;
+ menuIds?: any;
+ parentMenuId: string;
+ parentMenuName?: any;
+ tree: boolean;
+ crud: boolean;
+ params: DbParamForm;
+}
diff --git a/src/api/types.ts b/src/api/types.ts
new file mode 100644
index 0000000..617286c
--- /dev/null
+++ b/src/api/types.ts
@@ -0,0 +1,59 @@
+/**
+ * 注册
+ */
+export type RegisterForm = {
+ tenantId: string;
+ username: string;
+ password: string;
+ confirmPassword?: string;
+ code?: string;
+ uuid?: string;
+ userType?: string;
+};
+
+/**
+ * 登录请求
+ */
+export interface LoginData {
+ tenantId?: string;
+ username?: string;
+ password?: string;
+ rememberMe?: boolean;
+ socialCode?: string;
+ socialState?: string;
+ source?: string;
+ code?: string;
+ uuid?: string;
+ clientId: string;
+ grantType: string;
+}
+
+/**
+ * 登录响应
+ */
+export interface LoginResult {
+ access_token: string;
+}
+
+/**
+ * 验证码返回
+ */
+export interface VerifyCodeResult {
+ captchaEnabled: boolean;
+ uuid?: string;
+ img?: string;
+}
+
+/**
+ * 租户
+ */
+export interface TenantVO {
+ companyName: string;
+ domain: any;
+ tenantId: string;
+}
+
+export interface TenantInfo {
+ tenantEnabled: boolean;
+ voList: TenantVO[];
+}
diff --git a/src/api/workflow/category/index.ts b/src/api/workflow/category/index.ts
new file mode 100644
index 0000000..b6a83ea
--- /dev/null
+++ b/src/api/workflow/category/index.ts
@@ -0,0 +1,76 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { CategoryVO, CategoryForm, CategoryQuery, CategoryTreeVO } from '@/api/workflow/category/types';
+
+/**
+ * 查询流程分类列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listCategory = (query?: CategoryQuery): AxiosPromise => {
+ return request({
+ url: '/workflow/category/list',
+ method: 'get',
+ params: query
+ });
+};
+
+/**
+ * 查询流程分类详细
+ * @param categoryId
+ */
+export const getCategory = (categoryId: string | number): AxiosPromise => {
+ return request({
+ url: '/workflow/category/' + categoryId,
+ method: 'get'
+ });
+};
+
+/**
+ * 新增流程分类
+ * @param data
+ */
+export const addCategory = (data: CategoryForm) => {
+ return request({
+ url: '/workflow/category',
+ method: 'post',
+ data: data
+ });
+};
+
+/**
+ * 修改流程分类
+ * @param data
+ */
+export const updateCategory = (data: CategoryForm) => {
+ return request({
+ url: '/workflow/category',
+ method: 'put',
+ data: data
+ });
+};
+
+/**
+ * 删除流程分类
+ * @param categoryId
+ */
+export const delCategory = (categoryId: string | number | Array) => {
+ return request({
+ url: '/workflow/category/' + categoryId,
+ method: 'delete'
+ });
+};
+
+/**
+ * 获取流程分类树列表
+ * @param query 流程实例id
+ * @returns
+ */
+export const categoryTree = (query?: CategoryForm): AxiosPromise => {
+ return request({
+ url: `/workflow/category/categoryTree`,
+ method: 'get',
+ params: query
+ });
+};
diff --git a/src/api/workflow/category/types.ts b/src/api/workflow/category/types.ts
new file mode 100644
index 0000000..99a53a6
--- /dev/null
+++ b/src/api/workflow/category/types.ts
@@ -0,0 +1,67 @@
+export interface CategoryTreeVO {
+ id: number | string;
+ label: string;
+ parentId: number | string;
+ weight: number;
+ children: CategoryTreeVO[];
+}
+export interface CategoryVO {
+ /**
+ * 流程分类ID
+ */
+ categoryId: string | number;
+
+ /**
+ * 父级id
+ */
+ parentId: string | number;
+
+ /**
+ * 流程分类名称
+ */
+ categoryName: string;
+
+ /**
+ * 显示顺序
+ */
+ orderNum: number;
+
+ /**
+ * 创建时间
+ */
+ createTime: string;
+
+ /**
+ * 子对象
+ */
+ children: CategoryVO[];
+}
+
+export interface CategoryForm extends BaseEntity {
+ /**
+ * 流程分类ID
+ */
+ categoryId?: string | number;
+
+ /**
+ * 流程分类名称
+ */
+ categoryName?: string;
+
+ /**
+ * 父流程分类id
+ */
+ parentId?: string | number;
+
+ /**
+ * 显示顺序
+ */
+ orderNum?: number;
+}
+
+export interface CategoryQuery {
+ /**
+ * 流程分类名称
+ */
+ categoryName?: string;
+}
diff --git a/src/api/workflow/definition/index.ts b/src/api/workflow/definition/index.ts
new file mode 100644
index 0000000..49e6ee8
--- /dev/null
+++ b/src/api/workflow/definition/index.ts
@@ -0,0 +1,170 @@
+import request from '@/utils/request';
+import { FlowDefinitionQuery, definitionXmlVO, FlowDefinitionForm, FlowDefinitionVo } from '@/api/workflow/definition/types';
+import { AxiosPromise } from 'axios';
+
+/**
+ * 获取流程定义列表
+ * @param query 流程实例id
+ * @returns
+ */
+export const listDefinition = (query: FlowDefinitionQuery): AxiosPromise => {
+ return request({
+ url: `/workflow/definition/list`,
+ method: 'get',
+ params: query
+ });
+};
+
+/**
+ * 查询未发布的流程定义列表
+ * @param query 流程实例id
+ * @returns
+ */
+export const unPublishList = (query: FlowDefinitionQuery): AxiosPromise => {
+ return request({
+ url: `/workflow/definition/unPublishList`,
+ method: 'get',
+ params: query
+ });
+};
+
+/**
+ * 通过流程定义id获取xml
+ * @param definitionId 流程定义id
+ * @returns
+ */
+export const definitionXml = (definitionId: string): AxiosPromise => {
+ return request({
+ url: `/workflow/definition/definitionXml/${definitionId}`,
+ method: 'get'
+ });
+};
+
+/**
+ * 删除流程定义
+ * @param id 流程定义id
+ * @returns
+ */
+export const deleteDefinition = (id: string | string[]) => {
+ return request({
+ url: `/workflow/definition/${id}`,
+ method: 'delete'
+ });
+};
+
+/**
+ * 挂起/激活
+ * @param definitionId 流程定义id
+ * @param activityStatus 状态
+ * @returns
+ */
+export const active = (definitionId: string, activityStatus: boolean) => {
+ return request({
+ url: `/workflow/definition/active/${definitionId}`,
+ method: 'put',
+ params: {
+ active: activityStatus
+ }
+ });
+};
+
+/**
+ * 通过zip或xml部署流程定义
+ * @returns
+ */
+export function importDef(data: any) {
+ return request({
+ url: '/workflow/definition/importDef',
+ method: 'post',
+ data: data,
+ headers: {
+ repeatSubmit: false
+ }
+ });
+}
+
+/**
+ * 发布流程定义
+ * @param id 流程定义id
+ * @returns
+ */
+export const publish = (id: string) => {
+ return request({
+ url: `/workflow/definition/publish/${id}`,
+ method: 'put'
+ });
+};
+
+/**
+ * 取消发布流程定义
+ * @param id 流程定义id
+ * @returns
+ */
+export const unPublish = (id: string) => {
+ return request({
+ url: `/workflow/definition/unPublish/${id}`,
+ method: 'put'
+ });
+};
+
+/**
+ * 获取流程定义xml字符串
+ * @param id 流程定义id
+ * @returns
+ */
+export const xmlString = (id: string) => {
+ return request({
+ url: `/workflow/definition/xmlString/${id}`,
+ method: 'get'
+ });
+};
+
+/**
+ * 新增
+ * @param data 参数
+ * @returns
+ */
+export const add = (data: FlowDefinitionForm) => {
+ return request({
+ url: `/workflow/definition`,
+ method: 'post',
+ data: data
+ });
+};
+
+/**
+ * 修改
+ * @param data 参数
+ * @returns
+ */
+export const edit = (data: FlowDefinitionForm) => {
+ return request({
+ url: `/workflow/definition`,
+ method: 'put',
+ data: data
+ });
+};
+
+/**
+ * 查询详情
+ * @param id 参数
+ * @returns
+ */
+export const getInfo = (id: number | string) => {
+ return request({
+ url: `/workflow/definition/${id}`,
+ method: 'get'
+ });
+};
+
+/**
+ * 复制流程定义
+ * @param id 流程定义id
+ * @returns
+ */
+export const copy = (id: string) => {
+ return request({
+ url: `/workflow/definition/copy/${id}`,
+ method: 'post'
+ });
+};
diff --git a/src/api/workflow/definition/types.ts b/src/api/workflow/definition/types.ts
new file mode 100644
index 0000000..93d081e
--- /dev/null
+++ b/src/api/workflow/definition/types.ts
@@ -0,0 +1,34 @@
+export interface FlowDefinitionQuery extends PageQuery {
+ flowCode?: string;
+ flowName?: string;
+ category: string | number;
+ isPublish?: number;
+}
+
+export interface FlowDefinitionVo {
+ id: string;
+ flowName: string;
+ flowCode: string;
+ formPath: string;
+ version: string;
+ isPublish: number;
+ activityStatus: number;
+ createTime: Date;
+ updateTime: Date;
+}
+
+export interface FlowDefinitionForm {
+ id: string;
+ flowName: string;
+ flowCode: string;
+ category: string;
+ ext: string;
+ formPath: string;
+ formCustom: string;
+ modelValue: string;
+}
+
+export interface definitionXmlVO {
+ xml: string[];
+ xmlStr: string;
+}
diff --git a/src/api/workflow/instance/index.ts b/src/api/workflow/instance/index.ts
new file mode 100644
index 0000000..ad136f0
--- /dev/null
+++ b/src/api/workflow/instance/index.ts
@@ -0,0 +1,125 @@
+import request from '@/utils/request';
+import { FlowInstanceQuery, FlowInstanceVO } from '@/api/workflow/instance/types';
+import { AxiosPromise } from 'axios';
+
+/**
+ * 查询运行中实例列表
+ * @param query
+ * @returns {*}
+ */
+export const pageByRunning = (query: FlowInstanceQuery): AxiosPromise => {
+ return request({
+ url: '/workflow/instance/pageByRunning',
+ method: 'get',
+ params: query
+ });
+};
+
+/**
+ * 查询已完成实例列表
+ * @param query
+ * @returns {*}
+ */
+export const pageByFinish = (query: FlowInstanceQuery): AxiosPromise => {
+ return request({
+ url: '/workflow/instance/pageByFinish',
+ method: 'get',
+ params: query
+ });
+};
+
+/**
+ * 通过业务id获取历史流程图
+ */
+export const flowHisTaskList = (businessId: string | number) => {
+ return request({
+ url: `/workflow/instance/flowHisTaskList/${businessId}` + '?t' + Math.random(),
+ method: 'get'
+ });
+};
+
+/**
+ * 分页查询当前登录人单据
+ * @param query
+ * @returns {*}
+ */
+export const pageByCurrent = (query: FlowInstanceQuery): AxiosPromise => {
+ return request({
+ url: '/workflow/instance/pageByCurrent',
+ method: 'get',
+ params: query
+ });
+};
+
+/**
+ * 撤销流程
+ * @param data 参数
+ * @returns
+ */
+export const cancelProcessApply = (data: any) => {
+ return request({
+ url: `/workflow/instance/cancelProcessApply`,
+ method: 'put',
+ data: data
+ });
+};
+
+/**
+ * 获取流程变量
+ * @param instanceId 实例id
+ * @returns
+ */
+export const instanceVariable = (instanceId: string | number) => {
+ return request({
+ url: `/workflow/instance/instanceVariable/${instanceId}`,
+ method: 'get'
+ });
+};
+
+/**
+ * 删除
+ * @param instanceIds 流程实例id
+ * @returns
+ */
+export const deleteByInstanceIds = (instanceIds: Array | string | number) => {
+ return request({
+ url: `/workflow/instance/deleteByInstanceIds/${instanceIds}`,
+ method: 'delete'
+ });
+};
+
+/**
+ * 删除历史流程实例
+ * @param instanceIds
+ */
+export const deleteHisByInstanceIds = (instanceIds: Array | string | number) => {
+ return request({
+ url: `/workflow/instance/deleteHisByInstanceIds/${instanceIds}`,
+ method: 'delete'
+ });
+};
+
+/**
+ * 作废流程
+ * @param data 参数
+ * @returns
+ */
+export const invalid = (data: any) => {
+ return request({
+ url: `/workflow/instance/invalid`,
+ method: 'post',
+ data: data
+ });
+};
+/**
+ * 修改流程变量
+ * @param data 参数
+ * @returns
+ */
+export const updateVariable = (data: any) => {
+ return request({
+ url: `/workflow/instance/updateVariable`,
+ method: 'put',
+ data: data
+ });
+};
diff --git a/src/api/workflow/instance/types.ts b/src/api/workflow/instance/types.ts
new file mode 100644
index 0000000..7cb7f97
--- /dev/null
+++ b/src/api/workflow/instance/types.ts
@@ -0,0 +1,28 @@
+import { FlowTaskVO } from '@/api/workflow/task/types';
+
+export interface FlowInstanceQuery extends PageQuery {
+ category?: string | number;
+ nodeName?: string;
+ flowCode?: string;
+ flowName?: string;
+ createByIds?: string[] | number[];
+ businessId?: string;
+}
+
+export interface FlowInstanceVO extends BaseEntity {
+ id: string | number;
+ definitionId: string;
+ flowName: string;
+ flowCode: string;
+ version: string;
+ businessId: string;
+ activityStatus: number;
+ tenantId: string;
+ createTime: string;
+ createBy: string;
+ flowStatus: string;
+ flowStatusName: string;
+ flowTaskList: FlowTaskVO[];
+ businessCode: string;
+ businessTitle: string;
+}
diff --git a/src/api/workflow/leave/index.ts b/src/api/workflow/leave/index.ts
new file mode 100644
index 0000000..5a88edb
--- /dev/null
+++ b/src/api/workflow/leave/index.ts
@@ -0,0 +1,75 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { LeaveVO, LeaveQuery, LeaveForm } from '@/api/workflow/leave/types';
+
+/**
+ * 查询请假列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listLeave = (query?: LeaveQuery): AxiosPromise => {
+ return request({
+ url: '/workflow/leave/list',
+ method: 'get',
+ params: query
+ });
+};
+
+/**
+ * 查询请假详细
+ * @param id
+ */
+export const getLeave = (id: string | number): AxiosPromise => {
+ return request({
+ url: '/workflow/leave/' + id,
+ method: 'get'
+ });
+};
+
+/**
+ * 新增请假
+ * @param data
+ */
+export const addLeave = (data: LeaveForm): AxiosPromise => {
+ return request({
+ url: '/workflow/leave',
+ method: 'post',
+ data: data
+ });
+};
+
+/**
+ * 提交请假并发起流程
+ * @param data
+ */
+export const submitAndFlowStart = (data: LeaveForm): AxiosPromise => {
+ return request({
+ url: '/workflow/leave/submitAndFlowStart',
+ method: 'post',
+ data: data
+ });
+};
+
+/**
+ * 修改请假
+ * @param data
+ */
+export const updateLeave = (data: LeaveForm): AxiosPromise => {
+ return request({
+ url: '/workflow/leave',
+ method: 'put',
+ data: data
+ });
+};
+
+/**
+ * 删除请假
+ * @param id
+ */
+export const delLeave = (id: string | number | Array) => {
+ return request({
+ url: '/workflow/leave/' + id,
+ method: 'delete'
+ });
+};
diff --git a/src/api/workflow/leave/types.ts b/src/api/workflow/leave/types.ts
new file mode 100644
index 0000000..c8e58a7
--- /dev/null
+++ b/src/api/workflow/leave/types.ts
@@ -0,0 +1,26 @@
+export interface LeaveVO {
+ id: string | number;
+ applyCode?: string;
+ leaveType: string;
+ startDate: string;
+ endDate: string;
+ leaveDays: number;
+ remark: string;
+ status?: string;
+}
+
+export interface LeaveForm extends BaseEntity {
+ id?: string | number;
+ applyCode?: string;
+ leaveType?: string;
+ startDate?: string;
+ endDate?: string;
+ leaveDays?: number;
+ remark?: string;
+ status?: string;
+}
+
+export interface LeaveQuery extends PageQuery {
+ startLeaveDays?: number;
+ endLeaveDays?: number;
+}
diff --git a/src/api/workflow/spel/index.ts b/src/api/workflow/spel/index.ts
new file mode 100644
index 0000000..0cc8746
--- /dev/null
+++ b/src/api/workflow/spel/index.ts
@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { SpelVO, SpelForm, SpelQuery } from '@/api/workflow/spel/types';
+
+/**
+ * 查询流程spel表达式定义列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listSpel = (query?: SpelQuery): AxiosPromise => {
+ return request({
+ url: '/workflow/spel/list',
+ method: 'get',
+ params: query
+ });
+};
+
+/**
+ * 查询流程spel表达式定义详细
+ * @param id
+ */
+export const getSpel = (id: string | number): AxiosPromise => {
+ return request({
+ url: '/workflow/spel/' + id,
+ method: 'get'
+ });
+};
+
+/**
+ * 新增流程spel表达式定义
+ * @param data
+ */
+export const addSpel = (data: SpelForm) => {
+ return request({
+ url: '/workflow/spel',
+ method: 'post',
+ data: data
+ });
+};
+
+/**
+ * 修改流程spel表达式定义
+ * @param data
+ */
+export const updateSpel = (data: SpelForm) => {
+ return request({
+ url: '/workflow/spel',
+ method: 'put',
+ data: data
+ });
+};
+
+/**
+ * 删除流程spel表达式定义
+ * @param id
+ */
+export const delSpel = (id: string | number | Array) => {
+ return request({
+ url: '/workflow/spel/' + id,
+ method: 'delete'
+ });
+};
diff --git a/src/api/workflow/spel/types.ts b/src/api/workflow/spel/types.ts
new file mode 100644
index 0000000..e0a694c
--- /dev/null
+++ b/src/api/workflow/spel/types.ts
@@ -0,0 +1,111 @@
+export interface SpelVO {
+ /**
+ * 主键id
+ */
+ id: string | number;
+
+ /**
+ * 组件名称
+ */
+ componentName: string;
+
+ /**
+ * 方法名
+ */
+ methodName: string;
+
+ /**
+ * 参数
+ */
+ methodParams: string;
+
+ /**
+ * 预览spel值
+ */
+ viewSpel: string;
+
+ /**
+ * 状态(0正常 1停用)
+ */
+ status: string;
+
+ /**
+ * 备注
+ */
+ remark?: string;
+
+}
+
+export interface SpelForm extends BaseEntity {
+ /**
+ * 主键id
+ */
+ id?: string | number;
+
+ /**
+ * 组件名称
+ */
+ componentName?: string;
+
+ /**
+ * 方法名
+ */
+ methodName?: string;
+
+ /**
+ * 参数
+ */
+ methodParams?: string;
+
+ /**
+ * 预览spel值
+ */
+ viewSpel?: string;
+
+ /**
+ * 状态(0正常 1停用)
+ */
+ status?: string;
+
+ /**
+ * 备注
+ */
+ remark?: string;
+
+}
+
+export interface SpelQuery extends PageQuery {
+
+ /**
+ * 组件名称
+ */
+ componentName?: string;
+
+ /**
+ * 方法名
+ */
+ methodName?: string;
+
+ /**
+ * 参数
+ */
+ methodParams?: string;
+
+ /**
+ * 预览spel值
+ */
+ viewSpel?: string;
+
+ /**
+ * 状态(0正常 1停用)
+ */
+ status?: string;
+
+ /**
+ * 日期范围参数
+ */
+ params?: any;
+}
+
+
+
diff --git a/src/api/workflow/task/index.ts b/src/api/workflow/task/index.ts
new file mode 100644
index 0000000..dc52f62
--- /dev/null
+++ b/src/api/workflow/task/index.ts
@@ -0,0 +1,206 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { TaskQuery, FlowTaskVO, TaskOperationBo } from '@/api/workflow/task/types';
+
+/**
+ * 查询待办列表
+ * @param query
+ * @returns {*}
+ */
+export const pageByTaskWait = (query: TaskQuery): AxiosPromise => {
+ return request({
+ url: '/workflow/task/pageByTaskWait',
+ method: 'get',
+ params: query
+ });
+};
+
+/**
+ * 查询已办列表
+ * @param query
+ * @returns {*}
+ */
+export const pageByTaskFinish = (query: TaskQuery): AxiosPromise => {
+ return request({
+ url: '/workflow/task/pageByTaskFinish',
+ method: 'get',
+ params: query
+ });
+};
+
+/**
+ * 查询当前用户的抄送列表
+ * @param query
+ * @returns {*}
+ */
+export const pageByTaskCopy = (query: TaskQuery): AxiosPromise => {
+ return request({
+ url: '/workflow/task/pageByTaskCopy',
+ method: 'get',
+ params: query
+ });
+};
+
+/**
+ * 当前租户所有待办任务
+ * @param query
+ * @returns {*}
+ */
+export const pageByAllTaskWait = (query: TaskQuery): AxiosPromise => {
+ return request({
+ url: '/workflow/task/pageByAllTaskWait',
+ method: 'get',
+ params: query
+ });
+};
+
+/**
+ * 当前租户所有已办任务
+ * @param query
+ * @returns {*}
+ */
+export const pageByAllTaskFinish = (query: TaskQuery): AxiosPromise => {
+ return request({
+ url: '/workflow/task/pageByAllTaskFinish',
+ method: 'get',
+ params: query
+ });
+};
+
+/**
+ * 启动流程
+ * @param data
+ * @returns {*}
+ */
+export const startWorkFlow = (data: object): any => {
+ return request({
+ url: '/workflow/task/startWorkFlow',
+ method: 'post',
+ data: data
+ });
+};
+
+/**
+ * 办理流程
+ * @param data
+ * @returns {*}
+ */
+export const completeTask = (data: object) => {
+ return request({
+ url: '/workflow/task/completeTask',
+ method: 'post',
+ data: data
+ });
+};
+
+/**
+ * 任务驳回
+ * @param data
+ * @returns {*}
+ */
+export const backProcess = (data: any): any => {
+ return request({
+ url: '/workflow/task/backProcess',
+ method: 'post',
+ data: data
+ });
+};
+
+/**
+ * 获取当前任务
+ * @param taskId
+ * @returns
+ */
+export const getTask = (taskId: string) => {
+ return request({
+ url: '/workflow/task/getTask/' + taskId,
+ method: 'get'
+ });
+};
+
+/**
+ * 修改任务办理人
+ * @param taskIdList
+ * @param userId
+ * @returns
+ */
+export const updateAssignee = (taskIdList: Array, userId: string) => {
+ return request({
+ url: `/workflow/task/updateAssignee/${userId}`,
+ method: 'put',
+ data: taskIdList
+ });
+};
+
+/**
+ * 终止任务
+ * @returns
+ */
+export const terminationTask = (data: any) => {
+ return request({
+ url: `/workflow/task/terminationTask`,
+ method: 'post',
+ data: data
+ });
+};
+
+/**
+ * 获取可驳回得任务节点
+ * @returns
+ */
+export const getBackTaskNode = (taskId: string | number, nodeCode: string) => {
+ return request({
+ url: `/workflow/task/getBackTaskNode/${taskId}/${nodeCode}`,
+ method: 'get'
+ });
+};
+
+/**
+ * 任务操作 操作类型,委派 delegateTask、转办 transferTask、加签 addSignature、减签 reductionSignature
+ * @returns
+ */
+export const taskOperation = (data: TaskOperationBo, operation: string) => {
+ return request({
+ url: `/workflow/task/taskOperation/${operation}`,
+ method: 'post',
+ data: data
+ });
+};
+
+/**
+ * 获取当前任务办理人
+ * @param taskId 任务id
+ * @returns
+ */
+export const currentTaskAllUser = (taskId: string | number) => {
+ return request({
+ url: `/workflow/task/currentTaskAllUser/${taskId}`,
+ method: 'get'
+ });
+};
+
+/**
+ * 获取下一节点写
+ * @param data参数
+ * @returns
+ */
+export const getNextNodeList = (data: any): any => {
+ return request({
+ url: '/workflow/task/getNextNodeList',
+ method: 'post',
+ data: data
+ });
+};
+
+/**
+ * 催办任务
+ * @param data参数
+ * @returns
+ */
+export const urgeTask = (data: any): any => {
+ return request({
+ url: '/workflow/task/urgeTask',
+ method: 'post',
+ data: data
+ });
+};
diff --git a/src/api/workflow/task/types.ts b/src/api/workflow/task/types.ts
new file mode 100644
index 0000000..e3c9abf
--- /dev/null
+++ b/src/api/workflow/task/types.ts
@@ -0,0 +1,58 @@
+export interface TaskQuery extends PageQuery {
+ nodeName?: string;
+ flowCode?: string;
+ flowName?: string;
+ createByIds?: string[] | number[];
+}
+
+export interface ParticipantVo {
+ groupIds?: string[] | number[];
+ candidate: string[] | number[];
+ candidateName: string[];
+ claim: boolean;
+}
+export interface FlowTaskVO {
+ id: string | number;
+ createTime?: Date;
+ updateTime?: Date;
+ tenantId?: string;
+ definitionId?: string;
+ instanceId: string;
+ flowName: string;
+ businessId: string;
+ nodeCode: string;
+ nodeName: string;
+ flowCode: string;
+ flowStatus: string;
+ formCustom: string;
+ formPath: string;
+ nodeType: number;
+ nodeRatio: string | number;
+ version?: string;
+ applyNode?: boolean;
+ buttonList?: ButtonList[];
+ copyList?: FlowCopyVo[];
+ varList?: Map;
+ businessCode: string;
+ businessTitle: string;
+}
+
+export interface ButtonList {
+ code: string;
+ show: boolean;
+}
+export interface FlowCopyVo {
+ userId: string | number;
+ userName: string;
+}
+
+export interface TaskOperationBo {
+ //委派/转办人的用户ID(必填,准对委派/转办人操作)
+ userId?: string;
+ //加签/减签人的用户ID列表(必填,针对加签/减签操作)
+ userIds?: string[];
+ //任务ID(必填)
+ taskId: string | number;
+ //意见或备注信息(可选)
+ message?: string;
+}
diff --git a/src/api/workflow/workflowCommon/index.ts b/src/api/workflow/workflowCommon/index.ts
new file mode 100644
index 0000000..0f5ce1b
--- /dev/null
+++ b/src/api/workflow/workflowCommon/index.ts
@@ -0,0 +1,15 @@
+import { RouterJumpVo } from '@/api/workflow/workflowCommon/types';
+
+export default {
+ routerJump(routerJumpVo: RouterJumpVo, proxy) {
+ proxy.$tab.closePage(proxy.$route);
+ proxy.$router.push({
+ path: routerJumpVo.formPath,
+ query: {
+ id: routerJumpVo.businessId,
+ type: routerJumpVo.type,
+ taskId: routerJumpVo.taskId
+ }
+ });
+ }
+};
diff --git a/src/api/workflow/workflowCommon/types.ts b/src/api/workflow/workflowCommon/types.ts
new file mode 100644
index 0000000..8ab5a67
--- /dev/null
+++ b/src/api/workflow/workflowCommon/types.ts
@@ -0,0 +1,14 @@
+export interface RouterJumpVo {
+ businessId: string;
+ taskId: string | number;
+ type: string;
+ formCustom: string;
+ formPath: string;
+}
+
+export interface StartProcessBo {
+ businessId: string | number;
+ flowCode: string;
+ variables: any;
+ flowInstanceBizExtBo: any;
+}
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..d14d580
--- /dev/null
+++ b/src/assets/icons/svg/button.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/caret-back.svg b/src/assets/icons/svg/caret-back.svg
new file mode 100644
index 0000000..9bae722
--- /dev/null
+++ b/src/assets/icons/svg/caret-back.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/svg/caret-forward.svg b/src/assets/icons/svg/caret-forward.svg
new file mode 100644
index 0000000..1ec3f7d
--- /dev/null
+++ b/src/assets/icons/svg/caret-forward.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/category.svg b/src/assets/icons/svg/category.svg
new file mode 100644
index 0000000..df92526
--- /dev/null
+++ b/src/assets/icons/svg/category.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/finish.svg b/src/assets/icons/svg/finish.svg
new file mode 100644
index 0000000..4685c23
--- /dev/null
+++ b/src/assets/icons/svg/finish.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/model.svg b/src/assets/icons/svg/model.svg
new file mode 100644
index 0000000..0c57d70
--- /dev/null
+++ b/src/assets/icons/svg/model.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/my-copy.svg b/src/assets/icons/svg/my-copy.svg
new file mode 100644
index 0000000..49f69fa
--- /dev/null
+++ b/src/assets/icons/svg/my-copy.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/svg/my-task.svg b/src/assets/icons/svg/my-task.svg
new file mode 100644
index 0000000..1f1ea44
--- /dev/null
+++ b/src/assets/icons/svg/my-task.svg
@@ -0,0 +1 @@
+
\ 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/process-definition.svg b/src/assets/icons/svg/process-definition.svg
new file mode 100644
index 0000000..202d200
--- /dev/null
+++ b/src/assets/icons/svg/process-definition.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/waiting.svg b/src/assets/icons/svg/waiting.svg
new file mode 100644
index 0000000..2c2042d
--- /dev/null
+++ b/src/assets/icons/svg/waiting.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/workflow.svg b/src/assets/icons/svg/workflow.svg
new file mode 100644
index 0000000..2f7423a
--- /dev/null
+++ b/src/assets/icons/svg/workflow.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/svg/zip.svg b/src/assets/icons/svg/zip.svg
new file mode 100644
index 0000000..f806fc4
--- /dev/null
+++ b/src/assets/icons/svg/zip.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/images/dark.svg b/src/assets/images/dark.svg
new file mode 100644
index 0000000..f646bd7
--- /dev/null
+++ b/src/assets/images/dark.svg
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/assets/images/light.svg b/src/assets/images/light.svg
new file mode 100644
index 0000000..ab7cc08
--- /dev/null
+++ b/src/assets/images/light.svg
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/assets/images/login-background.jpg b/src/assets/images/login-background.jpg
new file mode 100644
index 0000000..fa6408b
Binary files /dev/null and b/src/assets/images/login-background.jpg differ
diff --git a/src/assets/images/profile.jpg b/src/assets/images/profile.jpg
new file mode 100644
index 0000000..f4fde57
Binary files /dev/null and b/src/assets/images/profile.jpg differ
diff --git a/src/assets/logo/logo.png b/src/assets/logo/logo.png
new file mode 100644
index 0000000..3f919d8
Binary files /dev/null and b/src/assets/logo/logo.png differ
diff --git a/src/assets/styles/btn.scss b/src/assets/styles/btn.scss
new file mode 100644
index 0000000..d614f2f
--- /dev/null
+++ b/src/assets/styles/btn.scss
@@ -0,0 +1,99 @@
+@use './variables.module.scss' as *;
+
+@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..e38c703
--- /dev/null
+++ b/src/assets/styles/element-ui.scss
@@ -0,0 +1,153 @@
+.el-collapse {
+ .collapse__title {
+ font-weight: 600;
+ padding: 0 8px;
+ font-size: 1.2em;
+ line-height: 1.1em;
+ }
+ .el-collapse-item__content {
+ padding: 0 8px;
+ }
+}
+
+.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__header {
+ padding: 16px 16px 8px 16px;
+ box-sizing: border-box;
+ border-bottom: 1px solid var(--brder-color);
+ margin-right: 0;
+ }
+ }
+ }
+}
+
+.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;
+}
+
+/* 当 el-form 的 inline 属性为 true 时 */
+/* 设置 label 的宽度默认为 68px */
+.el-form--inline .el-form-item__label {
+ width: 68px;
+}
+
+/* 设置 el-select 的宽度默认为 240px */
+.el-form--inline .el-select {
+ width: 240px;
+}
+
+/* 设置 el-input 的宽度默认为 240px */
+.el-form--inline .el-input {
+ width: 240px;
+}
+
+/* 设置 el-message-box 消息弹框内容强制换行 */
+.el-message-box .el-message-box__message {
+ word-break: break-word;
+}
diff --git a/src/assets/styles/index.scss b/src/assets/styles/index.scss
new file mode 100644
index 0000000..7228c52
--- /dev/null
+++ b/src/assets/styles/index.scss
@@ -0,0 +1,211 @@
+@use './variables.module.scss' as *;
+@use './mixin.scss';
+@use './transition.scss';
+@use './element-ui.scss';
+@use './sidebar.scss';
+@use './btn.scss';
+@use './ruoyi.scss';
+@use 'animate.css';
+@use '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;
+}
+
+.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..9526017
--- /dev/null
+++ b/src/assets/styles/ruoyi.scss
@@ -0,0 +1,284 @@
+@use './variables.module.scss' as *;
+
+/**
+ * 通用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 {
+ display: flex;
+ justify-content: flex-end;
+ margin-top: 20px;
+ padding: 10px 20px !important;
+}
+
+/* tree border */
+.tree-border {
+ margin-top: 5px;
+ border: 1px solid #e5e6e7;
+ background: #ffffff none;
+ border-radius: 4px;
+ width: 100%;
+}
+
+@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 {
+ 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..6ffcd33
--- /dev/null
+++ b/src/assets/styles/sidebar.scss
@@ -0,0 +1,296 @@
+@use './variables.module.scss' as *;
+
+#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: 0 0 8px 0 rgba(0, 0, 0, 0.1);
+
+ // 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: 10px;
+ }
+
+ .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 {
+ border-radius: 8px;
+ margin: 1px 5px 1px 5px;
+ &:hover {
+ background-color: $base-sub-menu-title-hover !important;
+ }
+ }
+ .sub-menu-title-noDropdown,
+ .el-sub-menu__title {
+ border-radius: 8px;
+ margin: 1px 5px 1px 5px;
+ &: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: calc($base-sidebar-width - 20px) !important;
+ border-radius: 8px;
+ height: 45px;
+ margin: 1px 5px 1px 5px;
+ &:not(.is-active):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;
+ border-radius: 8px;
+ height: 45px;
+ margin: 1px 5px 1px 5px;
+
+ &.is-active {
+ background-color: var(--el-menu-active-color) !important;
+ color: #fff;
+ }
+ &:not(.is-active):hover {
+ // you can use $sub-menuHover
+ background-color: $base-sub-menu-hover !important;
+ }
+ }
+
+ & .theme-dark .nest-menu .el-sub-menu > .el-sub-menu__title,
+ & .theme-dark .el-menu-item {
+ border-radius: 8px;
+ height: 45px;
+ margin: 1px 5px 1px 5px;
+
+ &.is-active {
+ background-color: var(--el-menu-active-color) !important;
+ color: #fff;
+ }
+ &:not(.is-active):hover {
+ // you can use $sub-menuHover
+ background-color: $base-menu-hover !important;
+ }
+ }
+
+ & .nest-menu .el-sub-menu > .el-sub-menu__title,
+ & .el-menu-item {
+ border-radius: 8px;
+ height: 45px;
+ margin: 1px 5px 1px 5px;
+
+ &.is-active {
+ background-color: var(--el-menu-active-color) !important;
+ color: #fff;
+ }
+ &:not(.is-active):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;
+ height: 45px;
+ // 选中状态的菜单
+ &.is-active {
+ background-color: var(--el-menu-active-color) !important;
+ color: #fff !important;
+ }
+
+ .el-tooltip {
+ padding: 0 !important;
+ }
+ }
+
+
+ & .el-sub-menu {
+ overflow: hidden;
+ border-radius: 8px;
+ .el-sub-menu__title.el-tooltip__trigger {
+ border-radius: 8px;
+ height: 45px;
+ }
+
+ // 选中状态的菜单
+ &.is-active .el-sub-menu__title.el-tooltip__trigger {
+ background-color: var(--el-menu-active-color) !important;
+ }
+
+
+ & > .el-sub-menu__title {
+ padding: 0 !important;
+ }
+ }
+
+ .el-menu--collapse {
+ .is-active .svg-icon {
+ fill: #fff;
+ }
+ .svg-icon {
+ display: flex;
+ margin: auto;
+ height: 100%;
+ // 这里设置width会跟随sidebar-container的transition 不符合预期
+ }
+ .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;
+ }
+ }
+}
+// 收起菜单后悬浮的菜单样式
+.el-popper.is-pure{
+ border-radius: 8px;
+ .el-menu--popup{
+ border-radius: 8px;
+ }
+ .el-menu-item{
+ border-radius: 4px;
+ }
+}
diff --git a/src/assets/styles/transition.scss b/src/assets/styles/transition.scss
new file mode 100644
index 0000000..7a548ca
--- /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-from,
+.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-from {
+ 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-from,
+.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..8f9c4bd
--- /dev/null
+++ b/src/assets/styles/variables.module.scss
@@ -0,0 +1,140 @@
+// 全局SCSS变量
+:root {
+ --menuBg: #1f2d3d;
+ --menuColor: #bfcbd9;
+ --menuActiveText: #f4f4f5;
+ --menuHover: #263445;
+
+ --subMenuBg: #1f2d3d;
+ --subMenuActiveText: #f4f4f5;
+ --subMenuHover: #001528;
+ --subMenuTitleHover: #293444;
+
+ // 菜单栏缩进
+ --el-menu-base-level-padding: 12px;
+ --el-menu-level-padding: 8px;
+ --el-menu-item-height: 50px;
+
+ --fixedHeaderBg: #ffffff;
+ --tableHeaderBg: #f8f8f9;
+ --tableHeaderTextColor: #515a6e;
+
+ // ele
+ --brder-color: #e8e8e8;
+
+ // 添加 tag 相关变量
+ --tags-view-active-bg: var(--el-color-primary);
+ --tags-view-active-border-color: var(--el-color-primary);
+}
+
+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;
+ }
+
+ .el-button--primary {
+ --el-button-bg-color: var(--el-color-primary-dark-6);
+ --el-button-border-color: var(--el-color-primary-light-2);
+ }
+
+ .el-switch {
+ --el-switch-on-color: var(--el-color-primary-dark-6);
+ --el-switch-border-color: var(--el-color-primary-light-2);
+ }
+
+ .el-tag--primary {
+ --el-tag-bg-color: var(--el-color-primary-dark-6);
+ --el-tag-border-color: var(--el-color-primary-light-2);
+ }
+
+ // 在深色模式下使用更深的颜色
+ --tags-view-active-bg: var(--el-color-primary-dark-6);
+ --tags-view-active-border-color: var(--el-color-primary-light-2);
+ // vxe-table 主题
+ --vxe-font-color: #98989e;
+ --vxe-primary-color: #2c7ecf;
+ --vxe-icon-background-color: #98989e;
+ --vxe-table-font-color: #98989e;
+ --vxe-table-resizable-color: #95969a;
+ --vxe-table-header-background-color: #28282a;
+ --vxe-table-body-background-color: #151518;
+ --vxe-table-background-color: #4a5663;
+ --vxe-table-border-width: 1px;
+ --vxe-table-border-color: #37373a;
+ --vxe-toolbar-background-color: #37373a;
+
+ // ele
+ --brder-color: #37373a;
+}
+
+// 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;
+}
\ No newline at end of file
diff --git a/src/components/Breadcrumb/index.vue b/src/components/Breadcrumb/index.vue
new file mode 100644
index 0000000..a9cdef4
--- /dev/null
+++ b/src/components/Breadcrumb/index.vue
@@ -0,0 +1,90 @@
+
+
+
+
+ {{ item.meta?.title }}
+ {{ item.meta?.title }}
+
+
+
+
+
+
+
+
diff --git a/src/components/DictTag/index.vue b/src/components/DictTag/index.vue
new file mode 100644
index 0000000..dbf5d7f
--- /dev/null
+++ b/src/components/DictTag/index.vue
@@ -0,0 +1,94 @@
+
+
+
+
+
+ {{ 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..67394aa
--- /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..1767b1b
--- /dev/null
+++ b/src/components/FileUpload/index.vue
@@ -0,0 +1,241 @@
+
+
+
+
+ 选取文件
+
+
+
+ 请上传
+
+ 大小不超过 {{ 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/IconSelect/index.vue b/src/components/IconSelect/index.vue
new file mode 100644
index 0000000..4be0219
--- /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..afe02c7
--- /dev/null
+++ b/src/components/ImagePreview/index.vue
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/ImageUpload/index.vue b/src/components/ImageUpload/index.vue
new file mode 100644
index 0000000..f81e4bc
--- /dev/null
+++ b/src/components/ImageUpload/index.vue
@@ -0,0 +1,242 @@
+
+
+
+
+
+
+
+
+
+ 请上传
+
+ 大小不超过 {{ 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..f615f61
--- /dev/null
+++ b/src/components/Pagination/index.vue
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
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/Process/MessageType.vue b/src/components/Process/MessageType.vue
new file mode 100644
index 0000000..4ef5fa9
--- /dev/null
+++ b/src/components/Process/MessageType.vue
@@ -0,0 +1,100 @@
+
+
+
+
+
+ 站内信
+ 邮件
+ 短信
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/Process/approvalButton.vue b/src/components/Process/approvalButton.vue
new file mode 100644
index 0000000..e53ad3b
--- /dev/null
+++ b/src/components/Process/approvalButton.vue
@@ -0,0 +1,57 @@
+
+
+
+ 暂存
+ 提 交
+ 审批
+ 流程进度
+
+
+
+ 返回
+
+
+
+
diff --git a/src/components/Process/approvalRecord.vue b/src/components/Process/approvalRecord.vue
new file mode 100644
index 0000000..2413f08
--- /dev/null
+++ b/src/components/Process/approvalRecord.vue
@@ -0,0 +1,127 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ item }}
+
+ 无
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 附件
+
+
+
+
+
+ 下载
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/Process/flowChart.vue b/src/components/Process/flowChart.vue
new file mode 100644
index 0000000..7603375
--- /dev/null
+++ b/src/components/Process/flowChart.vue
@@ -0,0 +1,40 @@
+
+
+
+
+
+
diff --git a/src/components/Process/flowChartImg.vue b/src/components/Process/flowChartImg.vue
new file mode 100644
index 0000000..ba35565
--- /dev/null
+++ b/src/components/Process/flowChartImg.vue
@@ -0,0 +1,154 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/Process/processMeddle.vue b/src/components/Process/processMeddle.vue
new file mode 100644
index 0000000..d024b69
--- /dev/null
+++ b/src/components/Process/processMeddle.vue
@@ -0,0 +1,211 @@
+
+
+
+ {{ task.nodeName }}
+ {{ task.nodeCode }}
+ {{ task.createTime }}
+ {{ task.instanceId }}
+ {{ task.version }}.0
+ {{ task.businessId }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 删除
+
+
+
+
+
+
+
+
diff --git a/src/components/Process/submitVerify.vue b/src/components/Process/submitVerify.vue
new file mode 100644
index 0000000..6163cfb
--- /dev/null
+++ b/src/components/Process/submitVerify.vue
@@ -0,0 +1,541 @@
+
+
+
+
+
+ 站内信
+ 邮件
+ 短信
+
+
+
+
+
+
+
+
+ {{ user.userName }}
+
+
+
+
+ 【{{ item.nodeName }}】:
+
+
+
+ 选择
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 站内信
+ 邮件
+ 短信
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 删除
+
+
+
+
+
+
+
+
+
diff --git a/src/components/RightToolbar/index.vue b/src/components/RightToolbar/index.vue
new file mode 100644
index 0000000..02a55f7
--- /dev/null
+++ b/src/components/RightToolbar/index.vue
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/RoleSelect/index.vue b/src/components/RoleSelect/index.vue
new file mode 100644
index 0000000..56a784b
--- /dev/null
+++ b/src/components/RoleSelect/index.vue
@@ -0,0 +1,250 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+ {{ role.roleName }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ proxy.parseTime(scope.row.createTime) }}
+
+
+
+
+
+
+
+ 取消
+ 确定
+
+
+
+
+
+
diff --git a/src/components/RuoYiDoc/index.vue b/src/components/RuoYiDoc/index.vue
new file mode 100644
index 0000000..8938b93
--- /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..8df3af8
--- /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..7222c0a
--- /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..b450a1c
--- /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..a7345fe
--- /dev/null
+++ b/src/components/TopNav/index.vue
@@ -0,0 +1,200 @@
+
+
+
+
+ {{ item.meta?.title }}
+
+
+
+
+ 更多菜单
+
+ {{ item.meta?.title }}
+
+
+
+
+
+
+
+
diff --git a/src/components/UserSelect/index.vue b/src/components/UserSelect/index.vue
new file mode 100644
index 0000000..c940b69
--- /dev/null
+++ b/src/components/UserSelect/index.vue
@@ -0,0 +1,311 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ resetQuery()">重置
+
+
+
+
+
+
+
+
+
+ {{ user.nickName }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ scope.row.createTime }}
+
+
+
+
+
+
+
+
+
+
+ 取消
+ 确定
+
+
+
+
+
+
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..0e605d3
--- /dev/null
+++ b/src/directive/common/copyText.ts
@@ -0,0 +1,67 @@
+/**
+ * v-copyText 复制文本内容
+ * Copyright (c) 2022 ruoyi
+ */
+import { DirectiveBinding } from 'vue';
+
+export default {
+ beforeMount(el: any, { value, arg }: DirectiveBinding) {
+ 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..c66da22
--- /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 === 'superadmin' || 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/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/hooks/useDialog.ts b/src/hooks/useDialog.ts
new file mode 100644
index 0000000..547f199
--- /dev/null
+++ b/src/hooks/useDialog.ts
@@ -0,0 +1,31 @@
+import { Ref } from 'vue';
+
+interface Options {
+ title?: string;
+}
+interface Return {
+ title: Ref;
+ visible: Ref;
+ openDialog: () => void;
+ closeDialog: () => void;
+}
+export default (ops?: Options): Return => {
+ const visible = ref(false);
+ const title = ref(ops.title || '');
+
+ const openDialog = () => {
+ visible.value = true;
+ };
+
+ const closeDialog = () => {
+ visible.value = false;
+ };
+
+ return {
+ title,
+ visible,
+
+ openDialog,
+ closeDialog
+ };
+};
diff --git a/src/lang/en_US.ts b/src/lang/en_US.ts
new file mode 100644
index 0000000..b090d3e
--- /dev/null
+++ b/src/lang/en_US.ts
@@ -0,0 +1,85 @@
+export default {
+ // 路由国际化
+ route: {
+ dashboard: 'Dashboard',
+ document: 'Document'
+ },
+ // 登录页面国际化
+ login: {
+ selectPlaceholder: 'Please select/enter a company name',
+ username: 'Username',
+ password: 'Password',
+ login: 'Login',
+ logging: 'Logging...',
+ code: 'Verification Code',
+ rememberPassword: 'Remember me',
+ switchRegisterPage: 'Sign up now',
+ rule: {
+ tenantId: {
+ required: 'Please enter your tenant id'
+ },
+ username: {
+ required: 'Please enter your account'
+ },
+ password: {
+ required: 'Please enter your password'
+ },
+ code: {
+ required: 'Please enter a verification code'
+ }
+ },
+ social: {
+ wechat: 'Wechat Login',
+ maxkey: 'MaxKey Login',
+ topiam: 'TopIam Login',
+ gitee: 'Gitee Login',
+ github: 'Github Login'
+ }
+ },
+ // 注册页面国际化
+ register: {
+ selectPlaceholder: 'Please select/enter a company name',
+ username: 'Username',
+ password: 'Password',
+ confirmPassword: 'Confirm Password',
+ register: 'Register',
+ registering: 'Registering...',
+ registerSuccess: 'Congratulations, your {username} account has been registered!',
+ code: 'Verification Code',
+ switchLoginPage: 'Log in with an existing account',
+ rule: {
+ tenantId: {
+ required: 'Please enter your tenant id'
+ },
+ username: {
+ required: 'Please enter your account',
+ length: 'The length of the user account must be between {min} and {max}'
+ },
+ password: {
+ required: 'Please enter your password',
+ length: 'The user password must be between {min} and {max} in length',
+ pattern: "Can't contain illegal characters: {strings}"
+ },
+ code: {
+ required: 'Please enter a verification code'
+ },
+ confirmPassword: {
+ required: 'Please enter your password again',
+ equalToPassword: 'The password entered twice is inconsistent'
+ }
+ }
+ },
+ // 导航栏国际化
+ 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..4f8e74c
--- /dev/null
+++ b/src/lang/index.ts
@@ -0,0 +1,33 @@
+// 自定义国际化配置
+import { createI18n } from 'vue-i18n';
+
+import { LanguageEnum } from '@/enums/LanguageEnum';
+import zh_CN from '@/lang/zh_CN';
+import en_US from '@/lang/en_US';
+
+/**
+ * 获取当前语言
+ * @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: {
+ zh_CN: zh_CN,
+ en_US: en_US
+ }
+});
+
+export default i18n;
+
+export type LanguageType = typeof zh_CN;
diff --git a/src/lang/zh_CN.ts b/src/lang/zh_CN.ts
new file mode 100644
index 0000000..3cc9872
--- /dev/null
+++ b/src/lang/zh_CN.ts
@@ -0,0 +1,85 @@
+export default {
+ // 路由国际化
+ route: {
+ dashboard: '首页',
+ document: '项目文档'
+ },
+ // 登录页面国际化
+ login: {
+ selectPlaceholder: '请选择/输入公司名称',
+ username: '用户名',
+ password: '密码',
+ login: '登 录',
+ logging: '登 录 中...',
+ code: '验证码',
+ rememberPassword: '记住我',
+ switchRegisterPage: '立即注册',
+ rule: {
+ tenantId: {
+ required: '请输入您的租户编号'
+ },
+ username: {
+ required: '请输入您的账号'
+ },
+ password: {
+ required: '请输入您的密码'
+ },
+ code: {
+ required: '请输入验证码'
+ }
+ },
+ social: {
+ wechat: '微信登录',
+ maxkey: 'MaxKey登录',
+ topiam: 'TopIam登录',
+ gitee: 'Gitee登录',
+ github: 'Github登录'
+ }
+ },
+ // 注册页面国际化
+ register: {
+ selectPlaceholder: '请选择/输入公司名称',
+ username: '用户名',
+ password: '密码',
+ confirmPassword: '确认密码',
+ register: '注 册',
+ registering: '注 册 中...',
+ registerSuccess: '恭喜你,您的账号 {username} 注册成功!',
+ code: '验证码',
+ switchLoginPage: '使用已有账户登录',
+ rule: {
+ tenantId: {
+ required: '请输入您的租户编号'
+ },
+ username: {
+ required: '请输入您的账号',
+ length: '用户账号长度必须介于 {min} 和 {max} 之间'
+ },
+ password: {
+ required: '请输入您的密码',
+ length: '用户密码长度必须介于 {min} 和 {max} 之间',
+ pattern: '不能包含非法字符:{strings}'
+ },
+ code: {
+ required: '请输入验证码'
+ },
+ confirmPassword: {
+ required: '请再次输入您的密码',
+ equalToPassword: '两次输入的密码不一致'
+ }
+ }
+ },
+ // 导航栏国际化
+ 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..b6c7b61
--- /dev/null
+++ b/src/layout/components/AppMain.vue
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+
diff --git a/src/layout/components/IframeToggle/index.vue b/src/layout/components/IframeToggle/index.vue
new file mode 100644
index 0000000..62a834b
--- /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..efb1ae3
--- /dev/null
+++ b/src/layout/components/Navbar.vue
@@ -0,0 +1,308 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/layout/components/Settings/index.vue b/src/layout/components/Settings/index.vue
new file mode 100644
index 0000000..bbd53cb
--- /dev/null
+++ b/src/layout/components/Settings/index.vue
@@ -0,0 +1,246 @@
+
+
+ 主题风格设置
+
+
+
+
+
+
+
+
+
+
+
+
+ 主题颜色
+
+
+
+
+
+ 深色模式
+
+
+
+
+
+
+
+ 系统布局配置
+
+
+ 开启 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..511d788
--- /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..a11159b
--- /dev/null
+++ b/src/layout/components/Sidebar/SidebarItem.vue
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/layout/components/Sidebar/index.vue b/src/layout/components/Sidebar/index.vue
new file mode 100644
index 0000000..b6ecbfa
--- /dev/null
+++ b/src/layout/components/Sidebar/index.vue
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/layout/components/SocialCallback/index.vue b/src/layout/components/SocialCallback/index.vue
new file mode 100644
index 0000000..a5cd768
--- /dev/null
+++ b/src/layout/components/SocialCallback/index.vue
@@ -0,0 +1,95 @@
+
+
+
+
+
diff --git a/src/layout/components/TagsView/ScrollPane.vue b/src/layout/components/TagsView/ScrollPane.vue
new file mode 100644
index 0000000..e24f5ec
--- /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..e9ee675
--- /dev/null
+++ b/src/layout/components/TagsView/index.vue
@@ -0,0 +1,347 @@
+
+
+
+
+
+ {{ tag.title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/layout/components/TopBar/search.vue b/src/layout/components/TopBar/search.vue
new file mode 100644
index 0000000..9cb36db
--- /dev/null
+++ b/src/layout/components/TopBar/search.vue
@@ -0,0 +1,158 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ 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..684ae6a
--- /dev/null
+++ b/src/layout/components/notice/index.vue
@@ -0,0 +1,130 @@
+
+
+
+
+
+
+
+
{{ v.message }}
+
+
{{ v.time }}
+
+
+
已读
+
未读
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/layout/index.vue b/src/layout/index.vue
new file mode 100644
index 0000000..b2d06dc
--- /dev/null
+++ b/src/layout/index.vue
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+
diff --git a/src/main.ts b/src/main.ts
new file mode 100644
index 0000000..16ac610
--- /dev/null
+++ b/src/main.ts
@@ -0,0 +1,57 @@
+import { createApp } from 'vue';
+// global css
+import 'virtual:uno.css';
+import 'element-plus/theme-chalk/dark/css-vars.css';
+import '@/assets/styles/index.scss';
+
+// App、router、store
+import App from './App.vue';
+import store from './store';
+import router from './router';
+
+// 自定义指令
+import directive from './directive';
+
+// 注册插件
+import plugins from './plugins/index'; // plugins
+
+// 高亮组件
+// import 'highlight.js/styles/a11y-light.css';
+import 'highlight.js/styles/atom-one-dark.css';
+import 'highlight.js/lib/common';
+import HighLight from '@highlightjs/vue-plugin';
+
+// svg图标
+import 'virtual:svg-icons-register';
+import ElementIcons from '@/plugins/svgicon';
+
+// permission control
+import './permission';
+
+// 国际化
+import i18n from '@/lang/index';
+
+// vxeTable
+import VXETable from 'vxe-table';
+import 'vxe-table/lib/style.css';
+VXETable.setConfig({
+ zIndex: 999999
+});
+
+// 修改 el-dialog 默认点击遮照为不关闭
+import { ElDialog } from 'element-plus';
+ElDialog.props.closeOnClickModal.default = false;
+
+const app = createApp(App);
+
+app.use(HighLight);
+app.use(ElementIcons);
+app.use(router);
+app.use(store);
+app.use(i18n);
+app.use(VXETable);
+app.use(plugins);
+// 自定义指令
+directive(app);
+
+app.mount('#app');
diff --git a/src/permission.ts b/src/permission.ts
new file mode 100644
index 0000000..fb60b07
--- /dev/null
+++ b/src/permission.ts
@@ -0,0 +1,70 @@
+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, isPathMatch } 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';
+import { ElMessage } from 'element-plus/es';
+
+NProgress.configure({ showSpinner: false });
+const whiteList = ['/login', '/register', '/social-callback', '/register*', '/register/*'];
+
+const isWhiteList = (path: string) => {
+ return whiteList.some((pattern) => isPathMatch(pattern, path));
+};
+
+router.beforeEach(async (to, from, next) => {
+ NProgress.start();
+ if (getToken()) {
+ to.meta.title && useSettingsStore().setTitle(to.meta.title as string);
+ /* has token*/
+ if (to.path === '/login') {
+ next({ path: '/' });
+ NProgress.done();
+ } else if (isWhiteList(to.path)) {
+ 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); // 动态添加可访问路由表
+ }
+ });
+ // @ts-expect-error hack方法 确保addRoutes已完成
+ 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 (isWhiteList(to.path)) {
+ // 在免登录白名单,直接进入
+ next();
+ } else {
+ const redirect = encodeURIComponent(to.fullPath || '/');
+ next(`/login?redirect=${redirect}`); // 否则全部重定向到登录页
+ NProgress.done();
+ }
+ }
+});
+
+router.afterEach(() => {
+ NProgress.done();
+});
diff --git a/src/plugins/auth.ts b/src/plugins/auth.ts
new file mode 100644
index 0000000..a5aaef9
--- /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..e5ceead
--- /dev/null
+++ b/src/plugins/cache.ts
@@ -0,0 +1,79 @@
+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);
+ }
+ return null;
+ },
+ 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);
+ }
+ return null;
+ },
+ 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..1f673b3
--- /dev/null
+++ b/src/plugins/tab.ts
@@ -0,0 +1,96 @@
+import router from '@/router';
+import { RouteLocationMatched, RouteLocationNormalized, RouteLocationRaw } 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: RouteLocationRaw): 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..a6497db
--- /dev/null
+++ b/src/router/index.ts
@@ -0,0 +1,114 @@
+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[] = [
+
+];
+
+/**
+ * 创建路由
+ */
+const router = createRouter({
+ history: createWebHistory(import.meta.env.VITE_APP_CONTEXT_PATH),
+ routes: constantRoutes,
+ // 刷新时,滚动条位置还原
+ scrollBehavior(to, from, savedPosition) {
+ if (savedPosition) {
+ return savedPosition;
+ }
+ return { top: 0 };
+ }
+});
+
+export default router;
diff --git a/src/settings.ts b/src/settings.ts
new file mode 100644
index 0000000..5d53a23
--- /dev/null
+++ b/src/settings.ts
@@ -0,0 +1,75 @@
+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,
+
+ /**
+ * 显示页签图标
+ */
+ tagsIcon: false,
+
+ /**
+ * 是否固定头部
+ */
+ fixedHeader: false,
+
+ /**
+ * 是否显示logo
+ */
+ sidebarLogo: true,
+
+ /**
+ * 是否显示动态标题
+ */
+ dynamicTitle: false,
+
+ /**
+ * 是否开启动画 开启随机 关闭渐进渐出
+ */
+ 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..02ae96a
--- /dev/null
+++ b/src/store/index.ts
@@ -0,0 +1,5 @@
+import { createPinia } from 'pinia';
+
+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..54edc36
--- /dev/null
+++ b/src/store/modules/app.ts
@@ -0,0 +1,73 @@
+import zhCN from 'element-plus/es/locale/lang/zh-cn';
+import enUS from 'element-plus/es/locale/lang/en';
+import { defineStore } from 'pinia';
+import { useStorage } from '@vueuse/core';
+import { ref, reactive, computed } from 'vue';
+
+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
+ };
+});
diff --git a/src/store/modules/dict.ts b/src/store/modules/dict.ts
new file mode 100644
index 0000000..742f900
--- /dev/null
+++ b/src/store/modules/dict.ts
@@ -0,0 +1,65 @@
+import { defineStore } from 'pinia';
+
+export const useDictStore = defineStore('dict', () => {
+ const dict = ref>(new Map());
+
+ /**
+ * 获取字典
+ * @param _key 字典key
+ */
+ const getDict = (_key: string): DictDataOption[] | null => {
+ if (!_key) {
+ return null;
+ }
+ return dict.value.get(_key) || null;
+ };
+
+ /**
+ * 设置字典
+ * @param _key 字典key
+ * @param _value 字典value
+ */
+ const setDict = (_key: string, _value: DictDataOption[]) => {
+ if (!_key) {
+ return false;
+ }
+ try {
+ dict.value.set(_key, _value);
+ return true;
+ } catch (e) {
+ console.error('Error in setDict:', e);
+ return false;
+ }
+ };
+
+ /**
+ * 删除字典
+ * @param _key
+ */
+ const removeDict = (_key: string): boolean => {
+ if (!_key) {
+ return false;
+ }
+ try {
+ return dict.value.delete(_key);
+ } catch (e) {
+ console.error('Error in removeDict:', e);
+ return false;
+ }
+ };
+
+ /**
+ * 清空字典
+ */
+ const cleanDict = (): void => {
+ dict.value.clear();
+ };
+
+ return {
+ dict,
+ getDict,
+ setDict,
+ removeDict,
+ cleanDict
+ };
+});
diff --git a/src/store/modules/notice.ts b/src/store/modules/notice.ts
new file mode 100644
index 0000000..ff1e8d2
--- /dev/null
+++ b/src/store/modules/notice.ts
@@ -0,0 +1,41 @@
+import { defineStore } from 'pinia';
+import { reactive } from 'vue';
+
+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
+ };
+});
diff --git a/src/store/modules/permission.ts b/src/store/modules/permission.ts
new file mode 100644
index 0000000..1169cc8
--- /dev/null
+++ b/src/store/modules/permission.ts
@@ -0,0 +1,207 @@
+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';
+import { ref } from 'vue';
+import { createCustomNameComponent } from '@/utils/createCustomNameComponent';
+
+// 匹配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 as RouteRecordRaw[];
+ };
+ const getDefaultRoutes = (): RouteRecordRaw[] => {
+ return defaultRoutes.value as RouteRecordRaw[];
+ };
+ const getSidebarRoutes = (): RouteRecordRaw[] => {
+ return sidebarRouters.value as RouteRecordRaw[];
+ };
+ const getTopbarRoutes = (): RouteRecordRaw[] => {
+ return topbarRouters.value as RouteRecordRaw[];
+ };
+
+ 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);
+ // 路由name重复检查
+ duplicateRouteChecker(asyncRoutes, sidebarRoutes);
+ 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, route.name as string);
+ }
+ 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) => {
+ el.path = lastRouter ? lastRouter.path + '/' + el.path : el.path;
+ if (el.children && el.children.length && el.component?.toString() === 'ParentView') {
+ children = children.concat(filterChildren(el.children, el));
+ } else {
+ children.push(el);
+ }
+ });
+ return children;
+ };
+ return {
+ routes,
+ topbarRouters,
+ sidebarRouters,
+ defaultRoutes,
+
+ getRoutes,
+ getDefaultRoutes,
+ 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, name: string) => {
+ let res;
+ for (const path in modules) {
+ const viewsIndex = path.indexOf('/views/');
+ let dir = path.substring(viewsIndex + 7);
+ dir = dir.substring(0, dir.lastIndexOf('.vue'));
+ if (dir === view) {
+ res = createCustomNameComponent(modules[path], { name });
+ return res;
+ }
+ }
+ return res;
+};
+
+// 非setup
+export const usePermissionStoreHook = () => {
+ return usePermissionStore(store);
+};
+
+interface Route {
+ name?: string | symbol;
+ path: string;
+ children?: Route[];
+}
+
+/**
+ * 检查路由name是否重复
+ * @param localRoutes 本地路由
+ * @param routes 动态路由
+ */
+function duplicateRouteChecker(localRoutes: Route[], routes: Route[]) {
+ // 展平
+ function flatRoutes(routes: Route[]) {
+ const res: Route[] = [];
+ routes.forEach((route) => {
+ if (route.children) {
+ res.push(...flatRoutes(route.children));
+ } else {
+ res.push(route);
+ }
+ });
+ return res;
+ }
+
+ const allRoutes = flatRoutes([...localRoutes, ...routes]);
+
+ const nameList: string[] = [];
+ allRoutes.forEach((route) => {
+ const name = route.name.toString();
+ if (name && nameList.includes(name)) {
+ const message = `路由名称: [${name}] 重复, 会造成 404`;
+ console.error(message);
+ ElNotification({
+ title: '路由名称重复',
+ message,
+ type: 'error'
+ });
+ return;
+ }
+ nameList.push(route.name.toString());
+ });
+}
diff --git a/src/store/modules/settings.ts b/src/store/modules/settings.ts
new file mode 100644
index 0000000..7d0a4c9
--- /dev/null
+++ b/src/store/modules/settings.ts
@@ -0,0 +1,50 @@
+import { defineStore } from 'pinia';
+import defaultSettings from '@/settings';
+import { useDynamicTitle } from '@/utils/dynamicTitle';
+import { useStorage } from '@vueuse/core';
+import { ref } from 'vue';
+
+export const useSettingsStore = defineStore('setting', () => {
+ const storageSetting = useStorage('layout-setting', {
+ topNav: defaultSettings.topNav,
+ tagsView: defaultSettings.tagsView,
+ tagsIcon: defaultSettings.tagsIcon,
+ 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 tagsIcon = ref(storageSetting.value.tagsIcon);
+ 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,
+ tagsIcon,
+ fixedHeader,
+ sidebarLogo,
+ dynamicTitle,
+ animationEnable,
+ dark,
+ setTitle
+ };
+});
diff --git a/src/store/modules/tagsView.ts b/src/store/modules/tagsView.ts
new file mode 100644
index 0000000..dc8850c
--- /dev/null
+++ b/src/store/modules/tagsView.ts
@@ -0,0 +1,234 @@
+import { RouteLocationNormalized } from 'vue-router';
+import { defineStore } from 'pinia';
+import { ref } from 'vue';
+
+export const useTagsViewStore = defineStore('tagsView', () => {
+ const visitedViews = ref([]);
+ const cachedViews = ref([]);
+ const iframeViews = ref([]);
+
+ const getVisitedViews = (): RouteLocationNormalized[] => {
+ return visitedViews.value as RouteLocationNormalized[];
+ };
+ const getIframeViews = (): RouteLocationNormalized[] => {
+ return iframeViews.value as RouteLocationNormalized[];
+ };
+ 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 as RouteLocationNormalized[])]);
+ });
+ };
+ 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 as RouteLocationNormalized[])],
+ 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 as RouteLocationNormalized[])]);
+ });
+ };
+ 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 as RouteLocationNormalized[])],
+ 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 as RouteLocationNormalized[])]);
+ });
+ };
+ 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 as RouteLocationNormalized[])],
+ cachedViews: [...cachedViews.value]
+ });
+ });
+ };
+ const delAllVisitedViews = (): Promise => {
+ return new Promise((resolve) => {
+ visitedViews.value = visitedViews.value.filter((tag: RouteLocationNormalized) => tag.meta?.affix);
+ resolve([...(visitedViews.value as RouteLocationNormalized[])]);
+ });
+ };
+
+ 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 as RouteLocationNormalized[])]);
+ });
+ };
+ 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 as RouteLocationNormalized[])]);
+ });
+ };
+
+ 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
+ };
+});
diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts
new file mode 100644
index 0000000..b714d03
--- /dev/null
+++ b/src/store/modules/user.ts
@@ -0,0 +1,86 @@
+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 { defineStore } from 'pinia';
+import { ref } from 'vue';
+
+export const useUserStore = defineStore('user', () => {
+ const token = ref(getToken());
+ const name = ref('');
+ const nickname = ref('');
+ const userId = ref('');
+ const tenantId = 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;
+ tenantId.value = user.tenantId;
+ 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,
+ tenantId,
+ token,
+ nickname,
+ avatar,
+ roles,
+ permissions,
+ login,
+ getInfo,
+ logout,
+ setAvatar
+ };
+});
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..ae6ecfc
--- /dev/null
+++ b/src/types/element.d.ts
@@ -0,0 +1,35 @@
+import type * as ep from 'element-plus';
+declare global {
+ declare type ElTagType = 'primary' | '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..1fb9f62
--- /dev/null
+++ b/src/types/env.d.ts
@@ -0,0 +1,27 @@
+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_MONITOR_ADMIN: string;
+ VITE_APP_SNAILJOB_ADMIN: string;
+ VITE_APP_ENV: string;
+ VITE_APP_ENCRYPT: string;
+ VITE_APP_RSA_PUBLIC_KEY: string;
+ VITE_APP_RSA_PRIVATE_KEY: string;
+ VITE_APP_CLIENT_ID: string;
+ VITE_APP_WEBSOCKET: string;
+ VITE_APP_SSE: 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..a31f87c
--- /dev/null
+++ b/src/types/global.d.ts
@@ -0,0 +1,166 @@
+import type { PropType as VuePropType, ComponentInternalInstance as ComponentInstance } from 'vue';
+import { LanguageEnum } from '@/enums/LanguageEnum';
+
+declare global {
+ /** vue Instance */
+ declare type ComponentInternalInstance = ComponentInstance;
+
+ /**
+ * 界面字段隐藏属性
+ */
+ 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 {
+ createBy?: any;
+ createDept?: any;
+ createTime?: string;
+ updateBy?: any;
+ updateTime?: any;
+ }
+
+ /**
+ * 分页数据
+ * T : 表单数据
+ * D : 查询参数
+ */
+ declare interface PageData {
+ form: T;
+ queryParams: D;
+ rules: ElFormRules;
+ }
+ /**
+ * 分页查询参数
+ */
+ declare interface PageQuery {
+ pageNum: number;
+ pageSize: number;
+ }
+ declare interface LayoutSetting {
+ /**
+ * 是否显示顶部导航
+ */
+ topNav: boolean;
+
+ /**
+ * 是否显示多标签导航
+ */
+ tagsView: boolean;
+ /**
+ * 显示页签图标
+ */
+ tagsIcon: 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;
+ }
+}
+export {};
diff --git a/src/types/module.d.ts b/src/types/module.d.ts
new file mode 100644
index 0000000..3b2f378
--- /dev/null
+++ b/src/types/module.d.ts
@@ -0,0 +1,50 @@
+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';
+import type { LanguageType } from '@/lang';
+
+export {};
+
+declare module '@vue/runtime-core' {
+ interface ComponentCustomProperties {
+ // 全局方法声明
+ $modal: typeof modal;
+ $tab: typeof tab;
+ $download: typeof download;
+ $auth: typeof auth;
+ $cache: typeof cache;
+ animate: typeof animate;
+ /**
+ * i18n $t方法支持ts类型提示
+ * @param key i18n key
+ */
+ $t(key: ObjKeysToUnion): string;
+
+ 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;
+ }
+}
+
+/**
+ * { a: 1, b: { ba: { baa: 1, bab: 2 }, bb: 2} } ---> a | b.ba.baa | b.ba.bab | b.bb
+ * https://juejin.cn/post/7280062870670606397
+ */
+export type ObjKeysToUnion = T extends object
+ ? {
+ [K in keyof T]: ObjKeysToUnion;
+ }[keyof T]
+ : P;
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/createCustomNameComponent.tsx b/src/utils/createCustomNameComponent.tsx
new file mode 100644
index 0000000..daf5866
--- /dev/null
+++ b/src/utils/createCustomNameComponent.tsx
@@ -0,0 +1,39 @@
+/**
+ * 后台返回的路由动态生成name 解决缓存问题
+ * 感谢 @fourteendp
+ * 详见 https://github.com/vbenjs/vue-vben-admin/issues/3927
+ */
+import { Component, defineComponent, h } from 'vue';
+
+interface Options {
+ name?: string;
+}
+
+export function createCustomNameComponent(loader: () => Promise, options: Options = {}): () => Promise {
+ const { name } = options;
+ let component: Component | null = null;
+
+ const load = async () => {
+ try {
+ const { default: loadedComponent } = await loader();
+ component = loadedComponent;
+ } catch (error) {
+ console.error(`Cannot resolve component ${name}, error:`, error);
+ }
+ };
+
+ return async () => {
+ if (!component) {
+ await load();
+ }
+
+ return Promise.resolve(
+ defineComponent({
+ name,
+ render() {
+ return h(component as Component);
+ }
+ })
+ );
+ };
+}
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..eb43d65
--- /dev/null
+++ b/src/utils/dict.ts
@@ -0,0 +1,26 @@
+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[];
+ }>({});
+
+ 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..6df068e
--- /dev/null
+++ b/src/utils/jsencrypt.ts
@@ -0,0 +1,21 @@
+import JSEncrypt from 'jsencrypt/bin/jsencrypt.min.js';
+// 密钥对生成 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..e9a0f7f
--- /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..f3b06ad
--- /dev/null
+++ b/src/utils/request.ts
@@ -0,0 +1,208 @@
+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';
+import router from '@/router';
+
+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 (import.meta.env.VITE_APP_ENCRYPT === 'true') {
+ // 当开启参数加密
+ 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) => {
+ if (import.meta.env.VITE_APP_ENCRYPT === 'true') {
+ // 加密后的 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(() => {
+ router.replace({
+ path: '/login',
+ query: {
+ redirect: encodeURIComponent(router.currentRoute.value.fullPath || '/')
+ }
+ })
+ });
+ }).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 blob = new Blob([resp]);
+ const resText = await blob.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..8cb9f97
--- /dev/null
+++ b/src/utils/ruoyi.ts
@@ -0,0 +1,236 @@
+// 日期格式化
+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 tree: T[] = [];
+ for (const d of data) {
+ const id = d[config.id];
+ childrenListMap[id] = d;
+ if (!d[config.childrenList]) {
+ d[config.childrenList] = [];
+ }
+ }
+
+ for (const d of data) {
+ const parentId = d[config.parentId];
+ const parentObj = childrenListMap[parentId];
+ if (!parentObj) {
+ tree.push(d);
+ } else {
+ parentObj[config.childrenList].push(d);
+ }
+ }
+ 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/sse.ts b/src/utils/sse.ts
new file mode 100644
index 0000000..059c8f8
--- /dev/null
+++ b/src/utils/sse.ts
@@ -0,0 +1,42 @@
+import { getToken } from '@/utils/auth';
+import { ElNotification } from 'element-plus';
+import { useNoticeStore } from '@/store/modules/notice';
+
+// 初始化
+export const initSSE = (url: any) => {
+ if (import.meta.env.VITE_APP_SSE === 'false') {
+ return;
+ }
+
+ url = url + '?Authorization=Bearer ' + getToken() + '&clientid=' + import.meta.env.VITE_APP_CLIENT_ID;
+ const { data, error } = useEventSource(url, [], {
+ autoReconnect: {
+ retries: 5,
+ delay: 5000,
+ onFailed() {
+ console.log('Failed to connect after 5 retries');
+ }
+ }
+ });
+
+ watch(error, () => {
+ console.log('SSE connection error:', error.value);
+ error.value = null;
+ });
+
+ watch(data, () => {
+ if (!data.value) return;
+ useNoticeStore().addNotice({
+ message: data.value,
+ read: false,
+ time: new Date().toLocaleString()
+ });
+ ElNotification({
+ title: '消息',
+ message: data.value,
+ type: 'success',
+ duration: 3000
+ });
+ data.value = null;
+ });
+};
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..e2886e4
--- /dev/null
+++ b/src/utils/validate.ts
@@ -0,0 +1,108 @@
+/**
+ * 路径匹配器
+ * @param {string} pattern
+ * @param {string} path
+ * @returns {Boolean}
+ */
+export function isPathMatch(pattern: string, path: string) {
+ const regexPattern = pattern
+ .replace(/\//g, '\\/')
+ .replace(/\*\*/g, '__DOUBLE_STAR__')
+ .replace(/\*/g, '[^\\/]*')
+ .replace(/__DOUBLE_STAR__/g, '.*');
+ const regex = new RegExp(`^${regexPattern}$`);
+ return regex.test(path);
+}
+
+/**
+ * 判断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..d8e3efc
--- /dev/null
+++ b/src/utils/websocket.ts
@@ -0,0 +1,51 @@
+import { getToken } from '@/utils/auth';
+import { ElNotification } from 'element-plus';
+import { useNoticeStore } from '@/store/modules/notice';
+
+// 初始化socket
+export const initWebSocket = (url: any) => {
+ if (import.meta.env.VITE_APP_WEBSOCKET === 'false') {
+ return;
+ }
+ url = url + '?Authorization=Bearer ' + getToken() + '&clientid=' + import.meta.env.VITE_APP_CLIENT_ID;
+ useWebSocket(url, {
+ autoReconnect: {
+ // 重连最大次数
+ retries: 3,
+ // 重连间隔
+ delay: 1000,
+ onFailed() {
+ console.log('websocket重连失败');
+ }
+ },
+ heartbeat: {
+ message: JSON.stringify({ type: 'ping' }),
+ // 发送心跳的间隔
+ interval: 10000,
+ // 接收到心跳response的超时时间
+ pongTimeout: 2000
+ },
+ onConnected() {
+ console.log('websocket已经连接');
+ },
+ onDisconnected() {
+ console.log('websocket已经断开');
+ },
+ onMessage: (_, e) => {
+ 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
+ });
+ }
+ });
+};
diff --git a/src/views/demo/demo/index.vue b/src/views/demo/demo/index.vue
new file mode 100644
index 0000000..e064f40
--- /dev/null
+++ b/src/views/demo/demo/index.vue
@@ -0,0 +1,245 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+ 新增
+
+
+ 修改
+
+
+ 删除
+
+
+ 导出
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/demo/tree/index.vue b/src/views/demo/tree/index.vue
new file mode 100644
index 0000000..ef923be
--- /dev/null
+++ b/src/views/demo/tree/index.vue
@@ -0,0 +1,259 @@
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+ 新增
+
+
+ 展开/折叠
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/error/401.vue b/src/views/error/401.vue
new file mode 100644
index 0000000..f90f50b
--- /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..0b20053
--- /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..bfda3f2
--- /dev/null
+++ b/src/views/index.vue
@@ -0,0 +1,165 @@
+
+
+
+
+ RuoYi-Vue-Plus多租户管理系统
+
+ RuoYi-Vue-Plus 是基于 RuoYi-Vue 针对 分布式集群 场景升级(不兼容原框架)
+
+ * 前端开发框架 Vue3、TS、Element Plus
+ * 后端开发框架 Spring Boot
+ * 容器框架 Undertow 基于 Netty 的高性能容器
+ * 权限认证框架 Sa-Token 支持多终端认证系统
+ * 关系数据库 MySQL 适配 8.X 最低 5.7
+ * 缓存数据库 Redis 适配 6.X 最低 4.X
+ * 数据库框架 Mybatis-Plus 快速 CRUD 增加开发效率
+ * 数据库框架 p6spy 更强劲的 SQL 分析
+ * 多数据源框架 dynamic-datasource 支持主从与多种类数据库异构
+ * 序列化框架 Jackson 统一使用 jackson 高效可靠
+ * Redis客户端 Redisson 性能强劲、API丰富
+ * 分布式限流 Redisson 全局、请求IP、集群ID 多种限流
+ * 分布式锁 Lock4j 注解锁、工具锁 多种多样
+ * 分布式幂等 Lock4j 基于分布式锁实现
+ * 分布式链路追踪 SkyWalking 支持链路追踪、网格分析、度量聚合、可视化
+ * 分布式任务调度 SnailJob 高性能 高可靠 易扩展
+ * 文件存储 Minio 本地存储
+ * 文件存储 七牛、阿里、腾讯 云存储
+ * 监控框架 SpringBoot-Admin 全方位服务监控
+ * 校验框架 Validation 增强接口安全性 严谨性
+ * Excel框架 FastExcel(原Alibaba EasyExcel) 性能优异 扩展性强
+ * 文档框架 SpringDoc、javadoc 无注解零入侵基于java注释
+ * 工具类框架 Hutool、Lombok 减少代码冗余 增加安全性
+ * 代码生成器 适配MP、SpringDoc规范化代码 一键生成前后端代码
+ * 部署方式 Docker 容器编排 一键部署业务集群
+ * 国际化 SpringMessage Spring标准国际化方案
+
+ 当前版本: v5.5.0
+
+ ¥免费开源
+
+
+ 访问码云
+ 访问GitHub
+ 更新日志
+
+
+
+
+ RuoYi-Cloud-Plus多租户微服务管理系统
+
+ RuoYi-Cloud-Plus 微服务通用权限管理系统 重写 RuoYi-Cloud 全方位升级(不兼容原框架)
+
+ * 前端开发框架 Vue3、TS、Element UI
+ * 后端开发框架 Spring Boot
+ * 微服务开发框架 Spring Cloud、Spring Cloud Alibaba
+ * 容器框架 Undertow 基于 XNIO 的高性能容器
+ * 权限认证框架 Sa-Token、Jwt 支持多终端认证系统
+ * 关系数据库 MySQL 适配 8.X 最低 5.7
+ * 关系数据库 Oracle 适配 11g 12c
+ * 关系数据库 PostgreSQL 适配 13 14
+ * 关系数据库 SQLServer 适配 2017 2019
+ * 缓存数据库 Redis 适配 6.X 最低 5.X
+ * 分布式注册中心 Alibaba Nacos 采用2.X 基于GRPC通信高性能
+ * 分布式配置中心 Alibaba Nacos 采用2.X 基于GRPC通信高性能
+ * 服务网关 Spring Cloud Gateway 响应式高性能网关
+ * 负载均衡 Spring Cloud Loadbalancer 负载均衡处理
+ * RPC远程调用 Apache Dubbo 原生态使用体验、高性能
+ * 分布式限流熔断 Alibaba Sentinel 无侵入、高扩展
+ * 分布式事务 Alibaba Seata 无侵入、高扩展 支持 四种模式
+ * 分布式消息队列 Apache Kafka 高性能高速度
+ * 分布式消息队列 Apache RocketMQ 高可用功能多样
+ * 分布式消息队列 RabbitMQ 支持各种扩展插件功能多样性
+ * 分布式搜索引擎 ElasticSearch 业界知名
+ * 分布式链路追踪 Apache SkyWalking 链路追踪、网格分析、度量聚合、可视化
+ * 分布式日志中心 ELK 业界成熟解决方案
+ * 分布式监控 Prometheus、Grafana 全方位性能监控
+ * 其余与 Vue 版本一致
+
+ 当前版本: v2.5.0
+
+ ¥免费开源
+
+
+ 访问码云
+ 访问GitHub
+ 更新日志
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/login.vue b/src/views/login.vue
new file mode 100644
index 0000000..f641549
--- /dev/null
+++ b/src/views/login.vue
@@ -0,0 +1,312 @@
+
+
+
+
+
{{ title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ proxy.$t('login.rememberPassword') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ proxy.$t('login.login') }}
+ {{ proxy.$t('login.logging') }}
+
+
+ {{ proxy.$t('login.switchRegisterPage') }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/monitor/admin/index.vue b/src/views/monitor/admin/index.vue
new file mode 100644
index 0000000..b71a5cc
--- /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..f894586
--- /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..da928cc
--- /dev/null
+++ b/src/views/monitor/logininfor/index.vue
@@ -0,0 +1,209 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+ 删除
+
+
+
+ 清空
+
+
+
+ 解锁
+
+
+
+ 导出
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ proxy.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..b93325c
--- /dev/null
+++ b/src/views/monitor/online/index.vue
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+ {{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ proxy.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..dc66371
--- /dev/null
+++ b/src/views/monitor/operlog/index.vue
@@ -0,0 +1,261 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+ 删除
+
+
+
+ 清空
+
+
+ 导出
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ proxy.parseTime(scope.row.operTime) }}
+
+
+
+
+ {{ scope.row.costTime }}毫秒
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/monitor/operlog/oper-info-dialog.vue b/src/views/monitor/operlog/oper-info-dialog.vue
new file mode 100644
index 0000000..8d06b86
--- /dev/null
+++ b/src/views/monitor/operlog/oper-info-dialog.vue
@@ -0,0 +1,111 @@
+
+
+
+
+
+ 正常
+ 失败
+
+
+
+ {{ info.operName }} / {{ info.deptName }} / {{ info.operIp }} / {{ info.operLocation }}
+
+
+ {{ info.requestMethod }} {{ info.operUrl }}
+
+
+ {{ info.title }} / {{ typeFormat(info) }}
+
+
+
+ {{ info.method }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ info.costTime }}ms
+
+
+
+ {{ proxy.parseTime(info.operTime) }}
+
+
+
+ {{ info.errorMsg }}
+
+
+
+
+
+
+
+
+
diff --git a/src/views/monitor/snailjob/index.vue b/src/views/monitor/snailjob/index.vue
new file mode 100644
index 0000000..89e3b23
--- /dev/null
+++ b/src/views/monitor/snailjob/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..2de999f
--- /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..8b3d6dc
--- /dev/null
+++ b/src/views/register.vue
@@ -0,0 +1,263 @@
+
+
+
+
+
{{ title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ proxy.$t('register.register') }}
+ {{ proxy.$t('register.registering') }}
+
+
+ {{ proxy.$t('register.switchLoginPage') }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/client/index.vue b/src/views/system/client/index.vue
new file mode 100644
index 0000000..0354d0d
--- /dev/null
+++ b/src/views/system/client/index.vue
@@ -0,0 +1,316 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+ 新增
+
+
+
+ 修改
+
+
+
+
+ 删除
+
+
+
+ 导出
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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..7401494
--- /dev/null
+++ b/src/views/system/config/index.vue
@@ -0,0 +1,261 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+ 新增
+
+
+
+ 修改
+
+
+
+
+ 删除
+
+
+
+ 导出
+
+
+ 刷新缓存
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ proxy.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..6339df9
--- /dev/null
+++ b/src/views/system/dept/index.vue
@@ -0,0 +1,320 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+ 新增
+
+
+ 展开/折叠
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ proxy.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..be5c87a
--- /dev/null
+++ b/src/views/system/dict/data.vue
@@ -0,0 +1,309 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+ 新增
+
+
+ 修改
+
+
+
+ 删除
+
+
+
+ 导出
+
+
+ 关闭
+
+
+
+
+
+
+
+
+
+
+ {{ scope.row.dictLabel }}
+ {{ scope.row.dictLabel }}
+
+
+
+
+
+
+
+ {{ proxy.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..c97b92e
--- /dev/null
+++ b/src/views/system/dict/index.vue
@@ -0,0 +1,246 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+ 新增
+
+
+ 修改
+
+
+
+ 删除
+
+
+
+ 导出
+
+
+ 刷新缓存
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ scope.row.dictType }}
+
+
+
+
+
+
+ {{ proxy.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..d281091
--- /dev/null
+++ b/src/views/system/menu/index.vue
@@ -0,0 +1,525 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+ 新增
+
+
+ 级联删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ 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..c219828
--- /dev/null
+++ b/src/views/system/notice/index.vue
@@ -0,0 +1,243 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+ 新增
+
+
+ 修改
+
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ proxy.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..89898d6
--- /dev/null
+++ b/src/views/system/oss/config.vue
@@ -0,0 +1,344 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+ 新增
+
+
+ 修改
+
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ private
+ public
+ custom
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ protocol }}
+
+
+
+
+
+
+ {{ protocol }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ 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..f5966d7
--- /dev/null
+++ b/src/views/system/oss/index.vue
@@ -0,0 +1,333 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+ 上传文件
+
+
+ 上传图片
+
+
+
+ 删除
+
+
+
+ 预览开关 : {{ previewListResource ? '禁用' : '启用' }}
+
+
+ 配置管理
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ proxy.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..803cf05
--- /dev/null
+++ b/src/views/system/post/index.vue
@@ -0,0 +1,361 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+ 新增
+
+
+ 修改
+
+
+
+ 删除
+
+
+
+ 导出
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ proxy.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..7c7fbeb
--- /dev/null
+++ b/src/views/system/role/authUser.vue
@@ -0,0 +1,158 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+ 添加用户
+
+
+
+ 批量取消授权
+
+
+
+ 关闭
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ 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..c6d83b4
--- /dev/null
+++ b/src/views/system/role/index.vue
@@ -0,0 +1,503 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+ 新增
+
+
+ 修改
+
+
+ 删除
+
+
+ 导出
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ proxy.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..cf36fb0
--- /dev/null
+++ b/src/views/system/role/selectUser.vue
@@ -0,0 +1,130 @@
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ proxy.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..b594546
--- /dev/null
+++ b/src/views/system/tenant/index.vue
@@ -0,0 +1,371 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+ 新增
+
+
+ 修改
+
+
+
+ 删除
+
+
+
+ 导出
+
+
+ 同步租户字典
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ proxy.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..84a9f2b
--- /dev/null
+++ b/src/views/system/tenantPackage/index.vue
@@ -0,0 +1,329 @@
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+ 新增
+
+
+
+ 修改
+
+
+
+
+ 删除
+
+
+
+ 导出
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 展开/折叠
+ 全选/全不选
+ 父子联动
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/user/authRole.vue b/src/views/system/user/authRole.vue
new file mode 100644
index 0000000..d322942
--- /dev/null
+++ b/src/views/system/user/authRole.vue
@@ -0,0 +1,146 @@
+
+
+
+
基本信息
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
角色信息
+
+
+
+
+ {{ (pageNum - 1) * pageSize + scope.$index + 1 }}
+
+
+
+
+
+
+
+
+ {{ proxy.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..da8c5bc
--- /dev/null
+++ b/src/views/system/user/index.vue
@@ -0,0 +1,679 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+ 新增
+
+
+
+ 修改
+
+
+
+
+ 删除
+
+
+
+
+
+ 更多
+
+
+
+ 下载模板
+
+ 导入数据
+ 导出数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ 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..7c1389a
--- /dev/null
+++ b/src/views/system/user/profile/index.vue
@@ -0,0 +1,122 @@
+
+
+
+
+
+
+
+ 个人信息
+
+
+
+
+
+
+
+
+ 用户名称
+ {{ 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/onlineDevice.vue b/src/views/system/user/profile/onlineDevice.vue
new file mode 100644
index 0000000..3ce63ba
--- /dev/null
+++ b/src/views/system/user/profile/onlineDevice.vue
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ proxy.parseTime(scope.row.loginTime) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/user/profile/resetPwd.vue b/src/views/system/user/profile/resetPwd.vue
new file mode 100644
index 0000000..3ca5ee9
--- /dev/null
+++ b/src/views/system/user/profile/resetPwd.vue
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ 保存
+ 关闭
+
+
+
+
+
diff --git a/src/views/system/user/profile/thirdParty.vue b/src/views/system/user/profile/thirdParty.vue
new file mode 100644
index 0000000..218b63a
--- /dev/null
+++ b/src/views/system/user/profile/thirdParty.vue
@@ -0,0 +1,145 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 解绑
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/user/profile/userAvatar.vue b/src/views/system/user/profile/userAvatar.vue
new file mode 100644
index 0000000..8709289
--- /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..2a5d1a0
--- /dev/null
+++ b/src/views/system/user/profile/userInfo.vue
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 男
+ 女
+
+
+
+ 保存
+ 关闭
+
+
+
+
+
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..5f11e25
--- /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..95065a0
--- /dev/null
+++ b/src/views/tool/gen/genInfoForm.vue
@@ -0,0 +1,294 @@
+
+
+
+
+
+ 生成模板
+
+
+
+
+
+
+
+
+
+
+ 生成包路径
+
+
+
+
+
+
+
+
+
+
+
+ 生成模块名
+
+
+
+
+
+
+
+
+
+
+
+ 生成业务名
+
+
+
+
+
+
+
+
+
+
+
+ 生成功能名
+
+
+
+
+
+
+
+
+
+
+
+ 上级菜单
+
+
+
+
+
+
+
+
+
+
+
+ 生成代码方式
+
+
+
+
+ zip压缩包
+ 自定义路径
+
+
+
+
+
+
+ 自定义路径
+
+
+
+
+
+
+
+
+ 最近路径快速选择
+
+
+
+
+ 恢复默认的生成基础路径
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 树编码字段
+
+
+
+
+
+
+
+
+
+
+
+
+ 树父编码字段
+
+
+
+
+
+
+
+
+
+
+
+
+ 树名称字段
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 关联子表的表名
+
+
+
+
+
+
+
+
+
+
+
+
+ 子表关联的外键名
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/tool/gen/importTable.vue b/src/views/tool/gen/importTable.vue
new file mode 100644
index 0000000..f86a54f
--- /dev/null
+++ b/src/views/tool/gen/importTable.vue
@@ -0,0 +1,122 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/tool/gen/index.vue b/src/views/tool/gen/index.vue
new file mode 100644
index 0000000..e73a3fb
--- /dev/null
+++ b/src/views/tool/gen/index.vue
@@ -0,0 +1,259 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+ 生成
+
+
+ 导入
+
+
+ 修改
+
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+ {{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 复制
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/workflow/category/index.vue b/src/views/workflow/category/index.vue
new file mode 100644
index 0000000..cd6984e
--- /dev/null
+++ b/src/views/workflow/category/index.vue
@@ -0,0 +1,254 @@
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+ 新增
+
+
+ 展开/折叠
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/workflow/leave/index.vue b/src/views/workflow/leave/index.vue
new file mode 100644
index 0000000..608cf05
--- /dev/null
+++ b/src/views/workflow/leave/index.vue
@@ -0,0 +1,236 @@
+
+
+
+
+
+
+
+
+ 至
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+ 新增
+
+
+ 导出
+
+
+
+
+
+
+
+
+
+
+ {{ options.find((e) => e.value === scope.row.leaveType)?.label }}
+
+
+
+
+ {{ proxy.parseTime(scope.row.startDate, '{y}-{m}-{d}') }}
+
+
+
+
+ {{ proxy.parseTime(scope.row.endDate, '{y}-{m}-{d}') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 修改
+
+
+ 删除
+
+
+
+
+ 查看
+
+
+ 撤销
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/workflow/leave/leaveEdit.vue b/src/views/workflow/leave/leaveEdit.vue
new file mode 100644
index 0000000..cbf1057
--- /dev/null
+++ b/src/views/workflow/leave/leaveEdit.vue
@@ -0,0 +1,286 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/workflow/processDefinition/design.vue b/src/views/workflow/processDefinition/design.vue
new file mode 100644
index 0000000..01fa552
--- /dev/null
+++ b/src/views/workflow/processDefinition/design.vue
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
diff --git a/src/views/workflow/processDefinition/index.vue b/src/views/workflow/processDefinition/index.vue
new file mode 100644
index 0000000..f6f2ff3
--- /dev/null
+++ b/src/views/workflow/processDefinition/index.vue
@@ -0,0 +1,562 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+ 添加
+
+
+ 修改
+
+
+ 删除
+
+
+ 部署流程文件
+
+
+ 导出
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ v{{ scope.row.version }}.0
+
+
+
+ handleProcessDefState(scope.row, status)"
+ />
+
+
+
+
+ 未发布
+ 已发布
+ 失效
+
+
+
+
+
+
+ 删除流程
+
+
+ 复制流程
+
+
+
+
+ 流程设计
+ 查看流程
+
+
+ 发布流程
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ * 请选择部署流程分类:
+
+
+
+
+ 点击上传,选择JSON流程文件
+ 仅支持json格式文件
+ PS:如若部署请部署从本项目模型管理导出的数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 经典模式
+ 仿钉钉模式
+
+
+
+
+
+
+
+ 是
+ 否
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/workflow/processInstance/index.vue b/src/views/workflow/processInstance/index.vue
new file mode 100644
index 0000000..d4b4f3f
--- /dev/null
+++ b/src/views/workflow/processInstance/index.vue
@@ -0,0 +1,478 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 选择申请人
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ scope.row.flowName }}v{{ scope.row.version }}
+
+
+
+
+
+
+
+ v{{ scope.row.version }}.0
+
+
+
+ 激活
+ 挂起
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 取消
+ 确认
+
+
+ 作废
+
+
+
+
+ 删除
+
+
+
+
+ 查看
+
+
+ 变量
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ v{{ scope.row.version }}.0
+
+
+
+ 激活
+ 挂起
+
+
+
+
+
+
+
+
+
+
+ 流程定义名称:{{ processDefinitionName }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 确认
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/workflow/spel/index.vue b/src/views/workflow/spel/index.vue
new file mode 100644
index 0000000..4968901
--- /dev/null
+++ b/src/views/workflow/spel/index.vue
@@ -0,0 +1,360 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+ 新增
+
+
+ 修改
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+ {{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}
+
+
+
+
+ {{ scope.row.componentName || '-' }}
+
+
+
+
+ {{ scope.row.methodName || '-' }}
+
+
+
+
+ {{ scope.row.methodParams || '-' }}
+
+
+
+
+
+ 正常
+ 停用
+
+
+
+
+ {{ scope.row.remark || '-' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 组件名称
+
+
+
+
+
+
+
+
+
+
+ 方法名称
+
+
+
+
+
+
+
+
+
+
+ 方法参数
+
+
+
+
+
+
+
+ {{ form.viewSpel || '例如:#{@组件名.方法名(#方法参数)} 或 ${方法参数}' }}
+
+
+
+
+
+ {{ dict.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/workflow/task/allTaskWaiting.vue b/src/views/workflow/task/allTaskWaiting.vue
new file mode 100644
index 0000000..eef3c0a
--- /dev/null
+++ b/src/views/workflow/task/allTaskWaiting.vue
@@ -0,0 +1,279 @@
+
+
+
+
+
+
+
+
+ 选择申请人
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+ 修改办理人
+ 催办
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ v{{ scope.row.version }}.0
+
+
+
+
+
+
+
+
+ {{ name }}
+
+
+
+ 无
+
+
+
+ {{ scope.row.approveName }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 查看
+
+
+ 流程干预
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/workflow/task/myDocument.vue b/src/views/workflow/task/myDocument.vue
new file mode 100644
index 0000000..18f3c39
--- /dev/null
+++ b/src/views/workflow/task/myDocument.vue
@@ -0,0 +1,246 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ v{{ scope.row.version }}.0
+
+
+
+ 激活
+ 挂起
+
+
+
+
+
+
+
+
+
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+ 查看
+
+
+ 撤销
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/workflow/task/taskCopyList.vue b/src/views/workflow/task/taskCopyList.vue
new file mode 100644
index 0000000..f66d098
--- /dev/null
+++ b/src/views/workflow/task/taskCopyList.vue
@@ -0,0 +1,138 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ v{{ scope.row.version }}.0
+
+
+
+
+
+
+
+
+
+ 查看
+
+
+
+
+
+
+
+
+
diff --git a/src/views/workflow/task/taskFinish.vue b/src/views/workflow/task/taskFinish.vue
new file mode 100644
index 0000000..38925b4
--- /dev/null
+++ b/src/views/workflow/task/taskFinish.vue
@@ -0,0 +1,186 @@
+
+
+
+
+
+
+
+
+ 选择申请人
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ v{{ scope.row.version }}.0
+
+
+
+
+
+
+ {{ scope.row.approveName || '无' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 查看
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/workflow/task/taskWaiting.vue b/src/views/workflow/task/taskWaiting.vue
new file mode 100644
index 0000000..434c674
--- /dev/null
+++ b/src/views/workflow/task/taskWaiting.vue
@@ -0,0 +1,185 @@
+
+
+
+
+
+
+
+
+ 选择申请人
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ name }}
+
+
+
+ 无
+
+
+
+
+
+
+
+
+
+
+
+ 办理
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..c5e5ca3
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,42 @@
+{
+ "$schema": "https://json.schemastore.org/tsconfig",
+ "compilerOptions": {
+ "baseUrl": ".",
+ // https://vite.dev/config/build-options.html#build-target
+ "target": "ES2020",
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "lib": ["ESNext", "DOM", "DOM.Iterable"],
+ "skipLibCheck": true,
+ // This setting lets you specify a file for storing incremental compilation information as a part of composite projects which enables faster building of larger TypeScript codebases.
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
+ "strict": true,
+ "allowJs": true,
+ "jsx": "preserve",
+ "sourceMap": true,
+ "resolveJsonModule": true,
+ "esModuleInterop": true,
+ "noEmit": true,
+ "paths": {
+ "@/*": ["./src/*"]
+ },
+ "types": ["node", "vite/client"],
+ // 自定义配置
+ "noImplicitAny": false, // 禁用 隐式的any错误
+ "removeComments": true, // 移除 ts注释
+ "experimentalDecorators": true, // 启用实验性的装饰器支持
+ "strictFunctionTypes": false, // 禁用严格函数类型检查
+ "strictNullChecks": false, // 禁用严格的空值检查
+ "allowSyntheticDefaultImports": true, // 允许默认导入
+ "forceConsistentCasingInFileNames": true // 强制在文件名中使用一致的大小写
+ },
+ "include": [
+ "src/**/*.ts",
+ "src/**/*.vue",
+ "vite.config.ts",
+ "vitest.config.ts",
+ "eslint.config.ts",
+ "src/**/*.d.ts"
+ ],
+ "exclude": ["node_modules", "dist", "src/**/__tests__/*"]
+}
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..58ca5f2
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,73 @@
+import { defineConfig, loadEnv } from 'vite';
+import createPlugins from './vite/plugins';
+import autoprefixer from 'autoprefixer'; // css自动添加兼容性前缀
+import path from 'path';
+
+export default defineConfig(({ mode, command }) => {
+ 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, './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: {
+ // additionalData: '@use "@/assets/styles/variables.module.scss as *";'
+ // javascriptEnabled: true
+ api: 'modern-compiler'
+ }
+ },
+ postcss: {
+ plugins: [
+ // 浏览器兼容性
+ autoprefixer(),
+ {
+ postcssPlugin: 'internal:charset-removal',
+ AtRule: {
+ charset: (atRule) => {
+ atRule.remove();
+ }
+ }
+ }
+ ]
+ }
+ },
+ // 预编译
+ optimizeDeps: {
+ include: [
+ 'vue',
+ 'vue-router',
+ 'pinia',
+ 'axios',
+ '@vueuse/core',
+ 'echarts',
+ 'vue-i18n',
+ '@vueup/vue-quill',
+ 'image-conversion',
+ 'element-plus/es/components/**/css'
+ ]
+ }
+ };
+});
diff --git a/vite/plugins/auto-import.ts b/vite/plugins/auto-import.ts
new file mode 100644
index 0000000..a271829
--- /dev/null
+++ b/vite/plugins/auto-import.ts
@@ -0,0 +1,20 @@
+import AutoImport from 'unplugin-auto-import/vite';
+import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';
+
+export default (path: any) => {
+ return AutoImport({
+ // 自动导入 Vue 相关函数
+ imports: ['vue', 'vue-router', '@vueuse/core', 'pinia'],
+ eslintrc: {
+ enabled: true,
+ filepath: './.eslintrc-auto-import.json',
+ globalsPropValue: true
+ },
+ resolvers: [
+ // 自动导入 Element Plus 相关函数ElMessage, ElMessageBox... (带样式)
+ ElementPlusResolver()
+ ],
+ 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/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..9ef8faf
--- /dev/null
+++ b/vite/plugins/index.ts
@@ -0,0 +1,25 @@
+import vue from '@vitejs/plugin-vue';
+import vueDevTools from 'vite-plugin-vue-devtools';
+
+import createUnoCss from './unocss';
+import createAutoImport from './auto-import';
+import createComponents from './components';
+import createIcons from './icons';
+import createSvgIconsPlugin from './svg-icon';
+import createCompression from './compression';
+import createSetupExtend from './setup-extend';
+import path from 'path';
+
+export default (viteEnv: any, isBuild = false): [] => {
+ const vitePlugins: any = [];
+ vitePlugins.push(vue());
+ vitePlugins.push(vueDevTools());
+ vitePlugins.push(createUnoCss());
+ vitePlugins.push(createAutoImport(path));
+ vitePlugins.push(createComponents(path));
+ vitePlugins.push(createCompression(viteEnv));
+ vitePlugins.push(createIcons());
+ vitePlugins.push(createSvgIconsPlugin(path));
+ vitePlugins.push(createSetupExtend());
+ 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..3ad93fb
--- /dev/null
+++ b/vite/plugins/svg-icon.ts
@@ -0,0 +1,10 @@
+import { createSvgIconsPlugin } from 'vite-plugin-svg-icons-ng';
+
+export default (path: any) => {
+ return createSvgIconsPlugin({
+ // 指定需要缓存的图标文件夹
+ iconDirs: [path.resolve(path.resolve(__dirname, '../../src'), 'assets/icons/svg')],
+ // 指定symbolId格式
+ symbolId: 'icon-[dir]-[name]'
+ });
+};
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,低版本浏览器是不支持的,启动后会报错
+ });
+};