jiangAB 3 months ago
commit
ac0a5c9b67
  1. 12
      .editorconfig
  2. 37
      .env
  3. 37
      .env.dev
  4. 34
      .env.local
  5. 34
      .env.prod
  6. 34
      .env.stage
  7. 34
      .env.test
  8. 8
      .eslintignore
  9. 259
      .eslintrc-auto-import.json
  10. 75
      .eslintrc.js
  11. 14
      .gitignore
  12. 11
      .prettierignore
  13. 6
      .stylelintignore
  14. 18
      .vscode/extensions.json
  15. 16
      .vscode/launch.json
  16. 146
      .vscode/settings.json
  17. 21
      LICENSE
  18. 295
      README.md
  19. 156
      index.html
  20. 18131
      package-lock.json
  21. 164
      package.json
  22. 10844
      pnpm-lock.yaml
  23. 5
      postcss.config.js
  24. 22
      prettier.config.js
  25. BIN
      public.zip
  26. BIN
      public/favicon.ico
  27. BIN
      public/img/logo.png
  28. BIN
      public/img/故障.png
  29. BIN
      public/img/离线.png
  30. BIN
      public/logo.gif
  31. BIN
      public/map/.index.vue.swp
  32. 784
      public/map/_index.vue
  33. 182
      public/map/components/BarChart.vue
  34. 165
      public/map/components/LineChart.vue
  35. 126
      public/map/components/PieChart.vue
  36. 679
      public/map/components/_index.vue
  37. 240
      public/map/components/index.html
  38. 529
      public/map/components/index.vue
  39. 703
      public/map/components/tags.ts
  40. 1379
      public/map/index.vue
  41. 57
      src/App.vue
  42. 65
      src/api/ai/chat/conversation/index.ts
  43. 104
      src/api/ai/chat/message/index.ts
  44. 102
      src/api/ai/image/index.ts
  45. 54
      src/api/ai/knowledge/document/index.ts
  46. 44
      src/api/ai/knowledge/knowledge/index.ts
  47. 75
      src/api/ai/knowledge/segment/index.ts
  48. 60
      src/api/ai/mindmap/index.ts
  49. 44
      src/api/ai/model/apiKey/index.ts
  50. 83
      src/api/ai/model/chatRole/index.ts
  51. 54
      src/api/ai/model/model/index.ts
  52. 42
      src/api/ai/model/tool/index.ts
  53. 41
      src/api/ai/music/index.ts
  54. 25
      src/api/ai/workflow/index.ts
  55. 85
      src/api/ai/write/index.ts
  56. 53
      src/api/bpm/category/index.ts
  57. 28
      src/api/bpm/definition/index.ts
  58. 56
      src/api/bpm/form/index.ts
  59. 27
      src/api/bpm/leave/index.ts
  60. 78
      src/api/bpm/model/index.ts
  61. 42
      src/api/bpm/processExpression/index.ts
  62. 115
      src/api/bpm/processInstance/index.ts
  63. 40
      src/api/bpm/processListener/index.ts
  64. 15
      src/api/bpm/simple/index.ts
  65. 122
      src/api/bpm/task/index.ts
  66. 47
      src/api/bpm/userGroup/index.ts
  67. 98
      src/api/crm/business/index.ts
  68. 68
      src/api/crm/business/status/index.ts
  69. 78
      src/api/crm/clue/index.ts
  70. 113
      src/api/crm/contact/index.ts
  71. 16
      src/api/crm/contract/config/index.ts
  72. 114
      src/api/crm/contract/index.ts
  73. 132
      src/api/crm/customer/index.ts
  74. 49
      src/api/crm/customer/limitConfig/index.ts
  75. 19
      src/api/crm/customer/poolConfig/index.ts
  76. 43
      src/api/crm/followup/index.ts
  77. 11
      src/api/crm/operateLog/index.ts
  78. 72
      src/api/crm/permission/index.ts
  79. 33
      src/api/crm/product/category/index.ts
  80. 49
      src/api/crm/product/index.ts
  81. 73
      src/api/crm/receivable/index.ts
  82. 74
      src/api/crm/receivable/plan/index.ts
  83. 168
      src/api/crm/statistics/customer.ts
  84. 58
      src/api/crm/statistics/funnel.ts
  85. 33
      src/api/crm/statistics/performance.ts
  86. 60
      src/api/crm/statistics/portrait.ts
  87. 67
      src/api/crm/statistics/rank.ts
  88. 61
      src/api/erp/finance/account/index.ts
  89. 61
      src/api/erp/finance/payment/index.ts
  90. 61
      src/api/erp/finance/receipt/index.ts
  91. 49
      src/api/erp/product/category/index.ts
  92. 57
      src/api/erp/product/product/index.ts
  93. 46
      src/api/erp/product/unit/index.ts
  94. 64
      src/api/erp/purchase/in/index.ts
  95. 64
      src/api/erp/purchase/order/index.ts
  96. 62
      src/api/erp/purchase/return/index.ts
  97. 58
      src/api/erp/purchase/supplier/index.ts
  98. 58
      src/api/erp/sale/customer/index.ts
  99. 64
      src/api/erp/sale/order/index.ts
  100. 62
      src/api/erp/sale/out/index.ts

12
.editorconfig

@ -0,0 +1,12 @@
root = true
[*.{js,ts,vue}]
charset = utf-8 # 设置文件字符集为 utf-8
end_of_line = lf # 控制换行类型(lf | cr | crlf)
insert_final_newline = true # 始终在文件末尾插入一个新行
indent_style = space # 缩进风格(tab | space)
indent_size = 2 # 缩进大小
max_line_length = 100 # 最大行长度
[*.md] # 仅 md 文件适用以下规则
max_line_length = off # 关闭最大行长度限制
trim_trailing_whitespace = false # 关闭末尾空格修剪

37
.env

@ -0,0 +1,37 @@
# 标题
VITE_APP_TITLE=芋道管理系统
# 项目本地运行端口号
VITE_PORT=80
# open 运行 npm run dev 时自动打开浏览器
VITE_OPEN=true
# 租户开关
VITE_APP_TENANT_ENABLE=true
# 验证码的开关
VITE_APP_CAPTCHA_ENABLE=true
# 文档地址的开关
VITE_APP_DOCALERT_ENABLE=true
# 百度统计
VITE_APP_BAIDU_CODE = a1ff8825baa73c3a78eb96aa40325abc
# 默认账户密码
VITE_APP_DEFAULT_LOGIN_TENANT = 芋道源码
VITE_APP_DEFAULT_LOGIN_USERNAME = admin
VITE_APP_DEFAULT_LOGIN_PASSWORD = admin123
# API 加解密
VITE_APP_API_ENCRYPT_ENABLE = true
VITE_APP_API_ENCRYPT_HEADER = X-Api-Encrypt
VITE_APP_API_ENCRYPT_ALGORITHM = AES
VITE_APP_API_ENCRYPT_REQUEST_KEY = 52549111389893486934626385991395
VITE_APP_API_ENCRYPT_RESPONSE_KEY = 96103715984234343991809655248883
# VITE_APP_API_ENCRYPT_REQUEST_KEY = MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCls2rIpnGdYnLFgz1XU13GbNQ5DloyPpvW00FPGjqn5Z6JpK+kDtVlnkhwR87iRrE5Vf2WNqRX6vzbLSgveIQY8e8oqGCb829myjf1MuI+ZzN4ghf/7tEYhZJGPI9AbfxFqBUzm+kR3/HByAI22GLT96WM26QiMK8n3tIP/yiLswIDAQAB
# VITE_APP_API_ENCRYPT_RESPONSE_KEY = MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAOH8IfIFxL/MR9XIg1UDv5z1fGXQI93E8wrU4iPFovL/sEt9uSgSkjyidC2O7N+m7EKtoN6b1u7cEwXSkwf3kfK0jdWLSQaNpX5YshqXCBzbDfugDaxuyYrNA4/tIMs7mzZAk0APuRXB35Dmupou7Yw7TFW/7QhQmGfzeEKULQvnAgMBAAECgYAw8LqlQGyQoPv5p3gRxEMOCfgL0JzD3XBJKztiRd35RDh40Nx1ejgjW4dPioFwGiVWd2W8cAGHLzALdcQT2KDJh+T/tsd4SPmI6uSBBK6Ff2DkO+kFFcuYvfclQQKqxma5CaZOSqhgenacmgTMFeg2eKlY3symV6JlFNu/IKU42QJBAOhxAK/Eq3e61aYQV2JSguhMR3b8NXJJRroRs/QHEanksJtl+M+2qhkC9nQVXBmBkndnkU/l2tYcHfSBlAyFySMCQQD445tgm/J2b6qMQmuUGQAYDN8FIkHjeKmha+l/fv0igWm8NDlBAem91lNDIPBUzHL1X1+pcts5bjmq99YdOnhtAkAg2J8dN3B3idpZDiQbC8fd5bGPmdI/pSUudAP27uzLEjr2qrE/QPPGdwm2m7IZFJtK7kK1hKio6u48t/bg0iL7AkEAuUUs94h+v702Fnym+jJ2CHEkXvz2US8UDs52nWrZYiM1o1y4tfSHm8H8bv8JCAa9GHyriEawfBraILOmllFdLQJAQSRZy4wmlaG48MhVXodB85X+VZ9krGXZ2TLhz7kz9iuToy53l9jTkESt6L5BfBDCVdIwcXLYgK+8KFdHN5W7HQ==
# 百度地图
VITE_BAIDU_MAP_KEY = 'efHIw2qmH8RzHPxK0z0rbCgzDVLup9LD'

37
.env.dev

@ -0,0 +1,37 @@
# 开发环境:本地只启动前端项目,依赖开发环境(后端、APP)
NODE_ENV=production
VITE_DEV=true
# 请求路径
VITE_BASE_URL='http://api-dashboard.yudao.iocoder.cn'
# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务
VITE_UPLOAD_TYPE=server
# 接口地址
VITE_API_URL=/admin-api
# 是否删除debugger
VITE_DROP_DEBUGGER=false
# 是否删除console.log
VITE_DROP_CONSOLE=false
# 是否sourcemap
VITE_SOURCEMAP=true
# 打包路径
VITE_BASE_PATH='http://server.ayaojies.com.cn:48080'
# 输出路径
VITE_OUT_DIR=dist
# 商城H5会员端域名
VITE_MALL_H5_DOMAIN='http://mall.yudao.iocoder.cn'
# 验证码的开关
VITE_APP_CAPTCHA_ENABLE=true
# GoView域名
VITE_GOVIEW_URL='http://127.0.0.1:3000'

34
.env.local

@ -0,0 +1,34 @@
# 本地开发环境:本地启动所有项目(前端、后端、APP)时使用,不依赖外部环境
NODE_ENV=development
VITE_DEV=true
# 请求路径
VITE_BASE_URL='http://server.ayaojies.com.cn:48080'
# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持 S3 服务
VITE_UPLOAD_TYPE=server
# 接口地址
VITE_API_URL=/admin-api
# 是否删除debugger
VITE_DROP_DEBUGGER=false
# 是否删除console.log
VITE_DROP_CONSOLE=false
# 是否sourcemap
VITE_SOURCEMAP=false
# 打包路径
VITE_BASE_PATH=/
# 商城H5会员端域名
VITE_MALL_H5_DOMAIN='http://localhost:3000'
# 验证码的开关
VITE_APP_CAPTCHA_ENABLE=false
# GoView域名
VITE_GOVIEW_URL='http://127.0.0.1:3000'

34
.env.prod

@ -0,0 +1,34 @@
# 生产环境:只在打包时使用
NODE_ENV=production
VITE_DEV=false
# 请求路径
VITE_BASE_URL='http://server.ayaojies.com.cn:48080'
# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务
VITE_UPLOAD_TYPE=server
# 接口地址
VITE_API_URL=/admin-api
# 是否删除debugger
VITE_DROP_DEBUGGER=true
# 是否删除console.log
VITE_DROP_CONSOLE=true
# 是否sourcemap
VITE_SOURCEMAP=false
# 打包路径
VITE_BASE_PATH=/
# 输出路径
VITE_OUT_DIR=dist-prod
# 商城H5会员端域名
VITE_MALL_H5_DOMAIN='http://mall.yudao.iocoder.cn'
# GoView域名
VITE_GOVIEW_URL='http://127.0.0.1:3000'

34
.env.stage

@ -0,0 +1,34 @@
# 预发布环境:只在打包时使用
NODE_ENV=production
VITE_DEV=false
# 请求路径
VITE_BASE_URL='http://server.ayaojies.com.cn:48080'
# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务
VITE_UPLOAD_TYPE=server
# 接口地址
VITE_API_URL=/admin-api
# 是否删除debugger
VITE_DROP_DEBUGGER=true
# 是否删除console.log
VITE_DROP_CONSOLE=true
# 是否sourcemap
VITE_SOURCEMAP=false
# 打包路径
VITE_BASE_PATH='http://static-vue3.yudao.iocoder.cn/'
# 输出路径
VITE_OUT_DIR=dist-stage
# 商城H5会员端域名
VITE_MALL_H5_DOMAIN='http://mall.yudao.iocoder.cn'
# GoView域名
VITE_GOVIEW_URL='http://127.0.0.1:3000'

34
.env.test

@ -0,0 +1,34 @@
# 测试环境:只在打包时使用
NODE_ENV=production
VITE_DEV=false
# 请求路径
VITE_BASE_URL='http://server.ayaojies.com.cn:48080'
# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务
VITE_UPLOAD_TYPE=server
# 接口地址
VITE_API_URL=/admin-api
# 是否删除debugger
VITE_DROP_DEBUGGER=true
# 是否删除console.log
VITE_DROP_CONSOLE=true
# 是否sourcemap
VITE_SOURCEMAP=false
# 打包路径
VITE_BASE_PATH=/admin-ui-vue3/
# 输出路径
VITE_OUT_DIR=dist-test
# 商城H5会员端域名
VITE_MALL_H5_DOMAIN='http://mall.yudao.iocoder.cn'
# GoView域名
VITE_GOVIEW_URL='http://127.0.0.1:3000'

8
.eslintignore

@ -0,0 +1,8 @@
/build/
/config/
/dist/
/*.js
/test/unit/coverage/
/node_modules/*
/dist*
/src/main.ts

259
.eslintrc-auto-import.json

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

75
.eslintrc.js

@ -0,0 +1,75 @@
// @ts-check
const { defineConfig } = require('eslint-define-config')
module.exports = defineConfig({
root: true,
env: {
browser: true,
node: true,
es6: true
},
parser: 'vue-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser',
ecmaVersion: 2020,
sourceType: 'module',
jsxPragma: 'React',
ecmaFeatures: {
jsx: true
}
},
extends: [
'plugin:vue/vue3-recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
'plugin:prettier/recommended',
'@unocss'
],
rules: {
'vue/no-setup-props-destructure': 'off',
'vue/script-setup-uses-vars': 'error',
'vue/no-reserved-component-names': 'off',
'@typescript-eslint/ban-ts-ignore': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/no-empty-function': 'off',
'vue/custom-event-name-casing': 'off',
'no-use-before-define': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'no-unused-vars': 'off',
'space-before-function-paren': 'off',
'vue/attributes-order': 'off',
'vue/one-component-per-file': 'off',
'vue/html-closing-bracket-newline': 'off',
'vue/max-attributes-per-line': 'off',
'vue/multiline-html-element-content-newline': 'off',
'vue/singleline-html-element-content-newline': 'off',
'vue/attribute-hyphenation': 'off',
'vue/require-default-prop': 'off',
'vue/require-explicit-emits': 'off',
'vue/require-toggle-inside-transition': 'off',
'vue/html-self-closing': [
'error',
{
html: {
void: 'always',
normal: 'never',
component: 'always'
},
svg: 'always',
math: 'always'
}
],
'vue/multi-word-component-names': 'off',
'vue/no-v-html': 'off',
'prettier/prettier': 'off', // 芋艿:默认关闭 prettier 的 ESLint 校验,因为我们使用的是 IDE 的 Prettier 插件
'@unocss/order': 'off', // 芋艿:禁用 unocss 【css】顺序的提示,因为暂时不需要这么严格,警告也有点繁琐
'@unocss/order-attributify': 'off' // 芋艿:禁用 unocss 【属性】顺序的提示,因为暂时不需要这么严格,警告也有点繁琐
}
})

14
.gitignore

@ -0,0 +1,14 @@
node_modules
.DS_Store
dist
dist-ssr
/dist*
pnpm-debug
auto-*.d.ts
.idea
.history
.image
build
public/model
public/Cesium

11
.prettierignore

@ -0,0 +1,11 @@
/node_modules/**
/dist/
/dist*
/public/*
/docs/*
/vite.config.ts
/src/types/env.d.ts
/src/types/auto-components.d.ts
/src/types/auto-imports.d.ts
/docs/**/*
CHANGELOG

6
.stylelintignore

@ -0,0 +1,6 @@
/dist/*
/public/*
public/*
/dist*
/src/types/env.d.ts
/docs/**/*

18
.vscode/extensions.json

@ -0,0 +1,18 @@
{
"recommendations": [
"christian-kohler.path-intellisense",
"vscode-icons-team.vscode-icons",
"davidanson.vscode-markdownlint",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"mrmlnc.vscode-less",
"lokalise.i18n-ally",
"redhat.vscode-yaml",
"csstools.postcss",
"mikestead.dotenv",
"eamodio.gitlens",
"antfu.iconify",
"antfu.unocss",
"Vue.volar"
]
}

16
.vscode/launch.json

@ -0,0 +1,16 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "msedge",
"request": "launch",
"name": "Launch Edge against localhost",
"url": "http://localhost",
"webRoot": "${workspaceFolder}/src",
"sourceMaps": true
}
]
}

146
.vscode/settings.json

@ -0,0 +1,146 @@
{
"typescript.tsdk": "node_modules/typescript/lib",
"npm.packageManager": "pnpm",
"editor.tabSize": 2,
"prettier.printWidth": 100, //
"editor.defaultFormatter": "esbenp.prettier-vscode",
"files.eol": "\n",
"search.exclude": {
"**/node_modules": true,
"**/*.log": true,
"**/*.log*": true,
"**/bower_components": true,
"**/dist": true,
"**/elehukouben": true,
"**/.git": true,
"**/.gitignore": true,
"**/.svn": true,
"**/.DS_Store": true,
"**/.idea": true,
"**/.vscode": false,
"**/yarn.lock": true,
"**/tmp": true,
"out": true,
"dist": true,
"node_modules": true,
"CHANGELOG.md": true,
"examples": true,
"res": true,
"screenshots": true,
"yarn-error.log": true,
"**/.yarn": true
},
"files.exclude": {
"**/.cache": true,
"**/.editorconfig": true,
"**/.eslintcache": true,
"**/bower_components": true,
"**/.idea": true,
"**/tmp": true,
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true
},
"files.watcherExclude": {
"**/.git/objects/**": true,
"**/.git/subtree-cache/**": true,
"**/.vscode/**": true,
"**/node_modules/**": true,
"**/tmp/**": true,
"**/bower_components/**": true,
"**/dist/**": true,
"**/yarn.lock": true
},
"stylelint.enable": true,
"stylelint.validate": ["css", "less", "postcss", "scss", "vue", "sass"],
"path-intellisense.mappings": {
"@/": "${workspaceRoot}/src"
},
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[less]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[scss]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.fixAll.stylelint": "explicit"
},
"editor.formatOnSave": true,
"[vue]": {
"editor.defaultFormatter": "Vue.volar"
},
"i18n-ally.localesPaths": ["src/locales"],
"i18n-ally.keystyle": "nested",
"i18n-ally.sortKeys": true,
"i18n-ally.namespace": false,
"i18n-ally.enabledParsers": ["ts"],
"i18n-ally.sourceLanguage": "en",
"i18n-ally.displayLanguage": "zh-CN",
"i18n-ally.enabledFrameworks": ["vue", "react"],
"cSpell.words": [
"brotli",
"browserslist",
"codemirror",
"commitlint",
"cropperjs",
"echart",
"echarts",
"esnext",
"esno",
"iconify",
"INTLIFY",
"lintstagedrc",
"logicflow",
"nprogress",
"pinia",
"pnpm",
"qrcode",
"sider",
"sortablejs",
"stylelint",
"svgs",
"unocss",
"unplugin",
"unref",
"videojs",
"VITE",
"vitejs",
"vueuse",
"wangeditor",
"xingyu",
"yudao",
"zxcvbn"
],
//
"explorer.fileNesting.enabled": true,
"explorer.fileNesting.expand": false,
"explorer.fileNesting.patterns": {
"*.ts": "$(capture).test.ts, $(capture).test.tsx",
"*.tsx": "$(capture).test.ts, $(capture).test.tsx",
"*.env": "$(capture).env.*",
"package.json": "pnpm-lock.yaml,yarn.lock,LICENSE,README*,CHANGELOG*,CNAME,.gitattributes,.eslintrc-auto-import.json,.gitignore,prettier.config.js,stylelint.config.js,commitlint.config.js,.stylelintignore,.prettierignore,.gitpod.yml,.eslintrc.js,.eslintignore"
},
"terminal.integrated.scrollback": 10000,
"nuxt.isNuxtApp": false
}

21
LICENSE

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021-present Archer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

295
README.md

@ -0,0 +1,295 @@
**严肃声明:现在、未来都不会有商业版本,所有代码全部开源!!**
**「我喜欢写代码,乐此不疲」**
**「我喜欢做开源,以此为乐」**
我 🐶 在上海艰苦奋斗,早中晚在 top3 大厂认真搬砖,夜里为开源做贡献。
如果这个项目让你有所收获,记得 Star 关注哦,这对我是非常不错的鼓励与支持。
## 🐶 新手必读
* nodejs > 16.18.0 && pnpm > 8.6.0 (强制使用pnpm)
* 演示地址【Vue3 + element-plus】:<http://dashboard-vue3.yudao.iocoder.cn>
* 演示地址【Vue3 + vben5.0(ant-design-vue)】:<http://dashboard-vben.yudao.iocoder.cn>
* 演示地址【Vue2 + element-ui】:<http://dashboard.yudao.iocoder.cn>
* 启动文档:<https://doc.iocoder.cn/quick-start/>
* 视频教程:<https://doc.iocoder.cn/video/>
## 🐯 平台简介
**芋道**,以开发者为中心,打造中国第一流的快速开发平台,全部开源,个人与企业可 100% 免费使用。
* 采用 [vue-element-plus-admin](https://gitee.com/kailong110120130/vue-element-plus-admin) 实现
* 改换 saas,自动引入等功能
* 使用 Element Plus 免费开源的中后台模版,具备如下特性:
![首页](.image/demo/vue3-ep.png)
* **最新技术栈**:使用 Vue3、Vite4 等前端前沿技术开发
* **TypeScript**: 应用程序级 JavaScript 的语言
* **主题**: 可配置的主题
* **国际化**:内置完善的国际化方案
* **权限**:内置完善的动态路由权限生成方案
* **组件**:二次封装了多个常用的组件
* **示例**:内置丰富的示例
## 技术栈
| 框架 | 说明 | 版本 |
|----------------------------------------------------------------------|------------------|--------|
| [Vue](https://staging-cn.vuejs.org/) | Vue 框架 | 3.3.8 |
| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 4.5.0 |
| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.4.2 |
| [TypeScript](https://www.typescriptlang.org/docs/) | JavaScript 的超集 | 5.2.2 |
| [pinia](https://pinia.vuejs.org/) | Vue 存储库 替代 vuex5 | 2.1.7 |
| [vueuse](https://vueuse.org/) | 常用工具集 | 10.6.1 |
| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.6.5 |
| [vue-router](https://router.vuejs.org/) | Vue 路由 | 4.2.5 |
| [unocss](https://uno.antfu.me/) | 原子 css | 0.57.4 |
| [iconify](https://icon-sets.iconify.design/) | 在线图标库 | 3.1.1 |
| [wangeditor](https://www.wangeditor.com/) | 富文本编辑器 | 5.1.23 |
## 开发工具
推荐 VS Code 开发,配合插件如下:
| 插件名 | 功能 |
|-------------------------------|---------------------|
| Vue - Official | Vue 与 TypeScript 支持 |
| unocss | unocss for vscode |
| Iconify IntelliSense | Iconify 预览和搜索 |
| i18n Ally | 国际化智能提示 |
| Stylelint | Css 格式化 |
| Prettier | 代码格式化 |
| ESLint | 脚本代码检查 |
| DotENV | env 文件高亮 |
## 🔥 后端架构
支持 Spring Boot、Spring Cloud 两种架构:
① Spring Boot 单体架构:<https://doc.iocoder.cn>
![架构图](/.image/common/ruoyi-vue-pro-architecture.png)
② Spring Cloud 微服务架构:<https://cloud.iocoder.cn>
![架构图](/.image/common/yudao-cloud-architecture.png)
## 内置功能
系统内置多种多种业务功能,可以用于快速你的业务系统:
系统内置多种多种业务功能,可以用于快速你的业务系统:
![功能分层](/.image/common/ruoyi-vue-pro-biz.png)
* 通用模块(必选):系统功能、基础设施
* 通用模块(可选):工作流程、支付系统、数据报表、会员中心
* 业务系统(按需):ERP 系统、CRM 系统、商城系统、微信公众号、AI 大模型
### 系统功能
| | 功能 | 描述 |
|-----|-------|---------------------------------|
| | 用户管理 | 用户是系统操作者,该功能主要完成系统用户配置 |
| ⭐️ | 在线用户 | 当前系统中活跃用户状态监控,支持手动踢下线 |
| | 角色管理 | 角色菜单权限分配、设置角色按机构进行数据范围权限划分 |
| | 菜单管理 | 配置系统菜单、操作权限、按钮权限标识等,本地缓存提供性能 |
| | 部门管理 | 配置系统组织机构(公司、部门、小组),树结构展现支持数据权限 |
| | 岗位管理 | 配置系统用户所属担任职务 |
| 🚀 | 租户管理 | 配置系统租户,支持 SaaS 场景下的多租户功能 |
| 🚀 | 租户套餐 | 配置租户套餐,自定每个租户的菜单、操作、按钮的权限 |
| | 字典管理 | 对系统中经常使用的一些较为固定的数据进行维护 |
| 🚀 | 短信管理 | 短信渠道、短息模板、短信日志,对接阿里云、腾讯云等主流短信平台 |
| 🚀 | 邮件管理 | 邮箱账号、邮件模版、邮件发送日志,支持所有邮件平台 |
| 🚀 | 站内信 | 系统内的消息通知,提供站内信模版、站内信消息 |
| 🚀 | 操作日志 | 系统正常操作日志记录和查询,集成 Swagger 生成日志内容 |
| ⭐️ | 登录日志 | 系统登录日志记录查询,包含登录异常 |
| 🚀 | 错误码管理 | 系统所有错误码的管理,可在线修改错误提示,无需重启服务 |
| | 通知公告 | 系统通知公告信息发布维护 |
| 🚀 | 敏感词 | 配置系统敏感词,支持标签分组 |
| 🚀 | 应用管理 | 管理 SSO 单点登录的应用,支持多种 OAuth2 授权方式 |
| 🚀 | 地区管理 | 展示省份、城市、区镇等城市信息,支持 IP 对应城市 |
![功能图](/.image/common/system-feature.png)
### 工作流程
![功能图](/.image/common/bpm-feature.png)
基于 Flowable 构建,可支持信创(国产)数据库,满足中国特色流程操作:
| BPMN 设计器 | 钉钉/飞书设计器 |
|-----------------------------|-------------------------------|
| ![](.image/工作流设计器-bpmn.jpg) | ![](.image/工作流设计器-simple.jpg) |
> 历经头部企业生产验证,工作流引擎须标配仿钉钉/飞书 + BPMN 双设计器!!!
>
> 前者支持轻量配置简单流程,后者实现复杂场景深度编排
| 功能列表 | 功能描述 | 是否完成 |
|------------|-------------------------------------------------------------------------------------|------|
| SIMPLE 设计器 | 仿钉钉/飞书设计器,支持拖拽搭建表单流程,10 分钟快速完成审批流程配置 | ✅ |
| BPMN 设计器 | 基于 BPMN 标准开发,适配复杂业务场景,满足多层级审批及流程自动化需求 | ✅ |
| 会签 | 同一个审批节点设置多个人(如 A、B、C 三人,三人会同时收到待办任务),需全部同意之后,审批才可到下一审批节点 | ✅ |
| 或签 | 同一个审批节点设置多个人,任意一个人处理后,就能进入下一个节点 | ✅ |
| 依次审批 | (顺序会签)同一个审批节点设置多个人(如 A、B、C 三人),三人按顺序依次收到待办,即 A 先审批,A 提交后 B 才能审批,需全部同意之后,审批才可到下一审批节点 | ✅ |
| 抄送 | 将审批结果通知给抄送人,同一个审批默认排重,不重复抄送给同一人 | ✅ |
| 驳回 | (退回)将审批重置发送给某节点,重新审批。可驳回至发起人、上一节点、任意节点 | ✅ |
| 转办 | A 转给其 B 审批,B 审批后,进入下一节点 | ✅ |
| 委派 | A 转给其 B 审批,B 审批后,转给 A,A 继续审批后进入下一节点 | ✅ |
| 加签 | 允许当前审批人根据需要,自行增加当前节点的审批人,支持向前、向后加签 | ✅ |
| 减签 | (取消加签)在当前审批人操作之前,减少审批人 | ✅ |
| 撤销 | (取消流程)流程发起人,可以对流程进行撤销处理 | ✅ |
| 终止 | 系统管理员,在任意节点终止流程实例 | ✅ |
| 表单权限 | 支持拖拉拽配置表单,每个审批节点可配置只读、编辑、隐藏权限 | ✅ |
| 超时审批 | 配置超时审批时间,超时后自动触发审批通过、不通过、驳回等操作 | ✅ |
| 自动提醒 | 配置提醒时间,到达时间后自动触发短信、邮箱、站内信等通知提醒,支持自定义重复提醒频次 | ✅ |
| 父子流程 | 主流程设置子流程节点,子流程节点会自动触发子流程。子流程结束后,主流程才会执行(继续往下下执行),支持同步子流程、异步子流程 | ✅ |
| 条件分支 | (排它分支)用于在流程中实现决策,即根据条件选择一个分支执行 | ✅ |
| 并行分支 | 允许将流程分成多条分支,不进行条件判断,所有分支都会执行 | ✅ |
| 包容分支 | (条件分支 + 并行分支的结合体)允许基于条件选择多条分支执行,但如果没有任何一个分支满足条件,则可以选择默认分支 | ✅ |
| 路由分支 | 根据条件选择一个分支执行(重定向到指定配置节点),也可以选择默认分支执行(继续往下执行) | ✅ |
| 触发节点 | 执行到该节点,触发 HTTP 请求、HTTP 回调、更新数据、删除数据等 | ✅ |
| 延迟节点 | 执行到该节点,审批等待一段时间再执行,支持固定时长、固定日期等 | ✅ |
| 拓展设置 | 流程前置/后置通知,节点(任务)前置、后置通知,流程报表,自动审批去重,自定流程编号、标题、摘要,流程报表等 | ✅ |
### 支付系统
| | 功能 | 描述 |
|-----|------|---------------------------|
| 🚀 | 应用信息 | 配置商户的应用信息,对接支付宝、微信等多个支付渠道 |
| 🚀 | 支付订单 | 查看用户发起的支付宝、微信等的【支付】订单 |
| 🚀 | 退款订单 | 查看用户发起的支付宝、微信等的【退款】订单 |
| 🚀 | 回调通知 | 查看支付回调业务的【支付】【退款】的通知结果 |
| 🚀 | 接入示例 | 提供接入支付系统的【支付】【退款】的功能实战 |
### 基础设施
| | 功能 | 描述 |
|-----|-----------|----------------------------------------------|
| 🚀 | 代码生成 | 前后端代码的生成(Java、Vue、SQL、单元测试),支持 CRUD 下载 |
| 🚀 | 系统接口 | 基于 Swagger 自动生成相关的 RESTful API 接口文档 |
| 🚀 | 数据库文档 | 基于 Screw 自动生成数据库文档,支持导出 Word、HTML、MD 格式 |
| | 表单构建 | 拖动表单元素生成相应的 HTML 代码,支持导出 JSON、Vue 文件 |
| 🚀 | 配置管理 | 对系统动态配置常用参数,支持 SpringBoot 加载 |
| ⭐️ | 定时任务 | 在线(添加、修改、删除)任务调度包含执行结果日志 |
| 🚀 | 文件服务 | 支持将文件存储到 S3(MinIO、阿里云、腾讯云、七牛云)、本地、FTP、数据库等 |
| 🚀 | WebSocket | 提供 WebSocket 接入示例,支持一对一、一对多发送方式 |
| 🚀 | API 日志 | 包括 RESTful API 访问日志、异常日志两部分,方便排查 API 相关的问题 |
| | MySQL 监控 | 监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈 |
| | Redis 监控 | 监控 Redis 数据库的使用情况,使用的 Redis Key 管理 |
| 🚀 | 消息队列 | 基于 Redis 实现消息队列,Stream 提供集群消费,Pub/Sub 提供广播消费 |
| 🚀 | Java 监控 | 基于 Spring Boot Admin 实现 Java 应用的监控 |
| 🚀 | 链路追踪 | 接入 SkyWalking 组件,实现链路追踪 |
| 🚀 | 日志中心 | 接入 SkyWalking 组件,实现日志中心 |
| 🚀 | 服务保障 | 基于 Redis 实现分布式锁、幂等、限流功能,满足高并发场景 |
| 🚀 | 日志服务 | 轻量级日志中心,查看远程服务器的日志 |
| 🚀 | 单元测试 | 基于 JUnit + Mockito 实现单元测试,保证功能的正确性、代码的质量等 |
![功能图](/.image/common/infra-feature.png)
### 数据报表
| | 功能 | 描述 |
|-----|-------|--------------------|
| 🚀 | 报表设计器 | 支持数据报表、图形报表、打印设计等 |
| 🚀 | 大屏设计器 | 拖拽生成数据大屏,内置几十种图表组件 |
### 微信公众号
| | 功能 | 描述 |
|----|--------|-------------------------------|
| 🚀 | 账号管理 | 配置接入的微信公众号,可支持多个公众号 |
| 🚀 | 数据统计 | 统计公众号的用户增减、累计用户、消息概况、接口分析等数据 |
| 🚀 | 粉丝管理 | 查看已关注、取关的粉丝列表,可对粉丝进行同步、打标签等操作 |
| 🚀 | 消息管理 | 查看粉丝发送的消息列表,可主动回复粉丝消息 |
| 🚀 | 模版消息 | 配置和发送模版消息,用于向粉丝推送通知类消息 |
| 🚀 | 自动回复 | 自动回复粉丝发送的消息,支持关注回复、消息回复、关键字回复 |
| 🚀 | 标签管理 | 对公众号的标签进行创建、查询、修改、删除等操作 |
| 🚀 | 菜单管理 | 自定义公众号的菜单,也可以从公众号同步菜单 |
| 🚀 | 素材管理 | 管理公众号的图片、语音、视频等素材,支持在线播放语音、视频 |
| 🚀 | 图文草稿箱 | 新增常用的图文素材到草稿箱,可发布到公众号 |
| 🚀 | 图文发表记录 | 查看已发布成功的图文素材,支持删除操作 |
### 商城系统
演示地址:<https://doc.iocoder.cn/mall-preview/>
![功能图](/.image/common/mall-feature.png)
![功能图](/.image/common/mall-preview.png)
### ERP 系统
演示地址:<https://doc.iocoder.cn/erp-preview/>
![功能图](/.image/common/erp-feature.png)
### CRM 系统
演示地址:<https://doc.iocoder.cn/crm-preview/>
![功能图](/.image/common/crm-feature.png)
### AI 大模型
演示地址:<https://doc.iocoder.cn/ai-preview/>
![功能图](/.image/common/ai-feature.png)
![功能图](/.image/common/ai-preview.gif)
## 🐷 演示图
### 系统功能
| 模块 | biu | biu | biu |
|----------|-----------------------------|---------------------------|--------------------------|
| 登录 & 首页 | ![登录](/.image/登录.jpg) | ![首页](/.image/首页.jpg) | ![个人中心](/.image/个人中心.jpg) |
| 用户 & 应用 | ![用户管理](/.image/用户管理.jpg) | ![令牌管理](/.image/令牌管理.jpg) | ![应用管理](/.image/应用管理.jpg) |
| 租户 & 套餐 | ![租户管理](/.image/租户管理.jpg) | ![租户套餐](/.image/租户套餐.png) | - |
| 部门 & 岗位 | ![部门管理](/.image/部门管理.jpg) | ![岗位管理](/.image/岗位管理.jpg) | - |
| 菜单 & 角色 | ![菜单管理](/.image/菜单管理.jpg) | ![角色管理](/.image/角色管理.jpg) | - |
| 审计日志 | ![操作日志](/.image/操作日志.jpg) | ![登录日志](/.image/登录日志.jpg) | - |
| 短信 | ![短信渠道](/.image/短信渠道.jpg) | ![短信模板](/.image/短信模板.jpg) | ![短信日志](/.image/短信日志.jpg) |
| 字典 & 敏感词 | ![字典类型](/.image/字典类型.jpg) | ![字典数据](/.image/字典数据.jpg) | ![敏感词](/.image/敏感词.jpg) |
| 错误码 & 通知 | ![错误码管理](/.image/错误码管理.jpg) | ![通知公告](/.image/通知公告.jpg) | - |
### 工作流程
| 模块 | biu | biu | biu |
|---------|---------------------------------|---------------------------------|---------------------------------|
| 流程模型 | ![流程模型-列表](/.image/流程模型-列表.jpg) | ![流程模型-设计](/.image/流程模型-设计.jpg) | ![流程模型-定义](/.image/流程模型-定义.jpg) |
| 表单 & 分组 | ![流程表单](/.image/流程表单.jpg) | ![用户分组](/.image/用户分组.jpg) | - |
| 我的流程 | ![我的流程-列表](/.image/我的流程-列表.jpg) | ![我的流程-发起](/.image/我的流程-发起.jpg) | ![我的流程-详情](/.image/我的流程-详情.jpg) |
| 待办 & 已办 | ![任务列表-审批](/.image/任务列表-审批.jpg) | ![任务列表-待办](/.image/任务列表-待办.jpg) | ![任务列表-已办](/.image/任务列表-已办.jpg) |
| OA 请假 | ![OA请假-列表](/.image/OA请假-列表.jpg) | ![OA请假-发起](/.image/OA请假-发起.jpg) | ![OA请假-详情](/.image/OA请假-详情.jpg) |
### 基础设施
| 模块 | biu | biu | biu |
|---------------|-------------------------------|-----------------------------|---------------------------|
| 代码生成 | ![代码生成](/.image/代码生成.jpg) | ![生成效果](/.image/生成效果.jpg) | - |
| 文档 | ![系统接口](/.image/系统接口.jpg) | ![数据库文档](/.image/数据库文档.jpg) | - |
| 文件 & 配置 | ![文件配置](/.image/文件配置.jpg) | ![文件管理](/.image/文件管理2.jpg) | ![配置管理](/.image/配置管理.jpg) |
| 定时任务 | ![定时任务](/.image/定时任务.jpg) | ![任务日志](/.image/任务日志.jpg) | - |
| API 日志 | ![访问日志](/.image/访问日志.jpg) | ![错误日志](/.image/错误日志.jpg) | - |
| MySQL & Redis | ![MySQL](/.image/MySQL.jpg) | ![Redis](/.image/Redis.jpg) | - |
| 监控平台 | ![Java监控](/.image/Java监控.jpg) | ![链路追踪](/.image/链路追踪.jpg) | ![日志中心](/.image/日志中心.jpg) |
### 支付系统
| 模块 | biu | biu | biu |
|---------|---------------------------|---------------------------------|---------------------------------|
| 商家 & 应用 | ![商户信息](/.image/商户信息.jpg) | ![应用信息-列表](/.image/应用信息-列表.jpg) | ![应用信息-编辑](/.image/应用信息-编辑.jpg) |
| 支付 & 退款 | ![支付订单](/.image/支付订单.jpg) | ![退款订单](/.image/退款订单.jpg) | --- |
### 数据报表
| 模块 | biu | biu | biu |
|-------|---------------------------------|---------------------------------|---------------------------------------|
| 报表设计器 | ![数据报表](/.image/报表设计器-数据报表.jpg) | ![图形报表](/.image/报表设计器-图形报表.jpg) | ![报表设计器-打印设计](/.image/报表设计器-打印设计.jpg) |
| 大屏设计器 | ![大屏列表](/.image/大屏设计器-列表.jpg) | ![大屏预览](/.image/大屏设计器-预览.jpg) | ![大屏编辑](/.image/大屏设计器-编辑.jpg) |

156
index.html

@ -0,0 +1,156 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="keywords"
content="芋道管理系统 基于 vue3 + CompositionAPI + typescript + vite3 + element plus 的后台开源免费管理系统!"
/>
<meta
name="description"
content="芋道管理系统 基于 vue3 + CompositionAPI + typescript + vite3 + element plus 的后台开源免费管理系统!"
/>
<title>%VITE_APP_TITLE%</title>
<link rel="stylesheet" href="/Cesium/Widgets/widgets.css" />
</head>
<body>
<div id="app">
<style>
.app-loading {
display: flex;
width: 100%;
height: 100%;
justify-content: center;
align-items: center;
flex-direction: column;
background: #f0f2f5;
}
.app-loading .app-loading-wrap {
position: absolute;
top: 50%;
left: 50%;
display: flex;
-webkit-transform: translate3d(-50%, -50%, 0);
transform: translate3d(-50%, -50%, 0);
justify-content: center;
align-items: center;
flex-direction: column;
}
.app-loading .app-loading-title {
margin-bottom: 30px;
font-size: 20px;
font-weight: bold;
text-align: center;
}
.app-loading .app-loading-logo {
width: 100px;
margin: 0 auto 15px auto;
}
.app-loading .app-loading-item {
position: relative;
display: inline-block;
width: 60px;
height: 60px;
vertical-align: middle;
border-radius: 50%;
}
.app-loading .app-loading-outter {
position: absolute;
width: 100%;
height: 100%;
border: 4px solid #2d8cf0;
border-bottom: 0;
border-left-color: transparent;
border-radius: 50%;
animation: loader-outter 1s cubic-bezier(0.42, 0.61, 0.58, 0.41) infinite;
}
.app-loading .app-loading-inner {
position: absolute;
top: calc(50% - 20px);
left: calc(50% - 20px);
width: 40px;
height: 40px;
border: 4px solid #87bdff;
border-right: 0;
border-top-color: transparent;
border-radius: 50%;
animation: loader-inner 1s cubic-bezier(0.42, 0.61, 0.58, 0.41) infinite;
}
@-webkit-keyframes loader-outter {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes loader-outter {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@-webkit-keyframes loader-inner {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(-360deg);
transform: rotate(-360deg);
}
}
@keyframes loader-inner {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(-360deg);
transform: rotate(-360deg);
}
}
</style>
<div class="app-loading">
<div class="app-loading-wrap">
<div class="app-loading-title">
<img src="/logo.gif" class="app-loading-logo" alt="Logo" />
<div class="app-loading-title">%VITE_APP_TITLE%</div>
</div>
<div class="app-loading-item">
<div class="app-loading-outter"></div>
<div class="app-loading-inner"></div>
</div>
</div>
</div>
</div>
<script src="/Cesium/Cesium.js"></script>
<script>
window.CESIUM_BASE_URL = '/Cesium/'
</script>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

18131
package-lock.json

File diff suppressed because it is too large

164
package.json

@ -0,0 +1,164 @@
{
"name": "yudao-ui-admin-vue3",
"version": "2025.11-snapshot",
"description": "基于vue3、vite4、element-plus、typesScript",
"author": "xingyu",
"private": false,
"scripts": {
"i": "pnpm install",
"dev": "vite --mode env.local",
"dev-server": "vite --mode dev",
"ts:check": "vue-tsc --noEmit",
"build:local": "node --max_old_space_size=4096 ./node_modules/vite/bin/vite.js build",
"build:dev": "node --max_old_space_size=4096 ./node_modules/vite/bin/vite.js build --mode dev",
"build:test": "node --max_old_space_size=4096 ./node_modules/vite/bin/vite.js build --mode test",
"build:stage": "node --max_old_space_size=4096 ./node_modules/vite/bin/vite.js build --mode stage",
"build:prod": "node --max_old_space_size=4096 ./node_modules/vite/bin/vite.js build --mode prod",
"serve:dev": "vite preview --mode dev",
"serve:prod": "vite preview --mode prod",
"preview": "pnpm build:local && vite preview",
"clean": "npx rimraf node_modules",
"clean:cache": "npx rimraf node_modules/.cache",
"lint:eslint": "eslint --fix --ext .js,.ts,.vue ./src",
"lint:format": "prettier --write --loglevel warn \"src/**/*.{js,ts,json,tsx,css,less,scss,vue,html,md}\"",
"lint:style": "stylelint --fix \"./src/**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
"lint:lint-staged": "lint-staged -c "
},
"dependencies": {
"@element-plus/icons-vue": "2.3.2",
"@form-create/designer": "^3.2.6",
"@form-create/element-ui": "^3.2.11",
"@iconify/iconify": "^3.1.1",
"@microsoft/fetch-event-source": "^2.0.1",
"@videojs-player/vue": "^1.0.0",
"@vueuse/core": "^10.9.0",
"@wangeditor-next/editor": "^5.6.46",
"@wangeditor-next/editor-for-vue": "^5.1.14",
"@wangeditor-next/plugin-mention": "^1.0.16",
"@zxcvbn-ts/core": "^3.0.4",
"animate.css": "^4.1.1",
"axios": "1.9.0",
"benz-amr-recorder": "^1.1.5",
"bpmn-js-token-simulation": "^0.36.0",
"camunda-bpmn-moddle": "^7.0.1",
"cesium": "1.123",
"coordtransform": "^2.1.2",
"cropperjs": "^1.6.1",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.19",
"diagram-js": "^12.8.0",
"driver.js": "^1.3.1",
"echarts": "^5.5.0",
"echarts-wordcloud": "^2.1.0",
"element-plus": "2.11.1",
"fast-xml-parser": "^4.3.2",
"highlight.js": "^11.9.0",
"jsencrypt": "^3.3.2",
"jsoneditor": "^10.1.3",
"leaflet": "^1.9.4",
"leaflet-image": "^0.4.0",
"leaflet-rotatedmarker": "^0.2.0",
"lodash-es": "^4.17.21",
"markdown-it": "^14.1.0",
"markmap-common": "^0.16.0",
"markmap-lib": "^0.16.1",
"markmap-toolbar": "^0.17.0",
"markmap-view": "^0.16.0",
"min-dash": "^4.1.1",
"mitt": "^3.0.1",
"nprogress": "^0.2.0",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1",
"qrcode": "^1.5.3",
"qs": "^6.12.0",
"snabbdom": "^3.6.2",
"sortablejs": "^1.15.3",
"steady-xml": "^0.1.0",
"url": "^0.11.3",
"video.js": "^7.21.5",
"vue": "3.5.12",
"vue-dompurify-html": "^4.1.4",
"vue-i18n": "9.10.2",
"vue-router": "4.4.5",
"vue-types": "^5.1.1",
"vue3-print-nb": "^0.1.4",
"vue3-signature": "^0.2.4",
"vuedraggable": "^4.1.0",
"web-storage-cache": "^1.1.1",
"xml-js": "^1.6.11"
},
"devDependencies": {
"@commitlint/cli": "^19.0.1",
"@commitlint/config-conventional": "^19.0.0",
"@iconify/json": "^2.2.187",
"@intlify/unplugin-vue-i18n": "^2.0.0",
"@purge-icons/generated": "^0.9.0",
"@types/jsoneditor": "^9.9.5",
"@types/lodash-es": "^4.17.12",
"@types/node": "^20.11.21",
"@types/nprogress": "^0.2.3",
"@types/qrcode": "^1.5.5",
"@types/qs": "^6.9.12",
"@typescript-eslint/eslint-plugin": "^7.1.0",
"@typescript-eslint/parser": "^7.1.0",
"@unocss/eslint-config": "^0.57.4",
"@unocss/eslint-plugin": "66.1.0-beta.5",
"@unocss/transformer-variant-group": "^0.58.5",
"@vitejs/plugin-legacy": "^5.3.1",
"@vitejs/plugin-vue": "^5.0.4",
"@vitejs/plugin-vue-jsx": "^3.1.0",
"autoprefixer": "^10.4.17",
"bpmn-js": "^17.9.2",
"bpmn-js-properties-panel": "5.23.0",
"consola": "^3.2.3",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-define-config": "^2.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-vue": "^9.22.0",
"lint-staged": "^15.2.2",
"postcss": "^8.4.35",
"postcss-html": "^1.6.0",
"postcss-scss": "^4.0.9",
"prettier": "^3.2.5",
"prettier-eslint": "^16.3.0",
"rimraf": "^5.0.5",
"rollup": "^4.12.0",
"sass": "^1.69.5",
"stylelint": "^16.2.1",
"stylelint-config-html": "^1.1.0",
"stylelint-config-recommended": "^14.0.0",
"stylelint-config-standard": "^36.0.0",
"stylelint-order": "^6.0.4",
"terser": "^5.28.1",
"typescript": "5.3.3",
"unocss": "^0.58.5",
"unplugin-auto-import": "^0.16.7",
"unplugin-element-plus": "^0.8.0",
"unplugin-vue-components": "^0.25.2",
"vite": "5.1.4",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-ejs": "^1.7.0",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-progress": "^0.0.7",
"vite-plugin-purge-icons": "^0.10.0",
"vite-plugin-svg-icons-ng": "^1.3.1",
"vite-plugin-top-level-await": "^1.4.4",
"vue-eslint-parser": "^9.3.2",
"vue-tsc": "^1.8.27"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://gitee.com/yudaocode/yudao-ui-admin-vue3"
},
"bugs": {
"url": "https://gitee.com/yudaocode/yudao-ui-admin-vue3/issues"
},
"homepage": "https://gitee.com/yudaocode/yudao-ui-admin-vue3",
"web-types": "./web-types.json",
"engines": {
"node": ">= 16.0.0",
"pnpm": ">=8.6.0"
}
}

10844
pnpm-lock.yaml

File diff suppressed because it is too large

5
postcss.config.js

@ -0,0 +1,5 @@
module.exports = {
plugins: {
autoprefixer: {}
}
}

22
prettier.config.js

@ -0,0 +1,22 @@
module.exports = {
printWidth: 100, // 每行代码长度(默认80)
tabWidth: 2, // 每个tab相当于多少个空格(默认2)ab进行缩进(默认false)
useTabs: false, // 是否使用tab
semi: false, // 声明结尾使用分号(默认true)
vueIndentScriptAndStyle: false,
singleQuote: true, // 使用单引号(默认false)
quoteProps: 'as-needed',
bracketSpacing: true, // 对象字面量的大括号间使用空格(默认true)
trailingComma: 'none', // 多行使用拖尾逗号(默认none)
jsxSingleQuote: false,
// 箭头函数参数括号 默认avoid 可选 avoid| always
// avoid 能省略括号的时候就省略 例如x => x
// always 总是有括号
arrowParens: 'always',
insertPragma: false,
requirePragma: false,
proseWrap: 'never',
htmlWhitespaceSensitivity: 'strict',
endOfLine: 'auto',
rangeStart: 0
}

BIN
public.zip

Binary file not shown.

BIN
public/favicon.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
public/img/logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
public/img/故障.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 943 B

BIN
public/img/离线.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
public/logo.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

BIN
public/map/.index.vue.swp

Binary file not shown.

784
public/map/_index.vue

@ -0,0 +1,784 @@
<template>
<PublicMapComponents class="map-base" :defCenter="[38.8417433306111, 118.43845367431642]" :defZoom="12" />
<div class="base-div">
<div class="title-top">
<el-row>
<el-col :span="6">
<div class="title-name">
<span>曹妃甸港区船舶岸电监管平台</span>
</div>
<div style="text-align: center;">
<span class="title-data">
<span></span>
<span style="padding: 0 10px">累计用电{{ totalPower.toFixed(2) }} kWH</span>
<span></span>
</span>
</div>
</el-col>
<el-col :span="14">
<el-row>
<template v-for="(item, index) in dataTypeList" :key="index">
<el-col :span="24 / dataTypeList.length">
<div class="show-type-btn" :class="{ 'show-type-btn-active': activeButtonType === item.value }"
@click="updateButtonType(index + 1)">
{{ item.label }}
</div>
</el-col>
</template>
</el-row>
</el-col>
<el-col :span="4">
<div class="title-time">
<!-- <div class="title-time" @click="splitScreen = !splitScreen">-->
<!-- 日期 不能换行 换行后显示中多空格 -->
{{ timer.getFullYear() + '-' }}{{ ((timer.getMonth() + 1) > 9 ? (timer.getMonth() + 1) : ('0' +
(timer.getMonth() + 1))) + '-' }}{{ (timer.getDate() > 9 ? timer.getDate() : ('0' + timer.getDate())) }}
<!-- 时间 不能换行 换行后显示中多空格 -->
{{ (timer.getHours() > 9 ? timer.getHours() : ('0' + timer.getHours())) + ':' }}{{ (timer.getMinutes() > 9 ?
timer.getMinutes() : ('0' + timer.getMinutes())) + ':' }}{{ timer.getSeconds() > 9 ? timer.getSeconds() :
('0' + timer.getSeconds()) }}
</div>
</el-col>
</el-row>
</div>
<div style="margin-top: 20px">
<el-row>
<el-col :span="showData ? 15 : 24" v-if="!splitScreen">
<!-- <PublicMapComponents :defCenter="[38.8417433306111, 118.43845367431642]" :defZoom="12" v-if="showData"/>-->
</el-col>
<el-col :span="showData ? 9 : 16" v-if="splitScreen">
<PublicMapComponents mapName="left" :illustration="true" :defCenter="[38.95448645628942, 118.4517351374956]"
:defZoom="15" v-if="showData" />
<PublicMapComponents mapName="left" :illustration="true" :defCenter="[38.95448645628942, 118.4517351374956]"
:defZoom="15" v-if="!showData" />
</el-col>
<el-col :span="showData ? 6 : 8" v-if="splitScreen">
<PublicMapComponents mapName="right" :illustration="false"
:defCenter="[38.98524127752966, 118.45273520834166]" :defZoom="16" v-if="showData" />
<PublicMapComponents mapName="right" :illustration="false"
:defCenter="[38.98524127752966, 118.45273520834166]" :defZoom="16" v-if="!showData" />
</el-col>
<el-col :span="showData ? 9 : 0">
<div class="data-info">
<el-scrollbar height="100%">
<div v-show="activeButtonType === 1" class="data-card">
<el-row>
<el-col :span="8">
<div class="data-card-title">港口岸电使用情况</div>
</el-col>
<el-col :span="16">
<div class="data-date">
<span :class="dataDate == 1 ? 'date-button-using' : 'date-button'"
@click="updateDataDate(1)"></span>
<span :class="dataDate == 2 ? 'date-button-using' : 'date-button'"
@click="updateDataDate(2)"></span>
<span :class="dataDate == 3 ? 'date-button-using' : 'date-button'"
@click="updateDataDate(3)"></span>
<span :class="dataDate == 4 ? 'date-button-using' : 'date-button'"
@click="updateDataDate(4)"></span>
<span :class="dataDate == 5 ? 'date-button-using' : 'date-button'"
@click="updateDataDate(5)">全部</span>
</div>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="24">
<div :class="dataType == 1 ? 'data-info-one-using' : 'data-info-one'" @click="dataType = 1">
<div class="data-card-content-big">{{ usedDl.toFixed(2) }}</div>
<div class="data-card-name">累计用电千瓦时</div>
</div>
</el-col>
<el-col :span="12">
<div :class="dataType == 2 ? 'data-info-one-using' : 'data-info-one'" @click="dataType = 2">
<div class="data-card-content">{{ (usedDl / 7936.84).toFixed(2) }}</div>
<div class="data-card-name">减少燃油</div>
</div>
</el-col>
<el-col :span="12">
<div :class="dataType == 3 ? 'data-info-one-using' : 'data-info-one'" @click="dataType = 3">
<div class="data-card-content">{{ (usedDl / 1.4925373).toFixed(2) }}</div>
<div class="data-card-name">减少二氧化碳排放千克</div>
</div>
</el-col>
<el-col :span="8">
<div :class="dataType == 4 ? 'data-info-one-using' : 'data-info-one'" @click="dataType = 4">
<div class="data-card-content">{{ (usedDl / 684.93132534).toFixed(2) }}</div>
<div class="data-card-name">减少PM2.5排放千克</div>
</div>
</el-col>
<el-col :span="8">
<div :class="dataType == 5 ? 'data-info-one-using' : 'data-info-one'" @click="dataType = 5">
<div class="data-card-content">{{ (usedDl / 55.24862549).toFixed(2) }}</div>
<div class="data-card-name">减少氮氧化物千克</div>
</div>
</el-col>
<el-col :span="8">
<div :class="dataType == 6 ? 'data-info-one-using' : 'data-info-one'" @click="dataType = 6">
<div class="data-card-content">{{ (usedDl / 95.238).toFixed(2) }}</div>
<div class="data-card-name">减少二氧化硫千克</div>
</div>
</el-col>
</el-row>
</div>
<div v-show="activeButtonType === 2" class="data-card">
<el-row>
<el-col :span="10">
<div class="data-card-title" v-if="dataType == 1">港口企业岸电使用</div>
<div class="data-card-title" v-else-if="dataType == 2">港口企业燃油消耗减少</div>
<div class="data-card-title" v-else-if="dataType == 3">港口企业二氧化碳排放减少</div>
<div class="data-card-title" v-else-if="dataType == 4">港口企业PM2.5排放减少</div>
<div class="data-card-title" v-else-if="dataType == 5">港口企业氮氧化物排放减少</div>
<div class="data-card-title" v-else-if="dataType == 6">港口企业二氧化硫排放减少</div>
</el-col>
<el-col :span="14">
<div class="data-date">
<span :class="dataDate == 1 ? 'date-button-using' : 'date-button'"
@click="updateDataDate(1)"></span>
<span :class="dataDate == 2 ? 'date-button-using' : 'date-button'"
@click="updateDataDate(2)"></span>
<span :class="dataDate == 3 ? 'date-button-using' : 'date-button'"
@click="updateDataDate(3)"></span>
<span :class="dataDate == 4 ? 'date-button-using' : 'date-button'"
@click="updateDataDate(4)"></span>
<span :class="dataDate == 5 ? 'date-button-using' : 'date-button'"
@click="updateDataDate(5)">全部</span>
<span class="date-button" v-show="showBerth" @click="updateBerth({})">返回</span>
</div>
</el-col>
</el-row>
<div class="data-card-black">
<div v-for="(item, index) in dockShowData" :key="index">
<div @click="updateBerth(item)">
<el-row>
<el-col class="data-progress-data" :span="6">
<div class="data-progress-name">{{ item.name }}</div>
</el-col>
<el-col class="data-progress-data" :span="18">
<div>
<el-progress :text-inside="true" :stroke-width="18" :percentage="item.value * 200"
status="success">
<span>
{{ (usedDl * item.value / (dataType == 1 ? 1 : dataType == 2 ? 7936.84 : dataType == 3 ?
1.4925373 : dataType == 4 ? 684.93132534 : dataType == 5 ? 55.24862549 : dataType == 6 ?
95.238 : 1)).toFixed(2) }}
{{ dataType == 1 ? ' kWH' : dataType == 2 ? ' t' : ' kg' }}
</span>
</el-progress>
</div>
</el-col>
</el-row>
</div>
</div>
<div class="data-card-black">
<div v-for="(item, index) in usingData" :key="index">
<el-row>
<el-col :span="4">
<div class="ship-info-info-green">{{ item.dock }}</div>
</el-col>
<el-col :span="5">
<div class="ship-info-info-green">{{ item.berth }}</div>
</el-col>
<el-col :span="7">
<div class="ship-info-info-green">{{ item.name }}</div>
</el-col>
<el-col :span="4">
<div class="ship-info-info-green">正在用电</div>
</el-col>
<el-col :span="4">
<div class="ship-info-info-green">{{ item.used }}</div>
</el-col>
</el-row>
</div>
</div>
</div>
</div>
<div v-show="activeButtonType === 3" class="data-card">
<el-row>
<el-col :span="10">
<div class="data-card-title">船舶岸电使用情况</div>
</el-col>
<el-col :span="14">
<div class="data-date">
<span :class="dataDate == 1 ? 'date-button-using' : 'date-button'"
@click="updateDataDate(1)"></span>
<span :class="dataDate == 2 ? 'date-button-using' : 'date-button'"
@click="updateDataDate(2)"></span>
<span :class="dataDate == 3 ? 'date-button-using' : 'date-button'"
@click="updateDataDate(3)"></span>
<span :class="dataDate == 4 ? 'date-button-using' : 'date-button'"
@click="updateDataDate(4)"></span>
<span :class="dataDate == 5 ? 'date-button-using' : 'date-button'"
@click="updateDataDate(5)">全部</span>
</div>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="8">
<div :class="showShipData == 1 ? 'data-info-one-using' : 'data-info-one'"
@click="showShipData = showShipData == 1 ? 0 : 1">
<div class="data-card-content">13</div>
<div class="data-card-name-big">在泊船舶数量</div>
</div>
</el-col>
<el-col :span="8">
<div :class="showShipData == 2 ? 'data-info-one-using' : 'data-info-one'"
@click="showShipData = showShipData == 2 ? 0 : 2">
<div class="data-card-content">3</div>
<div class="data-card-name-big">使用岸电船舶数量</div>
</div>
</el-col>
<el-col :span="8">
<div :class="showShipData == 3 ? 'data-info-one-using' : 'data-info-one'"
@click="showShipData = showShipData == 3 ? 0 : 3">
<div class="data-card-content">10</div>
<div class="data-card-name-big">未使用岸电船舶数量</div>
</div>
</el-col>
<el-col :span="24">
<div v-if="showShipData != 0" class="ship-info-card">
<div class="ship-info">
<div class="ship-info-title" @click="showShipData = 0">收起 </div>
<el-scrollbar height="180px">
<div v-for="(item, index) in shipData" :key="index">
<div
v-if="showShipData == 1 || (showShipData == 2 && item.reasonType == 1) || (showShipData == 3 && (item.reasonType == 2 || item.reasonType == 3))">
<el-popover :title="item.name" placement="left" :width="430">
<template #reference>
<el-row>
<el-col :span="3">
<div
:class="item.reasonType == 1 ? 'ship-info-green' : item.reasonType == 2 ? 'ship-info-yellow' : 'ship-info-red'">
{{ item.name }}</div>
</el-col>
<el-col :span="5">
<div
:class="item.reasonType == 1 ? 'ship-info-green' : item.reasonType == 2 ? 'ship-info-yellow' : 'ship-info-red'">
{{ item.dock }}</div>
</el-col>
<el-col :span="3">
<div
:class="item.reasonType == 1 ? 'ship-info-green' : item.reasonType == 2 ? 'ship-info-yellow' : 'ship-info-red'">
{{ item.berth }}</div>
</el-col>
<el-col :span="13">
<div
:class="item.reasonType == 1 ? 'ship-info-green' : item.reasonType == 2 ? 'ship-info-yellow' : 'ship-info-red'">
{{ item.reason }}</div>
</el-col>
</el-row>
</template>
<div>
<div class="ship-info-info-default">船名{{ item.name }}</div>
<div
:class="item.reasonType == 1 ? 'ship-info-info-green' : item.reasonType == 2 ? 'ship-info-info-yellow' : 'ship-info-info-red'">
岸电使用情况{{ item.reason }}</div>
<div class="ship-info-info-default">船舶信息{{ item.info }}</div>
</div>
</el-popover>
</div>
</div>
</el-scrollbar>
</div>
</div>
</el-col>
</el-row>
</div>
<!-- <div class="data-card">
<div class="data-card-title" style="text-align: center;">船舶使用岸电情况</div>
<el-row :gutter="10">
<el-col :span="6">
<div class="data-info-one" style="cursor: default;">
<div class="data-card-content">20</div>
<div class="data-card-name">岸电设备数量</div>
</div>
</el-col>
<el-col :span="6">
<div class="data-info-one" style="cursor: default;">
<div class="data-card-content">10</div>
<div class="data-card-name">在泊船舶数量</div>
</div>
</el-col>
<el-col :span="6">
<div class="data-info-one" style="cursor: default;">
<div class="data-card-content">3</div>
<div class="data-card-name">岸电使用船舶数量</div>
</div>
</el-col>
<el-col :span="6">
<div class="data-info-one" style="cursor: default;">
<div class="data-card-content">1</div>
<div class="data-card-name">故障/维护岸电设施数量</div>
</div>
</el-col>
</el-row>
</div>-->
</el-scrollbar>
</div>
</el-col>
</el-row>
</div>
</div>
</template>
<script setup lang="ts">
import PublicMapComponents from './components/index.vue'
defineOptions({ name: 'PublicMap' })
const timer = ref(new Date())
const getTime = () => {
timer.value = new Date()
setTimeout(() => {
getTime()
}, 100)
}
const splitScreen = ref(false)
const showData = ref(true)
const dataDate = ref<number>(1)
let defaultDlData = ref<number>(835229.91)
let totalPower = ref<number>(1492774 + defaultDlData.value)
let usedDl = ref<number>(defaultDlData.value * dataDate.value)
const buildDlData = () => {
setTimeout(() => {
buildDlData()
}, 5000)
defaultDlData.value = (defaultDlData.value + Math.random() * 10 + 30)
totalPower.value = (1492774 + defaultDlData.value * 1)
usedDl.value = (defaultDlData.value * dataDate.value)
}
const updateDataDate = (number: number) => {
if (number == 5) {
window.open("http://server.ayaojies.com.cn:801/login", '_blank');
} else {
dataDate.value = number
usedDl.value = 835229.9 * number
}
}
const dataType = ref<number>(1)
const dataTypeList = ref<{ value: number, label: string }[]>([
{ value: 1, label: '港口岸电使用情况' },
{ value: 2, label: '港口企业岸电使用' },
{ value: 3, label: '船舶岸电使用情况' },
])
const dockData = ref([
{
index: 1, name: '华能码头', value: 0.278, berth: [
{ index: 1, name: '1泊位', value: 0.25 },
{ index: 2, name: '2泊位', value: 0.15 },
{ index: 3, name: '3泊位', value: 0.1 },
{ index: 4, name: '4泊位', value: 0.4 },
{ index: 5, name: '5泊位', value: 0.1 },
]
},
{
index: 2, name: '国投码头', value: 0.495, berth: [
{ index: 1, name: '201泊位', value: 0.25 },
{ index: 2, name: '202泊位', value: 0.15 },
{ index: 3, name: '203泊位', value: 0.1 },
{ index: 4, name: '204泊位', value: 0.4 },
{ index: 5, name: '205泊位', value: 0.1 },
{ index: 1, name: '206泊位', value: 0.25 },
{ index: 2, name: '207泊位', value: 0.15 },
{ index: 3, name: '208泊位', value: 0.1 },
{ index: 4, name: '209泊位', value: 0.4 },
{ index: 5, name: '210泊位', value: 0.1 },
]
},
{
index: 3, name: '华电码头(储运)', value: 0.227, berth: [
{ index: 1, name: '806泊位', value: 0.25 },
{ index: 2, name: '807泊位', value: 0.15 },
{ index: 3, name: '808泊位', value: 0.1 },
{ index: 4, name: '809泊位', value: 0.4 },
{ index: 5, name: '810泊位', value: 0.1 },
]
}
])
const dockShowData = ref(dockData.value)
let showBerth = ref(false)
const updateBerth = (item: any) => {
if (showBerth.value) {
showBerth.value = false
dockShowData.value = dockData.value
}
if (!showBerth.value) {
if (item.berth) {
showBerth.value = true
dockShowData.value = item.berth
}
}
}
const usingData = ref([
{ index: 1, name: '华元503', dock: '华能码头', berth: '3泊位', used: '2479kWH' },
{ index: 1, name: '和泰通10', dock: '华电码头(储运)', berth: '810泊位', used: '2192kWH' },
{ index: 1, name: '永宁2', dock: '华电码头(储运)', berth: '808泊位', used: '692kWH' },
])
const shipData = ref([
{ index: 1, name: '华元503', dock: '华能码头', berth: '3泊位', reasonType: 1, reason: '使用岸电21小时45分钟,2479kWH', info: { info: '这里可以显示船舶的信息' } },
{ index: 2, name: '信洋新征程', dock: '国投码头', berth: '202#泊位', reasonType: 2, reason: '无受电设备', info: { info: '这里可以显示船舶的信息' } },
{ index: 3, name: '华海航2', dock: '华电码头(储运)', berth: '806泊位', reasonType: 2, reason: '船电设施损坏,预计2025-11-30恢复', info: { info: '这里可以显示船舶的信息' } },
{ index: 4, name: '东亿603', dock: '国投码头', berth: '205#泊位', reasonType: 2, reason: '电缆长度不匹配', info: { info: '这里可以显示船舶的信息' } },
{ index: 5, name: '东成山', dock: '华能码头', berth: '2泊位', reasonType: 2, reason: '岸电用电接口不匹配', info: { info: '这里可以显示船舶的信息' } },
{ index: 6, name: '中茂98', dock: '华电码头(储运)', berth: '807泊位', reasonType: 3, reason: '岸电设施维护中', info: { info: '这里可以显示船舶的信息' } },
{ index: 7, name: '舜华', dock: '国投码头', berth: '203#泊位', reasonType: 2, reason: '岸电用电接口不匹配', info: { info: '这里可以显示船舶的信息' } },
{ index: 8, name: '浙海温州', dock: '华能码头', berth: '5泊位', reasonType: 2, reason: '岸电设施电压/频率不匹配', info: { info: '这里可以显示船舶的信息' } },
{ index: 9, name: '浙能2', dock: '华能码头', berth: '1泊位', reasonType: 2, reason: '无受电设备', info: { info: '这里可以显示船舶的信息' } },
{ index: 10, name: '华润电力2', dock: '国投码头', berth: '201#泊位', reasonType: 3, reason: '气象因素禁止作业', info: { info: '这里可以显示船舶的信息' } },
{ index: 11, name: '和泰通10', dock: '华电码头(储运)', berth: '810泊位', reasonType: 1, reason: '使用岸电17小时45分钟,2192kWH', info: { info: '这里可以显示船舶的信息' } },
{ index: 12, name: '永宁2', dock: '华电码头(储运)', berth: '808泊位', reasonType: 1, reason: '使用岸电3小时15分钟,692kWH', info: { info: '这里可以显示船舶的信息' } },
{ index: 13, name: '盛达海', dock: '华能码头', berth: '4泊位', reasonType: 3, reason: '岸电设施维护中', info: { info: '这里可以显示船舶的信息' } },
{ index: 13, name: '新世纪128', dock: '国投码头', berth: '201#泊位', reasonType: 2, reason: '岸电设施电压/频率不匹配', info: { info: '这里可以显示船舶的信息' } },
{ index: 13, name: '太行128', dock: '国投码头', berth: '206#泊位', reasonType: 2, reason: '无受电设备', info: { info: '这里可以显示船舶的信息' } },
{ index: 13, name: '东和明16', dock: '国投码头', berth: '208#泊位', reasonType: 2, reason: '岸电设施电压/频率不匹配', info: { info: '这里可以显示船舶的信息' } },
{ index: 13, name: '华鲁海1', dock: '华电码头(储运)', berth: '809泊位', reasonType: 2, reason: '拒绝使用岸电', info: { info: '这里可以显示船舶的信息' } },
{ index: 13, name: '安永山', dock: '国投码头', berth: '204#泊位', reasonType: 2, reason: '岸电设施电压/频率不匹配', info: { info: '这里可以显示船舶的信息' } },
{ index: 13, name: '华盛151', dock: '国投码头', berth: '209#泊位', reasonType: 2, reason: '电缆长度不匹配', info: { info: '这里可以显示船舶的信息' } },
])
const showShipData = ref<number>(1)
const activeButtonType = ref<number>(1)
const updateButtonType = (type: number) => {
activeButtonType.value = type
//
console.log('切换到按钮类型:', type)
}
/** 初始化 **/
onMounted(() => {
getTime()
buildDlData()
})
</script>
<style scoped lang="scss">
/* .map-base {
position: fixed;
top: 0;
left: 0;
z-index: -999;
width: 100%;
height: 100%;
background-color: rgba(30, 30, 30, 0.7);
} */
.map-base:hover {
z-index: 999;
}
.base-div {
height: 100%;
//background-color: rgba(10, 50, 100, 1);
padding: 15px;
.title-top {
position: fixed;
top: 20px;
left: 30px;
right: 30px;
width: calc(100% - 60px);
padding: 30px;
border-radius: 10px;
background-color: rgba(255, 255, 255, 0.15);
box-shadow: 0 0 15px rgba(0, 0, 0, 0.3);
.title-time {
font-size: 20px;
color: rgba(20, 200, 255, 0.8);
font-weight: bold;
text-align: right;
}
.title-name {
font-size: 28px;
color: rgba(20, 200, 255, 1);
text-align: center;
font-weight: bold;
line-height: 30px;
text-shadow: 0 0 5px rgba(0, 0, 0, 1), 0 0 6px rgba(20, 200, 255, 0.3), 0 0 7px rgba(20, 200, 255, 0.2), 0 0 8px rgba(20, 200, 255, 0.1);
}
.show-type-btn {
margin: 5px;
padding: 8px 12px;
border: 1px solid rgba(10, 180, 240, 1);
border-radius: 5px;
font-size: 14px;
color: rgba(10, 180, 240, 1);
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
}
.show-type-btn-active {
background-color: rgba(10, 180, 240, 1);
color: rgba(0, 0, 0, 1);
font-weight: bold;
}
.show-type-btn:hover {
background-color: rgba(10, 180, 240, 0.2);
transform: translateY(-2px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
.show-type-btn-active:hover {
background-color: rgba(10, 180, 240, 1);
transform: none;
box-shadow: none;
}
.title-data {
padding: 8px 15px;
font-size: 20px;
color: rgba(20, 200, 255, 0.8);
font-weight: bold;
text-align: right;
border-radius: 10px;
&:active {
box-shadow: 0 0 15px 5px rgba(20, 200, 255, 0.3), 0 0 25px 10px rgba(20, 200, 255, 0.1);
}
}
}
.data-info {
position: fixed;
top: 100px;
right: 0px;
width: calc(40%);
padding: 30px;
border-radius: 10px;
margin-left: 10px;
.data-card-black {
margin-top: 15px;
padding: 10px;
background-color: rgba(0, 0, 0, 0.15);
border-radius: 10px;
}
.data-card {
margin-top: 15px;
padding: 10px;
background-color: rgba(255, 255, 255, 0.15);
border-radius: 10px;
.data-card-title {
font-size: 18px;
color: rgba(10, 180, 240, 1);
font-weight: bold;
line-height: 30px;
text-shadow: 0 0 3px rgba(0, 0, 0, 1), 0 0 4px rgba(20, 200, 255, 0.3), 0 0 5px rgba(20, 200, 255, 0.2), 0 0 6px rgba(20, 200, 255, 0.1);
}
.data-date {
text-align: right;
margin: 2px;
.date-button {
margin: 6px;
padding: 4px 8px;
border: 1px solid rgba(10, 180, 240, 1);
border-radius: 5px;
font-size: 12px;
color: rgba(10, 180, 240, 1);
cursor: pointer;
}
.date-button-using {
margin: 6px;
padding: 4px 8px;
background-color: rgba(10, 180, 240, 1);
border: 1px solid rgba(10, 180, 240, 1);
border-radius: 5px;
font-size: 12px;
color: rgba(0, 0, 0, 1);
cursor: default;
}
}
.data-info-one {
margin-top: 10px;
padding: 10px;
text-align: center;
background-color: rgba(10, 30, 30, 0.2);
border: 1px solid rgba(10, 130, 170, 1);
border-radius: 10px;
cursor: pointer;
.data-card-content-big {
font-size: 30px;
color: rgba(10, 180, 240, 1);
font-weight: bold;
line-height: 30px;
text-shadow: 0 0 3px rgba(0, 0, 0, 1), 0 0 4px rgba(20, 200, 255, 0.3), 0 0 5px rgba(20, 200, 255, 0.2), 0 0 6px rgba(20, 200, 255, 0.1);
}
.data-card-content {
font-size: 25px;
color: rgba(10, 180, 240, 1);
font-weight: bold;
line-height: 25px;
text-shadow: 0 0 3px rgba(0, 0, 0, 1), 0 0 4px rgba(20, 200, 255, 0.3), 0 0 5px rgba(20, 200, 255, 0.2), 0 0 6px rgba(20, 200, 255, 0.1);
}
.data-card-name {
margin-top: 5px;
font-size: 10px;
color: rgba(200, 200, 200, 1);
}
.data-card-name-big {
margin-top: 5px;
font-size: 16px;
color: rgba(240, 240, 240, 1);
}
}
.data-info-one-using {
margin-top: 10px;
padding: 10px;
text-align: center;
background-color: rgba(30, 190, 240, 0.2);
border: 1px solid rgba(50, 200, 255, 1);
border-radius: 10px;
cursor: pointer;
.data-card-content-big {
font-size: 30px;
color: rgba(10, 180, 240, 1);
font-weight: bold;
line-height: 30px;
text-shadow: 0 0 3px rgba(0, 0, 0, 1), 0 0 4px rgba(20, 200, 255, 0.3), 0 0 5px rgba(20, 200, 255, 0.2), 0 0 6px rgba(20, 200, 255, 0.1);
}
.data-card-content {
font-size: 25px;
color: rgba(10, 180, 240, 1);
font-weight: bold;
line-height: 25px;
text-shadow: 0 0 3px rgba(0, 0, 0, 1), 0 0 4px rgba(20, 200, 255, 0.3), 0 0 5px rgba(20, 200, 255, 0.2), 0 0 6px rgba(20, 200, 255, 0.1);
}
.data-card-name {
margin-top: 5px;
font-size: 10px;
color: rgba(200, 200, 200, 1);
}
.data-card-name-big {
margin-top: 5px;
font-size: 16px;
color: rgba(240, 240, 240, 1);
}
}
.data-progress-name {
font-size: 15px;
color: rgba(10, 180, 240, 1);
font-weight: bold;
line-height: 15px;
text-shadow: 0 0 3px rgba(0, 0, 0, 1), 0 0 4px rgba(20, 200, 255, 0.3), 0 0 5px rgba(20, 200, 255, 0.2), 0 0 6px rgba(20, 200, 255, 0.1);
cursor: pointer;
}
.data-progress-data {
margin-top: 10px;
margin-bottom: 5px;
cursor: pointer;
}
.ship-info-card {
margin-top: 10px;
font-size: 15px;
line-height: 30px;
.ship-info-title {
padding: 10px;
text-align: center;
font-size: 15px;
color: rgba(10, 180, 240, 1);
font-weight: 400;
line-height: 15px;
border: 1px solid rgba(10, 180, 240, 1);
border-radius: 7px;
cursor: pointer;
}
.ship-info {
border-radius: 10px;
border: 1px solid rgba(10, 180, 240, 1);
background-color: rgba(10, 30, 30, 0.2);
.ship-info-green {
margin-left: 10px;
color: rgba(50, 255, 100, 1);
}
.ship-info-red {
margin-left: 10px;
color: rgba(255, 70, 70, 1);
}
.ship-info-yellow {
margin-left: 10px;
color: rgba(255, 150, 0, 1);
}
}
}
}
}
}
.ship-info-info-default {
margin-left: 10px;
margin-bottom: 5px;
color: rgba(50, 50, 50, 1);
}
.ship-info-info-green {
margin-left: 10px;
margin-bottom: 5px;
color: rgba(50, 200, 80, 1);
}
.ship-info-info-red {
margin-left: 10px;
margin-bottom: 5px;
color: rgba(255, 70, 70, 1);
}
.ship-info-info-yellow {
margin-left: 10px;
margin-bottom: 5px;
color: rgba(255, 150, 0, 1);
}
</style>

182
public/map/components/BarChart.vue

@ -0,0 +1,182 @@
<template>
<div ref="chartContainer" class="bar-chart-container"></div>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
import * as echarts from 'echarts'
//
interface Props {
chartData?: Array<{ name: string; value: number }>
title?: string
color?: string
}
const props = withDefaults(defineProps<Props>(), {
chartData: () => [],
title: '',
color: '#1296db'
})
//
const chartContainer = ref<HTMLDivElement | null>(null)
let chartInstance: echarts.ECharts | null = null
//
const initChart = () => {
if (chartContainer.value) {
chartInstance = echarts.init(chartContainer.value)
updateChart()
}
}
//
const updateChart = () => {
if (!chartInstance || !props.chartData.length) return
//
const sortedData = [...props.chartData].sort((a, b) => b.value - a.value)
const option = {
title: {
// text: props.title,
textStyle: {
color: '#fff',
fontSize: 14
},
left: 'center'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
backgroundColor: 'rgba(0, 0, 0, 0.7)',
borderColor: 'rgba(30, 120, 255, 0.4)',
borderWidth: 1,
textStyle: {
color: '#fff'
}
},
grid: {
left: '5%',
right: '5%',
top: '5%',
bottom: '5%',
containLabel: true
},
xAxis: {
type: 'category',
data: sortedData.map(item => item.name),
axisLine: {
lineStyle: {
color: 'rgba(255, 255, 255, 0.3)'
}
},
axisLabel: {
color: 'rgba(255, 255, 255, 0.7)',
rotate: 0,
interval: 0,
margin: 15
}
},
yAxis: {
type: 'value',
show: false,
axisLine: {
show: false
},
splitLine: {
show: false
},
axisLabel: {
show: false
}
},
series: [
{
type: 'bar',
data: sortedData.map(item => item.value),
barWidth: '40%',
itemStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: props.color
},
{
offset: 1,
color: props.color + '80'
}
]
},
borderRadius: [4, 4, 0, 0]
},
emphasis: {
itemStyle: {
color: props.color
}
},
label: {
show: true,
position: 'top',
color: '#fff',
formatter: '{c}',
fontSize: 12,
overflow: 'truncate',
width: 80,
ellipsis: '...'
}
}
]
}
chartInstance.setOption(option)
}
//
watch(
() => props.chartData,
() => {
updateChart()
},
{ deep: true }
)
//
const handleResize = () => {
if (chartInstance) {
chartInstance.resize()
}
}
//
onMounted(() => {
initChart()
window.addEventListener('resize', handleResize)
})
//
onBeforeUnmount(() => {
window.removeEventListener('resize', handleResize)
if (chartInstance) {
chartInstance.dispose()
chartInstance = null
}
})
</script>
<style scoped>
.bar-chart-container {
width: 100%;
height: 100%;
min-height: 200px;
}
</style>

165
public/map/components/LineChart.vue

@ -0,0 +1,165 @@
<template>
<div ref="chartContainer" class="line-chart-container"></div>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
import * as echarts from 'echarts'
//
interface Props {
chartData?: Array<{ name: string; value: number }>
title?: string
color?: string
}
const props = withDefaults(defineProps<Props>(), {
chartData: () => [],
title: '',
color: '#1296db'
})
//
const chartContainer = ref<HTMLDivElement | null>(null)
let chartInstance: echarts.ECharts | null = null
//
const initChart = () => {
if (chartContainer.value) {
chartInstance = echarts.init(chartContainer.value)
updateChart()
}
}
//
const updateChart = () => {
if (!chartInstance || !props.chartData.length) return
const option = {
title: {
text: props.title,
textStyle: {
color: '#fff',
fontSize: 14
},
left: 'center'
},
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(0, 0, 0, 0.7)',
borderColor: 'rgba(30, 120, 255, 0.4)',
borderWidth: 1,
textStyle: {
color: '#fff'
}
},
xAxis: {
type: 'category',
data: props.chartData.map(item => item.name),
axisLine: {
lineStyle: {
color: 'rgba(255, 255, 255, 0.3)'
}
},
axisLabel: {
color: 'rgba(255, 255, 255, 0.7)'
}
},
yAxis: {
type: 'value',
axisLine: {
lineStyle: {
color: 'rgba(255, 255, 255, 0.3)'
}
},
splitLine: {
lineStyle: {
color: 'rgba(255, 255, 255, 0.1)'
}
},
axisLabel: {
color: 'rgba(255, 255, 255, 0.7)'
}
},
series: [
{
data: props.chartData.map(item => item.value),
type: 'line',
smooth: true,
showSymbol: false,
lineStyle: {
color: props.color,
width: 2
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: props.color + '80' //
},
{
offset: 1,
color: props.color + '00' //
}
]
}
}
}
],
grid: {
left: '5%',
right: '5%',
top: '15%',
bottom: '10%',
containLabel: true
}
}
chartInstance.setOption(option)
}
//
watch(
() => props.chartData,
() => {
updateChart()
},
{ deep: true }
)
//
const handleResize = () => {
if (chartInstance) {
chartInstance.resize()
}
}
//
onMounted(() => {
initChart()
window.addEventListener('resize', handleResize)
})
//
onBeforeUnmount(() => {
window.removeEventListener('resize', handleResize)
if (chartInstance) {
chartInstance.dispose()
chartInstance = null
}
})
</script>
<style scoped>
.line-chart-container {
width: 100%;
height: 100%;
min-height: 200px;
}
</style>

126
public/map/components/PieChart.vue

@ -0,0 +1,126 @@
<template>
<div ref="chartContainer" class="pie-chart-container"></div>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
import * as echarts from 'echarts'
// props
interface Props {
chartData?: Array<{ name: string; value: number }>
title?: string
}
const props = withDefaults(defineProps<Props>(), {
chartData: () => [],
title: ''
})
const chartContainer = ref<HTMLDivElement | null>(null)
let chartInstance: echarts.ECharts | null = null
//
const initChart = () => {
if (chartContainer.value) {
chartInstance = echarts.init(chartContainer.value)
updateChart()
}
}
//
const updateChart = () => {
if (!chartInstance || !props.chartData) return
const option = {
title: {
// text: props.title,
left: 'center',
textStyle: {
color: '#FFF'
}
},
tooltip: {
trigger: 'item'
},
legend: {
show: true,
type: 'scroll', // 👈
orient: 'vertical', // 👈
right: 10,
top: 'center',
textStyle: {
color: '#FFF'
}
},
series: [
{
name: props.title,
type: 'pie',
radius: ['30%', '50%'],
center: ['35%', '50%'], // 👈
avoidLabelOverlap: true,
itemStyle: {
borderRadius: 0,
borderColor: '#fff',
borderWidth: 1
},
label: {
show: true,
formatter: '{b}\n{d}%',
fontSize: 10
},
emphasis: {
label: {
show: true,
fontSize: 14,
fontWeight: 'bold'
}
},
labelLine: {
show: true
},
data: props.chartData
}
]
}
chartInstance.setOption(option, true)
}
//
const handleResize = () => {
if (chartInstance) {
chartInstance.resize()
}
}
//
watch(
() => props.chartData,
() => {
updateChart()
},
{ deep: true }
)
//
onMounted(() => {
initChart()
window.addEventListener('resize', handleResize)
})
onBeforeUnmount(() => {
if (chartInstance) {
chartInstance.dispose()
}
window.removeEventListener('resize', handleResize)
})
</script>
<style scoped>
.pie-chart-container {
width: 100%;
height: 100%;
}
</style>

679
public/map/components/_index.vue

@ -0,0 +1,679 @@
<template>
<div>
<div class="parent">
<div class="mapParent">
<div :id="defaultData.mapName" class="my-map"></div>
</div>
<div class="child">
<div class="text-title">状态图示</div>
<div>
<div class="circle circle-green"></div>
<div class="text-info text-info-green">正在使用岸电</div>
</div>
<div>
<div class="circle circle-yellow"></div>
<div class="text-info text-info-yellow">船方原因未使用岸电</div>
</div>
<div>
<div class="circle circle-red"></div>
<div class="text-info text-info-red">岸方原因未使用岸电</div>
</div>
</div>
<!-- <div class="child" v-if="showButton && usingData.status === 2">-->
<!-- <el-button class="text-title" style="width:100%;background-color: rgba(255, 255, 255, 0);text-align: center;border: none;" @click="updateStatus">确认该事件</el-button>-->
<!-- </div>-->
</div>
</div>
</template>
<script setup lang="ts">
import interface_red from '@/assets/svgs/svg/岸电箱-维护保养.png'
import interface_yellow from '@/assets/svgs/svg/用电接口-黄色.gif'
import interface_blue from '@/assets/svgs/svg/岸电箱-正常.png'
import interface_green from '@/assets/svgs/svg/用电接口-绿色.svg'
import ship_red from '@/assets/svgs/svg/船-红色.gif'
import ship_yellow from '@/assets/svgs/svg/船-黄色.svg'
import ship_blue from '@/assets/svgs/svg/船-蓝色.svg'
import ship_green from '@/assets/svgs/svg/船-绿色.png'
import ship_brown from '@/assets/svgs/svg/船-棕色.svg'
import ship_problem from '@/assets/svgs/svg/船-问号.gif'
import shorepower_blue from '@/assets/svgs/svg/配电室.png'
import shorepower_green from '@/assets/svgs/svg/岸电设施-绿色.svg'
import problem from '@/assets/svgs/svg/问号-棕色.svg'
import problem_red from '@/assets/svgs/svg/问号-红色.svg'
import L from "leaflet";
import "leaflet/dist/leaflet.css";
import "leaflet-rotatedmarker";
import {MapApi} from "@/api/shorepower/map";
import baseInfo from "@/views/shorepower/map/baseMapInfo";
defineOptions({ name: 'PublicMapComponents' })
// const getShorePowerEquipmentStr = (icon: any) => {
// return `<div class="table-message">
// <div class="table-name">${icon.info.name}</div>
// <div class="table-line"></div>
// <div class="base-table-info">
// <span class='table-info'> ${icon.info.name}</span></br>
// <span class='table-info'>${icon.info.installedPower}</span></br>
// <span class='table-info'> ${icon.info.voltage}</span></br>
// <span class='table-info'>${icon.info.frequency}</span></br>
// <span class='table-info'> ${icon.info.interfaceCount}</span></br>
// <span class='table-info'>${icon.info.simultaneouslyInterfaceCount}</span></br>
// </div>
// </div>`
// }
// const getBerthStr = (icon: any) => {
// return `
// <div class="table-message">
// <div class="table-name">${icon.info.berthName}</div>
// <div class="table-line"></div>
// <div class="base-table-info">
// <span class='table-info'> ${icon.info.berthName}</span></br>
// <span class='table-info'>${icon.info.totalPower}</span></br>
// <span class='table-info'> ${icon.info.updateTime}</span></br>
// <span class='table-info'>${icon.info.status}</span></br>
// <span class='table-info'> ${icon.info.InterfaceType}</span></br>
// <span class='table-info'>${icon.info.berthDepth}</span></br>
// </div>
// </div>`
// }
// const getShipStr = (icon: any) => {
// return `
// <div class="table-message">
// <div class="table-name">${icon.info.shipName}</div>
// <div class="table-line"></div>
// <div class="base-table-info">
// <span class='table-info'> ${icon.info.shipName}</span></br>
// <span class='table-info'>${icon.info.callSign}</span></br>
// <span class="table-info">:${icon.info.shipCompany}</span></br>
// <span class="table-info">:${icon.info.phone}</span></br>
// <span class='table-info'> ${icon.info.length}</span></br>
// <span class='table-info'>${icon.info.width}</span></br>
// <span class='table-info'> ${icon.info.tonnage}</span></br>
// <span class='table-info'>${icon.info.type}</span></br>
// <span class='table-info'>${icon.info.CommodityName}</span></br>
// <span class='table-info'>${icon.info.cargoWeight}</span></br>
// ${icon.reason == 9 ? `<span class='table-info'>${icon.info.startUsingTime}</span></br>` : `<span class='table-info'>使${reason.value[icon.reason]}</span></br>`}
// <span class='table-info'>${icon.info.scheduledDepartureTime}</span></br>
// ${icon.reason == 9 ? `<span class='table-info'>${icon.info.shorePowerConsumption}</span></br>` : ''}
// <span class='table-info'>${icon.info.totalAuxiliaryPower}</span></br>
// </div>
// </div>
// `
// }
// const getProblemStr = (icon: any) => {
// return `
// <div class="table-message">
// <div class="table-name">${icon.parent.info.shipName}</div>
// <div class="table-line"></div>
// <div class="base-table-info">
// <span class='table-info'> ${icon.parent.info.shipName}</span></br>
// <span class='table-info'>使${reason.value[icon.parent.reason]}</span></br>
// <span class='table-info'>${icon.parent.info.scheduledDepartureTime}</span></br>
// </div>
// </div>
// `
// }
let mapNum = 1
const usingAlarm = ref([])
const removeAlarm = () => {
for (let ships of usingAlarm.value) {
for (const alarm of usingAlarm.value[ships]) {
alarm.remove()
}
usingAlarm.value[ships] = []
}
}
const buildAlarm = (icon:any, nowMapNum: number, radius: number = 1, timeOutTime: number = 50, color: string = Math.random() > 0.95 ? `rgba(255, 50, 50, 1)` :`rgba(50, 255, 20, 0.13)`) => {
setTimeout(() => {
if (nowMapNum !== mapNum) return
if (_map.value.getZoom() <= 15) {
const alarm = L.circle(icon.xy, {
radius: 50 * radius,
color: color,
stroke: false,
interactive: false,
}).addTo(_map.value);
if (!usingAlarm.value[icon.id]) usingAlarm.value[icon.id] = []
usingAlarm.value[icon.id].push(alarm)
}
if (radius > (6 * 18 / _map.value.getZoom())) {
for (let nowAlarm of usingAlarm.value[icon.id]) {
nowAlarm.remove()
}
usingAlarm.value[icon.id] = []
buildAlarm(icon, nowMapNum, 1, 50, color)
} else (
buildAlarm(icon, nowMapNum, radius + 1, timeOutTime + 2 ** radius / 2 , color)
)
}, timeOutTime)
}
const svgs = {
interface_red: interface_red,
interface_yellow: interface_yellow,
interface_blue: interface_blue,
interface_green: interface_green,
ship_red: ship_red,
ship_yellow: ship_yellow,
ship_blue: ship_blue,
ship_green: ship_green,
ship_brown: ship_brown,
ship_problem: ship_problem,
shorepower_blue: shorepower_blue,
shorepower_green: shorepower_green,
problem: problem,
problem_red: problem_red
}
//
const countIconSize = (icon: any): [number, number] => {
let zoomRatio = 2 ** (20 - _map.value.getZoom())
return [icon.width * icon.ratio / zoomRatio, icon.height * icon.ratio / zoomRatio]
}
//
const countIconAnchor = (icon: any): [number, number] => {
let zoomRatio = 2 ** (21 - _map.value.getZoom())
return [icon.width * icon.ratio / zoomRatio, icon.height * icon.ratio / zoomRatio]
}
//
const countFontSize = (text: any) => {
return text.fontSize / (1.7 ** (20 - _map.value.getZoom())) * 10
}
// options
const buildOptions = (data: any) => {
try {
if ((data.type && data.type === 'gon') || (data.type && data.type === 'line') || (!data.type && (type.value == 'gon' || type.value == 'line'))) return data
if ((data.type && data.type === 'icon') || (!data.type && type.value == 'icon')) {
return {
icon: L.icon({
iconAnchor: countIconAnchor(data),
iconSize: countIconSize(data),
iconUrl: svgs[data.icon]
}),
rotationAngle: data.rotationAngle,
rotationOrigin: "center center"
}
}
if ((data.type && data.type === 'text') || (!data.type && type.value == 'text')) {
const fontSize = countFontSize(data)
let textWidth = fontSize * data.name.length
for (let i = 0; i < data.name.length; i++) {
if ((data.name[i]) in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '#']) textWidth = textWidth - 0.5 * fontSize
}
textWidth = textWidth * 1.2
const tagAnchor = data.tagAnchorType === 1 ? [0, 0] : data.tagAnchorType === 2 ? [textWidth, 0] : data.tagAnchorType === 3 ? [0, fontSize] : data.tagAnchorType === 4 ? [textWidth, fontSize] : [textWidth / 2, fontSize / 2]
const style = `
background-color:${data.background};border-radius: 5px;text-align: center;
color: ${data.color};
width: ${textWidth}px;
font-size: ${fontSize}px;
font-style: oblique;
font-weight: ${data.fontSize * 33};
cursor: grab;`
return {
icon: L.divIcon({
className: data.name,
iconAnchor: tagAnchor,
html: `<div style="${style}">${data.name}</div>`
})
}
}
} catch (e) {
console.log(e, data)
}
}
const _map = ref<any>({_zoom: 14,_animateToCenter: {lat: 0, lng: 0}})
const mapRef = ref(null);
const defaultData = ref({
defCenter: {lat: 38.8422480135733, lng: 118.4933866580094},
defZoom: 12,
mapName: 'map',
illustration: true
})
const center = ref(defaultData.value.defCenter)
const zoom = ref(defaultData.value.defZoom)
const mouse = ref(defaultData.value.defCenter)
const type = ref('gon') // 1: 2: 线 3: 4:
//
const polygon = ref({
stroke: true,
color:`rgb(50, 50, 50)`,
weight:1,
opacity:1,
fill:true,
fillColor:`rgb(255, 255, 200)`,
fillOpacity:1,
interactive:false,
smoothFactor:1,
xy:[]
})
const usingPolygon = ref()
const drawingPolygon = () => {
if (usingPolygon.value) usingPolygon.value.remove()
usingPolygon.value = L.polygon(polygon.value.xy, buildOptions(polygon.value)).addTo(_map.value)
usingPolygon.value.on('click', () => {
type.value = 'text'
})
}
// 线
const polyline = ref({
interactive: false,
color: `rgb(50, 50, 50)`, //
dashArray: [10, 5],
weight: 1,//
smoothFactor: 0,
xy: [],
})
const usingPolyline = ref()
const drawingPolyline = () => {
if (usingPolyline.value) usingPolyline.value.remove()
usingPolyline.value = L.polyline(polyline.value.xy, buildOptions(polyline.value)).addTo(_map.value);
usingPolygon.value.on('click', () => {
type.value = 'text'
})
}
//
const marker = ref({
rotationAngle: 0,
width: 1000,
height: 5200,
ratio: 1,
iconType: 'ship',
icon: 'ship_green',
anchor: 'left',
xy: [],
})
const usingMarker = ref()
const drawingMarker = () => {
if (usingMarker.value) usingMarker.value.remove()
if (marker.value.xy.length !== 2) return
usingMarker.value = L.marker(marker.value.xy, buildOptions(marker.value)).addTo(_map.value);
usingPolygon.value.on('click', () => {
type.value = 'text'
})
}
//
const textType = ref('ship')
const text = ref({
name: '浙能2',
xy: [38.94212905193746, 118.4492737054825],
// type: 0,
fontSize: 16,
tagAnchorType: 1,
background: 'rgba(255, 255, 255, 0.7)',
color: 'rgba(50, 50, 50, 1)',
anchor: 'left',
})
const usingText = ref()
const drawingText = () => {
if (usingText.value) usingText.value.remove()
if (text.value.xy.length !== 2) return
usingText.value = L.marker(text.value.xy, buildOptions(text.value)).addTo(_map.value)
usingPolygon.value.on('click', () => {
type.value = 'text'
})
}
const getDrawingInfo = () => {
if (type.value === 'gon') {
return {type: type.value, ...polygon.value}
} else if (type.value === 'line') {
return {type: type.value, ...polyline.value}
} else if (type.value === 'icon') {
return {type: type.value, ...marker.value}
} else if (type.value === 'text') {
if (textType.value !== 'ship') {
const { anchor, ...textInfo} = text.value
return {type: type.value, ...textInfo}
} else {
return {type: type.value, ...text.value}
}
}
}
const doCopy = async () => {
const str = JSON.stringify(getDrawingInfo())
try {
// 使API
await navigator.clipboard.writeText(str);
// console.log('',str);
return
} catch (err) {
//
const textArea = document.createElement("textarea");
textArea.value = str;
textArea.style.cssText = "position: fixed; top: -1000px; opacity: 0;";
document.body.appendChild(textArea);
textArea.select();
try {
document.execCommand('copy');
// console.log('',str);
} catch (err) {
alert('复制失败,请手动复制')
// console.log('');
}
document.body.removeChild(textArea);
}
}
const copyData = async () => {
await doCopy()
}
const addDrawingList = async (event) => {
if (event.keyCode === 32 || event.key === ' ') {
event.preventDefault();
if (type.value === 'gon') {
polygon.value.xy.push([mouse.value.lat, mouse.value.lng])
drawingPolygon()
} else if (type.value === 'line') {
polyline.value.xy.push([mouse.value.lat, mouse.value.lng])
drawingPolyline()
} else if (type.value === 'icon') {
marker.value.xy = [mouse.value.lat, mouse.value.lng]
drawingMarker()
} else if (type.value === 'text') {
text.value.xy = [mouse.value.lat, mouse.value.lng]
drawingText()
}
} else if (event.keyCode === 46 || event.key === 'Delete') {
event.preventDefault();
if (type.value === 'gon') {
polygon.value.xy.pop()
drawingPolygon()
} else if (type.value === 'line') {
polyline.value.xy.pop()
drawingPolyline()
} else if (type.value === 'icon') {
marker.value.xy = []
drawingMarker()
} else if (type.value === 'text') {
text.value.xy = []
drawingText()
}
} else if (event.keyCode === 13 || event.key === 'Enter') {
await copyData()
}
}
const doDrawing = () => {
if (type.value === 'gon') {
drawingPolygon()
} else if (type.value === 'line') {
drawingPolyline()
} else if (type.value === 'icon') {
drawingMarker()
} else if (type.value === 'text') {
drawingText()
}
}
const history = ref()
const usingHistory = ref([])
const doHistory = ref(false)
const removeHistory = () => {
if (usingHistory.value) {
for (const item of usingHistory.value) {
item.remove()
}
usingHistory.value = []
}
}
const drawingHistoryPolygon = (data: any) => {
if (_map.value.getZoom() < 12) return
const usingInfo = L.polygon(data.xy, buildOptions(data)).addTo(_map.value)
usingInfo.on('click', async () => {
if (doHistory.value) await drawingHistory(doHistory.value)
doHistory.value = true
polygon.value = data
type.value = 'gon'
usingInfo.remove()
doDrawing()
})
usingHistory.value.push(usingInfo)
}
const drawingHistoryPolyline = (data: any) => {
if (_map.value.getZoom() < 12) return
const usingInfo = L.polyline(data.xy, buildOptions(data)).addTo(_map.value)
usingInfo.on('click', async () => {
if (doHistory.value) await drawingHistory(doHistory.value)
doHistory.value = true
polyline.value = data
type.value = 'line'
usingInfo.remove()
doDrawing()
})
usingHistory.value.push(usingInfo)
}
const drawingHistoryMarker = (data: any) => {
if (data.iconType === 'ship') {
const nowMapNum = JSON.parse(JSON.stringify(mapNum))
buildAlarm(data, nowMapNum)
}
if (_map.value.getZoom() < 12) return
const usingInfo = L.marker(data.xy, buildOptions(data)).addTo(_map.value)
usingInfo.on('click', async () => {
if (doHistory.value) await drawingHistory(doHistory.value)
doHistory.value = true
marker.value = data
type.value = 'icon'
usingInfo.remove()
doDrawing()
})
usingHistory.value.push(usingInfo)
}
const drawingHistoryTest = (data: any) => {
if (_map.value.getZoom() < 12) return
const usingInfo = L.marker(data.xy, buildOptions(data)).addTo(_map.value)
usingInfo.on('click', async () => {
if (doHistory.value) await drawingHistory(doHistory.value)
doHistory.value = true
text.value = data
type.value = 'text'
usingInfo.remove()
doDrawing()
})
usingHistory.value.push(usingInfo)
}
const drawingHistoryInfo = (data: any) => {
if (data.type === 'gon') {
drawingHistoryPolygon(data)
} else if (data.type === 'line') {
drawingHistoryPolyline(data)
} else if (data.type === 'icon') {
drawingHistoryMarker(data)
} else if (data.type === 'text') {
drawingHistoryTest(data)
}
}
const drawingHistory = async (justOne: boolean = false) => {
// if (!history.value) {
// history.value = await MapApi.getAllData()
// }
const dataInfo = history.value ? history.value : await MapApi.getAllData()
if (justOne) {
drawingHistoryInfo(getDrawingInfo())
} else {
for (const item of dataInfo) {
if (!item.data) continue
const data = JSON.parse(item.data)
if (!data.xy) continue
drawingHistoryInfo(data)
}
}
}
const buildMap = () => {
_map.value = L.map(defaultData.value.mapName, {
center: defaultData.value.defCenter,
zoom: defaultData.value.defZoom,
zoomControl: false,
attributionControl: false,
})
L.tileLayer(
// 'http://150.138.79.224:8000/seamap/allmap/{z}/{y}/{x}.png',
// 'https://api.shipxy.com/h5s/api/3.5/sample?key=0cd1a6224aaa4b5ab1f3d0e79bb543cc&projection_type=wm&x={x}&y={y}&z={z}',
// 'https://m12.shipxy.com/tile.c?l=Na&m=o&x={x}&y={y}&z={z}',
'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
// '',
{
maxZoom: 19,
minZoom: 5,
attribution: ''
}).addTo(_map.value);
//
const southWest = L.latLng(38.30071455572194, 117.07885023513526); // 西
const northEast = L.latLng( 40.32351403031131, 119.9161164758117); //
_map.value.setMaxBounds(L.latLngBounds(southWest, northEast));
//
_map.value.on('mousemove', (e) => {
mouse.value = e.latlng;
});
//
_map.value.on('zoomend', async () => {
zoom.value = _map.value.getZoom();
removeHistory()
removeAlarm()
mapNum++
await drawingHistory()
doDrawing()
})
//
_map.value.on('moveend', () => {
center.value = _map.value.getCenter();
});
}
const buildBaseInfo = () => {
for (const item of baseInfo) {
if (item.xy) L.polygon(item.xy, item).addTo(_map.value)
}
}
const exportImage = async () => {
try {
alert('功能暂不可用!')
} catch (error) {
console.error('地图导出失败:', error)
alert('地图导出失败,请重试')
}
}
/** 初始化 **/
onMounted(() => {
buildMap()
buildBaseInfo()
window.addEventListener('keyup', addDrawingList)
drawingHistory()
})
</script>
<style >
.parent {
position: relative; /* 启用Grid布局 */
}
.child {
position: absolute;
top: 10px;
left: 20px;
z-index: 1;
width: 200px;
height: 150px;
background-color: rgba(30, 30, 30, 0.7);
border: 1px solid rgba(30, 30, 30, 0.4);
border-radius: 15px;
}
.text-title {
margin-top: 10px;
font-size: 16px;
color: rgba(20, 200, 255, 1);
text-align: center;
font-weight: bold;
line-height: 30px;
text-shadow: 0 0 5px rgba(0, 0, 0, 1), 0 0 6px rgba(20, 200, 255, 0.3), 0 0 7px rgba(20, 200, 255, 0.2), 0 0 8px rgba(20, 200, 255, 0.1);
}
.circle {
width: 10px;
height: 10px;
border-radius: 5px;
margin-left: 15px;
display: inline-block;
}
.circle-green {
background-color: rgba(50, 255, 100, 1);
}
.circle-yellow {
background-color: rgba(255, 150, 0, 1);
}
.circle-red {
background-color: rgba(255, 70, 70, 1);
}
.text-info {
margin-left: 8px;
margin-top: 8px;
display: inline-block;
}
.text-info-green {
color: rgba(50, 255, 100, 1);
}
.text-info-yellow {
color: rgba(255, 150, 0, 1);
}
.text-info-red {
color: rgba(255, 70, 70, 1);
}
.mapParent {
max-width: 100%;
max-height: 100%;
overflow: hidden;
}
.my-map {
height: 100vh;
background-color: #A3CCFF;
//transform: scale(1.4);
}
.table-message {
width: 300px;
margin-top: 10px;
}
.table-name {
margin-top: 25px;
font-size: 20px;
color: rgb(0, 200, 255);
}
.table-line {
min-height: 3px;
width: 100%;
margin-top: 10px;
margin-bottom: 10px;
background-color: rgb(0, 200, 255);
}
.base-table-info {
}
.table-info {
margin-top: 10px;
font-size: 16px;
line-height: 25px;
color: rgba(50, 50, 50, 1);
}
</style>

240
public/map/components/index.html

@ -0,0 +1,240 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>CesiumJS 天地图纯影像示例</title>
<link href="https://cesium.com/downloads/cesiumjs/releases/1.123/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
<script src="https://cesium.com/downloads/cesiumjs/releases/1.123/Build/Cesium/Cesium.js"></script>
<style>
/* 确保 Cesium 容器占满整个浏览器窗口 */
body { margin: 0; overflow: hidden; }
#cesiumContainer { width: 100%; height: 100vh; }
/* 悬浮按钮样式 */
.control-btn {
position: absolute;
z-index: 1000;
padding: 10px 15px;
background-color: rgba(0, 0, 0, 0.7);
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
box-shadow: 0 2px 5px rgba(0,0,0,0.3);
transition: background-color 0.3s;
}
.control-btn:hover {
background-color: rgba(0, 0, 0, 0.9);
}
#cameraInfoBtn {
top: 20px;
right: 20px;
}
/* 视角切换按钮 */
.view-buttons {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 10px;
z-index: 1000;
}
.view-btn {
padding: 8px 16px;
background-color: rgba(0, 0, 0, 0.7);
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
box-shadow: 0 2px 5px rgba(0,0,0,0.3);
transition: background-color 0.3s;
}
.view-btn:hover {
background-color: rgba(0, 0, 0, 0.9);
}
</style>
</head>
<body>
<div id="cesiumContainer"></div>
<button id="cameraInfoBtn" class="control-btn">获取当前视角参数</button>
<!-- 视角切换按钮 -->
<div class="view-buttons">
<button class="view-btn" data-view="overview">全局视角</button>
<button class="view-btn" data-view="ship1">船1视角</button>
<button class="view-btn" data-view="ship2">船2视角</button>
<button class="view-btn" data-view="ship3">船3视角</button>
<button class="view-btn" data-view="ship4">船4视角</button>
</div>
<script type="module">
// ----------------------------------------------------------------------
// ** 请替换为您申请的天地图密钥 **
// ----------------------------------------------------------------------
const TDT_KEY = 'b19d3ad72716d1a28cf77836dfa19a71';
// 步骤 1: 初始化 Viewer 并配置为使用天地图影像和平坦地形
const viewer = new Cesium.Viewer('cesiumContainer', {
// **核心设置:禁用所有默认影像,我们将手动添加天地图**
imageryProvider: false,
// **使用平坦地形,避免依赖 Cesium Ion**
terrainProvider: new Cesium.EllipsoidTerrainProvider(),
// 禁用所有不必要的UI控件
timeline: false,
animation: false,
baseLayerPicker: false,
geocoder: false,
sceneModePicker: false,
navigationHelpButton: false,
infoBox: false,
fullscreenButton: false,
homeButton: false,
// 启用抗锯齿和其他渲染优化
contextOptions: {
webgl: {
antialias: true
}
}
});
// 禁用所有鼠标和键盘的相机控制操作
viewer.scene.screenSpaceCameraController.enableRotate = false;
viewer.scene.screenSpaceCameraController.enableTranslate = false;
viewer.scene.screenSpaceCameraController.enableZoom = false;
viewer.scene.screenSpaceCameraController.enableTilt = false;
viewer.scene.screenSpaceCameraController.enableLook = false;
// 渲染优化设置
// 启用抗锯齿
viewer.scene.postProcessStages.fxaa.enabled = true;
// 启用深度测试,确保模型能够被地形正确遮挡
viewer.scene.globe.depthTestAgainstTerrain = true;
// 步骤 2: 添加天地图卫星影像底图 (img: 卫星图层, w: WGS84坐标系)
const tdtImage = new Cesium.WebMapTileServiceImageryProvider({
// URL 中 LAYER=img 表示卫星影像图层
url: "http://t{s}.tianditu.gov.cn/img_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=img&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles&tk=" + TDT_KEY,
subdomains: ['0', '1', '2', '3', '4', '5', '6', '7'],
layer: 'tdtImgLayer',
style: 'default',
format: 'tiles',
tileMatrixSetID: 'w', // 确保使用 WGS84 坐标系
maximumLevel: 18
});
// 步骤 3: 将图层添加到 Viewer 中
viewer.imageryLayers.addImageryProvider(tdtImage);
// **注意:由于您要求不添加标注层,因此这里不添加 tdtCVA 或 tdtCIA**
// 引入我们自定义的标记和定位功能
import { initMarkerAndPosition } from './src/cesium-utils.js';
// 初始化标记和定位
initMarkerAndPosition(viewer);
// 定义预设视角参数
const presetViews = {
overview: {
destination: Cesium.Cartesian3.fromDegrees(118.4603328835826, 38.953967794772765, 560.0105923892418),
orientation: {
heading: Cesium.Math.toRadians(231.0260194269599),
pitch: Cesium.Math.toRadians(-24.749471814600415),
roll: Cesium.Math.toRadians(0.005508519138937096)
}
},
ship1: {
destination: Cesium.Cartesian3.fromDegrees(118.45467237056748 + 0.005, 38.94345692673452, 150), // 在船的前方
orientation: {
heading: Cesium.Math.toRadians(212), // 朝向船的位置 (180+32)
pitch: Cesium.Math.toRadians(-15),
roll: 0
}
},
ship2: {
destination: Cesium.Cartesian3.fromDegrees(118.45183602217774 + 0.005, 38.94485094840323, 150), // 在船的前方
orientation: {
heading: Cesium.Math.toRadians(212), // 朝向船的位置 (180+32)
pitch: Cesium.Math.toRadians(-15),
roll: 0
}
},
ship3: {
destination: Cesium.Cartesian3.fromDegrees(118.4468305964142 + 0.005, 38.947237470602076, 150), // 在船的前方
orientation: {
heading: Cesium.Math.toRadians(212), // 朝向船的位置 (180+32)
pitch: Cesium.Math.toRadians(-15),
roll: 0
}
},
ship4: {
destination: Cesium.Cartesian3.fromDegrees(118.44446808752532 + 0.005, 38.94835610136433, 150), // 在船的前方
orientation: {
heading: Cesium.Math.toRadians(212), // 朝向船的位置 (180+32)
pitch: Cesium.Math.toRadians(-15),
roll: 0
}
}
};
// 设置初始视角
viewer.camera.setView(presetViews.overview);
// 添加按钮点击事件监听器
document.getElementById('cameraInfoBtn').addEventListener('click', function() {
// 获取当前相机位置
const camera = viewer.camera;
const position = camera.position;
const cartographic = Cesium.Cartographic.fromCartesian(position);
// 转换为度数
const longitude = Cesium.Math.toDegrees(cartographic.longitude);
const latitude = Cesium.Math.toDegrees(cartographic.latitude);
const height = cartographic.height;
// 获取相机方向
const heading = Cesium.Math.toDegrees(camera.heading);
const pitch = Cesium.Math.toDegrees(camera.pitch);
const roll = Cesium.Math.toDegrees(camera.roll);
// 输出到控制台
console.log('当前相机视角参数:');
console.log('位置 - 经度:', longitude, '纬度:', latitude, '高度:', height);
console.log('方向 - 航向角:', heading, '俯仰角:', pitch, '翻滚角:', roll);
// 显示提示信息
alert('相机参数已输出到控制台,请打开开发者工具查看。');
});
// 添加视角切换按钮事件监听器
document.querySelectorAll('.view-btn').forEach(button => {
button.addEventListener('click', function() {
const viewName = this.getAttribute('data-view');
const viewParams = presetViews[viewName];
if (viewParams) {
viewer.camera.flyTo({
destination: viewParams.destination,
orientation: viewParams.orientation,
duration: 2.0 // 动画持续时间(秒)
});
}
});
});
</script>
</body>
</html>

529
public/map/components/index.vue

@ -0,0 +1,529 @@
<template>
<div>
<div ref="cesiumContainerRef" class="cesium-container"></div>
<div class="btn-group">
<!-- <el-button class="get-view-btn" size="small" type="primary" @click="getCurrentViewInfo"
style="margin-right: 10px;">获取当前视角</el-button> -->
<!-- <el-button class="view-btn" size="small" type="success" @click="switchView('overview')"
style="margin-right: 10px;">概览视角</el-button> -->
<!-- <el-button class="view-btn" size="small" type="warning" @click="switchView('view1')"
style="margin-right: 10px;">视角1</el-button>
<el-button class="view-btn" size="small" type="warning" @click="switchView('view2')"
style="margin-right: 10px;">视角2</el-button> -->
</div>
</div>
</template>
<script setup>
import { onMounted, onBeforeUnmount, ref } from 'vue';
import coordtransform from 'coordtransform';
import { MapApi } from "@/api/shorepower/map";
import { toRaw } from 'vue'
// window 访 Cesium
const Cesium = window.Cesium;
let viewer = null;
const cesiumContainerRef = ref(null); // DOM
const history = ref(null); //
const dataWithModels = ref([]); //
//
const presetViews = {
overview: {
"longitude": 118.5132903711312,
"latitude": 38.84648563549342,
"height": 12675.768680075478,
"heading": 353.6789759103708,
"pitch": -36.657258995301596,
"roll": 0.0343469697692715
}
};
const createView = (obj) => {
return {
destination: Cesium.Cartesian3.fromDegrees(obj.longitude, obj.latitude, obj.height),
orientation: {
heading: Cesium.Math.toRadians(obj.heading),
pitch: Cesium.Math.toRadians(obj.pitch),
roll: Cesium.Math.toRadians(obj.roll)
}
}
}
onMounted(async () => {
if (!Cesium) {
console.error("Cesium 对象未找到,请检查 index.html 文件!");
return;
}
// 1: Viewer 使
try {
viewer = new Cesium.Viewer(cesiumContainerRef.value, {
// ****
imageryProvider: false,
// **使 Cesium Ion**
terrainProvider: new Cesium.EllipsoidTerrainProvider(),
// sceneMode: Cesium.SceneMode.COLUMBUS_VIEW,
// UI
timeline: false,
animation: false,
baseLayerPicker: false,
geocoder: false,
sceneModePicker: false,
navigationHelpButton: false,
infoBox: false,
fullscreenButton: false,
homeButton: false,
// 齿
contextOptions: {
webgl: {
antialias: true
}
}
});
//
/* viewer.scene.screenSpaceCameraController.enableRotate = false;
viewer.scene.screenSpaceCameraController.enableTranslate = false;
viewer.scene.screenSpaceCameraController.enableZoom = false;
viewer.scene.screenSpaceCameraController.enableTilt = false;
viewer.scene.screenSpaceCameraController.enableLook = false; */
//
// 齿
viewer.scene.postProcessStages.fxaa.enabled = true;
//
viewer.scene.globe.depthTestAgainstTerrain = true;
//
viewer.clock.shouldAnimate = true;
viewer.scene.screenSpaceCameraController.minimumZoomDistance = 200;
// 2:
const gaodeImage = new Cesium.UrlTemplateImageryProvider({
url: "https://webst0{s}.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}",
subdomains: ['1', '2', '3', '4'],
layer: 'gaodeImgLayer',
style: 'default',
format: 'image/png',
maximumLevel: 16
});
const dataInfo = history.value ? history.value : await MapApi.getAllData()
const shipData = await MapApi.getShipInfo({
harborDistrictId: 1
})
console.log('shipData', shipData)
// const unitedData =
// dataInfo
const addMarkersFromDataInfo = (dataInfo) => {
//
if (!dataInfo || !Array.isArray(dataInfo) || dataInfo.length === 0) {
console.log('No valid dataInfo array provided');
return [];
}
//
const dataWithModelsArray = [];
console.log(`Processing ${dataInfo.length} items`);
dataInfo.forEach((item, index) => {
//
if (!item) {
console.warn(`Item at index ${index} is null or undefined`);
return;
}
try {
//
let itemWithModel = { ...item };
// dataJSON
let dataObj;
if (typeof item.data === 'string') {
try {
dataObj = JSON.parse(item.data);
} catch (parseError) {
console.warn(`Failed to parse data for item ${item.id || index}:`, parseError);
dataWithModelsArray.push(itemWithModel); //
return;
}
} else {
dataObj = item.data;
}
// dataObj
if (!dataObj) {
console.warn(`No data object found for item ${item.id || index}`);
dataWithModelsArray.push(itemWithModel); //
return;
}
// -
let longitude, latitude;
let wgsLon, wgsLat;
if (dataObj.xy && Array.isArray(dataObj.xy) && dataObj.xy.length >= 2) {
const wgsCoords = coordtransform.wgs84togcj02(dataObj.xy[1], dataObj.xy[0]);
wgsLon = wgsCoords[0];
wgsLat = wgsCoords[1];
//
latitude = wgsLat;
longitude = wgsLon;
//
if (isNaN(latitude) || isNaN(longitude)) {
console.warn(`Invalid coordinates for item ${item.id || index}:`, dataObj.xy);
dataWithModelsArray.push(itemWithModel); //
return;
}
//
if (Math.abs(latitude) > 90 || Math.abs(longitude) > 180) {
console.warn(`Coordinates out of range for item ${item.id || index}:`, dataObj.xy[1], dataObj.xy[0]);
dataWithModelsArray.push(itemWithModel); //
return;
}
} else {
console.warn('无效的坐标信息:', item);
dataWithModelsArray.push(itemWithModel); //
return;
}
if (dataObj.icon === 'ship_green') {
const itemShipInfo = shipData.find(shipItem => (shipItem.shorePower.id === item.parentId) && item.type === 5)
const position = Cesium.Cartesian3.fromDegrees(wgsLon, wgsLat, 15);
const statusPosition = Cesium.Cartesian3.fromDegrees(wgsLon, wgsLat, 1);
const labelPosition = Cesium.Cartesian3.fromDegrees(wgsLon, wgsLat, 35);
//
const shipModel = viewer.entities.add({
name: 'Cargo Ship Model',
position: position,
orientation: Cesium.Transforms.headingPitchRollQuaternion(
position,
new Cesium.HeadingPitchRoll(
Cesium.Math.toRadians(dataObj.rotationAngle), // (Z)
Cesium.Math.toRadians(0), // (Y)
Cesium.Math.toRadians(0) // (X)
)
),
model: {
uri: '/model/cargo_ship_07.glb',
scale: 1.5, //
// minimumPixelSize: 100, // 50
// 使
enableDepthTest: true,
//
backFaceCulling: true
}
});
//
itemWithModel.modelInstance = shipModel;
itemWithModel.modelType = 'ship';
itemWithModel = { ...itemWithModel, ...itemShipInfo };
viewer.entities.add({
position: labelPosition, // 10
label: {
text: itemShipInfo.shipBasicInfo.name || `Marker-${item.id || index}`,
font: '12px sans-serif',
fillColor: Cesium.Color.WHITE,
outlineColor: Cesium.Color.BLACK,
outlineWidth: 2,
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
pixelOffset: new Cesium.Cartesian2(0, -30), //
disableDepthTestDistance: Number.POSITIVE_INFINITY //
}
});
const overlayBillboard = viewer.entities.add({
position: statusPosition,
billboard: {
image: '/img/故障.png',
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
verticalOrigin: Cesium.VerticalOrigin.CENTER,
disableDepthTestDistance: Number.POSITIVE_INFINITY,
scale: new Cesium.CallbackProperty(function (time, result) {
// t
const t = Cesium.JulianDate.toDate(time).getTime() / 1000;
const pulse = 0.6 + Math.sin(t * 4) * 0.2; // (0.6, ±0.2)
return pulse;
}, false)
}
});
}
if (dataObj.type === 'text') {
const labelPosition = Cesium.Cartesian3.fromDegrees(wgsLon, wgsLat, 35);
viewer.entities.add({
position: labelPosition, // 10
label: {
text: dataObj.name || `Marker-${item.id || index}`,
font: '20px sans-serif',
fillColor: Cesium.Color.WHITE,
outlineColor: Cesium.Color.BLACK,
outlineWidth: 2,
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
pixelOffset: new Cesium.Cartesian2(0, -30), //
disableDepthTestDistance: Number.POSITIVE_INFINITY //
}
});
}
if (dataObj.type === 'icon' && (dataObj.icon === 'interface_blue' || dataObj.icon === 'interface_red')) {
const position = Cesium.Cartesian3.fromDegrees(wgsLon, wgsLat, 1);
const labelPosition = Cesium.Cartesian3.fromDegrees(wgsLon, wgsLat, 5);
//
const electricalBoxModel = viewer.entities.add({
name: 'Electrical Box Model',
position: position,
orientation: Cesium.Transforms.headingPitchRollQuaternion(
position,
new Cesium.HeadingPitchRoll(
Cesium.Math.toRadians(dataObj.rotationAngle), // (Z)
Cesium.Math.toRadians(0), // (Y)
Cesium.Math.toRadians(0) // (X)
)
),
model: {
uri: '/model/electrical_box.glb',
scale: 1, //
// minimumPixelSize: 10, // 50
// 使
enableDepthTest: true,
//
backFaceCulling: true
}
});
//
itemWithModel.modelInstance = electricalBoxModel;
itemWithModel.modelType = 'electrical_box';
viewer.entities.add({
position: labelPosition, // 10
label: {
text: '岸电箱' || `Marker-${item.id || index}`,
font: '10px sans-serif',
fillColor: Cesium.Color.WHITE,
outlineColor: Cesium.Color.BLACK,
outlineWidth: 2,
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
pixelOffset: new Cesium.Cartesian2(0, -30), //
disableDepthTestDistance: Number.POSITIVE_INFINITY //
}
});
}
//
/* const entity = viewer.entities.add({
name: item.name || `Marker-${item.id || index}`,
position: Cesium.Cartesian3.fromDegrees(wgsLon, wgsLat, 1),
point: {
pixelSize: 10,
color: Cesium.Color.RED,
outlineColor: Cesium.Color.WHITE,
outlineWidth: 2
},
//
label: {
text: item.name || `Marker-${item.id || index}`,
font: '14px sans-serif',
fillColor: Cesium.Color.WHITE,
outlineColor: Cesium.Color.BLACK,
outlineWidth: 2,
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
pixelOffset: new Cesium.Cartesian2(0, -15)
}
});
//
itemWithModel.modelInstance = entity;
itemWithModel.modelType = 'point';
*/
//
dataWithModelsArray.push(itemWithModel);
} catch (error) {
console.error('Error processing item:', item, error);
//
dataWithModelsArray.push({ ...item });
}
});
return dataWithModelsArray;
};
// 3: Viewer
viewer.imageryLayers.addImageryProvider(gaodeImage);
//
dataWithModels.value = addMarkersFromDataInfo(dataInfo);
//
viewer.camera.flyTo(createView(presetViews.overview));
//
const SHOW_HEIGHT = 30000; // > 5000m
// const targets = []; // entities push
const targets = dataWithModels.value
.filter(item => item.modelInstance)
.map(item => toRaw(item.modelInstance));
console.log('targets', targets)
viewer.camera.changed.addEventListener(() => {
//
const height = viewer.camera.positionCartographic.height;
// console.log(":", height.toFixed(2), "");
// /
const visible = height <= SHOW_HEIGHT; // 1000m
targets.forEach(entity => {
if (entity.show !== visible) {
entity.show = visible;
}
});
//
if (height < 100000) {
//
console.log("当前处于近距离缩放级别。");
} else if (height > 5000000) {
//
console.log("当前处于远距离缩放级别。");
}
});
//
const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
handler.setInputAction(function (movement) {
console.log('地图点击事件触发');
//
const ray = viewer.camera.getPickRay(movement.position);
// 1:
let cartesian = viewer.scene.globe.pick(ray, viewer.scene);
//
if (!cartesian) {
cartesian = viewer.scene.pickPosition(movement.position);
console.log('使用场景拾取方法');
}
// 使
if (!cartesian) {
//
console.log('无法从点击位置获取精确坐标');
return;
}
if (cartesian) {
const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
if (cartographic) {
const longitude = Cesium.Math.toDegrees(cartographic.longitude);
const latitude = Cesium.Math.toDegrees(cartographic.latitude);
const gcj02 = coordtransform.gcj02towgs84(longitude, latitude);
console.log('点击位置经纬度:', {
longitude: gcj02[0],
latitude: gcj02[1]
});
} else {
console.log('无法从笛卡尔坐标转换为大地坐标');
}
} else {
console.log('未能获取到地球表面的点击位置');
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
//
viewer.scene.screenSpaceCameraController.enableInputs = true;
//
viewer.scene.useDepthPicking = true;
console.log('Cesium 高德地图 Viewer 初始化成功');
} catch (error) {
console.error('Cesium Viewer 初始化失败:', error);
}
});
//
const switchView = (viewName) => {
const viewParams = presetViews[viewName];
if (viewParams && viewer) {
viewer.camera.flyTo({
...createView(viewParams),
duration: 2.0 //
});
}
};
//
const switchModelView = (view) => {
const modelInstance = toRaw(view);
console.log(modelInstance)
if (viewer && view) {
viewer.flyTo(modelInstance);
}
};
//
const getCurrentViewInfo = () => {
if (!viewer) {
console.warn('Viewer 尚未初始化');
return null;
}
const camera = viewer.camera;
const position = camera.position; // x,y,z
const cartographic = Cesium.Cartographic.fromCartesian(position);
console.log(
{
longitude: Cesium.Math.toDegrees(cartographic.longitude),
latitude: Cesium.Math.toDegrees(cartographic.latitude),
height: cartographic.height,
heading: Cesium.Math.toDegrees(camera.heading),
pitch: Cesium.Math.toDegrees(camera.pitch),
roll: Cesium.Math.toDegrees(camera.roll)
}
)
};
// 使
defineExpose({
switchView,
dataWithModels,
switchModelView
});
onBeforeUnmount(() => {
// Viewer WebGL
if (viewer) {
viewer.destroy();
viewer = null;
console.log('Cesium Viewer 已销毁');
}
});
</script>
<style scoped>
.cesium-container {
width: 100%;
height: 100vh;
margin: 0;
padding: 0;
overflow: hidden;
}
.btn-group {
position: absolute;
top: 10px;
z-index: 1000;
display: flex;
gap: 12rpx;
}
</style>

703
public/map/components/tags.ts

@ -0,0 +1,703 @@
const tagsData: any = [
{name: '华能曹妃甸港口', xy: [38.9498140921284, 118.4373164176941], type: 0, fontSize: 18, tagAnchorType: 1, background: false,
parent: {},
tags:[],
icons: [
// {xy: [38.94223753278567, 118.44984233379365], type: 1, icon: 'shorepower_blue', rotationAngle: 0,
// info: {name: '1号岸电设备', installedPower: '2000千伏安', voltage: '0.4/0.44千伏', frequency: '50/60赫兹', interfaceCount: '2', simultaneouslyInterfaceCount: '1'},
// parent: {},
// tags:[],
// icons: [
{xy: [38.94250178103962, 118.44926834106447], type: 2, icon: 'interface_yellow', rotationAngle: -58.8,
info: {berthName: '1泊位', totalPower: '99999千瓦时', updateTime: '2025-10-31 23:59:59', status: '未使用', InterfaceType: '350A', berthDepth: '10米'},
parent: {},
tags:[
{name: '1泊位', xy: [38.942729866856865, 118.44900012016298], type: 0, fontSize: 16, tagAnchorType: 3, background: false, tags:[], parent: {}, icons: [],},
],
icons:[
{xy: [38.94241835068678, 118.44873189926149], type: 3, icon: 'ship_problem', rotationAngle: -59, reason: 7, status: 3,
info: {shipName: '浙能2', callSign: 'XHHH1', shipCompany:'华能', phone: '13333333333', length: '190米', width: '40米', tonnage: '18万吨', type: '运煤船', CommodityName: '煤炭', cargoWeight: '17万吨',
startUsingTime: '2025-11-01 01:50', scheduledDepartureTime: '2025-11-02 23:30', shorePowerConsumption: '0千瓦时', totalAuxiliaryPower: '1100千瓦'},
parent: {},
tags:[
{name: '浙能2', xy: [38.94212905193746, 118.4492737054825], type: 0, fontSize: 16, tagAnchorType: 2, background: true, parent: {}, tags:[], icons: [],},
],
icons: [
// {xy: [38.941786918944366, 118.44942927360536], type: 4, icon: 'problem', rotationAngle: 0, parent: {}, tags:[], icons: [],},
]
},
]
},
// ]},
// {xy: [38.943643596453846, 118.44687044620515], type: 1, icon: 'shorepower_blue', rotationAngle: 0,
// info: {name: '2号岸电设备', installedPower: '2000千伏安', voltage: '0.4/0.44千伏', frequency: '50/60赫兹', interfaceCount: '2', simultaneouslyInterfaceCount: '1'},
// parent: {},
// tags:[],
// icons: [
{xy: [38.9438032818323, 118.44653785228729], type: 2, icon: 'interface_yellow', rotationAngle: -58.8,
info: {berthName: '2泊位', totalPower: '99999千瓦时', updateTime: '2025-10-31 23:59:59', status: '未使用', InterfaceType: '350A', berthDepth: '10米'},
parent: {},
tags:[
{name: '2泊位', xy: [38.944215192999486, 118.4459960460663], type: 0, fontSize: 16, tagAnchorType: 3, background: false, tags:[], icons: [], parent: {},},
],
icons: [
{xy: [38.943469565958026, 118.44652175903322], type: 3, icon: 'ship_problem', rotationAngle: 121, reason: 1, status: 3,
info: {shipName: '东成山', callSign: 'XHHH1', shipCompany:'华能', phone: '13333333333', length: '190米', width: '40米', tonnage: '18万吨', type: '运煤船', CommodityName: '煤炭', cargoWeight: '17万吨',
startUsingTime: '2025-11-01 01:50', scheduledDepartureTime: '2025-11-02 23:30', shorePowerConsumption: '0千瓦时', totalAuxiliaryPower: '1100千瓦'},
parent: {},
tags:[
{name: '东成山', xy: [38.94355597905776, 118.44614624977113], type: 0, fontSize: 16, tagAnchorType: 2, background: true, tags:[], icons: [], parent: {},},
],
icons: [
// {xy: [38.94322219750925, 118.4463930130005], type: 4, icon: 'problem', rotationAngle: 0, parent: {}, tags:[], icons: [],},
]
}
]
},
// ]},
// {xy: [38.94499956615951, 118.44398975372316], type: 1, icon: 'shorepower_blue', rotationAngle: 0,
// info: {name: '3号岸电设备', installedPower: '2000千伏安', voltage: '6/6.6千伏', frequency: '50/60赫兹', interfaceCount: '3', simultaneouslyInterfaceCount: '1'},
// parent: {},
// tags:[],
// icons: [
{xy: [38.94521906957387, 118.44356996758943], type: 2, icon: 'interface_green', rotationAngle: -58.8,
info: {berthName: '3泊位', totalPower: '99999千瓦时', updateTime: '2025-10-31 23:59:59', status: '使用中', InterfaceType: '350A', berthDepth: '10米'},
parent: {},
tags:[
{name: '3泊位', xy: [38.94554194678001, 118.44319581985475], type: 0, fontSize: 16, tagAnchorType: 3, background: false, tags:[], icons: [], parent: {},},
],
icons: [
{xy: [38.944909090691475, 118.44343119744725], type: 3, icon: 'ship_green', rotationAngle: 121, reason: 9, status: 1,
info: {shipName: '华元503', callSign: 'XHHH1', shipCompany:'华能', phone: '13333333333', length: '190米', width: '40米', tonnage: '18万吨', type: '运煤船', CommodityName: '煤炭', cargoWeight: '17万吨',
startUsingTime: '2025-11-01 01:50', scheduledDepartureTime: '2025-11-02 23:30', shorePowerConsumption: '2192千瓦时', totalAuxiliaryPower: '1100千瓦'},
parent: {},
tags:[
{name: '华元503', xy: [38.94490777825935, 118.44327092170717], type: 0, fontSize: 16, tagAnchorType: 2, background: true, tags:[], icons: [], parent: {},},
],
icons: [
]
},
]
},
// ]
// },
// {xy: [38.94641391923306, 118.44098567962648], type: 1, icon: 'shorepower_blue', rotationAngle: 0,
// info: {name: '4号岸电设备', installedPower: '2000千伏安', voltage: '6/6.6千伏', frequency: '50/60赫兹', interfaceCount: '3', simultaneouslyInterfaceCount: '1'},
// parent: {},
// tags:[],
// icons: [
{xy: [38.946586276690354, 118.44064235687257], type: 2, icon: 'interface_red', rotationAngle: -58.8,
info: {berthName: '4泊位', totalPower: '99999千瓦时', updateTime: '2025-10-31 23:59:59', status: '故障/检修', InterfaceType: '350A', berthDepth: '10米'},
parent: {},
tags:[
{name: '4泊位', xy: [38.946910396364125, 118.44033122062685], type: 0, fontSize: 16, tagAnchorType: 3, background: false, tags:[], icons: [], parent: {},},
],
icons: [
{xy: [38.946256745210576, 118.4406477212906], type: 3, icon: 'ship_red', rotationAngle: 121, reason: 6, status: 3,
info: {shipName: '盛达海', callSign: 'XHHH1', shipCompany:'华能', phone: '13333333333', length: '190米', width: '40米', tonnage: '18万吨', type: '运煤船', CommodityName: '煤炭', cargoWeight: '17万吨',
startUsingTime: '2025-11-01 01:50', scheduledDepartureTime: '2025-11-02 23:30', shorePowerConsumption: '0千瓦时', totalAuxiliaryPower: '1100千瓦'},
parent: {},
tags:[
{name: '盛达海', xy: [38.94629292848092, 118.44037413597108], type: 0, fontSize: 16, tagAnchorType: 2, background: true, tags:[], icons: [], parent: {},},
],
icons: [
// {xy: [38.94598419252245, 118.44058871269227], type: 4, icon: 'problem', rotationAngle: 0, parent: {}, tags:[], icons: [],},
]
},
]
},
// ]
// },
// {xy: [38.94765719155025, 118.43841075897218], type: 1, icon: 'shorepower_blue', rotationAngle: 0,
// info: {name: '5号岸电设备', installedPower: '2000千伏安', voltage: '6/6.6千伏', frequency: '50/60赫兹', interfaceCount: '3', simultaneouslyInterfaceCount: '1'},
// parent: {},
// tags:[],
// icons: [
{xy: [38.94783347690156, 118.43805134296419], type: 2, icon: 'interface_yellow', rotationAngle: -58.8,
info: {berthName: '5泊位', totalPower: '99999千瓦时', updateTime: '2025-10-31 23:59:59', status: '未使用', InterfaceType: '350A', berthDepth: '10米'},
parent: {},
tags:[
{name: '5泊位', xy: [38.94819537985111, 118.43771338462831], type: 0, fontSize: 16, tagAnchorType: 3, background: false, tags:[], icons: [], parent: {},},
],
icons: [
{xy: [38.94748309511211, 118.43805134296419], type: 3, icon: 'ship_problem', rotationAngle: 121, reason: 2, status: 3,
info: {shipName: '浙海温州', callSign: 'XHHH1', shipCompany:'华能', phone: '13333333333', length: '190米', width: '40米', tonnage: '18万吨', type: '运煤船', CommodityName: '煤炭', cargoWeight: '17万吨',
startUsingTime: '2025-11-01 01:50', scheduledDepartureTime: '2025-11-02 23:30', shorePowerConsumption: '0千瓦时', totalAuxiliaryPower: '1100千瓦'},
parent: {},
tags:[
{name: '浙海温州', xy: [38.94756957911453, 118.43769192695619], type: 0, fontSize: 16, tagAnchorType: 2, background: true, parent: {}, tags:[], icons: [],},
],
icons: [
// {xy: [38.94723581646269, 118.43793869018556], type: 4, icon: 'problem', rotationAngle: 0, parent: {}, tags:[], icons: [],},
],
},
]
},
// ]
// },
]
},
{name: '河北华电曹妃甸储运', xy: [38.98916912512044, 118.45514774322511], type: 0, fontSize: 18, tagAnchorType: 2, background: false,
parent: {},
tags:[],
icons: [
{xy: [38.98171382209705, 118.4525352716446], type: 1, icon: 'shorepower_blue', rotationAngle: 0, ratio: 4,
info: {name: '1号岸电设备', installedPower: '2000千伏安', voltage: '6/6.6千伏', frequency: '50/60赫兹', interfaceCount: '2', simultaneouslyInterfaceCount: '1'},
parent: {},
tags:[],
icons: [
{xy: [38.980330557533094, 118.45477223396303], type: 2, icon: 'interface_yellow', rotationAngle: 0,
info: {berthName: '806泊位', totalPower: '99999千瓦时', updateTime: '2025-10-31 23:59:59', status: '未使用', InterfaceType: '350A', berthDepth: '10米'},
parent: {},
tags:[
{name: '806泊位', xy: [38.98052656782281, 118.45470786094667], type: 0, fontSize: 16, tagAnchorType: 2, background: false, tags:[], parent: {}, icons: [],},
],
icons:[
{xy: [38.980547376325056, 118.45514774322511], type: 3, icon: 'ship_problem', rotationAngle: 0, reason: 5, status: 3,
info: {shipName: '华海航2', callSign: 'XHHH1', shipCompany:'华能', phone: '13333333333', length: '190米', width: '40米', tonnage: '18万吨', type: '运煤船', CommodityName: '煤炭', cargoWeight: '17万吨',
startUsingTime: '2025-11-01 01:50', scheduledDepartureTime: '2025-11-02 23:30', shorePowerConsumption: '0千瓦时', totalAuxiliaryPower: '1100千瓦'},
parent: {},
tags:[
{name: '华海航2', xy: [38.98067662214458, 118.455308675766], type: 0, fontSize: 16, tagAnchorType: 1, background: true, parent: {}, tags:[], icons: [],},
],
icons: [
// {xy: [38.941786918944366, 118.44942927360536], type: 4, icon: 'problem', rotationAngle: 0, parent: {}, tags:[], icons: [],},
]
},
]
},
{xy: [38.98291158299341, 118.45474541187288], type: 2, icon: 'interface_yellow', rotationAngle: 0,
info: {berthName: '807泊位', totalPower: '99999千瓦时', updateTime: '2025-10-31 23:59:59', status: '未使用', InterfaceType: '350A', berthDepth: '10米'},
parent: {},
tags:[
{name: '807泊位', xy: [38.98310332916697, 118.45466494560243], type: 0, fontSize: 16, tagAnchorType: 2, background: false, tags:[], icons: [], parent: {},},
],
icons: [
{xy: [38.983115870390485, 118.45514237880708], type: 3, icon: 'ship_problem', rotationAngle: 0, reason: 7, status: 3,
info: {shipName: '中茂98', callSign: 'XHHH1', shipCompany:'华能', phone: '13333333333', length: '190米', width: '40米', tonnage: '18万吨', type: '运煤船', CommodityName: '煤炭', cargoWeight: '17万吨',
startUsingTime: '2025-11-01 01:50', scheduledDepartureTime: '2025-11-02 23:30', shorePowerConsumption: '0千瓦时', totalAuxiliaryPower: '1100千瓦'},
parent: {},
tags:[
{name: '中茂98', xy: [38.98322857125241, 118.45531404018404], type: 0, fontSize: 16, tagAnchorType: 1, background: true, tags:[], icons: [], parent: {},},
],
icons: [
// {xy: [38.94322219750925, 118.4463930130005], type: 4, icon: 'problem', rotationAngle: 0, parent: {}, tags:[], icons: [],},
]
}
]
},
]
},
{xy: [38.98820189340982, 118.45261573791505], type: 1, icon: 'shorepower_blue', rotationAngle: 0, ratio: 4,
info: {name: '2号岸电设备', installedPower: '2000千伏安', voltage: '6/6.6千伏', frequency: '50/60赫兹', interfaceCount: '3', simultaneouslyInterfaceCount: '1'},
parent: {},
tags:[],
icons: [
{xy: [38.98574299196949, 118.45476893109242], type: 2, icon: 'interface_green', rotationAngle: 0,
info: {berthName: '808泊位', totalPower: '99999千瓦时', updateTime: '2025-10-31 23:59:59', status: '使用中', InterfaceType: '350A', berthDepth: '10米'},
parent: {},
tags:[
{name: '808泊位', xy: [38.98596369669985, 118.45462739467622], type: 0, fontSize: 16, tagAnchorType: 2, background: false, tags:[], icons: [], parent: {},},
],
icons: [
{xy: [38.98548876283165, 118.45513701438905], type: 3, icon: 'ship_green', rotationAngle: 180, reason: 9, status: 1,
info: {shipName: '永宁2', callSign: 'XHHH1', shipCompany:'华能', phone: '13333333333', length: '190米', width: '40米', tonnage: '18万吨', type: '运煤船', CommodityName: '煤炭', cargoWeight: '17万吨',
startUsingTime: '2025-11-01 01:50', scheduledDepartureTime: '2025-11-02 23:30', shorePowerConsumption: '2192千瓦时', totalAuxiliaryPower: '1100千瓦'},
parent: {},
tags:[
{name: '永宁2', xy: [38.985901491123464, 118.45530331134798], type: 0, fontSize: 16, tagAnchorType: 1, background: true, tags:[], icons: [], parent: {},},
{name: '永宁2', xy: [38.985901491123464, 118.45530331134798], type: 0, fontSize: 16, tagAnchorType: 1, background: true, tags:[], icons: [], parent: {},},
],
icons: [
]
},
]
},
{xy: [38.98700208813734, 118.454766869545], type: 2, icon: 'interface_yellow', rotationAngle: 0,
info: {berthName: '809泊位', totalPower: '99999千瓦时', updateTime: '2025-10-31 23:59:59', status: '故障/检修', InterfaceType: '350A', berthDepth: '10米'},
parent: {},
tags:[
{name: '809泊位', xy: [38.987218846204165, 118.4546595811844], type: 0, fontSize: 16, tagAnchorType: 2, background: false, tags:[], icons: [], parent: {},},
],
icons: [
{xy: [38.98722303926324, 118.45514164997102], type: 3, icon: 'ship_problem', rotationAngle: 0, reason: 8, status: 3,
info: {shipName: '华鲁海1', callSign: 'XHHH1', shipCompany:'华能', phone: '13333333333', length: '190米', width: '40米', tonnage: '18万吨', type: '运煤船', CommodityName: '煤炭', cargoWeight: '17万吨',
startUsingTime: '2025-11-01 01:50', scheduledDepartureTime: '2025-11-02 23:30', shorePowerConsumption: '0千瓦时', totalAuxiliaryPower: '1100千瓦'},
parent: {},
tags:[
{name: '华鲁海1', xy: [38.98731892349952, 118.45531404018404], type: 0, fontSize: 16, tagAnchorType: 1, background: true, tags:[], icons: [], parent: {},},
],
icons: [
// {xy: [38.94598419252245, 118.44058871269227], type: 4, icon: 'problem', rotationAngle: 0, parent: {}, tags:[], icons: [],},
]
},
]
},
{xy: [38.991184197811535, 118.454766869545], type: 2, icon: 'interface_green', rotationAngle: 0,
info: {berthName: '810泊位', totalPower: '99999千瓦时', updateTime: '2025-10-31 23:59:59', status: '未使用', InterfaceType: '350A', berthDepth: '10米'},
parent: {},
tags:[
{name: '810泊位', xy: [38.991409041819146, 118.45464885234834], type: 0, fontSize: 16, tagAnchorType: 2, background: false, tags:[], icons: [], parent: {},},
],
icons: [
{xy: [38.990935693058206, 118.45513628555299], type: 3, icon: 'ship_green', rotationAngle: 180, reason: 9, status: 1,
info: {shipName: '和泰通10', callSign: 'XHHH1', shipCompany:'华能', phone: '13333333333', length: '190米', width: '40米', tonnage: '18万吨', type: '运煤船', CommodityName: '煤炭', cargoWeight: '17万吨',
startUsingTime: '2025-11-01 01:50', scheduledDepartureTime: '2025-11-02 23:30', shorePowerConsumption: '0千瓦时', totalAuxiliaryPower: '1100千瓦'},
parent: {},
tags:[
{name: '和泰通10', xy: [38.99135791146007, 118.4552925817847], type: 0, fontSize: 16, tagAnchorType: 1, background: true, parent: {}, tags:[], icons: [],},
],
icons: [
// {xy: [38.94723581646269, 118.43793869018556], type: 4, icon: 'problem', rotationAngle: 0, parent: {}, tags:[], icons: [],},
],
},
]
},
]
},
]
},
{name: '国投曹妃甸港口', xy: [38.95787372769494, 118.4544610977173], type: 0, fontSize: 18, tagAnchorType: 2, background: false,
parent: {},
tags:[],
icons: [
// {xy: [38.97960116983217, 118.45476150512695], type: 1, icon: 'shorepower_blue', rotationAngle: 0,
// info: {name: '1号岸电设备', installedPower: '2000千伏安', voltage: '6/6.6千伏', frequency: '50/60赫兹', interfaceCount: '2', simultaneouslyInterfaceCount: '1'},
// parent: {},
// tags:[],
// icons: [
{xy: [38.96404006594963, 118.45477223396303], type: 2, icon: 'interface_yellow', rotationAngle: 0,
info: {berthName: '201#泊位', totalPower: '99999千瓦时', updateTime: '2025-10-31 23:59:59', status: '未使用', InterfaceType: '350A', berthDepth: '10米'},
parent: {},
tags:[
{name: '201#泊位', xy: [38.964256852392246, 118.45469713211061], type: 0, fontSize: 16, tagAnchorType: 2, background: false, tags:[], parent: {}, icons: [],},
],
icons:[
{xy: [38.9642861101337, 118.45515310764314], type: 3, icon: 'ship_problem', rotationAngle: 0, reason: 2, status: 2,
info: {shipName: '新世纪128', callSign: 'XHHH1', shipCompany:'国投', phone: '13333333333', length: '190米', width: '40米', tonnage: '18万吨', type: '运煤船', CommodityName: '煤炭', cargoWeight: '17万吨',
startUsingTime: '2025-11-01 01:50', scheduledDepartureTime: '2025-11-02 23:30', shorePowerConsumption: '0千瓦时', totalAuxiliaryPower: '1100千瓦'},
parent: {},
tags:[
{name: '新世纪128', xy: [38.96440704679231, 118.4553247690201], type: 0, fontSize: 16, tagAnchorType: 1, background: true, parent: {}, tags:[], icons: [],},
],
icons: [
// {xy: [38.941786918944366, 118.44942927360536], type: 4, icon: 'problem', rotationAngle: 0, parent: {}, tags:[], icons: [],},
]
},
]
},
{xy: [38.96133711832845, 118.45479905605318], type: 2, icon: 'interface_yellow', rotationAngle: 0,
info: {berthName: '202#泊位', totalPower: '99999千瓦时', updateTime: '2025-10-31 23:59:59', status: '未使用', InterfaceType: '350A', berthDepth: '10米'},
parent: {},
tags:[
{name: '202#泊位', xy: [38.96155397914768, 118.4546595811844], type: 0, fontSize: 16, tagAnchorType: 2, background: false, tags:[], icons: [], parent: {},},
],
icons: [
{xy: [38.96159568307525, 118.4551638364792], type: 3, icon: 'ship_problem', rotationAngle: 0, reason: 7, status: 3,
info: {shipName: '信洋新征程', callSign: 'XHHH1', shipCompany:'国投', phone: '13333333333', length: '190米', width: '40米', tonnage: '18万吨', type: '运煤船', CommodityName: '煤炭', cargoWeight: '17万吨',
startUsingTime: '2025-11-01 01:50', scheduledDepartureTime: '2025-11-02 23:30', shorePowerConsumption: '0千瓦时', totalAuxiliaryPower: '1100千瓦'},
parent: {},
tags:[
{name: '信洋新征程', xy: [38.96169577240135, 118.4553247690201], type: 0, fontSize: 16, tagAnchorType: 1, background: true, tags:[], icons: [], parent: {},},
],
icons: [
// {xy: [38.94322219750925, 118.4463930130005], type: 4, icon: 'problem', rotationAngle: 0, parent: {}, tags:[], icons: [],},
]
}
]
},
// ]
// },
{xy: [38.95564214358565, 118.45195054960143], type: 1, icon: 'shorepower_blue', rotationAngle: 0, ratio: 4,
info: {name: '3号岸电设备', installedPower: '2000千伏安', voltage: '6/6.6千伏', frequency: '50/60赫兹', interfaceCount: '3', simultaneouslyInterfaceCount: '1'},
parent: {},
tags:[],
icons: [
{xy: [38.95914310536718, 118.45478733581544], type: 2, icon: 'interface_yellow', rotationAngle: 0,
info: {berthName: '203#泊位', totalPower: '99999千瓦时', updateTime: '2025-10-31 23:59:59', status: '使用中', InterfaceType: '350A', berthDepth: '10米'},
parent: {},
tags:[
{name: '203#泊位', xy: [38.95935580237831, 118.45465421676637], type: 0, fontSize: 16, tagAnchorType: 2, background: false, tags:[], icons: [], parent: {},},
],
icons: [
{xy: [38.95937665499213, 118.45516920089723], type: 3, icon: 'ship_problem', rotationAngle: 0, reason: 1, status: 3,
info: {shipName: '舜华', callSign: 'XHHH1', shipCompany:'国投', phone: '13333333333', length: '190米', width: '40米', tonnage: '18万吨', type: '运煤船', CommodityName: '煤炭', cargoWeight: '17万吨',
startUsingTime: '2025-11-01 01:50', scheduledDepartureTime: '2025-11-02 23:30', shorePowerConsumption: '2192千瓦时', totalAuxiliaryPower: '1100千瓦'},
parent: {},
tags:[
{name: '舜华', xy: [38.95937665499213, 118.45534086227418], type: 0, fontSize: 16, tagAnchorType: 1, background: true, tags:[], icons: [], parent: {},},
],
icons: [
]
},
]
},
{xy: [38.9566666666667, 118.45479], type: 2, icon: 'interface_yellow', rotationAngle: 0,
info: {berthName: '204#泊位', totalPower: '99999千瓦时', updateTime: '2025-10-31 23:59:59', status: '故障/检修', InterfaceType: '350A', berthDepth: '10米'},
parent: {},
tags:[
{name: '204#泊位', xy: [38.95688208014856, 118.45465421676637], type: 0, fontSize: 16, tagAnchorType: 2, background: false, tags:[], icons: [], parent: {},},
],
icons: [
{xy: [38.95688625081742, 118.45516701438905], type: 3, icon: 'ship_problem', rotationAngle: 0, reason: 2, status: 3,
info: {shipName: '安永山', callSign: 'XHHH1', shipCompany:'国投', phone: '13333333333', length: '190米', width: '40米', tonnage: '18万吨', type: '运煤船', CommodityName: '煤炭', cargoWeight: '17万吨',
startUsingTime: '2025-11-01 01:50', scheduledDepartureTime: '2025-11-02 23:30', shorePowerConsumption: '0千瓦时', totalAuxiliaryPower: '1100千瓦'},
parent: {},
tags:[
{name: '安永山', xy: [38.95689876282247, 118.45535159111024], type: 0, fontSize: 16, tagAnchorType: 1, background: true, tags:[], icons: [], parent: {},},
],
icons: [
// {xy: [38.94598419252245, 118.44058871269227], type: 4, icon: 'problem', rotationAngle: 0, parent: {}, tags:[], icons: [],},
]
},
]
},
{xy: [38.95382751650487, 118.45478487083635], type: 2, icon: 'interface_yellow', rotationAngle: 1,
info: {berthName: '205#泊位', totalPower: '99999千瓦时', updateTime: '2025-10-31 23:59:59', status: '未使用', InterfaceType: '350A', berthDepth: '10米'},
parent: {},
tags:[
{name: '205#泊位', xy: [38.9544906279055, 118.45498144626619], type: 0, fontSize: 16, tagAnchorType: 4, background: false, tags:[], icons: [], parent: {},},
],
icons: [
{xy: [38.95403710840712, 118.45515310764314], type: 3, icon: 'ship_problem', rotationAngle: 1, reason: 3, status: 2,
info: {shipName: '东亿603', callSign: 'XHHH1', shipCompany:'国投', phone: '13333333333', length: '190米', width: '40米', tonnage: '18万吨', type: '运煤船', CommodityName: '煤炭', cargoWeight: '17万吨',
startUsingTime: '2025-11-01 01:50', scheduledDepartureTime: '2025-11-02 23:30', shorePowerConsumption: '0千瓦时', totalAuxiliaryPower: '1100千瓦'},
parent: {},
tags:[
{name: '东亿603', xy: [38.9540823482338, 118.45533013343812], type: 0, fontSize: 16, tagAnchorType: 1, background: true, parent: {}, tags:[], icons: [],},
],
icons: [
// {xy: [38.94723581646269, 118.43793869018556], type: 4, icon: 'problem', rotationAngle: 0, parent: {}, tags:[], icons: [],},
],
},
]
},
]
},
{xy: [38.94949642639647, 118.45278739789693], type: 1, icon: 'shorepower_blue', rotationAngle: 0, ratio: 4,
info: {name: '4号岸电设备', installedPower: '2000千伏安', voltage: '6/6.6千伏', frequency: '50/60赫兹', interfaceCount: '3', simultaneouslyInterfaceCount: '1'},
parent: {},
tags:[],
icons: [
{xy: [38.94920619528106, 118.45447719097139], type: 2, icon: 'interface_yellow', rotationAngle: -58.5,
info: {berthName: '206#泊位', totalPower: '99999千瓦时', updateTime: '2025-10-31 23:59:59', status: '使用中', InterfaceType: '350A', berthDepth: '10米'},
parent: {},
tags:[
{name: '206#泊位', xy: [38.94914779956731, 118.4543538093567], type: 0, fontSize: 16, tagAnchorType: 2, background: false, tags:[], icons: [], parent: {},},
],
icons: [
{xy: [38.949429777655, 118.45474004745483], type: 3, icon: 'ship_problem', rotationAngle: 121.5, reason: 7, status: 3,
info: {shipName: '太行128', callSign: 'XHHH1', shipCompany:'国投', phone: '13333333333', length: '190米', width: '40米', tonnage: '18万吨', type: '运煤船', CommodityName: '煤炭', cargoWeight: '17万吨',
startUsingTime: '2025-11-01 01:50', scheduledDepartureTime: '2025-11-02 23:30', shorePowerConsumption: '2192千瓦时', totalAuxiliaryPower: '1100千瓦'},
parent: {},
tags:[
{name: '太行128', xy: [38.94948148871223, 118.45495462417604], type: 0, fontSize: 16, tagAnchorType: 3, background: true, tags:[], icons: [], parent: {},},
],
icons: [
]
},
]
},
{xy: [38.947992362158175, 118.4570360183716], type: 2, icon: 'interface_yellow', rotationAngle: -58.5,
info: {berthName: '207#泊位', totalPower: '99999千瓦时', updateTime: '2025-10-31 23:59:59', status: '故障/检修', InterfaceType: '350A', berthDepth: '10米'},
parent: {},
tags:[
{name: '207#泊位', xy: [38.947929820857425, 118.4569823741913], type: 0, fontSize: 16, tagAnchorType: 2, background: false, tags:[], icons: [], parent: {},},
],
icons: [
{xy: [38.94815255152406, 118.45741152763368], type: 3, icon: 'ship_problem', rotationAngle: 121.5, reason: 1, status: 3,
info: {shipName: '东疆胜', callSign: 'XHHH1', shipCompany:'国投', phone: '13333333333', length: '190米', width: '40米', tonnage: '18万吨', type: '运煤船', CommodityName: '煤炭', cargoWeight: '17万吨',
startUsingTime: '2025-11-01 01:50', scheduledDepartureTime: '2025-11-02 23:30', shorePowerConsumption: '0千瓦时', totalAuxiliaryPower: '1100千瓦'},
parent: {},
tags:[
{name: '东疆胜', xy: [38.94826768691209, 118.45749735832216], type: 0, fontSize: 16, tagAnchorType: 3, background: true, tags:[], icons: [], parent: {},},
],
icons: [
// {xy: [38.94598419252245, 118.44058871269227], type: 4, icon: 'problem', rotationAngle: 0, parent: {}, tags:[], icons: [],},
]
},
]
},
{xy: [38.946444199206084, 118.46030294895174], type: 2, icon: 'interface_yellow', rotationAngle: -58.5,
info: {berthName: '208#泊位', totalPower: '99999千瓦时', updateTime: '2025-10-31 23:59:59', status: '未使用', InterfaceType: '350A', berthDepth: '10米'},
parent: {},
tags:[
{name: '208#泊位', xy: [38.946369116068404, 118.46030831336977], type: 0, fontSize: 16, tagAnchorType: 2, background: false, tags:[], icons: [], parent: {},},
],
icons: [
{xy: [38.9466319058961, 118.46060872077943], type: 3, icon: 'ship_problem', rotationAngle: 121.5, reason: 2, status: 3,
info: {shipName: '东和明16', callSign: 'XHHH1', shipCompany:'国投', phone: '13333333333', length: '190米', width: '40米', tonnage: '18万吨', type: '运煤船', CommodityName: '煤炭', cargoWeight: '17万吨',
startUsingTime: '2025-11-01 01:50', scheduledDepartureTime: '2025-11-02 23:30', shorePowerConsumption: '0千瓦时', totalAuxiliaryPower: '1100千瓦'},
parent: {},
tags:[
{name: '东和明16', xy: [38.946732017163924, 118.46071600914003], type: 0, fontSize: 16, tagAnchorType: 3, background: true, parent: {}, tags:[], icons: [],},
],
icons: [
// {xy: [38.94723581646269, 118.43793869018556], type: 4, icon: 'problem', rotationAngle: 0, parent: {}, tags:[], icons: [],},
],
},
]
},
]
},
{xy: [38.946733887149165, 118.45633864229731], type: 1, icon: 'shorepower_blue', rotationAngle: 0, ratio: 4,
info: {name: '5号岸电设备', installedPower: '2000千伏安', voltage: '6/6.6千伏', frequency: '50/60赫兹', interfaceCount: '2', simultaneouslyInterfaceCount: '1'},
parent: {},
tags:[],
icons: [
{xy: [38.944815666282894, 118.4638434648514], type: 2, icon: 'interface_yellow', rotationAngle: -58.5, ratio:0.7,
info: {berthName: '209#泊位', totalPower: '99999千瓦时', updateTime: '2025-10-31 23:59:59', status: '未使用', InterfaceType: '350A', berthDepth: '10米'},
parent: {},
tags:[
{name: '209#泊位', xy: [38.944733926653164, 118.46453547477724], type: 0, fontSize: 16, tagAnchorType: 3, background: false, tags:[], parent: {}, icons: [],},
],
icons:[
{xy: [38.94517410846952, 118.46366643905641], type: 3, icon: 'ship_problem', rotationAngle: -58.5, reason: 3, status: 2,
info: {shipName: '华盛151', callSign: 'XHHH1', shipCompany:'国投', phone: '13333333333', length: '190米', width: '40米', tonnage: '18万吨', type: '运煤船', CommodityName: '煤炭', cargoWeight: '17万吨',
startUsingTime: '2025-11-01 01:50', scheduledDepartureTime: '2025-11-02 23:30', shorePowerConsumption: '0千瓦时', totalAuxiliaryPower: '1100千瓦'},
parent: {},
tags:[
{name: '华盛151', xy: [38.945384685350895, 118.46356987953187], type: 0, fontSize: 16, tagAnchorType: 3, background: true, parent: {}, tags:[], icons: [],},
],
icons: [
// {xy: [38.941786918944366, 118.44942927360536], type: 4, icon: 'problem', rotationAngle: 0, parent: {}, tags:[], icons: [],},
]
},
]
},
{xy: [38.94463691900152, 118.46389218444597], type: 2, icon: 'interface_red', rotationAngle: -58.5, ratio:0.7,
info: {berthName: '210#泊位', totalPower: '99999千瓦时', updateTime: '2025-10-31 23:59:59', status: '未使用', InterfaceType: '350A', berthDepth: '10米'},
parent: {},
tags:[
{name: '210#泊位', xy: [38.9449967491522, 118.4628349542618], type: 0, fontSize: 16, tagAnchorType: 4, background: false, tags:[], icons: [], parent: {},},
],
icons: [
{xy: [38.94455452597361, 118.46345471881868], type: 3, icon: 'ship_red', rotationAngle: -58.5, reason: 4, status: 3,
info: {shipName: '华润电力2', callSign: 'XHHH1', shipCompany:'国投', phone: '13333333333', length: '190米', width: '40米', tonnage: '18万吨', type: '运煤船', CommodityName: '煤炭', cargoWeight: '17万吨',
startUsingTime: '2025-11-01 01:50', scheduledDepartureTime: '2025-11-02 23:30', shorePowerConsumption: '0千瓦时', totalAuxiliaryPower: '1100千瓦'},
parent: {},
tags:[
{name: '华润电力2', xy: [38.94439598327865, 118.46348404884338], type: 0, fontSize: 16, tagAnchorType: 2, background: true, tags:[], icons: [], parent: {},},
],
icons: [
// {xy: [38.94322219750925, 118.4463930130005], type: 4, icon: 'problem', rotationAngle: 0, parent: {}, tags:[], icons: [],},
]
}
]
},
]
},
]
},
{type: 4, color: `rgba(130, 90, 10, 1)`, fillColor: `rgba(199, 187, 132, 1)`, fillOpacity: 1, minZoom: 13, // 华能曹妃甸港-区域坐标
xy: [
[38.95702275355564, 118.43592166900636],
[38.95640120226074, 118.43591630458833],
[38.956217655869274, 118.4363079071045],
[38.95624685646336, 118.43660831451416],
[38.95559192596182, 118.43797624111177],
[38.955400034349886, 118.4380030632019],
[38.95524151480031, 118.43798696994783],
[38.955137225429574, 118.43793332576753],
[38.955095509638305, 118.43791186809541],
[38.95495367576432, 118.43785285949708],
[38.95062343440496, 118.43446791172029],
[38.94917161796837, 118.43751490116121],
[38.948249615654795, 118.43681752681734],
[38.94158664520672, 118.45089912414551],
[38.94206229440991, 118.45126926898958],
[38.942199981741595, 118.45100104808809],
[38.94262555907637, 118.45133364200593],
[38.942266739139505, 118.45206320285799],
[38.94220832641979, 118.4522992372513],
[38.942162430677655, 118.45263183116914],
[38.942254222132206, 118.45551788806917],
[38.942604697501864, 118.45499217510225],
[38.94276741762028, 118.45498681068422],
[38.94303444469763, 118.45517992973329],
[38.9429676880226, 118.45531940460206],
[38.945963331936206, 118.45765829086305],
[38.946017569447626, 118.45756709575654],
[38.94645146804519, 118.4579372406006],
],
},
{type: 4, color: `rgba(130, 90, 10, 1)`, fillColor: `rgba(199, 187, 132, 1)`, fillOpacity: 1, minZoom: 13, // 国投曹妃甸港-区域坐标
xy: [
[38.94646398431148, 118.4579372406006],
[38.9464097471416, 118.45811426639558],
[38.94590909438329, 118.45916032791139],
[38.94577558638391, 118.45953583717348],
[38.94582982403899, 118.45973432064058],
[38.94633047735712, 118.46012592315675],
[38.94415678188506, 118.46471250057222],
[38.94439042605403, 118.46491634845735],
[38.949897529903765, 118.45332384109498],
[38.95199178589285, 118.45497071743013],
[38.95273852754434, 118.45491707324982],
[38.96562796722419, 118.45494389533998],
[38.96562796722419, 118.44710111618043],
[38.97092071810863, 118.43991279602052],
[38.97109171449716, 118.43591094017029],
[38.97021170428815, 118.43586266040803],
[38.96904390134737, 118.4361094236374],
[38.95702275355564, 118.43590557575227],
// [],
// [],
// [],
// [],
// [],
// [],
]
},
{type: 5, color: `rgba(220, 70, 20, 1)`, minZoom: 13, dashArray: [10, 7], weight: 2,
xy: [
[38.9815766467409, 118.45252454280855],
[38.98156382800117, 118.4547507762909],
[38.98032560436857, 118.45476150512695],
]
},
{type: 5, color: `rgba(220, 70, 20, 1)`, minZoom: 13, dashArray: [10, 7], weight: 2,
xy: [
[38.98156382800117, 118.4547507762909],
[38.982860467266946, 118.45474541187288],
]
},
{type: 5, color: `rgba(220, 70, 20, 1)`, minZoom: 13, dashArray: [10, 7], weight: 2,
xy: [
[38.98820189340982, 118.45261573791505],
[38.9881646528042, 118.45448255538942],
[38.9857050495674, 118.45447182655336],
[38.98570921853932, 118.45477223396303],
]
},
{type: 5, color: `rgba(220, 70, 20, 1)`, minZoom: 13, dashArray: [10, 7], weight: 2,
xy: [
[38.986959899035384, 118.45447182655336],
[38.98696823683122, 118.45474004745483],
]
},
{type: 5, color: `rgba(220, 70, 20, 1)`, minZoom: 13, dashArray: [10, 7], weight: 2,
xy: [
[38.9881646528042, 118.45448255538942],
[38.99112033712731, 118.45448255538942],
[38.991137011737834, 118.45476150512695],
]
},
{type: 5, color: `rgba(220, 70, 20, 1)`, minZoom: 13, dashArray: [10, 7], weight: 2,
xy: [
[38.95564214358565, 118.45195054960143],
[38.955600451863084, 118.45478296274207],
[38.95908758754183, 118.45478832627971],
]
},
{type: 5, color: `rgba(220, 70, 20, 1)`, minZoom: 13, dashArray: [10, 7], weight: 2,
xy: [
[38.955600451863084, 118.45478296274207],
[38.95378953344623, 118.45477759744465],
]
},
{type: 5, color: `rgba(220, 70, 20, 1)`, minZoom: 13, dashArray: [10, 7], weight: 2,
xy: [
[38.949363241777746, 118.4527659410885],
[38.94936348204682, 118.45412850350844],
[38.946413737058165, 118.46034586343174],
]
},
{type: 5, color: `rgba(220, 70, 20, 1)`, minZoom: 13, dashArray: [10, 7], weight: 2,
xy: [
[38.94660148813071, 118.45633327914759],
[38.94658897286304, 118.4578353160253],
[38.945821269938364, 118.45951974313134],
[38.94586299313705, 118.45969676891002],
[38.94636784189484, 118.46008837139007],
[38.944586252136524, 118.46385955691731],
]
},
{type: 5, color: `rgba(220, 70, 20, 1)`, minZoom: 13, dashArray: [10, 7], weight: 2,
xy: [
[38.94542489832619, 118.46209466354823],
[38.94557510256635, 118.46220731631647],
[38.944794871518646, 118.46387565016992],
]
},
]
export default tagsData ;

1379
public/map/index.vue

File diff suppressed because it is too large

57
src/App.vue

@ -0,0 +1,57 @@
<script lang="ts" setup>
import { isDark } from '@/utils/is'
import { useAppStore } from '@/store/modules/app'
import { useDesign } from '@/hooks/web/useDesign'
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
import routerSearch from '@/components/RouterSearch/index.vue'
defineOptions({ name: 'APP' })
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('app')
const appStore = useAppStore()
const currentSize = computed(() => appStore.getCurrentSize)
const greyMode = computed(() => appStore.getGreyMode)
const { wsCache } = useCache()
//
const setDefaultTheme = () => {
let isDarkTheme = wsCache.get(CACHE_KEY.IS_DARK)
if (isDarkTheme === null) {
isDarkTheme = isDark()
}
appStore.setIsDark(isDarkTheme)
}
setDefaultTheme()
</script>
<template>
<ConfigGlobal :size="currentSize">
<RouterView :class="greyMode ? `${prefixCls}-grey-mode` : ''" />
<routerSearch />
</ConfigGlobal>
</template>
<style lang="scss">
$prefix-cls: #{$namespace}-app;
.size {
width: 100%;
height: 100%;
}
html,
body {
@extend .size;
padding: 0 !important;
margin: 0;
overflow: hidden;
#app {
@extend .size;
}
}
.#{$prefix-cls}-grey-mode {
filter: grayscale(100%);
}
</style>

65
src/api/ai/chat/conversation/index.ts

@ -0,0 +1,65 @@
import request from '@/config/axios'
// AI 聊天对话 VO
export interface ChatConversationVO {
id: number // ID 编号
userId: number // 用户编号
title: string // 对话标题
pinned: boolean // 是否置顶
roleId: number // 角色编号
modelId: number // 模型编号
model: string // 模型标志
temperature: number // 温度参数
maxTokens: number // 单条回复的最大 Token 数量
maxContexts: number // 上下文的最大 Message 数量
createTime?: Date // 创建时间
// 额外字段
systemMessage?: string // 角色设定
modelName?: string // 模型名字
roleAvatar?: string // 角色头像
modelMaxTokens?: string // 模型的单条回复的最大 Token 数量
modelMaxContexts?: string // 模型的上下文的最大 Message 数量
}
// AI 聊天对话 API
export const ChatConversationApi = {
// 获得【我的】聊天对话
getChatConversationMy: async (id: number) => {
return await request.get({ url: `/ai/chat/conversation/get-my?id=${id}` })
},
// 新增【我的】聊天对话
createChatConversationMy: async (data?: ChatConversationVO) => {
return await request.post({ url: `/ai/chat/conversation/create-my`, data })
},
// 更新【我的】聊天对话
updateChatConversationMy: async (data: ChatConversationVO) => {
return await request.put({ url: `/ai/chat/conversation/update-my`, data })
},
// 删除【我的】聊天对话
deleteChatConversationMy: async (id: string) => {
return await request.delete({ url: `/ai/chat/conversation/delete-my?id=${id}` })
},
// 删除【我的】所有对话,置顶除外
deleteChatConversationMyByUnpinned: async () => {
return await request.delete({ url: `/ai/chat/conversation/delete-by-unpinned` })
},
// 获得【我的】聊天对话列表
getChatConversationMyList: async () => {
return await request.get({ url: `/ai/chat/conversation/my-list` })
},
// 获得对话分页
getChatConversationPage: async (params: any) => {
return await request.get({ url: `/ai/chat/conversation/page`, params })
},
// 管理员删除消息
deleteChatConversationByAdmin: async (id: number) => {
return await request.delete({ url: `/ai/chat/conversation/delete-by-admin?id=${id}` })
}
}

104
src/api/ai/chat/message/index.ts

@ -0,0 +1,104 @@
import request from '@/config/axios'
import { fetchEventSource } from '@microsoft/fetch-event-source'
import { getAccessToken } from '@/utils/auth'
import { config } from '@/config/axios/config'
// 聊天VO
export interface ChatMessageVO {
id: number // 编号
conversationId: number // 对话编号
type: string // 消息类型
userId: string // 用户编号
roleId: string // 角色编号
model: number // 模型标志
modelId: number // 模型编号
content: string // 聊天内容
reasoningContent?: string // 推理内容
attachmentUrls?: string[] // 附件 URL 数组
tokens: number // 消耗 Token 数量
segmentIds?: number[] // 段落编号
segments?: {
id: number // 段落编号
content: string // 段落内容
documentId: number // 文档编号
documentName: string // 文档名称
}[]
webSearchPages?: {
name: string // 名称
icon: string // 图标
title: string // 标题
url: string // URL
snippet: string // 内容的简短描述
summary: string // 内容的文本摘要
}[]
createTime: Date // 创建时间
roleAvatar: string // 角色头像
userAvatar: string // 用户头像
}
// AI chat 聊天
export const ChatMessageApi = {
// 消息列表
getChatMessageListByConversationId: async (conversationId: number | null) => {
return await request.get({
url: `/ai/chat/message/list-by-conversation-id?conversationId=${conversationId}`
})
},
// 发送 Stream 消息
// 为什么不用 axios 呢?因为它不支持 SSE 调用
sendChatMessageStream: async (
conversationId: number,
content: string,
ctrl,
enableContext: boolean,
enableWebSearch: boolean,
onMessage,
onError,
onClose,
attachmentUrls?: string[]
) => {
const token = getAccessToken()
return fetchEventSource(`${config.base_url}/ai/chat/message/send-stream`, {
method: 'post',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
},
openWhenHidden: true,
body: JSON.stringify({
conversationId,
content,
useContext: enableContext,
useSearch: enableWebSearch,
attachmentUrls: attachmentUrls || []
}),
onmessage: onMessage,
onerror: onError,
onclose: onClose,
signal: ctrl.signal
})
},
// 删除消息
deleteChatMessage: async (id: string) => {
return await request.delete({ url: `/ai/chat/message/delete?id=${id}` })
},
// 删除指定对话的消息
deleteByConversationId: async (conversationId: number) => {
return await request.delete({
url: `/ai/chat/message/delete-by-conversation-id?conversationId=${conversationId}`
})
},
// 获得消息分页
getChatMessagePage: async (params: any) => {
return await request.get({ url: '/ai/chat/message/page', params })
},
// 管理员删除消息
deleteChatMessageByAdmin: async (id: number) => {
return await request.delete({ url: `/ai/chat/message/delete-by-admin?id=${id}` })
}
}

102
src/api/ai/image/index.ts

@ -0,0 +1,102 @@
import request from '@/config/axios'
// AI 绘图 VO
export interface ImageVO {
id: number // 编号
platform: string // 平台
model: string // 模型
prompt: string // 提示词
width: number // 图片宽度
height: number // 图片高度
status: number // 状态
publicStatus: boolean // 公开状态
picUrl: string // 任务地址
errorMessage: string // 错误信息
options: any // 配置 Map<string, string>
taskId: number // 任务编号
buttons: ImageMidjourneyButtonsVO[] // mj 操作按钮
createTime: Date // 创建时间
finishTime: Date // 完成时间
}
export interface ImageDrawReqVO {
prompt: string // 提示词
modelId: number // 模型
style: string // 图像生成的风格
width: string // 图片宽度
height: string // 图片高度
options: object // 绘制参数,Map<String, String>
}
export interface ImageMidjourneyImagineReqVO {
prompt: string // 提示词
modelId: number // 模型
base64Array: string[] // size不能为空
width: string // 图片宽度
height: string // 图片高度
version: string // 版本
}
export interface ImageMidjourneyActionVO {
id: number // 图片编号
customId: string // MJ::JOB::upsample::1::85a4b4c1-8835-46c5-a15c-aea34fad1862 动作标识
}
export interface ImageMidjourneyButtonsVO {
customId: string // MJ::JOB::upsample::1::85a4b4c1-8835-46c5-a15c-aea34fad1862 动作标识
emoji: string // 图标 emoji
label: string // Make Variations 文本
style: number // 样式: 2(Primary)、3(Green)
}
// AI 图片 API
export const ImageApi = {
// 获取【我的】绘图分页
getImagePageMy: async (params: any) => {
return await request.get({ url: `/ai/image/my-page`, params })
},
// 获取【我的】绘图记录
getImageMy: async (id: number) => {
return await request.get({ url: `/ai/image/get-my?id=${id}` })
},
// 获取【我的】绘图记录列表
getImageListMyByIds: async (ids: number[]) => {
return await request.get({ url: `/ai/image/my-list-by-ids`, params: { ids: ids.join(',') } })
},
// 生成图片
drawImage: async (data: ImageDrawReqVO) => {
return await request.post({ url: `/ai/image/draw`, data })
},
// 删除【我的】绘画记录
deleteImageMy: async (id: number) => {
return await request.delete({ url: `/ai/image/delete-my?id=${id}` })
},
// ================ midjourney 专属 ================
// 【Midjourney】生成图片
midjourneyImagine: async (data: ImageMidjourneyImagineReqVO) => {
return await request.post({ url: `/ai/image/midjourney/imagine`, data })
},
// 【Midjourney】Action 操作(二次生成图片)
midjourneyAction: async (data: ImageMidjourneyActionVO) => {
return await request.post({ url: `/ai/image/midjourney/action`, data })
},
// ================ 绘图管理 ================
// 查询绘画分页
getImagePage: async (params: any) => {
return await request.get({ url: `/ai/image/page`, params })
},
// 更新绘画发布状态
updateImage: async (data: any) => {
return await request.put({ url: '/ai/image/update', data })
},
// 删除绘画
deleteImage: async (id: number) => {
return await request.delete({ url: `/ai/image/delete?id=` + id })
}
}

54
src/api/ai/knowledge/document/index.ts

@ -0,0 +1,54 @@
import request from '@/config/axios'
// AI 知识库文档 VO
export interface KnowledgeDocumentVO {
id: number // 编号
knowledgeId: number // 知识库编号
name: string // 文档名称
contentLength: number // 字符数
tokens: number // token 数
segmentMaxTokens: number // 分片最大 token 数
retrievalCount: number // 召回次数
status: number // 是否启用
}
// AI 知识库文档 API
export const KnowledgeDocumentApi = {
// 查询知识库文档分页
getKnowledgeDocumentPage: async (params: any) => {
return await request.get({ url: `/ai/knowledge/document/page`, params })
},
// 查询知识库文档详情
getKnowledgeDocument: async (id: number) => {
return await request.get({ url: `/ai/knowledge/document/get?id=` + id })
},
// 新增知识库文档(单个)
createKnowledgeDocument: async (data: any) => {
return await request.post({ url: `/ai/knowledge/document/create`, data })
},
// 新增知识库文档(多个)
createKnowledgeDocumentList: async (data: any) => {
return await request.post({ url: `/ai/knowledge/document/create-list`, data })
},
// 修改知识库文档
updateKnowledgeDocument: async (data: any) => {
return await request.put({ url: `/ai/knowledge/document/update`, data })
},
// 修改知识库文档状态
updateKnowledgeDocumentStatus: async (data: any) => {
return await request.put({
url: `/ai/knowledge/document/update-status`,
data
})
},
// 删除知识库文档
deleteKnowledgeDocument: async (id: number) => {
return await request.delete({ url: `/ai/knowledge/document/delete?id=` + id })
}
}

44
src/api/ai/knowledge/knowledge/index.ts

@ -0,0 +1,44 @@
import request from '@/config/axios'
// AI 知识库 VO
export interface KnowledgeVO {
id: number // 编号
name: string // 知识库名称
description: string // 知识库描述
embeddingModelId: number // 嵌入模型编号,高质量模式时维护
topK: number // topK
similarityThreshold: number // 相似度阈值
}
// AI 知识库 API
export const KnowledgeApi = {
// 查询知识库分页
getKnowledgePage: async (params: any) => {
return await request.get({ url: `/ai/knowledge/page`, params })
},
// 查询知识库详情
getKnowledge: async (id: number) => {
return await request.get({ url: `/ai/knowledge/get?id=` + id })
},
// 新增知识库
createKnowledge: async (data: KnowledgeVO) => {
return await request.post({ url: `/ai/knowledge/create`, data })
},
// 修改知识库
updateKnowledge: async (data: KnowledgeVO) => {
return await request.put({ url: `/ai/knowledge/update`, data })
},
// 删除知识库
deleteKnowledge: async (id: number) => {
return await request.delete({ url: `/ai/knowledge/delete?id=` + id })
},
// 获取知识库简单列表
getSimpleKnowledgeList: async () => {
return await request.get({ url: `/ai/knowledge/simple-list` })
}
}

75
src/api/ai/knowledge/segment/index.ts

@ -0,0 +1,75 @@
import request from '@/config/axios'
// AI 知识库分段 VO
export interface KnowledgeSegmentVO {
id: number // 编号
documentId: number // 文档编号
knowledgeId: number // 知识库编号
vectorId: string // 向量库编号
content: string // 切片内容
contentLength: number // 切片内容长度
tokens: number // token 数量
retrievalCount: number // 召回次数
status: number // 文档状态
createTime: number // 创建时间
}
// AI 知识库分段 API
export const KnowledgeSegmentApi = {
// 查询知识库分段分页
getKnowledgeSegmentPage: async (params: any) => {
return await request.get({ url: `/ai/knowledge/segment/page`, params })
},
// 查询知识库分段详情
getKnowledgeSegment: async (id: number) => {
return await request.get({ url: `/ai/knowledge/segment/get?id=` + id })
},
// 删除知识库分段
deleteKnowledgeSegment: async (id: number) => {
return await request.delete({ url: `/ai/knowledge/segment/delete?id=` + id })
},
// 新增知识库分段
createKnowledgeSegment: async (data: KnowledgeSegmentVO) => {
return await request.post({ url: `/ai/knowledge/segment/create`, data })
},
// 修改知识库分段
updateKnowledgeSegment: async (data: KnowledgeSegmentVO) => {
return await request.put({ url: `/ai/knowledge/segment/update`, data })
},
// 修改知识库分段状态
updateKnowledgeSegmentStatus: async (data: any) => {
return await request.put({
url: `/ai/knowledge/segment/update-status`,
data
})
},
// 切片内容
splitContent: async (url: string, segmentMaxTokens: number) => {
return await request.get({
url: `/ai/knowledge/segment/split`,
params: { url, segmentMaxTokens }
})
},
// 获取文档处理列表
getKnowledgeSegmentProcessList: async (documentIds: number[]) => {
return await request.get({
url: `/ai/knowledge/segment/get-process-list`,
params: { documentIds: documentIds.join(',') }
})
},
// 搜索知识库分段
searchKnowledgeSegment: async (params: any) => {
return await request.get({
url: `/ai/knowledge/segment/search`,
params
})
}
}

60
src/api/ai/mindmap/index.ts

@ -0,0 +1,60 @@
import { getAccessToken } from '@/utils/auth'
import { fetchEventSource } from '@microsoft/fetch-event-source'
import { config } from '@/config/axios/config'
import request from '@/config/axios' // AI 思维导图 VO
// AI 思维导图 VO
export interface MindMapVO {
id: number // 编号
userId: number // 用户编号
prompt: string // 生成内容提示
generatedContent: string // 生成的思维导图内容
platform: string // 平台
model: string // 模型
errorMessage: string // 错误信息
}
// AI 思维导图生成 VO
export interface AiMindMapGenerateReqVO {
prompt: string
}
export const AiMindMapApi = {
generateMindMap: ({
data,
onClose,
onMessage,
onError,
ctrl
}: {
data: AiMindMapGenerateReqVO
onMessage?: (res: any) => void
onError?: (...args: any[]) => void
onClose?: (...args: any[]) => void
ctrl: AbortController
}) => {
const token = getAccessToken()
return fetchEventSource(`${config.base_url}/ai/mind-map/generate-stream`, {
method: 'post',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
},
openWhenHidden: true,
body: JSON.stringify(data),
onmessage: onMessage,
onerror: onError,
onclose: onClose,
signal: ctrl.signal
})
},
// 查询思维导图分页
getMindMapPage: async (params: any) => {
return await request.get({ url: `/ai/mind-map/page`, params })
},
// 删除思维导图
deleteMindMap: async (id: number) => {
return await request.delete({ url: `/ai/mind-map/delete?id=` + id })
}
}

44
src/api/ai/model/apiKey/index.ts

@ -0,0 +1,44 @@
import request from '@/config/axios'
// AI API 密钥 VO
export interface ApiKeyVO {
id: number // 编号
name: string // 名称
apiKey: string // 密钥
platform: string // 平台
url: string // 自定义 API 地址
status: number // 状态
}
// AI API 密钥 API
export const ApiKeyApi = {
// 查询 API 密钥分页
getApiKeyPage: async (params: any) => {
return await request.get({ url: `/ai/api-key/page`, params })
},
// 获得 API 密钥列表
getApiKeySimpleList: async () => {
return await request.get({ url: `/ai/api-key/simple-list` })
},
// 查询 API 密钥详情
getApiKey: async (id: number) => {
return await request.get({ url: `/ai/api-key/get?id=` + id })
},
// 新增 API 密钥
createApiKey: async (data: ApiKeyVO) => {
return await request.post({ url: `/ai/api-key/create`, data })
},
// 修改 API 密钥
updateApiKey: async (data: ApiKeyVO) => {
return await request.put({ url: `/ai/api-key/update`, data })
},
// 删除 API 密钥
deleteApiKey: async (id: number) => {
return await request.delete({ url: `/ai/api-key/delete?id=` + id })
}
}

83
src/api/ai/model/chatRole/index.ts

@ -0,0 +1,83 @@
import request from '@/config/axios'
// AI 聊天角色 VO
export interface ChatRoleVO {
id: number // 角色编号
modelId: number // 模型编号
name: string // 角色名称
avatar: string // 角色头像
category: string // 角色类别
sort: number // 角色排序
description: string // 角色描述
systemMessage: string // 角色设定
welcomeMessage: string // 角色设定
publicStatus: boolean // 是否公开
status: number // 状态
knowledgeIds?: number[] // 引用的知识库 ID 列表
toolIds?: number[] // 引用的工具 ID 列表
mcpClientNames?: string[] // 引用的 MCP Client 名字列表
}
// AI 聊天角色 分页请求 vo
export interface ChatRolePageReqVO {
name?: string // 角色名称
category?: string // 角色类别
publicStatus: boolean // 是否公开
pageNo: number // 是否公开
pageSize: number // 是否公开
}
// AI 聊天角色 API
export const ChatRoleApi = {
// 查询聊天角色分页
getChatRolePage: async (params: any) => {
return await request.get({ url: `/ai/chat-role/page`, params })
},
// 查询聊天角色详情
getChatRole: async (id: number) => {
return await request.get({ url: `/ai/chat-role/get?id=` + id })
},
// 新增聊天角色
createChatRole: async (data: ChatRoleVO) => {
return await request.post({ url: `/ai/chat-role/create`, data })
},
// 修改聊天角色
updateChatRole: async (data: ChatRoleVO) => {
return await request.put({ url: `/ai/chat-role/update`, data })
},
// 删除聊天角色
deleteChatRole: async (id: number) => {
return await request.delete({ url: `/ai/chat-role/delete?id=` + id })
},
// ======= chat 聊天
// 获取 my role
getMyPage: async (params: ChatRolePageReqVO) => {
return await request.get({ url: `/ai/chat-role/my-page`, params })
},
// 获取角色分类
getCategoryList: async () => {
return await request.get({ url: `/ai/chat-role/category-list` })
},
// 创建角色
createMy: async (data: ChatRoleVO) => {
return await request.post({ url: `/ai/chat-role/create-my`, data })
},
// 更新角色
updateMy: async (data: ChatRoleVO) => {
return await request.put({ url: `/ai/chat-role/update-my`, data })
},
// 删除角色 my
deleteMy: async (id: number) => {
return await request.delete({ url: `/ai/chat-role/delete-my?id=` + id })
}
}

54
src/api/ai/model/model/index.ts

@ -0,0 +1,54 @@
import request from '@/config/axios'
// AI 模型 VO
export interface ModelVO {
id: number // 编号
keyId: number // API 秘钥编号
name: string // 模型名字
model: string // 模型标识
platform: string // 模型平台
type: number // 模型类型
sort: number // 排序
status: number // 状态
temperature?: number // 温度参数
maxTokens?: number // 单条回复的最大 Token 数量
maxContexts?: number // 上下文的最大 Message 数量
}
// AI 模型 API
export const ModelApi = {
// 查询模型分页
getModelPage: async (params: any) => {
return await request.get({ url: `/ai/model/page`, params })
},
// 获得模型列表
getModelSimpleList: async (type?: number) => {
return await request.get({
url: `/ai/model/simple-list`,
params: {
type
}
})
},
// 查询模型详情
getModel: async (id: number) => {
return await request.get({ url: `/ai/model/get?id=` + id })
},
// 新增模型
createModel: async (data: ModelVO) => {
return await request.post({ url: `/ai/model/create`, data })
},
// 修改模型
updateModel: async (data: ModelVO) => {
return await request.put({ url: `/ai/model/update`, data })
},
// 删除模型
deleteModel: async (id: number) => {
return await request.delete({ url: `/ai/model/delete?id=` + id })
}
}

42
src/api/ai/model/tool/index.ts

@ -0,0 +1,42 @@
import request from '@/config/axios'
// AI 工具 VO
export interface ToolVO {
id: number // 工具编号
name: string // 工具名称
description: string // 工具描述
status: number // 状态
}
// AI 工具 API
export const ToolApi = {
// 查询工具分页
getToolPage: async (params: any) => {
return await request.get({ url: `/ai/tool/page`, params })
},
// 查询工具详情
getTool: async (id: number) => {
return await request.get({ url: `/ai/tool/get?id=` + id })
},
// 新增工具
createTool: async (data: ToolVO) => {
return await request.post({ url: `/ai/tool/create`, data })
},
// 修改工具
updateTool: async (data: ToolVO) => {
return await request.put({ url: `/ai/tool/update`, data })
},
// 删除工具
deleteTool: async (id: number) => {
return await request.delete({ url: `/ai/tool/delete?id=` + id })
},
// 获取工具简单列表
getToolSimpleList: async () => {
return await request.get({ url: `/ai/tool/simple-list` })
}
}

41
src/api/ai/music/index.ts

@ -0,0 +1,41 @@
import request from '@/config/axios'
// AI 音乐 VO
export interface MusicVO {
id: number // 编号
userId: number // 用户编号
title: string // 音乐名称
lyric: string // 歌词
imageUrl: string // 图片地址
audioUrl: string // 音频地址
videoUrl: string // 视频地址
status: number // 音乐状态
gptDescriptionPrompt: string // 描述词
prompt: string // 提示词
platform: string // 模型平台
model: string // 模型
generateMode: number // 生成模式
tags: string // 音乐风格标签
duration: number // 音乐时长
publicStatus: boolean // 是否发布
taskId: string // 任务id
errorMessage: string // 错误信息
}
// AI 音乐 API
export const MusicApi = {
// 查询音乐分页
getMusicPage: async (params: any) => {
return await request.get({ url: `/ai/music/page`, params })
},
// 更新音乐
updateMusic: async (data: any) => {
return await request.put({ url: '/ai/music/update', data })
},
// 删除音乐
deleteMusic: async (id: number) => {
return await request.delete({ url: `/ai/music/delete?id=` + id })
}
}

25
src/api/ai/workflow/index.ts

@ -0,0 +1,25 @@
import request from '@/config/axios'
export const getWorkflowPage = async (params) => {
return await request.get({ url: '/ai/workflow/page', params })
}
export const getWorkflow = async (id) => {
return await request.get({ url: '/ai/workflow/get?id=' + id })
}
export const createWorkflow = async (data) => {
return await request.post({ url: '/ai/workflow/create', data })
}
export const updateWorkflow = async (data) => {
return await request.put({ url: '/ai/workflow/update', data })
}
export const deleteWorkflow = async (id) => {
return await request.delete({ url: '/ai/workflow/delete?id=' + id })
}
export const testWorkflow = async (data) => {
return await request.post({ url: '/ai/workflow/test', data })
}

85
src/api/ai/write/index.ts

@ -0,0 +1,85 @@
import { fetchEventSource } from '@microsoft/fetch-event-source'
import { getAccessToken } from '@/utils/auth'
import { config } from '@/config/axios/config'
import { AiWriteTypeEnum } from '@/views/ai/utils/constants'
import request from '@/config/axios'
export interface WriteVO {
type: AiWriteTypeEnum.WRITING | AiWriteTypeEnum.REPLY // 1:撰写 2:回复
prompt: string // 写作内容提示 1。撰写 2回复
originalContent: string // 原文
length: number // 长度
format: number // 格式
tone: number // 语气
language: number // 语言
userId?: number // 用户编号
platform?: string // 平台
model?: string // 模型
generatedContent?: string // 生成的内容
errorMessage?: string // 错误信息
createTime?: Date // 创建时间
}
export interface AiWritePageReqVO extends PageParam {
userId?: number // 用户编号
type?: AiWriteTypeEnum // 写作类型
platform?: string // 平台
createTime?: [string, string] // 创建时间
}
export interface AiWriteRespVo {
id: number
userId: number
type: number
platform: string
model: string
prompt: string
generatedContent: string
originalContent: string
length: number
format: number
tone: number
language: number
errorMessage: string
createTime: string
}
export const WriteApi = {
writeStream: ({
data,
onClose,
onMessage,
onError,
ctrl
}: {
data: WriteVO
onMessage?: (res: any) => void
onError?: (...args: any[]) => void
onClose?: (...args: any[]) => void
ctrl: AbortController
}) => {
const token = getAccessToken()
return fetchEventSource(`${config.base_url}/ai/write/generate-stream`, {
method: 'post',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
},
openWhenHidden: true,
body: JSON.stringify(data),
onmessage: onMessage,
onerror: onError,
onclose: onClose,
signal: ctrl.signal
})
},
// 获取写作列表
getWritePage: (params: AiWritePageReqVO) => {
return request.get<PageResult<AiWriteRespVo[]>>({ url: `/ai/write/page`, params })
},
// 删除写作
deleteWrite(id: number) {
return request.delete({ url: `/ai/write/delete`, params: { id } })
}
}

53
src/api/bpm/category/index.ts

@ -0,0 +1,53 @@
import request from '@/config/axios'
// BPM 流程分类 VO
export interface CategoryVO {
id: number // 分类编号
name: string // 分类名
code: string // 分类标志
status: number // 分类状态
sort: number // 分类排序
}
// BPM 流程分类 API
export const CategoryApi = {
// 查询流程分类分页
getCategoryPage: async (params: any) => {
return await request.get({ url: `/bpm/category/page`, params })
},
// 查询流程分类列表
getCategorySimpleList: async () => {
return await request.get({ url: `/bpm/category/simple-list` })
},
// 查询流程分类详情
getCategory: async (id: number) => {
return await request.get({ url: `/bpm/category/get?id=` + id })
},
// 新增流程分类
createCategory: async (data: CategoryVO) => {
return await request.post({ url: `/bpm/category/create`, data })
},
// 修改流程分类
updateCategory: async (data: CategoryVO) => {
return await request.put({ url: `/bpm/category/update`, data })
},
// 批量修改流程分类的排序
updateCategorySortBatch: async (ids: number[]) => {
return await request.put({
url: `/bpm/category/update-sort-batch`,
params: {
ids: ids.join(',')
}
})
},
// 删除流程分类
deleteCategory: async (id: number) => {
return await request.delete({ url: `/bpm/category/delete?id=` + id })
}
}

28
src/api/bpm/definition/index.ts

@ -0,0 +1,28 @@
import request from '@/config/axios'
export const getProcessDefinition = async (id?: string, key?: string) => {
return await request.get({
url: '/bpm/process-definition/get',
params: { id, key }
})
}
export const getProcessDefinitionPage = async (params) => {
return await request.get({
url: '/bpm/process-definition/page',
params
})
}
export const getProcessDefinitionList = async (params) => {
return await request.get({
url: '/bpm/process-definition/list',
params
})
}
export const getSimpleProcessDefinitionList = async () => {
return await request.get({
url: '/bpm/process-definition/simple-list'
})
}

56
src/api/bpm/form/index.ts

@ -0,0 +1,56 @@
import request from '@/config/axios'
export type FormVO = {
id: number
name: string
conf: string
fields: string[]
status: number
remark: string
createTime: string
}
// 创建工作流的表单定义
export const createForm = async (data: FormVO) => {
return await request.post({
url: '/bpm/form/create',
data: data
})
}
// 更新工作流的表单定义
export const updateForm = async (data: FormVO) => {
return await request.put({
url: '/bpm/form/update',
data: data
})
}
// 删除工作流的表单定义
export const deleteForm = async (id: number) => {
return await request.delete({
url: '/bpm/form/delete?id=' + id
})
}
// 获得工作流的表单定义
export const getForm = async (id: number) => {
return await request.get({
url: '/bpm/form/get?id=' + id
})
}
// 获得工作流的表单定义分页
export const getFormPage = async (params) => {
return await request.get({
url: '/bpm/form/page',
params
})
}
// 获得动态表单的精简列表
export const getFormSimpleList = async () => {
return await request.get({
url: '/bpm/form/simple-list'
})
}

27
src/api/bpm/leave/index.ts

@ -0,0 +1,27 @@
import request from '@/config/axios'
export type LeaveVO = {
id: number
status: number
type: number
reason: string
processInstanceId: string
startTime: string
endTime: string
createTime: string
}
// 创建请假申请
export const createLeave = async (data: LeaveVO) => {
return await request.post({ url: '/bpm/oa/leave/create', data: data })
}
// 获得请假申请
export const getLeave = async (id: number) => {
return await request.get({ url: '/bpm/oa/leave/get?id=' + id })
}
// 获得请假申请分页
export const getLeavePage = async (params: PageParam) => {
return await request.get({ url: '/bpm/oa/leave/page', params })
}

78
src/api/bpm/model/index.ts

@ -0,0 +1,78 @@
import request from '@/config/axios'
export type ProcessDefinitionVO = {
id: string
version: number
deploymentTIme: string
suspensionState: number
formType?: number
}
export type ModelVO = {
id: number
formName: string
key: string
name: string
description: string
category: string
formType: number
formId: number
formCustomCreatePath: string
formCustomViewPath: string
processDefinition: ProcessDefinitionVO
status: number
remark: string
createTime: string
bpmnXml: string
}
export const getModelList = async (name: string | undefined) => {
return await request.get({ url: '/bpm/model/list', params: { name } })
}
export const getModel = async (id: string) => {
return await request.get({ url: '/bpm/model/get?id=' + id })
}
export const updateModel = async (data: ModelVO) => {
return await request.put({ url: '/bpm/model/update', data: data })
}
// 批量修改流程分类的排序
export const updateModelSortBatch = async (ids: number[]) => {
return await request.put({
url: `/bpm/model/update-sort-batch`,
params: {
ids: ids.join(',')
}
})
}
export const updateModelBpmn = async (data: ModelVO) => {
return await request.put({ url: '/bpm/model/update-bpmn', data: data })
}
// 任务状态修改
export const updateModelState = async (id: number, state: number) => {
const data = {
id: id,
state: state
}
return await request.put({ url: '/bpm/model/update-state', data: data })
}
export const createModel = async (data: ModelVO) => {
return await request.post({ url: '/bpm/model/create', data: data })
}
export const deleteModel = async (id: number) => {
return await request.delete({ url: '/bpm/model/delete?id=' + id })
}
export const deployModel = async (id: number) => {
return await request.post({ url: '/bpm/model/deploy?id=' + id })
}
export const cleanModel = async (id: number) => {
return await request.delete({ url: '/bpm/model/clean?id=' + id })
}

42
src/api/bpm/processExpression/index.ts

@ -0,0 +1,42 @@
import request from '@/config/axios'
// BPM 流程表达式 VO
export interface ProcessExpressionVO {
id: number // 编号
name: string // 表达式名字
status: number // 表达式状态
expression: string // 表达式
}
// BPM 流程表达式 API
export const ProcessExpressionApi = {
// 查询BPM 流程表达式分页
getProcessExpressionPage: async (params: any) => {
return await request.get({ url: `/bpm/process-expression/page`, params })
},
// 查询BPM 流程表达式详情
getProcessExpression: async (id: number) => {
return await request.get({ url: `/bpm/process-expression/get?id=` + id })
},
// 新增BPM 流程表达式
createProcessExpression: async (data: ProcessExpressionVO) => {
return await request.post({ url: `/bpm/process-expression/create`, data })
},
// 修改BPM 流程表达式
updateProcessExpression: async (data: ProcessExpressionVO) => {
return await request.put({ url: `/bpm/process-expression/update`, data })
},
// 删除BPM 流程表达式
deleteProcessExpression: async (id: number) => {
return await request.delete({ url: `/bpm/process-expression/delete?id=` + id })
},
// 导出BPM 流程表达式 Excel
exportProcessExpression: async (params) => {
return await request.download({ url: `/bpm/process-expression/export-excel`, params })
}
}

115
src/api/bpm/processInstance/index.ts

@ -0,0 +1,115 @@
import request from '@/config/axios'
import { ProcessDefinitionVO } from '@/api/bpm/model'
import { NodeType, CandidateStrategy } from '@/components/SimpleProcessDesignerV2/src/consts'
export type Task = {
id: string
name: string
}
export type ProcessInstanceVO = {
id: number
name: string
processDefinitionId: string
category: string
result: number
tasks: Task[]
fields: string[]
status: number
remark: string
businessKey: string
createTime: string
endTime: string
processDefinition?: ProcessDefinitionVO
}
// 用户信息
export type User = {
id: number
nickname: string
avatar: string
}
// 审批任务信息
export type ApprovalTaskInfo = {
id: number
ownerUser: User
assigneeUser: User
status: number
reason: string
signPicUrl: string
}
// 审批节点信息
export type ApprovalNodeInfo = {
id: number
name: string
nodeType: NodeType
candidateStrategy?: CandidateStrategy
status: number
startTime?: Date
endTime?: Date
processInstanceId?: string
candidateUsers?: User[]
tasks: ApprovalTaskInfo[]
}
export const getProcessInstanceMyPage = async (params: any) => {
return await request.get({ url: '/bpm/process-instance/my-page', params })
}
export const getProcessInstanceManagerPage = async (params: any) => {
return await request.get({ url: '/bpm/process-instance/manager-page', params })
}
export const createProcessInstance = async (data) => {
return await request.post({ url: '/bpm/process-instance/create', data: data })
}
export const cancelProcessInstanceByStartUser = async (id: number, reason: string) => {
const data = {
id: id,
reason: reason
}
return await request.delete({ url: '/bpm/process-instance/cancel-by-start-user', data: data })
}
export const cancelProcessInstanceByAdmin = async (id: number, reason: string) => {
const data = {
id: id,
reason: reason
}
return await request.delete({ url: '/bpm/process-instance/cancel-by-admin', data: data })
}
export const getProcessInstance = async (id: string) => {
return await request.get({ url: '/bpm/process-instance/get?id=' + id })
}
export const getProcessInstanceCopyPage = async (params: any) => {
return await request.get({ url: '/bpm/process-instance/copy/page', params })
}
// 获取审批详情
export const getApprovalDetail = async (params: any) => {
return await request.get({ url: '/bpm/process-instance/get-approval-detail', params })
}
// 获取下一个执行的流程节点
export const getNextApprovalNodes = async (params: any) => {
return await request.get({ url: '/bpm/process-instance/get-next-approval-nodes', params })
}
// 获取表单字段权限
export const getFormFieldsPermission = async (params: any) => {
return await request.get({ url: '/bpm/process-instance/get-form-fields-permission', params })
}
// 获取流程实例的 BPMN 模型视图
export const getProcessInstanceBpmnModelView = async (id: string) => {
return await request.get({ url: '/bpm/process-instance/get-bpmn-model-view?id=' + id })
}
// 获取流程实例打印数据
export const getProcessInstancePrintData = async (id: string) => {
return await request.get({ url: '/bpm/process-instance/get-print-data?processInstanceId=' + id })
}

40
src/api/bpm/processListener/index.ts

@ -0,0 +1,40 @@
import request from '@/config/axios'
// BPM 流程监听器 VO
export interface ProcessListenerVO {
id: number // 编号
name: string // 监听器名字
type: string // 监听器类型
status: number // 监听器状态
event: string // 监听事件
valueType: string // 监听器值类型
value: string // 监听器值
}
// BPM 流程监听器 API
export const ProcessListenerApi = {
// 查询流程监听器分页
getProcessListenerPage: async (params: any) => {
return await request.get({ url: `/bpm/process-listener/page`, params })
},
// 查询流程监听器详情
getProcessListener: async (id: number) => {
return await request.get({ url: `/bpm/process-listener/get?id=` + id })
},
// 新增流程监听器
createProcessListener: async (data: ProcessListenerVO) => {
return await request.post({ url: `/bpm/process-listener/create`, data })
},
// 修改流程监听器
updateProcessListener: async (data: ProcessListenerVO) => {
return await request.put({ url: `/bpm/process-listener/update`, data })
},
// 删除流程监听器
deleteProcessListener: async (id: number) => {
return await request.delete({ url: `/bpm/process-listener/delete?id=` + id })
}
}

15
src/api/bpm/simple/index.ts

@ -0,0 +1,15 @@
import request from '@/config/axios'
export const updateBpmSimpleModel = async (data) => {
return await request.post({
url: '/bpm/model/simple/update',
data: data
})
}
export const getBpmSimpleModel = async (id) => {
return await request.get({
url: '/bpm/model/simple/get?id=' + id
})
}

122
src/api/bpm/task/index.ts

@ -0,0 +1,122 @@
import request from '@/config/axios'
/**
*
*/
export enum TaskStatusEnum {
/**
*
*/
SKIP = -2,
/**
*
*/
NOT_START = -1,
/**
*
*/
WAIT = 0,
/**
*
*/
RUNNING = 1,
/**
*
*/
APPROVE = 2,
/**
*
*/
REJECT = 3,
/**
*
*/
CANCEL = 4,
/**
* 退
*/
RETURN = 5,
/**
*
*/
APPROVING = 7
}
export const getTaskTodoPage = async (params: any) => {
return await request.get({ url: '/bpm/task/todo-page', params })
}
export const getTaskDonePage = async (params: any) => {
return await request.get({ url: '/bpm/task/done-page', params })
}
export const getTaskManagerPage = async (params: any) => {
return await request.get({ url: '/bpm/task/manager-page', params })
}
export const approveTask = async (data: any) => {
return await request.put({ url: '/bpm/task/approve', data })
}
export const rejectTask = async (data: any) => {
return await request.put({ url: '/bpm/task/reject', data })
}
export const getTaskListByProcessInstanceId = async (processInstanceId: string) => {
return await request.get({
url: '/bpm/task/list-by-process-instance-id?processInstanceId=' + processInstanceId
})
}
// 获取所有可退回的节点
export const getTaskListByReturn = async (id: string) => {
return await request.get({ url: '/bpm/task/list-by-return', params: { id } })
}
// 退回
export const returnTask = async (data: any) => {
return await request.put({ url: '/bpm/task/return', data })
}
// 委派
export const delegateTask = async (data: any) => {
return await request.put({ url: '/bpm/task/delegate', data })
}
// 转派
export const transferTask = async (data: any) => {
return await request.put({ url: '/bpm/task/transfer', data })
}
// 加签
export const signCreateTask = async (data: any) => {
return await request.put({ url: '/bpm/task/create-sign', data })
}
// 减签
export const signDeleteTask = async (data: any) => {
return await request.delete({ url: '/bpm/task/delete-sign', data })
}
// 抄送
export const copyTask = async (data: any) => {
return await request.put({ url: '/bpm/task/copy', data })
}
// 撤回
export const withdrawTask = async (taskId: string) => {
return await request.put({ url: '/bpm/task/withdraw', params: { taskId } })
}
// 获取我的待办任务
export const myTodoTask = async (processInstanceId: string) => {
return await request.get({ url: '/bpm/task/my-todo?processInstanceId=' + processInstanceId })
}
// 获取减签任务列表
export const getChildrenTaskList = async (id: string) => {
return await request.get({ url: '/bpm/task/list-by-parent-task-id?parentTaskId=' + id })
}

47
src/api/bpm/userGroup/index.ts

@ -0,0 +1,47 @@
import request from '@/config/axios'
export type UserGroupVO = {
id: number
name: string
description: string
userIds: number[]
status: number
remark: string
createTime: string
}
// 创建用户组
export const createUserGroup = async (data: UserGroupVO) => {
return await request.post({
url: '/bpm/user-group/create',
data: data
})
}
// 更新用户组
export const updateUserGroup = async (data: UserGroupVO) => {
return await request.put({
url: '/bpm/user-group/update',
data: data
})
}
// 删除用户组
export const deleteUserGroup = async (id: number) => {
return await request.delete({ url: '/bpm/user-group/delete?id=' + id })
}
// 获得用户组
export const getUserGroup = async (id: number) => {
return await request.get({ url: '/bpm/user-group/get?id=' + id })
}
// 获得用户组分页
export const getUserGroupPage = async (params) => {
return await request.get({ url: '/bpm/user-group/page', params })
}
// 获取用户组精简信息列表
export const getUserGroupSimpleList = async (): Promise<UserGroupVO[]> => {
return await request.get({ url: '/bpm/user-group/simple-list' })
}

98
src/api/crm/business/index.ts

@ -0,0 +1,98 @@
import request from '@/config/axios'
import { TransferReqVO } from '@/api/crm/permission'
export interface BusinessVO {
id: number
name: string
customerId: number
customerName?: string
followUpStatus: boolean
contactLastTime: Date
contactNextTime: Date
ownerUserId: number
ownerUserName?: string // 负责人的用户名称
ownerUserDept?: string // 负责人的部门名称
statusTypeId: number
statusTypeName?: string
statusId: number
statusName?: string
endStatus: number
endRemark: string
dealTime: Date
totalProductPrice: number
totalPrice: number
discountPercent: number
remark: string
creator: string // 创建人
creatorName?: string // 创建人名称
createTime: Date // 创建时间
updateTime: Date // 更新时间
products?: [
{
id: number
productId: number
productName: string
productNo: string
productUnit: number
productPrice: number
businessPrice: number
count: number
totalPrice: number
}
]
}
// 查询 CRM 商机列表
export const getBusinessPage = async (params) => {
return await request.get({ url: `/crm/business/page`, params })
}
// 查询 CRM 商机列表,基于指定客户
export const getBusinessPageByCustomer = async (params) => {
return await request.get({ url: `/crm/business/page-by-customer`, params })
}
// 查询 CRM 商机详情
export const getBusiness = async (id: number) => {
return await request.get({ url: `/crm/business/get?id=` + id })
}
// 获得 CRM 商机列表(精简)
export const getSimpleBusinessList = async () => {
return await request.get({ url: `/crm/business/simple-all-list` })
}
// 新增 CRM 商机
export const createBusiness = async (data: BusinessVO) => {
return await request.post({ url: `/crm/business/create`, data })
}
// 修改 CRM 商机
export const updateBusiness = async (data: BusinessVO) => {
return await request.put({ url: `/crm/business/update`, data })
}
// 修改 CRM 商机状态
export const updateBusinessStatus = async (data: BusinessVO) => {
return await request.put({ url: `/crm/business/update-status`, data })
}
// 删除 CRM 商机
export const deleteBusiness = async (id: number) => {
return await request.delete({ url: `/crm/business/delete?id=` + id })
}
// 导出 CRM 商机 Excel
export const exportBusiness = async (params) => {
return await request.download({ url: `/crm/business/export-excel`, params })
}
// 联系人关联商机列表
export const getBusinessPageByContact = async (params) => {
return await request.get({ url: `/crm/business/page-by-contact`, params })
}
// 商机转移
export const transferBusiness = async (data: TransferReqVO) => {
return await request.put({ url: '/crm/business/transfer', data })
}

68
src/api/crm/business/status/index.ts

@ -0,0 +1,68 @@
import request from '@/config/axios'
export interface BusinessStatusTypeVO {
id: number
name: string
deptIds: number[]
statuses?: {
id: number
name: string
percent: number
}
}
export const DEFAULT_STATUSES = [
{
endStatus: 1,
key: '结束',
name: '赢单',
percent: 100
},
{
endStatus: 2,
key: '结束',
name: '输单',
percent: 0
},
{
endStatus: 3,
key: '结束',
name: '无效',
percent: 0
}
]
// 查询商机状态组列表
export const getBusinessStatusPage = async (params: any) => {
return await request.get({ url: `/crm/business-status/page`, params })
}
// 新增商机状态组
export const createBusinessStatus = async (data: BusinessStatusTypeVO) => {
return await request.post({ url: `/crm/business-status/create`, data })
}
// 修改商机状态组
export const updateBusinessStatus = async (data: BusinessStatusTypeVO) => {
return await request.put({ url: `/crm/business-status/update`, data })
}
// 查询商机状态类型详情
export const getBusinessStatus = async (id: number) => {
return await request.get({ url: `/crm/business-status/get?id=` + id })
}
// 删除商机状态
export const deleteBusinessStatus = async (id: number) => {
return await request.delete({ url: `/crm/business-status/delete?id=` + id })
}
// 获得商机状态组列表
export const getBusinessStatusTypeSimpleList = async () => {
return await request.get({ url: `/crm/business-status/type-simple-list` })
}
// 获得商机阶段列表
export const getBusinessStatusSimpleList = async (typeId: number) => {
return await request.get({ url: `/crm/business-status/status-simple-list`, params: { typeId } })
}

78
src/api/crm/clue/index.ts

@ -0,0 +1,78 @@
import request from '@/config/axios'
import { TransferReqVO } from '@/api/crm/permission'
export interface ClueVO {
id: number // 编号
name: string // 线索名称
followUpStatus: boolean // 跟进状态
contactLastTime: Date // 最后跟进时间
contactLastContent: string // 最后跟进内容
contactNextTime: Date // 下次联系时间
ownerUserId: number // 负责人的用户编号
ownerUserName?: string // 负责人的用户名称
ownerUserDept?: string // 负责人的部门名称
transformStatus: boolean // 转化状态
customerId: number // 客户编号
customerName?: string // 客户名称
mobile: string // 手机号
telephone: string // 电话
qq: string // QQ
wechat: string // wechat
email: string // email
areaId: number // 所在地
areaName?: string // 所在地名称
detailAddress: string // 详细地址
industryId: number // 所属行业
level: number // 客户等级
source: number // 客户来源
remark: string // 备注
creator: string // 创建人
creatorName?: string // 创建人名称
createTime: Date // 创建时间
updateTime: Date // 更新时间
}
// 查询线索列表
export const getCluePage = async (params: any) => {
return await request.get({ url: `/crm/clue/page`, params })
}
// 查询线索详情
export const getClue = async (id: number) => {
return await request.get({ url: `/crm/clue/get?id=` + id })
}
// 新增线索
export const createClue = async (data: ClueVO) => {
return await request.post({ url: `/crm/clue/create`, data })
}
// 修改线索
export const updateClue = async (data: ClueVO) => {
return await request.put({ url: `/crm/clue/update`, data })
}
// 删除线索
export const deleteClue = async (id: number) => {
return await request.delete({ url: `/crm/clue/delete?id=` + id })
}
// 导出线索 Excel
export const exportClue = async (params) => {
return await request.download({ url: `/crm/clue/export-excel`, params })
}
// 线索转移
export const transferClue = async (data: TransferReqVO) => {
return await request.put({ url: '/crm/clue/transfer', data })
}
// 线索转化为客户
export const transformClue = async (id: number) => {
return await request.put({ url: '/crm/clue/transform', params: { id } })
}
// 获得分配给我的、待跟进的线索数量
export const getFollowClueCount = async () => {
return await request.get({ url: '/crm/clue/follow-count' })
}

113
src/api/crm/contact/index.ts

@ -0,0 +1,113 @@
import request from '@/config/axios'
import { TransferReqVO } from '@/api/crm/permission'
export interface ContactVO {
id: number // 编号
name: string // 联系人名称
customerId: number // 客户编号
customerName?: string // 客户名称
contactLastTime: Date // 最后跟进时间
contactLastContent: string // 最后跟进内容
contactNextTime: Date // 下次联系时间
ownerUserId: number // 负责人的用户编号
ownerUserName?: string // 负责人的用户名称
ownerUserDept?: string // 负责人的部门名称
mobile: string // 手机号
telephone: string // 电话
qq: string // QQ
wechat: string // wechat
email: string // email
areaId: number // 所在地
areaName?: string // 所在地名称
detailAddress: string // 详细地址
sex: number // 性别
master: boolean // 是否主联系人
post: string // 职务
parentId: number // 上级联系人编号
parentName?: string // 上级联系人名称
remark: string // 备注
creator: string // 创建人
creatorName?: string // 创建人名称
createTime: Date // 创建时间
updateTime: Date // 更新时间
}
export interface ContactBusinessReqVO {
contactId: number
businessIds: number[]
}
export interface ContactBusiness2ReqVO {
businessId: number
contactIds: number[]
}
// 查询 CRM 联系人列表
export const getContactPage = async (params) => {
return await request.get({ url: `/crm/contact/page`, params })
}
// 查询 CRM 联系人列表,基于指定客户
export const getContactPageByCustomer = async (params: any) => {
return await request.get({ url: `/crm/contact/page-by-customer`, params })
}
// 查询 CRM 联系人列表,基于指定商机
export const getContactPageByBusiness = async (params: any) => {
return await request.get({ url: `/crm/contact/page-by-business`, params })
}
// 查询 CRM 联系人详情
export const getContact = async (id: number) => {
return await request.get({ url: `/crm/contact/get?id=` + id })
}
// 新增 CRM 联系人
export const createContact = async (data: ContactVO) => {
return await request.post({ url: `/crm/contact/create`, data })
}
// 修改 CRM 联系人
export const updateContact = async (data: ContactVO) => {
return await request.put({ url: `/crm/contact/update`, data })
}
// 删除 CRM 联系人
export const deleteContact = async (id: number) => {
return await request.delete({ url: `/crm/contact/delete?id=` + id })
}
// 导出 CRM 联系人 Excel
export const exportContact = async (params) => {
return await request.download({ url: `/crm/contact/export-excel`, params })
}
// 获得 CRM 联系人列表(精简)
export const getSimpleContactList = async () => {
return await request.get({ url: `/crm/contact/simple-all-list` })
}
// 批量新增联系人商机关联
export const createContactBusinessList = async (data: ContactBusinessReqVO) => {
return await request.post({ url: `/crm/contact/create-business-list`, data })
}
// 批量新增联系人商机关联
export const createContactBusinessList2 = async (data: ContactBusiness2ReqVO) => {
return await request.post({ url: `/crm/contact/create-business-list2`, data })
}
// 解除联系人商机关联
export const deleteContactBusinessList = async (data: ContactBusinessReqVO) => {
return await request.delete({ url: `/crm/contact/delete-business-list`, data })
}
// 解除联系人商机关联
export const deleteContactBusinessList2 = async (data: ContactBusiness2ReqVO) => {
return await request.delete({ url: `/crm/contact/delete-business-list2`, data })
}
// 联系人转移
export const transferContact = async (data: TransferReqVO) => {
return await request.put({ url: '/crm/contact/transfer', data })
}

16
src/api/crm/contract/config/index.ts

@ -0,0 +1,16 @@
import request from '@/config/axios'
export interface ContractConfigVO {
notifyEnabled?: boolean
notifyDays?: number
}
// 获取合同配置
export const getContractConfig = async () => {
return await request.get({ url: `/crm/contract-config/get` })
}
// 更新合同配置
export const saveContractConfig = async (data: ContractConfigVO) => {
return await request.put({ url: `/crm/contract-config/save`, data })
}

114
src/api/crm/contract/index.ts

@ -0,0 +1,114 @@
import request from '@/config/axios'
import { TransferReqVO } from '@/api/crm/permission'
export interface ContractVO {
id: number
name: string
no: string
customerId: number
customerName?: string
businessId: number
businessName: string
contactLastTime: Date
ownerUserId: number
ownerUserName?: string
ownerUserDeptName?: string
processInstanceId: number
auditStatus: number
orderDate: Date
startTime: Date
endTime: Date
totalProductPrice: number
discountPercent: number
totalPrice: number
totalReceivablePrice: number
signContactId: number
signContactName?: string
signUserId: number
signUserName: string
remark: string
createTime?: Date
creator: string
creatorName: string
updateTime?: Date
products?: [
{
id: number
productId: number
productName: string
productNo: string
productUnit: number
productPrice: number
contractPrice: number
count: number
totalPrice: number
}
]
}
// 查询 CRM 合同列表
export const getContractPage = async (params) => {
return await request.get({ url: `/crm/contract/page`, params })
}
// 查询 CRM 联系人列表,基于指定客户
export const getContractPageByCustomer = async (params: any) => {
return await request.get({ url: `/crm/contract/page-by-customer`, params })
}
// 查询 CRM 联系人列表,基于指定商机
export const getContractPageByBusiness = async (params: any) => {
return await request.get({ url: `/crm/contract/page-by-business`, params })
}
// 查询 CRM 合同详情
export const getContract = async (id: number) => {
return await request.get({ url: `/crm/contract/get?id=` + id })
}
// 查询 CRM 合同下拉列表
export const getContractSimpleList = async (customerId: number) => {
return await request.get({
url: `/crm/contract/simple-list?customerId=${customerId}`
})
}
// 新增 CRM 合同
export const createContract = async (data: ContractVO) => {
return await request.post({ url: `/crm/contract/create`, data })
}
// 修改 CRM 合同
export const updateContract = async (data: ContractVO) => {
return await request.put({ url: `/crm/contract/update`, data })
}
// 删除 CRM 合同
export const deleteContract = async (id: number) => {
return await request.delete({ url: `/crm/contract/delete?id=` + id })
}
// 导出 CRM 合同 Excel
export const exportContract = async (params) => {
return await request.download({ url: `/crm/contract/export-excel`, params })
}
// 提交审核
export const submitContract = async (id: number) => {
return await request.put({ url: `/crm/contract/submit?id=${id}` })
}
// 合同转移
export const transferContract = async (data: TransferReqVO) => {
return await request.put({ url: '/crm/contract/transfer', data })
}
// 获得待审核合同数量
export const getAuditContractCount = async () => {
return await request.get({ url: '/crm/contract/audit-count' })
}
// 获得即将到期(提醒)的合同数量
export const getRemindContractCount = async () => {
return await request.get({ url: '/crm/contract/remind-count' })
}

132
src/api/crm/customer/index.ts

@ -0,0 +1,132 @@
import request from '@/config/axios'
import { TransferReqVO } from '@/api/crm/permission'
export interface CustomerVO {
id: number // 编号
name: string // 客户名称
followUpStatus: boolean // 跟进状态
contactLastTime: Date // 最后跟进时间
contactLastContent: string // 最后跟进内容
contactNextTime: Date // 下次联系时间
ownerUserId: number // 负责人的用户编号
ownerUserName?: string // 负责人的用户名称
ownerUserDept?: string // 负责人的部门名称
lockStatus?: boolean
dealStatus?: boolean
mobile: string // 手机号
telephone: string // 电话
qq: string // QQ
wechat: string // wechat
email: string // email
areaId: number // 所在地
areaName?: string // 所在地名称
detailAddress: string // 详细地址
industryId: number // 所属行业
level: number // 客户等级
source: number // 客户来源
remark: string // 备注
creator: string // 创建人
creatorName?: string // 创建人名称
createTime: Date // 创建时间
updateTime: Date // 更新时间
}
// 查询客户列表
export const getCustomerPage = async (params) => {
return await request.get({ url: `/crm/customer/page`, params })
}
// 进入公海客户提醒的客户列表
export const getPutPoolRemindCustomerPage = async (params) => {
return await request.get({ url: `/crm/customer/put-pool-remind-page`, params })
}
// 获得待进入公海客户数量
export const getPutPoolRemindCustomerCount = async () => {
return await request.get({ url: `/crm/customer/put-pool-remind-count` })
}
// 获得今日需联系客户数量
export const getTodayContactCustomerCount = async () => {
return await request.get({ url: `/crm/customer/today-contact-count` })
}
// 获得分配给我、待跟进的线索数量的客户数量
export const getFollowCustomerCount = async () => {
return await request.get({ url: `/crm/customer/follow-count` })
}
// 查询客户详情
export const getCustomer = async (id: number) => {
return await request.get({ url: `/crm/customer/get?id=` + id })
}
// 新增客户
export const createCustomer = async (data: CustomerVO) => {
return await request.post({ url: `/crm/customer/create`, data })
}
// 修改客户
export const updateCustomer = async (data: CustomerVO) => {
return await request.put({ url: `/crm/customer/update`, data })
}
// 更新客户的成交状态
export const updateCustomerDealStatus = async (id: number, dealStatus: boolean) => {
return await request.put({ url: `/crm/customer/update-deal-status`, params: { id, dealStatus } })
}
// 删除客户
export const deleteCustomer = async (id: number) => {
return await request.delete({ url: `/crm/customer/delete?id=` + id })
}
// 导出客户 Excel
export const exportCustomer = async (params: any) => {
return await request.download({ url: `/crm/customer/export-excel`, params })
}
// 下载客户导入模板
export const importCustomerTemplate = () => {
return request.download({ url: '/crm/customer/get-import-template' })
}
// 导入客户
export const handleImport = async (formData) => {
return await request.upload({ url: `/crm/customer/import`, data: formData })
}
// 客户列表
export const getCustomerSimpleList = async () => {
return await request.get({ url: `/crm/customer/simple-list` })
}
// ======================= 业务操作 =======================
// 客户转移
export const transferCustomer = async (data: TransferReqVO) => {
return await request.put({ url: '/crm/customer/transfer', data })
}
// 锁定/解锁客户
export const lockCustomer = async (id: number, lockStatus: boolean) => {
return await request.put({ url: `/crm/customer/lock`, data: { id, lockStatus } })
}
// 领取公海客户
export const receiveCustomer = async (ids: any[]) => {
return await request.put({ url: '/crm/customer/receive', params: { ids: ids.join(',') } })
}
// 分配公海给对应负责人
export const distributeCustomer = async (ids: any[], ownerUserId: number) => {
return await request.put({
url: '/crm/customer/distribute',
data: { ids: ids, ownerUserId }
})
}
// 客户放入公海
export const putCustomerPool = async (id: number) => {
return await request.put({ url: `/crm/customer/put-pool?id=${id}` })
}

49
src/api/crm/customer/limitConfig/index.ts

@ -0,0 +1,49 @@
import request from '@/config/axios'
export interface CustomerLimitConfigVO {
id?: number
type?: number
userIds?: string
deptIds?: string
maxCount?: number
dealCountEnabled?: boolean
}
/**
*
*/
export enum LimitConfType {
/**
*
*/
CUSTOMER_QUANTITY_LIMIT = 1,
/**
*
*/
CUSTOMER_LOCK_LIMIT = 2
}
// 查询客户限制配置列表
export const getCustomerLimitConfigPage = async (params) => {
return await request.get({ url: `/crm/customer-limit-config/page`, params })
}
// 查询客户限制配置详情
export const getCustomerLimitConfig = async (id: number) => {
return await request.get({ url: `/crm/customer-limit-config/get?id=` + id })
}
// 新增客户限制配置
export const createCustomerLimitConfig = async (data: CustomerLimitConfigVO) => {
return await request.post({ url: `/crm/customer-limit-config/create`, data })
}
// 修改客户限制配置
export const updateCustomerLimitConfig = async (data: CustomerLimitConfigVO) => {
return await request.put({ url: `/crm/customer-limit-config/update`, data })
}
// 删除客户限制配置
export const deleteCustomerLimitConfig = async (id: number) => {
return await request.delete({ url: `/crm/customer-limit-config/delete?id=` + id })
}

19
src/api/crm/customer/poolConfig/index.ts

@ -0,0 +1,19 @@
import request from '@/config/axios'
export interface CustomerPoolConfigVO {
enabled?: boolean
contactExpireDays?: number
dealExpireDays?: number
notifyEnabled?: boolean
notifyDays?: number
}
// 获取客户公海规则设置
export const getCustomerPoolConfig = async () => {
return await request.get({ url: `/crm/customer-pool-config/get` })
}
// 更新客户公海规则设置
export const saveCustomerPoolConfig = async (data: CustomerPoolConfigVO) => {
return await request.put({ url: `/crm/customer-pool-config/save`, data })
}

43
src/api/crm/followup/index.ts

@ -0,0 +1,43 @@
import request from '@/config/axios'
// 跟进记录 VO
export interface FollowUpRecordVO {
id: number // 编号
bizType: number // 数据类型
bizId: number // 数据编号
type: number // 跟进类型
content: string // 跟进内容
picUrls: string[] // 图片
fileUrls: string[] // 附件
nextTime: Date // 下次联系时间
businessIds: number[] // 关联的商机编号数组
businesses: {
id: number
name: string
}[] // 关联的商机数组
contactIds: number[] // 关联的联系人编号数组
contacts: {
id: number
name: string
}[] // 关联的联系人数组
creator: string
creatorName?: string
}
// 跟进记录 API
export const FollowUpRecordApi = {
// 查询跟进记录分页
getFollowUpRecordPage: async (params: any) => {
return await request.get({ url: `/crm/follow-up-record/page`, params })
},
// 新增跟进记录
createFollowUpRecord: async (data: FollowUpRecordVO) => {
return await request.post({ url: `/crm/follow-up-record/create`, data })
},
// 删除跟进记录
deleteFollowUpRecord: async (id: number) => {
return await request.delete({ url: `/crm/follow-up-record/delete?id=` + id })
}
}

11
src/api/crm/operateLog/index.ts

@ -0,0 +1,11 @@
import request from '@/config/axios'
export interface OperateLogVO extends PageParam {
bizType: number
bizId: number
}
// 获得操作日志
export const getOperateLogPage = async (params: OperateLogVO) => {
return await request.get({ url: `/crm/operate-log/page`, params })
}

72
src/api/crm/permission/index.ts

@ -0,0 +1,72 @@
import request from '@/config/axios'
export interface PermissionVO {
id?: number // 数据权限编号
userId: number // 用户编号
bizType: number // Crm 类型
bizId: number // Crm 类型数据编号
level: number // 权限级别
toBizTypes?: number[] // 同时添加至
deptName?: string // 部门名称
nickname?: string // 用户昵称
postNames?: string[] // 岗位名称数组
createTime?: Date
ids?: number[]
}
export interface TransferReqVO {
id: number // 模块编号
newOwnerUserId: number // 新负责人的用户编号
oldOwnerPermissionLevel?: number // 老负责人加入团队后的权限级别
toBizTypes?: number[] // 转移客户时,需要额外有【联系人】【商机】【合同】的 checkbox 选择
}
/**
* CRM
*
* @author HUIHUI
*/
export enum BizTypeEnum {
CRM_CLUE = 1, // 线索
CRM_CUSTOMER = 2, // 客户
CRM_CONTACT = 3, // 联系人
CRM_BUSINESS = 4, // 商机
CRM_CONTRACT = 5, // 合同
CRM_PRODUCT = 6, // 产品
CRM_RECEIVABLE = 7, // 回款
CRM_RECEIVABLE_PLAN = 8 // 回款计划
}
/**
* CRM
*/
export enum PermissionLevelEnum {
OWNER = 1, // 负责人
READ = 2, // 只读
WRITE = 3 // 读写
}
// 获得数据权限列表(查询团队成员列表)
export const getPermissionList = async (params) => {
return await request.get({ url: `/crm/permission/list`, params })
}
// 创建数据权限(新增团队成员)
export const createPermission = async (data: PermissionVO) => {
return await request.post({ url: `/crm/permission/create`, data })
}
// 编辑数据权限(修改团队成员权限级别)
export const updatePermission = async (data) => {
return await request.put({ url: `/crm/permission/update`, data })
}
// 删除数据权限(删除团队成员)
export const deletePermissionBatch = async (val: number[]) => {
return await request.delete({ url: '/crm/permission/delete?ids=' + val.join(',') })
}
// 删除自己的数据权限(退出团队)
export const deleteSelfPermission = async (id: number) => {
return await request.delete({ url: '/crm/permission/delete-self?id=' + id })
}

33
src/api/crm/product/category/index.ts

@ -0,0 +1,33 @@
import request from '@/config/axios'
// TODO @zange:挪到 product 下,建个 category 包,挪进去哈;
export interface ProductCategoryVO {
id: number
name: string
parentId: number
}
// 查询产品分类详情
export const getProductCategory = async (id: number) => {
return await request.get({ url: `/crm/product-category/get?id=` + id })
}
// 新增产品分类
export const createProductCategory = async (data: ProductCategoryVO) => {
return await request.post({ url: `/crm/product-category/create`, data })
}
// 修改产品分类
export const updateProductCategory = async (data: ProductCategoryVO) => {
return await request.put({ url: `/crm/product-category/update`, data })
}
// 删除产品分类
export const deleteProductCategory = async (id: number) => {
return await request.delete({ url: `/crm/product-category/delete?id=` + id })
}
// 产品分类列表
export const getProductCategoryList = async (params) => {
return await request.get({ url: `/crm/product-category/list`, params })
}

49
src/api/crm/product/index.ts

@ -0,0 +1,49 @@
import request from '@/config/axios'
export interface ProductVO {
id: number
name: string
no: string
unit: number
price: number
status: number
categoryId: number
categoryName?: string
description: string
ownerUserId: number
}
// 查询产品列表
export const getProductPage = async (params) => {
return await request.get({ url: `/crm/product/page`, params })
}
// 获得产品精简列表
export const getProductSimpleList = async () => {
return await request.get({ url: `/crm/product/simple-list` })
}
// 查询产品详情
export const getProduct = async (id: number) => {
return await request.get({ url: `/crm/product/get?id=` + id })
}
// 新增产品
export const createProduct = async (data: ProductVO) => {
return await request.post({ url: `/crm/product/create`, data })
}
// 修改产品
export const updateProduct = async (data: ProductVO) => {
return await request.put({ url: `/crm/product/update`, data })
}
// 删除产品
export const deleteProduct = async (id: number) => {
return await request.delete({ url: `/crm/product/delete?id=` + id })
}
// 导出产品 Excel
export const exportProduct = async (params) => {
return await request.download({ url: `/crm/product/export-excel`, params })
}

73
src/api/crm/receivable/index.ts

@ -0,0 +1,73 @@
import request from '@/config/axios'
export interface ReceivableVO {
id: number
no: string
planId?: number
customerId?: number
customerName?: string
contractId?: number
contract?: {
id?: number
name?: string
no: string
totalPrice: number
}
auditStatus: number
processInstanceId: number
returnTime: Date
returnType: number
price: number
ownerUserId: number
ownerUserName?: string
remark: string
creator: string // 创建人
creatorName?: string // 创建人名称
createTime: Date // 创建时间
updateTime: Date // 更新时间
}
// 查询回款列表
export const getReceivablePage = async (params) => {
return await request.get({ url: `/crm/receivable/page`, params })
}
// 查询回款列表
export const getReceivablePageByCustomer = async (params) => {
return await request.get({ url: `/crm/receivable/page-by-customer`, params })
}
// 查询回款详情
export const getReceivable = async (id: number) => {
return await request.get({ url: `/crm/receivable/get?id=` + id })
}
// 新增回款
export const createReceivable = async (data: ReceivableVO) => {
return await request.post({ url: `/crm/receivable/create`, data })
}
// 修改回款
export const updateReceivable = async (data: ReceivableVO) => {
return await request.put({ url: `/crm/receivable/update`, data })
}
// 删除回款
export const deleteReceivable = async (id: number) => {
return await request.delete({ url: `/crm/receivable/delete?id=` + id })
}
// 导出回款 Excel
export const exportReceivable = async (params) => {
return await request.download({ url: `/crm/receivable/export-excel`, params })
}
// 提交审核
export const submitReceivable = async (id: number) => {
return await request.put({ url: `/crm/receivable/submit?id=${id}` })
}
// 获得待审核回款数量
export const getAuditReceivableCount = async () => {
return await request.get({ url: '/crm/receivable/audit-count' })
}

74
src/api/crm/receivable/plan/index.ts

@ -0,0 +1,74 @@
import request from '@/config/axios'
export interface ReceivablePlanVO {
id: number
period: number
receivableId: number
price: number
returnTime: Date
remindDays: number
returnType: number
remindTime: Date
customerId: number
customerName?: string
contractId?: number
contractNo?: string
ownerUserId: number
ownerUserName?: string
remark: string
creator: string // 创建人
creatorName?: string // 创建人名称
createTime: Date // 创建时间
updateTime: Date // 更新时间
receivable?: {
price: number
returnTime: Date
}
}
// 查询回款计划列表
export const getReceivablePlanPage = async (params) => {
return await request.get({ url: `/crm/receivable-plan/page`, params })
}
// 查询回款计划列表
export const getReceivablePlanPageByCustomer = async (params) => {
return await request.get({ url: `/crm/receivable-plan/page-by-customer`, params })
}
// 查询回款计划详情
export const getReceivablePlan = async (id: number) => {
return await request.get({ url: `/crm/receivable-plan/get?id=` + id })
}
// 查询回款计划下拉数据
export const getReceivablePlanSimpleList = async (customerId: number, contractId: number) => {
return await request.get({
url: `/crm/receivable-plan/simple-list?customerId=${customerId}&contractId=${contractId}`
})
}
// 新增回款计划
export const createReceivablePlan = async (data: ReceivablePlanVO) => {
return await request.post({ url: `/crm/receivable-plan/create`, data })
}
// 修改回款计划
export const updateReceivablePlan = async (data: ReceivablePlanVO) => {
return await request.put({ url: `/crm/receivable-plan/update`, data })
}
// 删除回款计划
export const deleteReceivablePlan = async (id: number) => {
return await request.delete({ url: `/crm/receivable-plan/delete?id=` + id })
}
// 导出回款计划 Excel
export const exportReceivablePlan = async (params) => {
return await request.download({ url: `/crm/receivable-plan/export-excel`, params })
}
// 获得待回款提醒数量
export const getReceivablePlanRemindCount = async () => {
return await request.get({ url: '/crm/receivable-plan/remind-count' })
}

168
src/api/crm/statistics/customer.ts

@ -0,0 +1,168 @@
import request from '@/config/axios'
export interface CrmStatisticsCustomerSummaryByDateRespVO {
time: string
customerCreateCount: number
customerDealCount: number
}
export interface CrmStatisticsCustomerSummaryByUserRespVO {
ownerUserName: string
customerCreateCount: number
customerDealCount: number
contractPrice: number
receivablePrice: number
}
export interface CrmStatisticsFollowUpSummaryByDateRespVO {
time: string
followUpRecordCount: number
followUpCustomerCount: number
}
export interface CrmStatisticsFollowUpSummaryByUserRespVO {
ownerUserName: string
followupRecordCount: number
followupCustomerCount: number
}
export interface CrmStatisticsFollowUpSummaryByTypeRespVO {
followUpType: string
followUpRecordCount: number
}
export interface CrmStatisticsCustomerContractSummaryRespVO {
customerName: string
contractName: string
totalPrice: number
receivablePrice: number
customerType: string
customerSource: string
ownerUserName: string
creatorUserName: string
createTime: Date
orderDate: Date
}
export interface CrmStatisticsPoolSummaryByDateRespVO {
time: string
customerPutCount: number
customerTakeCount: number
}
export interface CrmStatisticsPoolSummaryByUserRespVO {
ownerUserName: string
customerPutCount: number
customerTakeCount: number
}
export interface CrmStatisticsCustomerDealCycleByDateRespVO {
time: string
customerDealCycle: number
}
export interface CrmStatisticsCustomerDealCycleByUserRespVO {
ownerUserName: string
customerDealCycle: number
customerDealCount: number
}
export interface CrmStatisticsCustomerDealCycleByAreaRespVO {
areaName: string
customerDealCycle: number
customerDealCount: number
}
export interface CrmStatisticsCustomerDealCycleByProductRespVO {
productName: string
customerDealCycle: number
customerDealCount: number
}
// 客户分析 API
export const StatisticsCustomerApi = {
// 1.1 客户总量分析(按日期)
getCustomerSummaryByDate: (params: any) => {
return request.get({
url: '/crm/statistics-customer/get-customer-summary-by-date',
params
})
},
// 1.2 客户总量分析(按用户)
getCustomerSummaryByUser: (params: any) => {
return request.get({
url: '/crm/statistics-customer/get-customer-summary-by-user',
params
})
},
// 2.1 客户跟进次数分析(按日期)
getFollowUpSummaryByDate: (params: any) => {
return request.get({
url: '/crm/statistics-customer/get-follow-up-summary-by-date',
params
})
},
// 2.2 客户跟进次数分析(按用户)
getFollowUpSummaryByUser: (params: any) => {
return request.get({
url: '/crm/statistics-customer/get-follow-up-summary-by-user',
params
})
},
// 3.1 获取客户跟进方式统计数
getFollowUpSummaryByType: (params: any) => {
return request.get({
url: '/crm/statistics-customer/get-follow-up-summary-by-type',
params
})
},
// 4.1 合同摘要信息(客户转化率页面)
getContractSummary: (params: any) => {
return request.get({
url: '/crm/statistics-customer/get-contract-summary',
params
})
},
// 5.1 获取客户公海分析(按日期)
getPoolSummaryByDate: (param: any) => {
return request.get({
url: '/crm/statistics-customer/get-pool-summary-by-date',
params: param
})
},
// 5.2 获取客户公海分析(按用户)
getPoolSummaryByUser: (param: any) => {
return request.get({
url: '/crm/statistics-customer/get-pool-summary-by-user',
params: param
})
},
// 6.1 获取客户成交周期(按日期)
getCustomerDealCycleByDate: (params: any) => {
return request.get({
url: '/crm/statistics-customer/get-customer-deal-cycle-by-date',
params
})
},
// 6.2 获取客户成交周期(按用户)
getCustomerDealCycleByUser: (params: any) => {
return request.get({
url: '/crm/statistics-customer/get-customer-deal-cycle-by-user',
params
})
},
// 6.2 获取客户成交周期(按用户)
getCustomerDealCycleByArea: (params: any) => {
return request.get({
url: '/crm/statistics-customer/get-customer-deal-cycle-by-area',
params
})
},
// 6.2 获取客户成交周期(按用户)
getCustomerDealCycleByProduct: (params: any) => {
return request.get({
url: '/crm/statistics-customer/get-customer-deal-cycle-by-product',
params
})
}
}

58
src/api/crm/statistics/funnel.ts

@ -0,0 +1,58 @@
import request from '@/config/axios'
export interface CrmStatisticFunnelRespVO {
customerCount: number // 客户数
businessCount: number // 商机数
businessWinCount: number // 赢单数
}
export interface CrmStatisticsBusinessSummaryByDateRespVO {
time: string // 时间
businessCreateCount: number // 商机数
totalPrice: number | string // 商机金额
}
export interface CrmStatisticsBusinessInversionRateSummaryByDateRespVO {
time: string // 时间
businessCount: number // 商机数量
businessWinCount: number // 赢单商机数
}
// 客户分析 API
export const StatisticFunnelApi = {
// 1. 获取销售漏斗统计数据
getFunnelSummary: (params: any) => {
return request.get({
url: '/crm/statistics-funnel/get-funnel-summary',
params
})
},
// 2. 获取商机结束状态统计
getBusinessSummaryByEndStatus: (params: any) => {
return request.get({
url: '/crm/statistics-funnel/get-business-summary-by-end-status',
params
})
},
// 3. 获取新增商机分析(按日期)
getBusinessSummaryByDate: (params: any) => {
return request.get({
url: '/crm/statistics-funnel/get-business-summary-by-date',
params
})
},
// 4. 获取商机转化率分析(按日期)
getBusinessInversionRateSummaryByDate: (params: any) => {
return request.get({
url: '/crm/statistics-funnel/get-business-inversion-rate-summary-by-date',
params
})
},
// 5. 获取商机列表(按日期)
getBusinessPageByDate: (params: any) => {
return request.get({
url: '/crm/statistics-funnel/get-business-page-by-date',
params
})
}
}

33
src/api/crm/statistics/performance.ts

@ -0,0 +1,33 @@
import request from '@/config/axios'
export interface StatisticsPerformanceRespVO {
time: string
currentMonthCount: number
lastMonthCount: number
lastYearCount: number
}
// 排行 API
export const StatisticsPerformanceApi = {
// 员工获得合同金额统计
getContractPricePerformance: (params: any) => {
return request.get({
url: '/crm/statistics-performance/get-contract-price-performance',
params
})
},
// 员工获得回款统计
getReceivablePricePerformance: (params: any) => {
return request.get({
url: '/crm/statistics-performance/get-receivable-price-performance',
params
})
},
//员工获得签约合同数量统计
getContractCountPerformance: (params: any) => {
return request.get({
url: '/crm/statistics-performance/get-contract-count-performance',
params
})
}
}

60
src/api/crm/statistics/portrait.ts

@ -0,0 +1,60 @@
import request from '@/config/axios'
export interface CrmStatisticCustomerBaseRespVO {
customerCount: number
dealCount: number
dealPortion: string | number
}
export interface CrmStatisticCustomerIndustryRespVO extends CrmStatisticCustomerBaseRespVO {
industryId: number
industryPortion: string | number
}
export interface CrmStatisticCustomerSourceRespVO extends CrmStatisticCustomerBaseRespVO {
source: number
sourcePortion: string | number
}
export interface CrmStatisticCustomerLevelRespVO extends CrmStatisticCustomerBaseRespVO {
level: number
levelPortion: string | number
}
export interface CrmStatisticCustomerAreaRespVO extends CrmStatisticCustomerBaseRespVO {
areaId: number
areaName: string
areaPortion: string | number
}
// 客户分析 API
export const StatisticsPortraitApi = {
// 1. 获取客户行业统计数据
getCustomerIndustry: (params: any) => {
return request.get({
url: '/crm/statistics-portrait/get-customer-industry-summary',
params
})
},
// 2. 获取客户来源统计数据
getCustomerSource: (params: any) => {
return request.get({
url: '/crm/statistics-portrait/get-customer-source-summary',
params
})
},
// 3. 获取客户级别统计数据
getCustomerLevel: (params: any) => {
return request.get({
url: '/crm/statistics-portrait/get-customer-level-summary',
params
})
},
// 4. 获取客户地区统计数据
getCustomerArea: (params: any) => {
return request.get({
url: '/crm/statistics-portrait/get-customer-area-summary',
params
})
}
}

67
src/api/crm/statistics/rank.ts

@ -0,0 +1,67 @@
import request from '@/config/axios'
export interface StatisticsRankRespVO {
count: number
nickname: string
deptName: string
}
// 排行 API
export const StatisticsRankApi = {
// 获得合同排行榜
getContractPriceRank: (params: any) => {
return request.get({
url: '/crm/statistics-rank/get-contract-price-rank',
params
})
},
// 获得回款排行榜
getReceivablePriceRank: (params: any) => {
return request.get({
url: '/crm/statistics-rank/get-receivable-price-rank',
params
})
},
// 签约合同排行
getContractCountRank: (params: any) => {
return request.get({
url: '/crm/statistics-rank/get-contract-count-rank',
params
})
},
// 产品销量排行
getProductSalesRank: (params: any) => {
return request.get({
url: '/crm/statistics-rank/get-product-sales-rank',
params
})
},
// 新增客户数排行
getCustomerCountRank: (params: any) => {
return request.get({
url: '/crm/statistics-rank/get-customer-count-rank',
params
})
},
// 新增联系人数排行
getContactsCountRank: (params: any) => {
return request.get({
url: '/crm/statistics-rank/get-contacts-count-rank',
params
})
},
// 跟进次数排行
getFollowCountRank: (params: any) => {
return request.get({
url: '/crm/statistics-rank/get-follow-count-rank',
params
})
},
// 跟进客户数排行
getFollowCustomerCountRank: (params: any) => {
return request.get({
url: '/crm/statistics-rank/get-follow-customer-count-rank',
params
})
}
}

61
src/api/erp/finance/account/index.ts

@ -0,0 +1,61 @@
import request from '@/config/axios'
// ERP 结算账户 VO
export interface AccountVO {
id: number // 结算账户编号
no: string // 账户编码
remark: string // 备注
status: number // 开启状态
sort: number // 排序
defaultStatus: boolean // 是否默认
name: string // 账户名称
}
// ERP 结算账户 API
export const AccountApi = {
// 查询结算账户分页
getAccountPage: async (params: any) => {
return await request.get({ url: `/erp/account/page`, params })
},
// 查询结算账户精简列表
getAccountSimpleList: async () => {
return await request.get({ url: `/erp/account/simple-list` })
},
// 查询结算账户详情
getAccount: async (id: number) => {
return await request.get({ url: `/erp/account/get?id=` + id })
},
// 新增结算账户
createAccount: async (data: AccountVO) => {
return await request.post({ url: `/erp/account/create`, data })
},
// 修改结算账户
updateAccount: async (data: AccountVO) => {
return await request.put({ url: `/erp/account/update`, data })
},
// 修改结算账户默认状态
updateAccountDefaultStatus: async (id: number, defaultStatus: boolean) => {
return await request.put({
url: `/erp/account/update-default-status`,
params: {
id,
defaultStatus
}
})
},
// 删除结算账户
deleteAccount: async (id: number) => {
return await request.delete({ url: `/erp/account/delete?id=` + id })
},
// 导出结算账户 Excel
exportAccount: async (params: any) => {
return await request.download({ url: `/erp/account/export-excel`, params })
}
}

61
src/api/erp/finance/payment/index.ts

@ -0,0 +1,61 @@
import request from '@/config/axios'
// ERP 付款单 VO
export interface FinancePaymentVO {
id: number // 付款单编号
no: string // 付款单号
supplierId: number // 供应商编号
paymentTime: Date // 付款时间
totalPrice: number // 合计金额,单位:元
status: number // 状态
remark: string // 备注
}
// ERP 付款单 API
export const FinancePaymentApi = {
// 查询付款单分页
getFinancePaymentPage: async (params: any) => {
return await request.get({ url: `/erp/finance-payment/page`, params })
},
// 查询付款单详情
getFinancePayment: async (id: number) => {
return await request.get({ url: `/erp/finance-payment/get?id=` + id })
},
// 新增付款单
createFinancePayment: async (data: FinancePaymentVO) => {
return await request.post({ url: `/erp/finance-payment/create`, data })
},
// 修改付款单
updateFinancePayment: async (data: FinancePaymentVO) => {
return await request.put({ url: `/erp/finance-payment/update`, data })
},
// 更新付款单的状态
updateFinancePaymentStatus: async (id: number, status: number) => {
return await request.put({
url: `/erp/finance-payment/update-status`,
params: {
id,
status
}
})
},
// 删除付款单
deleteFinancePayment: async (ids: number[]) => {
return await request.delete({
url: `/erp/finance-payment/delete`,
params: {
ids: ids.join(',')
}
})
},
// 导出付款单 Excel
exportFinancePayment: async (params: any) => {
return await request.download({ url: `/erp/finance-payment/export-excel`, params })
}
}

61
src/api/erp/finance/receipt/index.ts

@ -0,0 +1,61 @@
import request from '@/config/axios'
// ERP 收款单 VO
export interface FinanceReceiptVO {
id: number // 收款单编号
no: string // 收款单号
customerId: number // 客户编号
receiptTime: Date // 收款时间
totalPrice: number // 合计金额,单位:元
status: number // 状态
remark: string // 备注
}
// ERP 收款单 API
export const FinanceReceiptApi = {
// 查询收款单分页
getFinanceReceiptPage: async (params: any) => {
return await request.get({ url: `/erp/finance-receipt/page`, params })
},
// 查询收款单详情
getFinanceReceipt: async (id: number) => {
return await request.get({ url: `/erp/finance-receipt/get?id=` + id })
},
// 新增收款单
createFinanceReceipt: async (data: FinanceReceiptVO) => {
return await request.post({ url: `/erp/finance-receipt/create`, data })
},
// 修改收款单
updateFinanceReceipt: async (data: FinanceReceiptVO) => {
return await request.put({ url: `/erp/finance-receipt/update`, data })
},
// 更新收款单的状态
updateFinanceReceiptStatus: async (id: number, status: number) => {
return await request.put({
url: `/erp/finance-receipt/update-status`,
params: {
id,
status
}
})
},
// 删除收款单
deleteFinanceReceipt: async (ids: number[]) => {
return await request.delete({
url: `/erp/finance-receipt/delete`,
params: {
ids: ids.join(',')
}
})
},
// 导出收款单 Excel
exportFinanceReceipt: async (params: any) => {
return await request.download({ url: `/erp/finance-receipt/export-excel`, params })
}
}

49
src/api/erp/product/category/index.ts

@ -0,0 +1,49 @@
import request from '@/config/axios'
// ERP 产品分类 VO
export interface ProductCategoryVO {
id: number // 分类编号
parentId: number // 父分类编号
name: string // 分类名称
code: string // 分类编码
sort: number // 分类排序
status: number // 开启状态
}
// ERP 产品分类 API
export const ProductCategoryApi = {
// 查询产品分类列表
getProductCategoryList: async () => {
return await request.get({ url: `/erp/product-category/list` })
},
// 查询产品分类精简列表
getProductCategorySimpleList: async () => {
return await request.get({ url: `/erp/product-category/simple-list` })
},
// 查询产品分类详情
getProductCategory: async (id: number) => {
return await request.get({ url: `/erp/product-category/get?id=` + id })
},
// 新增产品分类
createProductCategory: async (data: ProductCategoryVO) => {
return await request.post({ url: `/erp/product-category/create`, data })
},
// 修改产品分类
updateProductCategory: async (data: ProductCategoryVO) => {
return await request.put({ url: `/erp/product-category/update`, data })
},
// 删除产品分类
deleteProductCategory: async (id: number) => {
return await request.delete({ url: `/erp/product-category/delete?id=` + id })
},
// 导出产品分类 Excel
exportProductCategory: async (params) => {
return await request.download({ url: `/erp/product-category/export-excel`, params })
}
}

57
src/api/erp/product/product/index.ts

@ -0,0 +1,57 @@
import request from '@/config/axios'
// ERP 产品 VO
export interface ProductVO {
id: number // 产品编号
name: string // 产品名称
barCode: string // 产品条码
categoryId: number // 产品类型编号
unitId: number // 单位编号
unitName?: string // 单位名字
status: number // 产品状态
standard: string // 产品规格
remark: string // 产品备注
expiryDay: number // 保质期天数
weight: number // 重量(kg)
purchasePrice: number // 采购价格,单位:元
salePrice: number // 销售价格,单位:元
minPrice: number // 最低价格,单位:元
}
// ERP 产品 API
export const ProductApi = {
// 查询产品分页
getProductPage: async (params: any) => {
return await request.get({ url: `/erp/product/page`, params })
},
// 查询产品精简列表
getProductSimpleList: async () => {
return await request.get({ url: `/erp/product/simple-list` })
},
// 查询产品详情
getProduct: async (id: number) => {
return await request.get({ url: `/erp/product/get?id=` + id })
},
// 新增产品
createProduct: async (data: ProductVO) => {
return await request.post({ url: `/erp/product/create`, data })
},
// 修改产品
updateProduct: async (data: ProductVO) => {
return await request.put({ url: `/erp/product/update`, data })
},
// 删除产品
deleteProduct: async (id: number) => {
return await request.delete({ url: `/erp/product/delete?id=` + id })
},
// 导出产品 Excel
exportProduct: async (params) => {
return await request.download({ url: `/erp/product/export-excel`, params })
}
}

46
src/api/erp/product/unit/index.ts

@ -0,0 +1,46 @@
import request from '@/config/axios'
// ERP 产品单位 VO
export interface ProductUnitVO {
id: number // 单位编号
name: string // 单位名字
status: number // 单位状态
}
// ERP 产品单位 API
export const ProductUnitApi = {
// 查询产品单位分页
getProductUnitPage: async (params: any) => {
return await request.get({ url: `/erp/product-unit/page`, params })
},
// 查询产品单位精简列表
getProductUnitSimpleList: async () => {
return await request.get({ url: `/erp/product-unit/simple-list` })
},
// 查询产品单位详情
getProductUnit: async (id: number) => {
return await request.get({ url: `/erp/product-unit/get?id=` + id })
},
// 新增产品单位
createProductUnit: async (data: ProductUnitVO) => {
return await request.post({ url: `/erp/product-unit/create`, data })
},
// 修改产品单位
updateProductUnit: async (data: ProductUnitVO) => {
return await request.put({ url: `/erp/product-unit/update`, data })
},
// 删除产品单位
deleteProductUnit: async (id: number) => {
return await request.delete({ url: `/erp/product-unit/delete?id=` + id })
},
// 导出产品单位 Excel
exportProductUnit: async (params) => {
return await request.download({ url: `/erp/product-unit/export-excel`, params })
}
}

64
src/api/erp/purchase/in/index.ts

@ -0,0 +1,64 @@
import request from '@/config/axios'
// ERP 采购入库 VO
export interface PurchaseInVO {
id: number // 入库工单编号
no: string // 采购入库号
customerId: number // 客户编号
inTime: Date // 入库时间
totalCount: number // 合计数量
totalPrice: number // 合计金额,单位:元
status: number // 状态
remark: string // 备注
outCount: number // 采购出库数量
returnCount: number // 采购退货数量
}
// ERP 采购入库 API
export const PurchaseInApi = {
// 查询采购入库分页
getPurchaseInPage: async (params: any) => {
return await request.get({ url: `/erp/purchase-in/page`, params })
},
// 查询采购入库详情
getPurchaseIn: async (id: number) => {
return await request.get({ url: `/erp/purchase-in/get?id=` + id })
},
// 新增采购入库
createPurchaseIn: async (data: PurchaseInVO) => {
return await request.post({ url: `/erp/purchase-in/create`, data })
},
// 修改采购入库
updatePurchaseIn: async (data: PurchaseInVO) => {
return await request.put({ url: `/erp/purchase-in/update`, data })
},
// 更新采购入库的状态
updatePurchaseInStatus: async (id: number, status: number) => {
return await request.put({
url: `/erp/purchase-in/update-status`,
params: {
id,
status
}
})
},
// 删除采购入库
deletePurchaseIn: async (ids: number[]) => {
return await request.delete({
url: `/erp/purchase-in/delete`,
params: {
ids: ids.join(',')
}
})
},
// 导出采购入库 Excel
exportPurchaseIn: async (params: any) => {
return await request.download({ url: `/erp/purchase-in/export-excel`, params })
}
}

64
src/api/erp/purchase/order/index.ts

@ -0,0 +1,64 @@
import request from '@/config/axios'
// ERP 采购订单 VO
export interface PurchaseOrderVO {
id: number // 订单工单编号
no: string // 采购订单号
customerId: number // 客户编号
orderTime: Date // 订单时间
totalCount: number // 合计数量
totalPrice: number // 合计金额,单位:元
status: number // 状态
remark: string // 备注
outCount: number // 采购出库数量
returnCount: number // 采购退货数量
}
// ERP 采购订单 API
export const PurchaseOrderApi = {
// 查询采购订单分页
getPurchaseOrderPage: async (params: any) => {
return await request.get({ url: `/erp/purchase-order/page`, params })
},
// 查询采购订单详情
getPurchaseOrder: async (id: number) => {
return await request.get({ url: `/erp/purchase-order/get?id=` + id })
},
// 新增采购订单
createPurchaseOrder: async (data: PurchaseOrderVO) => {
return await request.post({ url: `/erp/purchase-order/create`, data })
},
// 修改采购订单
updatePurchaseOrder: async (data: PurchaseOrderVO) => {
return await request.put({ url: `/erp/purchase-order/update`, data })
},
// 更新采购订单的状态
updatePurchaseOrderStatus: async (id: number, status: number) => {
return await request.put({
url: `/erp/purchase-order/update-status`,
params: {
id,
status
}
})
},
// 删除采购订单
deletePurchaseOrder: async (ids: number[]) => {
return await request.delete({
url: `/erp/purchase-order/delete`,
params: {
ids: ids.join(',')
}
})
},
// 导出采购订单 Excel
exportPurchaseOrder: async (params: any) => {
return await request.download({ url: `/erp/purchase-order/export-excel`, params })
}
}

62
src/api/erp/purchase/return/index.ts

@ -0,0 +1,62 @@
import request from '@/config/axios'
// ERP 采购退货 VO
export interface PurchaseReturnVO {
id: number // 采购退货编号
no: string // 采购退货号
customerId: number // 客户编号
returnTime: Date // 退货时间
totalCount: number // 合计数量
totalPrice: number // 合计金额,单位:元
status: number // 状态
remark: string // 备注
}
// ERP 采购退货 API
export const PurchaseReturnApi = {
// 查询采购退货分页
getPurchaseReturnPage: async (params: any) => {
return await request.get({ url: `/erp/purchase-return/page`, params })
},
// 查询采购退货详情
getPurchaseReturn: async (id: number) => {
return await request.get({ url: `/erp/purchase-return/get?id=` + id })
},
// 新增采购退货
createPurchaseReturn: async (data: PurchaseReturnVO) => {
return await request.post({ url: `/erp/purchase-return/create`, data })
},
// 修改采购退货
updatePurchaseReturn: async (data: PurchaseReturnVO) => {
return await request.put({ url: `/erp/purchase-return/update`, data })
},
// 更新采购退货的状态
updatePurchaseReturnStatus: async (id: number, status: number) => {
return await request.put({
url: `/erp/purchase-return/update-status`,
params: {
id,
status
}
})
},
// 删除采购退货
deletePurchaseReturn: async (ids: number[]) => {
return await request.delete({
url: `/erp/purchase-return/delete`,
params: {
ids: ids.join(',')
}
})
},
// 导出采购退货 Excel
exportPurchaseReturn: async (params: any) => {
return await request.download({ url: `/erp/purchase-return/export-excel`, params })
}
}

58
src/api/erp/purchase/supplier/index.ts

@ -0,0 +1,58 @@
import request from '@/config/axios'
// ERP 供应商 VO
export interface SupplierVO {
id: number // 供应商编号
name: string // 供应商名称
contact: string // 联系人
mobile: string // 手机号码
telephone: string // 联系电话
email: string // 电子邮箱
fax: string // 传真
remark: string // 备注
status: number // 开启状态
sort: number // 排序
taxNo: string // 纳税人识别号
taxPercent: number // 税率
bankName: string // 开户行
bankAccount: string // 开户账号
bankAddress: string // 开户地址
}
// ERP 供应商 API
export const SupplierApi = {
// 查询供应商分页
getSupplierPage: async (params: any) => {
return await request.get({ url: `/erp/supplier/page`, params })
},
// 获得供应商精简列表
getSupplierSimpleList: async () => {
return await request.get({ url: `/erp/supplier/simple-list` })
},
// 查询供应商详情
getSupplier: async (id: number) => {
return await request.get({ url: `/erp/supplier/get?id=` + id })
},
// 新增供应商
createSupplier: async (data: SupplierVO) => {
return await request.post({ url: `/erp/supplier/create`, data })
},
// 修改供应商
updateSupplier: async (data: SupplierVO) => {
return await request.put({ url: `/erp/supplier/update`, data })
},
// 删除供应商
deleteSupplier: async (id: number) => {
return await request.delete({ url: `/erp/supplier/delete?id=` + id })
},
// 导出供应商 Excel
exportSupplier: async (params) => {
return await request.download({ url: `/erp/supplier/export-excel`, params })
}
}

58
src/api/erp/sale/customer/index.ts

@ -0,0 +1,58 @@
import request from '@/config/axios'
// ERP 客户 VO
export interface CustomerVO {
id: number // 客户编号
name: string // 客户名称
contact: string // 联系人
mobile: string // 手机号码
telephone: string // 联系电话
email: string // 电子邮箱
fax: string // 传真
remark: string // 备注
status: number // 开启状态
sort: number // 排序
taxNo: string // 纳税人识别号
taxPercent: number // 税率
bankName: string // 开户行
bankAccount: string // 开户账号
bankAddress: string // 开户地址
}
// ERP 客户 API
export const CustomerApi = {
// 查询客户分页
getCustomerPage: async (params: any) => {
return await request.get({ url: `/erp/customer/page`, params })
},
// 查询客户精简列表
getCustomerSimpleList: async () => {
return await request.get({ url: `/erp/customer/simple-list` })
},
// 查询客户详情
getCustomer: async (id: number) => {
return await request.get({ url: `/erp/customer/get?id=` + id })
},
// 新增客户
createCustomer: async (data: CustomerVO) => {
return await request.post({ url: `/erp/customer/create`, data })
},
// 修改客户
updateCustomer: async (data: CustomerVO) => {
return await request.put({ url: `/erp/customer/update`, data })
},
// 删除客户
deleteCustomer: async (id: number) => {
return await request.delete({ url: `/erp/customer/delete?id=` + id })
},
// 导出客户 Excel
exportCustomer: async (params) => {
return await request.download({ url: `/erp/customer/export-excel`, params })
}
}

64
src/api/erp/sale/order/index.ts

@ -0,0 +1,64 @@
import request from '@/config/axios'
// ERP 销售订单 VO
export interface SaleOrderVO {
id: number // 订单工单编号
no: string // 销售订单号
customerId: number // 客户编号
orderTime: Date // 订单时间
totalCount: number // 合计数量
totalPrice: number // 合计金额,单位:元
status: number // 状态
remark: string // 备注
outCount: number // 销售出库数量
returnCount: number // 销售退货数量
}
// ERP 销售订单 API
export const SaleOrderApi = {
// 查询销售订单分页
getSaleOrderPage: async (params: any) => {
return await request.get({ url: `/erp/sale-order/page`, params })
},
// 查询销售订单详情
getSaleOrder: async (id: number) => {
return await request.get({ url: `/erp/sale-order/get?id=` + id })
},
// 新增销售订单
createSaleOrder: async (data: SaleOrderVO) => {
return await request.post({ url: `/erp/sale-order/create`, data })
},
// 修改销售订单
updateSaleOrder: async (data: SaleOrderVO) => {
return await request.put({ url: `/erp/sale-order/update`, data })
},
// 更新销售订单的状态
updateSaleOrderStatus: async (id: number, status: number) => {
return await request.put({
url: `/erp/sale-order/update-status`,
params: {
id,
status
}
})
},
// 删除销售订单
deleteSaleOrder: async (ids: number[]) => {
return await request.delete({
url: `/erp/sale-order/delete`,
params: {
ids: ids.join(',')
}
})
},
// 导出销售订单 Excel
exportSaleOrder: async (params: any) => {
return await request.download({ url: `/erp/sale-order/export-excel`, params })
}
}

62
src/api/erp/sale/out/index.ts

@ -0,0 +1,62 @@
import request from '@/config/axios'
// ERP 销售出库 VO
export interface SaleOutVO {
id: number // 销售出库编号
no: string // 销售出库号
customerId: number // 客户编号
outTime: Date // 出库时间
totalCount: number // 合计数量
totalPrice: number // 合计金额,单位:元
status: number // 状态
remark: string // 备注
}
// ERP 销售出库 API
export const SaleOutApi = {
// 查询销售出库分页
getSaleOutPage: async (params: any) => {
return await request.get({ url: `/erp/sale-out/page`, params })
},
// 查询销售出库详情
getSaleOut: async (id: number) => {
return await request.get({ url: `/erp/sale-out/get?id=` + id })
},
// 新增销售出库
createSaleOut: async (data: SaleOutVO) => {
return await request.post({ url: `/erp/sale-out/create`, data })
},
// 修改销售出库
updateSaleOut: async (data: SaleOutVO) => {
return await request.put({ url: `/erp/sale-out/update`, data })
},
// 更新销售出库的状态
updateSaleOutStatus: async (id: number, status: number) => {
return await request.put({
url: `/erp/sale-out/update-status`,
params: {
id,
status
}
})
},
// 删除销售出库
deleteSaleOut: async (ids: number[]) => {
return await request.delete({
url: `/erp/sale-out/delete`,
params: {
ids: ids.join(',')
}
})
},
// 导出销售出库 Excel
exportSaleOut: async (params: any) => {
return await request.download({ url: `/erp/sale-out/export-excel`, params })
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save