wanghongzhi 1 年之前
父节点
当前提交
f53746fd34
共有 100 个文件被更改,包括 14619 次插入0 次删除
  1. 10 0
      .env
  2. 40 0
      .env.development
  3. 0 0
      .env.production
  4. 307 0
      .eslintrc-auto-import.json
  5. 25 0
      .eslintrc.cjs
  6. 29 0
      .gitignore
  7. 9 0
      .prettierrc.json
  8. 13 0
      index.html
  9. 10462 0
      package-lock.json
  10. 46 0
      package.json
  11. 二进制
      public/yh.ico
  12. 9 0
      src/App.vue
  13. 40 0
      src/api/login.js
  14. 46 0
      src/api/system/dataList.js
  15. 39 0
      src/api/system/depart.js
  16. 42 0
      src/api/system/menu.js
  17. 36 0
      src/api/system/person.js
  18. 89 0
      src/api/system/role.js
  19. 94 0
      src/api/system/user.js
  20. 二进制
      src/assets/font/Alibaba-PuHuiTi-B.ttf
  21. 二进制
      src/assets/font/Alibaba-PuHuiTi-M.ttf
  22. 二进制
      src/assets/font/Alibaba-PuHuiTi-R.ttf
  23. 二进制
      src/assets/images/Slice12x.png
  24. 二进制
      src/assets/images/Slice22x.png
  25. 二进制
      src/assets/images/Slice32x.png
  26. 二进制
      src/assets/images/Slice42x.png
  27. 1 0
      src/assets/images/dingding.svg
  28. 1 0
      src/assets/images/email.svg
  29. 二进制
      src/assets/images/home-book.png
  30. 二进制
      src/assets/images/home-email.png
  31. 二进制
      src/assets/images/home-file.png
  32. 二进制
      src/assets/images/home-ring.png
  33. 二进制
      src/assets/images/loading-logo-old.png
  34. 二进制
      src/assets/images/login_back.png
  35. 二进制
      src/assets/images/login_lock.png
  36. 二进制
      src/assets/images/login_user.png
  37. 二进制
      src/assets/images/logo.png
  38. 二进制
      src/assets/images/menu_img.png
  39. 二进制
      src/assets/images/pwd.png
  40. 二进制
      src/assets/images/style1.png
  41. 二进制
      src/assets/images/user.png
  42. 二进制
      src/assets/images/welcomelogo.png
  43. 133 0
      src/assets/style/common.scss
  44. 6 0
      src/assets/style/elementPlusCustom.scss
  45. 20 0
      src/assets/style/font.scss
  46. 5 0
      src/assets/style/index.scss
  47. 109 0
      src/assets/style/reset.scss
  48. 15 0
      src/assets/style/theme.scss
  49. 39 0
      src/assets/style/variable.scss
  50. 1 0
      src/assets/svg/checkbox.svg
  51. 1 0
      src/assets/svg/home/test.svg
  52. 3 0
      src/assets/svg/readme.md
  53. 1 0
      src/assets/svg/test.svg
  54. 1 0
      src/assets/svg/user/test.svg
  55. 二进制
      src/assets/video/loginBackground.mp4
  56. 128 0
      src/components/IconSelect/index.vue
  57. 12 0
      src/components/IconSelect/requireIcons.js
  58. 3 0
      src/components/ParentView/index.vue
  59. 44 0
      src/components/SvgIcon/index.vue
  60. 9 0
      src/components/SvgIcon/svgicon.js
  61. 25 0
      src/components/test.vue
  62. 98 0
      src/config/config.js
  63. 5 0
      src/directive/index.js
  64. 22 0
      src/directive/permission/hasPermi.js
  65. 25 0
      src/i18n/index.js
  66. 6 0
      src/i18n/lang/en.js
  67. 6 0
      src/i18n/lang/zh-cn.js
  68. 45 0
      src/layout/components/Logo/index.vue
  69. 24 0
      src/layout/components/Main/index.vue
  70. 70 0
      src/layout/components/NavBar/components/Breadcrumb.vue
  71. 20 0
      src/layout/components/NavBar/components/FullScreenBtn.vue
  72. 29 0
      src/layout/components/NavBar/components/LayoutStyleSet.vue
  73. 211 0
      src/layout/components/NavBar/components/MenuSearch.vue
  74. 114 0
      src/layout/components/NavBar/index.vue
  75. 109 0
      src/layout/components/SideBar/index.vue
  76. 78 0
      src/layout/components/SideBar/indexH.vue
  77. 104 0
      src/layout/components/SideBar/sideBarItem.vue
  78. 331 0
      src/layout/components/TabsBar/index.vue
  79. 89 0
      src/layout/index.vue
  80. 24 0
      src/main.js
  81. 2 0
      src/micro/index.js
  82. 61 0
      src/micro/index.vue
  83. 69 0
      src/otherLogin/bx.vue
  84. 67 0
      src/otherLogin/ssoSgzl.vue
  85. 45 0
      src/permission.js
  86. 100 0
      src/router/constantRoutes.js
  87. 73 0
      src/router/dynamicRoutes.js
  88. 26 0
      src/router/index.js
  89. 172 0
      src/store/permission.js
  90. 27 0
      src/store/settings.js
  91. 196 0
      src/store/tabsbar.js
  92. 168 0
      src/store/user.js
  93. 118 0
      src/utils/aes.js
  94. 27 0
      src/utils/auth.js
  95. 71 0
      src/utils/color.js
  96. 5 0
      src/utils/mittBus.js
  97. 60 0
      src/utils/request.js
  98. 16 0
      src/utils/tjmTools.js
  99. 13 0
      src/views/404.vue
  100. 0 0
      src/views/error/noPermission.vue

+ 10 - 0
.env

@@ -0,0 +1,10 @@
+# 所有环境都会加载
+
+# 项目标识代码
+VITE_APP_CODE="TJM_VUE3_ELEPLUS_ADMIN"
+
+# 项目名
+VITE_APP_NAME="玉衡平台"
+
+# 项目描述
+VITE_APP_DESCRIPTION="玉衡框架vue3版本,提供基础服务"

+ 40 - 0
.env.development

@@ -0,0 +1,40 @@
+# 开发环境加载
+
+# 环境标识
+VITE_APP_ENV = "development"
+
+# 公共基础路径
+VITE_BASE = "/"
+
+# 代理URL路径
+VITE_BASE_URL ="/dev-api"
+
+# 模拟数据接口路径
+VITE_BASE_MOCK_URL ="/mock-api"
+
+# boot服务端接口路径
+VITE_BASE_SERVER_URL ="http://localhost:8000"
+
+
+# 微前端-工作流
+VITE_WORKFLOW_URL ="http://localhost:1888/"
+
+# 微前端-代码生成器
+VITE_CODEMAKER_URL = "http://localhost:32582/"
+
+# 宝信单点登录
+VITE_BX_CLIENT_ID = '项目英文名'
+VITE_BX_AUTH_URL = 'http://eplattest.qdgwlds.com/cloud/oauth/authorize'
+VITE_BX_REDIRECT_URL = 'http://localhost:8080/yh'
+VITE_BX_LOGOUT_URL = 'http://eplattest.qdgwlds.com/cloud/'
+
+# 边端单点登录
+VITE_EDGE_CLIENT_ID ='frame-demo'
+VITE_EDGE_AUTH_URL = 'http://qdport.auth.vue3.10.236.3.36.nip.io/oauth/authorize'
+VITE_EDGE_REDIRECT_URL = 'http://localhost:8080/ssoyh'
+VITE_EDGE_LOGOUT_URL = 'http://qdport.auth.vue3.10.236.3.36.nip.io/logout'
+
+
+# 打包是否使用Mock
+VITE_APP_PRODMOCK = false
+

+ 0 - 0
.env.production


+ 307 - 0
.eslintrc-auto-import.json

@@ -0,0 +1,307 @@
+{
+  "globals": {
+    "Component": true,
+    "ComponentPublicInstance": true,
+    "ComputedRef": true,
+    "EffectScope": true,
+    "ExtractDefaultPropTypes": true,
+    "ExtractPropTypes": true,
+    "ExtractPublicPropTypes": true,
+    "InjectionKey": true,
+    "PropType": true,
+    "Ref": true,
+    "VNode": true,
+    "WritableComputedRef": true,
+    "acceptHMRUpdate": true,
+    "computed": true,
+    "createApp": true,
+    "createPinia": true,
+    "customRef": true,
+    "defineAsyncComponent": true,
+    "defineComponent": true,
+    "defineStore": true,
+    "effectScope": true,
+    "getActivePinia": true,
+    "getCurrentInstance": true,
+    "getCurrentScope": true,
+    "h": true,
+    "inject": true,
+    "isProxy": true,
+    "isReactive": true,
+    "isReadonly": true,
+    "isRef": 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,
+    "onDeactivated": true,
+    "onErrorCaptured": true,
+    "onMounted": true,
+    "onRenderTracked": true,
+    "onRenderTriggered": true,
+    "onScopeDispose": true,
+    "onServerPrefetch": true,
+    "onUnmounted": true,
+    "onUpdated": true,
+    "provide": true,
+    "reactive": true,
+    "readonly": true,
+    "ref": true,
+    "resolveComponent": true,
+    "setActivePinia": true,
+    "setMapStoreSuffix": true,
+    "shallowReactive": true,
+    "shallowReadonly": true,
+    "shallowRef": true,
+    "storeToRefs": true,
+    "toRaw": true,
+    "toRef": true,
+    "toRefs": true,
+    "toValue": true,
+    "triggerRef": true,
+    "unref": true,
+    "useAttrs": true,
+    "useCssModule": true,
+    "useCssVars": true,
+    "useLink": true,
+    "useRoute": true,
+    "useRouter": true,
+    "useSlots": true,
+    "watch": true,
+    "watchEffect": true,
+    "watchPostEffect": true,
+    "watchSyncEffect": true,
+    "asyncComputed": true,
+    "autoResetRef": true,
+    "computedAsync": true,
+    "computedEager": true,
+    "computedInject": true,
+    "computedWithControl": true,
+    "controlledComputed": true,
+    "controlledRef": true,
+    "createEventHook": true,
+    "createGlobalState": true,
+    "createInjectionState": true,
+    "createReactiveFn": true,
+    "createReusableTemplate": true,
+    "createSharedComposable": true,
+    "createTemplatePromise": true,
+    "createUnrefFn": true,
+    "debouncedRef": true,
+    "debouncedWatch": true,
+    "eagerComputed": true,
+    "extendRef": true,
+    "ignorableWatch": true,
+    "injectLocal": true,
+    "isDefined": true,
+    "makeDestructurable": true,
+    "onClickOutside": true,
+    "onKeyStroke": true,
+    "onLongPress": true,
+    "onStartTyping": true,
+    "pausableWatch": true,
+    "provideLocal": true,
+    "reactify": true,
+    "reactifyObject": true,
+    "reactiveComputed": true,
+    "reactiveOmit": true,
+    "reactivePick": true,
+    "refAutoReset": true,
+    "refDebounced": true,
+    "refDefault": true,
+    "refThrottled": true,
+    "refWithControl": true,
+    "resolveRef": true,
+    "resolveUnref": true,
+    "syncRef": true,
+    "syncRefs": true,
+    "templateRef": true,
+    "throttledRef": true,
+    "throttledWatch": true,
+    "toReactive": true,
+    "tryOnBeforeMount": true,
+    "tryOnBeforeUnmount": true,
+    "tryOnMounted": true,
+    "tryOnScopeDispose": true,
+    "tryOnUnmounted": 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,
+    "useBase64": true,
+    "useBattery": true,
+    "useBluetooth": true,
+    "useBreakpoints": true,
+    "useBroadcastChannel": true,
+    "useBrowserLocation": true,
+    "useCached": true,
+    "useClipboard": true,
+    "useCloned": true,
+    "useColorMode": true,
+    "useConfirmDialog": true,
+    "useCounter": true,
+    "useCssVar": true,
+    "useCurrentElement": true,
+    "useCycleList": true,
+    "useDark": true,
+    "useDateFormat": true,
+    "useDebounce": true,
+    "useDebounceFn": true,
+    "useDebouncedRefHistory": true,
+    "useDeviceMotion": true,
+    "useDeviceOrientation": true,
+    "useDevicePixelRatio": true,
+    "useDevicesList": true,
+    "useDisplayMedia": true,
+    "useDocumentVisibility": true,
+    "useDraggable": true,
+    "useDropZone": true,
+    "useElementBounding": true,
+    "useElementByPoint": true,
+    "useElementHover": true,
+    "useElementSize": true,
+    "useElementVisibility": true,
+    "useEventBus": true,
+    "useEventListener": true,
+    "useEventSource": true,
+    "useEyeDropper": true,
+    "useFavicon": true,
+    "useFetch": true,
+    "useFileDialog": true,
+    "useFileSystemAccess": true,
+    "useFocus": true,
+    "useFocusWithin": true,
+    "useFps": true,
+    "useFullscreen": true,
+    "useGamepad": true,
+    "useGeolocation": true,
+    "useIdle": true,
+    "useImage": true,
+    "useInfiniteScroll": true,
+    "useIntersectionObserver": true,
+    "useInterval": true,
+    "useIntervalFn": true,
+    "useKeyModifier": true,
+    "useLastChanged": true,
+    "useLocalStorage": true,
+    "useMagicKeys": true,
+    "useManualRefHistory": true,
+    "useMediaControls": true,
+    "useMediaQuery": true,
+    "useMemoize": true,
+    "useMemory": true,
+    "useMounted": true,
+    "useMouse": true,
+    "useMouseInElement": true,
+    "useMousePressed": true,
+    "useMutationObserver": true,
+    "useNavigatorLanguage": true,
+    "useNetwork": true,
+    "useNow": true,
+    "useObjectUrl": true,
+    "useOffsetPagination": true,
+    "useOnline": true,
+    "usePageLeave": true,
+    "useParallax": true,
+    "useParentElement": true,
+    "usePerformanceObserver": true,
+    "usePermission": true,
+    "usePointer": true,
+    "usePointerLock": true,
+    "usePointerSwipe": true,
+    "usePreferredColorScheme": true,
+    "usePreferredContrast": true,
+    "usePreferredDark": true,
+    "usePreferredLanguages": true,
+    "usePreferredReducedMotion": true,
+    "usePrevious": true,
+    "useRafFn": true,
+    "useRefHistory": true,
+    "useResizeObserver": true,
+    "useScreenOrientation": true,
+    "useScreenSafeArea": true,
+    "useScriptTag": true,
+    "useScroll": true,
+    "useScrollLock": true,
+    "useSessionStorage": true,
+    "useShare": true,
+    "useSorted": true,
+    "useSpeechRecognition": true,
+    "useSpeechSynthesis": true,
+    "useStepper": true,
+    "useStorage": true,
+    "useStorageAsync": true,
+    "useStyleTag": true,
+    "useSupported": true,
+    "useSwipe": true,
+    "useTemplateRefsList": true,
+    "useTextDirection": true,
+    "useTextSelection": true,
+    "useTextareaAutosize": true,
+    "useThrottle": true,
+    "useThrottleFn": true,
+    "useThrottledRefHistory": true,
+    "useTimeAgo": true,
+    "useTimeout": true,
+    "useTimeoutFn": true,
+    "useTimeoutPoll": true,
+    "useTimestamp": true,
+    "useTitle": true,
+    "useToNumber": true,
+    "useToString": true,
+    "useToggle": true,
+    "useTransition": true,
+    "useUrlSearchParams": true,
+    "useUserMedia": true,
+    "useVModel": true,
+    "useVModels": true,
+    "useVibrate": true,
+    "useVirtualList": true,
+    "useWakeLock": true,
+    "useWebNotification": true,
+    "useWebSocket": true,
+    "useWebWorker": true,
+    "useWebWorkerFn": true,
+    "useWindowFocus": true,
+    "useWindowScroll": true,
+    "useWindowSize": true,
+    "watchArray": true,
+    "watchAtMost": true,
+    "watchDebounced": true,
+    "watchDeep": true,
+    "watchIgnorable": true,
+    "watchImmediate": true,
+    "watchOnce": true,
+    "watchPausable": true,
+    "watchThrottled": true,
+    "watchTriggerable": true,
+    "watchWithFilter": true,
+    "whenever": true,
+    "ElMessage": true,
+    "ElMessageBox": true,
+    "useClipboardItems": true
+  }
+}

+ 25 - 0
.eslintrc.cjs

@@ -0,0 +1,25 @@
+
+require('@rushstack/eslint-patch/modern-module-resolution')
+
+module.exports = {
+  env: {
+    browser: true,
+    node: true,
+    es2021: true, 
+  },
+  root: true,
+  'extends': [
+    './.eslintrc-auto-import.json', 
+    'plugin:vue/vue3-essential',
+    'eslint:recommended', 
+    '@vue/eslint-config-prettier/skip-formatting' 
+  ],
+  parserOptions: {
+    ecmaVersion: 'latest'
+  },
+  rules:{
+    semi: ["warn", "never"], 
+    "no-debugger": "warn",
+    'vue/multi-word-component-names': 'off'
+  }
+}

+ 29 - 0
.gitignore

@@ -0,0 +1,29 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+.DS_Store
+dist
+dist.zip
+dist-ssr
+coverage
+*.local
+
+/cypress/videos/
+/cypress/screenshots/
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 9 - 0
.prettierrc.json

@@ -0,0 +1,9 @@
+{
+  "$schema": "https://json.schemastore.org/prettierrc",
+  "semi": false,
+  "singleQuote": true,
+  "printWidth": 80,
+  "trailingComma": "none",
+  "arrowParens": "avoid",
+  "tabWidth": 2
+}

+ 13 - 0
index.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8">
+    <link rel="icon" href="/yh.ico">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>玉衡-Admin</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.js"></script>
+  </body>
+</html>

文件差异内容过多而无法显示
+ 10462 - 0
package-lock.json


+ 46 - 0
package.json

@@ -0,0 +1,46 @@
+{
+  "name": "tjm-vue3-elplus-admin",
+  "version": "0.0.0",
+  "private": true,
+  "scripts": {
+    "dev": "vite --mode development",
+    "build": "vite build --mode development",
+    "preview": "vite preview",
+    "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore",
+    "format": "prettier --write src/"
+  },
+  "dependencies": {
+    "@element-plus/icons-vue": "^2.1.0",
+    "@micro-zoe/micro-app": "^1.0.0-rc.4",
+    "@vueuse/core": "^10.5.0",
+    "axios": "^1.5.1",
+    "crypto-js": "^4.2.0",
+    "echarts": "^5.1.2",
+    "element-plus": "^2.4.0",
+    "fuse.js": "^6.6.2",
+    "js-cookie": "^3.0.5",
+    "mitt": "^3.0.1",
+    "pinia": "^2.1.6",
+    "pinia-plugin-persist": "^1.0.0",
+    "vue": "^3.3.4",
+    "vue-i18n": "^9.5.0",
+    "vue-router": "^4.2.4"
+  },
+  "devDependencies": {
+    "@iconify-json/ep": "^1.1.12",
+    "@iconify-json/mdi": "^1.1.54",
+    "@rushstack/eslint-patch": "^1.3.3",
+    "@vitejs/plugin-vue": "^4.3.4",
+    "@vue/eslint-config-prettier": "^8.0.0",
+    "eslint": "^8.49.0",
+    "eslint-plugin-vue": "^9.17.0",
+    "prettier": "^3.0.3",
+    "sass": "^1.69.3",
+    "sass-loader": "^13.3.2",
+    "unplugin-auto-import": "^0.16.6",
+    "unplugin-icons": "^0.17.0",
+    "unplugin-vue-components": "^0.25.2",
+    "vite": "^4.4.9",
+    "vite-plugin-svg-icons": "^2.0.1"
+  }
+}

二进制
public/yh.ico


+ 9 - 0
src/App.vue

@@ -0,0 +1,9 @@
+<script setup>
+const route = useRoute()
+</script>
+
+<template>
+  <router-view />
+</template>
+
+<style scoped></style>

+ 40 - 0
src/api/login.js

@@ -0,0 +1,40 @@
+import request from '@/utils/request'
+
+// 登录方法
+export function login(data) {
+  return request({
+    url: '/qdport-auth/oauth/token',
+    method: 'post',
+    headers: {
+      'Content-Type': 'application/x-www-form-urlencoded'
+    },
+    data: data
+  })
+}
+
+export function passwordChange(data) {
+  return request({
+    url: '/qdport-auth/oauth/forceChangePassword',
+    method: 'post',
+    headers: {
+      'Content-Type': 'application/x-www-form-urlencoded'
+    },
+    data: data
+  })
+}
+
+export function sendSmsCode(data) {
+  return request({
+    url: '/qdport-auth/oauth/sendSmsCode',
+    method: 'get',
+    params: data
+  })
+}
+
+export function userUnlock(data) {
+  return request({
+    url: '/qdport-auth/oauth/unlock',
+    method: 'post',
+    params: data
+  })
+}

+ 46 - 0
src/api/system/dataList.js

@@ -0,0 +1,46 @@
+import request from '@/utils/request'
+
+// 主列表
+export function getMainList(params) {
+  return request({
+    url: `/qdport-system/sys/menu/menuManage`,
+    method: 'GET',
+    params
+  })
+}
+
+// 权限列表
+export function getPowerList(params) {
+  return request({
+    url: `/qdport-system/data-scope/list`,
+    method: 'get',
+    params
+  })
+}
+
+// 新增权限
+export function scopeSave(data) {
+  return request({
+    url: `/qdport-system/data-scope/save`,
+    method: 'post',
+    data
+  })
+}
+
+// 修改权限
+export function scopeUpdate(data) {
+  return request({
+    url: `/qdport-system/data-scope/update`,
+    method: 'post',
+    data
+  })
+}
+
+// 删除权限  ids:record.id
+export function scopeRemove(data) {
+  return request({
+    url: `/qdport-system/data-scope/remove`,
+    method: 'post',
+    params: data
+  })
+}

+ 39 - 0
src/api/system/depart.js

@@ -0,0 +1,39 @@
+import request from '@/utils/request'
+
+//部门列表
+export function getMainList(params) {
+  return request({
+    url: '/qdport-system/sys/dept/tree',
+    method: 'GET',
+    params
+  })
+}
+
+// 更新部门
+export function setDepart(data) {
+  return request({
+    url: `/qdport-system/sys/dept/update`,
+    method: 'post',
+    params: data
+  })
+}
+
+// 新增部门
+export function addDepart(data) {
+  return request({
+    url: `/qdport-system/sys/dept/add`,
+    method: 'post',
+    params: data
+  })
+}
+
+// 删除部门
+export function deleteDepart(id) {
+  return request({
+    url: `/qdport-system/sys/dept/delete`,
+    method: 'post',
+    params: {
+      id
+    }
+  })
+}

+ 42 - 0
src/api/system/menu.js

@@ -0,0 +1,42 @@
+import request from '@/utils/request'
+
+// 获取菜单树
+export function getMenuTree(systemCode) {
+  return request({
+    url: '/qdport-system/sys/menu/menuManage',
+    method: 'GET',
+    params:{
+      systemCode
+    },
+  })
+}
+//新增
+export function addMenu(data) {
+  return request({
+    url: '/qdport-system/sys/menu/add',
+    method: 'post',
+    headers: {
+      'Content-Type': 'application/x-www-form-urlencoded'
+    },
+    data
+  })
+}
+//修改
+export function modMenu(data) {
+  return request({
+    url: '/qdport-system/sys/menu/update',
+    method: 'post',
+    headers: {
+      'Content-Type': 'application/x-www-form-urlencoded'
+    },
+    data
+  })
+}
+//删除
+export function delMenu(params) {
+  return request({
+    url: '/qdport-system/sys/menu/delete',
+    method: 'post',
+    params
+  })
+}

+ 36 - 0
src/api/system/person.js

@@ -0,0 +1,36 @@
+import request from '@/utils/request'
+
+// 获取用户信息
+export function getUserInfo() {
+  return request({
+    url: '/qdport-system/sys/user/ownInfo',
+    method: 'GET',
+  })
+}
+// 上传用户信息
+export function updateUserInfo(params) {
+  return request({
+    url: '/qdport-system/sys/user/changeOwnInfo',
+    method: 'POST',
+    params
+  })
+}
+
+export function uploadPhoto(params) {
+  return request({
+    url: '/qdport-resource/oss/endpoint/put-file',
+    method: 'POST',
+    data: params,
+    headers: {
+      'Content-Type': 'multipart/form-data'
+    }
+  })
+}
+
+export function upChangePassword(params) {
+  return request({
+    url: '/qdport-system/sys/user/changePassword',
+    method: 'POST',
+    params
+  })
+}

+ 89 - 0
src/api/system/role.js

@@ -0,0 +1,89 @@
+import request from '@/utils/request'
+
+//角色列表
+export function getRoleList(params) {
+  return request({
+    url: '/qdport-system/sys/role/list',
+    method: 'GET',
+    params
+  })
+}
+
+// 获取菜单树
+export function getMenuTree() {
+  return request({
+    url: '/qdport-system/sys/menu/basicList',
+    method: 'get',
+  })
+}
+
+// 获取角色的菜单数
+export function getRoleMenuTree(id) {
+  return request({
+    url: `/qdport-system/sys/role/${id}/menu/list`,
+    method: 'get',
+  })
+}
+
+// 接口权限树
+export function getPortTree() {
+  return request({
+    url: `/qdport-system/sys/menu/assignDataMenu`,
+    method: 'get',
+  })
+}
+
+// 角色接口权限树
+export function getRolePortTree(id) {
+  return request({
+    url: `/qdport-system/sys/role/${id}/menu/data/list`,
+    method: 'get',
+  })
+}
+
+// 更新角色菜单树
+export function setRoleMenuTree(id, data) {
+  return request({
+    url: `/qdport-system/sys/role/${id}/menu/update`,
+    method: 'post',
+    params: data
+  })
+}
+
+// 更新角色接口树
+export function setRolePortTree(data) {
+  return request({
+    url: `/qdport-system/sys/role/assignAuth`,
+    method: 'post',
+    params: data
+  })
+}
+
+// 更新角色
+export function setRole(data) {
+  return request({
+    url: `/qdport-system/sys/role/update`,
+    method: 'post',
+    params: data
+  })
+}
+
+// 新增角色
+export function addRole(data) {
+  return request({
+    url: `/qdport-system/sys/role/add`,
+    method: 'post',
+    params: data
+  })
+}
+
+// 删除角色
+export function deleteRole(id) {
+  return request({
+    url: `/qdport-system/sys/role/delete`,
+    method: 'post',
+    params: {
+      id
+    }
+  })
+}

+ 94 - 0
src/api/system/user.js

@@ -0,0 +1,94 @@
+import request from '@/utils/request'
+
+// 获取部门树
+export function getDeptTree() {
+    return request({
+        url: '/qdport-system/sys/dept/tree',
+        method: 'GET',
+
+    })
+}
+//用户列表
+export function getUserList(params) {
+    return request({
+        url: '/qdport-system/sys/user/list',
+        method: 'GET',
+        params
+    })
+}
+
+
+//角色表
+export function getRoleList(params) {
+    return request({
+        url: '/qdport-system/sys/role/list',
+        method: 'GET',
+        params
+    })
+}
+//用户拥有的角色
+
+export function byUserIdfnRole(userId) {
+    return request({
+        url: `/qdport-system/sys/user/${userId}/role/list`,
+        method: 'GET',
+    })
+}
+//更新用户角色
+export function byUserIduPRole(userId, data) {
+    return request({
+        url: `/qdport-system/sys/user/${userId}/role/update`,
+        method: 'post',
+        headers: {
+            'Content-Type': 'application/x-www-form-urlencoded'
+        },
+        data
+    })
+}
+
+
+//新增
+export function addUser(data) {
+    return request({
+        url: '/qdport-system/sys/user/add',
+        method: 'post',
+        headers: {
+            'Content-Type': 'application/x-www-form-urlencoded'
+        },
+        data
+    })
+}
+//修改
+export function modUser(data) {
+    return request({
+        url: '/qdport-system/sys/user/update',
+        method: 'post',
+        headers: {
+            'Content-Type': 'application/x-www-form-urlencoded'
+        },
+        data
+    })
+}
+//批量删除
+export function delUser(data) {
+    return request({
+        url: '/qdport-system/sys/user/delete/ids',
+        method: 'post',
+        headers: {
+            'Content-Type': 'application/x-www-form-urlencoded'
+        },
+        data
+    })
+}
+//批量重置密码
+export function restPwdUser(data) {
+    return request({
+        url: '/qdport-system/sys/user/resetPassword',
+        method: 'post',
+        headers: {
+            'Content-Type': 'application/x-www-form-urlencoded'
+        },
+        data
+    })
+}
+

二进制
src/assets/font/Alibaba-PuHuiTi-B.ttf


二进制
src/assets/font/Alibaba-PuHuiTi-M.ttf


二进制
src/assets/font/Alibaba-PuHuiTi-R.ttf


二进制
src/assets/images/Slice12x.png


二进制
src/assets/images/Slice22x.png


二进制
src/assets/images/Slice32x.png


二进制
src/assets/images/Slice42x.png


文件差异内容过多而无法显示
+ 1 - 0
src/assets/images/dingding.svg


文件差异内容过多而无法显示
+ 1 - 0
src/assets/images/email.svg


二进制
src/assets/images/home-book.png


二进制
src/assets/images/home-email.png


二进制
src/assets/images/home-file.png


二进制
src/assets/images/home-ring.png


二进制
src/assets/images/loading-logo-old.png


二进制
src/assets/images/login_back.png


二进制
src/assets/images/login_lock.png


二进制
src/assets/images/login_user.png


二进制
src/assets/images/logo.png


二进制
src/assets/images/menu_img.png


二进制
src/assets/images/pwd.png


二进制
src/assets/images/style1.png


二进制
src/assets/images/user.png


二进制
src/assets/images/welcomelogo.png


+ 133 - 0
src/assets/style/common.scss

@@ -0,0 +1,133 @@
+//ui 库
+//查询表格列表统一样式
+.tjm_card_style_custom {
+    // border:1px solid red;
+    .tjm_card_title {
+        font-size: 18px;
+        font-weight: 500;
+    }
+
+    .tjm_cart_pagination {
+        margin-top: 20px;
+        width: 100%;
+        display: flex;
+        justify-content: right;
+        align-items: center;
+    }
+
+    .tjm_card_select {
+        margin-top: 20px;
+        display: flex;
+        align-items: flex-start;
+        justify-content: space-between;
+
+        // 
+        .tjm_card_select_left {
+            // border:1px solid red;    
+            flex: 1;
+
+            // .el-form-item__content {
+            //     width: 280px;
+            //     // border:1px solid red;
+            // }
+            .el-form-item{
+                width: 308px !important;
+                // border:1px solid red;
+                .el-select {
+                    width: 100% !important;
+                }
+            }
+            .el-input {
+                // --el-input-width: 280px !important;
+            }
+
+            .el-select {
+                // width: 280px !important;
+            }
+
+            // .el-date-editor {
+            //     width: 280px !important;
+            // }
+
+            // .el-date-editor--daterange {
+            //     width: 280px !important;
+            // }
+        }
+
+        .tjm_card_select_right {
+            // border:1px solid red;
+            height: 80px;
+            width: 100px;
+            display: flex;
+            flex-direction: column;
+            justify-content: space-between;
+            align-items: flex-end;
+            border-left: 1px solid #F7F8FA;
+        }
+    }
+
+    .tjm_card_tools {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        margin-bottom: 20px;
+
+        .tjm_card_tools_left {}
+
+        .tjm_card_tools_right {}
+    }
+
+    .tjm_card_table {
+        display: flex;
+        margin-top: 20px;
+        .tjm_card_table_left {
+            margin-right: 10px;
+            width: 280px;
+            // border:1px solid red;
+        }
+
+        .tjm_card_table_right {
+            flex: 1;
+            width: 0;
+
+        }
+        .tjm_card_table_double_left{
+            width: 43%;
+    
+        }
+        .tjm_card_table_double_right{
+            width: 43%;
+        }
+        .tjm_card_table_double_opea{
+            flex: 1;
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            justify-content: center;
+        }
+        .tjm_card_table_header {
+            background: #F4F4F4;
+            color: #333333;
+        }
+    }
+
+    .tjm_card_pagination {
+        width: 100%;
+        margin-top: 20px;
+        display: flex;
+        justify-content: flex-end;
+
+    }   
+}
+
+//统一按钮内icon距右文字
+.tjm_btn_icon_right {
+    margin-right: 8px;
+    font-size: 24px;
+}
+
+// 横排列弹性盒子
+.flex-row {
+    display: flex;
+    align-items: center;
+}

+ 6 - 0
src/assets/style/elementPlusCustom.scss

@@ -0,0 +1,6 @@
+// 统一修改element plus 样式
+
+//去除 [ dropdown ]移入 黑框
+.el-tooltip__trigger:focus-visible {
+    outline: unset;
+}

+ 20 - 0
src/assets/style/font.scss

@@ -0,0 +1,20 @@
+
+@font-face {
+    font-family: 'Alibaba PuHuiTi B';
+    src: url('../font/Alibaba-PuHuiTi-B.ttf') format('truetype');
+}
+
+
+@font-face {
+    font-family: 'Alibaba PuHuiTi M';
+    src: url('../font/Alibaba-PuHuiTi-M.ttf') format('truetype');
+}
+
+
+@font-face {
+    font-family: 'Alibaba PuHuiTi R';
+    src: url('../font/Alibaba-PuHuiTi-R.ttf') format('truetype');
+}
+body,html{
+    font-family: 'Alibaba PuHuiTi M' !important;
+}

+ 5 - 0
src/assets/style/index.scss

@@ -0,0 +1,5 @@
+@import './theme';
+@import './reset';
+@import './font';
+@import './elementPlusCustom';
+@import './common';

+ 109 - 0
src/assets/style/reset.scss

@@ -0,0 +1,109 @@
+//样式重置
+body,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+hr,
+p,
+blockquote,
+dl,
+dt,
+dd,
+ul,
+ol,
+li,
+pre,
+form,
+fieldset,
+legend,
+button,
+input,
+textarea,
+th,
+td {
+    margin: 0;
+    padding: 0;
+}
+
+body,
+button,
+input,
+select,
+textarea {
+    font: 12px/1.5tahoma, arial, \5b8b\4f53;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+    font-size: 100%;
+}
+
+address,
+cite,
+dfn,
+em,
+var {
+    font-style: normal;
+}
+
+code,
+kbd,
+pre,
+samp {
+    font-family: couriernew, courier, monospace;
+}
+
+small {
+    font-size: 12px;
+}
+
+ul,
+ol {
+    list-style: none;
+}
+
+a {
+    text-decoration: none;
+}
+
+a:hover {
+    text-decoration: underline;
+}
+.tags-view-item {
+    text-decoration: none;
+  }
+sup {
+    vertical-align: text-top;
+}
+
+sub {
+    vertical-align: text-bottom;
+}
+
+legend {
+    color: #000;
+}
+
+fieldset,
+img {
+    border: 0;
+}
+
+button,
+input,
+select,
+textarea {
+    font-size: 100%;
+}
+
+table {
+    border-collapse: collapse;
+    border-spacing: 0;
+}

+ 15 - 0
src/assets/style/theme.scss

@@ -0,0 +1,15 @@
+/** element内置黑暗主题 */
+@use 'element-plus/theme-chalk/src/dark/css-vars.scss' as *;
+
+/** 自定义黑暗主题 */
+html.dark {
+
+    .tjm_navbar_container,
+    .tjm_tabsbar_container {
+        background: black !important;
+    }
+
+    .content_container {
+        background: black !important;
+    }
+}

+ 39 - 0
src/assets/style/variable.scss

@@ -0,0 +1,39 @@
+//字体相关配置
+$base-color-white: #fff;
+$base-border-radius: 4px;
+$base-color-gray: #F7F8FA;
+
+
+// 默认层级
+$base-z-index: 999;
+// 默认动画长
+$base-transition-time: 0.3s;
+// 默认阴影
+$base-box-shadow: 0 1px 4px rgb(0 21 41 / 8%);
+// 默认paddiing
+$base-padding: 10px;
+
+
+
+// sidebar 相关
+// 左侧宽度
+$base-sidebar-width: 224px; //256
+//折叠后宽度
+$base-sidebar-width-min: 64px;
+//菜单高度
+$base-sidebar-menu-item-height: 50px;
+//背景颜色
+$base-sidebar-menu-background: #283a5e;
+//====================
+//navbar相关
+$base-navbar-height: 60px;
+//====================
+//tabsBar相关
+// 顶部多标签页tabs-bar的高度
+$base-tabsbar-height: 55px;
+// 顶部多标签页tabs-bar中每一个item的高度
+$base-tag-item-height: 34px;
+
+//====================
+//main显示相关
+$base-main-height: calc(100vh - $base-navbar-height - $base-tabsbar-height - $base-padding - $base-padding - $base-padding);

文件差异内容过多而无法显示
+ 1 - 0
src/assets/svg/checkbox.svg


+ 1 - 0
src/assets/svg/home/test.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M4 6H2v14a2 2 0 0 0 2 2h14v-2H4V6m16-4a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h12m-3 5a3 3 0 0 0-3-3a3 3 0 0 0-3 3a3 3 0 0 0 3 3a3 3 0 0 0 3-3m-9 8v1h12v-1c0-2-4-3.1-6-3.1S8 13 8 15Z"/></svg>

+ 3 - 0
src/assets/svg/readme.md

@@ -0,0 +1,3 @@
+# 说明
+svg 下 编辑菜单下拉选择的数据 可动态使用 详见 侧边栏Icon渲染 
+其余为 自动导入静态使用

+ 1 - 0
src/assets/svg/test.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="m23 12l-2.44-2.78l.34-3.68l-3.61-.82l-1.89-3.18L12 3L8.6 1.54L6.71 4.72l-3.61.81l.34 3.68L1 12l2.44 2.78l-.34 3.69l3.61.82l1.89 3.18L12 21l3.4 1.46l1.89-3.18l3.61-.82l-.34-3.68L23 12m-10 5h-2v-2h2v2m0-4h-2V7h2v6Z"/></svg>

+ 1 - 0
src/assets/svg/user/test.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="m23 12l-2.44-2.78l.34-3.68l-3.61-.82l-1.89-3.18L12 3L8.6 1.54L6.71 4.72l-3.61.81l.34 3.68L1 12l2.44 2.78l-.34 3.69l3.61.82l1.89 3.18L12 21l3.4 1.46l1.89-3.18l3.61-.82l-.34-3.68L23 12m-10 5h-2v-2h2v2m0-4h-2V7h2v6Z"/></svg>

二进制
src/assets/video/loginBackground.mp4


+ 128 - 0
src/components/IconSelect/index.vue

@@ -0,0 +1,128 @@
+<template>
+  <div class="icon-body">
+    <el-input
+      v-model="iconName"
+      class="icon-search"
+      clearable
+      placeholder="请输入图标名称"
+      @clear="filterIcons"
+      @input="filterIcons"
+    >
+      <template #suffix><i class="el-icon-search el-input__icon" /></template>
+    </el-input>
+    <div class="icon-list">
+      <div class="list-container">
+        <div
+          v-for="(item, index) in iconList"
+          class="icon-item-wrapper"
+          :key="index"
+          @click="selectedIcon(item)"
+        >
+          <div :class="['icon-item', { active: activeIcon === item }]">
+            <el-icon size="16" class="icon" v-if="item.indexOf('ep')!==-1">
+              <component :is="item.slice(2)" />
+            </el-icon>
+            <svg-icon
+              v-if="item.indexOf('tjm')!==-1"
+              :icon-class="item.slice(4)"
+              class-name="icon"
+              style="height: 16px; width: 16px"
+            />
+            <span>{{ item.indexOf('ep')!==-1?item.slice(2):item.slice(4) }}</span>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import icons from './requireIcons'
+
+const props = defineProps({
+  activeIcon: {
+    type: String
+  }
+})
+
+const iconName = ref('')
+const iconList = ref(icons)
+const emit = defineEmits(['selected'])
+
+function filterIcons() {
+  iconList.value = icons
+  if (iconName.value) {
+    iconList.value = icons.filter(item => item.indexOf(iconName.value) !== -1)
+  }
+}
+
+function selectedIcon(name) {
+  emit('selected', name)
+  document.body.click()
+}
+
+function reset() {
+  iconName.value = ''
+  iconList.value = icons
+}
+
+defineExpose({
+  reset
+})
+</script>
+
+<style lang="scss" scoped>
+.icon-body {
+  width: 100%;
+  padding: 10px;
+  .icon-search {
+    position: relative;
+    margin-bottom: 5px;
+  }
+  .icon-list {
+    height: 200px;
+    overflow: auto;
+    .list-container {
+      display: flex;
+      flex-wrap: wrap;
+      .icon-item-wrapper {
+        width: calc(100% / 3);
+        height: 25px;
+        line-height: 25px;
+        cursor: pointer;
+        display: flex;
+        .icon-item {
+          display: flex;
+          max-width: 100%;
+          height: 100%;
+          padding: 0 5px;
+          // border:1px solid cyan;
+          align-items: center;
+          justify-content: space-between;
+          &:hover {
+            background: #ececec;
+            border-radius: 5px;
+          }
+          .icon {
+            flex-shrink: 0;
+            // border:1px solid red;
+          }
+          span {
+            display: inline-block;
+            vertical-align: -0.15em;
+            fill: currentColor;
+            padding-left: 2px;
+            overflow: hidden;
+            text-overflow: ellipsis;
+            white-space: nowrap;
+          }
+        }
+        .icon-item.active {
+          background: #ececec;
+          border-radius: 5px;
+        }
+      }
+    }
+  }
+}
+</style>

+ 12 - 0
src/components/IconSelect/requireIcons.js

@@ -0,0 +1,12 @@
+let icons = []
+const modules = import.meta.glob('./../../assets/svg/*.svg');
+import * as components from '@element-plus/icons-vue'
+for (const path in modules) {
+  const p = path.split('assets/svg/')[1].split('.svg')[0];
+  icons.push('tjm-'+p);
+}
+for (const key in components) {
+  icons.push("ep-"+key);
+}
+
+export default icons

+ 3 - 0
src/components/ParentView/index.vue

@@ -0,0 +1,3 @@
+<template >
+  <router-view />
+</template>

+ 44 - 0
src/components/SvgIcon/index.vue

@@ -0,0 +1,44 @@
+<template>
+  <svg :class="svgClass" aria-hidden="true">
+    <use :xlink:href="iconName" :fill="color" />
+  </svg>
+</template>
+
+<script>
+export default defineComponent({
+  props: {
+    iconClass: {
+      type: String,
+      required: true
+    },
+    className: {
+      type: String,
+      default: ''
+    },
+    color: {
+      type: String,
+      default: ''
+    },
+  },
+ 
+  setup(props) {
+    return {
+      iconName: computed(() => `#icon-${props.iconClass}`),
+      svgClass: computed(() => {
+        if (props.className) {
+          return `svg-icon ${props.className}`
+        }
+        return 'svg-icon'
+      })
+    }
+  }
+})
+</script>
+
+<style scope lang="scss">
+
+.svg-icon {
+  width: 24px;
+  height: 24px;
+}
+</style>

+ 9 - 0
src/components/SvgIcon/svgicon.js

@@ -0,0 +1,9 @@
+import * as components from '@element-plus/icons-vue'
+export default {
+    install: (app) => {
+        for (const key in components) {
+            const componentConfig = components[key];
+            app.component(componentConfig.name, componentConfig);
+        }
+    },
+};

+ 25 - 0
src/components/test.vue

@@ -0,0 +1,25 @@
+<script setup>
+const name = ref('tjm')
+31231313
+</script>
+<template>
+   <el-table mb-1 :data="[]" />
+      <el-pagination :total="100" />
+  <div>hello {{ name }}, this is helloworld components</div>
+  <div>hello {{ name }}, this is helloworld components</div>
+  <div>hello {{ name }}, this is helloworld components</div>
+  <div>hello {{ name }}, this is helloworld components</div>
+  <div>hello {{ name }}, this is helloworld components</div>
+  <div>hello {{ name }}, this is helloworld components</div>
+  <div>hello {{ name }}, this is helloworld components</div>
+  <div>hello {{ name }}, this is helloworld components</div>
+  <el-icon color="red" size="22">
+    <tjm-icon-ep-expand />
+  </el-icon>
+  <tjm-icon-mdi-account-circle class=""/>
+  <tjm-icon-user-test></tjm-icon-user-test>
+  <tjm-icon-home-test></tjm-icon-home-test>
+
+</template>
+
+<style scoped></style>

+ 98 - 0
src/config/config.js

@@ -0,0 +1,98 @@
+/*
+ * @Author: fangqing lfangqing@126.com
+ * @Date: 2023-10-17 13:45:04
+ * @Description: 全局config 配置文件
+ * @LastEditors: fangqing lfangqing@126.com
+ * @LastEditTime: 2023-10-26 17:35:44
+ */
+
+// 获取环境变量
+const ENV = import.meta.env
+// 配置文件
+let config = {
+
+}
+// 默认配置文件
+const configSource = {
+    appCode: ENV.VITE_APP_CODE,
+    // 项目标识代码
+    projectCode: `${ENV.VITE_APP_CODE}_${ENV.VITE_APP_ENV}`,
+    // 项目名
+    projectName: ENV.VITE_APP_NAME,
+    // 项目描述
+    projectDesc: ENV.VITE_APP_DESCRIPTION,
+    // 资源base地址
+    base: ENV.VITE_BASE,
+    // 接口代理URL路径
+    baseUrl: ENV.VITE_BASE_URL,
+    // 模拟数据接口路径
+    mockBaseUrl: ENV.VITE_BASE_MOCK_URL,
+    // 服务端接口路径
+    serverUrl: ENV.VITE_BASE_SERVER_URL,
+    //国际化默认语言
+    globalI18n: 'zh-cn',
+    // 客户端id
+    clientId: 'pcManageId',
+    // 客户端密钥
+    clientSecret: 'pcManageSecret',
+}
+
+/**
+ * @description: 重置全局配置
+ * @return {*} 局默认配置 configSource
+ */
+const resetConfig = () => {
+    config = { ...configSource }
+    return config
+}
+resetConfig()
+
+/**
+ * @description: 设置全局配置
+ * @param {*} cfg 配置项
+ * @return {*} 新的全局配置
+ */
+const setConfig = cfg => {
+    config = Object.assign(config, cfg)
+    return config
+}
+
+/**
+ * @description: 获取全局配置
+ * @param {*} key  配置项   
+ * @return {*} 新的全局配置项
+ */
+const getConfig = key => {
+    if (typeof key === 'string') {
+        const arr = key.split('.')
+        if (arr && arr.length) {
+            let data = config
+            arr.forEach(v => {
+                if (data && typeof data[v] !== 'undefined') {
+                    data = data[v]
+                } else {
+                    data = null
+                }
+            })
+            return data
+        }
+    }
+    if (Array.isArray(key)) {
+        const data = config
+        if (key && key.length > 1) {
+            let res = {}
+            key.forEach(v => {
+                if (data && typeof data[v] !== 'undefined') {
+                    res[v] = data[v]
+                } else {
+                    res[v] = null
+                }
+            })
+            return res
+        }
+        return data[key]
+    }
+    return { ...config }
+}
+
+export { getConfig, setConfig, resetConfig }

+ 5 - 0
src/directive/index.js

@@ -0,0 +1,5 @@
+
+import hasPermi from './permission/hasPermi'
+export default function directive(app){
+  app.directive('hasPermi', hasPermi)
+}

+ 22 - 0
src/directive/permission/hasPermi.js

@@ -0,0 +1,22 @@
+ 
+ import { useUserStore } from '@/store/user.js'
+ export default {
+   mounted(el, binding, vnode) {
+     const { value } = binding
+     const all_permission = "*:*:*";
+     const permissions = useUserStore().permissions
+     if (value && value instanceof Array && value.length > 0) {
+       const permissionFlag = value
+       const hasPermissions = permissions.some(permission => {
+         return all_permission === permission || permissionFlag.includes(permission)
+       })
+ 
+       if (!hasPermissions) {
+         el.parentNode && el.parentNode.removeChild(el)
+       }
+     } else {
+       throw new Error(`请设置操作权限标签值`)
+     }
+   }
+ }
+ 

+ 25 - 0
src/i18n/index.js

@@ -0,0 +1,25 @@
+import { createI18n } from 'vue-i18n'
+import {getConfig} from "@/config/config"
+import zhCnLocale from 'element-plus/dist/locale/zh-cn.mjs'
+import enLocale from 'element-plus/dist/locale/en.mjs'
+import nextZhCn from './lang/zh-cn'
+import nextEn from './lang/en'
+const messages = {
+	"zh-cn": {
+		...zhCnLocale,
+		noep: { ...nextZhCn},
+	},
+	"en": {
+		...enLocale,
+		noep: { ...nextEn, },
+	},
+}
+
+ const i18n = createI18n({
+	legacy: false, 
+	globalInjection: true, 
+	locale: getConfig('globalI18n'),
+	fallbackLocale: "zh-cn",
+	messages,
+})
+export default i18n

+ 6 - 0
src/i18n/lang/en.js

@@ -0,0 +1,6 @@
+export default {
+	test: {
+		home: 'home',
+		docsLink: 'System Docs ',
+	},
+}

+ 6 - 0
src/i18n/lang/zh-cn.js

@@ -0,0 +1,6 @@
+export default {
+    test: {
+		home: '首页',
+		docsLink: '系统指南',
+	},
+}

+ 45 - 0
src/layout/components/Logo/index.vue

@@ -0,0 +1,45 @@
+<script setup>
+import { useSettingStore } from '@/store/settings.js'
+import { getConfig } from '@/config/config'
+const settingStore = useSettingStore()
+const { collapse,layoutStyle } = storeToRefs(settingStore)
+const projectName = ref(getConfig('projectName'))
+</script>
+
+<template>
+  <div class="tjm_logo_container">
+    <img class="logo"  v-if="!collapse && layoutStyle=='vertical'" src="../../../assets/images/menu_img.png" alt="" />
+    <img class="onlyLogo" v-else src="../../../assets/images/loading-logo-old.png" alt="" srcset="">
+    <!-- <h1 class="title" v-if="!collapse">{{ projectName }}</h1> -->
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.tjm_logo_container {
+  position: relative;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 60px;
+  overflow: hidden;
+  background: transparent;
+  .logo {
+    display: inline-block;
+    width: 132px;
+    height: 32px;
+    vertical-align: middle;
+  }
+  .onlyLogo{
+    width: 60px;
+  }
+  .title {
+    display: inline-block;
+    margin-left: 12px;
+    font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif;
+    font-size: 20px;
+    font-weight: 600;
+    color: #fff;
+    vertical-align: middle;
+  }
+}
+</style>

+ 24 - 0
src/layout/components/Main/index.vue

@@ -0,0 +1,24 @@
+<script setup>
+  import { useTabsBarStore } from '@/store/tabsbar.js'
+  const cachedViews = useTabsBarStore().cachedViews
+  // v-if="!route.meta.link"
+</script>
+<template>
+  <section class="tjm_main_content_container">
+    <router-view v-slot="{ Component, route }">
+      <transition appear name="fade-transform" mode="out-in">
+        <keep-alive :include="cachedViews">
+          <component  :is="Component" :key="route.path"/>
+        </keep-alive>  
+      </transition>
+    </router-view>
+  </section>
+</template>
+
+<style lang="scss" scoped>
+.tjm_main_content_container {
+  min-height: $base-main-height;
+  background-color: inherit;
+  padding: $base-padding;
+}
+</style>

+ 70 - 0
src/layout/components/NavBar/components/Breadcrumb.vue

@@ -0,0 +1,70 @@
+<template>
+  <el-breadcrumb class="tjm-breadcrumb" separator="/">
+    <transition-group name="breadcrumb">
+      <el-breadcrumb-item v-for="(item, index) in levelList" :key="item.path">
+        <span
+          v-if="item.redirect === 'noRedirect' || index == levelList.length - 1"
+          class="no-redirect"
+          >{{ item.meta.title }}</span
+        >
+        <a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
+      </el-breadcrumb-item>
+    </transition-group>
+  </el-breadcrumb>
+</template>
+
+<script setup>
+const route = useRoute()
+const router = useRouter()
+const levelList = ref([])
+
+function getBreadcrumb() {
+ 
+  let matched = route.matched.filter(item => item.meta && item.meta.title)
+
+  const first = matched[0]
+  if (!isDashboard(first)) {
+    matched = [{ path: '/home/index', meta: { title: '首页' } }].concat(matched)
+  }
+  levelList.value = matched.filter(
+    item => item.meta && item.meta.title && item.meta.breadcrumb !== false
+  )
+}
+function isDashboard(route) {
+  const name = route && route.name
+  if (!name) {
+    return false
+  }
+  return name.trim() === 'Index'
+}
+function handleLink(item) {
+  const { redirect, path } = item
+  if (redirect) {
+    router.push(redirect)
+    return
+  }
+  // router.push(path)
+}
+
+watchEffect(() => {
+  if (route.path.startsWith('/redirect/')) {
+    return
+  }
+  getBreadcrumb()
+})
+getBreadcrumb()
+</script>
+
+<style lang="scss" scoped>
+.tjm-breadcrumb.el-breadcrumb {
+  display: inline-block;
+  font-size: 14px;
+  line-height: 50px;
+  margin-left: 8px;
+
+  .no-redirect {
+    color: #97a8be;
+    cursor: text;
+  }
+}
+</style>

+ 20 - 0
src/layout/components/NavBar/components/FullScreenBtn.vue

@@ -0,0 +1,20 @@
+<template>
+  <el-icon class="tjm_tools_icon" size="24" @click="toggle" :color="layoutStyle=='vertical'?'':'#fff'">
+    <tjm-icon-ep-FullScreen />
+  </el-icon>
+</template>
+
+<script setup>
+import { useFullscreen } from '@vueuse/core'
+import { useSettingStore } from '@/store/settings.js'
+const settingStore = useSettingStore()
+const { layoutStyle } = storeToRefs(settingStore)
+const { isFullscreen, enter, exit, toggle } = useFullscreen()
+</script>
+
+<style lang='scss' scoped>
+.tjm_tools_icon {
+  margin-left: 10px;
+  cursor: pointer;
+}
+</style>

+ 29 - 0
src/layout/components/NavBar/components/LayoutStyleSet.vue

@@ -0,0 +1,29 @@
+<template>
+  <div class="layoutStyleSet">
+    <el-icon class="tjm_tools_icon" size="24" :color="layoutStyle=='vertical'?'':'#fff'">
+      <tjm-icon-ep-Grid @click="microShowClick"/>
+    </el-icon>
+  </div>
+</template>
+
+<script setup>
+import { useSettingStore } from '@/store/settings.js'
+import { watchEffect } from 'vue'
+
+const settingStore = useSettingStore()
+
+const { layoutStyle } = storeToRefs(settingStore)
+
+function microShowClick() {
+  layoutStyle.value = layoutStyle.value =='vertical'?'horizontal':'vertical'
+}
+</script>
+
+<style lang='scss' scoped>
+.layoutStyleSet {
+  margin-left: 5px;
+  display: flex;
+  align-items: center;
+  cursor: pointer;
+}
+</style>

+ 211 - 0
src/layout/components/NavBar/components/MenuSearch.vue

@@ -0,0 +1,211 @@
+<template>
+  <div :class="{ show: show }" class="menu_search">
+    <el-icon class="tjm_tools_icon" size="24" :color="layoutStyle=='vertical'?'':'#fff'">
+      <tjm-icon-ep-Search @click.stop="click" />
+    </el-icon>
+    <el-select
+      v-model="search"
+      :remote-method="querySearch"
+      filterable
+      default-first-option
+      remote
+      placeholder="Select"
+      ref="menuSearchSelectRef"
+      class="menu_search_select"
+      auto-complete="off"
+      @change="change"
+    >
+      <el-option
+        v-for="option in options"
+        :key="option.item.path"
+        :value="option.item"
+        :label="option.item.title.join(' > ')"
+      />
+    </el-select>
+  </div>
+</template>
+
+<script setup>
+import Fuse from 'fuse.js' //轻量级模糊查询
+import { usePermissionStore } from '@/store/permission'
+const search = ref('')
+const options = ref([])
+const show = ref(false)
+const menuSearchSelectRef = ref(null)
+const fuse = ref(undefined)
+const router = useRouter()
+const sidebarRouters = computed(() => usePermissionStore().sidebarRouters)
+const searchPool = ref([])
+import { useSettingStore } from '@/store/settings.js'
+const settingStore = useSettingStore()
+const { layoutStyle } = storeToRefs(settingStore)
+function change(val) {
+  const path = val.path
+  const query = val.query
+  if (isHttp(path)) {
+    // http(s):// 路径新窗口打开
+    const pindex = path.indexOf('http')
+    window.open(path.substr(pindex, path.length), '_blank')
+  }
+  if (path.startsWith('/tjmchild')) {
+    const parts = path.split('/')
+    router.push({
+      name: parts[2],
+      params: { page: parts.slice(3).join('/') }
+    })
+  } else {
+    if (query) {
+      router.push({ path: path, query: JSON.parse(query) })
+    } else {
+      router.push(path)
+    }
+  }
+
+  search.value = ''
+  options.value = []
+  nextTick(() => {
+    show.value = false
+  })
+}
+function isHttp(url) {
+  return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1
+}
+
+function initFuse(list) {
+  fuse.value = new Fuse(list, {
+    shouldSort: true, // 是否对结果进行排序
+    threshold: 0.4, // 阈值控制匹配的敏感度
+    location: 0, // 匹配的位置,0 表示开头匹配
+    distance: 100, // 搜索的最大距离
+    minMatchCharLength: 1, // 最小匹配字符长度
+    keys: [
+      {
+        name: 'title',
+        weight: 0.7
+      },
+      {
+        name: 'path',
+        weight: 0.3
+      }
+    ]
+  })
+}
+watch(searchPool, list => {
+  initFuse(list)
+})
+onMounted(() => {
+  searchPool.value = generateRoutes(sidebarRouters.value)
+})
+
+watchEffect(() => {
+  searchPool.value = generateRoutes(sidebarRouters.value)
+})
+
+function getNormalPath(p) {
+  if (p.length === 0 || !p || p == 'undefined') {
+    return p
+  }
+  let res = p.replace('//', '/')
+  if (res[res.length - 1] === '/') {
+    return res.slice(0, res.length - 1)
+  }
+  return res
+}
+
+function generateRoutes(routes, basePath = '', prefixTitle = []) {
+  let res = []
+  for (const r of routes) {
+    if (r.hidden) {
+      continue
+    }
+    const p = r.path.length > 0 && r.path[0] === '/' ? r.path : '/' + r.path
+    const data = {
+      path: !isHttp(r.path) ? getNormalPath(basePath + p) : r.path,
+      title: [...prefixTitle]
+    }
+    if (r.meta && r.meta.title) {
+      data.title = [...data.title, r.meta.title]
+      if (r.redirect !== 'noRedirect') {
+        res.push(data)
+      }
+    }
+    if (r.query) {
+      data.query = r.query
+    }
+    if (r.children) {
+      const tempRoutes = generateRoutes(r.children, '', data.title)
+      if (tempRoutes.length >= 1) {
+        res = [...res, ...tempRoutes]
+      }
+    }
+  }
+  return res
+}
+
+function click() {
+  show.value = !show.value
+  if (show.value) {
+    menuSearchSelectRef.value && menuSearchSelectRef.value.focus()
+  }
+}
+function close() {
+  menuSearchSelectRef.value && menuSearchSelectRef.value.blur()
+  options.value = []
+  show.value = false
+}
+
+function querySearch(query) {
+  if (query !== '') {
+    options.value = fuse.value.search(query)
+  } else {
+    options.value = []
+  }
+}
+
+watch(show, value => {
+  if (value) {
+    document.body.addEventListener('click', close)
+  } else {
+    document.body.removeEventListener('click', close)
+  }
+})
+</script>
+
+<style lang='scss' scoped>
+.menu_search {
+  font-size: 0 !important;
+  display: flex;
+  align-items: center;
+  justify-content: space-around;
+  margin-left: 5px;
+  .tjm_tools_icon {
+    cursor: pointer;
+  }
+  .menu_search_select {
+    font-size: 18px;
+    transition: width 0.2s;
+    width: 0;
+    overflow: hidden;
+    background: transparent;
+    border-radius: 0;
+    display: inline-block;
+    vertical-align: middle;
+    --el-select-input-focus-border-color: none !important;
+    :deep(.el-input__inner) {
+      border-radius: 0;
+      border: 0;
+      padding-left: 0;
+      padding-right: 0;
+      box-shadow: none !important;
+      border-bottom: 1px solid #d9d9d9;
+      vertical-align: middle;
+    }
+  }
+  &.show {
+    .menu_search_select {
+      width: 210px;
+      margin-left: 10px;
+    }
+  }
+}
+</style>

+ 114 - 0
src/layout/components/NavBar/index.vue

@@ -0,0 +1,114 @@
+<template>
+  <div :class="{'tjm_navbar_container':true,'layoutStyleHorizontal':layoutStyle=='horizontal'}">
+    <div class="tjm_navbar_left" v-if="layoutStyle=='vertical'">
+      <el-icon
+        class="tjm_collapse_icon"
+        size="28"
+        @click="collapse = !collapse"
+      >
+        <tjm-icon-ep-fold v-if="!collapse" />
+        <tjm-icon-ep-expand v-else />
+      </el-icon>
+      &nbsp; &nbsp; &nbsp;
+      <Breadcrumb />
+    </div>
+    <div class="tjm_navbar_left" v-else>
+      <tjm-side-bar/>
+    </div>
+    <div class="tjm_navbar_right">
+      <div class="tjm_tools_container">
+        <full-screen-btn></full-screen-btn>
+        <menu-search></menu-search>
+        <layout-style-set></layout-style-set>
+      </div>
+      <div class="tjm_user_container">
+        <el-dropdown>
+          <el-avatar
+            :size="36"
+            src="https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png"
+          />
+          <template #dropdown>
+            <el-dropdown-menu>
+              <el-dropdown-item @click='goUserInfo'>个人信息</el-dropdown-item>
+              <el-dropdown-item @click="logout">退出登录</el-dropdown-item>
+            </el-dropdown-menu>
+          </template>
+        </el-dropdown>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import TjmSideBar from '../SideBar/indexH.vue'
+import Breadcrumb from './components/Breadcrumb.vue'
+import FullScreenBtn from '@/layout/components/NavBar/components/FullScreenBtn.vue'
+import MenuSearch from '@/layout/components/NavBar/components/MenuSearch.vue'
+import LayoutStyleSet from '@/layout/components/NavBar/components/LayoutStyleSet.vue'
+import { useSettingStore } from '@/store/settings.js'
+const settingStore = useSettingStore()
+const { collapse,layoutStyle } = storeToRefs(settingStore)
+import { useUserStore } from '@/store/user.js'
+const router = useRouter()
+function goUserInfo(){
+  router.push('/userInfo')
+}
+const logout = () => {
+  ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(() => {
+      useUserStore()
+        .logOut()
+        .then(() => {
+          location.href = '/index'
+        })
+    })
+    .catch(() => {})
+}
+
+</script>
+
+
+<style lang="scss" scoped>
+.tjm_navbar_container {
+  position: relative;
+  height: $base-navbar-height;
+  overflow: hidden;
+  user-select: none;
+  box-shadow: $base-box-shadow;
+  background: $base-color-white;
+  padding-right: $base-padding;
+  padding-left: $base-padding;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  .tjm_navbar_left{
+     display: flex;
+     align-items: center;  
+      flex: 1;
+  }
+  .tjm_navbar_right {
+    padding-left: 80px;
+    display: flex;
+    align-items: center;
+    .tjm_tools_container {
+      display: flex;
+      align-items: center;
+      margin-right: 20px;
+      .tjm_tools_icon {
+        margin-left: 10px;
+        cursor: pointer;
+      }
+    }
+    .tjm_user_container{
+      cursor: pointer;
+    }
+  }
+}
+.layoutStyleHorizontal{
+  background: #283a5e;
+}
+</style>

+ 109 - 0
src/layout/components/SideBar/index.vue

@@ -0,0 +1,109 @@
+<script setup>
+import Logo from '../Logo/index.vue'
+import { useSettingStore } from '@/store/settings.js'
+import { usePermissionStore } from '@/store/permission.js'
+import sideBarItem from './sideBarItem.vue'
+import { watchEffect } from 'vue'
+
+const route = useRoute()
+const router = useRouter()
+
+const { collapse } = storeToRefs(useSettingStore())
+
+const sidebarRouters = computed(() => usePermissionStore().sidebarRouters)
+
+const theme = computed(() => useSettingStore().theme)
+
+const activeMenu = computed(() => {
+  const { meta, path } = route
+  if (meta.activeMenu) {
+    return meta.activeMenu
+  }
+  let dealPath= decodeURIComponent(path)
+  if(dealPath.startsWith('/workflow')){
+      dealPath  =  '/tjmchild'+dealPath   
+      return dealPath
+  }
+  return path
+})
+</script>
+
+<template>
+  <div class="tjm_sidebar_container" :class="{ 'is-collapse': !collapse }">
+    <Logo />
+    <el-scrollbar style="height: calc(100% - 60px)">
+      <el-menu
+        :default-active="activeMenu"
+        :collapse="collapse"
+        background-color="#283a5e"
+        text-color="#fff"
+        :unique-opened="true"
+        :active-text-color="theme"
+        :collapse-transition="false"
+        mode="vertical"
+      >
+        <sideBarItem
+          v-for="(route, index) in sidebarRouters"
+          :key="route.path + index"
+          :item="route"
+        />
+      </el-menu>
+    </el-scrollbar>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@mixin active {
+  // &:hover {
+  //   color: $base-color-white;
+  // }
+
+  // &.is-active {
+  //   color: $base-color-white;
+  //   background-color: var(--el-color-primary) !important;
+  // }
+}
+
+.tjm_sidebar_container {
+  position: fixed;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  z-index: $base-z-index;
+  // width: $base-sidebar-width;
+  height: 100vh;
+  box-shadow: 2px 0 6px rgb(0 21 41 / 35%);
+  border-right: 1px solid #ccc;
+  background: $base-sidebar-menu-background;
+  transition: width $base-transition-time;
+
+  &.is-collapse {
+    width: $base-sidebar-width;
+    border-right: 0;
+  }
+
+  :deep(.el-scrollbar__wrap) {
+    overflow-x: hidden;
+
+    .el-menu {
+      border: 0;
+    }
+    .el-sub-menu.is-active > .el-sub-menu__title{
+      color: #409EFF !important;
+    }
+    .el-menu-item,
+    .el-submenu__title {
+      height: $base-sidebar-menu-item-height;
+      overflow: hidden;
+      line-height: $base-sidebar-menu-item-height;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+      vertical-align: middle;
+    }
+
+    .el-menu-item {
+      @include active;
+    }
+  }
+}
+</style>

+ 78 - 0
src/layout/components/SideBar/indexH.vue

@@ -0,0 +1,78 @@
+<script setup>
+import Logo from '../Logo/index.vue'
+import { useSettingStore } from '@/store/settings.js'
+import { usePermissionStore } from '@/store/permission.js'
+import sideBarItem from './sideBarItem.vue'
+import { watchEffect } from 'vue'
+
+const route = useRoute()
+const router = useRouter()
+
+const { collapse } = storeToRefs(useSettingStore())
+
+const sidebarRouters = computed(() => usePermissionStore().sidebarRouters)
+
+const theme = computed(() => useSettingStore().theme)
+
+const activeMenu = computed(() => {
+  const { meta, path } = route
+  if (meta.activeMenu) {
+    return meta.activeMenu
+  }
+  let dealPath= decodeURIComponent(path)
+  if(dealPath.startsWith('/workflow')){
+      dealPath  =  '/tjmchild'+dealPath   
+      return dealPath
+  }
+  return path
+})
+</script>
+
+<template>
+  <div class="tjm_sidebar_container">
+    <div class="logo-container">  
+         <Logo />
+    </div>
+    <el-scrollbar  class="sidebar-scrollbar">
+      <el-menu
+        :default-active="activeMenu"
+        background-color="#283a5e"
+        text-color="#fff"
+        :unique-opened="true"
+        :active-text-color="theme"
+        :collapse-transition="false"
+        mode="horizontal"
+        :ellipsis='false'
+      >
+        <sideBarItem
+          v-for="(route, index) in sidebarRouters"
+          :key="route.path + index"
+          :item="route"
+        />
+      </el-menu>
+    </el-scrollbar>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+
+.tjm_sidebar_container {
+    display: flex;
+    width: 100%;
+     height: $base-navbar-height;
+    .logo-container{
+        
+    }
+    .sidebar-scrollbar{
+        // width: 400px;
+        // border:1px solid red;
+    }
+      :deep(.el-scrollbar__wrap) {
+        .el-menu--horizontal.el-menu {
+            border-bottom:none;
+        }
+        
+      }
+
+}
+</style>

+ 104 - 0
src/layout/components/SideBar/sideBarItem.vue

@@ -0,0 +1,104 @@
+<script setup>
+const props = defineProps({
+  item: {
+    type: Object,
+    required: true
+  }
+})
+const onlyOneChild = ref(null)
+function fatherHasOneSonOrMenu(children = [], parent) {
+  if (!children) {
+    children = []
+  }
+  const showingChildren = children.filter(item => {
+    if (item.hidden) {
+      return false
+    } else {
+      onlyOneChild.value = item
+      return true
+    }
+  })
+  if (showingChildren.length === 1) {
+    return true
+  }
+  if (showingChildren.length === 0) {
+    onlyOneChild.value = { ...parent, noShowingChildren: true }
+    return true
+  }
+  return false
+}
+function isExternal(path) {
+  return /^(https?:|mailto:|tel:)/.test(path)
+}
+
+const router = useRouter()
+const handleClickMenu = item => {
+  console.log(item.path)
+  if (isExternal(item.path)) {
+    window.open(item.path, '_blank')
+  } else {
+    if (item.path.startsWith('/tjmchild')) {
+      const parts = item.path.split('/') // 使用斜杠分割路径
+      console.log(parts.slice(3).join('/'))
+      router.push({
+        name: parts[2],
+        params: { page: parts.slice(3).join('/') }
+      })
+    } else {
+      router.push(item.path)
+    }
+  }
+}
+</script>
+
+<template>
+  <template v-if="!item.hidden">
+    <template v-if="fatherHasOneSonOrMenu(item.children, item) && !item.alwaysShow">
+      <el-menu-item
+        :index="onlyOneChild.path"
+        @click="handleClickMenu(onlyOneChild)"
+      >
+        <el-icon
+          size="16"
+          class="icon"
+          v-if="onlyOneChild.meta.icon.indexOf('ep') !== -1"
+        >
+          <component :is="onlyOneChild.meta.icon.slice(2)" />
+        </el-icon>
+        <svg-icon
+          v-if="onlyOneChild.meta.icon.indexOf('tjm') !== -1"
+          :icon-class="onlyOneChild.meta.icon.slice(4)"
+          class-name="icon"
+          style="height: 16px; width: 16px"
+        />
+
+        <template #title>{{ onlyOneChild.meta.title }}</template>
+      </el-menu-item>
+    </template>
+    <el-sub-menu v-else :index="item.path">
+      <template #title>
+        <el-icon
+          size="16"
+          class="icon"
+          v-if="item.meta.icon.indexOf('ep') !== -1"
+        >
+          <component :is="item.meta.icon.slice(2)" />
+        </el-icon>
+        <svg-icon
+          v-if="item.meta.icon.indexOf('tjm') !== -1"
+          :icon-class="item.meta.icon.slice(4)"
+          class-name="icon"
+          style="height: 16px; width: 16px"
+        />
+        <span>{{ item.meta.title }}</span>
+      </template>
+      <sideBarItem
+        v-for="(route, index) in item.children"
+        :key="route.path + index"
+        :item="route"
+      />
+    </el-sub-menu>
+  </template>
+</template>
+
+<style lang="scss" scoped></style>

+ 331 - 0
src/layout/components/TabsBar/index.vue

@@ -0,0 +1,331 @@
+
+<template>
+  <div class="tjm_tabsbar_container">
+    <el-scrollbar
+      ref="scrollContainer"
+      :vertical="false"
+      class="tjm_tabs-content"
+      @wheel.prevent="handleScroll"
+    >
+      <router-link
+        v-for="tag in visitedViews"
+        :key="tag.path"
+        :data-path="tag.path"
+        :class="isActive(tag) ? 'active' : ''"
+        :to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
+        class="tags-view-item"
+        :style="activeStyle(tag)"
+        @click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
+        @contextmenu.prevent="openMenu(tag, $event)"
+      >
+        {{ tag.title }}
+        <span v-if="!isAffix(tag)" @click.prevent.stop="closeSelectedTag(tag)">
+          <close
+            class="el-icon-close"
+            style="width: 1em; height: 1em; vertical-align: middle"
+          />
+        </span>
+      </router-link>
+    </el-scrollbar>
+    <ul
+      v-show="visible"
+      :style="{ left: left + 'px', top: top + 'px' }"
+      class="contextmenu"
+    >
+      <li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">
+        <close style="width: 1em; height: 1em;" /> 关闭当前
+      </li>
+       <li @click="closeOthersTags">
+        <circle-close style="width: 1em; height: 1em;" /> 关闭其他
+      </li>
+    </ul>
+  </div>
+</template>
+<script setup>
+import { useTabsBarStore } from '@/store/tabsbar.js'
+import { usePermissionStore } from '@/store/permission.js'
+import { useSettingStore } from '@/store/settings.js'
+const routes = computed(() => usePermissionStore().routes)
+const route = useRoute()
+const router = useRouter()
+
+//==================================================
+const visitedViews = computed(() => useTabsBarStore().visitedViews)
+const affixTags = ref([])
+const visible = ref(false)
+const theme = computed(() => useSettingStore().theme)
+const top = ref(0)
+const left = ref(0)
+const selectedTag = ref({})
+
+const { proxy } = getCurrentInstance()
+watch(route, () => {
+  addTags()
+  // moveToCurrentTag()
+})
+onMounted(() => {
+  initTags()
+  addTags()
+})
+
+
+function addTags() {
+  const { name } = route
+  if (name) {
+    useTabsBarStore().addView(route)
+  }
+  return false
+}
+
+function initTags() {
+  const res = filterAffixTags(routes.value)
+  affixTags.value = res
+  for (const tag of res) {
+    if (tag.name) {
+      useTabsBarStore().addVisitedView(tag)
+    }
+  }
+}
+
+function moveToCurrentTag() {
+  nextTick(() => {
+    for (const r of visitedViews.value) {
+      if (r.path === route.path) {
+        scrollPaneRef.value.moveToTarget(r)
+        if (r.fullPath !== route.fullPath) {
+          useTagsViewStore().updateVisitedView(route)
+        }
+      }
+    }
+  })
+}
+
+function filterAffixTags(routes, basePath = '') {
+  let tags = []
+  routes.forEach(route => {
+    if (route.meta && route.meta.affix) {
+      const tagPath = getNormalPath(basePath + '/' + route.path)
+      tags.push({
+        fullPath: tagPath,
+        path: tagPath,
+        name: route.name,
+        meta: { ...route.meta }
+      })
+    }
+    if (route.children) {
+      const tempTags = filterAffixTags(route.children, route.path)
+      if (tempTags.length >= 1) {
+        tags = [...tags, ...tempTags]
+      }
+    }
+  })
+  return tags
+}
+
+
+function getNormalPath(p) {
+  if (p.length === 0 || !p || p == 'undefined') {
+    return p
+  }
+  let res = p.replace('//', '/')
+  if (res[res.length - 1] === '/') {
+    return res.slice(0, res.length - 1)
+  }
+  return res
+}
+
+
+
+function closeMenu() {
+  visible.value = false
+}
+
+function handleScroll() {
+  closeMenu()
+}
+watch(visible, (value) => {
+  if (value) {
+    document.body.addEventListener('click', closeMenu)
+  } else {
+    document.body.removeEventListener('click', closeMenu)
+  }
+})
+
+function openMenu(tag, e) {
+  const menuMinWidth = 105
+  const offsetLeft = proxy.$el.getBoundingClientRect().left 
+  const offsetWidth = proxy.$el.offsetWidth // container width
+  const maxLeft = offsetWidth - menuMinWidth // left boundary
+  const l = e.clientX - offsetLeft + 15 // 15: margin right
+
+  if (l > maxLeft) {
+    left.value = maxLeft
+  } else {
+    left.value = l
+  }
+  
+  top.value = 15
+  visible.value = true
+  selectedTag.value = tag
+ 
+}
+
+
+function isActive(r) {
+  return r.path === decodeURIComponent(route.path)
+}
+
+
+
+function activeStyle(tag) {
+  if (!isActive(tag)) return {}
+  return {
+    'background-color': theme.value,
+    'border-color': theme.value
+  }
+}
+
+function isAffix(tag) {
+  // if(tag.name=="Index") return true
+  return tag.meta && tag.meta.affix
+}
+
+function closeSelectedTag(view) {
+  useTabsBarStore()
+    .delView(view)
+    .then(({ visitedViews }) => {
+      if (isActive(view)) {
+        toLastView(visitedViews, view)
+      }
+    })
+}
+function closeOthersTags() {
+  useTabsBarStore().delOthersViews(selectedTag.value).then(({visitedViews})=>{
+     toLastView(visitedViews, selectedTag.value)
+  })
+}
+function toLastView(visitedViews, view) {
+  const latestView = visitedViews.slice(-1)[0]
+  if (latestView) {
+    router.push(latestView.fullPath)
+  } else {
+    if (view.name === 'Index') {
+      router.replace({ path: '/redirect' + view.fullPath })
+    } else {
+      router.push('/')
+    }
+  }
+}
+</script>
+ 
+<style lang="scss" scoped>
+.tjm_tabsbar_container {
+  position: relative;
+  height: $base-tabsbar-height;
+  user-select: none;
+  border-top: 1px solid #f6f6f6;
+  display: flex;
+  align-items: center;
+  padding-right: $base-padding;
+  padding-left: $base-padding;
+  background: $base-color-white;
+  white-space: nowrap;
+  .tjm_tabs-content {
+    height: 40px;
+    width: 100%;
+    // display: flex;
+    // align-items: center;
+    .tags-view-item {
+      display: inline-block;
+      position: relative;
+      cursor: pointer;
+      height: 26px;
+      line-height: 26px;
+      border: 1px solid #d8dce5;
+      color: #495060;
+      background: #fff;
+      padding: 0 8px;
+      font-size: 12px;
+      margin-left: 5px;
+      margin-top: 4px;
+      text-decoration: none;
+      &:hover {
+        border: 1px solid #409eff;
+      }
+      &:first-of-type {
+        margin-left: 15px;
+      }
+      &:last-of-type {
+        margin-right: 15px;
+      }
+      &.active {
+        background-color: #42b983;
+        color: #fff;
+        border-color: #42b983;
+
+        &::before {
+          content: '';
+          background: #fff;
+          display: inline-block;
+          width: 8px;
+          height: 8px;
+          border-radius: 50%;
+          position: relative;
+          margin-right: 5px;
+        }
+      }
+    }
+  }
+   .contextmenu {
+      margin: 0;
+      background: #fff;
+      position: absolute;
+      z-index: 3000;
+      list-style-type: none;
+      padding: 5px 0;
+      border-radius: 4px;
+      font-size: 12px;
+      font-weight: 400;
+      color: #333;
+      box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
+      li {
+        margin: 0;
+        padding: 7px 16px;
+        cursor: pointer;
+        &:hover {
+          background: #eee;
+        }
+      }
+    }
+}
+</style>
+<style lang="scss">
+.tjm_tabs-content {
+  a {
+    text-decoration: none;
+  }
+  .router-link-active {
+    text-decoration: none;
+  }
+  .el-icon-close {
+    width: 16px;
+    height: 16px;
+    vertical-align: 2px;
+    border-radius: 50%;
+    text-align: center;
+    transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
+    transform-origin: 100% 50%;
+    &:before {
+      transform: scale(0.6);
+      display: inline-block;
+      vertical-align: -3px;
+    }
+    &:hover {
+      background-color: #b4bccc;
+      color: #fff;
+      width: 12px !important;
+      height: 12px !important;
+    }
+  }
+}
+</style>

+ 89 - 0
src/layout/index.vue

@@ -0,0 +1,89 @@
+<template>
+  <!--  :locale="localeLang" -->
+  <el-config-provider :locale="localeLang">
+    <div class="tjm_wrapper">
+      <div class="tjm_warpper_container fixed">
+        <tjm-side-bar v-if="layoutStyle=='vertical'" />
+        <div class="tjm_container" :class="{ 'is-collapse': collapse,'is-layoutStyle':layoutStyle=='horizontal' }">
+          <div
+            class="header_container fixed-header"
+            :class="{ 'is-collapse': collapse,'is-layoutStyle':layoutStyle=='horizontal' }"
+          >
+            <tjm-nav-bar />
+            <tjm-tabs-bar />
+          </div>
+          <div class="content_container">
+            <tjm-main />
+          </div>
+        </div>
+      </div>
+    </div>
+  </el-config-provider>
+</template>
+<script setup>
+import { getConfig, setConfig } from '@/config/config'
+import TjmSideBar from './components/SideBar/index.vue'
+import TjmNavBar from './components/NavBar/index.vue'
+import TjmTabsBar from './components/TabsBar/index.vue'
+import TjmMain from './components/Main/index.vue'
+import { useSettingStore } from '@/store/settings.js'
+const settingStore = useSettingStore()
+const { collapse,layoutStyle } = storeToRefs(settingStore)
+import { useI18n } from 'vue-i18n'
+const { messages, locale, t } = useI18n()
+const localeLang = ref(messages[getConfig('globalI18n')])
+// 修改element 和 i18n 默认语言
+const changeLanguage = () => {
+  locale.value = getConfig('globalI18n')
+  localeLang.value = messages.value[locale.value]
+}
+// 监听修改语言实现element
+watchEffect(changeLanguage)
+</script>
+<style lang="scss" scoped>
+@mixin fix-header {
+  position: fixed;
+  top: 0;
+  right: 0px;
+  z-index: $base-z-index - 2;
+  width: calc(100% - $base-sidebar-width);
+}
+.tjm_wrapper {
+  position: relative;
+  height: 100%;
+  width: 100%;
+  overflow: auto;
+  .tjm_warpper_container {
+    &.fixed {
+      padding-top: calc(#{$base-navbar-height} + #{$base-tabsbar-height});
+    }
+    .tjm_container {
+      min-height: 100%;
+      margin-left: $base-sidebar-width;
+      &.is-collapse {
+        margin-left: $base-sidebar-width-min;
+        border-right: 0;
+      }
+      &.is-layoutStyle{
+        margin-left: 0;
+        border-right: 0;
+      }
+      .header_container {
+        box-shadow: 0 1px 4px rgb(0 21 41 / 8%);
+        &.fixed-header {
+          @include fix-header;
+        }
+        &.is-collapse {
+          width: calc(100% - $base-sidebar-width-min);
+        }
+        &.is-layoutStyle{
+           width:100%;
+        }
+      }
+      .content_container {
+        background: #f7f8fa;
+      }
+    }
+  }
+}
+</style>

+ 24 - 0
src/main.js

@@ -0,0 +1,24 @@
+import { createApp } from 'vue'
+import { createPinia } from 'pinia'
+import piniaPluginPersist from 'pinia-plugin-persist'
+import App from './App.vue'
+import router from './router'
+import './permission'
+import '@/micro/index.js'
+import i18n from '@/i18n'
+import '@/assets/style/index.scss'
+import 'virtual:svg-icons-register'
+import SvgIcon from '@/components/SvgIcon/index.vue'
+import elementIcons from '@/components/SvgIcon/svgicon.js'
+import directive from './directive'
+
+const pinia = createPinia()
+pinia.use(piniaPluginPersist)
+const app = createApp(App)
+directive(app)
+app.use(pinia)
+app.use(router)
+app.use(elementIcons)
+app.component('svg-icon', SvgIcon)
+app.use(i18n)
+app.mount('#app')

+ 2 - 0
src/micro/index.js

@@ -0,0 +1,2 @@
+import microApp from '@micro-zoe/micro-app'
+microApp.start()

+ 61 - 0
src/micro/index.vue

@@ -0,0 +1,61 @@
+<template>
+  <div class="warp" v-loading="loading">
+    <!-- <h1>子应用👇</h1> -->
+    <!-- name:应用名称, url:应用地址 -->
+    <micro-app
+      name="my-app"
+      router-mode="native"
+      :url="url"
+      baseroute="/workflow/"
+      :data="dataForChild"
+      @created="created"
+      @beforemount="beforemount"
+      @mounted="mounted"
+      @unmount="unmount"
+      @error="error"
+    ></micro-app>
+  </div>
+</template>
+
+<script setup>
+import microApp from '@micro-zoe/micro-app'
+import { getRefreshToken } from '@/utils/auth'
+const url = import.meta.env.VITE_WORKFLOW_URL
+const dataForChild = {
+  token: getRefreshToken()
+}
+const router = useRouter()
+const loading = ref(true)
+function created() {
+  console.log('micro-app元素被创建')
+}
+function beforemount() {
+  console.log('即将被渲染')
+  loading.value = true
+}
+function mounted() {
+  console.log('渲染完成')
+  const url = decodeURIComponent(router.currentRoute.value.fullPath)
+  //  microApp.setData("app1", { path: path})
+  microApp.router.push({ name: 'my-app', path: url })
+  
+  loading.value = false
+}
+function unmount() {
+  console.log('已经卸载')
+}
+function error() {
+  console.log('渲染出错')
+  ElMessage({
+    message: '工作流项目渲染出错请检查工作流项目',
+    type: 'error'
+  })
+  router.push('/index')
+}
+</script>
+
+<style lang='scss' scoped>
+.warp {
+  min-height: 600px;
+}
+</style>

+ 69 - 0
src/otherLogin/bx.vue

@@ -0,0 +1,69 @@
+<template>
+  <!-- 宝信单点登录 -->
+  <div>
+    <div class="center_loading" v-loading:body="loading"></div>
+  </div>
+</template>
+
+<script setup>
+import { onMounted } from 'vue'
+const { VITE_BX_AUTH_URL, VITE_BX_CLIENT_ID, VITE_BX_REDIRECT_URL } =import.meta.env
+import { useUserStore } from '@/store/user.js'
+const loading = ref(true)
+const router = useRouter()
+onMounted(() => {
+  let geturl = window.location.href
+  console.log(geturl)
+  let getqyinfo = geturl.split('?')[1] //  截取到参数部分
+  let getqys = new URLSearchParams('?' + getqyinfo) //将参数放在URLSearchParams函数中
+  let code = getqys.get('code')
+  let redirect_url = getqys.get('redirect_url')
+  if (redirect_url) {
+    localStorage.setItem('edgeRedirect', redirect_url)
+  }
+  if (code) {
+    goLoginEplat(code)
+  } else {
+    let url =
+      VITE_BX_AUTH_URL +
+      '?response_type=code&client_id=' +
+      VITE_BX_CLIENT_ID +
+      '&redirect_uri=' +
+      VITE_BX_REDIRECT_URL +
+      '&scope=write'
+    console.log(url)
+    window.location.href = url
+  }
+})
+function goLoginEplat(code) {
+  console.log(code)
+  useUserStore()
+    .bxLogin(code)
+    .then(() => {
+      const redirect_url = localStorage.getItem('edgeRedirect')
+      if (redirect_url) {
+        router.push(redirect_url)
+        localStorage.removeItem('edgeRedirect')
+      } else {
+        router.push('/')
+      }
+    })
+    .catch(err => {
+        router.push('/noPermission')
+        //  console.log("登录成功")
+    })
+}
+</script>
+
+<style lang='scss' scoped>
+.center_loading {
+  position: fixed;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  width: 100%; /* 调整宽度 */
+  height: 100px; /* 调整高度 */
+  //   background-color: #ccc; /* 背景颜色 */
+  //   border: 1px solid #000; /* 边框 */
+}
+</style>

+ 67 - 0
src/otherLogin/ssoSgzl.vue

@@ -0,0 +1,67 @@
+<template>
+ 
+  <div>
+    <div class="center_loading" v-loading:body="loading"></div>
+  </div>
+</template>
+
+<script setup>
+import { onMounted } from 'vue'
+const { VITE_EDGE_AUTH_URL, VITE_EDGE_CLIENT_ID, VITE_EDGE_REDIRECT_URL } =
+  import.meta.env
+import { useUserStore } from '@/store/user.js'
+const loading = ref(true)
+onMounted(() => {
+  let geturl = window.location.href
+  console.log(geturl)
+  let getqyinfo = geturl.split('?')[1] //  截取到参数部分
+  let getqys = new URLSearchParams('?' + getqyinfo) //将参数放在URLSearchParams函数中
+  let code = getqys.get('code')
+  let redirect_url = getqys.get('redirect_url')
+  if (redirect_url) {
+    localStorage.setItem('edgeRedirect', redirect_url)
+  }
+  if (code) {
+    goLoginEplat(code)
+  } else {
+    let url =
+      VITE_EDGE_AUTH_URL +
+      '?response_type=code&client_id=' +
+      VITE_EDGE_CLIENT_ID +
+      '&redirect_uri=' +
+      VITE_EDGE_REDIRECT_URL +
+      '&scope=all'
+    console.log(url)
+    window.location.href = url
+  }
+})
+const router = useRouter()
+function goLoginEplat(code) {
+  console.log(code)
+  useUserStore()
+    .edgeLogin(code)
+    .then(() => {
+      const redirect_url = localStorage.getItem('edgeRedirect')
+      if (redirect_url) {
+        router.push(redirect_url)
+        localStorage.removeItem('edgeRedirect')
+      } else {
+        router.push('/middleBackstage')
+      }
+    })
+    .catch(err => {})
+}
+</script>
+
+<style lang='scss' scoped>
+.center_loading {
+  position: fixed;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  width: 100%; /* 调整宽度 */
+  height: 100px; /* 调整高度 */
+  //   background-color: #ccc; /* 背景颜色 */
+  //   border: 1px solid #000; /* 边框 */
+}
+</style>

+ 45 - 0
src/permission.js

@@ -0,0 +1,45 @@
+import router from './router'
+import { getToken } from '@/utils/auth'
+import { useUserStore } from '@/store/user.js'
+import { useSettingStore } from '@/store/settings.js'
+import { usePermissionStore } from '@/store/permission.js'
+import { dynamicRoutes } from '@/router/dynamicRoutes'
+
+const whiteList = ['/login','/yh','/ssoyh']
+router.beforeEach((to, from, next) => {
+  if (getToken()) {
+    to.meta.title && useSettingStore().setTitle(to.meta.title)
+    if (useUserStore().roles.length === 0) {
+      useUserStore().RefreshInfo().then(res => {
+        usePermissionStore().generateRoutes().then(accessRoutes => {
+          accessRoutes.forEach(item => {
+            router.addRoute(item)
+          })
+          router.addRoute({
+            path: "/:pathMatch(.*)*",
+            name: 'notFound',
+            redirect: '/notfound',
+        })
+          next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
+        })
+      })
+    } else {
+      next()
+    }
+  } else { //登录
+    //白名单放行
+    if (whiteList.indexOf(to.path) !== -1) {
+      next()
+    } else {
+      next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
+    }
+  }
+
+})
+
+router.afterEach(() => {
+
+})
+router.onError((error) => {
+  console.warn('路由错误', error.message)
+})

+ 100 - 0
src/router/constantRoutes.js

@@ -0,0 +1,100 @@
+const Layout = () => import('@/layout/index.vue')
+export const staticRoutes = [
+  {
+    path: '/notfound',
+    name: 'notfound',
+    hidden: true,
+    component: () => import('@/views/404.vue')
+  },
+  {
+    path: '/redirect',
+    component: Layout,
+    hidden: true,
+    children: [
+      {
+        path: '/redirect/:path(.*)',
+        component: () => import('@/views/redirect/index.vue')
+      }
+    ]
+  },
+  {
+    path: '/login',
+    name: 'login',
+    hidden: true,
+    component: () => import('@/views/login/index.vue')
+  },
+  {
+    path: '/404',
+    name: '404',
+    hidden: true,
+    component: () => import('@/views/404.vue')
+  },
+  {
+    path: '/noPermission',
+    name: 'noPermission',
+    hidden: true,
+    component: () => import('@/views/error/noPermission.vue')
+  },
+  {
+    path: '/yh',
+    name: 'yhlogin',
+    hidden: true,
+    component: () => import('@/otherLogin/bx.vue'),
+  },
+  {
+    path: '/ssoyh',
+    name: 'ssoyh',
+    hidden: true,
+    component: () => import('@/otherLogin/ssoSgzl.vue'),
+  },
+  {
+    path: '',
+    component: Layout,
+    redirect: '/index',
+    alwaysShow: false,
+    children: [
+      
+      {
+        path: '/index',
+        name: 'Index',
+        component: () => import('@/views/home/home.vue'),
+        alwaysShow: false,
+        meta: {
+          title: '首页',
+          icon: 'ep-Cpu',
+          affix: false
+        },
+        children: []
+      },
+      {
+        path: '/workflow/:page*', 
+        name: 'workflow',
+        component: () => import('@/micro/index.vue'),
+        alwaysShow: false,
+        hidden:true,
+        meta: {
+            title: '微前端',
+            icon: 'ep-Cpu',
+            affix: false,
+            micro:true  //底座必须加
+        },
+        children: []
+      },
+      {
+        path: '/userInfo',
+        name: 'userInfo',
+        component: () => import('@/views/system/userInfo.vue'),
+        alwaysShow: false,
+        hidden:true,
+        meta: {
+          title: '个人信息',
+          icon: 'ep-Cpu',
+          affix: false
+        },
+        children: []
+      }
+    ]
+  },
+ 
+
+]

+ 73 - 0
src/router/dynamicRoutes.js

@@ -0,0 +1,73 @@
+//暂不启用
+const Layout = () => import('@/layout/index.vue')
+
+export const dynamicRoutes = [
+
+  // {
+  //     path: '/testManage',
+  //     component: Layout,
+  //     redirect: '/test',
+  //     meta: {
+  //         title: '测试',
+  //         icon: 'Box',
+  //     },
+  //     children: [
+  //         {
+  //             path: 'test',
+  //             name: 'Test',
+  //             component: () => import('@/views/test.vue'),
+  //             meta: {
+  //                 title: '测试',
+  //                 icon: 'Box',
+  //             },
+  //         }
+  //     ]
+  // },
+  // {
+  //     path: '/system',
+  //     component: Layout,
+  //     redirect: '/menu',
+  //     meta: {
+  //         title: '平台运维',
+  //         icon: 'Box',
+  //     },
+  //     children: [
+  //         {
+  //             path: 'user',
+  //             name: 'User',
+  //             component: () => import('@/views/system/user.vue'),
+  //             meta: {
+  //                 title: '用户管理',
+  //                 icon: 'Box',
+  //             },
+  //         },
+  //         {
+  //             path: 'dept',
+  //             name: 'Dept',
+  //             component: () => import('@/views/system/dept.vue'),
+  //             meta: {
+  //                 title: '部门管理',
+  //                 icon: 'Box',
+  //             },
+  //         },
+  //         {
+  //             path: 'role',
+  //             name: 'Role',
+  //             component: () => import('@/views/system/role.vue'),
+  //             meta: {
+  //                 title: '角色管理',
+  //                 icon: 'Box',
+  //             },
+  //         },
+  //         {
+  //             path: 'menu',
+  //             name: 'Menu',
+  //             component: () => import('@/views/system/menu.vue'),
+  //             meta: {
+  //                 title: '菜单管理',
+  //                 icon: 'Box',
+  //             },
+  //         }
+  //     ]
+  // }
+]

+ 26 - 0
src/router/index.js

@@ -0,0 +1,26 @@
+import { createRouter, createWebHistory } from 'vue-router'
+
+import { staticRoutes } from './constantRoutes'
+//缺陷:由于接口设计缺陷 menulist 返回不是一个类似的 路由信息 需要子级构造 多参数前端写死  @/stroe/permission
+// hidden: true,// 侧边栏隐藏
+// alwaysShow: false,//是否总是显示 (多用于控制目录只有一个菜单是否显示菜单) 目录属性
+// redirect: true,//在面包屑中是否可以点击 
+// meta: {
+//     title: 'title',
+//     icon: 'Box',
+//     cache:false, //是否缓存keepalive
+//     affix: true, //是否增加固定
+//     breadcrumb: false //是否添加到面包屑
+//     link:是否是链接   由于新建标签 显示故 暂时无作用
+//     micro:true  //底座必须加
+// },
+
+const router = createRouter({
+  history: createWebHistory(),
+  routes: staticRoutes,
+  // 刷新时,滚动条位置还原
+  scrollBehavior: () => ({ left: 0, top: 0 })
+})
+
+
+export default router

+ 172 - 0
src/store/permission.js

@@ -0,0 +1,172 @@
+import { staticRoutes } from '@/router/constantRoutes'
+import { dynamicRoutes } from '@/router/dynamicRoutes'
+
+const Micro = () => import('@/micro/index.vue')
+import { useUserStore } from '@/store/user.js'
+import Layout from '@/layout/index.vue'
+import ParentView from '@/components/ParentView/index.vue'
+
+const modules = import.meta.glob('./../views/**/*.vue')
+export const usePermissionStore = defineStore(
+  'permission',
+  {
+    state: () => ({
+      routes: [],
+      addRoutes: [],
+      sidebarRouters: [],
+      defaultRoutes: []
+    }),
+    actions: {
+      setRoutes(routes) {
+        this.addRoutes = routes
+        this.routes = staticRoutes.concat(routes)
+      },
+      setSidebarRouters(routes) {
+        this.sidebarRouters = routes
+      },
+      setDefaultRoutes(routes) {
+        this.defaultRoutes = staticRoutes.concat(routes)
+      },
+      generateRoutes() {
+        return new Promise(resolve => {
+          const sidebarRoutes = filterAsyncRouter(useUserStore().menuList[0].children[0].children)
+          this.setSidebarRouters(staticRoutes.concat(sidebarRoutes))
+          const rewriteRoutes = filterAsyncRouter(useUserStore().menuList[0].children[0].children, false)
+          this.setRoutes(rewriteRoutes)
+          resolve(rewriteRoutes)
+        })
+      }
+    }
+  }
+)
+// 
+function filterAsyncRouter(asyncRouterMap, httpSave = true) {
+  let arr = []
+  asyncRouterMap.filter(route => {
+    if (route.children != null && route.children && route.children.length && route.children[0].type != 2 && route.parentId == "1762659463383281665") {
+      let info = {
+        alwaysShow: true,
+        children: [],
+        component: Layout,
+        hidden: false,
+        meta: {
+          icon: route.icon,
+          link: '',
+          cache: false,
+          title: route.name
+        },
+        name: route.name,
+        path: route.path,
+        redirect: false
+      }
+      info.children = filterAsyncRouter(route.children)
+      if (info.children.length > 0) {
+        info.redirect = info.children[0].path
+      }
+      arr.push(info)
+    } else if (route.children != null && route.children && route.children.length && route.children[0].type != 2 && route.parentId != "1762659463383281665") {
+      let info = {
+        alwaysShow: true,
+        children: [],
+        component: ParentView,
+        hidden: false,
+        meta: {
+          icon: route.icon,
+          link: '',
+          cache: false,
+          title: route.name
+        },
+        name: route.name,
+        path: route.path,
+        redirect: false
+      }
+      info.children = filterAsyncRouter(route.children)
+      if (info.children.length > 0) {
+        info.redirect = info.children[0].path
+      }
+      arr.push(info)
+    } else if (route.children.length == 0 && route.parentId == "1762659463383281665") {
+      let info = {
+        path: '',
+        component: Layout,
+        redirect: route.path,
+        alwaysShow: false,
+        hidden: false,
+        meta: {
+          icon: route.icon,
+          title: route.name
+        },
+        children: [
+          {
+            // alwaysShow: false, //菜单不支持设置
+            children: [],
+            component: loadView(route.path),
+            hidden: false,
+            meta: {
+              icon: route.icon,
+              link: '',
+              cache: false,
+              title: route.name
+            },
+            name: route.name,
+            path: route.path,
+            redirect: false
+          }
+        ]
+
+      }
+      if (route.path.startsWith('http') || route.path.startsWith('/tjmchild') || route.path.includes('queryParams')) {
+        if (httpSave) {
+          // const prefix = '/tjmchild'
+          // if (route.path.startsWith(prefix)) {
+          //   info.redirect = route.path.slice(prefix.length)
+          //   info.children[0].path = route.path.slice(prefix.length)
+          // }
+          arr.push(info)
+        }
+      } else {
+        arr.push(info)
+      }
+    } else if (route.type == 1) {
+      let info = {
+        // alwaysShow: false, //菜单不支持设置
+        children: [],
+        component: loadView(route.path),
+        hidden: false,
+        meta: {
+          icon: route.icon,
+          link: '',
+          cache: false,
+          title: route.name
+        },
+        name: route.name,
+        path: route.path,
+        redirect: false
+      }
+      if (route.path.startsWith('http') || route.path.startsWith('/tjmchild') || route.path.includes('queryParams')) {  
+        if (httpSave) {
+          // const prefix = '/tjmchild'
+          // if (route.path.startsWith(prefix)) {
+          //   info.redirect = route.path.slice(prefix.length)
+          //   info.path = route.path.slice(prefix.length)
+          // }
+          arr.push(info)
+        }
+      } else {
+        arr.push(info)
+      }
+    }
+  })
+  return arr
+
+}
+
+function loadView(view) {
+  let res
+  for (const path in modules) {
+    const dir = '/' + path.split('views/')[1].split('.vue')[0]
+    if (dir === view) {
+      return modules[path]
+    }
+  }
+}

+ 27 - 0
src/store/settings.js

@@ -0,0 +1,27 @@
+import { getConfig } from '@/config/config'
+
+export const useSettingStore = defineStore('userSetting', {
+    state: () => ({
+        collapse: false,
+        layoutStyle:'vertical',
+        title: '',
+        theme:'#409EFF'
+    }),
+  persist: {
+    enabled: true,
+    strategies: [
+      {
+        key: 'useSetting',
+        storage: localStorage,
+      },
+    ],
+  },
+  getters: {},
+  actions: {
+    setTitle(title) {
+      this.title = title
+      document.title = getConfig('projectName') + '-' + title
+    }
+  }
+})
+

+ 196 - 0
src/store/tabsbar.js

@@ -0,0 +1,196 @@
+import { usePermissionStore } from '@/store/permission.js'
+import {findObjectByPath} from '@/utils/tjmTools'
+export  const useTabsBarStore = defineStore(
+    'tabsbar',
+    {
+        state: () => ({
+            visitedViews: [],  //已经访问
+            cachedViews: [],   //缓存
+            // iframeViews: [],  //弹出
+            sidebarRouters:computed(() => usePermissionStore().sidebarRouters)
+        }),
+        actions: {
+            
+            addVisitedView(view) {      
+                if (this.visitedViews.some(v => v.path === view.path)) return
+                const viewDeal = JSON.parse(JSON.stringify(view))
+                if(view.meta.micro){
+                    if (this.visitedViews.some(v => v.path === decodeURIComponent(view.path))) return
+                    view.meta.title =  findObjectByPath(this.sidebarRouters,view.path).meta.title
+                    let depath =decodeURIComponent(view.path)
+                    viewDeal.path = depath
+                    console.log(viewDeal)
+                }
+                this.visitedViews.push(
+                    Object.assign({}, viewDeal, {
+                        title:view.meta.title || '未知'
+                    })
+                )  
+            },
+            addCachedView(view) {
+                if (this.cachedViews.includes(view.name)) return
+                if (view.meta.cache) {
+                    this.cachedViews.push(view.name)
+                }
+            },
+            // addIframeView(view) {
+            //     if (this.iframeViews.some(v => v.path === view.path)) return
+            //     this.iframeViews.push(
+            //         Object.assign({}, view, {
+            //             title: view.meta.title || '未知'
+            //         })
+            //     )
+            // },
+           
+            addView(view) {
+                this.addVisitedView(view)
+                this.addCachedView(view)
+            },
+            delView(view) {
+                return new Promise(resolve => {
+                    this.delVisitedView(view)
+                    this.delCachedView(view)
+                    resolve({
+                        visitedViews: [...this.visitedViews],
+                        cachedViews: [...this.cachedViews]
+                    })
+                })
+            },
+            delVisitedView(view) {
+                return new Promise(resolve => {
+                    for (const [i, v] of this.visitedViews.entries()) {
+                        if (v.path === view.path) {
+                            this.visitedViews.splice(i, 1)
+                            break
+                        }
+                    }
+                    // this.iframeViews = this.iframeViews.filter(item => item.path !== view.path)
+                    resolve([...this.visitedViews])
+                })
+            },
+            // delIframeView(view) {
+            //     return new Promise(resolve => {
+            //         this.iframeViews = this.iframeViews.filter(item => item.path !== view.path)
+            //         resolve([...this.iframeViews])
+            //     })
+            // },
+
+            delCachedView(view) {
+                return new Promise(resolve => {
+                    const index = this.cachedViews.indexOf(view.name)
+                    index > -1 && this.cachedViews.splice(index, 1)
+                    resolve([...this.cachedViews])
+                })
+            },
+            delOthersViews(view) {
+                return new Promise(resolve => {
+                    this.delOthersVisitedViews(view)
+                    this.delOthersCachedViews(view)
+                    resolve({
+                        visitedViews: [...this.visitedViews],
+                        cachedViews: [...this.cachedViews]
+                    })
+                })
+            },
+            delOthersVisitedViews(view) {
+                return new Promise(resolve => {
+                    this.visitedViews = this.visitedViews.filter(v => {
+                        return v.meta.affix || v.path === view.path
+                    })
+                    // this.iframeViews = this.iframeViews.filter(item => item.path === view.path)
+                    resolve([...this.visitedViews])
+                })
+            },
+            delOthersCachedViews(view) {
+                return new Promise(resolve => {
+                    const index = this.cachedViews.indexOf(view.name)
+                    if (index > -1) {
+                        this.cachedViews = this.cachedViews.slice(index, index + 1)
+                    } else {
+                        this.cachedViews = []
+                    }
+                    resolve([...this.cachedViews])
+                })
+            },
+            delAllViews(view) {
+                return new Promise(resolve => {
+                    this.delAllVisitedViews(view)
+                    this.delAllCachedViews(view)
+                    resolve({
+                        visitedViews: [...this.visitedViews],
+                        cachedViews: [...this.cachedViews]
+                    })
+                })
+            },
+            delAllVisitedViews(view) {
+                return new Promise(resolve => {
+                    const affixTags = this.visitedViews.filter(tag => tag.meta.affix)
+                    this.visitedViews = affixTags
+                    // this.iframeViews = []
+                    resolve([...this.visitedViews])
+                })
+            },
+            delAllCachedViews(view) {
+                return new Promise(resolve => {
+                    this.cachedViews = []
+                    resolve([...this.cachedViews])
+                })
+            },
+            updateVisitedView(view) {
+                for (let v of this.visitedViews) {
+                    if (v.path === view.path) {
+                        v = Object.assign(v, view)
+                        break
+                    }
+                }
+            },
+            delRightTags(view) {
+                return new Promise(resolve => {
+                    const index = this.visitedViews.findIndex(v => v.path === view.path)
+                    if (index === -1) {
+                        return
+                    }
+                    this.visitedViews = this.visitedViews.filter((item, idx) => {
+                        if (idx <= index || (item.meta && item.meta.affix)) {
+                            return true
+                        }
+                        const i = this.cachedViews.indexOf(item.name)
+                        if (i > -1) {
+                            this.cachedViews.splice(i, 1)
+                        }
+                        // if (item.meta.link) {
+                        //     const fi = this.iframeViews.findIndex(v => v.path === item.path)
+                        //     this.iframeViews.splice(fi, 1)
+                        // }
+                        
+                        return false
+                    })
+                    resolve([...this.visitedViews])
+                })
+            },
+            delLeftTags(view) {
+                return new Promise(resolve => {
+                    const index = this.visitedViews.findIndex(v => v.path === view.path)
+                    if (index === -1) {
+                        return
+                    }
+                    this.visitedViews = this.visitedViews.filter((item, idx) => {
+                        if (idx >= index || (item.meta && item.meta.affix)) {
+                            return true
+                        }
+                        const i = this.cachedViews.indexOf(item.name)
+                        if (i > -1) {
+                            this.cachedViews.splice(i, 1)
+                        }
+                        // if (item.meta.link) {
+                        //     const fi = this.iframeViews.findIndex(v => v.path === item.path)
+                        //     this.iframeViews.splice(fi, 1)
+                        // }
+                        return false
+                    })
+                    resolve([...this.visitedViews])
+                })
+            }
+        },
+    }
+)

+ 168 - 0
src/store/user.js

@@ -0,0 +1,168 @@
+import { getToken, setToken, removeToken, setRefreshToken, getRefreshToken, removeRefreshToken } from '@/utils/auth'
+import { login } from '@/api/login'
+import CryptoJS from 'crypto-js'
+const { VITE_BX_CLIENT_ID, VITE_EDGE_CLIENT_ID } =import.meta.env
+export const useUserStore = defineStore(
+  'user',
+  {
+    state: () => ({
+      token: getToken(),
+      refreshToken: getRefreshToken(),
+      userInfo: {},
+      menuList: [],
+      roles: [],
+      permissions: []
+    }),
+    actions: {
+      //登录
+      login(userInfo) {
+        let data = {
+          username: userInfo.username,
+          password: CryptoJS.MD5(userInfo.password).toString(),
+          grant_type: 'password',
+          scope: 'all'
+        }
+        return new Promise((resolve, reject) => {
+          login(data).then(res => {
+            if (res.error_code == '400') {
+              ElMessage({
+                message: res.error_description,
+                type: 'warning'
+              })
+              reject('err')
+            } else if (res.error_code == '204') {
+              ElMessage({
+                message: '由于您长时间未登录,请修改密码后登录!',
+                type: 'warning'
+              })
+              reject(204)
+            } else if (res.error_code == '203') {
+              ElMessage({
+                message: '由于您长时间未登录,请校验手机号!',
+                type: 'warning'
+              })
+              reject( 203)
+            } else if (res.error_code == '500') {
+              ElMessage({
+                message: res.error_description,
+                type: 'warning'
+              })
+              reject('err')
+            } else {
+              setToken(res.access_token)
+              setRefreshToken(res.refresh_token)
+              this.userInfo = res.user
+              this.token = res.access_token
+              this.refreshToken = res.refresh_token
+              this.menuList = res.menus
+              this.permissions = getButtonPermiss([], res.menus)
+              resolve()
+            }
+          }).catch(error => {
+            reject(error)
+          })
+        })
+      },
+      //tips:获取信息(不需要实现持久化 刷新浏览器 重新调用接口获取最新的信息才是正确的处理方式)
+      RefreshInfo() {
+        return new Promise((resolve, reject) => {
+          let data = {
+            refresh_token: getRefreshToken(),
+            grant_type: 'refresh_token',
+            scope: 'all'
+          }
+          login(data).then(res => {
+            setToken(res.access_token)
+            setRefreshToken(res.refresh_token)
+            this.token = res.access_token
+            this.userInfo = res.user
+            this.refreshToken = res.refresh_token
+            this.roles = res.roles
+            this.menuList = res.menus
+            this.permissions = getButtonPermiss([], res.menus)
+            resolve(res)
+          }).catch(error => {
+            reject(error)
+          })
+        })
+      },
+       //宝信登录
+       bxLogin(code){
+        console.log("传递过code",code)
+        return new Promise((resolve, reject) => { 
+          login({
+            code,
+            source: VITE_BX_CLIENT_ID,
+            grant_type: "social",
+            scope: "all",
+          }).then(res => {
+            if (res.error_code == '400') {
+              ElMessage({
+                message: res.error_description,
+                type: 'warning'
+              })
+              reject('err')
+            } else{
+              setToken(res.access_token)
+              setRefreshToken(res.refresh_token)
+              this.userInfo = res.user
+              this.token = res.access_token
+              this.refreshToken = res.refresh_token
+              this.menuList = res.menus
+              this.permissions = getButtonPermiss([], res.menus)
+              resolve()
+            }
+          })
+        })
+      },
+       //边端登录
+       edgeLogin(code){
+        return new Promise((resolve, reject) => { 
+          login({
+            code,
+            source: VITE_EDGE_CLIENT_ID,
+            grant_type: "edge",
+            scope: "all",
+          }).then(res => {     
+            setToken(res.access_token)
+            setRefreshToken(res.refresh_token)
+            this.userInfo = res.user
+            this.token = res.access_token
+            this.refreshToken = res.refresh_token
+            this.menuList = res.menus
+            this.permissions = getButtonPermiss([], res.menus)
+            resolve()
+          })
+        })
+      },
+      // 退出系统
+      logOut() {
+        return new Promise((resolve, reject) => {
+          this.token = ''
+          this.refreshToken = ''
+          this.userInfo = {}
+          this.menuList = []
+          this.roles = []
+          this.permissions = []
+          removeToken()
+          removeRefreshToken()
+          resolve()
+
+        })
+      }
+    }
+  }
+)
+
+
+function getButtonPermiss(keys, List) {
+  for (let li of List) {
+    if (li.type == 2) {
+      keys.push(li.code)
+    }
+    if (li.children && li.children.length > 0) {
+      getButtonPermiss(keys, li.children)
+    }
+  }
+  return keys
+}

+ 118 - 0
src/utils/aes.js

@@ -0,0 +1,118 @@
+import AES from 'crypto-js'
+
+//加密方法
+export function Encrypt(word) {
+  //十六位十六进制数作为密钥
+  const key = AES.enc.Utf8.parse('0123456789ASDFGH')
+//十六位十六进制数作为密钥偏移量
+  const iv = AES.enc.Utf8.parse('ASDFGH0123456789')
+
+  const src = AES.enc.Utf8.parse(word)
+  const encrypted = AES.AES.encrypt(
+    src,
+    key,
+    {
+      iv: iv,
+      mode: AES.mode.CBC,
+      padding: AES.pad.Pkcs7
+    })
+  return encrypted.ciphertext.toString().toUpperCase()
+}
+
+//解密方法
+export function Decrypt(word) {
+  const encryptedHexStr = AES.enc.Hex.parse(word)
+  const src = AES.enc.Base64.stringify(encryptedHexStr)
+  const decrypt = AES.AES.decrypt(
+    src,
+    key,
+    {
+      iv: iv,
+      mode: AES.mode.CBC,
+      padding: AES.pad.Pkcs7
+    })
+  const decryptedStr = decrypt.toString(AES.enc.Utf8)
+  return decryptedStr.toString()
+
+}
+
+// 节流
+export function throttle(fn, time) {
+  let timer = null
+  time = time || 1000
+  return function(...args) {
+    if (timer) {
+      return
+    }
+    const _this = this
+    timer = setTimeout(() => {
+      timer = null
+    }, time)
+    fn.apply(_this, args)
+  }
+}
+
+// 防抖
+export function debounce(fn, time) {
+  time = time || 200
+  // 定时器
+  let timer = null
+  return function(...args) {
+    var _this = this
+    if (timer) {
+      clearTimeout(timer)
+    }
+    timer = setTimeout(function() {
+      timer = null
+      fn.apply(_this, args)
+    }, time)
+  }
+}
+
+export function debounceMax(func, wait, immediate = false) {
+  let timeout
+  return function() {
+    let context = this
+    let args = arguments
+    if (timeout) clearTimeout(timeout)
+    if (immediate) {
+      var callNow = !timeout
+      timeout = setTimeout(() => {
+        timeout = null
+      }, wait)
+      if (callNow) func.apply(context, args)
+    } else {
+      timeout = setTimeout(function() {
+        func.apply(context, args)
+      }, wait)
+    }
+  }
+}
+
+export function getClientIP(onNewIP) {
+  let MyPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection
+  let pc = new MyPeerConnection({
+    iceServers: []
+  })
+  let noop = () => {
+  }
+  let localIPs = {}
+  let ipRegex = /([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/g
+  let iterateIP = (ip) => {
+    if (!localIPs[ip]) onNewIP(ip)
+    localIPs[ip] = true
+  }
+  pc.createDataChannel('')
+  pc.createOffer().then((sdp) => {
+    sdp.sdp.split('\n').forEach(function(line) {
+      if (line.indexOf('candidate') < 0) return
+      line.match(ipRegex).forEach(iterateIP)
+    })
+    pc.setLocalDescription(sdp, noop, noop)
+  }).catch((reason) => {
+  })
+  pc.onicecandidate = (ice) => {
+    if (!ice || !ice.candidate || !ice.candidate.candidate || !ice.candidate.candidate.match(ipRegex)) return
+    ice.candidate.candidate.match(ipRegex).forEach(iterateIP)
+  }
+}

+ 27 - 0
src/utils/auth.js

@@ -0,0 +1,27 @@
+import Cookies from 'js-cookie'
+
+const TokenKey = 'Tjm-Admin-Boot-Token'
+const RefreshTokenKey = 'Tjm-Refresh-Admin-Boot-Token'
+export function getToken() {
+  return Cookies.get(TokenKey)
+}
+
+export function setToken(token) {
+  return Cookies.set(TokenKey, token)
+}
+
+export function removeToken() {
+  return Cookies.remove(TokenKey)
+}
+
+export function getRefreshToken() {
+  return Cookies.get(RefreshTokenKey)
+}
+
+export function setRefreshToken(token) {
+  return Cookies.set(RefreshTokenKey, token)
+}
+
+export function removeRefreshToken() {
+  return Cookies.remove(RefreshTokenKey)
+}

+ 71 - 0
src/utils/color.js

@@ -0,0 +1,71 @@
+import { ElMessage } from 'element-plus'
+
+/**
+ * hex颜色转rgb颜色
+ * @param str 颜色值字符串
+ * @returns 返回处理后的颜色值
+ */
+export function hexToRgb(str) {
+  let hexs = ''
+  const reg = /^\#?[0-9A-Fa-f]{6}$/
+  if (!reg.test(str)) return ElMessage.warning('输入错误的hex')
+  str = str.replace('#', '')
+
+  hexs = str.match(/../g)
+
+  for (let i = 0; i < 3; i++) hexs[i] = parseInt(hexs[i], 16)
+  return hexs
+}
+
+/**
+ * rgb颜色转Hex颜色
+ * @param r 代表红色
+ * @param g 代表绿色
+ * @param b 代表蓝色
+ * @returns 返回处理后的颜色值
+ */
+export function rgbToHex(r, g, b) {
+  const reg = /^\d{1,3}$/
+  if (!reg.test(r) || !reg.test(g) || !reg.test(b))
+    return ElMessage.warning('输入错误的rgb颜色值')
+  const hexs = [r.toString(16), g.toString(16), b.toString(16)]
+
+  for (let i = 0; i < 3; i++) if (hexs[i].length == 1) hexs[i] = `0${hexs[i]}`
+
+  return `#${hexs.join('')}`
+}
+
+/**
+ * 加深颜色值
+ * @param color 颜色值字符串
+ * @param level 加深的程度,限0-1之间
+ * @returns 返回处理后的颜色值
+ */
+export function getDarkColor(color, level) {
+  const reg = /^\#?[0-9A-Fa-f]{6}$/
+  if (!reg.test(color)) return ElMessage.warning('输入错误的hex颜色值')
+  const rgb = hexToRgb(color)
+  for (let i = 0; i < 3; i++)
+    rgb[i] = Math.round(20.5 * level + rgb[i] * (1 - level))
+  return rgbToHex(rgb[0], rgb[1], rgb[2])
+}
+
+/**
+ * 变浅颜色值
+ * @param color 颜色值字符串
+ * @param level 加深的程度,限0-1之间
+ * @returns 返回处理后的颜色值
+ */
+export function getLightColor(color, level) {
+  const reg = /^\#?[0-9A-Fa-f]{6}$/
+  if (!reg.test(color)) return ElMessage.warning('输入错误的hex颜色值')
+  const rgb = hexToRgb(color)
+
+  for (let i = 0; i < 3; i++) {
+    rgb[i] = Math.round(255 * level + rgb[i] * (1 - level))
+  }
+
+  console.log(rgb)
+
+  return rgbToHex(rgb[0], rgb[1], rgb[2])
+}

+ 5 - 0
src/utils/mittBus.js

@@ -0,0 +1,5 @@
+import mitt from 'mitt'
+
+const mittBus = mitt()
+
+export default mittBus

+ 60 - 0
src/utils/request.js

@@ -0,0 +1,60 @@
+import axios from 'axios'
+import { getConfig } from '@/config/config'
+import { getToken } from '@/utils/auth'
+import CryptoJS from "crypto-js"
+import { useUserStore } from '@/store/user.js'
+// 创建axios实例
+const request = axios.create({
+  baseURL: getConfig('baseUrl'),
+  timeout: 60000
+})
+
+request.interceptors.request.use(
+  config => {
+    if (getToken()) {
+      config.headers['qdport-Auth'] = 'bearer ' + getToken()
+    }
+    config.headers['Authorization'] = `Basic ${CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(`${getConfig('clientId')}:${getConfig('clientSecret')}`))}`
+    return config
+  }, error => {
+    console.log(error)
+    return Promise.reject(error)
+  }
+)
+// 响应拦截器
+request.interceptors.response.use(
+  response => {
+    const code = response.data.code || 200
+    if (code == 200) {
+      return response.data
+    } else {
+      ElMessage({ message: response.data.msg || response.data.error_description || "异常,请联系管理员", type: 'error', duration: 5 * 1000 })
+      return Promise.reject('error')
+    }
+
+  }, error => {
+    let { message, response } = error
+
+    if (error.response.data.code == 401|| error.response.status==401) {
+      ElMessageBox.confirm('登录状态已过期,请重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => {
+        useUserStore().logOut().then(() => {
+          location.href = '/index'
+        })
+      })
+    } else {
+      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) + "异常" + response.data.msg
+      }
+      if (process.env.NODE_ENV != 'production') {
+        message = message + "==>" + response.config.url
+      }
+      ElMessage({ message: message || "异常,请联系管理员", type: 'error', duration: 5 * 1000 })
+    }
+    return Promise.reject(error)
+  }
+)
+export default request

+ 16 - 0
src/utils/tjmTools.js

@@ -0,0 +1,16 @@
+export  function findObjectByPath(arr, targetPath) {
+  targetPath = decodeURIComponent(targetPath)
+  for (let i = 0; i < arr.length; i++) {
+    const obj = arr[i];
+    if (obj.path.startsWith('/tjmchild'+targetPath)) {
+      return obj;
+    }
+    if (obj.children && obj.children.length > 0) {
+      const result = findObjectByPath(obj.children, targetPath);
+      if (result) {
+        return result;
+      }
+    }
+  }
+  return null;
+}

+ 13 - 0
src/views/404.vue

@@ -0,0 +1,13 @@
+<script setup>
+
+</script>
+
+<template>
+    <div>
+        404 not found
+    </div>
+</template>
+
+<style lang='scss' scoped>
+
+</style>

+ 0 - 0
src/views/error/noPermission.vue


部分文件因为文件数量过多而无法显示