zs 4 月之前
當前提交
75c90ef1bc
共有 100 個文件被更改,包括 19567 次插入0 次删除
  1. 26 0
      .env.development
  2. 26 0
      .env.production
  3. 288 0
      .eslintrc-auto-import.json
  4. 28 0
      .eslintrc.cjs
  5. 31 0
      .gitignore
  6. 8 0
      .prettierrc.json
  7. 7 0
      .vscode/extensions.json
  8. 35 0
      README.md
  9. 13 0
      index.html
  10. 10 0
      jsconfig.json
  11. 13129 0
      package-lock.json
  12. 63 0
      package.json
  13. 二進制
      public/favicon.ico
  14. 二進制
      public/images/code.jpg
  15. 二進制
      public/images/logo-jilinbai.png
  16. 二進制
      public/images/logo.png
  17. 二進制
      public/images/logohome-bg.png
  18. 68 0
      public/images/site.js
  19. 72 0
      src/App.vue
  20. 1 0
      src/assets/logo.svg
  21. 63 0
      src/components/WangEditor/index.vue
  22. 154 0
      src/components/custom/custom-form.vue
  23. 328 0
      src/components/custom/custom-layout.vue
  24. 296 0
      src/components/custom/custom-search.vue
  25. 84 0
      src/components/custom/custom-upload.vue
  26. 13 0
      src/components/index.js
  27. 68 0
      src/layout/site.js
  28. 40 0
      src/main.js
  29. 76 0
      src/router/index.js
  30. 20 0
      src/router/modules/center.js
  31. 22 0
      src/router/register.js
  32. 16 0
      src/settings.js
  33. 43 0
      src/store/api/exportConfig.js
  34. 58 0
      src/store/api/login.js
  35. 54 0
      src/store/api/platform/achievement.js
  36. 55 0
      src/store/api/platform/chat.js
  37. 54 0
      src/store/api/platform/collection.js
  38. 54 0
      src/store/api/platform/demand.js
  39. 40 0
      src/store/api/platform/design.js
  40. 40 0
      src/store/api/platform/directory.js
  41. 54 0
      src/store/api/platform/footplate.js
  42. 49 0
      src/store/api/platform/friend.js
  43. 40 0
      src/store/api/platform/journal.js
  44. 45 0
      src/store/api/platform/match.js
  45. 99 0
      src/store/api/platform/matchExt.js
  46. 40 0
      src/store/api/platform/matchReg.js
  47. 45 0
      src/store/api/platform/news.js
  48. 40 0
      src/store/api/platform/notes.js
  49. 40 0
      src/store/api/platform/process.js
  50. 54 0
      src/store/api/platform/project.js
  51. 54 0
      src/store/api/platform/score.js
  52. 40 0
      src/store/api/platform/sector.js
  53. 49 0
      src/store/api/platform/sign.js
  54. 54 0
      src/store/api/platform/supply.js
  55. 54 0
      src/store/api/platform/support.js
  56. 37 0
      src/store/api/platform/tool.js
  57. 40 0
      src/store/api/system/dictData.js
  58. 40 0
      src/store/api/system/dictType.js
  59. 40 0
      src/store/api/system/menus.js
  60. 50 0
      src/store/api/system/message.js
  61. 54 0
      src/store/api/system/region.js
  62. 45 0
      src/store/api/system/role.js
  63. 40 0
      src/store/api/system/tags.js
  64. 40 0
      src/store/api/user/admin.js
  65. 40 0
      src/store/api/user/applyCompany.js
  66. 40 0
      src/store/api/user/association.js
  67. 49 0
      src/store/api/user/cirelation.js
  68. 63 0
      src/store/api/user/company.js
  69. 40 0
      src/store/api/user/companyYear.js
  70. 40 0
      src/store/api/user/competition.js
  71. 33 0
      src/store/api/user/contactApply.js
  72. 54 0
      src/store/api/user/expert.js
  73. 74 0
      src/store/api/user/incubator.js
  74. 40 0
      src/store/api/user/incubatorYear.js
  75. 40 0
      src/store/api/user/investment.js
  76. 40 0
      src/store/api/user/school.js
  77. 40 0
      src/store/api/user/state.js
  78. 40 0
      src/store/api/user/unit.js
  79. 45 0
      src/store/api/user/user.js
  80. 38 0
      src/store/api/util.js
  81. 12 0
      src/store/index.js
  82. 94 0
      src/store/modules/app.js
  83. 206 0
      src/store/modules/tagsView.js
  84. 24 0
      src/store/user.js
  85. 158 0
      src/styles/variables.scss
  86. 27 0
      src/utils/animation.js
  87. 157 0
      src/utils/axios-wrapper.js
  88. 45 0
      src/utils/base-methods.js
  89. 23 0
      src/utils/checkResult.js
  90. 61 0
      src/utils/error-code.js
  91. 26 0
      src/utils/file.js
  92. 16 0
      src/utils/rem.js
  93. 35 0
      src/utils/util-methods.js
  94. 11 0
      src/utils/variable.js
  95. 135 0
      src/utils/websocket.js
  96. 274 0
      src/views/home/index.vue
  97. 284 0
      src/views/match/one.vue
  98. 354 0
      src/views/match/thr.vue
  99. 383 0
      src/views/match/two.vue
  100. 0 0
      vite.config.js

+ 26 - 0
.env.development

@@ -0,0 +1,26 @@
+## 开发环境
+NODE_ENV='development'
+
+# 应用端口
+VITE_APP_PORT = 3004
+
+# 代理前缀
+VITE_APP_BASE_API = '/cxyy/api'
+
+VITE_APP_ES_API = '/cxyy/es'
+
+VITE_APP_BASE_APIWS ='/websocket/api'
+
+VITE_APP_HOME = "http://localhost:3000/"
+
+VITE_APP_HOST = ""
+
+VITE_BASE_URL = "/cxyyMatch"
+
+VITE_OUT_DIR = "cxyyMatch"
+
+# ws://localhost:15674/ws
+VITE_MQ_URL = 'ws://localhost:15674/ws'
+VITE_MQ_HOST = 'hxmsg'
+VITE_MQ_LOGIN = 'huaxin'
+VITE_MQ_PASSCODE = '1234qwerasdf'

+ 26 - 0
.env.production

@@ -0,0 +1,26 @@
+## 生产环境
+NODE_ENV='production'
+
+# 应用端口
+VITE_APP_PORT = 3004
+
+# 代理前缀
+VITE_APP_BASE_API = '/cxyy/api'
+
+VITE_APP_ES_API = '/cxyy/es'
+
+VITE_APP_BASE_APIWS ='/websocket/api'
+
+VITE_APP_HOST = ""
+
+VITE_APP_HOME = "http://localhost:3000/"
+
+
+VITE_BASE_URL = "/cxyyMatch"
+
+VITE_OUT_DIR = "cxyyMatch"
+
+VITE_MQ_URL = '/ws'
+VITE_MQ_HOST = 'hxmsg'
+VITE_MQ_LOGIN = 'huaxin'
+VITE_MQ_PASSCODE = '1234qwerasdf'

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

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

+ 28 - 0
.eslintrc.cjs

@@ -0,0 +1,28 @@
+/* eslint-env node */
+require('@rushstack/eslint-patch/modern-module-resolution')
+
+module.exports = {
+  root: true,
+  extends: ['plugin:vue/vue3-essential', 'eslint:recommended', '@vue/eslint-config-prettier/skip-formatting', './.eslintrc-auto-import.json'],
+  parserOptions: {
+    ecmaVersion: 'latest'
+  },
+  rules: {
+    'vue/multi-word-component-names': 0,
+    'max-len': [
+      'warn',
+      {
+        code: 200
+      }
+    ],
+    'prettier/prettier': [
+      'warn',
+      {
+        singleQuote: true,
+        bracketSpacing: true,
+        jsxBracketSameLine: true,
+        printWidth: 200
+      }
+    ]
+  }
+}

+ 31 - 0
.gitignore

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

+ 8 - 0
.prettierrc.json

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

+ 7 - 0
.vscode/extensions.json

@@ -0,0 +1,7 @@
+{
+  "recommendations": [
+    "Vue.volar",
+    "dbaeumer.vscode-eslint",
+    "esbenp.prettier-vscode"
+  ]
+}

+ 35 - 0
README.md

@@ -0,0 +1,35 @@
+# web
+
+This template should help get you started developing with Vue 3 in Vite.
+
+## Recommended IDE Setup
+
+[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
+
+## Customize configuration
+
+See [Vite Configuration Reference](https://vitejs.dev/config/).
+
+## Project Setup
+
+```sh
+npm install
+```
+
+### Compile and Hot-Reload for Development
+
+```sh
+npm run dev
+```
+
+### Compile and Minify for Production
+
+```sh
+npm run build
+```
+
+### Lint with [ESLint](https://eslint.org/)
+
+```sh
+npm run lint
+```

+ 13 - 0
index.html

@@ -0,0 +1,13 @@
+<!doctype html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" href="/favicon.ico" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>产学研用协同创新数字化平台</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.js"></script>
+  </body>
+</html>

+ 10 - 0
jsconfig.json

@@ -0,0 +1,10 @@
+{
+  "compilerOptions": {
+    "baseUrl": "./",
+    "paths": {
+      "@/*": ["./src/*"]
+    }
+  },
+  "exclude": ["node_modules", "dist"],
+  "include": ["src/**/*.vue", "**/*.js", "src/views/brain/five/json/home.json"]
+}

文件差異過大導致無法顯示
+ 13129 - 0
package-lock.json


+ 63 - 0
package.json

@@ -0,0 +1,63 @@
+{
+  "name": "web",
+  "version": "0.0.0",
+  "private": true,
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build",
+    "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.3.1",
+    "@kjgl77/datav-vue3": "^1.7.3",
+    "@stomp/stompjs": "^7.0.0",
+    "@vueuse/core": "^10.7.2",
+    "@vueuse/integrations": "^10.9.0",
+    "@wangeditor/editor": "^5.1.23",
+    "@wangeditor/editor-for-vue": "5.1.10",
+    "animate.css": "^4.1.1",
+    "ant-design-vue": "^4.0.8",
+    "aos": "^2.3.4",
+    "axios": "^1.6.7",
+    "dayjs": "^1.11.10",
+    "default-passive-events": "^2.0.0",
+    "echarts": "^5.5.0",
+    "echarts-gl": "^2.0.9",
+    "element-plus": "^2.5.6",
+    "lodash-es": "^4.17.21",
+    "moment": "^2.30.1",
+    "nprogress": "^0.2.0",
+    "path-browserify": "^1.0.1",
+    "path-to-regexp": "^6.2.1",
+    "pinia": "^2.1.7",
+    "postcss-px2rem": "^0.3.0",
+    "qrcode.vue": "^3.4.1",
+    "relation-graph-vue3": "^2.2.1",
+    "stompjs": "^2.3.3",
+    "swiper": "^11.1.12",
+    "universal-cookie": "^7.1.0",
+    "vue": "^3.4.15",
+    "vue-i18n": "^9.9.1",
+    "vue-router": "^4.2.5",
+    "vue3-seamless-scroll": "^2.0.1",
+    "vue3-tree-org": "^4.2.2"
+  },
+  "devDependencies": {
+    "@rushstack/eslint-patch": "^1.3.3",
+    "@vitejs/plugin-vue": "^5.0.3",
+    "@vue/eslint-config-prettier": "^8.0.0",
+    "eslint": "^8.49.0",
+    "eslint-plugin-vue": "^9.17.0",
+    "prettier": "^3.0.3",
+    "sass": "^1.71.0",
+    "unplugin-auto-import": "^0.17.5",
+    "unplugin-icons": "^0.18.5",
+    "unplugin-vue-components": "^0.26.0",
+    "vite": "^5.0.11",
+    "vite-plugin-inspect": "^0.8.3",
+    "vite-plugin-svg-icons": "^2.0.1"
+  }
+}

二進制
public/favicon.ico


二進制
public/images/code.jpg


二進制
public/images/logo-jilinbai.png


二進制
public/images/logo.png


二進制
public/images/logohome-bg.png


+ 68 - 0
public/images/site.js

@@ -0,0 +1,68 @@
+// 图片引入
+import logo from '/images/logo.png'
+import Logo from '/images/logo-jilinbai.png'
+import Code from '/images/code.jpg'
+// 网站基本设置
+export const siteInfo = {
+  display: false,
+  zhTitle: '产学研用协同创新数字化平台',
+  zhEnglish: 'Collaborative Innovation Digital Platform for Industry University Research Application',
+  logoUrl: logo
+}
+// 网站底部信息
+export const footInfo = {
+  Phone: '0431-81509921',
+  Email: 'gyyzhglb@jlitri.com',
+  Address: '吉林省长春市汽车经济技术开发区兴顺路1366号',
+  Copyright: 'Copyright ©2022  吉林省工业技术研究院集团有限公司  All Rights Reserved ',
+  Company: '吉林省工业技术研究院集团有限公司',
+  Logo,
+  Code
+}
+// 目录设置
+export const menuList = [
+  { key: '1', title: '首页', route: 'one', English: 'Home', label: '首页' },
+  { key: '2', title: '信息发布', route: 'two', English: 'Information Delivery', label: '信息发布' },
+  {
+    key: '3',
+    title: '创新中心',
+    English: 'News Information',
+    label: '创新中心',
+    children: [
+      { key: '3-1', title: '需求中心', route: 'thrOne', label: '需求中心' },
+      { key: '3-2', title: '供给中心', route: 'thrTwo', label: '供方中心' }
+    ]
+  },
+  { key: '4', title: '精准匹配', route: 'twelve', English: 'Accurate Matching', label: '精准匹配' },
+  { key: '5', title: '信息检索', route: 'four', English: 'Expert Tank', label: '信息检索' },
+  { key: '6', title: '双创活动', route: 'five', English: 'Authorized Operator', label: '双创活动' },
+  { key: '7', title: '中试平台', route: 'six', English: 'Project Selection', label: '中试平台' },
+  { key: '8', title: '服务支撑', route: 'seven', English: 'Innovation Competition', label: '服务支撑' },
+  { key: '9', title: '产业集群', route: 'eight', English: 'Innovation Competition', label: '产业集群' },
+  { key: '11', title: '孵化基地', route: 'eleven', English: 'Achievement Display', label: '孵化基地' },
+  {
+    key: '12',
+    title: '产业孵化大脑',
+    route: 'brain',
+    English: 'Achievement Display',
+    label: '产业孵化大脑'
+  },
+  { key: '13', title: '行研产研', route: 'thirteen', English: 'Research Development', label: '行研产研' }
+]
+// 目录设置
+export const menuList3 = [
+  { id: 1, name: '基本信息', route_name: 'center', path: '/center', icon: 'User' },
+  { id: 2, name: '认证入驻', route_name: 'attestation', path: '/attestation', icon: 'Finished' },
+  { id: 3, name: '通知管理', route_name: 'notice', path: '/notice', icon: 'Notebook' },
+  { id: 4, name: '成果管理', route_name: 'achievement', path: '/achievement', icon: 'Medal' },
+  { id: 5, name: '需求管理', route_name: 'demand', path: '/demand', icon: 'DataBoard' },
+  { id: 6, name: '我的收藏', route_name: 'collect', path: '/collect', icon: 'Collection' },
+  { id: 7, name: '活动管理', route_name: 'sign', path: '/sign', icon: 'Suitcase' },
+  { id: 8, name: '行业动态', route_name: 'news2', path: '/news2', icon: 'Message' },
+  { id: 9, name: '供给管理', route_name: 'supply', path: '/supply', icon: 'Notification' },
+  { id: 10, name: '中试管理', route_name: 'footplate', path: '/footplate', icon: 'TakeawayBox' },
+  { id: 11, name: '赛事管理', route_name: 'match', path: '/match', icon: 'CollectionTag' },
+  // { id: 12, name: '行研产研', route_name: 'journal', path: '/journal', icon: 'Reading' },
+  { id: 13, name: '项目管理', route_name: 'project', path: '/project', icon: 'Trophy' },
+  { id: 14, name: '修改密码', route_name: 'password', path: '/password', icon: 'Lock' }
+]

+ 72 - 0
src/App.vue

@@ -0,0 +1,72 @@
+<script setup>
+import zhCN from 'ant-design-vue/es/locale/zh_CN'
+</script>
+
+<template>
+  <a-config-provider :locale="zhCN">
+    <router-view v-slot="{ Component }">
+      <keep-alive>
+        <component :is="Component" :key="$route.name" v-if="$route.meta.keepAlive" />
+      </keep-alive>
+      <component :is="Component" :key="$route.name" v-if="!$route.meta.keepAlive" />
+    </router-view>
+  </a-config-provider>
+</template>
+
+<style lang="scss">
+body {
+  margin: 0;
+  font-family:
+    Avenir,
+    Tahoma,
+    Arial,
+    PingFang SC,
+    Lantinghei SC,
+    Microsoft Yahei,
+    Hiragino Sans GB,
+    'Microsoft Sans Serif',
+    WenQuanYi Micro Hei,
+    Helvetica,
+    sans-serif;
+}
+
+.w_1300 {
+  width: 1300px;
+  margin: 0 auto;
+}
+.w_1700 {
+  max-width: 1700px;
+  margin: 0 auto;
+}
+.textOne {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+.textMore {
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+/* 自定义整个滚动条 */
+::-webkit-scrollbar {
+  width: 5px; /* 滚动条宽度 */
+}
+
+/* 自定义滚动条轨道 */
+::-webkit-scrollbar-track {
+  background: #f1f1f1; /* 轨道颜色 */
+}
+
+/* 自定义滚动条的滑块(thumb) */
+::-webkit-scrollbar-thumb {
+  background: #1c66e7; /* 滑块颜色 */
+}
+/* 当hover或active状态时,自定义滑块的颜色 */
+::-webkit-scrollbar-thumb:hover {
+  background: #4826da;
+}
+</style>

+ 1 - 0
src/assets/logo.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>

+ 63 - 0
src/components/WangEditor/index.vue

@@ -0,0 +1,63 @@
+<template>
+  <div class="editor-wrapper" style="width: 100%; border: 1px solid #dcdfe6; margin: 0 0 10px 0">
+    <!-- 工具栏 -->
+    <Toolbar id="toolbar-container" :editor="editorRef" :default-config="toolbarConfig" :mode="mode" />
+    <!-- 编辑器 -->
+    <Editor style="height: 350px" id="editor-container" v-model="modelValue" :default-config="editorConfig" :mode="mode" @on-change="handleChange" @on-created="handleCreated" />
+  </div>
+</template>
+
+<script setup>
+import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
+
+// API 引用
+import { uploadFileApi } from '@/utils/file'
+
+const props = defineProps({
+  modelValue: {
+    type: [String],
+    default: ''
+  }
+})
+
+const emit = defineEmits(['update:modelValue'])
+
+const modelValue = useVModel(props, 'modelValue', emit)
+
+const editorRef = shallowRef() // 编辑器实例,必须用 shallowRef
+const mode = ref('default') // 编辑器模式
+const toolbarConfig = ref({}) // 工具条配置
+// 编辑器配置
+const editorConfig = ref({
+  placeholder: '请输入内容...',
+  MENU_CONF: {
+    uploadImage: {
+      // 自定义图片上传
+      async customUpload(file, insertFn) {
+        uploadFileApi(file).then((response) => {
+          const { errcode, uri } = response.data
+          const url = `${import.meta.env.VITE_APP_HOST}${uri}`
+          if (errcode === 0) insertFn(url)
+        })
+      }
+    }
+  }
+})
+
+const handleCreated = (editor) => {
+  editorRef.value = editor // 记录 editor 实例,重要!
+}
+
+function handleChange(editor) {
+  modelValue.value = editor.getHtml()
+}
+
+// 组件销毁时,也及时销毁编辑器
+onBeforeUnmount(() => {
+  const editor = editorRef.value
+  if (editor == null) return
+  editor.destroy()
+})
+</script>
+
+<style src="@wangeditor/editor/dist/css/style.css"></style>

+ 154 - 0
src/components/custom/custom-form.vue

@@ -0,0 +1,154 @@
+<template>
+  <div id="custom-form">
+    <el-form ref="formRef" :model="form" :rules="rules" :label-width="labelWidth" class="form" @submit.prevent :disabled="disabled">
+      <el-col :span="span" v-for="(item, index) in fields" :key="index">
+        <el-form-item v-if="display(item)" :key="`form-field-${item.model}`" :label="getField('label', item)" :prop="item.model" :required="item.required">
+          <template v-if="item.custom">
+            <slot :name="item.model" v-bind="{ item }"></slot>
+          </template>
+          <template v-else>
+            <template v-if="item.type === 'textarea'">
+              <el-input clearable v-model="form[item.model]" :type="item.type" :placeholder="getField('placeholder', item)" v-bind="item.options" @change="dataChange(item.model)" show-word-limit></el-input>
+            </template>
+            <template v-else-if="item.type === 'numbers'">
+              <el-input-number v-model="form[item.model]" :placeholder="getField('placeholder', item)" @change="dataChange(item.model)" style="width: 100%" />
+            </template>
+            <template v-else-if="item.type === 'radio'">
+              <el-radio-group v-model="form[item.model]" :type="item.type" v-bind="item.options" @change="dataChange(item.model)">
+                <slot :name="item.model" v-bind="{ item }"></slot>
+              </el-radio-group>
+            </template>
+            <template v-else-if="item.type === 'checkbox'">
+              <el-checkbox-group v-model="form[item.model]" :type="item.type" v-bind="item.options">
+                <slot :name="item.model" v-bind="{ item }"></slot>
+              </el-checkbox-group>
+            </template>
+            <template v-else-if="item.type === 'select'">
+              <el-tooltip effect="dark" content="可输入文本搜索选项" placement="top-start">
+                <el-select clearable filterable allow-create default-first-option v-model="form[item.model]" :type="item.type" :placeholder="getField('selectplaceholder', item)" v-bind="item.options" @change="dataChange(item.model)" style="width: 100%">
+                  <slot :name="item.model" v-bind="{ item }"></slot>
+                </el-select>
+              </el-tooltip>
+            </template>
+            <template v-else-if="item.type === 'selectMany'">
+              <el-tooltip effect="dark" content="可输入文本搜索选项" placement="top-start">
+                <el-select filterable clearable multiple v-model="form[item.model]" :type="item.type" :placeholder="getField('selectplaceholder', item)" v-bind="item.options" @change="dataChange(item.model)" style="width: 100%">
+                  <slot :name="item.model" v-bind="{ item }"></slot>
+                </el-select>
+              </el-tooltip>
+            </template>
+            <template v-else-if="item.type === `year` || item.type == 'month' || item.type == 'date' || item.type == 'daterange' || item.type == 'datetime' || item.type == 'datetimerange'">
+              <el-date-picker v-model="form[item.model]" :type="item.type" :placeholder="getField('selectplaceholder', item)" :format="getDateFormat(item.type)" :value-format="getDateFormat(item.type)" v-bind="item.options" @change="dataChange(item.model)" range-separator="至" style="width: 100%"> </el-date-picker>
+            </template>
+            <template v-else-if="item.type === `time`">
+              <el-time-picker v-model="form[item.model]" :placeholder="getField('selectplaceholder', item)" :format="getDateFormat(item.type)" :value-format="getDateFormat(item.type)" v-bind="item.options" @change="dataChange(item.model)" style="width: 100%"> </el-time-picker>
+            </template>
+            <template v-else-if="item.type === `inputnumber`">
+              <el-input-number v-model="form[item.model]" :placeholder="getField('placeholder', item)" v-bind="item.options" @change="dataChange(item.model)" style="width: 100%"></el-input-number>
+            </template>
+            <template v-else>
+              <el-input clearable v-model="form[item.model]" :type="getField('type', item)" :placeholder="getField('placeholder', item)" :show-password="getField('type', item) === 'password'" v-bind="item.options" @change="dataChange(item.model)"></el-input>
+            </template>
+          </template>
+        </el-form-item>
+      </el-col>
+      <el-col :span="24" label="" class="btn" v-if="useSave">
+        <slot name="submit">
+          <el-button type="warning" v-if="DraftSave" @click="draftSave(formRef)">{{ submitDraft || '保存草稿' }}</el-button>
+          <el-button type="primary" @click="save(formRef)">{{ submitText || '保存并提交审核' }}</el-button>
+        </slot>
+      </el-col>
+    </el-form>
+  </div>
+</template>
+
+<script setup>
+import { get, isFunction } from 'lodash-es'
+const props = defineProps({
+  modelValue: { type: Object },
+  rules: { type: Array, default: () => {} },
+  labelWidth: { type: String, default: 'auto' },
+  disabled: { type: Boolean, default: false },
+  fields: { type: Array, default: () => [] },
+  submitText: { type: String },
+  submitDraft: { type: String },
+  DraftSave: { type: Boolean, default: true },
+  useSave: { type: Boolean, default: true },
+  span: { type: Number, default: 24 } // 限制两侧的距离,24就是整行全用
+})
+const emits = defineEmits(['update:modelValue', 'dataChange', 'save', 'draftSave'])
+const formRef = ref()
+const form = computed({
+  get() {
+    return props.modelValue
+  },
+  set(value) {
+    console.log(value)
+    emits('update:modelValue', value)
+  }
+})
+const save = async (formEl) => {
+  if (!formEl) return
+  await formEl.validate((valid, fields) => {
+    if (valid) {
+      ElMessageBox.confirm(`您确认保存并提交审核该数据?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
+        .then(async () => {
+          emits('save', form.value)
+        })
+        .catch(() => {})
+    } else {
+      console.log('error submit!', fields)
+    }
+  })
+}
+const draftSave = async (formEl) => {
+  if (!formEl) return
+  await formEl.validate((valid, fields) => {
+    if (valid) {
+      emits('draftSave', form.value)
+    } else {
+      console.log('error submit!', fields)
+    }
+  })
+}
+const getField = (item, data) => {
+  let res = get(data, item, null)
+  if (item === 'type') res = res === null ? `text` : res
+  if (item === 'placeholder') res = res === null ? `请输入${data.label}` : res
+  if (item === `selectplaceholder`) res = res === null ? `请选择${data.label}` : res
+  if (item === 'required') res = res === null ? false : res
+  if (item === `error`) res = res === null ? `${data.label}错误` : res
+  return res
+}
+const dataChange = (model) => {
+  const value = form.value[model]
+  emits('dataChange', { model, value })
+}
+const display = (field) => {
+  let dis = get(field, `display`)
+  if (!isFunction(dis)) return true
+  else {
+    return dis(field, form)
+  }
+}
+const getDateFormat = (e) => {
+  if (e === 'year') return 'YYYY'
+  if (e === 'month') return 'MM'
+  if (e === 'date') return 'YYYY-MM-DD'
+  if (e === 'daterange') return 'YYYY-MM-DD'
+  if (e === 'datetime') return 'YYYY-MM-DD HH:mm:ss'
+  if (e === 'datetimerange') return 'YYYY-MM-DD HH:mm:ss'
+  if (e === 'time') return 'HH:mm:ss'
+}
+</script>
+<style scoped>
+.btn {
+  text-align: center;
+}
+
+.form {
+  display: flex;
+  flex-direction: row;
+  flex-wrap: wrap;
+}
+</style>

+ 328 - 0
src/components/custom/custom-layout.vue

@@ -0,0 +1,328 @@
+<template>
+  <div id="c-layout">
+    <div class="header">
+      <div class="header_1 w_1700">
+        <div class="left">
+          <el-image class="image" :src="logo" fit="fill" />
+        </div>
+        <div class="right">
+          <div class="right_1" v-if="user && user.id">
+            {{ user.nick_name || '暂无昵称' }}
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="main">
+      <slot></slot>
+    </div>
+    <div class="footer" v-if="is_foot">
+      <div class="w_1700">
+        <div class="foot_1">
+          <div class="foot_left">
+            <el-image class="image" v-if="footInfos && footInfos.Logo && footInfos.Logo.length > 0" :src="getUrl(footInfos.Logo)" fit="fill" />
+            <el-image class="image" v-else :src="footInfo.Logo" fit="fill" />
+            <div class="foot_content">
+              <div class="content_left">
+                <el-image class="image" v-if="footInfos && footInfos.Code && footInfos.Code.length > 0" :src="getUrl(footInfos.Code)" fit="fill" />
+                <el-image class="image" v-else :src="footInfo.Code" fit="fill" />
+              </div>
+              <div class="content_right">
+                <div class="title">电话:{{ footInfos.Phone || footInfo.Phone }}</div>
+                <div class="title">邮箱:{{ footInfos.Email || footInfo.Email }}</div>
+                <div class="title">地址:{{ footInfos.Address || footInfo.Address }}</div>
+              </div>
+            </div>
+          </div>
+          <div class="foot_right">
+            <div class="right_1">友情连接:</div>
+            <div class="right_2">
+              <div class="list" v-for="(item, index) in typeList" :key="index">
+                <div class="list_1" v-if="item.list && item.list.length > 0">
+                  <div class="title">{{ item.title }}</div>
+                </div>
+                <div class="list_2">
+                  <div v-for="(tag, indexx) in item.list" :key="indexx" @click="toLink(tag)">{{ tag.name }}</div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+        <div class="foot_2">
+          {{ footInfo.Copyright }}
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+// 图片引入
+import logo from '/images/logohome-bg.png'
+
+import { cloneDeep, get } from 'lodash-es'
+import { footInfo, menuList } from '@/layout/site'
+// 接口
+import { DesignStore } from '@/store/api/platform/design'
+const designStore = DesignStore()
+import { UserStore } from '@/store/user'
+const userStore = UserStore()
+const user = computed(() => userStore.user)
+
+const route = useRoute()
+const $checkRes = inject('$checkRes')
+const props = defineProps({
+  is_menu: { type: Boolean, default: () => true },
+  is_foot: { type: Boolean, default: () => true },
+  is_carousel: { type: Boolean, default: () => false },
+  carouselList: { type: Array, default: () => [] }
+})
+const configInfo = ref({})
+const footInfos = ref({})
+const data = ref([])
+const isIncubator = ref(false)
+const hasbrain = ref(false)
+
+// 分类
+const typeList = ref([
+  { title: '热门高校', list: [] },
+  { title: '政府部门', list: [] },
+  { title: '科研机构', list: [] }
+])
+// 请求
+onMounted(async () => {
+  search()
+})
+
+const search = async () => {
+  for (const val of menuList) {
+    if (route.name === val.route) val.hover = true
+    else val.hover = false
+  }
+  let menus = cloneDeep(menuList)
+  // 判断, 如果没有孵化基地角色, 则不显示孵化基地菜单
+  if (user.value) {
+    const hasIncubator = get(user.value, 'role', []).find((f) => f === 'Incubator')
+    if (hasIncubator) isIncubator.value = true
+  }
+  if (!isIncubator.value) menus = menus.filter((f) => f.key !== '11')
+
+  // 特定用户查看产业大脑
+  if (user.value && user.value.id == 17) hasbrain.value = true
+  else hasbrain.value = false
+  if (!hasbrain.value) menus = menus.filter((f) => f.key !== '12')
+
+  data.value = menus
+
+  // 基础设置
+  const result = await designStore.query({})
+  if ($checkRes(result)) {
+    configInfo.value = result.data[0] || {}
+    footInfo.value = result.data[0].footInfo || {}
+    const friendship = result.data[0].friendship || []
+    for (const val of friendship) {
+      for (const tag of typeList.value) {
+        if (val.type == tag.title) {
+          tag.list.push(val)
+        }
+      }
+    }
+  }
+}
+const toLink = (item) => {
+  window.open(item.href, '_blank') // 在新标签页中打开URL
+}
+const getUrl = (item) => {
+  if (item && item.length > 0) return `${import.meta.env.VITE_APP_HOST}${item[0].uri}`
+}
+</script>
+
+<style lang="scss" scoped>
+#c-layout {
+  .header {
+    position: relative;
+    background: $global-color-fff;
+    .header_1 {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      padding: 10px 0;
+      .right {
+        display: flex;
+        font-size: $global-font-size-18;
+        cursor: pointer; /* 改变鼠标样式为手形 */
+        .right_1 {
+          display: flex;
+          align-items: center;
+          color: $global-color-107;
+          margin: 0 10px 0 0;
+          span {
+            margin: 0 5px;
+          }
+          .el-dropdown-link {
+            font-size: $global-font-size-18;
+            cursor: pointer;
+            color: #409eff;
+            display: flex;
+            align-items: center;
+          }
+        }
+        .right_2 {
+          display: flex;
+          align-items: center;
+          margin: 0 5px;
+          span {
+            margin: 0 0 0 5px;
+          }
+        }
+      }
+    }
+  }
+  .main {
+    min-height: 57vh;
+  }
+  .footer {
+    font-family: PingFangSC-Regular;
+    padding: 25px 0;
+    background: linear-gradient(to bottom, #009fff, #0077ff);
+    box-shadow: 0 6px 10px 0 rgba(0, 0, 0, 0.2);
+    color: $global-color-fff;
+    font-size: $global-font-size-18;
+    .foot_1 {
+      display: flex;
+      .foot_left {
+        width: 50%;
+        .foot_content {
+          margin: 40px 0 10px 0;
+          display: flex;
+          align-items: center;
+          .content_right {
+            margin: 0 0 0 30px;
+            .title {
+              cursor: default;
+              margin: 8px 0 0 0;
+            }
+            .title:first-child {
+              margin: 0;
+            }
+          }
+        }
+      }
+      .foot_right {
+        width: 50%;
+        .right_1 {
+          font-size: $global-font-size-21;
+          margin: 0 0 40px 0;
+        }
+        .right_2 {
+          display: flex;
+          justify-content: space-between;
+          .list_1 {
+            .title {
+              font-size: $global-font-size-21;
+              background: #fff;
+              width: 113px;
+              height: 33px;
+              line-height: 33px;
+              border-radius: 16px;
+              margin: 0 0 10px 0;
+              color: #0078ff;
+              text-align: center;
+              font-family: PingFangSC-Semibold;
+            }
+          }
+          .list_2 {
+            font-size: $global-font-size-18;
+            margin: 15px 0 0 15px;
+            cursor: default;
+          }
+        }
+      }
+    }
+    .foot_2 {
+      text-align: center;
+      padding: 10px 0 0 0;
+    }
+
+    .foot {
+      margin: 0 20px;
+      .foot_2 {
+        color: $global-color-fff;
+        font-size: $global-font-size-16;
+        .footer_left {
+          padding: 10px 0;
+          display: flex;
+          justify-content: space-between;
+          align-items: center;
+          // border-bottom: 1px solid rgba(229, 241, 255, 0.7);
+          .left {
+            .image {
+              width: 334px;
+              height: auto;
+              margin: 0 0 10px 0;
+            }
+            .title {
+              margin: 5px 0 0 0;
+            }
+          }
+          .center {
+            color: $global-color-fff;
+            font-size: $global-font-size-16;
+            display: flex;
+            margin: 10px 0;
+            padding: 0 0 10px 0;
+
+            span {
+              width: 80px;
+            }
+            .list {
+              width: 250px;
+              text-align: center;
+              .list_1 {
+                margin: 0 0 10px 0;
+              }
+              .list_2 {
+                display: flex;
+                flex-wrap: wrap;
+                justify-content: center;
+                cursor: default;
+                div {
+                  margin: 0 5px 5px 0;
+                  cursor: default;
+                }
+              }
+            }
+          }
+          .right {
+            text-align: center;
+            .image {
+              width: 120px;
+              height: 120px;
+            }
+            .title {
+              margin: 5px 0 0 0;
+              cursor: default;
+            }
+          }
+        }
+        .footer_right {
+          text-align: center;
+          padding: 10px 0 0 0;
+          border-top: 1px solid rgba(229, 241, 255, 0.7);
+        }
+      }
+    }
+  }
+}
+@media screen and (max-width: 1280px) {
+  #c-layout {
+    min-width: 1920px;
+    margin: 0 auto;
+  }
+}
+@media screen and (min-width: 1921px) {
+  #c-layout {
+    max-width: 1920px;
+    margin: 0 auto;
+  }
+}
+</style>

+ 296 - 0
src/components/custom/custom-search.vue

@@ -0,0 +1,296 @@
+<template>
+  <div class="c-search">
+    <div class="searchList" v-for="(item, index) in searchFields" :key="index">
+      <div class="twoSeacher" v-if="item.type == '1'">
+        <div class="twoLeft">
+          <span>{{ item.title }}</span>
+        </div>
+        <div v-if="!oneShow" class="twoRight">
+          <div class="label" :class="[tags.is_active ? 'show' : '']" v-for="(tags, indexs) in plateList.slice(0, 6)" :key="indexs" @click="toSelect(tags, item.type)">
+            {{ tags.title }}
+          </div>
+        </div>
+        <div v-else class="twoRight">
+          <div class="label" :class="[tags.is_active ? 'show' : '']" v-for="(tags, indexs) in plateList" :key="indexs" @click="toSelect(tags, item.type)">
+            {{ tags.title }}
+          </div>
+        </div>
+        <div class="button">
+          <span v-if="!oneShow" @click="oneShow = true">
+            <el-icon><ArrowDown /></el-icon>
+          </span>
+          <span v-else @click="oneShow = false">
+            <el-icon><ArrowUp /></el-icon>
+          </span>
+        </div>
+      </div>
+      <div class="twoSeacher" v-else-if="item.type == '2'">
+        <div class="twoLeft">
+          <span>{{ item.title }}</span>
+        </div>
+        <div v-if="!twoShow" class="twoRight">
+          <div class="label" :class="[tags.is_active ? 'show' : '']" v-for="(tags, indexs) in typeList.slice(0, 10)" :key="indexs" @click="toSelect(tags, item.type)">
+            {{ tags.label }}
+          </div>
+        </div>
+        <div v-else class="twoRight">
+          <div class="label" :class="[tags.is_active ? 'show' : '']" v-for="(tags, indexs) in typeList" :key="indexs" @click="toSelect(tags, item.type)">
+            {{ tags.label }}
+          </div>
+        </div>
+        <div class="button">
+          <span v-if="!twoShow" @click="twoShow = true">
+            <el-icon><ArrowDown /></el-icon>
+          </span>
+          <span v-else @click="twoShow = false">
+            <el-icon><ArrowUp /></el-icon>
+          </span>
+        </div>
+      </div>
+      <div class="twoSeacher" v-else-if="item.type == '3'">
+        <div class="twoLeft">
+          <span>{{ item.title }}</span>
+        </div>
+        <div class="twoRight">
+          <div class="label" :class="[tags.is_active ? 'show' : '']" v-for="(tags, indexs) in cityList" :key="indexs" @click="toSelect(tags, item.type)">
+            {{ tags.name }}
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="twoIpunt" v-if="is_search">
+      <el-input class="input" v-for="(item, index) in fields" :key="index" clearable size="large" v-model="searchForm[item.model]" :placeholder="getField('placeholder', item)" />
+      <el-button class="button" size="large" type="primary" @click="toSearchInfo">检索</el-button>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { get } from 'lodash-es'
+const props = defineProps({
+  searchFields: { type: Array, default: () => [] },
+  fields: { type: Array, default: () => [] },
+  is_search: { type: Boolean, default: () => true },
+  plateList: { type: Array, default: () => [] },
+  typeList: { type: Array, default: () => [] },
+  cityList: { type: Array, default: () => [] }
+})
+
+const { plateList } = toRefs(props)
+const { typeList } = toRefs(props)
+const { cityList } = toRefs(props)
+
+const searchForm = ref({})
+// // 是否展开
+const oneShow = ref(false)
+const twoShow = ref(false)
+
+// 查询
+const industry = ref([])
+const field = ref([])
+const city = ref([])
+
+const emits = defineEmits(['toSelect', 'toDel', 'toSearchInfo'])
+
+// 筛选条件
+const toSearchFind = async () => {
+  if (industry.value && industry.value.length > 0) {
+    searchForm.value.industry = industry.value.map((i) => {
+      return i.title
+    })
+  } else delete searchForm.value.industry
+  if (field.value && field.value.length > 0) {
+    searchForm.value.field = field.value.map((i) => {
+      return i.label
+    })
+  } else delete searchForm.value.field
+  if (city.value && city.value.length > 0) {
+    searchForm.value.area = city.value.map((i) => {
+      return i.name
+    })
+  } else delete searchForm.value.area
+
+  await toSearchInfo()
+}
+
+const toSelect = async (data, type) => {
+  if (data.is_active) {
+    toDel(data, type)
+  } else {
+    if (data.id != '-1') {
+      if (type == '1') {
+        for (const val of plateList.value) {
+          if (data.id == val.id) val.is_active = true
+          if (val.id == '-1') val.is_active = false
+        }
+        const res = industry.value.find((i) => i.id == data.id)
+        if (!res) industry.value.push(data)
+      } else if (type == '2') {
+        for (const val of typeList.value) {
+          if (data.id == val.id) val.is_active = true
+          if (val.id == '-1') val.is_active = false
+        }
+        const res = field.value.find((i) => i.id == data.id)
+        if (!res) field.value.push(data)
+      } else {
+        for (const val of cityList.value) {
+          if (data.id == val.id) val.is_active = true
+          if (val.id == '-1') val.is_active = false
+        }
+        const res = city.value.find((i) => i.id == data.id)
+        if (!res) city.value.push(data)
+      }
+    } else {
+      if (type == '1') {
+        for (const val of plateList.value) {
+          if (val.id == '-1') val.is_active = true
+          else val.is_active = false
+        }
+        industry.value = []
+      } else if (type == '2') {
+        for (const val of typeList.value) {
+          if (val.id == '-1') val.is_active = true
+          else val.is_active = false
+        }
+        field.value = []
+      } else {
+        for (const val of cityList.value) {
+          if (val.id == '-1') val.is_active = true
+          else val.is_active = false
+        }
+        city.value = []
+      }
+    }
+  }
+  await toSearchFind()
+}
+const toDel = async (data, type) => {
+  if (type == '1') {
+    for (const val of plateList.value) {
+      if (data.id == val.id) val.is_active = false
+    }
+    industry.value = industry.value.filter((f) => f.id != data.id)
+    if (industry.value.length == 0) {
+      for (const val of plateList.value) {
+        if (val.id == '-1') val.is_active = true
+      }
+    }
+  } else if (type == '2') {
+    for (const val of typeList.value) {
+      if (data.id == val.id) val.is_active = false
+    }
+    field.value = field.value.filter((f) => f.id != data.id)
+    if (field.value.length == 0) {
+      for (const val of typeList.value) {
+        if (val.id == '-1') val.is_active = true
+      }
+    }
+  } else {
+    for (const val of cityList.value) {
+      if (data.id == val.id) val.is_active = false
+    }
+    city.value = city.value.filter((f) => f.id != data.id)
+    if (city.value.length == 0) {
+      for (const val of cityList.value) {
+        if (val.id == '-1') val.is_active = true
+      }
+    }
+  }
+  await toSearchFind()
+}
+const removeEmptyValues = (obj) => {
+  Object.keys(obj).forEach((item) => {
+    if (!obj[item]) delete obj[item]
+  })
+  return obj
+}
+const toSearchInfo = async () => {
+  const search = await removeEmptyValues(searchForm.value)
+  emits('toSearchInfo', search)
+}
+const getField = (item, data) => {
+  let res = get(data, item, null)
+  if (item === 'type') res = res === null ? `text` : res
+  if (item === 'placeholder') res = res === null ? `请输入${data.label}` : res
+  if (item === `selectplaceholder`) res = res === null ? `请选择${data.label}` : res
+  if (item === 'required') res = res === null ? false : res
+  if (item === `error`) res = res === null ? `${data.label}错误` : res
+  return res
+}
+</script>
+<style scoped lang="scss">
+.c-search {
+  .twoSeacher {
+    background-color: #fff;
+    display: flex;
+    justify-content: center;
+    align-items: stretch;
+    position: relative;
+    border: solid 1px #e5e5e5;
+    border-top: 0;
+    font-size: $global-font-size-18;
+    color: #666;
+    min-height: 60px;
+    overflow: hidden;
+    .twoLeft {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      flex-shrink: 0;
+      width: 110px;
+      text-align: center;
+      color: #000;
+      font-weight: bold;
+      background-color: #fafafa;
+    }
+    .twoRight {
+      display: flex;
+      flex-wrap: wrap;
+      align-items: center;
+      padding: 12px;
+      flex: 1;
+      border-left: solid 1px #e5e5e5;
+      background-color: #fff;
+      .label {
+        margin: 0 3px 3px 0;
+        color: #313131;
+        padding: 8px 10px;
+        border-radius: 3px;
+        background-color: #fff;
+        border: solid 1px transparent;
+        cursor: pointer;
+      }
+      .show {
+        color: #0a58c2;
+        border: solid 1px #006dd2;
+      }
+
+      .label:hover {
+        color: $global-color-107;
+      }
+    }
+    .button {
+      display: flex;
+      align-items: center;
+      margin: 0 5px 0 0;
+    }
+  }
+  .searchList:first-child {
+    .twoSeacher {
+      border-top: solid 1px #e5e5e5;
+    }
+  }
+  .twoIpunt {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    margin: 10px 0 0 0;
+    .input {
+      margin: 0 5px 0 0;
+    }
+    .button {
+      margin: 0 0 0 5px;
+    }
+  }
+}
+</style>

+ 84 - 0
src/components/custom/custom-upload.vue

@@ -0,0 +1,84 @@
+<template>
+  <div id="c-upload">
+    <el-upload v-if="url" ref="upload" :action="url" :limit="limit" :accept="accept" :file-list="list" :list-type="listType" :on-exceed="outLimit" :on-preview="filePreview" :on-success="onSuccess" :before-remove="onRemove">
+      <el-button type="primary">选择文件</el-button>
+      <template #tip v-if="tip">
+        <p style="color: #ff0000">{{ tip }}</p>
+      </template>
+    </el-upload>
+    <el-dialog v-model="dialog.show" append-to-body>
+      <img width="100%" :src="dialog.url" alt="" />
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ElMessage } from 'element-plus'
+import { omit, cloneDeep, isArray } from 'lodash-es'
+
+let dialog = ref({ show: false, url: '' })
+const props = defineProps({
+  url: { type: String, default: () => '' },
+  limit: { type: Number, default: () => 6 },
+  accept: { type: String, default: () => 'image/png, image/jpeg' },
+  listType: { type: String, default: () => 'text' }, //'text' | 'picture' | 'picture-card'
+  tip: { type: String, default: () => undefined },
+  list: { type: Array, default: () => [] },
+  model: { type: String, default: () => '' }
+})
+// 图片上传地址
+const { url } = toRefs(props)
+// 可上传文件数目
+const { limit } = toRefs(props)
+// 接收上传的文件类型
+const { accept } = toRefs(props)
+// 文件列表的类型--picture-card---picture
+const { listType } = toRefs(props)
+// 文件提醒
+const { tip } = toRefs(props)
+// 已有数据,赋值,预览
+const { list } = toRefs(props)
+const { model } = toRefs(props)
+// const list = ref<UploadUserFile[]>([]);
+
+const emit = defineEmits(['change'])
+// 图片预览
+const filePreview = (file) => {
+  // this.dialog = { show: true, url: file.url };
+  window.open(file.url)
+}
+// 只允许上传多少个文件
+const outLimit = () => {
+  ElMessage.error(`只允许上传${limit.value}个文件`)
+}
+// 上传成功,response:成功信息,file:图片信息,fileList:图片列表
+const onSuccess = (response, file) => {
+  if (response.errcode !== 0) {
+    ElMessage({ type: 'error', message: '删除成功' })
+    return
+  }
+  let ponse = omit(response, ['errcode', 'errmsg'])
+  let arr = cloneDeep(list)
+  if (isArray(list.value)) {
+    arr.value.push({ ...ponse, name: file.name, url: `${response.uri}` })
+  } else {
+    arr.value = [{ ...ponse, name: file.name, url: `${response.uri}` }]
+  }
+  emit('change', { model: model.value, value: arr.value })
+}
+// 删除图片
+// file: { id: any; uri: string }, fileList: any
+const onRemove = (uploadFile, uploadFiles) => {
+  console.log(uploadFile, uploadFiles)
+  let info = uploadFiles.filter((f) => f.id != uploadFile.id)
+  emit('change', { model: model.value, value: info })
+}
+
+// #endregion
+</script>
+
+<style lang="scss" scoped>
+#c-upload {
+  width: 100%;
+}
+</style>

+ 13 - 0
src/components/index.js

@@ -0,0 +1,13 @@
+export default function globalComponents(app) {
+  const components = import.meta.glob('./**/**.{vue,tsx}', { eager: true }) //获取文件夹及其嵌套的多级子文件夹
+  for (let [key, value] of Object.entries(components)) {
+    let name = key.replace('./', '').split('/')[0]
+    if (name == 'custom') {
+      name = key
+        .replace('./custom/', '')
+        .split('/')[0]
+        .replace(/\.vue$/, '')
+    }
+    app.component(value.default.name || name, value.default)
+  }
+}

+ 68 - 0
src/layout/site.js

@@ -0,0 +1,68 @@
+// 图片引入
+import logo from '/images/logo.png'
+import Logo from '/images/logo-jilinbai.png'
+import Code from '/images/code.jpg'
+// 网站基本设置
+export const siteInfo = {
+  display: false,
+  zhTitle: '产学研用协同创新数字化平台',
+  zhEnglish: 'Collaborative Innovation Digital Platform for Industry University Research Application',
+  logoUrl: logo
+}
+// 网站底部信息
+export const footInfo = {
+  Phone: '0431-81509921',
+  Email: 'gyyzhglb@jlitri.com',
+  Address: '吉林省长春市汽车经济技术开发区兴顺路1366号',
+  Copyright: 'Copyright ©2022  吉林省工业技术研究院集团有限公司  All Rights Reserved ',
+  Company: '吉林省工业技术研究院集团有限公司',
+  Logo,
+  Code
+}
+// 目录设置
+export const menuList = [
+  { key: '1', title: '首页', route: 'one', English: 'Home', label: '首页' },
+  { key: '2', title: '信息发布', route: 'two', English: 'Information Delivery', label: '信息发布' },
+  {
+    key: '3',
+    title: '创新中心',
+    English: 'News Information',
+    label: '创新中心',
+    children: [
+      { key: '3-1', title: '需求中心', route: 'thrOne', label: '需求中心' },
+      { key: '3-2', title: '供给中心', route: 'thrTwo', label: '供方中心' }
+    ]
+  },
+  { key: '4', title: '精准匹配', route: 'twelve', English: 'Accurate Matching', label: '精准匹配' },
+  { key: '5', title: '信息检索', route: 'four', English: 'Expert Tank', label: '信息检索' },
+  { key: '6', title: '双创活动', route: 'five', English: 'Authorized Operator', label: '双创活动' },
+  { key: '7', title: '中试平台', route: 'six', English: 'Project Selection', label: '中试平台' },
+  { key: '8', title: '服务支撑', route: 'seven', English: 'Innovation Competition', label: '服务支撑' },
+  { key: '9', title: '产业集群', route: 'eight', English: 'Innovation Competition', label: '产业集群' },
+  { key: '11', title: '孵化基地', route: 'eleven', English: 'Achievement Display', label: '孵化基地' },
+  {
+    key: '12',
+    title: '产业孵化大脑',
+    route: 'brain',
+    English: 'Achievement Display',
+    label: '产业孵化大脑'
+  },
+  { key: '13', title: '行研产研', route: 'thirteen', English: 'Research Development', label: '行研产研' }
+]
+// 目录设置
+export const menuList3 = [
+  { id: 1, name: '基本信息', route_name: 'center', path: '/center', icon: 'User' },
+  { id: 2, name: '认证入驻', route_name: 'attestation', path: '/attestation', icon: 'Finished' },
+  { id: 3, name: '通知管理', route_name: 'notice', path: '/notice', icon: 'Notebook' },
+  { id: 4, name: '成果管理', route_name: 'achievement', path: '/achievement', icon: 'Medal' },
+  { id: 5, name: '需求管理', route_name: 'demand', path: '/demand', icon: 'DataBoard' },
+  { id: 6, name: '我的收藏', route_name: 'collect', path: '/collect', icon: 'Collection' },
+  { id: 7, name: '活动管理', route_name: 'sign', path: '/sign', icon: 'Suitcase' },
+  { id: 8, name: '行业动态', route_name: 'news2', path: '/news2', icon: 'Message' },
+  { id: 9, name: '供给管理', route_name: 'supply', path: '/supply', icon: 'Notification' },
+  { id: 10, name: '中试管理', route_name: 'footplate', path: '/footplate', icon: 'TakeawayBox' },
+  { id: 11, name: '赛事管理', route_name: 'match', path: '/match', icon: 'CollectionTag' },
+  // { id: 12, name: '行研产研', route_name: 'journal', path: '/journal', icon: 'Reading' },
+  { id: 13, name: '项目管理', route_name: 'project', path: '/project', icon: 'Trophy' },
+  { id: 14, name: '修改密码', route_name: 'password', path: '/password', icon: 'Lock' }
+]

+ 40 - 0
src/main.js

@@ -0,0 +1,40 @@
+import { createApp } from 'vue'
+import { setupStore } from '@/store'
+
+import App from './App.vue'
+import router from './router'
+
+// element
+import ElementPlus from 'element-plus'
+import 'element-plus/dist/index.css'
+import locale from 'element-plus/es/locale/lang/zh-cn'
+import * as ElementPlusIconsVue from '@element-plus/icons-vue'
+
+// 请求检查函数
+import { InitCheckResult } from './utils/checkResult'
+import { InitVariable } from './utils/variable'
+import './utils/rem'
+// 组件
+import globalComponents from '@/components'
+// 自动滚动
+import vue3SeamlessScroll from 'vue3-seamless-scroll'
+// Antd
+import Antd from 'ant-design-vue'
+import 'ant-design-vue/dist/reset.css'
+import 'default-passive-events'
+
+import 'animate.css/animate.min.css'
+
+const app = createApp(App)
+globalComponents(app)
+setupStore(app)
+for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
+  app.component(key, component)
+}
+app.use(router)
+app.use(Antd)
+app.use(ElementPlus, { locale })
+app.use(vue3SeamlessScroll, { name: 'vue3SeamlessScroll' })
+InitCheckResult(app)
+InitVariable(app)
+app.mount('#app')

+ 76 - 0
src/router/index.js

@@ -0,0 +1,76 @@
+import { createRouter, createWebHistory } from 'vue-router'
+import { get } from 'lodash-es'
+import { UserStore } from '@/store/user'
+import { isInCenter, hasCenterRoutes } from './modules/center'
+import axios from 'axios'
+const router = createRouter({
+  history: createWebHistory(import.meta.env.BASE_URL),
+  routes: [
+    {
+      path: '/',
+      redirect: '/home'
+    },
+    {
+      path: '/home',
+      name: 'home',
+      meta: { title: '产学研用协同创新数字化平台', keepAlive: true },
+      component: () => import('@/views/home/index.vue')
+    }
+  ]
+})
+const getRedirectUri = (route) => {
+  return route.path
+}
+const centerRoutePagePath = '/info/loading'
+router.beforeEach(async (to, from, next) => {
+  const userStore = UserStore()
+  document.title = get(to, 'meta.title', '产学研用协同创新数字化平台')
+  const routeCheck = isInCenter(to)
+  // 进入个人中心,检查是否有token,没有token直接返回login
+  const token = localStorage.getItem('token')
+  if (token) {
+    const res = await axios.request({
+      method: 'get',
+      url: `${import.meta.env.VITE_APP_BASE_API}/token/tokenView`,
+      responseType: 'json',
+      headers: {
+        token: token
+      }
+    })
+    if (res.data.errcode === 0) userStore.setUser(res.data.data)
+    if (!routeCheck) {
+      // 没有进入个人中心,直接放行
+      next()
+      return
+    }
+    //个人中心处理
+    const isReg = hasCenterRoutes(router, to)
+    // 返回结果是undefined,不需要处理,直接next
+    if (!isReg) {
+      // 返回false,是需要注册路由,跳转进入路由页面
+      const menus = userStore.menus
+      if (menus && menus.length > 0) {
+        // 已注册完路由,但是没有当前路由信息,回到基础信息
+        next({ path: '/center/basic' })
+        return
+      }
+      next({ path: centerRoutePagePath, query: { redirectPath: getRedirectUri(to) } })
+    } else {
+      let redirectPath = get(to, 'query.redirectPath')
+      if (redirectPath) next(redirectPath)
+      else next()
+      return
+    }
+  } else {
+    if (!routeCheck) {
+      // 没有进入个人中心,直接放行
+      next()
+      return
+    }
+    next('/login?status=1')
+  }
+})
+router.afterEach(() => {
+  window.scrollTo(0, 0)
+})
+export default router

+ 20 - 0
src/router/modules/center.js

@@ -0,0 +1,20 @@
+/**
+ * 检查是否进入个人中心:path以/center开始或者name以center_开始
+ *  不是进入个人中心,直接跳出
+ *  是进入个人中心:
+ *    1.检查是否已经注册路由:
+ *      已注册: 跳过
+ *      未注册:
+ *        1.注册路由至一级的Layout下
+ */
+export const isInCenter = (to) => {
+  const { path } = to
+  if (!path.includes('/center')) return false
+  return true
+}
+
+export const hasCenterRoutes = (router, to) => {
+  const routes = router.getRoutes()
+  const thisRoute = routes.find((f) => f.path === to.path)
+  return thisRoute
+}

+ 22 - 0
src/router/register.js

@@ -0,0 +1,22 @@
+export const addUserRoutes = (menus, router) => {
+  const menuArr = toRaw(menus)
+  // 默认注册位置
+  const __def = 'Layout'
+  const loadComponent = import.meta.glob('../views/**/*.vue')
+  for (const m of menuArr) {
+    const { path, route_name, type, component, name } = m
+    const getComponent = loadComponent[`../views${component}.vue`]
+    const route = {
+      path: path,
+      name: route_name,
+      meta: {
+        title: `${name}`,
+        type
+      },
+      component: getComponent
+    }
+    if (getComponent) {
+      router.addRoute(__def, route)
+    }
+  }
+}

+ 16 - 0
src/settings.js

@@ -0,0 +1,16 @@
+const defaultSettings = {
+  title: 'admin-template',
+  version: 'v0.0.1',
+  showSettings: true,
+  tagsView: true,
+  fixedHeader: false,
+  sidebarLogo: true,
+  layout: 'left',
+  theme: 'light',
+  size: 'default',
+  language: 'zh-cn',
+  themeColor: '#409EFF',
+  watermark: { enabled: false, content: '福瑞科技' }
+}
+
+export default defaultSettings

+ 43 - 0
src/store/api/exportConfig.js

@@ -0,0 +1,43 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+const url = '/ec'
+const axios = new AxiosWrapper()
+
+export const ExportConfigStore = defineStore('exportConfig', () => {
+  const dict = async (payload) => {
+    const res = await axios.$get(`${url}/dict`)
+    return res
+  }
+  const update = async (payload) => {
+    const table = get(payload, 'table')
+    const res = await axios.$post(`${url}/${table}`, payload)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/user/${payload}`)
+    return res
+  }
+  const mission = async (payload) => {
+    const res = await axios.$post(`/asyncExport`, payload)
+    return res
+  }
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`/asyncExport`, cond)
+    return res
+  }
+  const reExecute = async (payload) => {
+    const res = await axios.$post(`/asyncExport/reExecute`, { id: payload })
+    return res
+  }
+
+  return {
+    fetch,
+    mission,
+    query,
+    reExecute
+  }
+})

+ 58 - 0
src/store/api/login.js

@@ -0,0 +1,58 @@
+import { defineStore } from 'pinia'
+import { get, omit } from 'lodash-es'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+const axios = new AxiosWrapper()
+export const LoginStore = defineStore('login', () => {
+  const login = async (payload) => {
+    const type = get(payload, 'type')
+    const np = omit(payload, 'type')
+    const res = await axios.$post(`/login/${type}`, np)
+    return res
+  }
+  const rp = async (payload) => {
+    const type = get(payload, 'type')
+    const np = omit(payload, 'type')
+    const res = await axios.$post(`/login/updatePwd/${type}`, np)
+    return res
+  }
+  const rpNoNewPassword = async (payload) => {
+    const type = get(payload, 'type')
+    const np = omit(payload, 'type')
+    const res = await axios.$post(`/login/resetPwd/${type}`, np)
+    return res
+  }
+  const loginSmsCode = async (payload) => {
+    const result = await axios.$post(`/login/sms/code`, payload)
+    return result
+  }
+  const loginBySms = async (payload) => {
+    const result = await axios.$post(`/login/sms/login`, payload)
+    return result
+  }
+  /**找回密码-1.获取验证码 */
+  const pwdCode = async (payload) => {
+    const result = await axios.$post(`/login/pwd/code`, payload)
+    return result
+  }
+  /**找回密码-2.验证码验证 */
+  const pwdCheck = async (payload) => {
+    const result = await axios.$post(`/login/pwd/check`, payload)
+    return result
+  }
+  /**找回密码-3.密码重置 */
+  const pwdReset = async (payload) => {
+    const result = await axios.$post(`/login/pwd/reset`, payload)
+    return result
+  }
+  /**注册-1.获取验证码 */
+  const regCode = async (payload) => {
+    const result = await axios.$post(`/login/reg/code`, payload)
+    return result
+  }
+  /**注册-2.验证码验证 */
+  const regCheck = async (payload) => {
+    const result = await axios.$post(`/login/reg/check`, payload)
+    return result
+  }
+  return { login, rp, rpNoNewPassword, loginSmsCode, loginBySms, pwdCode, pwdCheck, pwdReset, regCode, regCheck }
+})

+ 54 - 0
src/store/api/platform/achievement.js

@@ -0,0 +1,54 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/achievement'
+const axios = new AxiosWrapper()
+
+export const AchievementStore = defineStore('achievement', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const list = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}/list`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const detail = async (payload) => {
+    const res = await axios.$get(`${url}/detail/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    list,
+    fetch,
+    detail,
+    create,
+    update,
+    del
+  }
+})

+ 55 - 0
src/store/api/platform/chat.js

@@ -0,0 +1,55 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/chat'
+const axios = new AxiosWrapper()
+
+export const ChatStore = defineStore('chat', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const chat = async (cond) => {
+    const res = await axios.$get(`${url}/chat`, cond)
+    return res
+  }
+  const read = async (cond) => {
+    const res = await axios.$get(`${url}/read`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const cancel = async (payload) => {
+    const res = await axios.$post(`${url}/cancel`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    chat,
+    read,
+    fetch,
+    create,
+    update,
+    cancel,
+    del
+  }
+})

+ 54 - 0
src/store/api/platform/collection.js

@@ -0,0 +1,54 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/collection'
+const axios = new AxiosWrapper()
+
+export const CollectionStore = defineStore('collection', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const list = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}/list`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const cancel = async (payload) => {
+    const res = await axios.$post(`${url}/cancel`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    list,
+    cancel,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 54 - 0
src/store/api/platform/demand.js

@@ -0,0 +1,54 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/demand'
+const axios = new AxiosWrapper()
+
+export const DemandStore = defineStore('demand', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const list = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}/list`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const detail = async (payload) => {
+    const res = await axios.$get(`${url}/detail/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    list,
+    fetch,
+    create,
+    detail,
+    update,
+    del
+  }
+})

+ 40 - 0
src/store/api/platform/design.js

@@ -0,0 +1,40 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/design'
+const axios = new AxiosWrapper()
+
+export const DesignStore = defineStore('design', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 40 - 0
src/store/api/platform/directory.js

@@ -0,0 +1,40 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/directory'
+const axios = new AxiosWrapper()
+
+export const DirectoryStore = defineStore('directory', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, 'id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 54 - 0
src/store/api/platform/footplate.js

@@ -0,0 +1,54 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/footplate'
+const axios = new AxiosWrapper()
+
+export const FootplateStore = defineStore('footplate', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const list = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}/list`, cond)
+    return res
+  }
+  const detail = async (payload) => {
+    const res = await axios.$get(`${url}/detail/${payload}`)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, 'id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    list,
+    detail,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 49 - 0
src/store/api/platform/friend.js

@@ -0,0 +1,49 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/friend'
+const axios = new AxiosWrapper()
+
+export const FriendStore = defineStore('friend', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const list = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}/list`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, 'id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    list,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 40 - 0
src/store/api/platform/journal.js

@@ -0,0 +1,40 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/journal'
+const axios = new AxiosWrapper()
+
+export const JournalStore = defineStore('journal', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, 'id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 45 - 0
src/store/api/platform/match.js

@@ -0,0 +1,45 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/match'
+const axios = new AxiosWrapper()
+
+export const MatchStore = defineStore('match', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const detail = async (payload) => {
+    const res = await axios.$get(`${url}/detail/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    detail,
+    create,
+    update,
+    del
+  }
+})

+ 99 - 0
src/store/api/platform/matchExt.js

@@ -0,0 +1,99 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/matchExt'
+const axios = new AxiosWrapper()
+
+export const MatchExtStore = defineStore('matchExt', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  const firstStep = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/firstStep/${id}`, payload)
+    return res
+  }
+  const step1 = async (payload) => {
+    const res = await axios.$post(`${url}/step1`, payload)
+    return res
+  }
+  const step2 = async (payload) => {
+    const res = await axios.$post(`${url}/step2`, payload)
+    return res
+  }
+  const stepFill = async (payload) => {
+    const res = await axios.$post(`${url}/step2/fill`, payload)
+    return res
+  }
+  const step3 = async (payload) => {
+    const res = await axios.$post(`${url}/step3`, payload)
+    return res
+  }
+  const step4 = async (payload) => {
+    const res = await axios.$post(`${url}/step4`, payload)
+    return res
+  }
+  const step4Score = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/step4/score/${id}`, payload)
+    return res
+  }
+  const step4To5 = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/step4/to5/${id}`, payload)
+    return res
+  }
+  const step5 = async (payload) => {
+    const res = await axios.$post(`${url}/step5`, payload)
+    return res
+  }
+  const step5Time = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/step5/time/${id}`, payload)
+    return res
+  }
+  const step5Sms = async (payload) => {
+    const res = await axios.$get(`${url}/step5/sms/${payload}`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    create,
+    update,
+    firstStep,
+    step1,
+    step2,
+    stepFill,
+    step3,
+    step4,
+    step4Score,
+    step4To5,
+    step5,
+    step5Time,
+    step5Sms,
+    del
+  }
+})

+ 40 - 0
src/store/api/platform/matchReg.js

@@ -0,0 +1,40 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/matchReg'
+const axios = new AxiosWrapper()
+
+export const MatchRegStore = defineStore('matchReg', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 45 - 0
src/store/api/platform/news.js

@@ -0,0 +1,45 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/news'
+const axios = new AxiosWrapper()
+
+export const NewsStore = defineStore('news', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const detail = async (payload) => {
+    const res = await axios.$get(`${url}/detail/${payload}`)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    detail,
+    create,
+    update,
+    del
+  }
+})

+ 40 - 0
src/store/api/platform/notes.js

@@ -0,0 +1,40 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/notes'
+const axios = new AxiosWrapper()
+
+export const NotesStore = defineStore('notes', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, 'id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 40 - 0
src/store/api/platform/process.js

@@ -0,0 +1,40 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/matchPath'
+const axios = new AxiosWrapper()
+
+export const ProcessStore = defineStore('process', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, 'id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 54 - 0
src/store/api/platform/project.js

@@ -0,0 +1,54 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/project'
+const axios = new AxiosWrapper()
+
+export const ProjectStore = defineStore('project', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const list = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}/list`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const detail = async (payload) => {
+    const res = await axios.$get(`${url}/detail/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    list,
+    fetch,
+    detail,
+    create,
+    update,
+    del
+  }
+})

+ 54 - 0
src/store/api/platform/score.js

@@ -0,0 +1,54 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/score'
+const axios = new AxiosWrapper()
+
+export const ScoreStore = defineStore('score', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const list = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}/list`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const detail = async (payload) => {
+    const res = await axios.$get(`${url}/detail/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    list,
+    fetch,
+    detail,
+    create,
+    update,
+    del
+  }
+})

+ 40 - 0
src/store/api/platform/sector.js

@@ -0,0 +1,40 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/sector'
+const axios = new AxiosWrapper()
+
+export const SectorStore = defineStore('sector', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 49 - 0
src/store/api/platform/sign.js

@@ -0,0 +1,49 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/sign'
+const axios = new AxiosWrapper()
+
+export const SignStore = defineStore('sign', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const sign = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}/sign`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    sign,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 54 - 0
src/store/api/platform/supply.js

@@ -0,0 +1,54 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/supply'
+const axios = new AxiosWrapper()
+
+export const SupplyStore = defineStore('supply', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const list = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}/list`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const detail = async (payload) => {
+    const res = await axios.$get(`${url}/detail/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    list,
+    fetch,
+    create,
+    detail,
+    update,
+    del
+  }
+})

+ 54 - 0
src/store/api/platform/support.js

@@ -0,0 +1,54 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/support'
+const axios = new AxiosWrapper()
+
+export const SupportStore = defineStore('support', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const list = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}/list`, cond)
+    return res
+  }
+  const detail = async (payload) => {
+    const res = await axios.$get(`${url}/detail/${payload}`)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, 'id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    list,
+    detail,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 37 - 0
src/store/api/platform/tool.js

@@ -0,0 +1,37 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+const url = '/tool'
+const axios = new AxiosWrapper()
+
+export const ToolStore = defineStore('tool', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}/query`, cond)
+    return res
+  }
+  const getCollection = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}/MyCollection`, cond)
+    return res
+  }
+  const total = async (info) => {
+    const res = await axios.$get(`${url}/getTotal`, info)
+    return res
+  }
+  const getTotal = async () => {
+    const res = await axios.$get(`${url}/CollectionTotal`)
+    return res
+  }
+  return {
+    query,
+    total,
+    getCollection,
+    getTotal
+  }
+})

+ 40 - 0
src/store/api/system/dictData.js

@@ -0,0 +1,40 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/dictData'
+const axios = new AxiosWrapper()
+
+export const DictDataStore = defineStore('dictData', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 40 - 0
src/store/api/system/dictType.js

@@ -0,0 +1,40 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/dictType'
+const axios = new AxiosWrapper()
+
+export const DictTypeStore = defineStore('dictType', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 40 - 0
src/store/api/system/menus.js

@@ -0,0 +1,40 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/menus'
+const axios = new AxiosWrapper()
+
+export const MenusStore = defineStore('menus', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 50 - 0
src/store/api/system/message.js

@@ -0,0 +1,50 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/message'
+const axios = new AxiosWrapper()
+
+export const MessageStore = defineStore('message', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, 'id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  const read = async (payload) => {
+    const res = await axios.$get(`${url}/read/${payload}`)
+    return res
+  }
+  const checkNotRead = async () => {
+    const res = await axios.$get(`${url}/checkNotRead`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    create,
+    update,
+    read,
+    del,
+    checkNotRead
+  }
+})

+ 54 - 0
src/store/api/system/region.js

@@ -0,0 +1,54 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/region'
+const axios = new AxiosWrapper()
+
+export const RegionStore = defineStore('region', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const area = async (payload) => {
+    const res = await axios.$get(`${url}/area`, payload)
+    return res
+  }
+  const list = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}/list`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, 'id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    list,
+    area,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 45 - 0
src/store/api/system/role.js

@@ -0,0 +1,45 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/role'
+const axios = new AxiosWrapper()
+
+export const RoleStore = defineStore('role', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const prove = async (payload) => {
+    const res = await axios.$get(`${url}/prove`, payload)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    prove,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 40 - 0
src/store/api/system/tags.js

@@ -0,0 +1,40 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/tags'
+const axios = new AxiosWrapper()
+
+export const TagsStore = defineStore('tags', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 40 - 0
src/store/api/user/admin.js

@@ -0,0 +1,40 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/admin'
+const axios = new AxiosWrapper()
+
+export const AdminStore = defineStore('admin', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 40 - 0
src/store/api/user/applyCompany.js

@@ -0,0 +1,40 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/applyCompany'
+const axios = new AxiosWrapper()
+
+export const ApplyCompanyStore = defineStore('applyCompany', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 40 - 0
src/store/api/user/association.js

@@ -0,0 +1,40 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/association'
+const axios = new AxiosWrapper()
+
+export const AssociationStore = defineStore('association', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 49 - 0
src/store/api/user/cirelation.js

@@ -0,0 +1,49 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/cirelation'
+const axios = new AxiosWrapper()
+
+export const CirelationStore = defineStore('cirelation', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const list = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}/list`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    list,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 63 - 0
src/store/api/user/company.js

@@ -0,0 +1,63 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/company'
+const axios = new AxiosWrapper()
+
+export const CompanyStore = defineStore('company', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const list = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}/list`, cond)
+    return res
+  }
+  const contact = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}/contact`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const detail = async (payload) => {
+    const res = await axios.$get(`${url}/detail/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    list,
+    contact,
+    fetch,
+    detail,
+    create,
+    update,
+    del
+  }
+})

+ 40 - 0
src/store/api/user/companyYear.js

@@ -0,0 +1,40 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/companyYear'
+const axios = new AxiosWrapper()
+
+export const CompanyYearStore = defineStore('companyYear', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 40 - 0
src/store/api/user/competition.js

@@ -0,0 +1,40 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/competition'
+const axios = new AxiosWrapper()
+
+export const CompetitionStore = defineStore('competition', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 33 - 0
src/store/api/user/contactApply.js

@@ -0,0 +1,33 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+const url = '/contactApply'
+const axios = new AxiosWrapper()
+
+export const ContactApplyStore = defineStore('contactApply', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const signCreate = async (payload) => {
+    const res = await axios.$post(`${url}/sign`, payload)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    create,
+    signCreate
+  }
+})

+ 54 - 0
src/store/api/user/expert.js

@@ -0,0 +1,54 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/expert'
+const axios = new AxiosWrapper()
+
+export const ExpertStore = defineStore('expert', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const list = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}/list`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const detail = async (payload) => {
+    const res = await axios.$get(`${url}/detail/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    list,
+    fetch,
+    detail,
+    create,
+    update,
+    del
+  }
+})

+ 74 - 0
src/store/api/user/incubator.js

@@ -0,0 +1,74 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/incubator'
+const axios = new AxiosWrapper()
+
+export const IncubatorStore = defineStore('incubator', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const list = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}/list`, cond)
+    return res
+  }
+  const all = async (payload) => {
+    const res = await axios.$get(`${url}/allList`, payload)
+    return res
+  }
+  const field = async (payload) => {
+    const res = await axios.$get(`${url}/field`, payload)
+    return res
+  }
+  const cfield = async (payload) => {
+    const res = await axios.$get(`${url}/cfield`, payload)
+    return res
+  }
+  const statistics = async (payload) => {
+    const res = await axios.$get(`${url}/statistics`, payload)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const detail = async (payload) => {
+    const res = await axios.$get(`${url}/detail/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    list,
+    all,
+    field,
+    cfield,
+    statistics,
+    detail,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 40 - 0
src/store/api/user/incubatorYear.js

@@ -0,0 +1,40 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/incubatorYear'
+const axios = new AxiosWrapper()
+
+export const IncubatorYearStore = defineStore('incubatorYear', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 40 - 0
src/store/api/user/investment.js

@@ -0,0 +1,40 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/investment'
+const axios = new AxiosWrapper()
+
+export const InvestmentStore = defineStore('investment', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 40 - 0
src/store/api/user/school.js

@@ -0,0 +1,40 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/school'
+const axios = new AxiosWrapper()
+
+export const SchoolStore = defineStore('school', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 40 - 0
src/store/api/user/state.js

@@ -0,0 +1,40 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/state'
+const axios = new AxiosWrapper()
+
+export const StateStore = defineStore('state', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 40 - 0
src/store/api/user/unit.js

@@ -0,0 +1,40 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/unit'
+const axios = new AxiosWrapper()
+
+export const UnitStore = defineStore('unit', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 45 - 0
src/store/api/user/user.js

@@ -0,0 +1,45 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/user'
+const axios = new AxiosWrapper()
+
+export const UsersStore = defineStore('users', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const detail = async (payload) => {
+    const res = await axios.$get(`${url}/detail/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    detail,
+    create,
+    update,
+    del
+  }
+})

+ 38 - 0
src/store/api/util.js

@@ -0,0 +1,38 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+const axios = new AxiosWrapper()
+export const UtilStore = defineStore('util', () => {
+  const toImport = async (payload) => {
+    const res = await axios.$post(`/util/toImport`, payload)
+    return res
+  }
+  const toExport = async (payload) => {
+    const res = await axios.$post(`/util/toExport`, payload)
+    return res
+  }
+  const toTotal = async (payload) => {
+    const res = await axios.$post(`/util/toTotal`, payload)
+    return res
+  }
+  const cstatistics = async (payload) => {
+    const res = await axios.$get(`/util/cstatistics`, payload)
+    return res
+  }
+  const sstatistics = async (payload) => {
+    const res = await axios.$get(`/util/sstatistics`, payload)
+    return res
+  }
+  const dstatistics = async (payload) => {
+    const res = await axios.$get(`/util/dstatistics`, payload)
+    return res
+  }
+  const pstatistics = async (payload) => {
+    const res = await axios.$get(`/util/pstatistics`, payload)
+    return res
+  }
+  const astatistics = async (payload) => {
+    const res = await axios.$get(`/util/astatistics`, payload)
+    return res
+  }
+  return { toImport, toExport, toTotal, cstatistics, sstatistics, dstatistics, pstatistics, astatistics }
+})

+ 12 - 0
src/store/index.js

@@ -0,0 +1,12 @@
+import { createPinia } from 'pinia'
+
+const store = createPinia()
+
+// 全局注册 store
+export function setupStore(app) {
+  app.use(store)
+}
+
+export * from './modules/app'
+export * from './modules/tagsView'
+export { store }

+ 94 - 0
src/store/modules/app.js

@@ -0,0 +1,94 @@
+import defaultSettings from '@/settings'
+import { useStorage } from '@vueuse/core'
+import { useCookies } from '@vueuse/integrations/useCookies'
+// 导入 Element Plus 中英文语言包
+import zhCn from 'element-plus/es/locale/lang/zh-cn'
+import en from 'element-plus/es/locale/lang/en'
+const cookies = useCookies()
+
+// setup
+export const useAppStore = defineStore('app', () => {
+  // state
+  const device = useStorage('device', 'desktop')
+  const size = useStorage('size', defaultSettings.size)
+  let lang = cookies.get('locale')
+  if(!lang) lang = defaultSettings.language
+  const language = useStorage('language', lang)
+  const sidebarStatus = useStorage('sidebarStatus', 'closed')
+
+  const sidebar = reactive({
+    opened: sidebarStatus.value !== 'closed',
+    withoutAnimation: false
+  })
+  const activeTopMenuPath = useStorage('activeTopMenuPath', '')
+  /**
+   * 根据语言标识读取对应的语言包
+   */
+  const locale = computed(() => {
+    if (language?.value == 'en-us') {
+      return en
+    } else {
+      return zhCn
+    }
+  })
+
+  // actions
+  function toggleSidebar() {
+    sidebar.opened = !sidebar.opened
+    if (sidebar.opened) {
+      sidebarStatus.value = 'opened'
+    } else {
+      sidebarStatus.value = 'closed'
+    }
+  }
+
+  function closeSideBar() {
+    sidebar.opened = false
+    sidebarStatus.value = 'closed'
+  }
+
+  function openSideBar() {
+    sidebar.opened = true
+    sidebarStatus.value = 'opened'
+  }
+
+  function toggleDevice(val) {
+    device.value = val
+  }
+
+  function changeSize(val) {
+    size.value = val
+  }
+  /**
+   * 切换语言
+   *
+   * @param val
+   */
+  function changeLanguage(val) {
+    language.value = val
+    // cookie切换
+    cookies.set('locale', val, { path: '/' })
+  }
+  /**
+   * 混合模式顶部切换
+   */
+  function activeTopMenu(val) {
+    activeTopMenuPath.value = val
+  }
+
+  return {
+    device,
+    sidebar,
+    language,
+    locale,
+    size,
+    activeTopMenu,
+    toggleDevice,
+    changeSize,
+    changeLanguage,
+    toggleSidebar,
+    closeSideBar,
+    openSideBar,
+    activeTopMenuPath
+  }
+})

+ 206 - 0
src/store/modules/tagsView.js

@@ -0,0 +1,206 @@
+export const useTagsViewStore = defineStore('tagsView', () => {
+  const visitedViews = ref([{ affix: true, fullPath: '/', keepAlive: true, name: 'home', path: '/', title: 'home' }])
+  const cachedViews = ref([])
+
+  /**
+   * 添加已访问视图到已访问视图列表中
+   */
+  function addVisitedView(view) {
+    // 如果已经存在于已访问的视图列表中,则不再添加
+    if (visitedViews.value.some((v) => v.path === view.path)) {
+      return
+    }
+    // 如果视图是固定的(affix),则在已访问的视图列表的开头添加
+    if (view.affix) {
+      visitedViews.value.unshift(view)
+    } else {
+      // 如果视图不是固定的,则在已访问的视图列表的末尾添加
+      visitedViews.value.push(view)
+    }
+  }
+
+  /**
+   * 添加缓存视图到缓存视图列表中
+   */
+  function addCachedView(view) {
+    const viewName = view.name
+    // 如果缓存视图名称已经存在于缓存视图列表中,则不再添加
+    if (cachedViews.value.includes(viewName)) {
+      return
+    }
+    // 如果视图需要缓存(keepAlive),则将其路由名称添加到缓存视图列表中
+    if (view.keepAlive) {
+      cachedViews.value.push(viewName)
+    }
+  }
+
+  /**
+   * 从已访问视图列表中删除指定的视图
+   */
+  function delVisitedView(view) {
+    return new Promise((resolve) => {
+      for (const [i, v] of visitedViews.value.entries()) {
+        // 找到与指定视图路径匹配的视图,在已访问视图列表中删除该视图
+        if (v.path === view.path) {
+          visitedViews.value.splice(i, 1)
+          break
+        }
+      }
+      resolve([...visitedViews.value])
+    })
+  }
+
+  function delCachedView(view) {
+    const viewName = view.name
+    return new Promise((resolve) => {
+      const index = cachedViews.value.indexOf(viewName)
+      index > -1 && cachedViews.value.splice(index, 1)
+      resolve([...cachedViews.value])
+    })
+  }
+
+  function delOtherVisitedViews(view) {
+    return new Promise((resolve) => {
+      visitedViews.value = visitedViews.value.filter((v) => {
+        return v?.affix || v.path === view.path
+      })
+      resolve([...visitedViews.value])
+    })
+  }
+
+  function delOtherCachedViews(view) {
+    const viewName = view.name
+    return new Promise((resolve) => {
+      const index = cachedViews.value.indexOf(viewName)
+      if (index > -1) {
+        cachedViews.value = cachedViews.value.slice(index, index + 1)
+      } else {
+        // if index = -1, there is no cached tags
+        cachedViews.value = []
+      }
+      resolve([...cachedViews.value])
+    })
+  }
+
+  function updateVisitedView(view) {
+    for (let v of visitedViews.value) {
+      if (v.path === view.path) {
+        v = Object.assign(v, view)
+        break
+      }
+    }
+  }
+
+  function addView(view) {
+    addVisitedView(view)
+    addCachedView(view)
+  }
+
+  function delView(view) {
+    return new Promise((resolve) => {
+      delVisitedView(view)
+      delCachedView(view)
+      resolve({
+        visitedViews: [...visitedViews.value],
+        cachedViews: [...cachedViews.value]
+      })
+    })
+  }
+
+  function delOtherViews(view) {
+    return new Promise((resolve) => {
+      delOtherVisitedViews(view)
+      delOtherCachedViews(view)
+      resolve({
+        visitedViews: [...visitedViews.value],
+        cachedViews: [...cachedViews.value]
+      })
+    })
+  }
+
+  function delLeftViews(view) {
+    return new Promise((resolve) => {
+      const currIndex = visitedViews.value.findIndex((v) => v.path === view.path)
+      if (currIndex === -1) {
+        return
+      }
+      visitedViews.value = visitedViews.value.filter((item, index) => {
+        if (index >= currIndex || item?.affix) {
+          return true
+        }
+
+        const cacheIndex = cachedViews.value.indexOf(item.name)
+        if (cacheIndex > -1) {
+          cachedViews.value.splice(cacheIndex, 1)
+        }
+        return false
+      })
+      resolve({
+        visitedViews: [...visitedViews.value]
+      })
+    })
+  }
+  function delRightViews(view) {
+    return new Promise((resolve) => {
+      const currIndex = visitedViews.value.findIndex((v) => v.path === view.path)
+      if (currIndex === -1) {
+        return
+      }
+      visitedViews.value = visitedViews.value.filter((item, index) => {
+        if (index <= currIndex || item?.affix) {
+          return true
+        }
+      })
+      resolve({
+        visitedViews: [...visitedViews.value]
+      })
+    })
+  }
+
+  function delAllViews() {
+    return new Promise((resolve) => {
+      const affixTags = visitedViews.value.filter((tag) => tag?.affix)
+      visitedViews.value = affixTags
+      cachedViews.value = []
+      resolve({
+        visitedViews: [...visitedViews.value],
+        cachedViews: [...cachedViews.value]
+      })
+    })
+  }
+
+  function delAllVisitedViews() {
+    return new Promise((resolve) => {
+      const affixTags = visitedViews.value.filter((tag) => tag?.affix)
+      visitedViews.value = affixTags
+      resolve([...visitedViews.value])
+    })
+  }
+
+  function delAllCachedViews() {
+    return new Promise((resolve) => {
+      cachedViews.value = []
+      resolve([...cachedViews.value])
+    })
+  }
+
+  return {
+    visitedViews,
+    cachedViews,
+    addVisitedView,
+    addCachedView,
+    delVisitedView,
+    delCachedView,
+    delOtherVisitedViews,
+    delOtherCachedViews,
+    updateVisitedView,
+    addView,
+    delView,
+    delOtherViews,
+    delLeftViews,
+    delRightViews,
+    delAllViews,
+    delAllVisitedViews,
+    delAllCachedViews
+  }
+})

+ 24 - 0
src/store/user.js

@@ -0,0 +1,24 @@
+import { defineStore } from 'pinia'
+export const UserStore = defineStore('user', () => {
+  const user = ref({})
+  const menus = ref()
+  /**
+   * 将用户信息存起来;用户信息是在路由变更时,路由前置守卫去服务进行兑换而来
+   * @param {String} payload token串
+   */
+  const setUser = (payload) => {
+    user.value = payload
+  }
+  /**
+   * 清除用户信息并删除token
+   */
+  const logOut = () => {
+    user.value = {}
+    menus.value = null
+    localStorage.removeItem('token')
+  }
+  const setMenus = (payload) => {
+    menus.value = payload
+  }
+  return { user, setUser, logOut, menus, setMenus }
+})

+ 158 - 0
src/styles/variables.scss

@@ -0,0 +1,158 @@
+// 全局变量文件,比如命名为 _variables.scss
+$global-font-size-12: 12px;
+$global-font-size-13: 13px;
+$global-font-size-14: 14px;
+$global-font-size-15: 15px;
+$global-font-size-16: 16px;
+$global-font-size-17: 17px;
+$global-font-size-18: 18px;
+$global-font-size-19: 19px;
+$global-font-size-20: 20px;
+$global-font-size-21: 21px;
+$global-font-size-22: 22px;
+$global-font-size-23: 23px;
+$global-font-size-24: 24px;
+$global-font-size-25: 25px;
+$global-font-size-26: 26px;
+$global-font-size-27: 27px;
+$global-font-size-28: 28px;
+$global-font-size-29: 29px;
+$global-font-size-30: 30px;
+$global-font-size-31: 31px;
+$global-font-size-32: 32px;
+$global-font-size-34: 34px;
+$global-font-size-36: 36px;
+$global-font-size-40: 40px;
+$global-font-size-46: 46px;
+$global-font-size-48: 48px;
+$global-color-003: #003894;
+$global-color-107: #1073ff;
+$global-color-2D2: #2d2d2d;
+$global-color-fff: #ffffff;
+$global-color-bbb: #bbbbbb;
+$global-color-595: #595959;
+
+/* 全局覆盖antd表格表头样式 */
+.ant-table-thead > tr > th {
+  font-size: 18px; /* 设置表头字体大小 */
+}
+
+/* 全局覆盖antd表格表体样式 */
+.ant-table-tbody > tr > td {
+  font-size: 16px; /* 设置表体字体大小 */
+}
+
+.el-form-item__label {
+  font-size: 18px !important; /* 设置标签字体大小 */
+}
+
+// .el-input__inner {
+//   font-size: 16px !important; /* 设置输入框字体大小 */
+// }
+
+/* custom-element-plus.css */
+.el-tabs__item {
+  font-size: 18px !important; /* 设置标签页的字体大小 */
+}
+
+.el-tabs__item-text {
+  font-size: 18px !important; /* 确保标签页文本也有相同的字体大小 */
+}
+
+/* custom-antd.css */
+.ant-tabs-tab {
+  font-size: 18px; /* 或者你想要的任何字体大小 */
+}
+
+/* 如果需要增大标签页标题的字体大小 */
+.ant-tabs-tab-btn {
+  font-size: 18px; /* 保持与上面一致或按需调整 */
+}
+
+/* custom-element-ui.css */
+.el-radio__label {
+  font-size: 16px !important; /* 设置你想要的字体大小 */
+}
+
+/* 如果你还想调整单选按钮本身的字体大小(通常是内部的 span),你也可以添加如下规则 */
+.el-radio__original {
+  font-size: 16px !important; /* 确保内部元素也有相应的字体大小 */
+}
+
+.el-button {
+  font-size: 16px !important; /* 设置按钮的字体大小为你想要的尺寸 */
+}
+/* custom-element-plus.css */
+.el-select-dropdown__item {
+  font-size: 16px !important; /* 设置下拉选项的字体大小 */
+}
+
+.el-cascader__menu-item {
+  font-size: 16px !important; /* 设置级联选择器菜单项的字体大小 */
+}
+
+/* 如果需要调整级联选择器输入框的字体大小 */
+.el-cascader__input {
+  font-size: 16px !important; /* 设置输入框内的字体大小 */
+}
+/* custom-element-plus.css */
+.el-pagination__item {
+  font-size: 16px !important; /* 设置分页项(如页码、跳转按钮等)的字体大小 */
+}
+.el-select--large .el-select__wrapper {
+  font-size: 16px !important; /* 设置分页项(如页码、跳转按钮等)的字体大小 */
+}
+.el-range-editor--large .el-range-input {
+  font-size: 16px !important; /* 设置分页项(如页码、跳转按钮等)的字体大小 */
+}
+
+/* 如果需要调整页码选择器的字体大小 */
+.el-select-dropdown__item {
+  font-size: 16px !important; /* 分页选择器下拉项字体大小 */
+}
+
+/* 如果需要调整输入框的字体大小 */
+.el-pagination__editor {
+  font-size: 16px !important; /* 分页输入框的字体大小 */
+}
+
+/* 如果需要调整跳转按钮的字体大小 */
+.el-pagination__go-button {
+  font-size: 16px !important; /* 跳转按钮的字体大小 */
+}
+/* custom-antd-vue.css */
+.ant-descriptions-item-content {
+  font-size: 16px !important; /* 设置描述列表项内容的字体大小 */
+}
+
+/* 如果需要调整标题的字体大小 */
+.ant-descriptions-title {
+  font-size: 18px !important; /* 设置描述列表标题的字体大小 */
+}
+/* custom-antd.css */
+.ant-checkbox-wrapper {
+  font-size: 18px; /* 设置复选框标签的字体大小 */
+}
+
+/* 如果你还想调整复选框内部的文字大小(例如当使用 indeterminate 状态时) */
+.ant-checkbox-input {
+  & + .ant-checkbox-inner {
+    font-size: 16px; /* 设置复选框内部文字的字体大小 */
+  }
+}
+.ant-menu-title-content {
+  font-size: 18px !important; /* 设置复选框内部文字的字体大小 */
+}
+.el-checkbox__label {
+  font-size: 16px !important; /* 设置复选框内部文字的字体大小 */
+}
+.el-collapse-item__header {
+  font-size: $global-font-size-20 !important;
+  height: 60px !important;
+}
+.el-collapse-item__content {
+  font-size: $global-font-size-18 !important;
+}
+.el-descriptions--large .el-descriptions__body .el-descriptions__table .el-descriptions__cell {
+  font-size: $global-font-size-18 !important;
+}

+ 27 - 0
src/utils/animation.js

@@ -0,0 +1,27 @@
+// from 起始数字
+// to 最终数字
+// duration 延迟间隔(ms)
+// onProgress 更新回调函数
+export const useNumberAnimation = (options) => {
+  const { from, to, duration, onProgress } = options
+  let value = from
+  // 算出增长速度
+  const speed = (to - from) / duration
+  // 起始时间
+  const startTime = Date.now()
+  const run = () => {
+    //时间间隔
+    const t = Date.now() - startTime //当时间间隔大于延迟时间时终止
+    if (t >= duration) {
+      value = to
+      onProgress?.(value)
+      return
+    }
+    // 当前值 = 起始值 + 时间间隔 *增长速度
+    value = from + t * speed
+    onProgress?.(value)
+    // 继续执行
+    requestAnimationFrame(run)
+  }
+  run()
+}

+ 157 - 0
src/utils/axios-wrapper.js

@@ -0,0 +1,157 @@
+/* eslint-disable no-console */
+/* eslint-disable no-param-reassign */
+
+import { get, isObject, isArray } from 'lodash-es'
+import Axios from 'axios'
+import { trimData, isNullOrUndefined } from './util-methods'
+import { ErrorCode } from './error-code'
+
+let currentRequests = 0
+
+export class AxiosWrapper {
+  constructor({ baseUrl = import.meta.env.VITE_APP_BASE_API, unwrap = true } = {}) {
+    this.baseUrl = baseUrl
+    this.unwrap = unwrap
+  }
+  baseUrl
+  unwrap
+
+  // 替换uri中的参数变量
+  static merge(uri, query) {
+    if (!uri.includes(':')) {
+      return uri
+    }
+    const keys = []
+    const regexp = /\/:([a-z0-9_]+)/gi
+    let res
+    // eslint-disable-next-line no-cond-assign
+    while ((res = regexp.exec(uri)) != null) {
+      keys.push(res[1])
+    }
+    keys.forEach((key) => {
+      const val = get(query, key)
+      if (!isNullOrUndefined(val)) {
+        uri = uri.replace(`:${key}`, `${val}`)
+      }
+    })
+    return uri
+  }
+  // 新函数,重新生成url,并且把参数拼进uri中
+  static getUriWithQuery(uri, query) {
+    if (!uri || !query) return uri
+    uri = `${uri}?`
+    const arr = []
+    for (const i in query) {
+      const val = query[i]
+      if (isArray(val)) {
+        for (const v of val) {
+          const str = `${i}=${v}`
+          arr.push(str)
+        }
+      } else {
+        const str = `${i}=${val}`
+        arr.push(str)
+      }
+    }
+    const sign = '&'
+    uri = `${uri}${arr.join(sign)}`
+    return uri
+  }
+
+  $get(uri, query, options) {
+    return this.$request(uri, undefined, query, options)
+  }
+
+  $post(uri, data = {}, query, options) {
+    return this.$request(uri, data, query, options)
+  }
+  $delete(uri, data = {}, query, options = {}) {
+    options = { ...options, method: 'delete' }
+    return this.$request(uri, data, query, options)
+  }
+  async $request(uri, data, query, options = {}) {
+    // 此处需要重写,因为现在出现了同一个名称的变量要出现多次并且是不同的值 的情况
+    // if (query && isObject(query)) {
+    //   const keys = Object.keys(query)
+    //   for (const key of keys) {
+    //     const val = get(query, key)
+    //     if (val === '') {
+    //       delete query[key]
+    //     }
+    //   }
+    // }
+    /**
+     * 需要处理 query,options
+     * query: 将值拼入uri中
+     * options保持原数据即可,一般不会用
+     */
+    const url = AxiosWrapper.getUriWithQuery(uri, query)
+    // if (isObject(query) && isObject(options)) {
+    //   options = { ...options, params: query, method: 'get' }
+    // } else if (isObject(query) && !query.params) {
+    //   options = { params: query }
+    // } else if (isObject(query) && query.params) {
+    //   options = query
+    // }
+    // if (!options) options = {}
+    // if (options.params) options.params = trimData(options.params, null, null)
+    // const params = get(options, 'params')
+    // const url = AxiosWrapper.merge(uri, params)
+    currentRequests += 1
+    // Indicator.open({
+    //   spinnerType: 'fading-circle',
+    // });
+    try {
+      let returnData
+      const axios = Axios.create({
+        baseURL: this.baseUrl,
+        withCredentials: true
+      })
+      // if (util.token && util.token !== null) axios.defaults.headers.common.Authorization = util.token;
+      const token = localStorage.getItem('token')
+      const apiToken = localStorage.getItem('apiToken')
+      if (token) axios.defaults.headers.common['token'] = token
+      if (apiToken) axios.defaults.headers.common['api-token'] = apiToken
+      const res = await axios.request({
+        method: isNullOrUndefined(data) ? 'get' : 'post',
+        url,
+        data,
+        responseType: 'json',
+        ...options
+      })
+      const returnRes = res.data
+      const { errcode, errmsg, details } = returnRes
+      if (errcode) {
+        console.warn(`[${uri}] fail: ${errcode}-${errmsg} ${details}`)
+        return returnRes
+      }
+      // unwrap data
+      if (this.unwrap) {
+        returnData = returnRes
+      }
+      // 处理apiToken
+      const { apiToken: at, ...others } = returnData
+      if (at) localStorage.setItem('apiToken', at)
+      return others
+    } catch (err) {
+      let errmsg = '接口请求失败,请稍后重试'
+      if (err.response) {
+        const { status } = err.response
+        if (status === 401) errmsg = '用户认证失败,请重新登录'
+        if (status === 403) errmsg = '当前用户不允许执行该操作'
+      }
+      console.error(
+        `[AxiosWrapper] 接口请求失败: ${err.config && err.config.url} - 
+        ${err.message}`
+      )
+      return { errcode: ErrorCode.SERVICE_FAULT, errmsg, details: err.message }
+    } finally {
+      /* eslint-disable */
+      currentRequests -= 1
+      if (currentRequests <= 0) {
+        currentRequests = 0
+        // Indicator.close();
+      }
+    }
+  }
+}

+ 45 - 0
src/utils/base-methods.js

@@ -0,0 +1,45 @@
+import { cloneDeep, get } from 'lodash-es'
+const InitBaseMethods = (store) => {
+  const $checkRes = inject('$checkRes')
+  let limit = inject('limit')
+  const data = ref([])
+  const total = ref(0)
+  const searchForm = ref({})
+  const form = ref({})
+  const b_search = async (query) => {
+    const info = { skip: query.skip, limit: query.limit, ...searchForm.value, is_del: '0' }
+    const res = await store.query(info)
+    if (res.errcode == '0') {
+      data.value = res.data
+      total.value = res.total
+    }
+  }
+  const b_delete = async (data) => {
+    const res = await store.del(data._id)
+    if ($checkRes(res, true)) {
+      b_search({ skip: 0, limit })
+    }
+  }
+  const b_save = async () => {
+    const data = cloneDeep(form.value)
+    let res
+    if (get(data, '_id')) res = await store.update(data)
+    else res = await store.create(data)
+    if ($checkRes(res, true)) {
+      b_search({ skip: 0, limit })
+    }
+  }
+  return {
+    $checkRes,
+    limit,
+    data,
+    total,
+    searchForm,
+    form,
+    b_search,
+    b_delete,
+    b_save
+  }
+}
+
+export default InitBaseMethods

+ 23 - 0
src/utils/checkResult.js

@@ -0,0 +1,23 @@
+import { isFunction, isString } from 'lodash-es'
+import { ElMessage } from 'element-plus'
+export const checkResult = (res, okText, errText) => {
+  const { errcode = 0, errmsg } = res || {}
+  if (errcode === 0) {
+    if (isFunction(okText)) {
+      return okText()
+    }
+    if (isString(okText)) ElMessage.success(okText)
+    else if (okText) ElMessage.success('操作成功')
+    return true
+  }
+  if (isFunction(errText)) {
+    return errText()
+  }
+  ElMessage.error(errText || errmsg)
+  // Message({ message: _errText || errmsg, duration: 60000 });
+  return false
+}
+
+export const InitCheckResult = (app) => {
+  app.provide('$checkRes', checkResult)
+}

+ 61 - 0
src/utils/error-code.js

@@ -0,0 +1,61 @@
+const UNKNOW = 'UNKNOW'
+const BADPARAM = 'BADPARAM'
+const NETWORK = 'NETWORK'
+const JSON_ERROR = 'JSON_ERROR'
+const USER_NOT_EXIST = 'USER_NOT_EXIST'
+const BAD_PASSWORD = 'BAD_PASSWORD'
+const NOT_LOGIN = 'NOT_LOGIN'
+const ACCESS_DENIED = 'ACCESS_DENIED'
+const DATA_NOT_EXIST = 'DATA_NOT_EXIST'
+const DATA_EXISTED = 'DATA_EXISTED'
+const DATA_INVALID = 'DATA_INVALID'
+const VERIFYCODE_INVALID = 'VERIFYCODE_INVALID'
+const SERVICE_FAULT = 'SERVICE_FAULT'
+const DATABASE_FAULT = 'DATABASE_FAULT'
+const FILE_FAULT = 'FILE_FAULT'
+const USER_NOT_BIND = 'USER_NOT_BIND'
+const BUSINESS = 'BUSINESS'
+
+const ErrorCode = {
+  [UNKNOW]: -1,
+  [BADPARAM]: -2,
+  [NETWORK]: -3,
+  [JSON_ERROR]: -4,
+  [USER_NOT_EXIST]: -5,
+  [BAD_PASSWORD]: -6,
+  [NOT_LOGIN]: -7,
+  [ACCESS_DENIED]: -8,
+  [DATA_NOT_EXIST]: -9,
+  [DATA_EXISTED]: -10,
+  [DATA_INVALID]: -11,
+  [VERIFYCODE_INVALID]: -12,
+  [SERVICE_FAULT]: -13,
+  [DATABASE_FAULT]: -14,
+  [FILE_FAULT]: -15,
+  [USER_NOT_BIND]: -16,
+  [BUSINESS]: -100
+}
+
+const errmsg = () => {
+  return {
+    UNKNOW: 'unknow',
+    BADPARAM: 'bad param',
+    NETWORK: 'network',
+    JSON_ERROR: 'json error',
+    USER_NOT_EXIST: 'user not exist',
+    BAD_PASSWORD: 'bad password',
+    NOT_LOGIN: 'not login',
+    ACCESS_DENIED: 'access denied',
+    DATA_NOT_EXIST: 'data not exist',
+    DATA_EXISTED: 'data existed',
+    DATA_INVALID: 'data invalid',
+    VERIFYCODE_INVALID: 'verifycode invalid',
+    SERVICE_FAULT: 'service fault',
+    DATABASE_FAULT: 'database fault',
+    FILE_FAULT: 'file fault',
+    USER_NOT_BIND: 'user not bind',
+    BUSINESS: 'business error'
+  }
+}
+
+export { ErrorCode, errmsg }

+ 26 - 0
src/utils/file.js

@@ -0,0 +1,26 @@
+import Axios from 'axios'
+/**
+ * 上传文件
+ *
+ * @param file
+ */
+export async function uploadFileApi(file) {
+  const formData = new FormData()
+  formData.append('file', file)
+  const axios = Axios.create({
+    baseURL: '/files',
+    withCredentials: true
+  })
+  const token = localStorage.getItem('token')
+  const apiToken = localStorage.getItem('apiToken')
+  if (token) axios.defaults.headers.common['token'] = token
+  if (apiToken) axios.defaults.headers.common['api-token'] = apiToken
+  return await axios.request({
+    url: '/web/cxyy_wangEditor/upload',
+    method: 'post',
+    data: formData,
+    headers: {
+      'Content-Type': 'multipart/form-data'
+    }
+  })
+}

+ 16 - 0
src/utils/rem.js

@@ -0,0 +1,16 @@
+// rem等比适配配置文件
+// 基准大小
+const baseSize = 14
+// 设置 rem 函数
+function setRem() {
+  // 当前页面宽度相对于 1920宽的缩放比例,可根据自己需要修改。
+  const scale = document.documentElement.clientWidth / 1920
+  // 设置页面根节点字体大小(“Math.min(scale, 2)” 指最高放大比例为2,可根据实际业务需求调整)
+  document.documentElement.style.fontSize = baseSize * Math.min(scale, 2) + 'px'
+}
+// 初始化
+setRem()
+// 改变窗口大小时重新设置 `rem`
+window.onresize = function () {
+  setRem()
+}

+ 35 - 0
src/utils/util-methods.js

@@ -0,0 +1,35 @@
+import { isUndefined, isString, isNumber, isBoolean } from 'lodash-es'
+export const trimData = (data, exclude = [], include) => {
+  if (data === null || data === undefined) {
+    return data
+  }
+
+  for (const key in data) {
+    if (isUndefined(data[key]) || (exclude && exclude.indexOf(key) !== -1)) delete data[key]
+    if (include && include.indexOf(key) === -1) delete data[key]
+  }
+  return data
+}
+// 检查email格式
+export const isEmail = function (val) {
+  // 允许汉字、英文字母、数字
+  return /^[A-Za-z0-9\u4e00-\u9fa5]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/.test(val)
+}
+
+// 是否为空
+export const isNullOrUndefined = function (val) {
+  return val === null || val === undefined
+}
+
+// 转换为boolean
+export const toBoolean = function (val) {
+  if (isString(val) && (val.toLowerCase() === 'true' || val.toLowerCase() !== '0')) {
+    val = true
+  } else if (isNumber(val) && val !== 0) {
+    val = true
+  } else {
+    val = isBoolean(val) && val
+  }
+
+  return val
+}

+ 11 - 0
src/utils/variable.js

@@ -0,0 +1,11 @@
+const variable = {
+  limit: 12
+}
+
+export const InitVariable = (app) => {
+  for (const key in variable) {
+    if (Object.hasOwnProperty.call(variable, key)) {
+      app.provide([key], variable[key])
+    }
+  }
+}

+ 135 - 0
src/utils/websocket.js

@@ -0,0 +1,135 @@
+let websock = null
+let messageCallback = null
+let resCallback = null
+let errorCallback = null
+let wsUrl = ''
+let tryTime = 0
+let interval = null
+
+// 接收ws后端返回的数据
+function websocketonmessage(e) {
+  if (e.data instanceof Blob && e.data.size === 0) {
+    //心跳
+    messageCallback(e.data)
+  } else {
+    //返回数据
+    messageCallback(JSON.parse(e.data))
+  }
+}
+
+/**
+ * 发起websocket连接
+ * @param {Object} agentData 需要向后台传递的参数数据
+ */
+function websocketSend(agentData) {
+  // 加延迟是为了尽量让ws连接状态变为OPEN
+  setTimeout(() => {
+    // 添加状态判断,当为OPEN时,发送消息
+    if (websock.readyState === websock.OPEN) {
+      // websock.OPEN = 1
+      // 发给后端的数据需要字符串化
+      if (agentData == 'ping') {
+        //发送心跳
+        const pingMsg = new Uint8Array()
+        websock.send(pingMsg)
+      } else {
+        //发送消息
+        websock.send(JSON.stringify(agentData))
+      }
+    }
+    if (websock.readyState === websock.CLOSED) {
+      // websock.CLOSED = 3
+      console.log('websock.readyState=3', 'ws连接断开')
+      clearInterval(interval)
+      errorCallback()
+    }
+  }, 3000)
+}
+
+//向后端发送消息
+export function websocketSendMess(agentData) {
+  websock.send(JSON.stringify(agentData))
+}
+
+// 关闭ws连接
+function websocketclose(e) {
+  // e.code === 1000  表示正常关闭。 无论为何目的而创建, 该链接都已成功完成任务。
+  // e.code !== 1000  表示非正常关闭。
+  //可以根据code情况判断 是否要重连
+  if (e) {
+    console.log('ws连接异常,请稍候重试')
+    clearInterval(interval)
+    errorCallback()
+    // 如果需要设置异常重连则可替换为下面的代码,自行进行测试
+    //重新连接几次后 是否继续重新 自行判断tryTime
+    setTimeout(function () {
+      websock = null
+      tryTime++
+      sendWebsocket(wsUrl, messageCallback, resCallback, errorCallback)
+      console.log(`第${tryTime}次重连`)
+    }, 3 * 1000)
+  }
+}
+// 建立ws连接
+function websocketOpen(e) {
+  tryTime = 0
+  resCallback(e)
+}
+
+// 初始化weosocket
+function initWebSocket() {
+  if (typeof WebSocket === 'undefined') {
+    console.log('您的浏览器不支持WebSocket,无法获取数据')
+    return false
+  }
+
+  // ws请求完整地址
+  websock = new WebSocket(wsUrl)
+
+  websock.onmessage = function (e) {
+    websocketonmessage(e)
+  }
+  websock.onopen = function () {
+    websocketOpen()
+  }
+  websock.onerror = function () {
+    console.log('ws连接异常,请稍候重试')
+    closeWebsocket()
+    errorCallback()
+  }
+  websock.onclose = function (e) {
+    websocketclose(e)
+  }
+}
+
+/**
+ * 发起websocket请求函数
+ * @param {string} url ws连接地址
+ * @param {function} successCallback 接收到ws数据,对数据进行处理的回调函数
+ * @param {function} errCallback ws连接错误的回调函数
+ * @param {function} resorCallback ws连接成功的回调函数
+ */
+export function sendWebsocket(url, successCallback, errCallback, resorCallback) {
+  wsUrl = url
+  initWebSocket()
+  messageCallback = successCallback
+  resCallback = resorCallback
+  errorCallback = errCallback
+  //   websocketSend(agentData);
+  //保持心跳
+  clearInterval(interval)
+  interval = setInterval(() => {
+    websocketSend('ping')
+  }, 1000 * 5)
+}
+
+/**
+ * 关闭websocket函数
+ */
+export function closeWebsocket() {
+  if (websock) {
+    clearInterval(interval)
+    websock.close() // 关闭websocket
+    websock.onclose() // 关闭websocket
+  }
+}

+ 274 - 0
src/views/home/index.vue

@@ -0,0 +1,274 @@
+<template>
+  <custom-layout v-loading="loading" :is_menu="false">
+    <div class="main w_1300">
+      <div class="info">
+        <el-descriptions title="赛事介绍" size="large" :column="3" border>
+          <el-descriptions-item label="赛事名称">
+            {{ info.name }}
+          </el-descriptions-item>
+          <el-descriptions-item label="赛事类别">
+            {{ getDict(info.form, 'form') || '暂无赛事类别' }}
+          </el-descriptions-item>
+          <el-descriptions-item label="赛事状态">
+            {{ getDict(info.match_status, 'status') || '暂无赛事状态' }}
+          </el-descriptions-item>
+          <el-descriptions-item label="赛事流程状态">
+            {{ getDict(info.ext_status, 'ext_status') || '暂无赛事状态' }}
+          </el-descriptions-item>
+          <el-descriptions-item label="组织单位">
+            {{ info.work || '暂无组织单位' }}
+          </el-descriptions-item>
+        </el-descriptions>
+      </div>
+      <div class="user" v-if="user && user.id">
+        <div class="status">
+          <el-steps :active="parseInt(info.ext_status)" align-center finish-status="success">
+            <el-step v-for="(item, index) in extList" :key="index" :title="item.label" />
+          </el-steps>
+        </div>
+        <div class="content">
+          <one v-if="info.ext_status == '0'" :info="info" @toExamtion="toExamtion" @toExport="toExport"></one>
+          <two v-if="info.ext_status == '1' || info.ext_status == '2' || info.ext_status == '3' || info.ext_status == '4'" :info="info" @toStep2="toStep2" @stepFill="stepFill" @toStep3="toStep3" @toStep4="toStep4" @toExport="toExport" @step4Score="step4Score" @toStep5="toStep5" @toFinals="toFinals"></two>
+          <thr v-if="info.ext_status == '5' || info.ext_status == '6' || info.ext_status == '7' || info.ext_status == '8'" :info="info" @toExport="toExport" @step5Time="step5Time" @toMessage="toMessage"></thr>
+        </div>
+      </div>
+    </div>
+  </custom-layout>
+</template>
+
+<script setup>
+import { get } from 'lodash-es'
+const $checkRes = inject('$checkRes')
+
+import { UserStore } from '@/store/user'
+const userStore = UserStore()
+const user = computed(() => userStore.user)
+// 接口
+import { DictDataStore } from '@/store/api/system/dictData'
+import { MatchStore } from '@/store/api/platform/match'
+import { MatchExtStore } from '@/store/api/platform/matchExt'
+const matchExtStore = MatchExtStore()
+const dictDataStore = DictDataStore()
+const store = MatchStore()
+// 组件
+import one from '../match/one.vue'
+import two from '../match/two.vue'
+import thr from '../match/thr.vue'
+// 加载中
+const loading = ref(false)
+// 路由
+const route = useRoute()
+// 赛事详情
+const info = ref({})
+
+// 字典表
+const statusList = ref([])
+const formList = ref([])
+const extList = ref([])
+const isUseList = ref([])
+// 请求
+onMounted(async () => {
+  loading.value = true
+  await searchOther()
+  await search()
+  loading.value = false
+})
+const searchOther = async () => {
+  let result
+  // 赛事状态
+  result = await dictDataStore.query({ code: 'matchStatus', is_use: '0' })
+  if ($checkRes(result)) statusList.value = result.data
+  // 赛事状态
+  result = await dictDataStore.query({ code: 'matchForm', is_use: '0' })
+  if ($checkRes(result)) formList.value = result.data
+  // 流程状态
+  result = await dictDataStore.query({ code: 'extStatus', is_use: '0' })
+  if ($checkRes(result)) extList.value = result.data
+  // 是否使用
+  result = await dictDataStore.query({ code: 'isUse', is_use: '0' })
+  if ($checkRes(result)) isUseList.value = result.data
+}
+const search = async () => {
+  let id = route.query.id
+  if (id) {
+    let res = await store.fetch(id)
+    if (res.errcode == '0') {
+      info.value = res.data
+    }
+  }
+}
+// 字典数据转换
+const getDict = (data, model) => {
+  if (data) {
+    let res
+    if (model == 'form') res = formList.value.find((f) => f.value == data)
+    else if (model == 'status') res = statusList.value.find((f) => f.value == data)
+    else if (model == 'ext_status') res = extList.value.find((f) => f.value == data)
+    return get(res, 'label')
+  }
+}
+// 结束报名
+const toExamtion = () => {
+  ElMessageBox.confirm('是否确定结束该赛事的报名?', '报名信息', {
+    confirmButtonText: '确认',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(async () => {
+      const res = await matchExtStore.step1({ match_id: route.query.id })
+      if ($checkRes(res, true)) {
+        search()
+      }
+    })
+    .catch(() => {})
+}
+// 组织初审
+const toStep2 = () => {
+  ElMessageBox.confirm('是否确定进入组织初审阶段?', '组织初审', {
+    confirmButtonText: '确认',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(async () => {
+      const res = await matchExtStore.step2({ match_id: route.query.id })
+      if ($checkRes(res, true)) {
+        search()
+      }
+    })
+    .catch(() => {})
+}
+// 补充初审时间
+const stepFill = (data) => {
+  ElMessageBox.confirm('是否确定补充选择用户的初审时间?', '组织初审', {
+    confirmButtonText: '确认',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(async () => {
+      const res = await matchExtStore.stepFill(data)
+      if ($checkRes(res, true)) {
+        search()
+      }
+    })
+    .catch(() => {})
+}
+// 公示名单
+const toStep3 = () => {
+  ElMessageBox.confirm('是否确定进入初审阶段公示名单?', '公示名单', {
+    confirmButtonText: '确认',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(async () => {
+      const res = await matchExtStore.step3({ match_id: route.query.id })
+      if ($checkRes(res, true)) {
+        search()
+      }
+    })
+    .catch(() => {})
+}
+// 初审阶段-赛事进行
+const toStep4 = () => {
+  ElMessageBox.confirm('是否确定进入初审阶段赛事进行?', '赛事进行', {
+    confirmButtonText: '确认',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(async () => {
+      const res = await matchExtStore.step4({ match_id: route.query.id })
+      if ($checkRes(res, true)) {
+        search()
+      }
+    })
+    .catch(() => {})
+}
+// 导出
+const toExport = (selectionList) => {
+  console.log(selectionList)
+}
+// 上传初审分数
+const step4Score = async (data) => {
+  const res = await matchExtStore.step4Score(data)
+  if ($checkRes(res, true)) {
+    search()
+  }
+}
+// 进入决赛的名单
+const toFinals = async (data) => {
+  ElMessageBox.confirm('是否确定选择改报名信息进入决赛名单?', '决赛名单', {
+    confirmButtonText: '确认',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(async () => {
+      const res = await matchExtStore.step4To5(data)
+      if ($checkRes(res, true)) {
+        search()
+      }
+    })
+    .catch(() => {})
+}
+// 决赛组织阶段
+const toStep5 = async () => {
+  ElMessageBox.confirm('是否确定进入决赛组织阶段?', '赛事进行', {
+    confirmButtonText: '确认',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(async () => {
+      const res = await matchExtStore.step5({ match_id: route.query.id })
+      if ($checkRes(res, true)) {
+        search()
+      }
+    })
+    .catch(() => {})
+}
+// 补充决赛时间
+const step5Time = (data) => {
+  ElMessageBox.confirm('是否确定补充选择用户的决赛时间?', '决赛时间', {
+    confirmButtonText: '确认',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(async () => {
+      const res = await matchExtStore.step5Time(data)
+      if ($checkRes(res, true)) {
+        search()
+      }
+    })
+    .catch(() => {})
+}
+// 发送消息提示
+const toMessage = () => {
+  ElMessageBox.confirm('是否确定对已经设置决赛时间的报名用户发送消息提示?', '发送消息提示', {
+    confirmButtonText: '确认',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(async () => {
+      let id = route.query.id
+      const res = await matchExtStore.step5Sms(id)
+      if ($checkRes(res, true)) {
+        search()
+      }
+    })
+    .catch(() => {})
+}
+provide('extList', extList)
+provide('isUseList', isUseList)
+</script>
+<style scoped lang="scss">
+.main {
+  margin: 10px auto;
+  .info {
+    margin: 20px 0;
+  }
+  .user {
+    .status {
+      margin: 50px 0;
+    }
+    .content {
+    }
+  }
+}
+</style>

+ 284 - 0
src/views/match/one.vue

@@ -0,0 +1,284 @@
+<template>
+  <div class="main" v-loading="loading">
+    <el-col :span="24" class="one">
+      <div class="one_left" @click="toExport">导出</div>
+      <div class="warn one_left" @click="toExamtion" v-if="info.match_status == '1'">结束报名</div>
+    </el-col>
+    <el-col :span="24" class="two">
+      <el-table :data="list" style="width: 100%" size="large" :header-cell-style="{ backgroundColor: '#edf3ff' }" @selection-change="handleSelectionChange">
+        <template #empty>
+          <el-empty description="暂无数据" />
+        </template>
+        <el-table-column type="selection" width="55"> </el-table-column>
+        <el-table-column prop="no" align="center" label="编号" width="100"> </el-table-column>
+        <el-table-column prop="user_name" align="center" label="用户"> </el-table-column>
+        <el-table-column prop="time" align="center" label="报名时间" />
+        <el-table-column prop="ext_status" align="center" label="流程状态">
+          <template #default="scope">
+            {{ getDict(scope.row.ext_status, 'ext_status') || '暂无' }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="status" align="center" label="状态" width="100">
+          <template #default="scope">
+            <el-tag v-if="scope.row.status == '0'" type="primary">待审核</el-tag>
+            <el-tag v-else-if="scope.row.status == '1'" type="success">已通过</el-tag>
+            <el-tag v-else type="info">已退回</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column align="center" label="操作" width="160">
+          <template #default="{ row }">
+            <el-link :underline="false" type="primary" size="mini" @click="toView(row, true)" style="margin-right: 10px">查看</el-link>
+            <el-link v-if="row.status == '0' && row.ext_status == '0'" :underline="false" type="warning" size="mini" @click="toView(row, false)" style="margin-right: 10px">报名审核</el-link>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-col>
+    <el-col :span="24" class="thr">
+      <el-pagination background layout="prev, pager, next" :total="total" :page-size="limit" v-model:current-page="currentPage" @current-change="changePage" @size-change="sizeChange" />
+    </el-col>
+    <el-dialog v-model="dialog.show" :title="dialog.title" :destroy-on-close="false" @close="toClose">
+      <el-descriptions title="" :column="1" border>
+        <template #extra v-if="!is_look">
+          <el-button type="primary" @click="toExam('1')">通过申请</el-button>
+          <el-button type="danger" @click="toExam('2')">退回申请</el-button>
+        </template>
+        <el-descriptions-item v-for="(item, index) in extInfo" :key="index" :label="item.problem">
+          <div class="type" v-if="item.type == '0' || item.type == '2' || item.type == '3' || item.type == '4' || item.type == '5'">{{ item.reply || '暂无内容' }}</div>
+          <div class="type" v-if="item.type == '1' && item.reply">{{ item.reply.join(',') }}</div>
+          <div class="type" v-if="item.type == '6'">
+            <div v-for="(as, img) in item.reply" :key="img">
+              <el-link :href="getUrl(as)" target="_blank">{{ as.name }}</el-link>
+            </div>
+          </div>
+          <div class="type" v-if="item.type == '7'">
+            <div class="list">
+              <div v-for="(aa, ina) in item.answer" :key="ina" class="name">{{ aa.text }}</div>
+            </div>
+            <div class="list" v-for="(gg, inx) in item.reply" :key="inx">
+              <div v-for="(aa, ina) in item.answer" :key="ina" class="input">
+                {{ gg[aa.text] }}
+              </div>
+            </div>
+          </div>
+          <div class="type" v-if="item.type == '8'">
+            <div v-for="(as, img) in item.answer" :key="img">
+              <el-link :href="getUrl(as)" target="_blank">{{ as.name }}</el-link>
+            </div>
+          </div>
+        </el-descriptions-item>
+        <el-descriptions-item v-if="is_look" label="初审时间">{{ form.start_time }}</el-descriptions-item>
+        <el-descriptions-item v-if="is_look" label="初审分数">{{ form.score }}</el-descriptions-item>
+        <el-descriptions-item v-if="is_look" label="是否进入决赛">{{ getDict(form.final_confirm, 'final_confirm') || '暂无' }}</el-descriptions-item>
+        <el-descriptions-item v-if="is_look" label="决赛时间">{{ form.final_start_time }}</el-descriptions-item>
+        <el-descriptions-item v-if="is_look" label="决赛分数">{{ form.final_score }}</el-descriptions-item>
+        <el-descriptions-item v-if="is_look" label="流程状态">{{ getDict(form.ext_status, 'ext_status') || '暂无' }}</el-descriptions-item>
+      </el-descriptions>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { get } from 'lodash-es'
+const $checkRes = inject('$checkRes')
+// 加载中
+const loading = ref(false)
+const id = ref('')
+const props = defineProps({
+  info: { type: Object }
+})
+const match = computed({
+  get() {
+    return props.info
+  }
+})
+// 接口
+import { MatchRegStore } from '@/store/api/platform/matchReg'
+const store = MatchRegStore()
+
+// 列表
+const list = ref([])
+let skip = 0
+let limit = inject('limit')
+const total = ref(0)
+const currentPage = ref(1)
+
+const extInfo = ref([])
+
+const form = ref({})
+const dialog = ref({ type: '1', show: false, title: '报名信息' })
+
+// 流程状态字典表
+const extList = inject('extList')
+const isUseList = inject('isUseList')
+
+// 批量导出
+const selectionList = ref([])
+const is_look = ref(false)
+
+const emits = defineEmits(['toExamtion', 'toExport'])
+
+const search = async (query = { skip, limit }) => {
+  skip = query.skip
+  limit = query.limit
+  const info = {
+    skip: query.skip,
+    limit: query.limit,
+    match_id: id.value
+  }
+  const res = await store.query(info)
+  if (res.errcode == '0') {
+    list.value = res.data
+    total.value = res.total
+  }
+}
+// 字典数据转换
+const getDict = (data, model) => {
+  if (data) {
+    let res
+    if (model == 'ext_status') res = extList.value.find((f) => f.value == data)
+    if (model == 'final_confirm') res = isUseList.value.find((f) => f.value == data)
+    return get(res, 'label')
+  }
+}
+const handleSelectionChange = (val) => {
+  selectionList.value = val
+}
+// 审核
+const toView = (data, is_no) => {
+  is_look.value = is_no
+  form.value = data
+  extInfo.value = data.info
+  dialog.value = { type: '1', show: true, title: '报名信息' }
+}
+// 图片处理
+const getUrl = (e) => {
+  if (e) return `${import.meta.env.VITE_APP_HOST}${get(e, 'uri')}`
+}
+// 审核
+const toExam = (status) => {
+  let title
+  if (status == '1') title = '确定将该通过该申请的报名信息?'
+  else title = '确定将退回该申请的报名信息?'
+  ElMessageBox.confirm(title, '审核信息', {
+    confirmButtonText: '确认',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(async () => {
+      const data = form.value
+      const res = await store.update({ id: data.id, status })
+      if ($checkRes(res, true)) {
+        toClose()
+      }
+    })
+    .catch(() => {})
+}
+// 结束报名
+const toExamtion = () => {
+  emits('toExamtion')
+}
+// 分页
+const changePage = (page = currentPage.value) => {
+  search({ skip: (page - 1) * limit, limit: limit })
+}
+const sizeChange = (limits) => {
+  limit = limits
+  currentPage.value = 1
+  search({ skip: 0, limit: limit })
+}
+// 导出
+const toExport = () => {
+  if (selectionList.value && selectionList.value.length > 0) {
+    emits('toExport', selectionList)
+  } else {
+    ElMessage({
+      message: '请选择要导出的数据!',
+      type: 'warning'
+    })
+  }
+}
+const toClose = async () => {
+  is_look.value = false
+  form.value = {}
+  dialog.value = { show: false }
+  await search({ skip, limit })
+}
+watch(
+  match,
+  async (item) => {
+    if (item.id) {
+      id.value = item.id
+      loading.value = true
+      await search({ skip, limit })
+      loading.value = false
+    }
+  },
+  {
+    immediate: true //初始化立即执行
+  }
+)
+</script>
+<style scoped lang="scss">
+.main {
+  .one {
+    height: 50px;
+    display: flex;
+    align-items: center;
+    margin: 0 0 10px 0;
+    .one_left {
+      margin: 0 10px 0 0;
+      background: #1875df;
+      padding: 0 10px;
+      height: 30px;
+      color: #fff;
+      line-height: 30px;
+      text-align: center;
+      font-size: 16px;
+      cursor: default;
+    }
+    .warn {
+      background: #f56c6c;
+    }
+  }
+  .thr {
+    display: flex;
+    justify-content: center;
+    margin: 20px 0 0 0;
+  }
+  .type {
+    .image {
+      width: 100%;
+    }
+  }
+
+  .list {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin: 10px 0;
+
+    .name {
+      width: 50%;
+      text-align: center;
+    }
+
+    .input {
+      width: 50%;
+      margin: 0 5px 0 0;
+      border: 1px solid #e5e5e5;
+      border-radius: 5px;
+      text-align: center;
+
+      .name {
+        width: 50%;
+        text-align: center;
+        margin: 0 0 10px 0;
+      }
+    }
+  }
+  :deep(.el-descriptions__body .el-descriptions__table.is-bordered .el-descriptions__cell) {
+    text-align: center;
+    width: 10px !important; /* 设置你想要的宽度 */
+  }
+}
+</style>

+ 354 - 0
src/views/match/thr.vue

@@ -0,0 +1,354 @@
+<template>
+  <div class="main">
+    <el-row class="one" v-if="info.ext_status == '5'">
+      <el-col :span="10">
+        <el-form ref="ruleFormRef" :model="timeForm" :rules="rules" label-width="auto" class="form" label-position="left">
+          <el-form-item label="设置决赛时间" prop="start_time">
+            <el-date-picker format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss" v-model="timeForm.start_time" type="datetime" placeholder="请选择决赛时间" style="width: 50%" />
+          </el-form-item>
+        </el-form>
+      </el-col>
+      <el-col :span="14">
+        <el-button type="primary" :disabled="selectionList && selectionList.length > 0 ? false : true" v-if="info.ext_status == '5'" @click="submitForm(ruleFormRef)">补充决赛时间</el-button>
+      </el-col>
+      <el-col :span="24">
+        <el-alert title="请设置决赛时间并选择要填写决赛时间的用户!" type="warning" :closable="false" />
+      </el-col>
+    </el-row>
+    <el-col :span="14" class="export">
+      <div class="one_left" @click="toExport">导出</div>
+      <div class="one_left" @click="toMessage">对已经设置决赛时间的报名用户发送消息提示</div>
+    </el-col>
+    <el-col :span="24" class="two">
+      <el-table :data="list" style="width: 100%" size="large" :header-cell-style="{ backgroundColor: '#edf3ff' }" @selection-change="handleSelectionChange">
+        <template #empty>
+          <el-empty description="暂无数据" />
+        </template>
+        <el-table-column type="selection" width="55"> </el-table-column>
+        <el-table-column prop="no" align="center" label="编号" width="100"> </el-table-column>
+        <el-table-column prop="user_name" align="center" label="用户"> </el-table-column>
+        <el-table-column prop="time" align="center" label="报名时间" />
+        <el-table-column prop="start_time" align="center" label="初审时间"> </el-table-column>
+        <el-table-column prop="score" align="center" label="初审分数"> </el-table-column>
+        <el-table-column prop="final_start_time" align="center" label="决赛时间"> </el-table-column>
+        <el-table-column prop="ext_status" align="center" label="流程状态">
+          <template #default="scope">
+            {{ getDict(scope.row.ext_status, 'ext_status') || '暂无' }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="final_confirm" align="center" label="是否参加决赛">
+          <template #default="scope">
+            {{ getDict(scope.row.final_confirm, 'final_confirm') || '暂无' }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="status" align="center" label="状态" width="100">
+          <template #default="scope">
+            <el-tag v-if="scope.row.status == '0'" type="primary">待审核</el-tag>
+            <el-tag v-else-if="scope.row.status == '1'" type="success">已通过</el-tag>
+            <el-tag v-else type="info">已退回</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column align="center" label="操作" width="160">
+          <template #default="{ row }">
+            <el-link :underline="false" type="primary" size="mini" @click="toView(row, true)" style="margin-right: 10px">查看</el-link>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-col>
+    <el-col :span="24" class="thr">
+      <el-pagination background layout="prev, pager, next" :total="total" :page-size="limit" v-model:current-page="currentPage" @current-change="changePage" @size-change="sizeChange" />
+    </el-col>
+    <el-col :span="24" class="button">
+      <el-button type="primary" @click="toStep6">进入决赛名单公示阶段</el-button>
+    </el-col>
+    <el-dialog v-model="dialog.show" :title="dialog.title" :destroy-on-close="false" @close="toClose">
+      <div v-if="dialog.type == '1'">
+        <el-descriptions title="" :column="1" border>
+          <el-descriptions-item v-for="(item, index) in extInfo" :key="index" :label="item.problem">
+            <div class="type" v-if="item.type == '0' || item.type == '2' || item.type == '3' || item.type == '4' || item.type == '5'">{{ item.reply || '暂无内容' }}</div>
+            <div class="type" v-if="item.type == '1' && item.reply">{{ item.reply.join(',') }}</div>
+            <div class="type" v-if="item.type == '6'">
+              <div v-for="(as, img) in item.reply" :key="img">
+                <el-link :href="getUrl(as)" target="_blank">{{ as.name }}</el-link>
+              </div>
+            </div>
+            <div class="type" v-if="item.type == '7'">
+              <div class="list">
+                <div v-for="(aa, ina) in item.answer" :key="ina" class="name">{{ aa.text }}</div>
+              </div>
+              <div class="list" v-for="(gg, inx) in item.reply" :key="inx">
+                <div v-for="(aa, ina) in item.answer" :key="ina" class="input">
+                  {{ gg[aa.text] }}
+                </div>
+              </div>
+            </div>
+            <div class="type" v-if="item.type == '8'">
+              <div v-for="(as, img) in item.answer" :key="img">
+                <el-link :href="getUrl(as)" target="_blank">{{ as.name }}</el-link>
+              </div>
+            </div>
+          </el-descriptions-item>
+          <el-descriptions-item v-if="is_look" label="初审时间">{{ form.start_time }}</el-descriptions-item>
+          <el-descriptions-item v-if="is_look" label="初审分数">{{ form.score }}</el-descriptions-item>
+          <el-descriptions-item v-if="is_look" label="是否进入决赛">{{ getDict(form.final_confirm, 'final_confirm') || '暂无' }}</el-descriptions-item>
+          <el-descriptions-item v-if="is_look" label="决赛时间">{{ form.final_start_time }}</el-descriptions-item>
+          <el-descriptions-item v-if="is_look" label="决赛分数">{{ form.final_score }}</el-descriptions-item>
+          <el-descriptions-item v-if="is_look" label="流程状态">{{ getDict(form.ext_status, 'ext_status') || '暂无' }}</el-descriptions-item>
+        </el-descriptions>
+      </div>
+      <div v-else-if="dialog.type == '2'">
+        <el-form ref="scoreFormRef" :model="scoreForm" :rules="scoreRules" label-width="auto" class="form" label-position="left">
+          <el-form-item label="决赛总分数" prop="final_score">
+            <el-input v-model="scoreForm.final_score" type="number" placeholder="请输入决赛总分数" />
+          </el-form-item>
+          <div style="text-align: center">
+            <el-button type="primary" @click="onSubmit(scoreFormRef)">保存初审分数</el-button>
+          </div>
+        </el-form>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+<script setup>
+import { get } from 'lodash-es'
+const id = ref('')
+const props = defineProps({
+  info: { type: Object }
+})
+const match = computed({
+  get() {
+    return props.info
+  }
+})
+// 接口
+import { MatchRegStore } from '@/store/api/platform/matchReg'
+const store = MatchRegStore()
+
+// 加载中
+const loading = ref(false)
+
+const timeForm = ref({})
+// 表单
+const ruleFormRef = ref()
+const rules = reactive({
+  start_time: [{ required: true, message: '请选择决赛时间', trigger: 'blur' }]
+})
+// 列表
+const list = ref([])
+let skip = 0
+let limit = inject('limit')
+const total = ref(0)
+const currentPage = ref(1)
+
+const extInfo = ref([])
+
+const form = ref({})
+const dialog = ref({ type: '1', show: false, title: '报名信息' })
+
+// 流程状态字典表
+const extList = inject('extList')
+const isUseList = inject('isUseList')
+
+// 批量导出
+const selectionList = ref([])
+const is_look = ref(false)
+
+// 上传分数
+const scoreForm = ref({})
+// 表单
+const scoreFormRef = ref()
+const scoreRules = reactive({
+  score: [{ required: true, message: '请输入初审分数', trigger: 'blur' }]
+})
+
+const emits = defineEmits(['step5Time', 'toExport', 'toMessage'])
+
+const search = async (query = { skip, limit }) => {
+  skip = query.skip
+  limit = query.limit
+  const data = {
+    skip: query.skip,
+    limit: query.limit,
+    match_id: id.value,
+    status: '0'
+  }
+  const res = await store.query(data)
+  if (res.errcode == '0') {
+    list.value = res.data
+    total.value = res.total
+  }
+}
+// 字典数据转换
+const getDict = (data, model) => {
+  if (data) {
+    let res
+    if (model == 'ext_status') res = extList.value.find((f) => f.value == data)
+    if (model == 'final_confirm') res = isUseList.value.find((f) => f.value == data)
+    return get(res, 'label')
+  }
+}
+const handleSelectionChange = (val) => {
+  selectionList.value = val
+}
+// 补充决赛时间
+const submitForm = async (formEl) => {
+  if (!formEl) return
+  await formEl.validate(async (valid, fields) => {
+    if (valid) {
+      const data = {
+        start_time: timeForm.value.start_time,
+        id: id.value,
+        ids: selectionList.value.map((i) => {
+          return i.id
+        })
+      }
+      emits('step5Time', data)
+    } else {
+      console.log('error submit!', fields)
+    }
+  })
+}
+// 审核
+const toView = (data, is_no) => {
+  is_look.value = is_no
+  form.value = data
+  extInfo.value = data.info
+  dialog.value = { type: '1', show: true, title: '报名信息' }
+}
+// 上传决赛分数
+const toScore = (data) => {
+  scoreForm.value = data
+  dialog.value = { type: '2', show: true, title: '上传决赛分数' }
+}
+// 图片处理
+const getUrl = (e) => {
+  if (e) return `${import.meta.env.VITE_APP_HOST}${get(e, 'uri')}`
+}
+
+// 保存初审分数
+const onSubmit = async (formEl) => {
+  if (!formEl) return
+  await formEl.validate((valid, fields) => {
+    if (valid) {
+      const data = { id: id.value, reg_id: scoreForm.value.id, score: parseInt(scoreForm.value.score) }
+      emits('step4Score', data)
+      toClose()
+    } else {
+      console.log('error submit!', fields)
+    }
+  })
+}
+// 分页
+const changePage = (page = currentPage.value) => {
+  search({ skip: (page - 1) * limit, limit: limit })
+}
+const sizeChange = (limits) => {
+  limit = limits
+  currentPage.value = 1
+  search({ skip: 0, limit: limit })
+}
+// 导出
+const toExport = () => {
+  if (selectionList.value && selectionList.value.length > 0) {
+    emits('toExport', selectionList)
+  } else {
+    ElMessage({
+      message: '请选择要导出的数据!',
+      type: 'warning'
+    })
+  }
+}
+// 发送短信提醒
+const toMessage = () => {
+  emits('toMessage')
+}
+const toClose = async () => {
+  is_look.value = false
+  form.value = {}
+  scoreForm.value = {}
+  dialog.value = { show: false }
+  await search({ skip, limit })
+}
+watch(
+  match,
+  async (item) => {
+    if (item.id) {
+      id.value = item.id
+      loading.value = true
+      await search({ skip, limit })
+      loading.value = false
+    }
+  },
+  {
+    immediate: true //初始化立即执行
+  }
+)
+</script>
+<style scoped lang="scss">
+.main {
+  .export {
+    display: flex;
+    justify-content: flex-start;
+    margin: 10px 0;
+    .one_left {
+      margin: 0 10px 0 0;
+      background: #1875df;
+      padding: 0 10px;
+      height: 30px;
+      color: #fff;
+      line-height: 30px;
+      text-align: center;
+      font-size: 16px;
+      cursor: default;
+    }
+  }
+  .thr {
+    display: flex;
+    justify-content: center;
+    margin: 20px 0 0 0;
+  }
+  .button {
+    margin: 20px 0 0 0;
+    text-align: center;
+    button {
+      width: 400px;
+      height: 40px;
+    }
+  }
+  .type {
+    .image {
+      width: 100%;
+    }
+  }
+
+  .list {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin: 10px 0;
+
+    .name {
+      width: 50%;
+      text-align: center;
+    }
+
+    .input {
+      width: 50%;
+      margin: 0 5px 0 0;
+      border: 1px solid #e5e5e5;
+      border-radius: 5px;
+      text-align: center;
+
+      .name {
+        width: 50%;
+        text-align: center;
+        margin: 0 0 10px 0;
+      }
+    }
+  }
+  :deep(.el-descriptions__body .el-descriptions__table.is-bordered .el-descriptions__cell) {
+    text-align: center;
+    width: 10px !important; /* 设置你想要的宽度 */
+  }
+}
+</style>

+ 383 - 0
src/views/match/two.vue

@@ -0,0 +1,383 @@
+<template>
+  <div class="main">
+    <el-row class="one" v-if="info.ext_status == '2'">
+      <el-col :span="10">
+        <el-form ref="ruleFormRef" :model="timeForm" :rules="rules" label-width="auto" class="form" label-position="left">
+          <el-form-item label="设置初审时间" prop="start_time">
+            <el-date-picker format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss" v-model="timeForm.start_time" type="datetime" placeholder="请选择初审时间" style="width: 50%" />
+          </el-form-item>
+        </el-form>
+      </el-col>
+      <el-col :span="14">
+        <el-button type="primary" :disabled="selectionList && selectionList.length > 0 ? false : true" v-if="info.ext_status == '2'" @click="submitForm(ruleFormRef)">补充初审时间</el-button>
+      </el-col>
+      <el-col :span="24">
+        <el-alert title="请设置初审时间并选择要填写初审时间的用户!" type="warning" :closable="false" />
+      </el-col>
+    </el-row>
+    <el-col :span="6" class="export">
+      <div class="one_left" @click="toExport">导出</div>
+      <div class="one_left" :disabled="selectionList && selectionList.length > 0 ? false : true" @click="toFinals">选择决赛名单</div>
+    </el-col>
+    <el-col :span="24" class="two">
+      <el-table :data="list" style="width: 100%" size="large" :header-cell-style="{ backgroundColor: '#edf3ff' }" @selection-change="handleSelectionChange">
+        <template #empty>
+          <el-empty description="暂无数据" />
+        </template>
+        <el-table-column type="selection" width="55"> </el-table-column>
+        <el-table-column prop="no" align="center" label="编号" width="100"> </el-table-column>
+        <el-table-column prop="user_name" align="center" label="用户"> </el-table-column>
+        <el-table-column prop="time" align="center" label="报名时间" />
+        <el-table-column prop="start_time" align="center" v-if="info.ext_status !== '0' || info.ext_status == '1'" label="初审时间"> </el-table-column>
+        <el-table-column prop="score" align="center" label="初审分数" v-if="info.ext_status !== '0' || info.ext_status == '1' || info.ext_status == '2' || info.ext_status == '3'"> </el-table-column>
+        <el-table-column prop="ext_status" align="center" label="流程状态">
+          <template #default="scope">
+            {{ getDict(scope.row.ext_status, 'ext_status') || '暂无' }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="status" align="center" label="状态" width="100">
+          <template #default="scope">
+            <el-tag v-if="scope.row.status == '0'" type="primary">待审核</el-tag>
+            <el-tag v-else-if="scope.row.status == '1'" type="success">已通过</el-tag>
+            <el-tag v-else type="info">已退回</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column align="center" label="操作" width="160">
+          <template #default="{ row }">
+            <el-link :underline="false" type="primary" size="mini" @click="toView(row, true)" style="margin-right: 10px">查看</el-link>
+            <el-link :underline="false" v-if="info.ext_status == '4'" type="primary" size="mini" @click="toScore(row)">上传初审分数</el-link>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-col>
+    <el-col :span="24" class="thr">
+      <el-pagination background layout="prev, pager, next" :total="total" :page-size="limit" v-model:current-page="currentPage" @current-change="changePage" @size-change="sizeChange" />
+    </el-col>
+    <el-col :span="24" class="button">
+      <el-button type="primary" v-if="info.ext_status == '1'" @click="toStep2">进入组织初审阶段</el-button>
+      <el-button type="primary" v-if="info.ext_status == '2'" @click="toStep3">进入初审公示名单阶段</el-button>
+      <el-button type="primary" v-if="info.ext_status == '3'" @click="toStep4">进入初审赛事进行阶段</el-button>
+      <el-button type="primary" v-if="info.ext_status == '4'" @click="toStep5">进入决赛组织决赛阶段</el-button>
+    </el-col>
+    <el-dialog v-model="dialog.show" :title="dialog.title" :destroy-on-close="false" @close="toClose">
+      <div v-if="dialog.type == '1'">
+        <el-descriptions title="" :column="1" border>
+          <el-descriptions-item v-for="(item, index) in extInfo" :key="index" :label="item.problem">
+            <div class="type" v-if="item.type == '0' || item.type == '2' || item.type == '3' || item.type == '4' || item.type == '5'">{{ item.reply || '暂无内容' }}</div>
+            <div class="type" v-if="item.type == '1' && item.reply">{{ item.reply.join(',') }}</div>
+            <div class="type" v-if="item.type == '6'">
+              <div v-for="(as, img) in item.reply" :key="img">
+                <el-link :href="getUrl(as)" target="_blank">{{ as.name }}</el-link>
+              </div>
+            </div>
+            <div class="type" v-if="item.type == '7'">
+              <div class="list">
+                <div v-for="(aa, ina) in item.answer" :key="ina" class="name">{{ aa.text }}</div>
+              </div>
+              <div class="list" v-for="(gg, inx) in item.reply" :key="inx">
+                <div v-for="(aa, ina) in item.answer" :key="ina" class="input">
+                  {{ gg[aa.text] }}
+                </div>
+              </div>
+            </div>
+            <div class="type" v-if="item.type == '8'">
+              <div v-for="(as, img) in item.answer" :key="img">
+                <el-link :href="getUrl(as)" target="_blank">{{ as.name }}</el-link>
+              </div>
+            </div>
+          </el-descriptions-item>
+          <el-descriptions-item v-if="is_look" label="初审时间">{{ form.start_time }}</el-descriptions-item>
+          <el-descriptions-item v-if="is_look" label="初审分数">{{ form.score }}</el-descriptions-item>
+          <el-descriptions-item v-if="is_look" label="是否进入决赛">{{ getDict(form.final_confirm, 'final_confirm') || '暂无' }}</el-descriptions-item>
+          <el-descriptions-item v-if="is_look" label="决赛时间">{{ form.final_start_time }}</el-descriptions-item>
+          <el-descriptions-item v-if="is_look" label="决赛分数">{{ form.final_score }}</el-descriptions-item>
+          <el-descriptions-item v-if="is_look" label="流程状态">{{ getDict(form.ext_status, 'ext_status') || '暂无' }}</el-descriptions-item>
+        </el-descriptions>
+      </div>
+      <div v-else-if="dialog.type == '2'">
+        <el-form ref="scoreFormRef" :model="scoreForm" :rules="scoreRules" label-width="auto" class="form" label-position="left">
+          <el-form-item label="初审分数" prop="score">
+            <el-input v-model="scoreForm.score" type="number" placeholder="请输入初审分数" />
+          </el-form-item>
+          <div style="text-align: center">
+            <el-button type="primary" @click="onSubmit(scoreFormRef)">保存初审分数</el-button>
+          </div>
+        </el-form>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+<script setup>
+import { get } from 'lodash-es'
+const id = ref('')
+const props = defineProps({
+  info: { type: Object }
+})
+const match = computed({
+  get() {
+    return props.info
+  }
+})
+// 接口
+import { MatchRegStore } from '@/store/api/platform/matchReg'
+const store = MatchRegStore()
+
+// 加载中
+const loading = ref(false)
+
+const timeForm = ref({})
+// 表单
+const ruleFormRef = ref()
+const rules = reactive({
+  start_time: [{ required: true, message: '请选择初审时间', trigger: 'blur' }]
+})
+// 列表
+const list = ref([])
+let skip = 0
+let limit = inject('limit')
+const total = ref(0)
+const currentPage = ref(1)
+
+const extInfo = ref([])
+
+const form = ref({})
+const dialog = ref({ type: '1', show: false, title: '报名信息' })
+
+// 流程状态字典表
+const extList = inject('extList')
+const isUseList = inject('isUseList')
+
+// 批量导出
+const selectionList = ref([])
+const is_look = ref(false)
+
+// 上传分数
+const scoreForm = ref({})
+// 表单
+const scoreFormRef = ref()
+const scoreRules = reactive({
+  score: [{ required: true, message: '请输入初审分数', trigger: 'blur' }]
+})
+
+const emits = defineEmits(['toStep2', 'stepFill', 'toExport', 'toStep3', 'toStep4', 'step4Score', 'toFinals', 'toStep5'])
+
+const search = async (query = { skip, limit }) => {
+  skip = query.skip
+  limit = query.limit
+  const data = {
+    skip: query.skip,
+    limit: query.limit,
+    match_id: id.value,
+    ext_status: match.value.ext_status
+  }
+  if (match.value.ext_status == '2' || match.value.ext_status == '3' || match.value.ext_status == '4') {
+    data.status = '0'
+  }
+  const res = await store.query(data)
+  if (res.errcode == '0') {
+    list.value = res.data
+    total.value = res.total
+  }
+}
+// 字典数据转换
+const getDict = (data, model) => {
+  if (data) {
+    let res
+    if (model == 'ext_status') res = extList.value.find((f) => f.value == data)
+    if (model == 'final_confirm') res = isUseList.value.find((f) => f.value == data)
+    return get(res, 'label')
+  }
+}
+const handleSelectionChange = (val) => {
+  selectionList.value = val
+}
+// 补充初审时间
+const submitForm = async (formEl) => {
+  if (!formEl) return
+  await formEl.validate(async (valid, fields) => {
+    if (valid) {
+      const data = {
+        start_time: timeForm.value.start_time,
+        match_id: id.value,
+        ids: selectionList.value.map((i) => {
+          return i.id
+        })
+      }
+      emits('stepFill', data)
+    } else {
+      console.log('error submit!', fields)
+    }
+  })
+}
+// 审核
+const toView = (data, is_no) => {
+  is_look.value = is_no
+  form.value = data
+  extInfo.value = data.info
+  dialog.value = { type: '1', show: true, title: '报名信息' }
+}
+// 上传初审分数
+const toScore = (data) => {
+  scoreForm.value = data
+  dialog.value = { type: '2', show: true, title: '上传初审分数' }
+}
+// 图片处理
+const getUrl = (e) => {
+  if (e) return `${import.meta.env.VITE_APP_HOST}${get(e, 'uri')}`
+}
+// 组织初审
+const toStep2 = () => {
+  emits('toStep2')
+}
+// 公示公告
+const toStep3 = () => {
+  emits('toStep3')
+}
+// 赛事进行
+const toStep4 = () => {
+  emits('toStep4')
+}
+// 决赛组织决赛
+const toStep5 = () => {
+  emits('toStep5')
+}
+// 保存初审分数
+const onSubmit = async (formEl) => {
+  if (!formEl) return
+  await formEl.validate((valid, fields) => {
+    if (valid) {
+      const data = { id: id.value, reg_id: scoreForm.value.id, score: parseInt(scoreForm.value.score) }
+      emits('step4Score', data)
+      toClose()
+    } else {
+      console.log('error submit!', fields)
+    }
+  })
+}
+// 分页
+const changePage = (page = currentPage.value) => {
+  search({ skip: (page - 1) * limit, limit: limit })
+}
+const sizeChange = (limits) => {
+  limit = limits
+  currentPage.value = 1
+  search({ skip: 0, limit: limit })
+}
+// 导出
+const toExport = () => {
+  if (selectionList.value && selectionList.value.length > 0) {
+    emits('toExport', selectionList)
+  } else {
+    ElMessage({
+      message: '请选择要导出的数据!',
+      type: 'warning'
+    })
+  }
+}
+// 选择决赛名单
+const toFinals = () => {
+  if (selectionList.value && selectionList.value.length > 0) {
+    const data = {
+      id: id.value,
+      ids: selectionList.value.map((i) => {
+        return i.id
+      })
+    }
+    emits('toFinals', data)
+  } else {
+    ElMessage({
+      message: '请选择要进入决赛的名单数据!',
+      type: 'warning'
+    })
+  }
+}
+const toClose = async () => {
+  is_look.value = false
+  form.value = {}
+  scoreForm.value = {}
+  dialog.value = { show: false }
+  await search({ skip, limit })
+}
+watch(
+  match,
+  async (item) => {
+    if (item.id) {
+      id.value = item.id
+      loading.value = true
+      await search({ skip, limit })
+      loading.value = false
+    }
+  },
+  {
+    immediate: true //初始化立即执行
+  }
+)
+</script>
+<style scoped lang="scss">
+.main {
+  .export {
+    display: flex;
+    justify-content: flex-start;
+    margin: 10px 0;
+    .one_left {
+      margin: 0 10px 0 0;
+      background: #1875df;
+      padding: 0 10px;
+      height: 30px;
+      color: #fff;
+      line-height: 30px;
+      text-align: center;
+      font-size: 16px;
+      cursor: default;
+    }
+  }
+  .thr {
+    display: flex;
+    justify-content: center;
+    margin: 20px 0 0 0;
+  }
+  .button {
+    margin: 20px 0 0 0;
+    text-align: center;
+    button {
+      width: 400px;
+      height: 40px;
+    }
+  }
+  .type {
+    .image {
+      width: 100%;
+    }
+  }
+
+  .list {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin: 10px 0;
+
+    .name {
+      width: 50%;
+      text-align: center;
+    }
+
+    .input {
+      width: 50%;
+      margin: 0 5px 0 0;
+      border: 1px solid #e5e5e5;
+      border-radius: 5px;
+      text-align: center;
+
+      .name {
+        width: 50%;
+        text-align: center;
+        margin: 0 0 10px 0;
+      }
+    }
+  }
+  :deep(.el-descriptions__body .el-descriptions__table.is-bordered .el-descriptions__cell) {
+    text-align: center;
+    width: 10px !important; /* 设置你想要的宽度 */
+  }
+}
+</style>

+ 0 - 0
vite.config.js


部分文件因文件數量過多而無法顯示