Browse Source

feat:1. 增加对话列表;2. 增加mqtt;

dxj
李朋徽 1 year ago
parent
commit
36d92d85ca
  1. 10
      .env.development
  2. 10
      .env.production
  3. 1
      eslint.config.js
  4. 1
      package.json
  5. 258
      pnpm-lock.yaml
  6. 47
      src/api/base/message.ts
  7. BIN
      src/assets/images/conversation/user.png
  8. 22
      src/assets/svg/user.svg
  9. 9
      src/components/AppMessage/index.d.ts
  10. 3
      src/components/AppMessage/index.ts
  11. 138
      src/components/AppMessage/index.vue
  12. 11
      src/components/AppSubMenuList/index.d.ts
  13. 11
      src/components/AppTextarea/index.vue
  14. 4
      src/enums/cacheEnum.ts
  15. 26
      src/enums/messageEnum.ts
  16. 54
      src/enums/mqttEnum.ts
  17. 86
      src/hooks/useMqtt.ts
  18. 12
      src/layout/AppMenu/index.vue
  19. 20
      src/store/moules/messageStore/index.d.ts
  20. 51
      src/store/moules/messageStore/index.ts
  21. 7
      src/store/moules/userStore/index.d.ts
  22. 26
      src/store/moules/userStore/index.ts
  23. 93
      src/utils/mqtt.ts
  24. 138
      src/views/conversation/index.vue
  25. 7
      types/vite-env.d.ts

10
.env.development

@ -4,7 +4,15 @@
VITE_GLOB_BASE_URL = "http://localhost:48080"
# 本地MQTT地址
VITE_GLOB_MQTT_URL = "http://localhost:48080"
VITE_GLOB_MQTT_HOST = "223.99.228.240"
VITE_GLOB_MQTT_PORT = 28083
VITE_GLOB_MQTT_PROTOCOL = "ws"
# 本地MQTT用户名
VITE_GLOB_MQTT_USERNAME = "serverAdmin"
# 本地MQTT密码
VITE_GLOB_MQTT_PASSWORD = "EnpfgI9yuSwi"
# 接口授权标识
VITE_GLOB_APP_AUTHORIZATION = "ZmFsY29uOmZhbGNvbl9zZWNyZXQ="

10
.env.production

@ -4,7 +4,15 @@
VITE_GLOB_BASE_URL = "http://223.99.228.207:19872"
# 本地MQTT地址
VITE_GLOB_MQTT_URL = "http://localhost:48080"
VITE_GLOB_MQTT_HOST = "223.99.228.240"
VITE_GLOB_MQTT_PORT = 28083
VITE_GLOB_MQTT_PROTOCOL = "ws"
# 本地MQTT用户名
VITE_GLOB_MQTT_USERNAME = "serverAdmin"
# 本地MQTT密码
VITE_GLOB_MQTT_PASSWORD = "EnpfgI9yuSwi"
# 接口授权标识
VITE_GLOB_APP_AUTHORIZATION = "ZmFsY29uOmZhbGNvbl9zZWNyZXQ="

1
eslint.config.js

@ -7,6 +7,7 @@ export default antfu(
formatters: true,
rules: {
'vue/html-self-closing': 'off',
'curly': 'off',
},
},
)

1
package.json

@ -22,6 +22,7 @@
"axios": "^1.6.5",
"crypto-js": "^4.2.0",
"lodash-es": "^4.17.21",
"mqtt": "^5.3.4",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1",
"qs": "^6.11.2",

258
pnpm-lock.yaml

@ -23,6 +23,9 @@ dependencies:
lodash-es:
specifier: ^4.17.21
version: 4.17.21
mqtt:
specifier: ^5.3.4
version: 5.3.4
pinia:
specifier: ^2.1.7
version: 2.1.7(typescript@5.3.3)(vue@3.4.13)
@ -1147,7 +1150,6 @@ packages:
resolution: {integrity: sha512-o9bjXmDNcF7GbM4CNQpmi+TutCgap/K3w1JyKgxAjqx41zp9qlIAVFi0IhCNsJcXolEqLWhbFbEeL0PvYm4pcQ==}
dependencies:
undici-types: 5.26.5
dev: true
/@types/normalize-package-data@2.4.4:
resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==}
@ -1157,6 +1159,13 @@ packages:
resolution: {integrity: sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==}
dev: true
/@types/readable-stream@4.0.10:
resolution: {integrity: sha512-AbUKBjcC8SHmImNi4yK2bbjogQlkFSg7shZCcicxPQapniOlajG8GCc39lvXzCWX4lLRRs7DM3VAeSlqmEVZUA==}
dependencies:
'@types/node': 20.11.0
safe-buffer: 5.1.2
dev: false
/@types/semver@7.5.6:
resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==}
dev: true
@ -1175,6 +1184,12 @@ packages:
resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==}
dev: false
/@types/ws@8.5.10:
resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==}
dependencies:
'@types/node': 20.11.0
dev: false
/@typescript-eslint/eslint-plugin@6.18.1(@typescript-eslint/parser@6.18.1)(eslint@8.56.0)(typescript@5.3.3):
resolution: {integrity: sha512-nISDRYnnIpk7VCFrGcu1rnZfM1Dh9LRHnfgdkjcbi/l7g16VYRri3TjXi9Ir4lOZSw5N/gnV/3H7jIPQ8Q4daA==}
engines: {node: ^16.0.0 || >=18.0.0}
@ -1668,6 +1683,13 @@ packages:
- vue
dev: false
/abort-controller@3.0.0:
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
engines: {node: '>=6.5'}
dependencies:
event-target-shim: 5.0.1
dev: false
/acorn-jsx@5.3.2(acorn@8.11.3):
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
@ -1828,7 +1850,10 @@ packages:
/balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
dev: true
/base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
dev: false
/base@0.11.2:
resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==}
@ -1852,6 +1877,14 @@ packages:
engines: {node: '>=8'}
dev: true
/bl@6.0.10:
resolution: {integrity: sha512-F14DFhDZfxtVm2FY0k9kG2lWAwzZkO9+jX3Ytuoy/V0E1/5LBuBzzQHXAjqpxXEDIpmTPZZf5GVIGPQcLxFpaA==}
dependencies:
buffer: 6.0.3
inherits: 2.0.4
readable-stream: 4.5.2
dev: false
/bluebird@3.7.2:
resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==}
dev: true
@ -1871,7 +1904,6 @@ packages:
resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
dependencies:
balanced-match: 1.0.2
dev: true
/braces@2.3.2:
resolution: {integrity: sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==}
@ -1909,6 +1941,17 @@ packages:
update-browserslist-db: 1.0.13(browserslist@4.22.2)
dev: true
/buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
dev: false
/buffer@6.0.3:
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
dependencies:
base64-js: 1.5.1
ieee754: 1.2.1
dev: false
/builtin-modules@3.3.0:
resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==}
engines: {node: '>=6'}
@ -2098,6 +2141,10 @@ packages:
engines: {node: '>= 12.0.0'}
dev: true
/commist@3.2.0:
resolution: {integrity: sha512-4PIMoPniho+LqXmpS5d3NuGYncG6XWlkBSVGiWycL22dd42OYdUGil2CWuzklaJoNxyxUSpO4MKIBU94viWNAw==}
dev: false
/component-emitter@1.3.1:
resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==}
dev: true
@ -2114,6 +2161,16 @@ packages:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
dev: true
/concat-stream@2.0.0:
resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==}
engines: {'0': node >= 6.0}
dependencies:
buffer-from: 1.1.2
inherits: 2.0.4
readable-stream: 3.6.2
typedarray: 0.0.6
dev: false
/consola@3.2.3:
resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==}
engines: {node: ^14.18.0 || >=16.10.0}
@ -2247,7 +2304,6 @@ packages:
optional: true
dependencies:
ms: 2.1.2
dev: true
/decode-uri-component@0.2.2:
resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==}
@ -2927,6 +2983,16 @@ packages:
engines: {node: '>= 0.6'}
dev: true
/event-target-shim@5.0.1:
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
engines: {node: '>=6'}
dev: false
/events@3.3.0:
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
engines: {node: '>=0.8.x'}
dev: false
/execa@5.1.1:
resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
engines: {node: '>=10'}
@ -3030,6 +3096,14 @@ packages:
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
dev: true
/fast-unique-numbers@8.0.13:
resolution: {integrity: sha512-7OnTFAVPefgw2eBJ1xj2PGGR9FwYzSUso9decayHgCDX4sJkHLdcsYTytTg+tYv+wKF3U8gJuSBz2jJpQV4u/g==}
engines: {node: '>=16.1.0'}
dependencies:
'@babel/runtime': 7.23.8
tslib: 2.6.2
dev: false
/fastq@1.16.0:
resolution: {integrity: sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==}
dependencies:
@ -3131,7 +3205,6 @@ packages:
/fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
dev: true
/fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
@ -3209,6 +3282,17 @@ packages:
path-is-absolute: 1.0.1
dev: true
/glob@8.1.0:
resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==}
engines: {node: '>=12'}
dependencies:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
minimatch: 5.1.6
once: 1.4.0
dev: false
/globals@11.12.0:
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
engines: {node: '>=4'}
@ -3334,6 +3418,13 @@ packages:
hasBin: true
dev: true
/help-me@4.2.0:
resolution: {integrity: sha512-TAOnTB8Tz5Dw8penUuzHVrKNKlCIbwwbHnXraNJxPwf8LRtE2HlM84RYuezMFcwOJmoYOCWVDyJ8TQGxn9PgxA==}
dependencies:
glob: 8.1.0
readable-stream: 3.6.2
dev: false
/hosted-git-info@2.8.9:
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
dev: true
@ -3359,6 +3450,10 @@ packages:
engines: {node: '>=16.17.0'}
dev: true
/ieee754@1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
dev: false
/ignore@5.3.0:
resolution: {integrity: sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==}
engines: {node: '>= 4'}
@ -3397,11 +3492,9 @@ packages:
dependencies:
once: 1.4.0
wrappy: 1.0.2
dev: true
/inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
dev: true
/is-accessor-descriptor@1.0.1:
resolution: {integrity: sha512-YBUanLI8Yoihw923YeFUS5fs0fF2f5TSFTNiYAAzhhDscDa3lEqYuz1pDOEP5KvX94I9ey3vsqjJcLVFVU+3QA==}
@ -3587,6 +3680,10 @@ packages:
resolution: {integrity: sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==}
dev: true
/js-sdsl@4.3.0:
resolution: {integrity: sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==}
dev: false
/js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
@ -3775,6 +3872,11 @@ packages:
js-tokens: 4.0.0
dev: false
/lru-cache@10.1.0:
resolution: {integrity: sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==}
engines: {node: 14 || >=16.14}
dev: false
/lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
dependencies:
@ -3917,6 +4019,13 @@ packages:
brace-expansion: 1.1.11
dev: true
/minimatch@5.1.6:
resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
engines: {node: '>=10'}
dependencies:
brace-expansion: 2.0.1
dev: false
/minimatch@9.0.3:
resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==}
engines: {node: '>=16 || 14 >=14.17'}
@ -3926,7 +4035,6 @@ packages:
/minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
dev: true
/mixin-deep@1.3.2:
resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==}
@ -3945,6 +4053,43 @@ packages:
ufo: 1.3.2
dev: true
/mqtt-packet@9.0.0:
resolution: {integrity: sha512-8v+HkX+fwbodsWAZIZTI074XIoxVBOmPeggQuDFCGg1SqNcC+uoRMWu7J6QlJPqIUIJXmjNYYHxBBLr1Y/Df4w==}
dependencies:
bl: 6.0.10
debug: 4.3.4
process-nextick-args: 2.0.1
transitivePeerDependencies:
- supports-color
dev: false
/mqtt@5.3.4:
resolution: {integrity: sha512-nyhr2bnFtyiv68jV3yfR6eQtGcGs/jr2l3ETKXYc0amttsasXa1KgvETHRNRjfeDt/yc68IqoEjFzKkHpoQUPQ==}
engines: {node: '>=16.0.0'}
hasBin: true
dependencies:
'@types/readable-stream': 4.0.10
'@types/ws': 8.5.10
commist: 3.2.0
concat-stream: 2.0.0
debug: 4.3.4
help-me: 4.2.0
lru-cache: 10.1.0
minimist: 1.2.8
mqtt-packet: 9.0.0
number-allocator: 1.0.14
readable-stream: 4.5.2
reinterval: 1.1.0
rfdc: 1.3.0
split2: 4.2.0
worker-timers: 7.1.1
ws: 8.16.0
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
dev: false
/mrmime@2.0.0:
resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==}
engines: {node: '>=10'}
@ -3956,7 +4101,6 @@ packages:
/ms@2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
dev: true
/ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
@ -4044,6 +4188,15 @@ packages:
boolbase: 1.0.0
dev: true
/number-allocator@1.0.14:
resolution: {integrity: sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==}
dependencies:
debug: 4.3.4
js-sdsl: 4.3.0
transitivePeerDependencies:
- supports-color
dev: false
/object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
@ -4088,7 +4241,6 @@ packages:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
dependencies:
wrappy: 1.0.2
dev: true
/onetime@5.1.2:
resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
@ -4371,6 +4523,15 @@ packages:
hasBin: true
dev: true
/process-nextick-args@2.0.1:
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
dev: false
/process@0.11.10:
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
engines: {node: '>= 0.6.0'}
dev: false
/prompts@2.4.2:
resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
engines: {node: '>= 6'}
@ -4433,7 +4594,17 @@ packages:
inherits: 2.0.4
string_decoder: 1.3.0
util-deprecate: 1.0.2
dev: true
/readable-stream@4.5.2:
resolution: {integrity: sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies:
abort-controller: 3.0.0
buffer: 6.0.3
events: 3.3.0
process: 0.11.10
string_decoder: 1.3.0
dev: false
/readdirp@3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
@ -4466,6 +4637,10 @@ packages:
jsesc: 0.5.0
dev: true
/reinterval@1.1.0:
resolution: {integrity: sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==}
dev: false
/repeat-element@1.1.4:
resolution: {integrity: sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==}
engines: {node: '>=0.10.0'}
@ -4518,6 +4693,10 @@ packages:
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
dev: true
/rfdc@1.3.0:
resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==}
dev: false
/rimraf@3.0.2:
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
hasBin: true
@ -4554,9 +4733,12 @@ packages:
queue-microtask: 1.2.3
dev: true
/safe-buffer@5.1.2:
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
dev: false
/safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
dev: true
/safe-regex@1.1.0:
resolution: {integrity: sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==}
@ -4768,6 +4950,11 @@ packages:
extend-shallow: 3.0.2
dev: true
/split2@4.2.0:
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
engines: {node: '>= 10.x'}
dev: false
/stable@0.1.8:
resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==}
deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility'
@ -4799,7 +4986,6 @@ packages:
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
dependencies:
safe-buffer: 5.2.1
dev: true
/strip-ansi@3.0.1:
resolution: {integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==}
@ -4987,7 +5173,6 @@ packages:
/tslib@2.6.2:
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
dev: true
/type-check@0.4.0:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
@ -5011,6 +5196,10 @@ packages:
engines: {node: '>=8'}
dev: true
/typedarray@0.0.6:
resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==}
dev: false
/typescript@5.3.3:
resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==}
engines: {node: '>=14.17'}
@ -5031,7 +5220,6 @@ packages:
/undici-types@5.26.5:
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
dev: true
/union-value@1.0.1:
resolution: {integrity: sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==}
@ -5130,7 +5318,6 @@ packages:
/util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
dev: true
/validate-npm-package-license@3.0.4:
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
@ -5299,6 +5486,31 @@ packages:
isexe: 2.0.0
dev: true
/worker-timers-broker@6.1.1:
resolution: {integrity: sha512-CTlDnkXAewtYvw5gOwVIc6UuIPcNHJrqWxBMhZbCWOmadvl20nPs9beAsXlaTEwW3G2KBpuKiSgkhBkhl3mxDA==}
dependencies:
'@babel/runtime': 7.23.8
fast-unique-numbers: 8.0.13
tslib: 2.6.2
worker-timers-worker: 7.0.65
dev: false
/worker-timers-worker@7.0.65:
resolution: {integrity: sha512-Dl4nGONr8A8Fr+vQnH7Ee+o2iB480S1fBcyJYqnMyMwGRVyQZLZU+o91vbMvU1vHqiryRQmjXzzMYlh86wx+YQ==}
dependencies:
'@babel/runtime': 7.23.8
tslib: 2.6.2
dev: false
/worker-timers@7.1.1:
resolution: {integrity: sha512-axtq83GwPqYwkQmQmei2abQ9cT7oSwmLw4lQCZ9VmMH9g4t4kuEF1Gw+tdnIJGHCiZ2QPDnr/+307bYx6tynLA==}
dependencies:
'@babel/runtime': 7.23.8
tslib: 2.6.2
worker-timers-broker: 6.1.1
worker-timers-worker: 7.0.65
dev: false
/wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
@ -5310,7 +5522,19 @@ packages:
/wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
dev: true
/ws@8.16.0:
resolution: {integrity: sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: '>=5.0.2'
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
dev: false
/xml-name-validator@4.0.0:
resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==}

47
src/api/base/message.ts

@ -0,0 +1,47 @@
import { defHttp } from '@/utils/axios/index'
/**
* @description
*/
export async function conversationList() {
return defHttp.get({
url: `/open-chat/chat/conversation/list`,
})
}
/**
* @description
*/
export async function historyMessage(data: {
conversationId: string
current: number
size: number
}) {
return defHttp.get({
url: `/open-chat/chat/chatMessageLog/page`,
data,
})
}
/**
* @description
*/
export async function sendMessage(data: {
roleId: number
conversationId: string
question: string
}) {
return defHttp.post({
url: `/open-chat/chat/session`,
data,
})
}
/**
* @description chat信息
*/
export async function chatInfo() {
return defHttp.get({
url: `/open-chat/chat/ai/getWxUserInfo`,
})
}

BIN
src/assets/images/conversation/user.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

22
src/assets/svg/user.svg

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="60px" height="60px" viewBox="0 0 60 60" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>编组 7备份 4</title>
<defs>
<rect id="path-1" x="0" y="0" width="58" height="58" rx="29"></rect>
</defs>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="回答中" transform="translate(-1710.000000, -112.000000)">
<g id="编组-7备份-4" transform="translate(1711.000000, 113.000000)">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<use id="矩形备份-3" stroke="#EEEEEE" fill="#F2F8FF" xlink:href="#path-1"></use>
<g id="denglu-yonghu" mask="url(#mask-2)" fill="#0070FF" fill-rule="nonzero" opacity="0.160505022">
<g transform="translate(5.970588, 5.117647)" id="形状">
<path d="M46.9117647,52.8823529 C45.8899543,40.6305836 35.7791024,31.010496 23.4561738,31.010496 C11.1332452,31.010496 1.02122752,40.6305836 0,52.8823529 L46.9117647,52.8823529 Z M23.5068854,0 C15.4396544,0 8.90018433,6.6443449 8.90018433,14.8396291 C8.90018433,23.0349134 15.4419859,29.6792583 23.5068854,29.6792583 C31.5717849,29.6792583 38.1159181,23.0355056 38.1159181,14.8396291 C38.1159181,6.64375272 31.5764481,0 23.5068854,0 Z M23.5068854,22.6665727 C21.016392,22.6663151 18.9401803,20.7301133 18.7271676,18.2091766 L28.2866033,18.2091766 C28.0735906,20.7301133 25.9973789,22.6663151 23.5068854,22.6665727 L23.5068854,22.6665727 Z"></path>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

9
src/components/AppMessage/index.d.ts vendored

@ -0,0 +1,9 @@
import type { MessageStatusEnum, MessageTypeEnum } from '@/enums/messageEnum'
export interface MessageItem {
messageType: MessageType
content: string
time: string
avatar: string
messageStatus?: MessageStatusEnum
}

3
src/components/AppMessage/index.ts

@ -0,0 +1,3 @@
import AppMessage from './index.vue'
export { AppMessage }

138
src/components/AppMessage/index.vue

@ -0,0 +1,138 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import type { MessageItem } from './index.d'
import { MessageStatusEnum, MessageTypeEnum } from '@/enums/messageEnum'
defineProps<{
list: MessageItem[]
}>()
const messageRef = ref<Element | null>(null)
onMounted(async () => {
})
</script>
<template>
<div ref="messageRef" class="app-message">
<div class="content-wrap">
<div v-for="(item, index) in list" :key="item.time + index" class="item">
<div v-if="item.messageType === MessageTypeEnum.USER" class="user">
<div class="content">
{{ item.content }}
</div>
<img class="icon-user" src="@/assets/images/conversation/user.png" alt="">
</div>
<div v-if="item.messageType === MessageTypeEnum.AI" class="ai">
<img class="icon-ai" src="@/assets/images/conversation/logo.png" alt="">
<div class="content">
{{ item.content }}
</div>
<div v-if="item.messageStatus === MessageStatusEnum.ERROR" class="error">
哎呀出问题了...
</div>
<div
v-if="item.messageStatus === MessageStatusEnum.ACTICON"
class="loading"
>
正在回答中...
</div>
<div v-if="item.messageStatus === MessageStatusEnum.END" class="btns">
<div class="copy" @click="copyText(item.content)">
复制
</div>
<div v-if="index === 0" class="reload" @click="reloadMessage">
重新回答
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
@include app('message') {
height: calc(100% - 120px);
overflow: auto;
.content-wrap {
box-sizing: border-box;
transform: rotate(180deg);
.item {
padding: 0 15px;
margin-top: 15px;
transform: rotate(180deg);
.user,
.ai {
display: flex;
}
.user {
justify-content: flex-end;
.content {
color: #ffffff;
padding: 10px 15px;
background: linear-gradient(131deg, #009bfc 0%, #00eadb 100%);
border-radius: 15px 15px 0px 15px;
}
.icon-user {
width: 40px;
height: 40px;
margin-left: 15px;
}
}
.ai {
position: relative;
margin-bottom: 15px;
display: flex;
flex-wrap: wrap;
.icon-ai {
width: 40px;
height: 40px;
margin-right: 15px;
}
.content {
width: calc(100% - 55px);
padding: 10px 15px;
background: #ffffff;
border-radius: 0rpx 15px 15px 15px;
border: 1px solid #e7edef;
}
.error {
color: red;
height: 30px;
padding-left: 60px;
margin: 10px auto 0 auto;
}
.loading {
height: 30px;
color: #009dfb;
padding-left: 60px;
margin: 20rpx auto 0 auto;
}
.btns {
width: 100%;
height: 30px;
padding-left: 60px;
margin: 10px auto 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
.copy {
color: #009dfb;
padding: 5px 15px;
background: #c1effb;
border-radius: 15px;
text-align: center;
margin-right: 20px;
}
.reload {
color: #009dfb;
}
}
}
}
}
}
</style>

11
src/components/AppSubMenuList/index.d.ts vendored

@ -1,5 +1,14 @@
export interface SubMenuItem {
createDept: string
createTime: string
createUser: string
id: string
isDeleted: number
roleId: number
status: number
title: string
updateTime: string
updateUser: string
userId: string
content: string
id: string
}

11
src/components/AppTextarea/index.vue

@ -8,6 +8,10 @@ defineProps({
type: String,
default: '在此输入你想了解的内容(Shift + Enter = 换行)',
},
btnLoading: {
type: Boolean,
default: false,
},
})
const emit = defineEmits(['pressEnter', 'send'])
@ -38,7 +42,12 @@ function handeleSend() {
@press-enter="pressEnter"
>
</Textarea>
<Button class="w-28 absolute right-5 bottom-3" type="primary" @click="handeleSend">
<Button
class="w-28 absolute right-5 bottom-3"
type="primary"
:loading="btnLoading"
@click="handeleSend"
>
发送
</Button>
</div>

4
src/enums/cacheEnum.ts

@ -4,9 +4,11 @@ export const ACCESS_TOKEN_KEY = 'ACCESS_TOKEN'
// user info key
export const USER_INFO_KEY = 'USER_INFO'
export const USET_STORE_KEY = 'USER_STORE'
// chat info key
export const CHAT_INFO_KEY = 'CHAT_INFO'
export enum CatchTypeEnum {
ACCESS_TOKEN_KEY,
USER_INFO_KEY,
CHAT_INFO_KEY,
}

26
src/enums/messageEnum.ts

@ -0,0 +1,26 @@
/**
* @description: ai/user
*/
export enum MessageTypeEnum {
AI = 'ai',
USER = 'user',
}
export enum MessageStatusEnum {
/*
* loading
*/
LOADING = 'loading',
/*
*
*/
ACTICON = 'a',
/*
*
*/
END = 'e',
/*
*
*/
ERROR = 'error',
}

54
src/enums/mqttEnum.ts

@ -0,0 +1,54 @@
/*
* @Description:
* @Author: yeke
* @Date: 2024-01-09 14:10:43
* @LastEditors: yeke
* @LastEditTime: 2024-01-10 14:25:19
*/
/**
* @description: mqtt
* @param UNCONNECTED
* @param CONNECTING
* @param CONNECTED
* @param CONNECT_FAILED
* @param DISCONNECTED
* @param RECONNECTING
*/
export enum MqttConnectStateEnum {
// 未连接
UNCONNECTED = "unConnected",
// 连接中
CONNECTING = "connecting",
// 已连接
CONNECTED = "connected",
// 连接失败
CONNECT_FAILED = "connectFailed",
// 断开连接
DISCONNECTED = "disConnected",
// 断开中
DISCONNECTING = "disconnecting",
// 断开失败
DISCONNECT_FAILED = "disconnectFailed",
// 重连中
RECONNECTING = "reConnecting",
}
/**
* @description: mqtt
* @param UNSUBSCRIBED
* @param SUBSCRIBING
* @param SUBSCRIBE_SUCCESS
* @param SUBSCRIBE_FAILED
*/
export enum MqttSubcribeStateEnum {
// 未订阅
UNSUBSCRIBED = "unSubscribed",
// 订阅中
SUBSCRIBING = "subscribing",
// 订阅成功
SUBSCRIBE_SUCCESS = "subscribeSuccess",
// 订阅失败
SUBSCRIBE_FAILED = "subscribeFailed",
}

86
src/hooks/useMqtt.ts

@ -0,0 +1,86 @@
import type { Options } from '@/utils/mqtt'
import { MqttService } from '@/utils/mqtt'
import { useUserStore } from '@/store/moules/userStore/index'
import { useMessageStore } from '@/store/moules/messageStore/index'
import { MessageStatusEnum, MessageTypeEnum } from '@/enums/messageEnum'
export function useMqtt() {
const userStore = useUserStore()
const messageStore = useMessageStore()
const options: Options = {
host: import.meta.env.VITE_GLOB_MQTT_HOST,
port: import.meta.env.VITE_GLOB_MQTT_PORT,
protocol: import.meta.env.VITE_GLOB_MQTT_PROTOCOL,
clean: true,
connectTimeout: 4000, // 超时时间
reconnectPeriod: 4000, // 重连时间间隔
clientId: `Consumer@${userStore.getUserInfo?.chatCode}_${new Date().getTime()}`,
username: import.meta.env.VITE_GLOB_MQTT_USERNAME,
password: import.meta.env.VITE_GLOB_MQTT_PASSWORD,
}
const mqttService = new MqttService(options)
/**
* @description:
*/
const subscribe = (topicKey: string) => {
mqttService
.subscribe(topicKey)
.then(() => {
mqttService.onMessage(topicKey, (messageData: any) => {
console.log(messageData)
if (messageData.message_type === MessageStatusEnum.ACTICON) {
messageStore.setMessageStatus(MessageStatusEnum.ACTICON)
messageStore.setMessageFirstItem({
messageType: MessageTypeEnum.AI,
content: messageData.message_content,
time: String(new Date().getTime()),
avatar: '',
messageStatus: MessageStatusEnum.ACTICON,
})
}
if (messageData.message_type === MessageStatusEnum.END) {
messageStore.setMessageStatus(MessageStatusEnum.END)
messageStore.setMessageFirstItem({
messageType: MessageTypeEnum.AI,
content: messageData.message_content,
time: String(new Date().getTime()),
avatar: '',
messageStatus: MessageStatusEnum.END,
})
}
})
})
.catch(() => {
// uni.hideLoading()
})
}
/**
* @description:
*/
const connect = async () => {
return new Promise((resolve, reject) => {
mqttService
.connect()
.then(() => {
const topicKey = `chatgpt_${userStore.getChatInfo?.chatCode}_${messageStore.conversationData?.id}`
messageStore.setTopicKey(topicKey)
subscribe(topicKey)
resolve(true)
})
.catch(() => {
// eslint-disable-next-line prefer-promise-reject-errors
reject(false)
})
})
}
return {
connect,
subscribe,
}
}

12
src/layout/AppMenu/index.vue

@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref } from 'vue'
import { ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import type { MenuItem } from './index.d'
import { SvgIcon } from '@/components/SvgIcon'
@ -49,6 +49,16 @@ const footMenu = ref([
},
])
watch(
() => router.currentRoute.value.path,
(toPath) => {
const menuIndex = menu.value.findIndex(item => toPath.startsWith(item.path))
if (menuIndex !== -1)
menuActive.value = menu.value[menuIndex].key
},
{ immediate: true, deep: true },
)
function handleToPath(item: MenuItem) {
menuActive.value = item.key
router.push({ path: item.path })

20
src/store/moules/messageStore/index.d.ts vendored

@ -0,0 +1,20 @@
import type { MessageStatusEnum } from '@/enums/messageEnum'
import type { MessageItem } from '@/components/AppMessage/index.d'
export interface ConversationData {
id: string
roleId: number
}
export interface MessageStoreType {
// 消息当前状态
messageStatus: MessageStatusEnum
// 当前会话数据
conversationData?: ConversationData
// 消息主题key
topicKey: string
// 当前会话消息列表
messageList: MessageItem[]
}

51
src/store/moules/messageStore/index.ts

@ -0,0 +1,51 @@
import { defineStore } from 'pinia'
import type { ConversationData, MessageStoreType } from './index.d'
import { MessageStatusEnum } from '@/enums/messageEnum'
import type { MessageItem } from '@/components/AppMessage/index.d'
export const useMessageStore = defineStore('useMessageStore', {
state: (): MessageStoreType => {
return {
messageStatus: MessageStatusEnum.END,
conversationData: undefined,
topicKey: '',
messageList: [],
}
},
getters: {
getMessageStatus(): MessageStatusEnum {
return this.messageStatus
},
getConversationData(): ConversationData | undefined {
return this.conversationData
},
getTopicKey(): string {
return this.topicKey
},
getMessageList(): MessageItem[] {
return this.messageList
},
},
actions: {
setMessageStatus(status: MessageStatusEnum): void {
this.messageStatus = status
},
setConversationData(data: ConversationData) {
this.conversationData = data
},
setMessagePushItem(item: MessageItem) {
this.messageList.push(item)
},
setMessageFirstItem(item: MessageItem) {
this.messageList.unshift(item)
},
setTopicKey(key: string) {
this.topicKey = key
},
},
})

7
src/store/moules/userStore/index.d.ts vendored

@ -1,6 +1,7 @@
export interface UserStateType {
token: string | null
userInfo: string | null
chatInfo: string | null
}
export interface UserInfoType {
@ -12,4 +13,10 @@ export interface UserInfoType {
user_name: string
real_name: string
nick_name: string
chatCode: string
}
export interface ChatInfoType {
id: string
chatCode: string
}

26
src/store/moules/userStore/index.ts

@ -1,9 +1,10 @@
import { defineStore } from 'pinia'
import type { UserInfoType, UserStateType } from './index.d'
import type { ChatInfoType, UserInfoType, UserStateType } from './index.d'
import { router } from '@/router'
import { PageEnum } from '@/enums/pageEnum'
import { ACCESS_TOKEN_KEY, USER_INFO_KEY } from '@/enums/cacheEnum'
import { ACCESS_TOKEN_KEY, CHAT_INFO_KEY, USER_INFO_KEY } from '@/enums/cacheEnum'
import { token } from '@/api/base/login'
import { chatInfo } from '@/api/base/message'
import type { TokenParams } from '@/api/base/login'
import crypto from '@/utils/crypto'
@ -12,6 +13,7 @@ export const useUserStore = defineStore('useUserStore', {
return {
token: null,
userInfo: null,
chatInfo: null,
}
},
getters: {
@ -21,6 +23,9 @@ export const useUserStore = defineStore('useUserStore', {
getUserInfo(): UserInfoType | null {
return this.userInfo ? JSON.parse(crypto.decryptAES(this.userInfo, crypto.localKey)) : null
},
getChatInfo(): ChatInfoType | null {
return this.chatInfo ? JSON.parse(crypto.decryptAES(this.chatInfo, crypto.localKey)) : null
},
},
actions: {
setToken(token: string) {
@ -31,11 +36,16 @@ export const useUserStore = defineStore('useUserStore', {
this.userInfo = userInfo
},
setChatInfo(chatInfo: string) {
this.chatInfo = chatInfo
},
async login(params: TokenParams) {
return new Promise<void>((resolve, reject) => {
return new Promise<any>((resolve, reject) => {
token(params).then((res) => {
this.setToken(crypto.encryptAES(res.access_token, crypto.localKey))
this.setUserInfo(crypto.encryptAES(JSON.stringify(res), crypto.localKey))
this.getChatInfoFun()
resolve(res)
}).catch((err) => {
reject(err)
@ -43,6 +53,12 @@ export const useUserStore = defineStore('useUserStore', {
})
},
getChatInfoFun() {
chatInfo().then((res) => {
this.setChatInfo(crypto.encryptAES(JSON.stringify(res), crypto.localKey))
})
},
/**
* @description: logout
*/
@ -62,5 +78,9 @@ export const useUserStore = defineStore('useUserStore', {
paths: ['userInfo'],
key: USER_INFO_KEY,
},
{
paths: ['chatInfo'],
key: CHAT_INFO_KEY,
},
],
})

93
src/utils/mqtt.ts

@ -0,0 +1,93 @@
import mqtt from 'mqtt'
export interface Options {
host: string
port: number // ws -> 8083; wss -> 8084
protocol: mqtt.MqttProtocol // ws or wss
clean: boolean
clientId: string
connectTimeout: number
reconnectPeriod: number
username: string
password: string
}
export class MqttService {
client: mqtt.MqttClient | null
options: Options
constructor(options: Options) {
this.client = null
this.options = options
}
connect() {
return new Promise((resolve, reject) => {
const { protocol, host, port } = this.options
const connectUrl = `${protocol}://${host}:${port}/mqtt`
this.client = mqtt.connect(connectUrl, this.options)
console.log(this.client)
this.client.on('connect', () => {
console.log('连接成功')
resolve(true)
})
this.client.on('error', (error: any) => {
console.log('连接失败!', error)
reject(error)
})
})
}
subscribe(topic: string) {
if (!this.client || !this.client.connected) {
throw new Error('MQTT client is not connected.')
}
return new Promise((resolve, reject) => {
this.client.subscribe(topic, (err: any) => {
if (err) {
console.log('订阅失败!', err)
reject(err)
}
else {
console.log('订阅成功!', topic)
resolve(true)
}
})
})
}
onMessage(topic: string, callback: any) {
if (!this.client || !this.client.connected) {
throw new Error('MQTT client is not connected.')
}
this.client.on(
'message',
(receivedTopic: string, message: { toString: () => any }) => {
if (receivedTopic === topic) {
callback(JSON.parse(message.toString()))
}
},
)
}
unsubscribe(topic: string) {
if (!this.client || !this.client.connected) {
throw new Error('MQTT client is not connected.')
}
this.client.unsubscribe(topic, (err: any) => {
if (err) {
console.log('取消订阅失败!', err)
}
else {
console.log('取消订阅成功!', topic)
}
})
}
end() {
if (this.client) {
this.client.end()
console.log('mqtt已断开')
}
}
}

138
src/views/conversation/index.vue

@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref } from 'vue'
import { computed, onMounted, ref } from 'vue'
import { Button } from 'ant-design-vue'
import { AppContainerBox } from '@/components/AppContainerBox'
import { AppSubMenuTitle } from '@/components/AppSubMenuTitle'
@ -7,44 +7,146 @@ import { AppSubMenuList } from '@/components/AppSubMenuList'
import { AppConversationDefault } from '@/components/AppConversationDefault'
import type { SubMenuItem } from '@/components/AppSubMenuList/index.d'
import { AppTextarea } from '@/components/AppTextarea'
import { AppMessage } from '@/components/AppMessage'
import type { MessageItem } from '@/components/AppMessage/index.d'
import { useMessageStore } from '@/store/moules/messageStore/index'
import { MessageStatusEnum, MessageTypeEnum } from '@/enums/messageEnum'
import { conversationList, historyMessage, sendMessage } from '@/api/base/message'
import { useMqtt } from '@/hooks/useMqtt'
const messageStore = useMessageStore()
const sendBtnLoading = ref(false)
const subMenuActive = ref(0)
const subMenuList = ref<SubMenuItem[]>([
{
title: '新对话1',
content: '这是一个新的对话哦;啦啦啦',
id: '1',
},
{
title: '新对话2',
content: '这是一个新的对话哦',
id: '2',
},
])
const subMenuList = ref<SubMenuItem[]>([])
const messageList = computed(() => messageStore.getMessageList)
const conversationData = computed(() => messageStore.getConversationData)
const historyMessageParams = ref({
conversationId: conversationData.value ? conversationData.value.id : '',
current: 1,
size: 10,
total: 0,
})
const conversationDefaultShow = ref(true)
function handleSubMenuChange(index: number) {
subMenuActive.value = index
}
function pressEnter(value: string) {
console.log('pressEnter', value)
function handleSend(value: string) {
console.log('handleSend', value)
sendBtnLoading.value = true
conversationDefaultShow.value = false
messageStore.setMessageStatus(MessageStatusEnum.LOADING)
messageStore.setMessageFirstItem({
messageType: MessageTypeEnum.USER,
content: value,
time: String(new Date().getTime()),
avatar: '',
})
messageStore.setMessageFirstItem({
messageType: MessageTypeEnum.AI,
content: '正在思考中...',
time: String(new Date().getTime()),
avatar: '',
messageStatus: MessageStatusEnum.LOADING,
})
if (!messageStore.getConversationData) {
return
}
sendMessage({
roleId: messageStore.getConversationData.roleId,
conversationId: messageStore.getConversationData.id,
question: value,
}).then(() => {
}).catch(() => {
messageStore.setMessageStatus(MessageStatusEnum.END)
})
}
/**
* @description: 获取会话列表
*/
async function getConversationList() {
const res = await conversationList()
subMenuList.value = res
subMenuActive.value = 0
if (subMenuList.value.length) {
messageStore.setConversationData(subMenuList.value[subMenuActive.value])
historyMessageParams.value.current = 1
getHistoryMessage()
await useMqtt().connect()
}
}
/**
* @description: 获取历史对话记录
*/
async function getHistoryMessage() {
const res = await historyMessage(historyMessageParams.value)
if (!res || !res.records || !res.records.length) {
return
}
res.records.forEach((item: any) => {
const itemData: MessageItem = {
messageType: item.roleType === MessageTypeEnum.USER ? MessageTypeEnum.USER : MessageTypeEnum.AI,
content: item.messageContent,
time: item.messageTime,
avatar: '',
messageStatus: MessageStatusEnum.END,
}
historyMessageParams.value.total = res.total
messageStore.setMessagePushItem(itemData)
})
conversationDefaultShow.value = false
}
onMounted(() => {
getConversationList()
})
</script>
<template>
<AppContainerBox>
<template #subMenu>
<!-- 标题 -->
<AppSubMenuTitle></AppSubMenuTitle>
<!-- 按钮 -->
<div class="px-5 mb-5">
<Button type="primary" class="w-full">
新建会话
</Button>
</div>
<AppSubMenuList :list="subMenuList" :active-index="subMenuActive" @change="handleSubMenuChange"></AppSubMenuList>
<!-- 会话列表 -->
<AppSubMenuList
:list="subMenuList"
:active-index="subMenuActive"
@change="handleSubMenuChange"
>
</AppSubMenuList>
</template>
<template #content>
<AppConversationDefault height="calc(100% - 120px)"></AppConversationDefault>
<AppTextarea class="pl-52 pr-32 mt-10" @press-enter="pressEnter"></AppTextarea>
<!-- 默认导语 -->
<AppConversationDefault
v-if="conversationDefaultShow"
height="calc(100% - 120px)"
>
</AppConversationDefault>
<!-- 消息列表 -->
<AppMessage v-else :list="messageList"></AppMessage>
<!-- 发送框 -->
<AppTextarea
class="pl-52 pr-32 mt-10"
:btn-loading="sendBtnLoading"
@send="handleSend"
></AppTextarea>
</template>
</AppContainerBox>
</template>

7
types/vite-env.d.ts vendored

@ -1,4 +1,5 @@
/// <reference types="vite/client" />
import type mqtt from 'mqtt'
interface ImportMetaEnv {
NODE_ENV: string
@ -6,7 +7,11 @@ interface ImportMetaEnv {
VITE_GLOB_APP_TITLE: string
VITE_GLOB_APP_SHORT_NAME: string
VITE_GLOB_BASE_URL: string
VITE_GLOB_MQTT_URL: string
VITE_GLOB_MQTT_HOST: string
VITE_GLOB_MQTT_PORT: number
VITE_GLOB_MQTT_PROTOCOL: mqtt.MqttProtocol
VITE_GLOB_APP_AUTHORIZATION: string
VITE_GLOB_APP_TOKEN_KEY: string
VITE_GLOB_MQTT_USERNAME: string
VITE_GLOB_MQTT_PASSWORD: string
}

Loading…
Cancel
Save