Переглянути джерело

Merge branch 'openIm' of http://1.14.104.71:10880/root/ylrz_his_scrm_doctorUI

# Conflicts:
#	src/router/index.js
#	src/store/modules/user.js
#	src/views/his/prescribe/index.vue
15376779826 2 тижнів тому
батько
коміт
44a263daa7
98 змінених файлів з 6914 додано та 2318 видалено
  1. 4 0
      .env.development
  2. 4 1
      .env.prod-hdt
  3. 2 1
      .env.prod-jnmy
  4. 4 1
      .env.prod-jzzx
  5. 4 2
      .env.prod-myhk
  6. 1 0
      README.md
  7. 9 3
      package.json
  8. 146 0
      patches/@openim+wasm-client-sdk+3.8.3-patch.3.patch
  9. 148 0
      patches/open-im-sdk-wasm+3.8.0.patch
  10. 2 1
      public/index.html
  11. BIN
      public/openIM.wasm
  12. BIN
      public/sql-wasm.wasm
  13. 561 0
      public/wasm_exec.js
  14. 26 0
      src/api/collection.js
  15. 27 0
      src/api/diagnosis.js
  16. 7 0
      src/api/doctor.js
  17. 22 1
      src/api/inquiryOrder.js
  18. 9 0
      src/api/prescribe.js
  19. BIN
      src/assets/audio/beCalled.mp3
  20. 48 0
      src/assets/doctor.svg
  21. 14 0
      src/assets/guanjia.svg
  22. 19 6
      src/components/avatar.vue
  23. 180 91
      src/components/conversation/conversation-item.vue
  24. 12 2
      src/components/conversation/conversation-list.vue
  25. 3 3
      src/components/conversation/conversation-profile.vue
  26. 13 15
      src/components/conversation/conversation-selected-list.vue
  27. 2 2
      src/components/conversation/conversationProfile/group-member-info.vue
  28. 11 11
      src/components/conversation/conversationProfile/group-profile.vue
  29. 5 5
      src/components/conversation/conversationProfile/user-profile.vue
  30. 29 24
      src/components/conversation/current-conversation.vue
  31. 8 4
      src/components/friend/friend-application/application-item.vue
  32. 274 214
      src/components/friend/friend-container.vue
  33. 94 45
      src/components/friend/friend-item.vue
  34. 64 8
      src/components/friend/friend-list.vue
  35. 3 3
      src/components/group-live/components/live-chat.vue
  36. 3 3
      src/components/group-live/components/live-pusher.vue
  37. 6 6
      src/components/group/create-group.vue
  38. 6 6
      src/components/group/group-list.vue
  39. 16 10
      src/components/layout/side-bar.vue
  40. 170 111
      src/components/message/merger-message/mergerMessage-item.vue
  41. 7 6
      src/components/message/merger-message/message-merger.vue
  42. 130 62
      src/components/message/merger-message/message-relay.vue
  43. 30 14
      src/components/message/message-bubble.vue
  44. 32 15
      src/components/message/message-elements/custom-element.vue
  45. 28 20
      src/components/message/message-elements/file-element.vue
  46. 1 1
      src/components/message/message-elements/group-system-notice-element.vue
  47. 7 7
      src/components/message/message-elements/group-tip-element.vue
  48. 4 3
      src/components/message/message-elements/image-element.vue
  49. 1 1
      src/components/message/message-elements/merger-element.vue
  50. 119 74
      src/components/message/message-elements/sound-element.vue
  51. 3 1
      src/components/message/message-elements/text-element.vue
  52. 6 4
      src/components/message/message-footer.vue
  53. 3 3
      src/components/message/message-header.vue
  54. 88 66
      src/components/message/message-item.vue
  55. 427 146
      src/components/message/message-send-box.vue
  56. 719 652
      src/components/message/trtc-calling/calling-index.vue
  57. 6 1
      src/components/message/trtc-calling/group-member-list.vue
  58. 11 10
      src/components/my-profile.vue
  59. 4 4
      src/components/profile-card.vue
  60. 217 33
      src/components/user/login.vue
  61. 4 0
      src/constant/call.js
  62. 90 0
      src/im/eventListeners.js
  63. 4 1
      src/layout/components/Sidebar/Logo.vue
  64. 10 14
      src/main.js
  65. 47 0
      src/router/index.js
  66. 1 1
      src/store/modules/blacklist.js
  67. 208 62
      src/store/modules/conversation.js
  68. 7 16
      src/store/modules/friend.js
  69. 3 3
      src/store/modules/group.js
  70. 7 5
      src/store/modules/imuser.js
  71. 27 3
      src/store/modules/permission.js
  72. 3 0
      src/store/modules/user.js
  73. 13 13
      src/tim.js
  74. 11 11
      src/trtc-calling.js
  75. 55 55
      src/utils/common.js
  76. 1 1
      src/utils/decodeText.js
  77. 215 1
      src/utils/emojiMap.js
  78. 37 4
      src/utils/im.js
  79. 38 0
      src/utils/openIM.js
  80. 1 0
      src/utils/request.js
  81. 23 23
      src/utils/rtc-client.js
  82. 172 0
      src/utils/trtc.js
  83. 225 0
      src/views/collection/index.vue
  84. 13 0
      src/views/components/drugReport/addDrugReport.vue
  85. 1 2
      src/views/components/drugReport/drugReportDetails.vue
  86. 140 140
      src/views/components/msg/followMsgDetails.vue
  87. 141 141
      src/views/components/msg/msgDetails.vue
  88. 37 9
      src/views/components/order/inquiryOrderDetails.vue
  89. 50 3
      src/views/components/order/inquiryOrderList.vue
  90. 3 0
      src/views/components/order/inquiryOrderReportDetails.vue
  91. 214 0
      src/views/diagnosis/index.vue
  92. 242 0
      src/views/doctor/prescribeAudit/index.vue
  93. 19 6
      src/views/his/prescribe/index.vue
  94. 744 0
      src/views/his/refuse/index.vue
  95. 233 66
      src/views/im/index.vue
  96. 17 5
      src/views/login.vue
  97. 21 1
      src/views/order/inquiryOrder/index.vue
  98. 38 30
      vue.config.js

+ 4 - 0
.env.development

@@ -4,6 +4,10 @@ VUE_APP_TITLE = 医生服务系统
 # 腾讯IM
 VUE_APP_IM_CONFIG = 1600089394
 
+#openIM
+VUE_APP_API_ADDR=https://im.muyi88.com/api
+VUE_APP_WS_ADDR=wss://im.muyi88.com/msg_gateway
+IS_OPENIM =  true
 
 # 公司名称
 VUE_APP_COMPANY_NAME =福州市木易华康医药有限公司

+ 4 - 1
.env.prod-hdt

@@ -3,7 +3,10 @@ VUE_APP_TITLE = 医生服务系统
 
 # 腾讯IM
 VUE_APP_IM_CONFIG = 1600093112
-
+#openIM
+VUE_APP_API_ADDR=https://im.muyi88.com/api
+VUE_APP_WS_ADDR=wss://im.muyi88.com/msg_gateway
+IS_OPENIM =  false
 
 # 公司名称
 VUE_APP_COMPANY_NAME =河北红德堂医药连锁有限公司保定第五十七分公司

+ 2 - 1
.env.prod-jnmy

@@ -12,7 +12,8 @@ VUE_APP_ICP_RECORD =蜀ICP备2024052643号-6
 VUE_APP_ICP_URL =https://beian.miit.gov.cn
 # 网站LOG
 VUE_APP_LOG_URL =@/assets/logo/jnmy.png
-
+#openIM
+IS_OPENIM =  false
 # 生产环境配置
 ENV = 'production'
 

+ 4 - 1
.env.prod-jzzx

@@ -3,7 +3,10 @@ VUE_APP_TITLE = 医生服务系统
 
 # 腾讯IM
 VUE_APP_IM_CONFIG = 1600092852
-
+#openIM
+VUE_APP_API_ADDR=https://im.muyi88.com/api
+VUE_APP_WS_ADDR=wss://im.muyi88.com/msg_gateway
+IS_OPENIM =  false
 
 # 公司名称
 VUE_APP_COMPANY_NAME =成都双流九州在线互联网医院有限公司

+ 4 - 2
.env.prod-myhk

@@ -3,8 +3,10 @@ VUE_APP_TITLE = 医生服务系统
 
 # 腾讯IM
 VUE_APP_IM_CONFIG = 1600089394
-
-
+#openIM
+VUE_APP_API_ADDR=https://im.muyi88.com/api
+VUE_APP_WS_ADDR=wss://im.muyi88.com/msg_gateway
+IS_OPENIM =  true
 # 公司名称
 VUE_APP_COMPANY_NAME =福州市木易华康医药有限公司
 # ICP备案号

+ 1 - 0
README.md

@@ -35,3 +35,4 @@ his_doctorui
 4.  [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
 5.  Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
 6.  Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
+7.  拉取代码执行npm install命令以后需要参照文档修改node_moudles/@openim/lib中的index.js及imdex.es.js文件,文档地址:https://github.com/openimsdk/openim-sdk-js-wasm/issues/73

+ 9 - 3
package.json

@@ -15,7 +15,8 @@
     "build:stage": "vue-cli-service build --mode staging",
     "build:prod-bjyjb": "vue-cli-service build --mode prod-bjyjb",
     "preview": "node build/index.js --preview",
-    "lint": "eslint --ext .js,.vue src"
+    "lint": "eslint --ext .js,.vue src",
+    "postinstall": "patch-package"
   },
   "husky": {
     "hooks": {
@@ -39,6 +40,7 @@
   ],
   "dependencies": {
     "@amap/amap-jsapi-loader": "^1.0.1",
+    "@openim/wasm-client-sdk": "^3.8.3-patch.10",
     "@riophae/vue-treeselect": "0.4.0",
     "axios": "0.21.0",
     "chart.js": "^2.9.4",
@@ -53,8 +55,10 @@
     "js-beautify": "1.13.0",
     "js-cookie": "2.2.1",
     "jsencrypt": "3.0.0-rc.1",
+    "livekit-client": "^2.15.4",
     "mta-h5-analysis": "^2.0.15",
     "nprogress": "0.2.0",
+    "patch-package": "^8.0.0",
     "quill": "1.3.7",
     "screenfull": "5.0.2",
     "sortablejs": "1.10.2",
@@ -90,10 +94,12 @@
     "sass": "^1.32.0",
     "sass-loader": "^10.1.0",
     "script-ext-html-webpack-plugin": "2.1.5",
+    "stylus": "^0.54.7",
+    "stylus-loader": "^3.0.2",
     "svg-sprite-loader": "5.1.1",
     "vue-template-compiler": "2.6.12",
-    "stylus": "^0.54.7",
-    "stylus-loader": "^3.0.2"
+    "worker-loader": "^3.0.8",
+    "worker-plugin": "^5.0.1"
   },
   "engines": {
     "node": ">=8.9",

+ 146 - 0
patches/@openim+wasm-client-sdk+3.8.3-patch.3.patch

@@ -0,0 +1,146 @@
+diff --git a/node_modules/@openim/wasm-client-sdk/lib/index.es.js b/node_modules/@openim/wasm-client-sdk/lib/index.es.js
+index daa048f..28bd430 100644
+--- a/node_modules/@openim/wasm-client-sdk/lib/index.es.js
++++ b/node_modules/@openim/wasm-client-sdk/lib/index.es.js
+@@ -475,42 +475,32 @@ function supportsModuleWorkers() {
+     }
+ }
+ function initWorker() {
+-    if (typeof window === 'undefined') {
+-        return;
+-    }
+-    // use for webpack 4
+-    // const IMWorker = require('worker-loader!./worker.js');
+-    // worker = new IMWorker.default();
+-    // use for webpack5+ or vite
+-    const isViteEnvironment = import.meta.url.includes('.vite/deps');
+-    const isNuxtEnvironment = import.meta.url.includes('_nuxt/node_modules');
+-    const isQuasarEnvironment = import.meta.url.includes('.q-cache');
+-    const isSupportModuleWorker = supportsModuleWorkers();
+-    let workerUrl = isSupportModuleWorker
+-        ? new URL('worker.js', import.meta.url)
+-        : new URL('worker-legacy.js', import.meta.url);
+-    if (isViteEnvironment) {
+-        workerUrl = workerUrl.href.replace('.vite/deps', '@openim/wasm-client-sdk/lib');
+-    }
+-    if (isNuxtEnvironment) {
+-        workerUrl = workerUrl.href.replace('.cache/vite/client/deps', '@openim/wasm-client-sdk/lib');
+-    }
+-    if (isQuasarEnvironment) {
+-        workerUrl = workerUrl.href.replace(/\.q-cache\/dev-spa\/[^/]+\/deps/, '@openim/wasm-client-sdk/lib');
+-    }
+-    worker = new Worker(workerUrl, {
+-        type: isSupportModuleWorker ? 'module' : 'classic',
+-    });
+-    // This is only required because Safari doesn't support nested
+-    // workers. This installs a handler that will proxy creating web
+-    // workers through the main thread
+-    initBackend(worker);
+-    rpc = new RPC({
+-        event: new RPCMessageEvent({
+-            currentEndpoint: worker,
+-            targetEndpoint: worker,
+-        }),
+-    });
++  if (typeof window === 'undefined') {
++    return;
++  }
++  // use for webpack 4
++  const IMWorker = require('worker-loader!./worker.js');
++  worker = new IMWorker.default();
++  // use for webpack5+ or vite
++  // const isViteEnvironment = import.meta.url.includes('.vite/deps');
++  // const isSupportModuleWorker = supportsModuleWorkers();
++  // const getWorkerUrl = (url) => url.href.replace('.vite/deps', 'open-im-sdk-wasm/lib');
++  // const workerUrl = isSupportModuleWorker
++  //     ? new URL('worker.js', import.meta.url)
++  //     : new URL('worker-legacy.js', import.meta.url);
++  // worker = new Worker(isViteEnvironment ? getWorkerUrl(workerUrl) : workerUrl, {
++  //     type: isSupportModuleWorker ? 'module' : 'classic',
++  // });
++  // This is only required because Safari doesn't support nested
++  // workers. This installs a handler that will proxy creating web
++  // workers through the main thread
++  initBackend(worker);
++  rpc = new RPC({
++    event: new RPCMessageEvent({
++      currentEndpoint: worker,
++      targetEndpoint: worker,
++    }),
++  });
+ }
+ function resetWorker() {
+     if (rpc) {
+diff --git a/node_modules/@openim/wasm-client-sdk/lib/index.js b/node_modules/@openim/wasm-client-sdk/lib/index.js
+index 61b6467..31253c3 100644
+--- a/node_modules/@openim/wasm-client-sdk/lib/index.js
++++ b/node_modules/@openim/wasm-client-sdk/lib/index.js
+@@ -479,42 +479,32 @@ function supportsModuleWorkers() {
+     }
+ }
+ function initWorker() {
+-    if (typeof window === 'undefined') {
+-        return;
+-    }
+-    // use for webpack 4
+-    // const IMWorker = require('worker-loader!./worker.js');
+-    // worker = new IMWorker.default();
+-    // use for webpack5+ or vite
+-    const isViteEnvironment = (typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.src || new URL('index.js', document.baseURI).href)).includes('.vite/deps');
+-    const isNuxtEnvironment = (typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.src || new URL('index.js', document.baseURI).href)).includes('_nuxt/node_modules');
+-    const isQuasarEnvironment = (typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.src || new URL('index.js', document.baseURI).href)).includes('.q-cache');
+-    const isSupportModuleWorker = supportsModuleWorkers();
+-    let workerUrl = isSupportModuleWorker
+-        ? new URL('worker.js', (typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.src || new URL('index.js', document.baseURI).href)))
+-        : new URL('worker-legacy.js', (typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.src || new URL('index.js', document.baseURI).href)));
+-    if (isViteEnvironment) {
+-        workerUrl = workerUrl.href.replace('.vite/deps', '@openim/wasm-client-sdk/lib');
+-    }
+-    if (isNuxtEnvironment) {
+-        workerUrl = workerUrl.href.replace('.cache/vite/client/deps', '@openim/wasm-client-sdk/lib');
+-    }
+-    if (isQuasarEnvironment) {
+-        workerUrl = workerUrl.href.replace(/\.q-cache\/dev-spa\/[^/]+\/deps/, '@openim/wasm-client-sdk/lib');
+-    }
+-    worker = new Worker(workerUrl, {
+-        type: isSupportModuleWorker ? 'module' : 'classic',
+-    });
+-    // This is only required because Safari doesn't support nested
+-    // workers. This installs a handler that will proxy creating web
+-    // workers through the main thread
+-    initBackend(worker);
+-    rpc = new RPC({
+-        event: new RPCMessageEvent({
+-            currentEndpoint: worker,
+-            targetEndpoint: worker,
+-        }),
+-    });
++  if (typeof window === 'undefined') {
++    return;
++  }
++  // use for webpack 4
++  const IMWorker = require('worker-loader!./worker.js');
++  worker = new IMWorker.default();
++  // use for webpack5+ or vite
++  // const isViteEnvironment = import.meta.url.includes('.vite/deps');
++  // const isSupportModuleWorker = supportsModuleWorkers();
++  // const getWorkerUrl = (url) => url.href.replace('.vite/deps', 'open-im-sdk-wasm/lib');
++  // const workerUrl = isSupportModuleWorker
++  //     ? new URL('worker.js', import.meta.url)
++  //     : new URL('worker-legacy.js', import.meta.url);
++  // worker = new Worker(isViteEnvironment ? getWorkerUrl(workerUrl) : workerUrl, {
++  //     type: isSupportModuleWorker ? 'module' : 'classic',
++  // });
++  // This is only required because Safari doesn't support nested
++  // workers. This installs a handler that will proxy creating web
++  // workers through the main thread
++  initBackend(worker);
++  rpc = new RPC({
++    event: new RPCMessageEvent({
++      currentEndpoint: worker,
++      targetEndpoint: worker,
++    }),
++  });
+ }
+ function resetWorker() {
+     if (rpc) {

+ 148 - 0
patches/open-im-sdk-wasm+3.8.0.patch

@@ -0,0 +1,148 @@
+diff --git a/node_modules/open-im-sdk-wasm/lib/index.es.js b/node_modules/open-im-sdk-wasm/lib/index.es.js
+index 64c4047..3400689 100644
+--- a/node_modules/open-im-sdk-wasm/lib/index.es.js
++++ b/node_modules/open-im-sdk-wasm/lib/index.es.js
+@@ -472,38 +472,32 @@ function supportsModuleWorkers() {
+     }
+ }
+ function initWorker() {
+-    if (typeof window === 'undefined') {
+-        return;
+-    }
+-    // use for webpack 4
+-    // const IMWorker = require('worker-loader!./worker.js');
+-    // worker = new IMWorker.default();
+-    // use for webpack5+ or vite
+-    const isViteEnvironment = import.meta.url.includes('.vite/deps');
+-    const isNuxtEnvironment = import.meta.url.includes('_nuxt/node_modules');
+-    const isSupportModuleWorker = supportsModuleWorkers();
+-    let workerUrl = isSupportModuleWorker
+-        ? new URL('worker.js', import.meta.url)
+-        : new URL('worker-legacy.js', import.meta.url);
+-    if (isViteEnvironment) {
+-        workerUrl = workerUrl.href.replace('.vite/deps', 'open-im-sdk-wasm/lib');
+-    }
+-    if (isNuxtEnvironment) {
+-        workerUrl = workerUrl.href.replace('.cache/vite/client/deps', 'open-im-sdk-wasm/lib');
+-    }
+-    worker = new Worker(workerUrl, {
+-        type: isSupportModuleWorker ? 'module' : 'classic',
+-    });
+-    // This is only required because Safari doesn't support nested
+-    // workers. This installs a handler that will proxy creating web
+-    // workers through the main thread
+-    initBackend(worker);
+-    rpc = new RPC({
+-        event: new RPCMessageEvent({
+-            currentEndpoint: worker,
+-            targetEndpoint: worker,
+-        }),
+-    });
++  if (typeof window === 'undefined') {
++    return;
++  }
++  // use for webpack 4
++  const IMWorker = require('worker-loader!./worker.js');
++  worker = new IMWorker.default();
++  // use for webpack5+ or vite
++  // const isViteEnvironment = import.meta.url.includes('.vite/deps');
++  // const isSupportModuleWorker = supportsModuleWorkers();
++  // const getWorkerUrl = (url) => url.href.replace('.vite/deps', 'open-im-sdk-wasm/lib');
++  // const workerUrl = isSupportModuleWorker
++  //     ? new URL('worker.js', import.meta.url)
++  //     : new URL('worker-legacy.js', import.meta.url);
++  // worker = new Worker(isViteEnvironment ? getWorkerUrl(workerUrl) : workerUrl, {
++  //     type: isSupportModuleWorker ? 'module' : 'classic',
++  // });
++  // This is only required because Safari doesn't support nested
++  // workers. This installs a handler that will proxy creating web
++  // workers through the main thread
++  initBackend(worker);
++  rpc = new RPC({
++    event: new RPCMessageEvent({
++      currentEndpoint: worker,
++      targetEndpoint: worker,
++    }),
++  });
+ }
+ function resetWorker() {
+     if (rpc) {
+diff --git a/node_modules/open-im-sdk-wasm/lib/index.js b/node_modules/open-im-sdk-wasm/lib/index.js
+index cd572cc..e0e7ea5 100644
+--- a/node_modules/open-im-sdk-wasm/lib/index.js
++++ b/node_modules/open-im-sdk-wasm/lib/index.js
+@@ -476,48 +476,32 @@ function supportsModuleWorkers() {
+     }
+ }
+ function initWorker() {
+-    if (typeof window === 'undefined') {
+-        return;
+-    }
+-    // use for webpack 4
+-    // const IMWorker = require('worker-loader!./worker.js');
+-    // worker = new IMWorker.default();
+-    // use for webpack5+ or vite
+-    const isViteEnvironment = (typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.src || new URL('index.js', document.baseURI).href)).includes('.vite/deps');
+-    const isNuxtEnvironment = (typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.src || new URL('index.js', document.baseURI).href)).includes('_nuxt/node_modules');
+-    const isSupportModuleWorker = supportsModuleWorkers();
+-    let workerUrl = isSupportModuleWorker
+-        ? new URL('worker.js', (typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.src || new URL('index.js', document.baseURI).href)))
+-        : new URL('worker-legacy.js', (typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.src || new URL('index.js', document.baseURI).href)));
+-    if (isViteEnvironment) {
+-        workerUrl = workerUrl.href.replace('.vite/deps', 'open-im-sdk-wasm/lib');
+-    }
+-    if (isNuxtEnvironment) {
+-        workerUrl = workerUrl.href.replace('.cache/vite/client/deps', 'open-im-sdk-wasm/lib');
+-    }
+-    worker = new Worker(workerUrl, {
+-        type: isSupportModuleWorker ? 'module' : 'classic',
+-    });
+-    // This is only required because Safari doesn't support nested
+-    // workers. This installs a handler that will proxy creating web
+-    // workers through the main thread
+-    initBackend(worker);
+-    rpc = new RPC({
+-        event: new RPCMessageEvent({
+-            currentEndpoint: worker,
+-            targetEndpoint: worker,
+-        }),
+-    });
+-}
+-function resetWorker() {
+-    if (rpc) {
+-        rpc.destroy();
+-        rpc = undefined;
+-    }
+-    if (worker) {
+-        worker.terminate();
+-        worker = undefined;
+-    }
++  if (typeof window === 'undefined') {
++    return;
++  }
++  // use for webpack 4
++  const IMWorker = require('worker-loader!./worker.js');
++  worker = new IMWorker.default();
++  // use for webpack5+ or vite
++  // const isViteEnvironment = import.meta.url.includes('.vite/deps');
++  // const isSupportModuleWorker = supportsModuleWorkers();
++  // const getWorkerUrl = (url) => url.href.replace('.vite/deps', 'open-im-sdk-wasm/lib');
++  // const workerUrl = isSupportModuleWorker
++  //     ? new URL('worker.js', import.meta.url)
++  //     : new URL('worker-legacy.js', import.meta.url);
++  // worker = new Worker(isViteEnvironment ? getWorkerUrl(workerUrl) : workerUrl, {
++  //     type: isSupportModuleWorker ? 'module' : 'classic',
++  // });
++  // This is only required because Safari doesn't support nested
++  // workers. This installs a handler that will proxy creating web
++  // workers through the main thread
++  initBackend(worker);
++  rpc = new RPC({
++    event: new RPCMessageEvent({
++      currentEndpoint: worker,
++      targetEndpoint: worker,
++    }),
++  });
+ }
+ initWorker();
+ function catchErrorHandle(error) {

+ 2 - 1
public/index.html

@@ -1,6 +1,7 @@
 <!DOCTYPE html>
 <html>
   <head>
+    <script src="/wasm_exec.js"></script>
     <meta charset="utf-8">
     <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
     <meta name="renderer" content="webkit">
@@ -205,6 +206,6 @@
 		    <div class="load_title">正在加载系统资源,请耐心等待</div>
         </div>
 	</div>
- 
+
   </body>
 </html>

BIN
public/openIM.wasm


BIN
public/sql-wasm.wasm


+ 561 - 0
public/wasm_exec.js

@@ -0,0 +1,561 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+"use strict";
+
+(() => {
+	const enosys = () => {
+		const err = new Error("not implemented");
+		err.code = "ENOSYS";
+		return err;
+	};
+
+	if (!globalThis.fs) {
+		let outputBuf = "";
+		globalThis.fs = {
+			constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
+			writeSync(fd, buf) {
+				outputBuf += decoder.decode(buf);
+				const nl = outputBuf.lastIndexOf("\n");
+				if (nl != -1) {
+					console.log(outputBuf.substring(0, nl));
+					outputBuf = outputBuf.substring(nl + 1);
+				}
+				return buf.length;
+			},
+			write(fd, buf, offset, length, position, callback) {
+				if (offset !== 0 || length !== buf.length || position !== null) {
+					callback(enosys());
+					return;
+				}
+				const n = this.writeSync(fd, buf);
+				callback(null, n);
+			},
+			chmod(path, mode, callback) { callback(enosys()); },
+			chown(path, uid, gid, callback) { callback(enosys()); },
+			close(fd, callback) { callback(enosys()); },
+			fchmod(fd, mode, callback) { callback(enosys()); },
+			fchown(fd, uid, gid, callback) { callback(enosys()); },
+			fstat(fd, callback) { callback(enosys()); },
+			fsync(fd, callback) { callback(null); },
+			ftruncate(fd, length, callback) { callback(enosys()); },
+			lchown(path, uid, gid, callback) { callback(enosys()); },
+			link(path, link, callback) { callback(enosys()); },
+			lstat(path, callback) { callback(enosys()); },
+			mkdir(path, perm, callback) { callback(enosys()); },
+			open(path, flags, mode, callback) { callback(enosys()); },
+			read(fd, buffer, offset, length, position, callback) { callback(enosys()); },
+			readdir(path, callback) { callback(enosys()); },
+			readlink(path, callback) { callback(enosys()); },
+			rename(from, to, callback) { callback(enosys()); },
+			rmdir(path, callback) { callback(enosys()); },
+			stat(path, callback) { callback(enosys()); },
+			symlink(path, link, callback) { callback(enosys()); },
+			truncate(path, length, callback) { callback(enosys()); },
+			unlink(path, callback) { callback(enosys()); },
+			utimes(path, atime, mtime, callback) { callback(enosys()); },
+		};
+	}
+
+	if (!globalThis.process) {
+		globalThis.process = {
+			getuid() { return -1; },
+			getgid() { return -1; },
+			geteuid() { return -1; },
+			getegid() { return -1; },
+			getgroups() { throw enosys(); },
+			pid: -1,
+			ppid: -1,
+			umask() { throw enosys(); },
+			cwd() { throw enosys(); },
+			chdir() { throw enosys(); },
+		}
+	}
+
+	if (!globalThis.crypto) {
+		throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)");
+	}
+
+	if (!globalThis.performance) {
+		throw new Error("globalThis.performance is not available, polyfill required (performance.now only)");
+	}
+
+	if (!globalThis.TextEncoder) {
+		throw new Error("globalThis.TextEncoder is not available, polyfill required");
+	}
+
+	if (!globalThis.TextDecoder) {
+		throw new Error("globalThis.TextDecoder is not available, polyfill required");
+	}
+
+	const encoder = new TextEncoder("utf-8");
+	const decoder = new TextDecoder("utf-8");
+
+	globalThis.Go = class {
+		constructor() {
+			this.argv = ["js"];
+			this.env = {};
+			this.exit = (code) => {
+				if (code !== 0) {
+					console.warn("exit code:", code);
+				}
+			};
+			this._exitPromise = new Promise((resolve) => {
+				this._resolveExitPromise = resolve;
+			});
+			this._pendingEvent = null;
+			this._scheduledTimeouts = new Map();
+			this._nextCallbackTimeoutID = 1;
+
+			const setInt64 = (addr, v) => {
+				this.mem.setUint32(addr + 0, v, true);
+				this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true);
+			}
+
+			const setInt32 = (addr, v) => {
+				this.mem.setUint32(addr + 0, v, true);
+			}
+
+			const getInt64 = (addr) => {
+				const low = this.mem.getUint32(addr + 0, true);
+				const high = this.mem.getInt32(addr + 4, true);
+				return low + high * 4294967296;
+			}
+
+			const loadValue = (addr) => {
+				const f = this.mem.getFloat64(addr, true);
+				if (f === 0) {
+					return undefined;
+				}
+				if (!isNaN(f)) {
+					return f;
+				}
+
+				const id = this.mem.getUint32(addr, true);
+				return this._values[id];
+			}
+
+			const storeValue = (addr, v) => {
+				const nanHead = 0x7FF80000;
+
+				if (typeof v === "number" && v !== 0) {
+					if (isNaN(v)) {
+						this.mem.setUint32(addr + 4, nanHead, true);
+						this.mem.setUint32(addr, 0, true);
+						return;
+					}
+					this.mem.setFloat64(addr, v, true);
+					return;
+				}
+
+				if (v === undefined) {
+					this.mem.setFloat64(addr, 0, true);
+					return;
+				}
+
+				let id = this._ids.get(v);
+				if (id === undefined) {
+					id = this._idPool.pop();
+					if (id === undefined) {
+						id = this._values.length;
+					}
+					this._values[id] = v;
+					this._goRefCounts[id] = 0;
+					this._ids.set(v, id);
+				}
+				this._goRefCounts[id]++;
+				let typeFlag = 0;
+				switch (typeof v) {
+					case "object":
+						if (v !== null) {
+							typeFlag = 1;
+						}
+						break;
+					case "string":
+						typeFlag = 2;
+						break;
+					case "symbol":
+						typeFlag = 3;
+						break;
+					case "function":
+						typeFlag = 4;
+						break;
+				}
+				this.mem.setUint32(addr + 4, nanHead | typeFlag, true);
+				this.mem.setUint32(addr, id, true);
+			}
+
+			const loadSlice = (addr) => {
+				const array = getInt64(addr + 0);
+				const len = getInt64(addr + 8);
+				return new Uint8Array(this._inst.exports.mem.buffer, array, len);
+			}
+
+			const loadSliceOfValues = (addr) => {
+				const array = getInt64(addr + 0);
+				const len = getInt64(addr + 8);
+				const a = new Array(len);
+				for (let i = 0; i < len; i++) {
+					a[i] = loadValue(array + i * 8);
+				}
+				return a;
+			}
+
+			const loadString = (addr) => {
+				const saddr = getInt64(addr + 0);
+				const len = getInt64(addr + 8);
+				return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
+			}
+
+			const timeOrigin = Date.now() - performance.now();
+			this.importObject = {
+				_gotest: {
+					add: (a, b) => a + b,
+				},
+				gojs: {
+					// Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
+					// may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported
+					// function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).
+					// This changes the SP, thus we have to update the SP used by the imported function.
+
+					// func wasmExit(code int32)
+					"runtime.wasmExit": (sp) => {
+						sp >>>= 0;
+						const code = this.mem.getInt32(sp + 8, true);
+						this.exited = true;
+						delete this._inst;
+						delete this._values;
+						delete this._goRefCounts;
+						delete this._ids;
+						delete this._idPool;
+						this.exit(code);
+					},
+
+					// func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
+					"runtime.wasmWrite": (sp) => {
+						sp >>>= 0;
+						const fd = getInt64(sp + 8);
+						const p = getInt64(sp + 16);
+						const n = this.mem.getInt32(sp + 24, true);
+						fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
+					},
+
+					// func resetMemoryDataView()
+					"runtime.resetMemoryDataView": (sp) => {
+						sp >>>= 0;
+						this.mem = new DataView(this._inst.exports.mem.buffer);
+					},
+
+					// func nanotime1() int64
+					"runtime.nanotime1": (sp) => {
+						sp >>>= 0;
+						setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
+					},
+
+					// func walltime() (sec int64, nsec int32)
+					"runtime.walltime": (sp) => {
+						sp >>>= 0;
+						const msec = (new Date).getTime();
+						setInt64(sp + 8, msec / 1000);
+						this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true);
+					},
+
+					// func scheduleTimeoutEvent(delay int64) int32
+					"runtime.scheduleTimeoutEvent": (sp) => {
+						sp >>>= 0;
+						const id = this._nextCallbackTimeoutID;
+						this._nextCallbackTimeoutID++;
+						this._scheduledTimeouts.set(id, setTimeout(
+							() => {
+								this._resume();
+								while (this._scheduledTimeouts.has(id)) {
+									// for some reason Go failed to register the timeout event, log and try again
+									// (temporary workaround for https://github.com/golang/go/issues/28975)
+									console.warn("scheduleTimeoutEvent: missed timeout event");
+									this._resume();
+								}
+							},
+							getInt64(sp + 8),
+						));
+						this.mem.setInt32(sp + 16, id, true);
+					},
+
+					// func clearTimeoutEvent(id int32)
+					"runtime.clearTimeoutEvent": (sp) => {
+						sp >>>= 0;
+						const id = this.mem.getInt32(sp + 8, true);
+						clearTimeout(this._scheduledTimeouts.get(id));
+						this._scheduledTimeouts.delete(id);
+					},
+
+					// func getRandomData(r []byte)
+					"runtime.getRandomData": (sp) => {
+						sp >>>= 0;
+						crypto.getRandomValues(loadSlice(sp + 8));
+					},
+
+					// func finalizeRef(v ref)
+					"syscall/js.finalizeRef": (sp) => {
+						sp >>>= 0;
+						const id = this.mem.getUint32(sp + 8, true);
+						this._goRefCounts[id]--;
+						if (this._goRefCounts[id] === 0) {
+							const v = this._values[id];
+							this._values[id] = null;
+							this._ids.delete(v);
+							this._idPool.push(id);
+						}
+					},
+
+					// func stringVal(value string) ref
+					"syscall/js.stringVal": (sp) => {
+						sp >>>= 0;
+						storeValue(sp + 24, loadString(sp + 8));
+					},
+
+					// func valueGet(v ref, p string) ref
+					"syscall/js.valueGet": (sp) => {
+						sp >>>= 0;
+						const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16));
+						sp = this._inst.exports.getsp() >>> 0; // see comment above
+						storeValue(sp + 32, result);
+					},
+
+					// func valueSet(v ref, p string, x ref)
+					"syscall/js.valueSet": (sp) => {
+						sp >>>= 0;
+						Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
+					},
+
+					// func valueDelete(v ref, p string)
+					"syscall/js.valueDelete": (sp) => {
+						sp >>>= 0;
+						Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16));
+					},
+
+					// func valueIndex(v ref, i int) ref
+					"syscall/js.valueIndex": (sp) => {
+						sp >>>= 0;
+						storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
+					},
+
+					// valueSetIndex(v ref, i int, x ref)
+					"syscall/js.valueSetIndex": (sp) => {
+						sp >>>= 0;
+						Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
+					},
+
+					// func valueCall(v ref, m string, args []ref) (ref, bool)
+					"syscall/js.valueCall": (sp) => {
+						sp >>>= 0;
+						try {
+							const v = loadValue(sp + 8);
+							const m = Reflect.get(v, loadString(sp + 16));
+							const args = loadSliceOfValues(sp + 32);
+							const result = Reflect.apply(m, v, args);
+							sp = this._inst.exports.getsp() >>> 0; // see comment above
+							storeValue(sp + 56, result);
+							this.mem.setUint8(sp + 64, 1);
+						} catch (err) {
+							sp = this._inst.exports.getsp() >>> 0; // see comment above
+							storeValue(sp + 56, err);
+							this.mem.setUint8(sp + 64, 0);
+						}
+					},
+
+					// func valueInvoke(v ref, args []ref) (ref, bool)
+					"syscall/js.valueInvoke": (sp) => {
+						sp >>>= 0;
+						try {
+							const v = loadValue(sp + 8);
+							const args = loadSliceOfValues(sp + 16);
+							const result = Reflect.apply(v, undefined, args);
+							sp = this._inst.exports.getsp() >>> 0; // see comment above
+							storeValue(sp + 40, result);
+							this.mem.setUint8(sp + 48, 1);
+						} catch (err) {
+							sp = this._inst.exports.getsp() >>> 0; // see comment above
+							storeValue(sp + 40, err);
+							this.mem.setUint8(sp + 48, 0);
+						}
+					},
+
+					// func valueNew(v ref, args []ref) (ref, bool)
+					"syscall/js.valueNew": (sp) => {
+						sp >>>= 0;
+						try {
+							const v = loadValue(sp + 8);
+							const args = loadSliceOfValues(sp + 16);
+							const result = Reflect.construct(v, args);
+							sp = this._inst.exports.getsp() >>> 0; // see comment above
+							storeValue(sp + 40, result);
+							this.mem.setUint8(sp + 48, 1);
+						} catch (err) {
+							sp = this._inst.exports.getsp() >>> 0; // see comment above
+							storeValue(sp + 40, err);
+							this.mem.setUint8(sp + 48, 0);
+						}
+					},
+
+					// func valueLength(v ref) int
+					"syscall/js.valueLength": (sp) => {
+						sp >>>= 0;
+						setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
+					},
+
+					// valuePrepareString(v ref) (ref, int)
+					"syscall/js.valuePrepareString": (sp) => {
+						sp >>>= 0;
+						const str = encoder.encode(String(loadValue(sp + 8)));
+						storeValue(sp + 16, str);
+						setInt64(sp + 24, str.length);
+					},
+
+					// valueLoadString(v ref, b []byte)
+					"syscall/js.valueLoadString": (sp) => {
+						sp >>>= 0;
+						const str = loadValue(sp + 8);
+						loadSlice(sp + 16).set(str);
+					},
+
+					// func valueInstanceOf(v ref, t ref) bool
+					"syscall/js.valueInstanceOf": (sp) => {
+						sp >>>= 0;
+						this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0);
+					},
+
+					// func copyBytesToGo(dst []byte, src ref) (int, bool)
+					"syscall/js.copyBytesToGo": (sp) => {
+						sp >>>= 0;
+						const dst = loadSlice(sp + 8);
+						const src = loadValue(sp + 32);
+						if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) {
+							this.mem.setUint8(sp + 48, 0);
+							return;
+						}
+						const toCopy = src.subarray(0, dst.length);
+						dst.set(toCopy);
+						setInt64(sp + 40, toCopy.length);
+						this.mem.setUint8(sp + 48, 1);
+					},
+
+					// func copyBytesToJS(dst ref, src []byte) (int, bool)
+					"syscall/js.copyBytesToJS": (sp) => {
+						sp >>>= 0;
+						const dst = loadValue(sp + 8);
+						const src = loadSlice(sp + 16);
+						if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) {
+							this.mem.setUint8(sp + 48, 0);
+							return;
+						}
+						const toCopy = src.subarray(0, dst.length);
+						dst.set(toCopy);
+						setInt64(sp + 40, toCopy.length);
+						this.mem.setUint8(sp + 48, 1);
+					},
+
+					"debug": (value) => {
+						console.log(value);
+					},
+				}
+			};
+		}
+
+		async run(instance) {
+			if (!(instance instanceof WebAssembly.Instance)) {
+				throw new Error("Go.run: WebAssembly.Instance expected");
+			}
+			this._inst = instance;
+			this.mem = new DataView(this._inst.exports.mem.buffer);
+			this._values = [ // JS values that Go currently has references to, indexed by reference id
+				NaN,
+				0,
+				null,
+				true,
+				false,
+				globalThis,
+				this,
+			];
+			this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id
+			this._ids = new Map([ // mapping from JS values to reference ids
+				[0, 1],
+				[null, 2],
+				[true, 3],
+				[false, 4],
+				[globalThis, 5],
+				[this, 6],
+			]);
+			this._idPool = [];   // unused ids that have been garbage collected
+			this.exited = false; // whether the Go program has exited
+
+			// Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
+			let offset = 4096;
+
+			const strPtr = (str) => {
+				const ptr = offset;
+				const bytes = encoder.encode(str + "\0");
+				new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes);
+				offset += bytes.length;
+				if (offset % 8 !== 0) {
+					offset += 8 - (offset % 8);
+				}
+				return ptr;
+			};
+
+			const argc = this.argv.length;
+
+			const argvPtrs = [];
+			this.argv.forEach((arg) => {
+				argvPtrs.push(strPtr(arg));
+			});
+			argvPtrs.push(0);
+
+			const keys = Object.keys(this.env).sort();
+			keys.forEach((key) => {
+				argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
+			});
+			argvPtrs.push(0);
+
+			const argv = offset;
+			argvPtrs.forEach((ptr) => {
+				this.mem.setUint32(offset, ptr, true);
+				this.mem.setUint32(offset + 4, 0, true);
+				offset += 8;
+			});
+
+			// The linker guarantees global data starts from at least wasmMinDataAddr.
+			// Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr.
+			const wasmMinDataAddr = 4096 + 8192;
+			if (offset >= wasmMinDataAddr) {
+				throw new Error("total length of command line and environment variables exceeds limit");
+			}
+
+			this._inst.exports.run(argc, argv);
+			if (this.exited) {
+				this._resolveExitPromise();
+			}
+			await this._exitPromise;
+		}
+
+		_resume() {
+			if (this.exited) {
+				throw new Error("Go program has already exited");
+			}
+			this._inst.exports.resume();
+			if (this.exited) {
+				this._resolveExitPromise();
+			}
+		}
+
+		_makeFuncWrapper(id) {
+			const go = this;
+			return function () {
+				const event = { id: id, this: this, args: arguments };
+				go._pendingEvent = event;
+				go._resume();
+				return event.result;
+			};
+		}
+	}
+})();

+ 26 - 0
src/api/collection.js

@@ -0,0 +1,26 @@
+import request from '@/utils/request'
+
+//查询初诊单列表
+export function list(query) {
+  return request({
+    url: '/app/collection/getCollectionList',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询用户采集详情
+export function detail(id) {
+  return request({
+    url: '/app/collection/detail/' + id,
+    method: 'get'
+  })
+}
+
+export function doctorConfirm(data) {
+  return request({
+    url: '/app/collection/doctorConfirm',
+    method: 'put',
+    data: data
+  })
+}

+ 27 - 0
src/api/diagnosis.js

@@ -0,0 +1,27 @@
+import request from '@/utils/request'
+
+
+//查询初诊单列表
+export function getDiagnosisList(query) {
+  return request({
+    url: '/app/diagnosis/getDiagnosisList',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询初诊单详情
+export function detail(id) {
+  return request({
+    url: '/app/diagnosis/' + id,
+    method: 'get'
+  })
+}
+
+export function fill(data) {
+  return request({
+    url: '/app/diagnosis/fill',
+    method: 'put',
+    data: data
+  })
+}

+ 7 - 0
src/api/doctor.js

@@ -14,6 +14,13 @@ export function login(account, password,type,code,uuid) {
     data: data
   })
 }
+export function accountCheck(userId) {
+  return request({
+    url: '/app/doctor/accountCheck',
+    method: 'post',
+    data: { userId: userId }
+  })
+}
 export function getDoctorDetails() {
   return request({
     url: '/app/doctor/getDoctorDetails',

+ 22 - 1
src/api/inquiryOrder.js

@@ -9,6 +9,13 @@ export function getInquiryOrderList(query) {
     params: query
   })
 }
+export function getInquiryOrderListByUser(query) {
+  return request({
+    url: '/app/inquiryOrder/getInquiryOrderListByUser',
+    method: 'get',
+    params: query
+  })
+}
 export function getCompanyList() {
   return request({
     url: '/app/inquiryOrder/getCompanyList',
@@ -52,7 +59,13 @@ export function getInquiryOrderReport(orderId) {
     }
   })
 }
-
+export function exportOrder(query) {
+  return request({
+    url: '/app/inquiryOrder/export',
+    method: 'get',
+    params: query
+  })
+}
 //接单
 export function receiveOrder(data) {
   return request({
@@ -119,5 +132,13 @@ export function getInquiryOrderMsgList(query) {
   })
 }
 
+export function closeOrder(orderId) {
+  return request({
+    url: '/app/inquiryOrder/closeOrder',
+    method: 'post',
+    data: orderId
+  })
+}
+
 
 

+ 9 - 0
src/api/prescribe.js

@@ -79,6 +79,15 @@ export function getPrescribeImg(param) {
   })
 }
 
+// 审核处方
+export function auditPrescribe(data) {
+  return request({
+    url: '/app/prescribe/audit',
+    method: 'post',
+    data: data
+  })
+}
+
 
 
 

BIN
src/assets/audio/beCalled.mp3


+ 48 - 0
src/assets/doctor.svg

@@ -0,0 +1,48 @@
+<svg width="60" height="61" viewBox="0 0 60 61" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g id="&#229;&#140;&#187;&#231;&#148;&#159;">
+<path id="Vector" d="M30 60C13.43 60 0 46.57 0 30C0 13.43 13.43 0 30 0C46.57 0 60 13.43 60 30C60 46.57 46.57 60 30 60ZM30 4C15.64 4 4 15.64 4 30C4 44.36 15.64 56 30 56C44.36 56 56 44.36 56 30C56 15.64 44.36 4 30 4Z" fill="url(#paint0_linear_2111_3396)"/>
+<g id="Vector_2">
+<mask id="path-2-inside-1_2111_3396" fill="white">
+<path d="M59 30C59 13.98 46.02 1 30 1C13.98 1 1 13.98 1 30C1 46.02 13.98 59 30 59V60C13.43 60 0 46.57 0 30C0 13.43 13.43 0 30 0C46.57 0 60 13.43 60 30C60 46.57 46.57 60 30 60V59C46.02 59 59 46.02 59 30Z"/>
+</mask>
+<path d="M59 30C59 13.98 46.02 1 30 1C13.98 1 1 13.98 1 30C1 46.02 13.98 59 30 59V60C13.43 60 0 46.57 0 30C0 13.43 13.43 0 30 0C46.57 0 60 13.43 60 30C60 46.57 46.57 60 30 60V59C46.02 59 59 46.02 59 30Z" fill="url(#paint1_linear_2111_3396)"/>
+<path d="M30 59H29.5V60H30H30.5V59H30ZM59 30H60C60 13.4277 46.5723 0 30 0V1V2C45.4677 2 58 14.5323 58 30H59ZM30 1V0C13.4277 0 0 13.4277 0 30H1H2C2 14.5323 14.5323 2 30 2V1ZM1 30H0C0 46.5723 13.4277 60 30 60V59V58C14.5323 58 2 45.4677 2 30H1ZM30 60V59C13.9823 59 1 46.0177 1 30H0H-1C-1 47.1223 12.8777 61 30 61V60ZM0 30H1C1 13.9823 13.9823 1 30 1V0V-1C12.8777 -1 -1 12.8777 -1 30H0ZM30 0V1C46.0177 1 59 13.9823 59 30H60H61C61 12.8777 47.1223 -1 30 -1V0ZM60 30H59C59 46.0177 46.0177 59 30 59V60V61C47.1223 61 61 47.1223 61 30H60ZM30 59V60C46.5723 60 60 46.5723 60 30H59H58C58 45.4677 45.4677 58 30 58V59Z" fill="url(#paint2_linear_2111_3396)" mask="url(#path-2-inside-1_2111_3396)"/>
+</g>
+<path id="Vector_3" d="M30 58C14.54 58 2 45.46 2 30C2 14.54 14.54 2 30 2C45.46 2 58 14.54 58 30C58 45.46 45.46 58 30 58ZM30 4C15.64 4 4 15.64 4 30C4 44.36 15.64 56 30 56C44.36 56 56 44.36 56 30C56 15.64 44.36 4 30 4Z" fill="url(#paint3_linear_2111_3396)"/>
+<path id="Vector_4" d="M57 30C57 15.09 44.91 3 30 3C15.09 3 3 15.09 3 30C3 44.91 15.09 57 30 57V58C14.54 58 2 45.46 2 30C2 14.54 14.54 2 30 2C45.46 2 58 14.54 58 30C58 45.46 45.46 58 30 58V57C44.91 57 57 44.91 57 30Z" fill="url(#paint4_linear_2111_3396)"/>
+<path id="Vector_5" d="M18.8499 46H39.0899C39.9999 46 40.8299 46.49 41.2699 47.28L41.3499 47.44L45.5499 56.44C46.3199 58.1 45.1099 60 43.2799 60H15.3699C13.6099 60 12.4099 58.24 13.0399 56.6L16.5099 47.6C16.8799 46.64 17.8099 46 18.8399 46H18.8499Z" fill="url(#paint5_linear_2111_3396)" stroke="url(#paint6_linear_2111_3396)"/>
+<path id="Vector_6" d="M24.6502 51.9299C24.4202 52.2899 24.1602 52.5899 23.8802 52.8399L23.2402 52.2499C23.8202 51.7299 24.2402 51.0999 24.4902 50.3599L25.2602 50.5799C25.1902 50.7899 25.1202 50.9799 25.0402 51.1599H28.4802V51.9299H26.6202V52.3299C26.6002 52.5499 26.5802 52.7599 26.5602 52.9599H29.0202V53.7599H26.3902C26.2602 54.1699 26.0802 54.5299 25.8602 54.8399C25.4102 55.4099 24.6802 55.8499 23.6602 56.1699L23.2002 55.4499C24.1902 55.1399 24.8602 54.7499 25.2302 54.2799C25.3402 54.1199 25.4402 53.9399 25.5102 53.7599H23.2502V52.9599H25.7302C25.7702 52.7499 25.7802 52.5499 25.7902 52.3299V51.9299H24.6502ZM26.7002 54.0999C27.5402 54.4599 28.3002 54.9099 29.0002 55.4399L28.5302 56.1599C27.7902 55.5499 27.0402 55.0699 26.2602 54.7199L26.6902 54.0999H26.7002ZM21.9502 49.4199H29.1602V50.2199H22.7702V56.2999H29.4502V57.1199H21.9502V49.4199ZM32.2602 51.5399C31.9002 52.3499 31.4502 53.0199 30.9002 53.5599L30.3402 52.8499C31.1502 51.9999 31.7002 50.8399 31.9902 49.3899L32.8602 49.5399C32.7702 49.9499 32.6702 50.3399 32.5602 50.6999H34.2302V49.1899H35.0802V50.6999H37.9702V51.5299H35.0802V53.3899H37.7902V54.2199H35.0802V56.2699H38.5502V57.1199H30.5702V56.2699H34.2302V54.2199H31.6602V53.3899H34.2302V51.5299H32.2502L32.2602 51.5399Z" fill="#D38307"/>
+</g>
+<defs>
+<linearGradient id="paint0_linear_2111_3396" x1="21.91" y1="1.82" x2="37.95" y2="57.72" gradientUnits="userSpaceOnUse">
+<stop stop-color="#FFC737"/>
+<stop offset="0.5" stop-color="#FFDD64"/>
+<stop offset="0.98" stop-color="#CF7C00"/>
+</linearGradient>
+<linearGradient id="paint1_linear_2111_3396" x1="13.9568" y1="4.10072" x2="30" y2="60" gradientUnits="userSpaceOnUse">
+<stop stop-color="#FFC737"/>
+<stop offset="0.501018" stop-color="#FFDD64"/>
+<stop offset="0.979167" stop-color="#CF7C00"/>
+</linearGradient>
+<linearGradient id="paint2_linear_2111_3396" x1="30" y1="0" x2="30" y2="60" gradientUnits="userSpaceOnUse">
+<stop stop-color="#FFF5DA"/>
+<stop offset="0.245067" stop-color="white" stop-opacity="0"/>
+</linearGradient>
+<linearGradient id="paint3_linear_2111_3396" x1="21.91" y1="1.82" x2="37.95" y2="57.72" gradientUnits="userSpaceOnUse">
+<stop stop-color="#FFB800"/>
+<stop offset="1" stop-color="#FFE195"/>
+</linearGradient>
+<linearGradient id="paint4_linear_2111_3396" x1="30" y1="2" x2="30" y2="58" gradientUnits="userSpaceOnUse">
+<stop stop-color="#FFD874"/>
+<stop offset="1" stop-color="#FFEDBF"/>
+</linearGradient>
+<linearGradient id="paint5_linear_2111_3396" x1="15.8799" y1="48.99" x2="55.5699" y2="63" gradientUnits="userSpaceOnUse">
+<stop stop-color="#FFE6A8"/>
+<stop offset="1" stop-color="#FFBA08"/>
+</linearGradient>
+<linearGradient id="paint6_linear_2111_3396" x1="34.9999" y1="39.11" x2="30.3899" y2="54.08" gradientUnits="userSpaceOnUse">
+<stop stop-color="white" stop-opacity="0.55"/>
+<stop offset="1" stop-color="white" stop-opacity="0"/>
+</linearGradient>
+</defs>
+</svg>

Різницю між файлами не показано, бо вона завелика
+ 14 - 0
src/assets/guanjia.svg


+ 19 - 6
src/components/avatar.vue

@@ -6,18 +6,31 @@
 
 <script>
 import systemAvatar from '@/assets/image/system.png'
+import { getOpenIM } from '@/utils/openIM';
 export default {
+  data:{
+    return:{
+      OpenIM: null,
+    }
+  },
   props: {
     src: String,
     type: {
       type: String,
-      default: 'C2C'
+      default: '1'
     },
     shape: {
       type: String,
       default: 'circle'
     }
   },
+  async created() {
+    try {
+      this.OpenIM = getOpenIM()
+    } catch (e) {
+      console.error('OpenIM 获取失败', e)
+    }
+  },
   computed: {
     avatarSrc: function () {
       let src = this.src
@@ -29,13 +42,13 @@ export default {
     },
     defaultSrc: function () {
       switch(this.type) {
-        case 'C2C':
+        case 1:
           // 个人头像
           return 'https://imgcache.qq.com/open/qcloud/video/act/webim-avatar/avatar-2.png'
-        case 'GROUP':
+        case 3:
           // 群默认头像
           return 'https://imgcache.qq.com/open/qcloud/video/act/webim-avatar/avatar-3.png'
-        case this.TIM.TYPES.CONV_SYSTEM:
+        case 4:
           return systemAvatar
         default:
           // 默认头像
@@ -47,13 +60,13 @@ export default {
 </script>
 
 <style lang="stylus" scoped>
-.avatar 
+.avatar
   background-color $first
   text-align center
   width 100%
   height 100%
   overflow hidden
-  img 
+  img
     width 100%
     height 100%
 .shape-circle

+ 180 - 91
src/components/conversation/conversation-item.vue

@@ -8,21 +8,24 @@
         <span class="tim-icon-close" title="删除会话" @click="deleteConversation"></span>
       </div>
       <div class="warp">
-        <avatar :src="avatar" :type="conversation.type" />
+        <div class="avatar-wrapper">
+          <avatar :src="avatar" :type="conversation.faceURL" class="avatar" />
+          <img v-if="avatarBorder" :src="avatarBorder" class="avatar-border" />
+        </div>
         <div class="c-content">
           <div class="row-1">
             <div class="name">
               <div class="text-ellipsis">
-                <span :title="conversation.userProfile.nick || conversation.userProfile.userID"
-                  v-if="conversation.type ===  TIM.TYPES.CONV_C2C"
-                  >{{conversation.remark || conversation.userProfile.nick || conversation.userProfile.userID}}
+                <span :title="conversation.showName || conversation.userID"
+                      v-if="conversation.conversationType ===  1"
+                >{{conversation.remark || conversation.showName || conversation.userID}}
                 </span>
-                <span :title="conversation.groupProfile.name || conversation.groupProfile.groupID"
+<!--                <span :title="conversation.groupProfile.name || conversation.groupProfile.groupID"
                   v-else-if="conversation.type ===  TIM.TYPES.CONV_GROUP"
                   >{{conversation.groupProfile.name || conversation.groupProfile.groupID}}
-                </span>
+                </span>-->
                 <span
-                  v-else-if="conversation.type === TIM.TYPES.CONV_SYSTEM"
+                  v-else-if="conversation.conversationType === 4"
                   >系统通知
                 </span>
               </div>
@@ -35,10 +38,10 @@
           </div>
           <div class="row-2">
             <div class="summary">
-              <div v-if="conversation.lastMessage" class="text-ellipsis">
-                <span class="remind"  v-if="hasMessageAtMe">{{messageAtMeText}}</span>
-                <span class="text" :title="conversation.lastMessage.messageForShow">
-                  {{messageForShow}}
+              <div v-if="conversation.conversationID" class="text-ellipsis">
+                <span class="remind" v-if="hasMessageAtMe">{{ messageAtMeText }}</span>
+                <span class="text" :title="messageForShow">
+                  {{ messageForShow }}
                 </span>
               </div>
             </div>
@@ -55,16 +58,30 @@
 <script>
 import { mapGetters, mapState } from 'vuex'
 import { isToday, getDate, getTime } from '../../utils/date'
+import { getOpenIM } from '@/utils/openIM';
+import doctorBorder from '@/assets/doctor.svg'
+import guanjiaBorder from '@/assets/guanjia.svg'
+
 export default {
   name: 'conversation-item',
   props: ['conversation'],
   data() {
     return {
       popoverVisible: false,
-      showMessageAtMe_text:''
+      showMessageAtMe_text:'',
+      OpenIM: null,
+      lastMsg : {},
     }
   },
   computed: {
+    avatarBorder() {
+      if (this.conversation.userID?.startsWith('D')) {
+        return doctorBorder
+      } else if (this.conversation.userID?.startsWith('C')) {
+        return guanjiaBorder
+      }
+      return null
+    },
     hasMessageAtMe() {
       return (
               this.currentConversation.conversationID !==
@@ -75,13 +92,13 @@ export default {
       let text = ''
       if (this.conversation.groupAtInfoList.length > 0) {
         this.conversation.groupAtInfoList.forEach((item) => {
-          if (item.atTypeArray[0] === this.TIM.TYPES.CONV_AT_ME) {
+          if (item.atTypeArray[0] === 1) {
             text.indexOf('[@所有人]') !== -1 ? text = '[@所有人][有人@我]' : text = '[有人@我]'
           }
-          if (item.atTypeArray[0] === this.TIM.TYPES.CONV_AT_ALL) {
+          if (item.atTypeArray[0] === 2) {
             text.indexOf('[有人@我]') !== -1 ? text = '[有人@我][@所有人]' : text = '[@所有人]'
           }
-          if (item.atTypeArray[0] === this.TIM.TYPES.CONV_AT_ALL_AT_ME) {
+          if (item.atTypeArray[0] === 3) {
             text = '[@所有人][有人@我]'
           }
         })
@@ -100,41 +117,41 @@ export default {
     },
     date() {
       if (
-        !this.conversation.lastMessage ||
-        !this.conversation.lastMessage.lastTime
+        !this.conversation.latestMsg ||
+        !this.conversation.latestMsgSendTime
       ) {
         return ''
       }
-      const date = new Date(this.conversation.lastMessage.lastTime * 1000)
+      const date = new Date(this.conversation.latestMsgSendTime)
       if (isToday(date)) {
         return getTime(date)
       }
       return getDate(date)
     },
     avatar: function() {
-      switch (this.conversation.type) {
-        case 'GROUP':
-          return this.conversation.groupProfile.avatar
-        case 'C2C':
-          return this.conversation.userProfile.avatar
+      switch (this.conversation.conversationType) {
+        case 3:
+          return this.conversation.faceURL
+        case 1:
+          return this.conversation.faceURL
         default:
           return ''
       }
     },
     conversationName: function() {
-      if (this.conversation.type === this.TIM.TYPES.CONV_C2C) {
-        return this.conversation.userProfile.nick || this.conversation.userProfile.userID
+      if (this.conversation.conversationType === 1) {
+        return this.conversation.showName || this.conversation.userID
       }
-      if (this.conversation.type === this.TIM.TYPES.CONV_GROUP) {
+      if (this.conversation.conversationType === 3) {
         return this.conversation.groupProfile.name || this.conversation.groupProfile.groupID
       }
-      if (this.conversation.type === this.TIM.TYPES.CONV_SYSTEM) {
+      if (this.conversation.conversationType === 4) {
         return '系统通知'
       }
       return ''
     },
     showGrayBadge() {
-      if (this.conversation.type !== this.TIM.TYPES.CONV_GROUP) {
+      if (this.conversation.conversationType !== 4) {
         return false
       }
       return (
@@ -143,40 +160,82 @@ export default {
       )
     },
     messageForShow() {
-      if (this.conversation.lastMessage.isRevoked) {
-        // if (this.conversation.lastMessage.fromAccount === this.currentUserProfile.userID) {
-        //   return '你撤回了一条消息'
-        // }
-        if (this.conversation.type === this.TIM.TYPES.CONV_C2C) {
+      this.lastMsg = JSON.parse(this.conversation.latestMsg)
+      /*if (this.lastMsg.contentType === 2101) {
+        if (this.lastMsg.sendID === this.$store.getters.userID) {
+          return '你撤回了一条消息'
+        }
+        if (this.lastMsg.sessionType === 1) {
           return '对方撤回了一条消息'
         }
-        return `${this.conversation.lastMessage.fromAccount}撤回了一条消息`
+        return `${this.lastMsg.sendID}撤回了一条消息`
+      }*/
+      let text = ''
+      switch (this.lastMsg.contentType) {
+        case 2101:
+          if (this.lastMsg.sendID === this.$store.getters.userID) {
+            return '你撤回了一条消息'
+          }
+          if (this.lastMsg.sessionType === 1) {
+            return '对方撤回了一条消息'
+          }
+          return `${this.lastMsg.sendID}撤回了一条消息`
+        case 101:
+          text = this.lastMsg.textElem.content || ''
+          if (text.length > 20) {
+            text = text.slice(0, 20)
+          }
+          return text
+        case 107:
+          return '[聊天记录]'
+        case 102:
+          return '[图片]'
+        case 103:
+          return '[音频]'
+        case 104:
+          return '[视频]'
+        case 110:
+          return '[自定义消息]'
+        case 105:
+          return '[文件]'
+        case 115:
+          return '[动画表情]'
+        case 1701:
+          return '[阅后即焚]'
+        case 1201:
+          return '[成为好友通知]'
+        case 114:
+          return '[引用消息]'
       }
-      return this.conversation.lastMessage.messageForShow
+      //return  this.lastMsg.textElem.content
     },
     ...mapState({
       currentConversation: state => state.conversation.currentConversation,
-      currentUserProfile: state => state.user.currentUserProfile
+      currentUserProfile: state => state.user.currentUserProfile,
+      userID: state => state.imuser.userID,
     }),
     ...mapGetters(['toAccount'])
   },
   mounted() {
-
+    this.OpenIM = getOpenIM()
   },
   methods: {
     selectConversation() {
+      //console.log("this.currentConversation.conversationID"+this.currentConversation.conversationID)
       if (this.conversation.conversationID !== this.currentConversation.conversationID) {
+        console.log("用户id"+this.conversation.userID)
+        console.log("会话类型"+this.conversation.conversationType)
         this.$store.dispatch(
           'checkoutConversation',
-          this.conversation.conversationID
+          this.conversation,
         )
       }
     },
     deleteConversation(event) {
       // 停止冒泡,避免和点击会话的事件冲突
       event.stopPropagation()
-      this.tim
-        .deleteConversation(this.conversation.conversationID)
+      this.OpenIM
+        .deleteConversationAndDeleteAllMsg(this.conversation.conversationID)
         .then(() => {
           this.$store.commit('showMessage', {
             message: `会话【${this.conversationName}】删除成功!`,
@@ -201,28 +260,50 @@ export default {
 </script>
 
 <style lang="stylus" scoped>
-.conversation-item-container
-  padding 15px 20px
-  cursor pointer
-  position relative
-  overflow hidden
-  transition .2s
-  // &:first-child
-  //   padding-top 30px
-  &:hover
-    background-color $background
-    .close-btn
-      right 3px
+  .avatar-wrapper
+    position relative
+    width 40px
+    height 40px
+    margin-right 10px
+    flex-shrink 0
+
+  .avatar
+    width 100%
+    height 100%
+    border-radius 50%
+    display block
+
+  .avatar-border
+    position absolute
+    top 0
+    left 0
+    width 100%
+    height 100%
+    pointer-events none
+
+  .conversation-item-container
+    padding 15px 20px
+    cursor pointer
+    position relative
+    overflow hidden
+    transition .2s
+    &:hover
+      background-color $background
+      .close-btn
+        right 3px
+
   .close-btn
     position absolute
     right -20px
     top 3px
     color $font-dark
-    transition: all .2s ease;
-    &:hover
-      color $danger
+    transition all .2s ease
+     &:hover
+       color $danger
+
   .warp
     display flex
+
   .avatar
     width 40px
     height 40px
@@ -233,53 +314,61 @@ export default {
     flex 1
     height 40px
     overflow hidden
+
     .row-1
       display flex
       line-height 21px
-      .name
-        color $font-light
-        flex 1
-        min-width 0px
-      .unread-count
-        padding-left 10px
-        flex-shrink 0
-        color $font-dark
-        font-size 12px
-        .badge
-          vertical-align bottom
-          background-color $danger
-          border-radius 10px
-          color #FFF
-          display inline-block
-          font-size 12px
-          height 18px
-          max-width 40px
-          line-height 18px
-          padding 0 6px
-          text-align center
-          white-space nowrap
+
+       .name
+         color $font-light
+         flex 1
+         min-width 0px
+
+       .unread-count
+         padding-left 10px
+         flex-shrink 0
+         color $font-dark
+         font-size 12px
+
+         .badge
+           vertical-align bottom
+           background-color $danger
+           border-radius 10px
+           color #FFF
+           display inline-block
+           font-size 12px
+           height 18px
+           max-width 40px
+           line-height 18px
+           padding 0 6px
+           text-align center
+           white-space nowrap
+
     .row-2
       display flex
       font-size 12px
       padding-top 3px
+
       .summary
         flex 1
         overflow hidden
         min-width 0px
-        color: $secondary
+        color $secondary
+
         .remind
           color $danger
-      .date
-        padding-left 10px
-        flex-shrink 0
-        text-align right
-        color $font-dark
-.choose {
-  background-color: $background;
-}
-.context-menu-button {
-  padding: 10px
-  border: 2px solid $primary;
-  border-radius: 8px;
-}
+
+        .date
+          padding-left 10px
+          flex-shrink 0
+          text-align right
+          color $font-dark
+
+    .choose
+     background-color $background
+
+    .context-menu-button
+      padding 10px
+      border 2px solid $primary
+      border-radius 8px
 </style>

+ 12 - 2
src/components/conversation/conversation-list.vue

@@ -28,6 +28,7 @@
 <script>
 import ConversationItem from './conversation-item'
 import { mapState } from 'vuex'
+import { getOpenIM } from '@/utils/openIM';
 export default {
   name: 'ConversationList',
   components: { ConversationItem },
@@ -36,7 +37,11 @@ export default {
       showDialog: false,
       userID: '',
       isCheckouting: false, // 是否正在切换会话
-      timeout: null
+      timeout: null,
+      OpenIM: null,
+      conversationList1:[
+
+      ]
     }
   },
   computed: {
@@ -51,6 +56,9 @@ export default {
   destroyed() {
     window.removeEventListener('keydown', this.handleKeydown)
   },
+  created() {
+    this.OpenIM = getOpenIM()
+  },
   methods: {
     handleRefresh() {
       this.refreshConversation()()
@@ -61,7 +69,9 @@ export default {
         if (!that.timeout) {
           that.timeout = setTimeout(() =>{
             that.timeout = null
-            that.tim.getConversationList().then(() => {
+            that.OpenIM.getAllConversationList().then(({data}) => {
+              console.log("分页获取会话",data)
+              that.$store.commit('updateConversationList', data)
               that.$store.commit('showMessage', {
                 message: '刷新成功',
                 type: 'success'

+ 3 - 3
src/components/conversation/conversation-profile.vue

@@ -1,11 +1,11 @@
 <template>
   <div class="conversation-profile-wrapper">
     <user-profile
-      v-if="currentConversation.type === TIM.TYPES.CONV_C2C"
+      v-if="currentConversation.type === 1"
       :userProfile="currentConversation.userProfile"
     />
     <group-profile
-      v-else-if="currentConversation.type === TIM.TYPES.CONV_GROUP"
+      v-else-if="currentConversation.type === 3"
       :groupProfile="currentConversation.groupProfile"
     />
   </div>
@@ -33,7 +33,7 @@ export default {
 </script>
 
 <style lang="stylus" scoped>
-.conversation-profile-wrapper 
+.conversation-profile-wrapper
   background-color $white
   height 100%
   overflow-y scroll

+ 13 - 15
src/components/conversation/conversation-selected-list.vue

@@ -15,18 +15,16 @@
                                     <div class="row-1">
                                         <div class="name">
                                             <div class="text-ellipsis">
-                <span :title="conversation.userProfile.nick || conversation.userProfile.userID"
-                      v-if="conversation.type ===  TIM.TYPES.CONV_C2C"
-                >{{conversation.userProfile.nick || conversation.userProfile.userID}}
+                <span :title="conversation.showName || conversation.userID"
+                      v-if="conversation.conversationType ===  1"
+                >{{conversation.showName || conversation.userID}}
                 </span>
-                                                <span :title="conversation.groupProfile.name || conversation.groupProfile.groupID"
-                                                      v-else-if="conversation.type ===  TIM.TYPES.CONV_GROUP"
-                                                >{{conversation.groupProfile.name || conversation.groupProfile.groupID}}
-                </span>
-                                                <span
-                                                        v-else-if="conversation.type === TIM.TYPES.CONV_SYSTEM"
-                                                >系统通知
+                <!--<span :title="conversation.groupProfile.name || conversation.groupProfile.groupID" v-else-if="conversation.conversationType === 3">
+                  {{conversation.groupProfile.name || conversation.groupProfile.groupID}}
                 </span>
+                <span v-else-if="conversation.conversationType === 4" >
+                  系统通知
+                </span>-->
                                             </div>
                                         </div>
                                     </div>
@@ -69,11 +67,11 @@
       },
       avatar() {
         return function (conversation) {
-          switch (conversation.type) {
-            case 'GROUP':
-              return conversation.groupProfile.avatar
-            case 'C2C':
-              return conversation.userProfile.avatar
+          switch (conversation.conversationType) {
+            case 3:
+              return conversation.faceURL
+            case 1:
+              return conversation.faceURL
             default:
               return ''
           }

+ 2 - 2
src/components/conversation/conversationProfile/group-member-info.vue

@@ -110,7 +110,7 @@ export default {
     showCancelBan() {
       if (
         this.showMuteUntil &&
-        this.currentConversation.type === this.TIM.TYPES.CONV_GROUP &&
+        this.currentConversation.type === 3 &&
         !this.isMine
       ) {
         return this.isOwner || this.isAdmin
@@ -119,7 +119,7 @@ export default {
     },
     // 是否显示禁言按钮
     showBan() {
-      if (this.currentConversation.type === this.TIM.TYPES.CONV_GROUP) {
+      if (this.currentConversation.type === 3) {
         return this.isOwner || this.isAdmin
       }
       return false

+ 11 - 11
src/components/conversation/conversationProfile/group-profile.vue

@@ -279,33 +279,33 @@ export default {
   computed: {
     editable() {
       return (
-        this.groupProfile.type === this.TIM.TYPES.GRP_WORK ||
-        [this.TIM.TYPES.GRP_MBR_ROLE_OWNER, this.TIM.TYPES.GRP_MBR_ROLE_ADMIN].includes(this.groupProfile.selfInfo.role)
+        this.groupProfile.type === this.OpenIM.TYPES.GRP_WORK ||
+        [this.OpenIM.TYPES.GRP_MBR_ROLE_OWNER, this.OpenIM.TYPES.GRP_MBR_ROLE_ADMIN].includes(this.groupProfile.selfInfo.role)
       )
     },
     isOwner() {
-      return this.groupProfile.selfInfo.role === this.TIM.TYPES.GRP_MBR_ROLE_OWNER
+      return this.groupProfile.selfInfo.role === this.OpenIM.TYPES.GRP_MBR_ROLE_OWNER
     },
     isAdmin() {
-      return this.groupProfile.selfInfo.role === this.TIM.TYPES.GRP_MBR_ROLE_ADMIN
+      return this.groupProfile.selfInfo.role === this.OpenIM.TYPES.GRP_MBR_ROLE_ADMIN
     },
     showDissmissGroup() {
       // 好友工作群不能解散
-      return this.isOwner && this.groupProfile.type !== this.TIM.TYPES.GRP_WORK
+      return this.isOwner && this.groupProfile.type !== this.OpenIM.TYPES.GRP_WORK
     },
     groupType() {
-      switch (this.groupProfile.type) {
-        case this.TIM.TYPES.GRP_WORK:
+      /*switch (this.groupProfile.type) {
+        case this.OpenIM.TYPES.GRP_WORK:
           return '好友工作群(Work)'
-        case this.TIM.TYPES.GRP_PUBLIC:
+        case this.OpenIM.TYPES.GRP_PUBLIC:
           return '陌生人社交群(Public)'
-        case this.TIM.TYPES.GRP_CHATROOM:
+        case this.OpenIM.TYPES.GRP_CHATROOM:
           return '临时会议群(Meeting)'
-        case this.TIM.TYPES.GRP_AVCHATROOM:
+        case this.OpenIM.TYPES.GRP_AVCHATROOM:
           return '直播群(AVChatRoom)'
         default:
           return ''
-      }
+      }*/
     }
   },
   watch: {

+ 5 - 5
src/components/conversation/conversationProfile/user-profile.vue

@@ -115,9 +115,9 @@ export default {
     },
     gender() {
       switch (this.userProfile.gender) {
-        case this.TIM.TYPES.GENDER_MALE:
+        case this.OpenIM.TYPES.GENDER_MALE:
           return '男'
-        case this.TIM.TYPES.GENDER_FEMALE:
+        case this.OpenIM.TYPES.GENDER_FEMALE:
           return '女'
         default:
           return '未设置'
@@ -125,9 +125,9 @@ export default {
     },
     genderClass() {
       switch (this.userProfile.gender) {
-        case this.TIM.TYPES.GENDER_MALE:
+        case this.OpenIM.TYPES.GENDER_MALE:
           return 'icon-male'
-        case this.TIM.TYPES.GENDER_FEMALE:
+        case this.OpenIM.TYPES.GENDER_FEMALE:
           return 'icon-female'
         default:
           return ''
@@ -183,7 +183,7 @@ export default {
     removeFromFriendList() {
       let options = {
         userIDList: [this.userProfile.userID],
-        type: this.TIM.TYPES.SNS_DELETE_TYPE_BOTH
+        type: this.OpenIM.TYPES.SNS_DELETE_TYPE_BOTH
       }
       this.tim.deleteFriend(options).then(() => {
       }).catch(error => {

+ 29 - 24
src/components/conversation/current-conversation.vue

@@ -21,11 +21,12 @@
           </div>
           <div class="no-more" v-else>没有更多了</div>
           <el-checkbox-group v-model="checkList" v-if="selectMessage">
-            <el-checkbox :label="message.ID" v-for="message in currentMessageList" :key="message.ID" :disabled="message.status==='fail'">
+            <el-checkbox :label="message.clientMsgID" v-for="message in currentMessageList" :key="message.clientMsgID" :disabled="message.status==='fail'">
               <message-item   :message="message"/>
             </el-checkbox>
           </el-checkbox-group>
-          <message-item v-else v-for="message in currentMessageList" :key="message.ID" :message="message"/>
+          <message-item v-else v-for="message in currentMessageList" :key="message.clientMsgID" :message="message">
+          </message-item>
         </div>
         <div v-show="isShowScrollButtomTips" class="newMessageTips" @click="scrollMessageListToButtom">回到最新位置</div>
       </div>
@@ -86,7 +87,7 @@ import MessageMerger from '../message/merger-message/message-merger'
 import MessageRelay from '../message/merger-message/message-relay'
 import FriendProfile from '../friend/friend-container'
 import close from '../../assets/image/close.png'
-
+import { getOpenIM } from '@/utils/openIM';
 export default {
   name: 'CurrentConversation',
   components: {
@@ -112,6 +113,7 @@ export default {
       mergerMessage: null,
       positionX: 0,
       positionY: 0,
+      OpenIM: null,
     }
   },
   computed: {
@@ -134,7 +136,7 @@ export default {
       return this.friendContent
     },
     name() {
-      if (this.currentConversation.type === 'C2C') {
+      if (this.currentConversation.conversationType === 1) {
         // let name = this.currentConversation.userProfile.nick || this.toAccount
         // let list = this.currentMessageList
         // let len = list.length
@@ -145,21 +147,22 @@ export default {
         //     break
         //   }
         // }
-        return this.currentConversation.remark || this.currentConversation.userProfile.nick || this.currentConversation.userProfile.userID
+        return this.currentConversation.remark || this.currentConversation.showName || this.currentConversation.userID
 
-      } else if (this.currentConversation.type === 'GROUP') {
-        return this.currentConversation.groupProfile.name || this.toAccount
-      } else if (this.currentConversation.conversationID === '@TIM#SYSTEM') {
+      } else if (this.currentConversation.conversationType === 3) {
+        return this.currentConversation.showName || this.toAccount
+      } else if (this.currentConversation.conversationType === 4) {
         return '系统通知'
       }
       return this.toAccount
     },
     showMessageSendBox() {
-      return this.currentConversation.type !== this.TIM.TYPES.CONV_SYSTEM
+      return this.currentConversation.type !== 4
     },
     mergerTitle() {
+      console.log("this.mergerMessage",this.mergerMessage)
       if (this.mergerMessage) {
-        return this.mergerMessage.payload.title || '聊天记录'
+        return this.mergerMessage.mergeElem.title || '聊天记录'
       } else {
         return  '聊天记录'
       }
@@ -176,7 +179,7 @@ export default {
     this.$bus.$on('mergerMessageShow', this.mergerShow)
 
 
-    if (this.currentConversation.conversationID === '@TIM#SYSTEM') {
+    if (this.currentConversation.conversationType === 4) {
       return false
     }
   },
@@ -190,23 +193,26 @@ export default {
     // 1. 系统会话隐藏右侧资料组件
     // 2. 没有当前会话时,隐藏右侧资料组件。
     //    背景:退出群组/删除会话时,会出现一处空白区域
-    if (this.currentConversation.conversationID === '@TIM#SYSTEM' ||
-        typeof this.currentConversation.conversationID === 'undefined') {
+    if (this.currentConversation.conversationType === 4 ||
+      typeof this.currentConversation.conversationType === 'undefined') {
       this.showConversationProfile = false
     }
   },
   watch: {
     currentUnreadCount(next) {
       if (!this.hidden && next > 0) {
-        this.tim.setMessageRead({ conversationID: this.currentConversation.conversationID })
+        this.OpenIM.markConversationMessageAsRead(this.currentConversation.conversationID)
       }
     },
     hidden(next) {
       if (!next && this.currentUnreadCount > 0) {
-        this.tim.setMessageRead({ conversationID: this.currentConversation.conversationID })
+        this.OpenIM.markConversationMessageAsRead(this.currentConversation.conversationID )
       }
     }
   },
+  created() {
+    this.OpenIM = getOpenIM();
+  },
   methods: {
     move(e) {
       let odiv = this.$refs.dropdown.$el.children[0]//e.target        //获取目标元素
@@ -261,15 +267,14 @@ export default {
       this.selectedMessage()
       this.$store.commit('setRelayType', 2)
     },
-    selectedMessage () {
-      let messageList = []
-      this.selectedMessageList = []
-      this.checkList.forEach((id) => {
-        messageList = this.currentMessageList.find((item) => {
-          return item.ID === id
-        })
-        this.selectedMessageList.push(messageList)
-      })
+    selectedMessage() {
+      this.selectedMessageList = this.checkList.map((id) => {
+        return this.currentMessageList.find((item) => item.clientMsgID === id)
+      }).filter(Boolean)
+
+      console.log("checkList:", this.checkList)
+      console.log("currentMessageList:", this.currentMessageList)
+      console.log("selectedMessageList:", this.selectedMessageList)
 
       this.$store.commit('showConversationList', true)
       //this.closeSelectMessage() // TODO

+ 8 - 4
src/components/friend/friend-application/application-item.vue

@@ -12,6 +12,7 @@
 
 <script>
 import { mapState } from 'vuex'
+import { getOpenIM} from '@/utils/openIM';
 export default {
   components: {
   },
@@ -25,6 +26,7 @@ export default {
         tag: '',
         type: 'Response_Action_AgreeAndAdd',
       },
+      OpenIm:null,
     }
   },
   computed: {
@@ -32,13 +34,14 @@ export default {
       applicationList: state => state.friend.applicationList
     }),
     comeInApplicationList() {
-      return this.applicationList.filter((item) => {return item.type === this.TIM.TYPES.SNS_APPLICATION_SENT_TO_ME})
+      return this.applicationList.filter((item) => {return item.type === this.OpenIM.TYPES.SNS_APPLICATION_SENT_TO_ME})
     },
     sendOutApplicationList() {
-      return this.applicationList.filter((item) => {return item.type === this.TIM.TYPES.SNS_APPLICATION_SENT_BY_ME})
+      return this.applicationList.filter((item) => {return item.type === this.OpenIM.TYPES.SNS_APPLICATION_SENT_BY_ME})
     }
   },
   created() {
+    this.OpenIm =getOpenIM()
     this.showApplicationList = this.comeInApplicationList
   },
   mounted() {
@@ -50,8 +53,9 @@ export default {
     },
     // 应答
     acceptFriendApplication(userID) {
-      this.tim.acceptFriendApplication({
-        userID: userID,
+      this.OpenIm.acceptFriendApplication({
+        toUserID: userID,
+        handleMsg:"",
         remark: this.acceptApplication.remark,
         tag: this.acceptApplication.tag,
         type: this.acceptApplication.type

+ 274 - 214
src/components/friend/friend-container.vue

@@ -1,232 +1,291 @@
 <template>
-    <div class="friend-content" v-if="showFriendContent || showApplicationContent" @click="closeHandler">
-        <div class="friend-box" v-if="showFriendContent">
-            <div class="profile-container" >
-                <div class="item-nick text-ellipsis">{{friendProfile.profile.nick||friendProfile.profile.userID}}</div>
-                <img
-                        class="avatar"
-                        :src="friendProfile.profile.avatar ? friendProfile.profile.avatar : 'https://imgcache.qq.com/open/qcloud/video/act/webim-avatar/avatar-2.png'"
-                />
-            </div>
-            <el-divider></el-divider>
-            <p class="content-box"><span class="content-title">userID</span><span class="content-text">{{friendProfile.userID}}</span></p>
-            <div class="content-box"><span class="content-title">备注名</span>
-                <div class="text-box"   v-if="!showEditRemark">
-                    <span class="content-text  content-width text-ellipsis">{{friendProfile.remark || '暂无'}}</span>
-                    <i
-                            class="el-icon-edit"
-                            @click.stop="
-                        showEditRemark = true
-                        inputFocus('showEditRemark')
+  <div class="friend-content" v-if="showFriendContent || showApplicationContent" @click="closeHandler">
+    <div class="friend-box" v-if="showFriendContent">
+      <div class="profile-container" >
+        <div class="item-nick text-ellipsis">{{friendProfile.nickname||friendProfile.userID}}</div>
+        <img
+          class="avatar"
+          :src="friendProfile.faceURL ? friendProfile.faceURL : 'https://imgcache.qq.com/open/qcloud/video/act/webim-avatar/avatar-2.png'"
+        />
+      </div>
+      <el-divider></el-divider>
+      <p class="content-box"><span class="content-title">userID</span><span class="content-text">{{friendProfile.userID}}</span></p>
+      <div class="content-box">
+        <span class="content-title">备注名</span>
 
-            "
-                            style="cursor:pointer; font-size:16px; margin-left: 6px"
-                    />
-                </div>
-                <el-input
-                        style="display: inline-block"
-                        ref="showEditRemark"
-                        v-else
-                        autofocus
-                        v-model="profileRemark"
-                        size="mini"
-                        @blur="blurHandler"
-                        @keydown.enter.native="editRemarkHandler"
-                />
-            </div>
+        <!-- 显示模式 -->
+        <div class="text-box" v-if="!showEditRemark">
+              <span class="content-text content-width text-ellipsis">
+                {{ friendProfile.remark || '暂无' }}
+              </span>
+          <i
+            class="el-icon-edit"
+            @click.stop="
+                showEditRemark = true,
+                inputFocus('remarkInput')"
+            style="cursor: pointer; font-size: 16px; margin-left: 6px"
+          />
+        </div>
 
-            <p class="content-box"><span class="content-title">来源</span><span class="content-text">{{getSource(friendProfile.source)}}</span></p>
-            <el-divider></el-divider>
-            <div class="content-box" v-show="friendType==='friendList'">
-                <span class="content-title" v-show="!showEdit" style="line-height: 35px">所在分组</span>
-                <span class="content-title" v-show="showEdit"  style="line-height: 35px">添加到分组</span>
-                <div class="text-content">
-                    <span class="content-text" v-show="!showEdit">{{groupName}}</span>
-                    <i class="el-icon-edit edit-icon" v-show="!showEdit"   @click.stop="showEditHandler"></i>
-                </div>
-                <el-select v-if="showEdit" v-model="addGroupName"  placeholder="选择分组" @change="addToFriendGroup">
-                    <el-option
-                            v-for="item in friendGroupList"
-                            @blur="showEdit = false"
-                            :key="item.name"
-                            :label="item.name"
-                            :value="item.name">
-                    </el-option>
-                </el-select>
-            </div>
-            <p class="content-box" v-show="friendType==='groupFriend'" style="height: 40px">
-                <span class="content-title" style="line-height: 35px">所在分组</span>
-                <span class="content-text" v-show="!showEdit" style="line-height: 35px">{{groupName}}</span>
-            </p>
-            <div class="sendBtn" @click.stop="checkoutConversation(friendProfile.userID)">发送消息</div>
-            <div class="delete-text" v-show="friendType==='groupFriend'" @click.stop="removeFromFriendGroup(friendProfile.userID)">从该群组中移除</div>
-            <div class="delete-text" v-show="friendType==='friendList'" @click.stop="removeFromFriendList(friendProfile.userID)">删除该好友</div>
+        <!-- 编辑模式 -->
+        <div v-else style="display: inline-flex; align-items: center;">
+          <el-input
+            ref="remarkInput"
+            v-model="profileRemark"
+            size="mini"
+            style="width: 200px; margin-right: 8px;"
+          />
+          <el-button type="primary" size="mini" @click="editRemarkHandler">
+            保存
+          </el-button>
+          <el-button size="mini" @click="showEditRemark = false" style="margin-left: 5px;">
+            取消
+          </el-button>
         </div>
-        <div class="friend-box" v-if="showApplicationContent">
-            <div class="profile-container" >
-                <div class="item-nick text-ellipsis">{{applicationContent.nick||applicationContent.userID}}</div>
-                <img
-                        class="avatar"
-                        :src="applicationContent.avatar ? applicationContent.avatar : 'http://imgcache.qq.com/open/qcloud/video/act/webim-avatar/avatar-2.png'"
-                />
-            </div>
-            <el-divider></el-divider>
-            <p class="content-box"><span class="content-title">userID</span><span class="content-text">{{applicationContent.userID}}</span></p>
-            <p class="content-box"><span class="content-title">来源</span><span class="content-text content-width text-ellipsis">{{getSource(applicationContent.source)}}</span></p>
-            <div class="content-box" v-if="showRemark"><span class="content-title">备注名</span>
-                <el-input
-                        style="display: inline-block"
-                        ref="showRemark"
-                        autofocus
-                        v-model="acceptApplication.remark"
-                        size="mini"
-                        @blur="remarkBlurHandler"
-                        @keydown.enter.native="editRemarkHandler"
-                />
-            </div>
-            <el-divider></el-divider>
-            <p class="content-box">
-                <span class="content-title">打招呼</span>
-                <el-tooltip class="item" effect="dark" :content="applicationContent.wording" placement="bottom-end">
-                    <span class="content-text content-width text-ellipsis">{{applicationContent.wording}}</span>
-                </el-tooltip>
-            </p>
-            <div class="application-box" v-if="!showRemark">
-                <p class="application-refuse" @click="acceptHandler">同意</p>
-                <p class="application-delete"  @click="refuseFriendApplication(applicationContent.userID)">拒绝</p>
-            </div>
-            <div class="application-box" v-else>
-                <p class="application-refuse" @click="acceptFriendApplication(applicationContent.userID)">确认</p>
-                <p class="application-delete"  @click="cancelHandler()">取消</p>
-            </div>
+      </div>
+
+      <p class="content-box"><span class="content-title">来源</span><span class="content-text">{{getSource(friendProfile.addSource)}}</span></p>
+      <el-divider></el-divider>
+      <div class="content-box" v-show="friendType==='friendList'">
+        <span class="content-title" v-show="!showEdit" style="line-height: 35px">所在分组</span>
+        <span class="content-title" v-show="showEdit"  style="line-height: 35px">添加到分组</span>
+        <div class="text-content">
+          <span class="content-text" v-show="!showEdit">{{groupName}}</span>
+          <i class="el-icon-edit edit-icon" v-show="!showEdit"   @click.stop="showEditHandler"></i>
         </div>
+        <el-select v-if="showEdit" v-model="addGroupName"  placeholder="选择分组" @change="addToFriendGroup">
+          <el-option
+            v-for="item in friendGroupList"
+            @blur="showEdit = false"
+            :key="item.name"
+            :label="item.name"
+            :value="item.name">
+          </el-option>
+        </el-select>
+      </div>
+      <p class="content-box" v-show="friendType==='groupFriend'" style="height: 40px">
+        <span class="content-title" style="line-height: 35px">所在分组</span>
+        <span class="content-text" v-show="!showEdit" style="line-height: 35px">{{groupName}}</span>
+      </p>
+      <div class="sendBtn" @click.stop="checkoutConversation(friendProfile.userID)">发送消息</div>
+      <div class="delete-text" v-show="friendType==='groupFriend'" @click.stop="removeFromFriendGroup(friendProfile.userID)">从该群组中移除</div>
+      <div class="delete-text" v-show="friendType==='friendList'" @click.stop="removeFromFriendList(friendProfile.userID)">删除该好友</div>
     </div>
+    <div class="friend-box" v-if="showApplicationContent">
+      <div class="profile-container" >
+        <div class="item-nick text-ellipsis">{{applicationContent.nick||applicationContent.userID}}</div>
+        <img
+          class="avatar"
+          :src="applicationContent.avatar ? applicationContent.avatar : 'http://imgcache.qq.com/open/qcloud/video/act/webim-avatar/avatar-2.png'"
+        />
+      </div>
+      <el-divider></el-divider>
+      <p class="content-box"><span class="content-title">userID</span><span class="content-text">{{applicationContent.userID}}</span></p>
+      <p class="content-box"><span class="content-title">来源</span><span class="content-text content-width text-ellipsis">{{getSource(applicationContent.addSource)}}</span></p>
+      <div class="content-box" v-if="showRemark"><span class="content-title">备注名</span>
+        <el-input
+          style="display: inline-block"
+          ref="showRemark"
+          autofocus
+          v-model="acceptApplication.remark"
+          size="mini"
+          @blur="remarkBlurHandler"
+          @keydown.enter.native="editRemarkHandler"
+        />
+      </div>
+      <el-divider></el-divider>
+      <p class="content-box">
+        <span class="content-title">打招呼</span>
+        <el-tooltip class="item" effect="dark" :content="applicationContent.wording" placement="bottom-end">
+          <span class="content-text content-width text-ellipsis">{{applicationContent.wording}}</span>
+        </el-tooltip>
+      </p>
+      <div class="application-box" v-if="!showRemark">
+        <p class="application-refuse" @click="acceptHandler">同意</p>
+        <p class="application-delete"  @click="refuseFriendApplication(applicationContent.userID)">拒绝</p>
+      </div>
+      <div class="application-box" v-else>
+        <p class="application-refuse" @click="acceptFriendApplication(applicationContent.userID)">确认</p>
+        <p class="application-delete"  @click="cancelHandler()">取消</p>
+      </div>
+    </div>
+  </div>
 </template>
 
 <script>
-  import { mapState } from 'vuex'
-  export default {
-    name: 'index',
-    data() {
-      return {
-        showEdit: false,
-        showEditRemark: false,
-        showRemark: false,
-        addGroupName: '',
-        profileRemark: '',
-        editGroupList: [],
-        isUpdate: false,
-        acceptApplication: {
-          remark: '',
-          type: 'Response_Action_AgreeAndAdd',
-        },
+import { mapState } from 'vuex'
+import ScrollPane from "../../layout/components/TagsView/ScrollPane";
+import { getOpenIM,getCbEvents } from '@/utils/openIM';
+export default {
+  name: 'index',
+  components: {ScrollPane},
+  data() {
+    return {
+      OpenIM:null,
+      showEdit: false,
+      showEditRemark: false,
+      showRemark: false,
+      addGroupName: '',
+      profileRemark: '',
+      editGroupList: [],
+      isUpdate: false,
+      acceptApplication: {
+        remark: '',
+        type: 'Response_Action_AgreeAndAdd',
+      },
+    }
+  },
+  computed: {
+    ...mapState({
+      friendContent: state => state.friend.friendContent,
+      applicationContent: state => state.friend.applicationContent,
+      friendGroupList: state => state.friend.friendGroupList,
+    }),
+    showFriendContent() {
+      if (JSON.stringify(this.friendContent) === '{}') {
+        return false
       }
+      return true
     },
-    computed: {
-      ...mapState({
-        friendContent: state => state.friend.friendContent,
-        applicationContent: state => state.friend.applicationContent,
-        friendGroupList: state => state.friend.friendGroupList,
-      }),
-      showFriendContent() {
-        if (JSON.stringify(this.friendContent) === '{}') {
-          return false
-        }
-        return true
-      },
-      showApplicationContent() {
-        if (JSON.stringify(this.applicationContent) === '{}') {
-          return false
-        }
-        return true
-      },
-      friendProfile() {
-        return this.friendContent.friend
-      },
-      groupName() {
-        if(this.friendProfile.groupList.length>0) {
-          return this.friendProfile.groupList.join(',')
-        }else {
-          return '暂无分组'
-        }
-      },
-      groupList:{
-        get() {
-          return this.friendProfile.groupList
-        },
-        set(value) {
-          this.editGroupList = value
-        }
-      },
-      friendType() {
-        return this.friendContent.type
-      },
-      getSource() {
-        return function (source) {
-          return source.substring(15)
-        }
-      },
+    showApplicationContent() {
+      if (JSON.stringify(this.applicationContent) === '{}') {
+        return false
+      }
+      return true
     },
-    methods: {
-      showEditHandler() {
-        this.showEdit = true
-        this.addGroupName = ''
-      },
-      inputFocus(ref) {
-        this.profileRemark = this.friendProfile.remark
-        this.$nextTick(() => {
-          this.$refs[ref].focus()
-        })
-      },
-      blurHandler() {
-        this.showEditRemark = false
-        this.isUpdate = true
-      },
-      remarkBlurHandler() {
-        // this.showEditRemark = false
-      },
-      acceptHandler() {
-        this.showRemark = true
-        this.acceptApplication.remark = ''
-        this.$nextTick(() => {
-          this.$refs.showRemark.focus()
-        })
-      },
-      cancelHandler() {
-        this.showRemark = false
+    friendProfile() {
+      console.log("this.friendContent",this.friendContent)
+      return this.friendContent.friend
+    },
+    groupName() {
+      /*console.log("this.friendProfile",this.friendProfile)
+      if(this.friendProfile.groupList.length>0) {
+        return this.friendProfile.groupList.join(',')
+      }else {
+        return '暂无分组'
+      }*/
+      return '暂无分组'
+    },
+    groupList:{
+      get() {
+        return this.friendProfile.groupList
       },
-      closeHandler() {
-        this.showEdit = false
-        if (this.isUpdate) {
-          this.editRemarkHandler()
+      set(value) {
+        this.editGroupList = value
+      }
+    },
+    friendType() {
+      return this.friendContent.type
+    },
+    getSource() {
+      return function (source) {
+        switch (source) {
+          case 1:
+            return '管理员导入添加'
+          case 2:
+            return '申请添加'
         }
-      },
-      editRemarkHandler() {
-        this.showEditRemark = false
-        // 更新好友备注
-        this.tim.updateFriend({
-          userID: this.friendProfile.userID,
-          remark: this.profileRemark
-        }).then(()=> {
-        }).catch((imError)=> {
-          console.warn('getFriendProfile error:', imError) // 更新失败
-        })
-        this.isUpdate = false
-      },
-      updateFriendGroup() {
-        this.profileRemark = this.friendProfile.remark
-        // 更新好友分组
-        this.tim.updateFriend({
-          userID: this.friendProfile.userID,
-          groupList: this.editGroupList
-        }).then(()=>{
-        }).catch((imError)=> {
-          console.warn('getFriendProfile error:', imError) // 更新失败
+      }
+    },
+  },
+  created() {
+    this.OpenIM = getOpenIM();
+  },
+  methods: {
+    showEditHandler() {
+      this.showEdit = true
+      this.addGroupName = ''
+    },
+    inputFocus(ref) {
+      this.profileRemark = this.friendProfile.remark
+      this.$nextTick(() => {
+        this.$refs[ref].focus()
+      })
+    },
+    blurHandler() {
+      this.showEditRemark = false
+      this.isUpdate = true
+    },
+    remarkBlurHandler() {
+      // this.showEditRemark = false
+    },
+    acceptHandler() {
+      this.showRemark = true
+      this.acceptApplication.remark = ''
+      this.$nextTick(() => {
+        this.$refs.showRemark.focus()
+      })
+    },
+    cancelHandler() {
+      this.showRemark = false
+    },
+    closeHandler() {
+      this.showEdit = false
+      if (this.isUpdate) {
+        this.editRemarkHandler()
+      }
+    },
+    editRemarkHandler() {
+      this.showEditRemark = false
+      console.log("this.OpenIM000",this.OpenIM)
+      // 更新好友备注
+
+      this.OpenIM.updateFriends({
+        friendUserIDs: [this.friendProfile.userID],
+        remark: this.profileRemark
+      })
+        .then(({ data }) => {
+          this.$store.commit('showMessage', {
+            message: '备注修改成功',
+            type: 'success',
+          });
+          // 更新 friendProfile.remark 本地显示
+          this.friendProfile.remark = this.profileRemark;
+          // 调用成功,冲新加载好友列表和会话列表
+          //查询会话列表
+          this.OpenIM.getAllConversationList()
+            .then(({ data }) => {
+              // 调用成功
+              console.log("获取到会话列表",data)
+              this.conversationList= data
+              this.$store.commit('updateConversationList', data)
+            })
+          //查询好友列表
+          this.OpenIM.getFriendListPage({ offset:0, count:100 })
+            .then(({ data }) => {
+              // 调用成功
+              console.log("获取到好友列表",data)
+              //this.conversationList= data
+              this.$store.commit('updateFriendList', data)
+            })
         })
-      },
-      checkoutConversation(userID) {
+        .catch(({ errCode, errMsg }) => {
+          // 调用失败
+        });
+      this.isUpdate = false
+    },
+    updateFriendGroup() {
+      this.profileRemark = this.friendProfile.remark
+      // 更新好友分组
+      this.tim.updateFriend({
+        userID: this.friendProfile.userID,
+        groupList: this.editGroupList
+      }).then(()=>{
+      }).catch((imError)=> {
+        console.warn('getFriendProfile error:', imError) // 更新失败
+      })
+    },
+    checkoutConversation(userID) {
+      console.log(userID)
+
+      //查询会话
+      this.OpenIM.getOneConversation({
+        sourceID: userID.toString(),
+        sessionType: 1,
+      }).then(({ data }) => {
+        console.log("查询单挑回话",data)
         this.$store
-          .dispatch('checkoutConversation', `C2C${userID}`)
+          .dispatch('checkoutConversation', data)
           .then(() => {
             this.showDialog = false
             this.$bus.$emit('checkoutConversation')
@@ -236,7 +295,8 @@
             type: 'warning'
           })
         })
-      },
+      })
+    },
       addToFriendGroup() {
         this.tim.addToFriendGroup({name: this.addGroupName, userIDList: [this.friendProfile.userID]}).then(() => {
           this.$store.commit('showMessage', {

+ 94 - 45
src/components/friend/friend-item.vue

@@ -1,11 +1,24 @@
 <template>
-  <div class="friendList-item-wrapper" :id="'friend-'+name+'-'+index" @click="selectedItem">
-    <avatar :src="friend.profile.avatar" />
-    <div class="item-nick text-ellipsis">{{friend.remark || friend.profile.nick||friend.profile.userID}}</div>
+  <div
+    class="friendList-item-wrapper"
+    :id="'friend-'+name+'-'+index"
+    @click="selectedItem"
+  >
+    <div class="avatar-wrapper">
+      <avatar :src="friend.faceURL" class="avatar" />
+      <img v-if="avatarBorder" :src="avatarBorder" class="avatar-border" />
+    </div>
+
+    <div class="item-nick text-ellipsis">
+      {{ friend.remark || friend.nickname || friend.userID }}
+    </div>
   </div>
 </template>
 
 <script>
+import {mapState} from "vuex";
+import doctorBorder from '@/assets/doctor.svg'
+import guanjiaBorder from '@/assets/guanjia.svg'
   export default {
   props: ['friend','friendGroupNameList','type','groupName','index'],
   data() {
@@ -16,18 +29,41 @@
       },
     }
   },
-  computed: {
-    name() {
-      if(this.groupName) {
-        return this.groupName
-      }else {
-        return ''
-      }
-    }
-  },
+    computed: {
+      avatarBorder() {
+        if (this.friend.userID?.startsWith('D')) {
+          return doctorBorder
+        } else if (this.friend.userID?.startsWith('C')) {
+          return guanjiaBorder
+        }
+        return null
+      },
+      name() {
+        if(this.groupName) {
+          return this.groupName
+        }else {
+          return ''
+        }
+      },
+      borderClass() {
+        // 判断 userID 第一个字母
+        const userID = this.friend?.friendInfo?.userID || '';
+        if (userID.startsWith('D')) {
+          return 'doctor-border';
+        } else if (userID.startsWith('C')) {
+          return 'guanjia-border';
+        } else {
+          return ''; // 默认不加边框
+        }
+      },
+      ...mapState({
+        friendList: state => state.friend.friendList,
+      }),
+    },
 
   methods:{
     selectedItem() {
+      console.log("this.friendList",this.friendList)
       this.$store.commit('resetCurrentConversation')
       this.$store.dispatch('setFriendContent', {
         friend: this.friend,
@@ -84,38 +120,60 @@
 </script>
 
 <style lang="stylus" scoped>
-  .item-nick {
-    padding-left: 20px;
-    width: 100%;
-    color: $white;
-    box-sizing: border-box;
-    word-wrap: break-word;
-    overflow: hidden;
-    text-overflow: ellipsis;
-    font-size 16px
-  }
+  .avatar-wrapper
+    position relative
+    width 40px
+    height 40px
+    margin-right 10px
+    flex-shrink 0
 
-  .friendList-item-wrapper {
+  .avatar
+    width 100%
+    eight 100%
+    border-radius 50%
+    display block
+  .avatar-border
+    position absolute
+    top 0
+    left 0
+    width 100%
+    height 100%
+    pointer-events none
+  .avatar
+    width 40px
+    height 40px
+    margin-right 10px
+    border-radius 50%
+    flex-shrink 0
+  .item-nick
+    padding-left 20px
+    width 100%
+    color #fff
+    box-sizing border-box
+    word-wrap break-word
+    overflow hidden
+    text-overflow ellipsis
+    font-size 16px
+  .friendList-item-wrapper
     cursor pointer
     padding 0 40px
-    padding-bottom: 15px;
-    display: flex;
-    align-items: center;
-    justify-content: flex-start;
-  }
-  .group-friend-btn {
+    padding-bottom 15px
+    display flex
+    align-items center
+    justify-content flex-start
+  .group-friend-btn
     position absolute
     right 13px
-  }
-  .unread-count {
+
+  .unread-count
     padding-left 10px
     flex-shrink 0
-    color $font-dark
+    color #666
     font-size 12px
-  }
-  .badge {
+
+  .badge
     vertical-align bottom
-    background-color $danger
+    background-color #f56c6c
     border-radius 10px
     color #FFF
     display inline-block
@@ -126,15 +184,6 @@
     padding 0 6px
     text-align center
     white-space nowrap
-    }
-  .avatar {
-    width: 40px;
-    height: 40px;
-    border-radius: 50%;
-    flex-shrink: 0
-    box-shadow: 0 5px 10px 0 rgba(0, 0, 0, 0.1);
-  }
-  .el-icon-chat-dot-round:before {
+  .el-icon-chat-dot-round:before
     color #dddddd
-  }
 </style>

+ 64 - 8
src/components/friend/friend-list.vue

@@ -1,9 +1,9 @@
 <template>
   <div class="friend-container">
-    <div class="add-friend" @click="handleAddButtonClick">
+<!--    <div class="add-friend" @click="handleAddButtonClick">
       <i class="tim-icon-friend-add" style="font-size: 28px"></i>
       <span style="margin-left: 6px">加好友</span>
-    </div>
+    </div>-->
     <el-dialog title="快速搜索好友" :visible.sync="showDialog" width="400px">
       <el-input placeholder="请输入用户ID" v-model="userID" @keydown.enter.native="addFriendConfirm">
         <el-button slot="append" icon="el-icon-search" @click="addFriendConfirm"></el-button>
@@ -63,13 +63,24 @@
     </el-dialog>
     <div class="scroll-container">
       <div class="menu-container">
+        <div style="padding: 10px; display: flex; gap: 6px;">
+          <el-input
+            v-model="searchKeyword"
+            placeholder="输入昵称/备注"
+            clearable
+            size="small"
+            style="flex: 1;"
+          />
+          <el-button type="primary" size="small" @click="searchFriendList">搜索</el-button>
+          <el-button size="small" @click="resetSearch">清空</el-button>
+        </div>
         <el-menu
                 default-active="application"
                 class="el-menu-vertical-demo"
                 :default-openeds="openeds"
                 @open="handleOpen"
                 @close="handleClose">
-          <el-submenu index="application" style="border-buttom:1px solid #1c2438">
+<!--          <el-submenu index="application" style="border-buttom:1px solid #1c2438">
             <template slot="title">
               <i :class="{'el-icon-arrow-right': !active['application'], 'el-icon-arrow-down': active['application']}"></i>
               <span>新的好友
@@ -116,14 +127,14 @@
                 </el-menu-item>
               </el-submenu>
             </el-menu-item-group>
-          </el-submenu>
+          </el-submenu>-->
           <el-submenu :hide-timeout="hideTimeOut"  index="friendList" style="border-buttom:1px solid #1c2438">
             <template slot="title">
               <i :class="{'el-icon-arrow-right': !active['friendList'], 'el-icon-arrow-down': active['friendList']}"></i>
               <span>联系人</span>
             </template>
             <el-menu-item-group>
-              <el-menu-item   :index="friend.userID" v-for="(friend,index) in friendList"  :key="friend.userID">
+              <el-menu-item   :index="friend.userID" v-for="(friend,index) in filteredList"  :key="friend.userID">
                 <friend-item  :index="index" :friend="friend"  :friendGroupNameList="friendGroupNameList" :type="'friendList'"/>
               </el-menu-item>
             </el-menu-item-group>
@@ -139,6 +150,7 @@
 import { mapState } from 'vuex'
 import FriendItem from './friend-item.vue'
 import FriendApplication from './friend-application/application-item.vue'
+import { getOpenIM,getCbEvents } from '@/utils/openIM';
 export default {
   components: {
     FriendItem,
@@ -146,6 +158,9 @@ export default {
   },
   data() {
     return {
+      searchKeyword: '',
+      filteredList: [],
+      OpenIM:null,
       active: {},
       hideTimeOut: 1000,
       openeds: ['friendList'],
@@ -163,7 +178,8 @@ export default {
         remark: '',
         groupName: '',
         wording: '',
-        type: this.TIM.TYPES.SNS_ADD_TYPE_BOTH
+        //type: this.OpenIM.TYPES.SNS_ADD_TYPE_BOTH
+        type: 1
       },
       addInfo: {
         groupName: '',
@@ -174,7 +190,7 @@ export default {
         nick: '',
         userID: '',
       },
-      formLabelWidth: '120px'
+      formLabelWidth: '120px',
     }
   },
   computed: {
@@ -192,7 +208,7 @@ export default {
       return this.friendGroupList.map((item) => {return item.name})
     },
     comeInApplicationList() {
-      return this.applicationList.filter((item) => {return item.type === this.TIM.TYPES.SNS_APPLICATION_SENT_TO_ME})
+      //return this.applicationList.filter((item) => {return item.type === this.OpenIM.TYPES.SNS_APPLICATION_SENT_TO_ME})
     },
     groupFriend() {
       return function (userIDList) {
@@ -205,8 +221,48 @@ export default {
   },
   mounted() {
     this.$set(this.active, 'friendList',  true)
+    this.filteredList = this.friendList;
+  },
+  created() {
+    this.OpenIM = getOpenIM();
+    this.searchFriendList();
   },
   methods: {
+    searchFriendList() {
+      const keyword = this.searchKeyword.trim().toLowerCase();
+      if (!keyword) {
+        this.OpenIM.getFriendListPage({ offset:0, count:100 })
+          .then(({ data }) => {
+            // 调用成功
+            //this.conversationList= data
+            this.$store.commit('updateFriendList', data)
+            this.filteredList = this.friendList;
+            return;
+          })
+          .catch(({ errCode, errMsg }) => {
+            // 调用失败
+          })
+
+      }
+      this.OpenIM.searchFriends({
+        keywordList: [keyword],
+        isSearchUserID: false,
+        isSearchNickname: true,
+        isSearchRemark: true,
+      })
+        .then(({data}) => {
+          // 调用成功
+          this.$store.commit('updateFriendList', data)
+          this.filteredList = this.friendList;
+        })
+        .catch(({ errCode, errMsg }) => {
+          // 调用失败
+        });
+    },
+    resetSearch() {
+      this.searchKeyword = '';
+      this.searchFriendList();
+    },
     handleOpen(key, keyPath) {
       if(keyPath.length ===1) {
         this.$set(this.active, keyPath[0], true)

+ 3 - 3
src/components/group-live/components/live-chat.vue

@@ -173,7 +173,7 @@ export default {
     this.$bus.$on('group-live-send-gift', (index)=> {
       const message = this.tim.createCustomMessage({
         to: this.groupLiveInfo.roomID,
-        conversationType: this.TIM.TYPES.CONV_GROUP,
+        conversationType: 3,
         payload: {
           data: JSON.stringify({version: '1.0.0' ,'message': `${index}`,'command':'6','action':301}),
           description: '',
@@ -217,7 +217,7 @@ export default {
         groupID: this.groupLiveInfo.roomID
       }).then((imResponse) => {
         const status = imResponse.data.status
-        if (status === this.TIM.TYPES.JOIN_STATUS_SUCCESS || status === this.TIM.TYPES.JOIN_STATUS_ALREADY_IN_GROUP) {
+        if (status === this.OpenIM.TYPES.JOIN_STATUS_SUCCESS || status === this.OpenIM.TYPES.JOIN_STATUS_ALREADY_IN_GROUP) {
           this.sendAvailable = true
         }
       }).catch(() => {})
@@ -244,7 +244,7 @@ export default {
       }
       const message = this.tim.createTextMessage({
         to: this.groupLiveInfo.roomID,
-        conversationType: this.TIM.TYPES.CONV_GROUP,
+        conversationType: 3,
         payload: { text: this.messageContent }
       })
       // 此处用JSON序列化和反序列化对message断链

+ 3 - 3
src/components/group-live/components/live-pusher.vue

@@ -184,7 +184,7 @@ export default {
       await this.tim.createGroup({
         name: this.roomName,
         groupID: this.roomID,
-        type: this.TIM.TYPES.GRP_AVCHATROOM,
+        type: this.OpenIM.TYPES.GRP_AVCHATROOM,
       })
       this.$bus.$emit('join-group-live-avchatroom')
     },
@@ -238,8 +238,8 @@ export default {
       }
       const message = this.tim.createCustomMessage({
         to: this.groupLiveInfo.groupID,
-        conversationType: this.TIM.TYPES.CONV_GROUP,
-        priority: this.TIM.TYPES.MSG_PRIORITY_NORMAL,
+        conversationType: 3,
+        priority: this.OpenIM.TYPES.MSG_PRIORITY_NORMAL,
         payload: {
           data: JSON.stringify(form),
           description: '',

+ 6 - 6
src/components/group/create-group.vue

@@ -38,7 +38,7 @@
           multiple
           filterable
           remote
-          :disabled="form.type === TIM.TYPES.GRP_AVCHATROOM"
+          :disabled="form.type === OpenIM.TYPES.GRP_AVCHATROOM"
           :remote-method="handleSearchUser"
           :loading="loading"
           placeholder="请输入群成员 userID"
@@ -79,7 +79,7 @@ export default {
       form: {
         groupID: '',
         name: '',
-        type: this.TIM.TYPES.GRP_WORK,
+        type: this.OpenIM.TYPES.GRP_WORK,
         avatar: '',
         introduction: '',
         notification: '',
@@ -96,9 +96,9 @@ export default {
   computed: {
     joinOptionDisabled() {
       return [
-        this.TIM.TYPES.GRP_WORK,
-        this.TIM.TYPES.GRP_MEETING,
-        this.TIM.TYPES.GRP_AVCHATROOM
+        this.OpenIM.TYPES.GRP_WORK,
+        this.OpenIM.TYPES.GRP_MEETING,
+        this.OpenIM.TYPES.GRP_AVCHATROOM
       ].includes(this.form.type)
     }
   },
@@ -134,7 +134,7 @@ export default {
         ...this.form,
         memberList: this.form.memberList.map(userID => ({ userID }))
       }
-      if ([this.TIM.TYPES.GRP_WORK, this.TIM.TYPES.GRP_AVCHATROOM].includes(this.form.type)) {
+      if ([this.OpenIM.TYPES.GRP_WORK, this.OpenIM.TYPES.GRP_AVCHATROOM].includes(this.form.type)) {
         delete options.joinOption
       }
       return options

+ 6 - 6
src/components/group/group-list.vue

@@ -66,7 +66,7 @@ export default {
     searchGroupByID(queryString, showInSearchResult) {
       if (queryString.trim().length > 0) {
         this.hideSearchLoading = false
-        this.tim
+        this.OpenIM
           .searchGroupByID(queryString)
           .then(({ data: { group } }) => {
             showInSearchResult([group])
@@ -85,17 +85,17 @@ export default {
       this.$store.commit('updateCreateGroupModelVisible', true)
     },
     applyJoinGroup(group) {
-      this.tim
+      this.OpenIM
         .joinGroup({ groupID: group.groupID })
         .then(async res => {
           switch(res.data.status) {
-            case this.TIM.TYPES.JOIN_STATUS_WAIT_APPROVAL:
+            case this.OpenIM.TYPES.JOIN_STATUS_WAIT_APPROVAL:
               this.$store.commit('showMessage', {
                 message: '申请成功,等待群管理员确认。',
                 type: 'info'
               })
               break
-            case this.TIM.TYPES.JOIN_STATUS_SUCCESS:
+            case this.OpenIM.TYPES.JOIN_STATUS_SUCCESS:
               await this.$store.dispatch(
                 'checkoutConversation',
                 `GROUP${res.data.group.groupID}`
@@ -105,7 +105,7 @@ export default {
                 type: 'success'
               })
               break
-            case this.TIM.TYPES.JOIN_STATUS_ALREADY_IN_GROUP:
+            case this.OpenIM.TYPES.JOIN_STATUS_ALREADY_IN_GROUP:
               this.$store.commit('showMessage', {
                 message: '您已经是群成员了,请勿重复加群哦!',
                 type: 'info'
@@ -131,7 +131,7 @@ export default {
   width 100%
   display flex
   flex-direction column
-  .group-container 
+  .group-container
     overflow-y scroll
   .header-bar
     display: flex;

+ 16 - 10
src/components/layout/side-bar.vue

@@ -14,12 +14,6 @@
             <template v-else>{{totalUnreadCount}}</template>
           </sup>
         </div>
-        <!-- <div
-          id="group-list"
-          class="iconfont icon-group"
-          :class="{ active: showGroupList }"
-          title="群组列表"
-        ></div>
         <div
           id="friend-list"
           class="iconfont icon-contact"
@@ -31,6 +25,13 @@
             <template v-else>{{applicationUnreadCount}}</template>
           </sup>
         </div>
+        <!-- <div
+          id="group-list"
+          class="iconfont icon-group"
+          :class="{ active: showGroupList }"
+          title="群组列表"
+        ></div>
+
         <div
           id="black-list"
           class="iconfont icon-blacklist"
@@ -63,7 +64,7 @@ import ConversationList from '../conversation/conversation-list'
 import GroupList from '../group/group-list'
 import FriendList from '../friend/friend-list'
 import BlackList from '../blacklist/blacklist'
-
+import { getOpenIM,getCbEvents } from '@/utils/openIM';
 const activeName = {
   CONVERSATION_LIST: 'conversation-list',
   GROUP_LIST: 'group-list',
@@ -83,7 +84,8 @@ export default {
   data() {
     return {
       active: activeName.CONVERSATION_LIST,
-      activeName: activeName
+      activeName: activeName,
+      OpenIM:null
     }
   },
   computed: {
@@ -116,6 +118,9 @@ export default {
     })
 
   },
+  created() {
+    this.OpenIM = getOpenIM();
+  },
   methods: {
     checkoutActive(name) {
       this.active = name
@@ -141,9 +146,10 @@ export default {
       }
     },
     handleRefresh() {
+      console.log("好友列表")
       switch (this.active) {
         case activeName.CONVERSATION_LIST:
-          this.tim.getConversationList().catch(error => {
+          this.OpenIM.getAllConversationList().catch(error => {
             this.$store.commit('showMessage', {
               type: 'error',
               message: error.message
@@ -175,7 +181,7 @@ export default {
         })
     },
     getFriendList() {
-      this.tim
+      this.OpenIM
         .getFriendList()
         .then(({ data: friendList }) => {
           this.$store.commit('upadteFriendList', friendList)

+ 170 - 111
src/components/message/merger-message/mergerMessage-item.vue

@@ -2,75 +2,75 @@
     <div class="message-wrapper col-2">
         <div class="content-wrapper">
 <!--文本消息-->
-            <div class="message-container" v-if="message.type === 'TIMTextElem'">
-                <div class="text-message" v-for="(item, index) in contentList" :key="index">
-                    <span  :key="index" v-if="item.name === 'text'">{{ item.text }}</span>
-                    <img v-else-if="item.name === 'img'" :src="item.src" width="20px" height="20px" :key="index"/>
-                </div>
-            </div>
+          <div class="message-container" v-if="message.contentType === 101">
+              <div class="text-message" v-for="(item, index) in contentList" :key="index">
+                  <span  :key="index" v-if="item.name === 'text'">{{ item.text }}</span>
+                  <img v-else-if="item.name === 'img'" :src="item.src" width="20px" height="20px" :key="index"/>
+              </div>
+          </div>
 <!--图片消息-->
-            <div class="message-container" v-else-if="message.type === 'TIMImageElem'">
-                <img class="image-element" :src="payload.imageInfoArray[0].url" @load="onImageLoaded" @click="handlePreview()" />
-            </div>
+          <div class="message-container" v-else-if="message.contentType === 102">
+            <img class="image-element" :src="message.pictureElem.sourcePicture.url" @load="onImageLoaded" @click="handlePreview()" />
+          </div>
 <!--文件消息-->
-            <div class="message-container" v-else-if="message.type === 'TIMFileElem'">
+            <div class="message-container" v-else-if="message.type === 105">
                 <div class="file-element-wrapper" title="单击下载" @click="downloadFile">
                     <div class="file-box">
                         <i class="el-icon-document file-icon"></i>
                         <div class="file-element">
-                            <span class="file-name">{{ payload.fileName }}</span>
-                            <span class="file-size">{{ fileSize }}</span>
+                          <span class="file-name">{{ message.fileElem.fileName }}</span>
+                          <span class="file-size">{{ message.fileElem.fileSize }}</span>
                         </div>
                     </div>
                 </div>
             </div>
 <!--表情消息-->
 
-            <div class="message-container" v-else-if="message.type === 'TIMFaceElem'">
-                <img :src="faceUrl"/>
+            <div class="message-container" v-else-if="message.type === 115">
+              <img :src="faceUrl"/>
             </div>
 <!--视频消息-->
-            <div class="message-container" v-else-if="message.type === TIM.TYPES.MSG_VIDEO">
-                <video
-                        :src="payload.videoUrl"
-                        controls
-                        class="merger-video"
-                        @error="videoError"
-                ></video>
-            </div>
+          <div class="message-container" v-else-if="message.contentType === 104">
+            <video
+              :src="message.videoElem.videoUrl"
+              controls
+              class="merger-video"
+              @error="videoError"
+            ></video>
+          </div>
 <!--音频消息-->
-            <div class="sound-element-wrapper"  v-else-if="message.type === TIM.TYPES.MSG_AUDIO" title="单击播放" @click="play">
-                <i class="iconfont icon-voice"></i>
-                {{ payload.second + '"' }}
-            </div>
- <!--自定义消息-->
-            <div class="message-container" v-else-if="message.type === 'TIMCustomElem'">
-                <div class="custom-element-wrapper">
-                    <div class="survey"  v-if="this.payload.data === 'survey'">
-                        <div class="title">对IM DEMO的评分和建议</div>
-                        <el-rate
-                                v-model="rate"
-                                disabled
-                                show-score
-                                text-color="#ff9900"
-                                score-template="{value}">
-                        </el-rate>
-                        <div class="suggestion">{{this.payload.extension}}</div>
-                    </div>
-                    <span class="text" title="您可以自行解析自定义消息" v-else>
+          <div class="sound-element-wrapper" v-else-if="message.contentType === 103" :title="playStatus === 'playing' ? '单击暂停' : '单击播放'" @click="handleClick">
+            <i class="iconfont icon-voice"></i>
+            {{ message.soundElem.duration}}
+          </div>
+          <!--自定义消息-->
+          <div class="message-container" v-else-if="message.contentType === 110">
+            <div class="custom-element-wrapper">
+              <div class="survey"  v-if="this.payload.data === 'survey'">
+                <div class="title">对IM DEMO的评分和建议</div>
+                <el-rate
+                  v-model="rate"
+                  disabled
+                  show-score
+                  text-color="#ff9900"
+                  score-template="{value}">
+                </el-rate>
+                <div class="suggestion">{{this.payload.extension}}</div>
+              </div>
+              <span class="text" title="您可以自行解析自定义消息" v-else>
                       <template >{{translateCustomMessage(this.payload)}}</template>
                     </span>
-                </div>
             </div>
-<!--合并的消息-->
-            <div class="message-container"  @click="mergerHandler(message)" v-else-if="message.type === TIM.TYPES.MSG_MERGER">
-                <div class="merger-item">
-                    <p class="merger-title">{{payload.title}}</p>
-                    <p class="merger-text" v-for="(item, index) in payload.abstractList" :key="index">
-                        {{item}}
-                    </p>
-                </div>
+          </div>
+          <!--合并的消息-->
+          <div class="message-container"  @click="mergerHandler(message)" v-else-if="message.contentType === 107">
+            <div class="merger-item">
+              <p class="merger-title">{{message.mergeElem.title}}</p>
+              <p class="merger-text" v-for="(item, index) in message.mergeElem.abstractList" :key="index">
+                {{item}}
+              </p>
             </div>
+          </div>
         </div>
 
     </div>
@@ -91,8 +91,8 @@
       },
       payload: {
         type: Object,
-        required: true
-      },
+        default: () => ({})
+      }
 
     },
     components: {
@@ -105,9 +105,19 @@
         relayMessage: {},
         selectedConversation: [],
         messageSelected:[],
+        amr: null,
+        audio: null,
+        isAMR: false,
+        playStatus: 'stopped' // 'playing' | 'paused' | 'stopped'
       }
     },
     computed: {
+      url() {
+        return this.message.soundElem.sourceUrl
+      },
+      second() {
+        return this.message.soundElem.duration
+      },
       ...mapState({
         currentConversation: state => state.conversation.currentConversation,
         currentUserProfile: state => state.imuser.currentUserProfile,
@@ -120,7 +130,7 @@
       },
       // 图片消息
       imageUrl() {
-        const url = this.payload.imageInfoArray[0].imageUrl
+        const url = this.message.pictureElem.sourcePicture.url
         if (typeof url !== 'string') {
           return ''
         }
@@ -149,7 +159,7 @@
 
       // 文件消息大小
       fileSize() {
-        const size = this.payload.fileSize
+        const size = this.message.fileElem.fileSize
         if (size > 1024) {
           if (size / 1024 > 1024) {
             return `${this.toFixed(size / 1024 / 1024)} Mb`
@@ -160,7 +170,7 @@
       },
       // 消息昵称
       from() {
-        const isC2C = this.currentConversation.type === this.TIM.TYPES.CONV_C2C
+        const isC2C = this.currentConversation.type === 1
         // 自己发送的用昵称渲染
         if (this.isMine) {
           return  this.currentUserProfile.nick || this.currentUserProfile.userID
@@ -195,6 +205,7 @@
         return this.message.flow === 'out'
       },
       contentList() {
+        console.log("this.payload",this.payload)
         return decodeText(this.payload)
       },
     },
@@ -227,7 +238,7 @@
       },
       handlePreview() {
         this.$bus.$emit('image-preview', {
-          url: this.payload.imageInfoArray[0].url,
+          url: this.message.pictureElem.sourcePicture.url,
           flag: true
         })
       },
@@ -264,19 +275,19 @@
         let type = ''
         let toUserId = ''
         this.selectedConversation.forEach((item) => {
-          if(item.indexOf(this.TIM.TYPES.CONV_C2C) !== -1) {
-            type = this.TIM.TYPES.CONV_C2C
+          if(item.indexOf(this.OpenIM.TYPES.CONV_C2C) !== -1) {
+            type = 1
             toUserId = item.substring(3,item.length)
           }
-          if(item.indexOf(this.TIM.TYPES.CONV_GROUP) !== -1) {
-            type = this.TIM.TYPES.CONV_GROUP
+          if(item.indexOf(3) !== -1) {
+            type = 3
             toUserId = item.substring(5,item.length)
           }
           const message = this.tim.createForwardMessage({
             to: toUserId,
             conversationType: type,
             payload: this.relayMessage,
-            priority: this.TIM.TYPES.MSG_PRIORITY_NORMAL
+            priority: this.OpenIM.TYPES.MSG_PRIORITY_NORMAL
           })
           this.tim.sendMessage(message).catch(imError => {
             this.$store.commit('showMessage', {
@@ -289,6 +300,7 @@
       },
       // 合并的消息
       mergerHandler(message) {
+        console.log("setMergerMessage",message)
         this.$store.commit('setMergerMessage', message)
         // this.$bus.$emit('mergerMessage', message)
       },
@@ -298,75 +310,122 @@
       },
       // 音频消息
       play() {
-        // 目前移动端的语音消息采用 aac 格式,以前用 amr 格式。默认先用 audio 标签播放,若无法播放则尝试 amr 格式播放。
-        const audio = document.createElement('audio')
-        audio.addEventListener('error', this.tryPlayAMR) // 播放出错,则尝试使用 AMR 播放
-        audio.src = this.payload.url
-        const promise = audio.play()
-        if (promise) {
-          promise.catch(() => {})
+        this.cleanup()
+
+        // 默认使用 HTML5 audio 播放
+        this.audio = new Audio(this.url)
+        console.log(this.audio)
+        this.audio.addEventListener('error', this.tryPlayAMR)
+        this.audio.addEventListener('ended', this.cleanup)
+        this.audio.play()
+          .then(() => {
+            this.playStatus = 'playing'
+          })
+          .catch(() => {
+            // 播放失败 fallback 到 tryPlayAMR
+          })
+      },
+      handleClick() {
+        console.log("this.message.soundElem.sourceUrl",this.message.soundElem.sourceUrl)
+        if (this.playStatus === 'playing') {
+          this.pause()
+        } else if (this.playStatus === 'paused') {
+          this.resume()
+        } else {
+          this.play()
         }
       },
+      pause() {
+        if (this.isAMR && this.amr) {
+          this.amr.pause()
+        } else if (this.audio) {
+          this.audio.pause()
+        }
+        this.playStatus = 'paused'
+      },
+      resume() {
+        if (this.isAMR && this.amr) {
+          this.amr.play()
+        } else if (this.audio) {
+          this.audio.play()
+        }
+        this.playStatus = 'playing'
+      },
       tryPlayAMR() {
-        try {
-          const isIE = /MSIE|Trident|Edge/.test(window.navigator.userAgent)
-          // amr 播放组件库在 IE 不支持
-          if (isIE) {
-            this.$store.commit('showMessage', {
-              message: '您的浏览器不支持该格式的语音消息播放,请尝试更换浏览器,建议使用:谷歌浏览器',
-              type: 'warning'
-            })
-            return
-          }
-          // 动态插入 amr 播放组件库
-          if (!window.BenzAMRRecorder) {
-            const script = document.createElement('script')
-            script.addEventListener('load', this.playAMR)
-            script.src = '/BenzAMRRecorder.js'
-            const firstScript = document.getElementsByTagName('script')[0]
-            firstScript.parentNode.insertBefore(script, firstScript)
-            return
-          }
-          this.playAMR()
-        } catch (error) {
+        this.isAMR = true
+        const isIE = /MSIE|Trident|Edge/.test(window.navigator.userAgent)
+        if (isIE) {
           this.$store.commit('showMessage', {
             message: '您的浏览器不支持该格式的语音消息播放,请尝试更换浏览器,建议使用:谷歌浏览器',
             type: 'warning'
           })
+          return
+        }
+
+        if (!window.BenzAMRRecorder) {
+          const script = document.createElement('script')
+          script.addEventListener('load', this.playAMR)
+          script.src = '/BenzAMRRecorder.js'
+          document.head.appendChild(script)
+          return
         }
+
+        this.playAMR()
       },
       playAMR() {
         if (!this.amr && window.BenzAMRRecorder) {
           this.amr = new window.BenzAMRRecorder()
+          this.amr.onEnded(() => {
+            this.cleanup()
+          })
         }
+
         if (this.amr.isInit()) {
           this.amr.play()
-          return
+          this.playStatus = 'playing'
+        } else {
+          this.amr.initWithUrl(this.url).then(() => {
+            this.amr.play()
+            this.playStatus = 'playing'
+          })
         }
-        this.amr.initWithUrl(this.url).then(() => {
-          this.amr.play()
-        })
+      },
+      cleanup() {
+        if (this.audio) {
+          this.audio.pause()
+          this.audio = null
+        }
+        if (this.amr) {
+          this.amr.stop()
+        }
+        this.playStatus = 'stopped'
       },
       // 文件消息
       downloadFile() {
-        // 浏览器支持fetch则用blob下载,避免浏览器点击a标签,跳转到新页面预览的行为
-        if (window.fetch) {
-          fetch(this.payload.fileUrl)
-            .then(res => res.blob())
-            .then(blob => {
-              let a = document.createElement('a')
-              let url = window.URL.createObjectURL(blob)
-              a.href = url
-              a.download = this.payload.fileName
-              a.click()
-            })
-        } else {
-          let a = document.createElement('a')
-          a.href = this.payload.fileUrl
-          a.target = '_blank'
-          a.download = this.filename
-          a.click()
-        }
+        const fileUrl = this.message.fileElem.sourceUrl;
+        const fileName = this.message.fileElem.fileName;
+        console.log(fileUrl)
+        fetch(fileUrl)
+          .then(response => {
+            if (!response.ok) throw new Error(`下载失败,状态码:${response.status}`);
+            return response.blob();
+          })
+          .then(blob => {
+            /*const mime = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
+            const docBlob = new Blob([blob], { type: mime });*/ // 💡 明确指定 MIME 类型
+            const blobUrl = window.URL.createObjectURL(blob);
+
+            const a = document.createElement('a');
+            a.href = blobUrl;
+            a.download = fileName;
+            document.body.appendChild(a);
+            a.click();
+            document.body.removeChild(a);
+            window.URL.revokeObjectURL(blobUrl); // 释放资源
+          })
+          .catch(err => {
+            console.error('下载失败:', err);
+          });
       }
     }
   }

+ 7 - 6
src/components/message/merger-message/message-merger.vue

@@ -6,14 +6,14 @@
                     <div   v-for="(messageItem, index) in mergerList(mergerMessage)" :key="index">
                         <div class="message-item">
                             <div class="avatar-box">
-                                <avatar class="group-member-avatar" :src="messageItem.avatar"/>
+                                <avatar class="group-member-avatar" :src="messageItem.senderFaceUrl"/>
                             </div>
                             <div class="container-box">
                                 <div class="nick-date">
-                                    <div class="name text-ellipsis">{{messageItem.nick || messageItem.from || '小晨曦'}}</div>
-                                    <div class="date">{{ getDate(messageItem.time) }}</div>
+                                    <div class="name text-ellipsis">{{messageItem.senderNickname || messageItem.from || '小晨曦'}}</div>
+                                    <div class="date">{{ getDate(messageItem.sendTime) }}</div>
                                 </div>
-                                <merger-message-item v-for="(item,index) in messageItem.messageBody" :key="index" :message="item" :payload="item.payload"/>
+                                <merger-message-item  :message="messageItem" :payload="messageItem.textElem"/>
                             </div>
                         </div>
                         <el-divider></el-divider>
@@ -48,7 +48,8 @@
       }),
       mergerList() {
         return function(message) {
-          return message.payload.messageList
+          console.log(message)
+          return message.mergeElem.multiMessage
         }
       }
     },
@@ -65,7 +66,7 @@
     },
     methods: {
       getDate(time) {
-        return getFullDate(new Date(time * 1000))
+        return getFullDate(new Date(time))
       },
       onScroll({ target: { scrollTop } }) {
         let messageListNode = this.$refs['message-list']

+ 130 - 62
src/components/message/merger-message/message-relay.vue

@@ -13,7 +13,7 @@
 <script>
   import { mapState } from 'vuex'
   import ConversationSelectedList from '../../conversation/conversation-selected-list'
-
+  import { getOpenIM } from '@/utils/openIM';
   export default {
     name: 'MessageBubble',
     components: {
@@ -21,6 +21,7 @@
     },
     data() {
       return {
+        OpenIM:null,
         isTimeout: false,
         showConversationList: false,
         selectedConversation: [],
@@ -28,6 +29,7 @@
       }
     },
     created() {
+      this.OpenIM = getOpenIM();
     },
     mounted() {
       if (this.$refs.dropdown && this.$refs.dropdown.$el) {
@@ -58,14 +60,15 @@
 
       },
       sendSingleMessage(to, type, message) {
-        const _message = this.tim.createForwardMessage({
-          to: to,
-          conversationType: type,
-          payload: message,
-          priority: this.TIM.TYPES.MSG_PRIORITY_NORMAL
-        })
-        this.$store.commit('pushCurrentMessageList', _message)   // ??
-        return _message
+        return this.OpenIM.createForwardMessage(message)
+          .then(({ data }) => {
+            console.log("创建转发消息成功111", data);
+            this.$store.commit('pushCurrentMessageList', data);
+            return data
+          })
+          .catch(({ errCode, errMsg }) => {
+            console.error("创建转发消息失败", errCode, errMsg);
+          });
       },
       mergerSort() {
         this.selectedMessageList.sort((a, b) =>  {
@@ -76,42 +79,65 @@
           }
         })
       },
-      mergerMessage(to, type) {
-        let _abstractList = [], _count = 0, _title = ''
-        _count = this.selectedMessageList.length < 3 ? this.selectedMessageList.length : 3
-
-        for(let i = 0; i < _count; i++) {
-          _abstractList.push(this.setAbstractList(this.selectedMessageList[i]))
+      updateConversationList(){
+        this.OpenIM.getAllConversationList().then(({ data }) => {
+            // 调用成功
+            console.log(data,"147852")
+            this.$store.commit('updateConversationList', data)
+          })
+          .catch(({ errCode, errMsg }) => {
+            // 调用失败
+          })
+      },
+      mergerMessage(to, type, messageList) {
+        let _summaryList = [];
+        const _count = this.selectedMessageList.length < 3 ? this.selectedMessageList.length : 3;
+        for (let i = 0; i < _count; i++) {
+          _summaryList.push(this.setAbstractList(this.selectedMessageList[i]));
         }
-        _title = this.selectedMessageList[0].conversationType === 'GROUP' ? '群聊的聊天记录' : `${this.selectedMessageList[0].nick || this.selectedMessageList[0].from} 和 ${this.selectedMessageList[0].to} 的聊天记录`
-        let message = this.tim.createMergerMessage({
-          to: to,
-          conversationType: type,
-          payload: {
-            messageList: this.selectedMessageList ,
-            title: _title,
-            abstractList: _abstractList,
-            compatibleText: '请升级IMSDK到v2.10.1或更高版本查看此消息'
-          }
+
+        /*const _title = this.selectedMessageList[0].conversationType === 'GROUP'
+          ? '群聊的聊天记录'
+          : `${this.selectedMessageList[0].senderNickname || this.selectedMessageList[0].from} 和 ${this.selectedMessageList[0].to} 的聊天记录`;*/
+        const _title = "聊天记录"
+        console.log("创建合并转发消息参数",this.selectedMessageList)
+        return this.OpenIM.createMergerMessage({
+          messageList: this.selectedMessageList,
+          title: _title,
+          summaryList: _summaryList
         })
-        this.$store.commit('pushCurrentMessageList', message)   // ??
-        return message
+          .then(({ data }) => {
+            // 将返回的合并消息对象存入 Vuex 或其他状态管理
+            this.$store.commit('pushCurrentMessageList', data);
+            console.log("wwwwwwwwwww",data)
+            return data;  // 返回创建的合并消息对象
+          })
+          .catch(({ errCode, errMsg }) => {
+            console.error('合并消息创建失败:', errCode, errMsg);
+            throw new Error(errMsg || '合并消息创建失败');
+          });
       },
+
       async messageRelay() {
+
         let _type = '', _to = ''
+        const myId = this.$store.getters.userID
         for (let item of this.selectedConversation) {
-          if(item.indexOf(this.TIM.TYPES.CONV_C2C) !== -1) {
-            _type = this.TIM.TYPES.CONV_C2C
-            _to = item.substring(3, item.length)
+          if(item.startsWith("si")) {
+            _type = 1
+            _to = item.replace("si","").replace(myId,"").replaceAll("_","");
           }
-          if(item.indexOf(this.TIM.TYPES.CONV_GROUP) !== -1) {
-            _type = this.TIM.TYPES.CONV_GROUP
+          if(item.startsWith("sg")) {
+            _type = 3
             _to = item.substring(5, item.length)
           }
           // 排序
+          console.log(this.$store.state.conversation,"||||")
           this.mergerSort()
           if (this.relayType === 1) {
-            let message = this.sendSingleMessage(_to, _type, this.relayMessage)
+            this.relayMessage.recvID = _to
+            let message =await this.sendSingleMessage(_to, _type, this.relayMessage)
+            console.log(message,"message")
             this.sendMessageHandler(message)
           }
 
@@ -123,23 +149,44 @@
               })
               return
             }
+
             for (let selectedMessage of this.selectedMessageList) {
+              console.log("selectedMessage",selectedMessage)
+              selectedMessage.recvID = _to
               let message = await this.sendSingleMessage(_to, _type, selectedMessage)
-              await this.tim.sendMessage(message)
+              console.log("转发类型",message)
+              const sendText={
+                message:message,
+                recvID:message.recvID,
+                groupID:"",
+                offlinePushInfo : {
+                  title:message.senderNickname,
+                  desc:this.setAbstractList(message),
+                  iOSPushSound:"",
+                  iOSBadgeCount:true,
+                  operatorUserID:"",
+                  ex:""
+                }
+              }
+              console.log("this.OpenIM",this.OpenIM)
+              await this.OpenIM.sendMessage(sendText)
                 .then((res) => {
                   return res
                 })
-                .catch(imError => {
+                .catch(error => {
                   this.$store.commit('showMessage', {
-                    message: imError.message,
-                    type: 'error'
+                    type: 'error',
+                    message: error.message
                   })
                 })
 
             }
           }
           if (this.relayType === 3) {
-            let message = this.mergerMessage(_to, _type)
+            let message =await this.mergerMessage(_to, _type,this.selectedMessageList)
+
+            message.recvID = _to
+            console.log("wwwwwwwwwwwwwwwwww",message)
             this.sendMessageHandler(message)
           }
         }
@@ -148,37 +195,58 @@
       },
 
       sendMessageHandler(message) {
-        this.tim.sendMessage(message).catch(imError => {
-            this.$store.commit('showMessage', {
-              message: imError.message,
-              type: 'error'
-            })
+        console.log("this.OpenIM111",message)
+        const sendText={
+          message:message,
+          recvID:message.recvID,
+          groupID:"",
+          offlinePushInfo : {
+            title:message.senderNickname,
+            desc:this.setAbstractList(message),
+            iOSPushSound:"",
+            iOSBadgeCount:true,
+            operatorUserID:"",
+            ex:""
+          }
+        }
+        this.OpenIM.sendMessage(sendText).then(({ data }) => {
+          const msgList = Array.isArray(data) ? data : [data];
+          this.$store.commit('pushCurrentMessageList', msgList)
+          this.updateConversationList()
+          this.$bus.$emit('scroll-bottom')
+        }).catch(({ errCode, errMsg }) => {
+          // 调用失败
+          this.$store.commit('showMessage', {
+            message: errMsg.message,
+            type: 'error'
           })
+        });
       },
       setAbstractList(message) {
-        let nick = message.nick || message.from
+        console.log(message,"message1111111111111111111111")
+        let nickname = message.senderNickname || message.from
         let text = ''
-        switch (message.type) {
-          case this.TIM.TYPES.MSG_TEXT:
-            text = message.payload.text || ''
+        switch (message.contentType) {
+          case 101:
+            text = message.textElem.content || ''
             if (text.length > 20) {
               text = text.slice(0, 20)
             }
-            return `${nick}: ${text}`
-          case this.TIM.TYPES.MSG_MERGER:
-            return `${nick}: [聊天记录]`
-          case this.TIM.TYPES.MSG_IMAGE:
-            return `${nick}: [图片]`
-          case this.TIM.TYPES.MSG_AUDIO:
-            return `${nick}: [音频]`
-          case this.TIM.TYPES.MSG_VIDEO:
-            return `${nick}: [视频]`
-          case this.TIM.TYPES.MSG_CUSTOM:
-            return `${nick}: [自定义消息]`
-          case this.TIM.TYPES.MSG_FILE:
-            return `${nick}: [文件]`
-          case this.TIM.TYPES.MSG_FACE:
-            return `${nick}: [动画表情]`
+            return `${nickname}: ${text}`
+          case 107:
+            return `${nickname}: [聊天记录]`
+          case 102:
+            return `${nickname}: [图片]`
+          case 103:
+            return `${nickname}: [音频]`
+          case 104:
+            return `${nickname}: [视频]`
+          case 110:
+            return `${nickname}: [自定义消息]`
+          case 105:
+            return `${nickname}: [文件]`
+          case 115:
+            return `${nickname}: [动画表情]`
         }
       },
       handleDropDownMousedown(e) {

+ 30 - 14
src/components/message/message-bubble.vue

@@ -5,7 +5,7 @@
         <div v-if="isMine && messageReadByPeer" class="message-status">
           <span>{{messageReadByPeer}}</span>
         </div>
-        <div class="message-content" :class="bubbleStyle">
+        <div v-if="message.contentType!==2101" class="message-content" :class="bubbleStyle">
           <slot></slot>
         </div>
       </div>
@@ -15,7 +15,7 @@
         <el-dropdown-item command="merger" v-show="message.status !=='fail'">多选</el-dropdown-item>
       </el-dropdown-menu>
     </el-dropdown>
-    <div class="group-tip-element-wrapper" v-if="message.isRevoked">
+    <div class="group-tip-element-wrapper" v-if="isRevoked">
       {{text}}
       <el-button type="text" size="mini" class="edit-button" v-show="isEdit" @click="reEdit">&nbsp;重新编辑</el-button>
     </div>
@@ -23,18 +23,21 @@
 </template>
 
 <script>
-
+  import { getOpenIM } from '@/utils/openIM';
+  import {mapGetters, mapState} from "vuex";
   export default {
   name: 'MessageBubble',
   components: {
   },
   data() {
     return {
+      OpenIM:null,
       isTimeout: false,
       showConversationList: false,
       relayMessage: {},
       selectedConversation: [],
       testMergerMessage: {}
+
     }
   },
   props: {
@@ -50,6 +53,7 @@
     }
   },
   created() {
+    this.OpenIM = getOpenIM();
     this.isTimeoutHandler()
   },
   mounted() {
@@ -78,23 +82,28 @@
       return classString
     },
     text() {
-      if (this.message.conversationType === this.TIM.TYPES.CONV_C2C && !this.isMine) {
+      if (this.message.sessionType === 1 && !this.isMine) {
         return '对方撤回了一条消息'
       }
-      if (this.message.conversationType === this.TIM.TYPES.CONV_GROUP && !this.isMine) {
+      if (this.message.sessionType === 3 && !this.isMine) {
         return `${this.message.from}撤回了一条消息`
       }
       return '你撤回了一条消息'
     },
+    isRevoked() {
+      return this.message.contentType === 2101
+    },
     messageReadByPeer() {
-      if (this.message.status !== 'success') {
+      if (this.message.status !== 2) {
         return false
       }
-      if (this.message.conversationType === this.TIM.TYPES.CONV_C2C && this.message.isPeerRead) {
-        return '已读'
-      }
-      if (this.message.conversationType === this.TIM.TYPES.CONV_C2C && !this.message.isPeerRead) {
-        return '未读'
+      if (this.message.contentType!== 2101){
+        if (this.message.sessionType === 1 && this.message.isRead) {
+          return '已读'
+        }
+        if (this.message.sessionType === 1 && !this.message.isRead) {
+          return '未读'
+        }
       }
       return ''
     },
@@ -102,7 +111,7 @@
       if (!this.isMine) {
         return false
       }
-      if (this.message.type !== this.TIM.TYPES.MSG_TEXT) {
+      if (this.message.contentType !== 101) {
         return false
       }
       if (this.isTimeout) {
@@ -124,7 +133,14 @@
     handleCommand(command) {
       switch (command) {
         case 'revoke':
-          this.tim.revokeMessage(this.message).then(() => {
+          this.OpenIM.revokeMessage({
+            conversationID:this.$store.state.conversation.currentConversation.conversationID,
+            clientMsgID:this.message.clientMsgID
+          }).then(() => {
+            this.$store.dispatch(
+              'checkoutConversation',
+              this.$store.state.conversation.currentConversation,
+            )
             this.isTimeoutHandler()
           }).catch((err) => {
             this.$store.commit('showMessage', {
@@ -148,7 +164,7 @@
     },
     isTimeoutHandler() { // 从发送消息时间开始算起,两分钟内可以编辑
       let now = new Date()
-      if (parseInt(now.getTime() / 1000) - this.message.time > 2 * 60) {
+      if (parseInt(now.getTime()) - this.message.sendTime > 2 * 60*1000) {
         this.isTimeout = true
         return
       }

+ 32 - 15
src/components/message/message-elements/custom-element.vue

@@ -1,25 +1,32 @@
 <template>
 <message-bubble :isMine=isMine :message=message>
   <div class="custom-element-wrapper">
-    <span v-if="payload.data === 'startInquiry'" class="text"   >
+    <!--<span v-if="payload.customType === 200" class="text"  >
+      {{text}}
+    </span>-->
+    <span v-if="payload.payload.data === 'startInquiry'" class="text"   >
       {{text}}
     </span>
-    <span v-if="payload.data === 'finishInquiry'" class="text"   >
+    <span v-else-if="payload.payload.data === 'finishInquiry'" class="text"   >
       {{text}}
     </span>
-    <span v-if="payload.data === 'prescribe'" class="text"   >
-      处方单
+    <span v-else-if="payload.payload.data === 'prescribe'" class="text"   >
+      电子处方单
     </span>
-    <span v-if="payload.data === 'follow'" class="text" @click="openFollow(payload)"   >
+    <span v-else-if="payload.payload.data === 'follow'" class="text" @click="openFollow(payload.payload)"   >
       随访单
     </span>
-    <span v-if="payload.data === 'report'" class="text"   >
+    <span v-else-if="payload.payload.data === 'report'" class="text"   >
       问诊报告单
     </span>
-    <span v-if="payload.data === 'drugReport'" class="text" @click="openDrugReport(payload)"  >
+    <span v-else-if="payload.payload.data === 'drugReport'" class="text" @click="openDrugReport(payload.payload)"  >
       用药报告单
     </span>
-    <span v-else    >
+    <span v-else-if="payload.payload.data === 'order'" class="text"  >
+      问诊订单
+    </span>
+
+    <span v-else  >
       {{text}}
     </span>
     <el-drawer  :append-to-body="true" :with-header="false" size="75%" :title="show.title" :visible.sync="show.open">
@@ -32,7 +39,7 @@
 
 <script>
 import { mapState } from 'vuex'
-import MessageBubble from '../message-bubble'
+import MessageBubble from '@/components/message/message-bubble.vue'
 import followDetails from '@/views/components/follow/followDetails.vue';
 import drugReportDetails from '@/views/components/drugReport/drugReportDetails.vue';
 export default {
@@ -75,7 +82,7 @@ export default {
   methods: {
     openFollow(data){
       var that=this;
-      var followId=JSON.parse(data.extension).followId;
+      var followId=data.extension.followId;
       this.show.open=true;
       this.show.type=1;
       this.show.title="随访单"
@@ -84,6 +91,7 @@ export default {
       }, 500);
     },
     openDrugReport(data){
+      console.log("qwertttttttt",data)
       var that=this;
       var reportId=data.description;
       this.show.open=true;
@@ -95,7 +103,7 @@ export default {
     },
     translateCustomMessage(payload) {
       let videoPayload = {}
-      try{
+      /*try{
         videoPayload = JSON.parse(payload.data)
       } catch(e) {
         videoPayload = {}
@@ -107,12 +115,21 @@ export default {
         videoPayload.roomId = videoPayload.roomId.toString()
         videoPayload.isFromGroupLive = 1
         return videoPayload
+      }*/
+      /*console.log("qwerasdf",payload)
+      console.log("qwerasdf",payload.data.mediaType==='video')
+      if (payload.data.mediaType==='video'){
+        return '视频通话'
       }
-      if(payload.text) {
-        return payload.text
+      if (payload.data.mediaType==='audio'){
+        return '语音通话'
+      }*/
+      if(payload.payload.text) {
+        return payload.payload.text
       }else{
-        var obj=JSON.parse(payload.extension);
-        return obj.title
+        return payload.payload.extension.title
+        /*var obj=JSON.parse(payload.extension);
+        return obj.title*/
       }
     }
   }

+ 28 - 20
src/components/message/message-elements/file-element.vue

@@ -1,5 +1,5 @@
 <template>
-<message-bubble :isMine=isMine :message=message>  
+<message-bubble :isMine=isMine :message=message>
   <div class="file-element-wrapper" title="单击下载" @click="downloadFile">
     <div class="header">
       <i class="el-icon-document file-icon"></i>
@@ -44,7 +44,7 @@ export default {
       return this.payload.fileName
     },
     fileUrl() {
-      return this.payload.fileUrl
+      return this.payload.sourceUrl
     },
     size() {
       const size = this.payload.fileSize
@@ -68,24 +68,32 @@ export default {
       return number.toFixed(precision)
     },
     downloadFile() {
-      // 浏览器支持fetch则用blob下载,避免浏览器点击a标签,跳转到新页面预览的行为
-      if (window.fetch) {
-        fetch(this.fileUrl)
-          .then(res => res.blob())
-          .then(blob => {
-            let a = document.createElement('a')
-            let url = window.URL.createObjectURL(blob)
-            a.href = url
-            a.download = this.fileName
-            a.click()
-          })
-      } else {
-        let a = document.createElement('a')
-        a.href = this.fileUrl
-        a.target = '_blank'
-        a.download = this.filename
-        a.click()
-      }
+      console.log(this.payload)
+      console.log(this.message)
+      const fileUrl = this.fileUrl;
+      const fileName = this.fileName;
+      console.log(fileUrl)
+      fetch(fileUrl)
+        .then(response => {
+          if (!response.ok) throw new Error(`下载失败,状态码:${response.status}`);
+          return response.blob();
+        })
+        .then(blob => {
+          const mime = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
+          const docBlob = new Blob([blob], { type: mime }); // 💡 明确指定 MIME 类型
+          const blobUrl = window.URL.createObjectURL(docBlob);
+
+          const a = document.createElement('a');
+          a.href = blobUrl;
+          a.download = fileName;
+          document.body.appendChild(a);
+          a.click();
+          document.body.removeChild(a);
+          window.URL.revokeObjectURL(blobUrl); // 释放资源
+        })
+        .catch(err => {
+          console.error('下载失败:', err);
+        });
     }
   }
 }

+ 1 - 1
src/components/message/message-elements/group-system-notice-element.vue

@@ -69,7 +69,7 @@ export default {
       return translateGroupSystemNotice(this.message)
     },
     title() {
-      if (this.message.type === this.TIM.TYPES.MSG_GRP_SYS_NOTICE) {
+       if (this.message.type === this.OpenIM.TYPES.MSG_GRP_SYS_NOTICE) {
         return '群系统通知'
       }
       return '系统通知'

+ 7 - 7
src/components/message/message-elements/group-tip-element.vue

@@ -37,17 +37,17 @@
       let nick = message.nick || ((message.from === this.userID) && this.currentUserProfile.nick) ||  message.from
       const userName = message.nick || message.payload.userIDList.join(',')
       switch (message.payload.operationType) {
-        case this.TIM.TYPES.GRP_TIP_MBR_JOIN:
+        case this.OpenIM.TYPES.GRP_TIP_MBR_JOIN:
           return `群成员:${userName} 加入群组`
-        case this.TIM.TYPES.GRP_TIP_MBR_QUIT:
+        case this.OpenIM.TYPES.GRP_TIP_MBR_QUIT:
           return `群成员:${userName} 退出群组`
-        case this.TIM.TYPES.GRP_TIP_MBR_KICKED_OUT:
+        case this.OpenIM.TYPES.GRP_TIP_MBR_KICKED_OUT:
           return `群成员:${userName} 被${message.payload.operatorID}踢出群组`
-        case this.TIM.TYPES.GRP_TIP_MBR_SET_ADMIN:
+        case this.OpenIM.TYPES.GRP_TIP_MBR_SET_ADMIN:
           return `群成员:${userName} 成为管理员`
-        case this.TIM.TYPES.GRP_TIP_MBR_CANCELED_ADMIN:
+        case this.OpenIM.TYPES.GRP_TIP_MBR_CANCELED_ADMIN:
           return `群成员:${userName} 被撤销管理员`
-        case this.TIM.TYPES.GRP_TIP_GRP_PROFILE_UPDATED:
+        case this.OpenIM.TYPES.GRP_TIP_GRP_PROFILE_UPDATED:
           return '群资料修改'
         case this.callTips:
           if(message.payload.text.indexOf('结束群聊')> -1) {
@@ -55,7 +55,7 @@
           }else {
             return  `"${nick}" ${message.payload.text}`
           }
-        case this.TIM.TYPES.GRP_TIP_MBR_PROFILE_UPDATED:
+        case this.OpenIM.TYPES.GRP_TIP_MBR_PROFILE_UPDATED:
           for (let member of message.payload.memberList) {
             if (member.muteTime > 0) {
               return `群成员:${member.userID}被禁言${member.muteTime}秒`

+ 4 - 3
src/components/message/message-elements/image-element.vue

@@ -8,7 +8,7 @@
       :color="percentage => (percentage === 100 ? '#67c23a' : '#409eff')"
     />
 
- 
+
   </message-bubble>
 </template>
 
@@ -38,7 +38,8 @@ export default {
   computed: {
     ...mapGetters(['imgUrlList']),
     imageUrl() {
-      const url = this.payload.imageInfoArray[0].url
+      console.log("this.payload",this.payload)
+      const url = this.payload.sourcePicture.url
       if (typeof url !== 'string') {
         return ''
       }
@@ -57,7 +58,7 @@ export default {
     },
     handlePreview() {
       this.$bus.$emit('image-preview', {
-        url: this.payload.imageInfoArray[0].url
+        url: this.payload.sourcePicture.url
       })
     }
   }

+ 1 - 1
src/components/message/message-elements/merger-element.vue

@@ -19,7 +19,7 @@
     props: {
       payload: {
         type: Object,
-        required: true
+        default: () => ({}),
       },
       message: {
         type: Object,

+ 119 - 74
src/components/message/message-elements/sound-element.vue

@@ -1,63 +1,95 @@
 <template>
-<message-bubble :isMine=isMine :message=message>
-  <div class="sound-element-wrapper" title="单击播放" @click="play">
-    <i class="iconfont icon-voice"></i>
-    {{ second + '"' }}
-  </div>
-</message-bubble>
+  <message-bubble :isMine="isMine" :message="message">
+    <div class="sound-element-wrapper" :title="playStatus === 'playing' ? '单击暂停' : '单击播放'" @click="handleClick">
+      <i class="iconfont icon-voice"></i>
+      {{ second + '"' }}
+    </div>
+  </message-bubble>
 </template>
 
 <script>
-import MessageBubble from '../message-bubble'
-export default {
-  name: 'SoundElement',
-  props: {
-    payload: {
-      type: Object,
-      required: true
+  import MessageBubble from '@/components/message/message-bubble.vue'
+
+  export default {
+    name: 'SoundElement',
+    props: {
+      payload: {
+        type: Object,
+        required: true
+      },
+      message: {
+        type: Object,
+        required: true
+      },
+      isMine: {
+        type: Boolean
+      }
     },
-    message: {
-      type: Object,
-      required: true
+    components: {
+      MessageBubble
     },
-    isMine: {
-      type: Boolean
-    }
-  },
-  components: {
-    MessageBubble
-  },
-  data() {
-    return {
-      amr: null
-    }
-  },
-  computed: {
-    url() {
-      return this.payload.url
-    },
-    size() {
-      return this.payload.size
+    data() {
+      return {
+        amr: null,
+        audio: null,
+        isAMR: false,
+        playStatus: 'stopped' // 'playing' | 'paused' | 'stopped'
+      }
     },
-    second() {
-      return this.payload.second
-    }
-  },
-  methods: {
-    play() {
-      // 目前移动端的语音消息采用 aac 格式,以前用 amr 格式。默认先用 audio 标签播放,若无法播放则尝试 amr 格式播放。
-      const audio = document.createElement('audio')
-      audio.addEventListener('error', this.tryPlayAMR) // 播放出错,则尝试使用 AMR 播放
-      audio.src = this.url
-      const promise = audio.play()
-      if (promise) {
-        promise.catch(() => {})
+    computed: {
+      url() {
+        return this.payload.sourceUrl
+      },
+      second() {
+        return this.payload.duration
       }
     },
-    tryPlayAMR() {
-      try {
+    methods: {
+      handleClick() {
+        console.log(this.payload.sourceUrl)
+        if (this.playStatus === 'playing') {
+          this.pause()
+        } else if (this.playStatus === 'paused') {
+          this.resume()
+        } else {
+          this.play()
+        }
+      },
+      play() {
+        this.cleanup()
+
+        // 默认使用 HTML5 audio 播放
+        this.audio = new Audio(this.url)
+        console.log(this.audio)
+        this.audio.addEventListener('error', this.tryPlayAMR)
+        this.audio.addEventListener('ended', this.cleanup)
+        this.audio.play()
+          .then(() => {
+            this.playStatus = 'playing'
+          })
+          .catch(() => {
+            // 播放失败 fallback 到 tryPlayAMR
+          })
+      },
+      pause() {
+        if (this.isAMR && this.amr) {
+          this.amr.pause()
+        } else if (this.audio) {
+          this.audio.pause()
+        }
+        this.playStatus = 'paused'
+      },
+      resume() {
+        if (this.isAMR && this.amr) {
+          this.amr.play()
+        } else if (this.audio) {
+          this.audio.play()
+        }
+        this.playStatus = 'playing'
+      },
+      tryPlayAMR() {
+        this.isAMR = true
         const isIE = /MSIE|Trident|Edge/.test(window.navigator.userAgent)
-        // amr 播放组件库在 IE 不支持
         if (isIE) {
           this.$store.commit('showMessage', {
             message: '您的浏览器不支持该格式的语音消息播放,请尝试更换浏览器,建议使用:谷歌浏览器',
@@ -65,42 +97,55 @@ export default {
           })
           return
         }
-        // 动态插入 amr 播放组件库
+
         if (!window.BenzAMRRecorder) {
           const script = document.createElement('script')
           script.addEventListener('load', this.playAMR)
           script.src = '/BenzAMRRecorder.js'
-          const firstScript = document.getElementsByTagName('script')[0]
-          firstScript.parentNode.insertBefore(script, firstScript)
+          document.head.appendChild(script)
           return
         }
+
         this.playAMR()
-      } catch (error) {
-        this.$store.commit('showMessage', {
-          message: '您的浏览器不支持该格式的语音消息播放,请尝试更换浏览器,建议使用:谷歌浏览器',
-          type: 'warning'
-        })
+      },
+      playAMR() {
+        if (!this.amr && window.BenzAMRRecorder) {
+          this.amr = new window.BenzAMRRecorder()
+          this.amr.onEnded(() => {
+            this.cleanup()
+          })
+        }
+
+        if (this.amr.isInit()) {
+          this.amr.play()
+          this.playStatus = 'playing'
+        } else {
+          this.amr.initWithUrl(this.url).then(() => {
+            this.amr.play()
+            this.playStatus = 'playing'
+          })
+        }
+      },
+      cleanup() {
+        if (this.audio) {
+          this.audio.pause()
+          this.audio = null
+        }
+        if (this.amr) {
+          this.amr.stop()
+        }
+        this.playStatus = 'stopped'
       }
     },
-    playAMR() {
-      if (!this.amr && window.BenzAMRRecorder) {
-        this.amr = new window.BenzAMRRecorder()
-      }
-      if (this.amr.isInit()) {
-        this.amr.play()
-        return
-      }
-      this.amr.initWithUrl(this.url).then(() => {
-        this.amr.play()
-      })
+    beforeDestroy() {
+      this.cleanup()
     }
   }
-}
 </script>
 
 <style lang="stylus" scoped>
-.sound-element-wrapper {
-  padding: 0px 10px;
-  cursor: pointer;
-}
+  .sound-element-wrapper {
+    padding: 0px 10px;
+    cursor: pointer;
+  }
 </style>

+ 3 - 1
src/components/message/message-elements/text-element.vue

@@ -39,7 +39,9 @@ export default {
   },
   computed: {
     contentList() {
-      return decodeText(this.payload)
+      if (this.message.contentType===101){
+        return decodeText(this.payload)
+      }
     }
   }
 }

+ 6 - 4
src/components/message/message-footer.vue

@@ -20,13 +20,14 @@ export default {
     ...mapState({
       currentConversation: state => state.conversation.currentConversation,
       currentUserProfile: state => state.imuser.currentUserProfile,
-      currentMemberList: state => state.group.currentMemberList
+      currentMemberList: state => state.group.currentMemberList,
+      userID: state => state.imuser.userID,
     }),
     date() {
-      return getFullDate(new Date(this.message.time * 1000))
+      return getFullDate(new Date(this.message.sendTime))
     },
     from() {
-      const isC2C = this.currentConversation.type === this.TIM.TYPES.CONV_C2C
+      const isC2C = this.currentConversation.conversationType === 1
       // 自己发送的用昵称渲染
       if (this.isMine) {
         return this.currentUserProfile.nick || this.currentUserProfile.userID
@@ -42,7 +43,8 @@ export default {
       return this.message.nick || this.message.from
     },
     isMine() {
-      return this.message.flow === 'out'
+
+      return this.message.sendID ===  this.$store.getters.userID
     }
   }
 }

+ 3 - 3
src/components/message/message-header.vue

@@ -26,7 +26,7 @@ export default {
       return getFullDate(new Date(this.message.time * 1000))
     },
     from() {
-      const isC2C = this.currentConversation.type === this.TIM.TYPES.CONV_C2C
+      const isC2C = this.currentConversation.type === 1
       // 自己发送的用昵称渲染
       if (this.isMine) {
         return this.message.nameCard || this.currentUserProfile.nick || this.currentUserProfile.userID
@@ -37,8 +37,8 @@ export default {
           this.currentConversation.userProfile.nick ||
           this.currentConversation.userProfile.userID
         )
-      } else if (this.currentConversation.type === this.TIM.TYPES.CONV_SYSTEM) {
-        return this.message.type === this.TIM.TYPES.MSG_GRP_SYS_NOTICE
+      } else if (this.currentConversation.type === 4) {
+        return this.message.type === this.OpenIM.TYPES.MSG_GRP_SYS_NOTICE
           ? '群系统通知'
           : '系统通知'
       }

+ 88 - 66
src/components/message/message-item.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="message-wrapper" :class="messagePosition">
     <div
-            v-if="currentConversationType === TIM.TYPES.CONV_C2C"
+            v-if="currentConversationType === 1"
             class="c2c-layout"
             :class="messagePosition"
     >
@@ -14,70 +14,70 @@
         <div class="content-wrapper">
           <message-status-icon v-if="isMine" :message="message" />
           <text-element
-                  v-if="message.type === TIM.TYPES.MSG_TEXT"
+                  v-if="message.contentType === 101||message.contentType===2101"
                   :isMine="isMine"
-                  :payload="message.payload"
+                  :payload="message.contentType === 101 ? message.textElem : {}"
                   :message="message"
           />
           <image-element
-                  v-else-if="message.type === TIM.TYPES.MSG_IMAGE"
+                  v-else-if="message.contentType === 102"
                   :isMine="isMine"
-                  :payload="message.payload"
+                  :payload="message.pictureElem"
                   :message="message"
           />
           <file-element
-                  v-else-if="message.type === TIM.TYPES.MSG_FILE"
+                  v-else-if="message.contentType === 105"
                   :isMine="isMine"
-                  :payload="message.payload"
+                  :payload="message.fileElem"
                   :message="message"
           />
           <sound-element
-                  v-else-if="message.type === TIM.TYPES.MSG_SOUND"
+                  v-else-if="message.contentType === 103"
                   :isMine="isMine"
-                  :payload="message.payload"
+                  :payload="message.soundElem"
                   :message="message"
           />
           <group-tip-element
-                  v-else-if="message.type===TIM.TYPES.MSG_GRP_TIP"
-                  :payload="message.payload"
+                  v-else-if="message.contentType===1519"
+                  :payload="message.textElem"
                   :message="message"
           />
           <group-system-notice-element
-                  v-else-if="message.type === TIM.TYPES.MSG_GRP_SYS_NOTICE"
-                  :payload="message.payload"
+                  v-else-if="message.contentType === 1400"
+                  :payload="message.textElem"
                   :message="message"
           />
           <custom-element
-                  v-else-if="message.type === TIM.TYPES.MSG_CUSTOM"
+                  v-else-if="message.contentType === 110"
                   :isMine="isMine"
-                  :payload="message.payload"
+                  :payload="parsedCustomPayload"
                   :message="message"
           />
           <face-element
-                  v-else-if="message.type === TIM.TYPES.MSG_FACE"
+                  v-else-if="message.contentType === 115"
                   :isMine="isMine"
-                  :payload="message.payload"
+                  :payload="message.textElem"
                   :message="message"
           />
           <video-element
-                  v-else-if="message.type === TIM.TYPES.MSG_VIDEO"
+                  v-else-if="message.contentType === 104"
                   :isMine="isMine"
-                  :payload="message.payload"
+                  :payload="message.videoElem"
                   :message="message"
           />
           <geo-element
-                  v-else-if="message.type === TIM.TYPES.MSG_GEO"
+                  v-else-if="message.contentType === 109"
                   :isMine="isMine"
-                  :payload="message.payload"
+                  :payload="message.textElem"
                   :message="message"
           />
           <merger-element
-                  v-else-if="message.type === TIM.TYPES.MSG_MERGER"
+                  v-else-if="message.contentType === 107"
                   :isMine="isMine"
-                  :payload="message.payload"
+                  :payload="message.textElem"
                   :message="message"
           />
-          <span v-else>暂未支持的消息类型:{{message.type}}</span>
+          <span v-else>暂未支持的消息类型:{{message.contentType}}</span>
         </div>
         <message-footer v-if="showMessageHeader" :message="message" />
       </div>
@@ -87,7 +87,7 @@
     </div>
 
     <div
-            v-if="currentConversationType === TIM.TYPES.CONV_GROUP"
+            v-if="currentConversationType === 3"
             class="group-layout"
             :class="messagePosition"
     >
@@ -100,67 +100,77 @@
         <message-header v-if="showMessageHeader" :message="message" />
         <div class="content-wrapper">
           <message-status-icon v-if="isMine" :message="message" />
+          <!--文本-->
           <text-element
-                  v-if="message.type === TIM.TYPES.MSG_TEXT"
+                  v-if="message.contentType ===101"
                   :isMine="isMine"
-                  :payload="message.payload"
+                  :payload="message.textElem"
                   :message="message"
           />
+          <!--图片-->
           <image-element
-                  v-else-if="message.type === TIM.TYPES.MSG_IMAGE"
+                  v-else-if="message.contentType === 102"
                   :isMine="isMine"
-                  :payload="message.payload"
+                  :payload="message.textElem"
                   :message="message"
           />
+          <!--文件-->
           <file-element
-                  v-else-if="message.type === TIM.TYPES.MSG_FILE"
+                  v-else-if="message.contentType === 105"
                   :isMine="isMine"
-                  :payload="message.payload"
+                  :payload="message.textElem"
                   :message="message"
           />
+          <!--语音-->
           <sound-element
-                  v-else-if="message.type === TIM.TYPES.MSG_SOUND"
+                  v-else-if="message.contentType === 103"
                   :isMine="isMine"
-                  :payload="message.payload"
+                  :payload="message.soundElem"
                   :message="message"
           />
+          <!--群提示-->
           <group-tip-element
-                  v-else-if="message.type===TIM.TYPES.MSG_GRP_TIP"
+                  v-else-if="message.contentType===1519"
                   :isMine="isMine"
-                  :payload="message.payload"
+                  :payload="message.textElem"
                   :message="message"
           />
+          <!--自定义-->
           <custom-element
-                  v-else-if="message.type === TIM.TYPES.MSG_CUSTOM"
+                  v-else-if="message.contentType === 110"
                   :isMine="isMine"
-                  :payload="message.payload"
+                  :payload="message.textElem"
                   :message="message"
           />
+          <!--表情-->
           <face-element
-                  v-else-if="message.type === TIM.TYPES.MSG_FACE"
+                  v-else-if="message.contentType === 115"
                   :isMine="isMine"
-                  :payload="message.payload"
+                  :payload="message.textElem"
                   :message="message"
           />
+          <!--视频-->
           <video-element
-                  v-else-if="message.type === TIM.TYPES.MSG_VIDEO"
+                  v-else-if="message.contentType === 104"
                   :isMine="isMine"
-                  :payload="message.payload"
+                  :payload="message.textElem"
                   :message="message"
           />
+          <!--位置-->
           <geo-element
-                  v-else-if="message.type === TIM.TYPES.MSG_GEO"
+                  v-else-if="message.contentType === 109"
                   :isMine="isMine"
-                  :payload="message.payload"
+                  :payload="message.textElem"
                   :message="message"
           />
+          <!--合并消息-->
           <merger-element
-                  v-else-if="message.type === TIM.TYPES.MSG_MERGER"
+                  v-else-if="message.contentType === 107"
                   :isMine="isMine"
-                  :payload="message.payload"
+                  :payload="message.mergeElem"
                   :message="message"
           />
-          <span v-else>暂未支持的消息类型:{{message.type}}</span>
+          <span v-else>暂未支持的消息类型:{{message.contentType}}</span>
         </div>
       </div>
       <div class="col-3">
@@ -168,13 +178,13 @@
       </div>
     </div>
 
-    <div class="system-layout" v-if="currentConversationType === TIM.TYPES.CONV_SYSTEM ">
+    <div class="system-layout" v-if="currentConversationType === 4 ">
       <div class="col-1">
         <avatar :src="avatar" :type="currentConversationType" />
       </div>
       <div class="col-2">
         <message-header :message="message" />
-        <group-system-notice-element :payload="message.payload" :message="message" />
+        <group-system-notice-element :payload="message.textElem" :message="message" />
       </div>
     </div>
   </div>
@@ -220,6 +230,7 @@ export default {
     VideoElement,
     GeoElement,
     MergerElement,
+
   },
   data() {
     return {
@@ -227,47 +238,59 @@ export default {
     }
   },
   computed: {
+    parsedCustomPayload() {
+      try {
+        if (this.message.customElem?.data) {
+          const rawData = JSON.parse(this.message.customElem.data);
+          return rawData;
+        }
+      } catch (e) {
+        console.error('解析消息数据失败:', e);
+      }
+      return null;
+    },
     ...mapState({
       currentConversation: state => state.conversation.currentConversation,
       currentUserProfile: state => state.imuser.currentUserProfile,
+      userID: state => state.imuser.userID,
 
     }),
     // 是否显示头像,群提示消息不显示头像
     showAvatar() {
-      if (this.currentConversation.type === 'C2C' && !this.message.isRevoked) { // C2C且没有撤回的消息
+      if (this.currentConversation.conversationType === 1 && this.message.contentType !== 2101) { // C2C且没有撤回的消息
         return true
-      } else if (this.currentConversation.type === 'GROUP' && !this.message.isRevoked) { // group且没有撤回的消息
-        return this.message.type !== this.TIM.TYPES.MSG_GRP_TIP
+      } else if (this.currentConversation.sessionType === 3 && !this.message.contentType !== 2101) { // group且没有撤回的消息
+        return this.message.type !== 1519
       }
       return false
     },
     avatar() {
-      if (this.currentConversation.type === 'C2C') {
-        return this.message.avatar
-      } else if (this.currentConversation.type === 'GROUP') {
+      if (this.currentConversation.conversationType === 1) {
+        return this.message.senderFaceUrl
+      } else if (this.currentConversation.conversationType === 3) {
         return this.isMine
-          ? this.currentUserProfile.avatar
-          : this.message.avatar
+          ? this.currentUserProfile.faceUrl
+          : this.message.faceUrl
       } else {
         return ''
       }
     },
     currentConversationType() {
-      return this.currentConversation.type
+      return this.currentConversation.conversationType
     },
     isMine() {
       // console.log(this.currentUserProfile, this.currentConversation);
-      return this.message.flow === 'out'
+      return this.message.sendID ===  this.$store.getters.userID
     },
     messagePosition() {
       if (
               ['TIMGroupTipElem', 'TIMGroupSystemNoticeElem'].includes(
-                      this.message.type
+                      this.message.contentType
               )
       ) {
         return 'position-center'
       }
-      if (this.message.isRevoked) { // 撤回消息
+      if (this.message.contentType===2101) { // 撤回消息
         return 'position-center'
       }
       if (this.isMine) {
@@ -277,14 +300,14 @@ export default {
       }
     },
     showMessageHeader() {
-      if (
+      /*if (
               ['TIMGroupTipElem', 'TIMGroupSystemNoticeElem'].includes(
-                      this.message.type
+                      this.message.contentType
               )
       ) {
         return false
-      }
-      if (this.message.isRevoked) { // 撤回消息
+      }*/
+      if (this.message.contentType===2101) { // 撤回消息
         return false
       }
       return true
@@ -292,8 +315,7 @@ export default {
   },
   methods: {
     showGroupMemberProfile(event) {
-      this.tim
-              .getGroupMemberProfile({
+      this.tim.getGroupMemberProfile({
                 groupID: this.message.to,
                 userIDList: [this.message.from]
               })

+ 427 - 146
src/components/message/message-send-box.vue

@@ -4,7 +4,10 @@
       <el-popover placement="top" width="400" trigger="click">
         <div class="emojis">
           <div v-for="item in emojiName" class="emoji" :key="item" @click="chooseEmoji(item)">
-            <img :src="emojiUrl + emojiMap[item]" style="width:30px;height:30px" />
+            <!--<img :src="emojiUrl + emojiCharMap[item]" style="width:30px;height:30px" />-->
+            <span style="width:30px;height:30px">
+              {{emojiCharMap[item]}}
+            </span>
           </div>
         </div>
         <i class="iconfont icon-smile" slot="reference" title="发表情"></i>
@@ -17,23 +20,25 @@
       <!-- <i class="iconfont icon-diaocha" title="小调查" @click="surveyDialogVisible = true"></i> -->
       <el-dropdown>
       <span class="el-dropdown-link">
-      <i class="el-icon-phone-outline" v-if="toAccount !== userID&&((imType==1&&orderType==2)||imType==2)" title="语音通话"></i>
+      <!--<i class="el-icon-phone-outline" v-if="toAccount !== userID&&((imType==1&&orderType==2)||imType==2)" title="语音通话"></i>-->
+      <i class="el-icon-phone-outline"  title="语音通话"></i>
       </span>
         <el-dropdown-menu slot="dropdown">
           <el-dropdown-item  @click.native="trtcCalling('video')">视频通话</el-dropdown-item>
           <el-dropdown-item  @click.native="trtcCalling('audio')">语音通话</el-dropdown-item>
         </el-dropdown-menu>
       </el-dropdown>
-      <div class="group-live-icon-box" v-if="currentConversationType === TIM.TYPES.CONV_GROUP && groupProfile.type !== 'AVChatRoom'" title="群直播" @click="groupLive">
+      <div class="group-live-icon-box" v-if="currentConversationType === 4&& groupProfile.type !== 'AVChatRoom'" title="群直播" @click="groupLive">
         <i class="group-live-icon"></i>
         <i class="group-live-icon-hover"></i>
       </div>
-      <i class="el-icon-s-order" v-if="imType==1" title="问诊订单" @click="handleInquiryOrder()"></i>
+
+      <i class="el-icon-s-order" title="问诊订单" @click="handleInquiryOrder()"></i>
       <i class="el-icon-document" v-if="imType==1" title="诊断报告" @click="handleInquiryReport()"></i>
       <i class="el-icon-edit-outline" v-if="imType==1" title="开处方" @click="handlePrescribe()"></i>
       <i class="el-icon-circle-close" v-if="imType==1" title="结束问诊" @click="handleFinishInquiry()"></i>
       <i class="el-icon-finished" v-if="imType==2" title="随访单" @click="handleFollow()"></i>
-      <i class="el-icon-tickets"  title="药品订单" @click="handleStoreOrder()"></i>
+      <i class="el-icon-tickets" v-if="this.$store.state.conversation.currentConversation.conversationID.includes('U')" title="药品订单" @click="handleStoreOrder()"></i>
       <i class="el-icon-edit-outline" v-if="imType==2" title="开报告" @click="handleDrugReport()"></i>
       <i class="el-icon-circle-close" v-if="imType==2" title="结束咨询" @click="handleFinishDrugReport()"></i>
       <i class="el-icon-chat-dot-square" title="常用语" @click="handleDoctorWords()"></i>
@@ -114,7 +119,7 @@
     />
     <input type="file" id="filePicker" ref="filePicker" @change="sendFile" style="display:none" />
     <input type="file" id="videoPicker" ref="videoPicker" @change="sendVideo" style="display:none" accept=".mp4"/>
-    <div class="calling-member-list" v-if="currentConversationType === TIM.TYPES.CONV_GROUP && showCallingMember">
+    <div class="calling-member-list" v-if="currentConversationType === 3 && showCallingMember">
       <calling-member-list @getList="getList" :type="listTpye"></calling-member-list>
       <div class="calling-list-btn">
         <span class="calling-btn" @click="cancelCalling">取消</span>
@@ -158,7 +163,8 @@ import {
   Tooltip,
   Rate
 } from 'element-ui'
-import { emojiMap, emojiName, emojiUrl } from '../../utils/emojiMap'
+import { getOpenIM } from '@/utils/openIM';
+import { emojiMap, emojiName, emojiUrl,emojiCharMap } from '../../utils/emojiMap'
 
 export default {
   name: 'message-send-box',
@@ -209,6 +215,7 @@ export default {
       file: '',
       emojiMap: emojiMap,
       emojiName: emojiName,
+      emojiCharMap:emojiCharMap,
       emojiUrl: emojiUrl,
       showAtGroupMember: false,
       atUserID: '',
@@ -255,6 +262,12 @@ export default {
   beforeDestroy() {
     this.$refs['text-input'].removeEventListener('paste', this.handlePaste)
   },
+  created() {
+    if (!this.OpenIM) {
+      this.OpenIM = getOpenIM()
+      console.log("OpenIM SDK 初始化完成");
+    }
+  },
   methods: {
     sendWords(msg){
       console.log(msg)
@@ -314,7 +327,11 @@ export default {
       this.show.type=2;
       this.show.open=true;
       this.show.title="药品订单"
-      var userId=this.$store.state.conversation.currentConversation.conversationID.split("-")[1];
+      console.log(this.$store.state.conversation.currentConversation.conversationID)
+      const conversationID = this.$store.state.conversation.currentConversation.conversationID;
+      const match = conversationID.match(/U(\d+)/);
+      const userId = match ? Number(match[1]) : null;
+      console.log("this.$store.state.conversation.currentConversation.conversationID.",userId)
       setTimeout(() => {
         that.$refs.storeOrderList.getData(userId);
       }, 500);
@@ -325,8 +342,11 @@ export default {
       var that=this;
       this.show.type=1;
       this.show.open=true;
-      var userId=this.$store.state.conversation.currentConversation.conversationID.split("-")[1];
-        console.log(userId)
+      const userId = this.$store.state.conversation.currentConversation.conversationID
+        .split("_")
+        .filter(id => id.includes("U"))
+        .map(id => id.replace("U", ""));
+        //console.log(userID,"yonghh")
       this.show.title="问诊订单"
       setTimeout(() => {
 
@@ -362,6 +382,7 @@ export default {
         cancelButtonText: '取消',
         type: 'warning'
       }).then(() => {
+        console.log(that.$store.state.conversation)
         var data={orderId:that.$store.state.conversation.orderId}
         finishOrder(data).then(res => {
             if(res.code==200){
@@ -410,7 +431,7 @@ export default {
       if (this.listTpye === 'calling') {
         let callingData = {
           memberList:this.callingList,
-          type:this.TIM.TYPES.CONV_GROUP
+          type:3
         }
         this.$store.commit('setCallingList',callingData)
         if (this.callingType === 'video') {
@@ -424,28 +445,90 @@ export default {
 
     },
     trtcCalling(type) {
+      console.log(`尝试发起${type === 'video' ? '视频' : '语音'}通话`)
+
+      // 1. 检查OpenIM是否初始化
+      /*if (!this.OpenIM) {
+        console.error('OpenIM未初始化')
+        this.$message.error('IM服务未就绪')
+        return
+      }*/
+
+      // 2. 设置通话类型
       this.listTpye = 'calling'
-      if (type === 'video') {
-        this.callingType = 'video'
-      }
-      if (type === 'audio') {
-        this.callingType = 'audio'
+      this.callingType = type
+
+      // 3. 处理不同类型的会话
+      if (this.currentConversationType === 1) {
+        // 单聊
+        this.startSingleCall(type)
+      } else if (this.currentConversationType === 3) {
+        // 群聊 - 显示成员选择
+        this.showCallingMember = true
+      } else {
+        console.error('不支持的会话类型')
+        this.$message.error('当前会话不支持通话')
       }
-      // 呼叫方设置
-      if(this.currentConversationType === 'C2C') {
-        let member = [this.toAccount]
-        let callingData = {
-          memberList:member,
-          type:'C2C'
+    },
+
+    // 发起单聊通话
+    async startSingleCall(type) {
+      try {
+        const member = [this.toAccount]
+        const callingData = {
+          memberList: member,
+          type: 1 // 单聊
         }
-        this.$store.commit('setCallingList',callingData)
+
+        // 1. 存储通话信息
+        this.$store.commit('setCallingList', callingData)
+        console.log("通话信息",callingData)
+        // 2. 触发通话事件
         this.$bus.$emit(`${type}-call`)
+
+        // 3. 日志记录
+        console.log(`已触发${type}-call事件`, {
+          targetUser: this.toAccount,
+          conversationType: this.currentConversationType
+        })
+      } catch (error) {
+        console.error('发起通话失败:', error)
+        this.$message.error('发起通话失败')
+      }
+    },
+
+    // 群聊确定按钮处理
+    callingHandler() {
+      if (this.callingList.length < 1) {
+        this.$message.warning('请选择成员')
         return
       }
-      if(this.currentConversationType === this.TIM.TYPES.CONV_GROUP) {
-        this.showCallingMember = true
+
+      if (this.listTpye === 'groupAt') {
+        // @成员处理...(原有逻辑)
+        return
+      }
+
+      if (this.listTpye === 'calling') {
+        const callingData = {
+          memberList: this.callingList,
+          type: 3 // 群聊
+        }
+
+        // 1. 存储通话信息
+        this.$store.commit('setCallingList', callingData)
+
+        // 2. 根据类型触发不同事件
+        if (this.callingType === 'video') {
+          console.log("发起群视频通话,成员:", this.callingList)
+          this.$bus.$emit('video-call')
+        } else {
+          console.log("发起群语音通话,成员:", this.callingList)
+          this.$bus.$emit('audio-call')
+        }
+
+        this.showCallingMember = false
       }
-      // this.$store.commit('pushCurrentMessageList', true)
     },
     handleEmojiShow () {
       this.emojiShow = true
@@ -461,7 +544,7 @@ export default {
     },
     chooseBigEmoji(item) {
       this.popoverVisible = false
-      let message = this.tim.createFaceMessage({
+      let message = this.OpenIM.createFaceMessage({
         to: this.toAccount,
         conversationType: this.currentConversationType,
         payload: {
@@ -469,9 +552,18 @@ export default {
           data: `${item}@2x`
         }
       })
+      let offlinePushInfo = {
+        title:"芳华未来",
+        desc:"表情消息",
+        iOSPushSound:"",
+        iOSBadgeCount:true,
+        operatorUserID:"",
+        ex:""
+      }
       this.$store.commit('pushCurrentMessageList', message)
+      this.updateConversationList()
       this.$bus.$emit('scroll-bottom')
-      this.tim.sendMessage(message).catch(error => {
+      this.OpenIM.sendMessage(message,offlinePushInfo).catch(error => {
         this.$store.commit('showMessage', {
           type: 'error',
           message: error.message
@@ -503,7 +595,7 @@ export default {
       this.sendTextMessage()
     },
     inputChange(value) {
-      if (this.currentConversationType === this.TIM.TYPES.CONV_GROUP && value.data === '@') {
+      if (this.currentConversationType === 3 && value.data === '@') {
         this.groupAt = true
         this.listTpye = 'groupAt'
         this.showCallingMember = true
@@ -532,7 +624,7 @@ export default {
         return
       }
       // 1. 创建消息实例,接口返回的实例可以上屏
-      let message = this.tim.createImageMessage({
+      let message = this.OpenIM.createImageMessageFromFullPath({
         to: this.toAccount,
         conversationType: this.currentConversationType,
         payload: {
@@ -543,9 +635,9 @@ export default {
         }
       })
       this.$store.commit('pushCurrentMessageList', message)
-
+      this.updateConversationList()
       // 2. 发送消息
-      let promise = this.tim.sendMessage(message)
+      let promise = this.OpenIM.sendMessage(message)
       promise.catch(error => {
         this.$store.commit('showMessage', {
           type: 'error',
@@ -558,7 +650,7 @@ export default {
       let file = e.dataTransfer.files[0]
       let message = {}
       if (file.type === 'video/mp4') {
-        message = this.tim.createVideoMessage({
+        message = this.OpenIM.createVideoMessage({
           to: this.toAccount,
           conversationType: this.currentConversationType,
           payload: {
@@ -569,7 +661,7 @@ export default {
           }
         })
       } else {
-        message = this.tim.createFileMessage({
+        message = this.OpenIM.createFileMessage({
           to: this.toAccount,
           conversationType: this.currentConversationType,
           payload: {
@@ -581,7 +673,8 @@ export default {
         })
       }
       this.$store.commit('pushCurrentMessageList', message)
-      this.tim
+      this.updateConversationList()
+      this.OpenIM
         .sendMessage(message)
         .then(() => {
           this.$refs.videoPicker.value = null
@@ -612,20 +705,22 @@ export default {
           followId:this.$store.state.conversation.followId,
           orderType:this.$store.state.conversation.orderType
       }
-      if (this.currentConversationType === this.TIM.TYPES.CONV_GROUP && this.groupAt) {
+      if (this.currentConversationType === 3 && this.groupAt) {
 
-        let message = this.tim.createTextAtMessage({
+        let message = this.OpenIM.createTextAtMessage({
           cloudCustomData:JSON.stringify(customData),
           to: this.toAccount,
-          conversationType: this.TIM.TYPES.CONV_GROUP,
+          conversationType: 3,
           payload: {
             text: this.messageContent,
             atUserList: this.callingList // 'denny' 'lucy' 都是 userID,而非昵称
           }
         })
+
         this.$store.commit('pushCurrentMessageList', message)
+        this.updateConversationList()
         this.$bus.$emit('scroll-bottom')
-        this.tim.sendMessage(message).catch(error => {
+        this.OpenIM.sendMessage(message).catch(error => {
           this.$store.commit('showMessage', {
             type: 'error',
             message: error.message
@@ -635,21 +730,68 @@ export default {
         this.groupAt = false
         return
       }
-      const message = this.tim.createTextMessage({
-        cloudCustomData:JSON.stringify(customData),
-        to: this.toAccount,
-        conversationType: this.currentConversationType,
-        payload: { text: this.messageContent }
-      })
-      this.$store.commit('pushCurrentMessageList', message)
-      this.$bus.$emit('scroll-bottom')
-      this.tim.sendMessage(message).catch(error => {
-        this.$store.commit('showMessage', {
-          type: 'error',
-          message: error.message
+      console.log("this.messageContent",this.messageContent)
+      this.OpenIM.createTextMessage(this.messageContent).then(({ data }) => {
+        console.log("创建文本消息返回参数")
+        console.log(data)
+        console.log("接受哦人id"+ this.$store.getters.toAccount)
+        // 调用成功
+        //console.log("customData",customData)
+        data.ex = JSON.stringify(customData)
+        const sendText={
+          message:data,
+          recvID:this.$store.getters.toAccount,
+          groupID:"",
+          offlinePushInfo : {
+            title:data.senderNickname,
+            desc:this.messageContent,
+            iOSPushSound:"",
+            iOSBadgeCount:true,
+            operatorUserID:"",
+            ex:""
+          }
+        }
+        console.log("发送消息参数",sendText)
+        console.log("发送消息参数",this.OpenIM)
+        console.log("发送消息参数",this.OpenIM.isLogin)
+        /*if (!this.OpenIM || !this.OpenIM.isLogin) {
+          console.warn('⚠️ OpenIM SDK 尚未登录完成,无法发起邀请');
+          return;
+        }*/
+        this.OpenIM.sendMessage(sendText).then(({ data }) => {
+          console.log("发送消息返回参数",data)
+          // 调用成功
+          console.log("customData",customData)
+          const msgList = Array.isArray(data) ? data : [data];
+          this.$store.commit('pushCurrentMessageList', msgList)
+          this.updateConversationList()
+          this.$bus.$emit('scroll-bottom')
+        }).catch(({ errCode, errMsg }) => {
+            // 调用失败
+          });
+        this.messageContent = ''
+      }).catch(({ errCode, errMsg }) => {
+          // 调用失败
+        });
+      /*{
+      cloudCustomData:JSON.stringify(customData),
+      to: this.toAccount,
+      conversationType: this.currentConversationType,
+      payload: { text: this.messageContent }
+    }*/
+
+
+    },
+    updateConversationList(){
+      this.OpenIM.getAllConversationList()
+        .then(({ data }) => {
+          // 调用成功
+          console.log(data,"147852")
+          this.$store.commit('updateConversationList', data)
+        })
+        .catch(({ errCode, errMsg }) => {
+          // 调用失败
         })
-      })
-      this.messageContent = ''
     },
     sendCustomMessage() {
       if (
@@ -670,7 +812,20 @@ export default {
           followId:this.$store.state.conversation.followId,
           orderType:this.$store.state.conversation.orderType
       }
-      const message = this.tim.createCustomMessage({
+      const customMessageData = {
+        data:this.form.data,
+        description: this.form.description,
+        extension: this.form.extension
+      }
+      console.log("customMessageData",customMessageData)
+      this.OpenIM.createCustomMessage(customMessageData)
+        .then(({ data }) => {
+          // 调用成功
+        })
+        .catch(({ errCode, errMsg }) => {
+          // 调用失败
+        });
+      /*const message = this.OpenIM.createCustomMessage({
         cloudCustomData:JSON.stringify(customData),
         to: this.toAccount,
         conversationType: this.currentConversationType,
@@ -679,9 +834,9 @@ export default {
           description: this.form.description,
           extension: this.form.extension
         }
-      })
+      })*/
       this.$store.commit('pushCurrentMessageList', message)
-      this.tim.sendMessage(message).catch(error => {
+      this.OpenIM.sendMessage(message).catch(error => {
         this.$store.commit('showMessage', {
           type: 'error',
           message: error.message
@@ -705,7 +860,8 @@ export default {
           followId:this.$store.state.conversation.followId,
           orderType:this.$store.state.conversation.orderType
       }
-      const message = this.tim.createCustomMessage({
+      console.log("创建自定义消息")
+      const message = this.OpenIM.createCustomMessage({
         cloudCustomData:JSON.stringify(customData),
         to: this.toAccount,
         conversationType: this.currentConversationType,
@@ -721,9 +877,10 @@ export default {
         description: '',
         extension: ''
       })
-      this.tim
+      this.OpenIM
         .sendMessage(message)
         .then(() => {
+          console.log("发送自定义消息")
           Object.assign(this, {
             rate: 5,
             suggestion: ''
@@ -738,7 +895,9 @@ export default {
       this.surveyDialogVisible = false
     },
     chooseEmoji(item) {
-      this.messageContent += item
+      const emojiChar = this.emojiCharMap[item] || item;
+      console.log("emojiChar",emojiChar)
+      this.messageContent += emojiChar;
     },
     handleSendImageClick() {
       this.$refs.imagePicker.click()
@@ -756,102 +915,224 @@ export default {
       })
       this.$bus.$emit('open-group-live', { channel: 1 })
     },
-    sendImage() {
-      var customData={
-          type:this.$store.state.conversation.type,
-          imType:this.$store.state.conversation.imType,
-          orderId:this.$store.state.conversation.orderId,
-          followId:this.$store.state.conversation.followId,
-          orderType:this.$store.state.conversation.orderType
-      }
-      const message = this.tim.createImageMessage({
-        cloudCustomData:JSON.stringify(customData),
-        to: this.toAccount,
-        conversationType: this.currentConversationType,
-        payload: {
-          file: document.getElementById('imagePicker') // 或者用event.target
-        },
-        onProgress: percent => {
-          this.$set(message, 'progress', percent) // 手动给message 实例加个响应式属性: progress
-        }
+    generateUUID() {
+      return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
+        var r = (Math.random() * 16) | 0,
+          v = c == 'x' ? r : (r & 0x3) | 0x8
+        return v.toString(16)
       })
-      this.$store.commit('pushCurrentMessageList', message)
-      this.tim
-        .sendMessage(message)
-        .then(() => {
-          this.$refs.imagePicker.value = null
-        })
-        .catch(imError => {
-          this.$store.commit('showMessage', {
-            message: imError.message,
-            type: 'error'
+    },
+    sendImage() {
+      const input = document.getElementById('imagePicker');
+      const imageFile = input?.files?.[0];
+      if (!imageFile) return;
+
+      const picBaseInfo = {
+        uuid: this.generateUUID(),
+        type: imageFile.type,
+        size: imageFile.size,
+        width: 0,
+        height: 0,
+        url: '',
+      };
+
+      const setImageDimensions = (file, callback) => {
+        const reader = new FileReader();
+        reader.onload = (e) => {
+          const img = new Image();
+          img.onload = () => {
+            callback({ width: img.width, height: img.height });
+          };
+          img.src = e.target.result;
+        };
+        reader.readAsDataURL(file);
+      };
+
+      setImageDimensions(imageFile, ({ width, height }) => {
+        // 创建图片对象
+        const updatedPicInfo = {
+          ...picBaseInfo,
+          width,
+          height,
+        };
+
+        const messageData = {
+          sourcePicture: { ...updatedPicInfo },
+          bigPicture: { ...updatedPicInfo },
+          snapshotPicture: { ...updatedPicInfo },
+          file: imageFile,
+          sourcePath: imageFile.name,
+          onProgress: percent => {
+            this.$set(updatedPicInfo, 'progress', percent);
+          },
+        };
+
+        this.OpenIM.createImageMessageByFile(messageData)
+          .then(({ data: message }) => {
+            const isGroup = this.currentConversationType === 3;
+            return this.OpenIM.sendMessage({
+              message,
+              recvID: isGroup ? '' : this.$store.getters.toAccount,
+              groupID: isGroup ? this.$store.getters.toAccount : '',
+              offlinePushInfo : {
+                title:message.senderNickname,
+                desc:"[图片]",
+                iOSPushSound:"",
+                iOSBadgeCount:true,
+                operatorUserID:"",
+                ex:""
+              }
+            });
           })
-        })
+          .then(({ data }) => {
+            console.log('发送图片成功', data);
+            const msgList = Array.isArray(data) ? data : [data];
+            this.$store.commit('pushCurrentMessageList', msgList);
+            this.updateConversationList()
+            this.$bus.$emit('scroll-bottom');
+            this.$refs.imagePicker.value = null;
+          })
+          .catch(({ errCode, errMsg }) => {
+            console.error('发送图片失败', errCode, errMsg);
+            this.$store.commit('showMessage', {
+              message: errMsg,
+              type: 'error',
+            });
+          });
+      });
     },
+
     sendFile() {
-      var customData={
-          type:this.$store.state.conversation.type,
-          imType:this.$store.state.conversation.imType,
-          orderId:this.$store.state.conversation.orderId,
-          followId:this.$store.state.conversation.followId,
-          orderType:this.$store.state.conversation.orderType
-      }
-      const message = this.tim.createFileMessage({
-        cloudCustomData:JSON.stringify(customData),
-        to: this.toAccount,
-        conversationType: this.currentConversationType,
-        payload: {
-          file: document.getElementById('filePicker') // 或者用event.target
-        },
+      const input = document.getElementById('filePicker');
+      const file = input.files[0];
+      if (!file) return;
+
+      const customData = {
+        type: this.$store.state.conversation.type,
+        imType: this.$store.state.conversation.imType,
+        orderId: this.$store.state.conversation.orderId,
+        followId: this.$store.state.conversation.followId,
+        orderType: this.$store.state.conversation.orderType
+      };
+
+      // 1. 创建文件消息
+      this.OpenIM.createFileMessageByFile({
+        filePath: file.name,
+        fileName: file.name,
+        uuid: this.generateUUID(),
+        sourceUrl: '',
+        fileSize: file.size,
+        fileType: file.type,
+        file,
+        cloudCustomData: JSON.stringify(customData),
         onProgress: percent => {
-          this.$set(message, 'progress', percent) // 手动给message 实例加个响应式属性: progress
+          this.$set(file, 'progress', percent);
         }
-      })
-      this.$store.commit('pushCurrentMessageList', message)
-      this.tim
-        .sendMessage(message)
-        .then(() => {
-          this.$refs.filePicker.value = null
-        })
-        .catch(imError => {
-          this.$store.commit('showMessage', {
-            message: imError.message,
-            type: 'error'
-          })
-        })
+      }).then(({ data }) => {
+        // 2. 发送文件消息
+        const sendFile = {
+          message: data,
+          recvID: this.$store.getters.toAccount,
+          groupID: this.currentConversationType === 3 ? this.toAccount : "",
+          offlinePushInfo : {
+            title:data.senderNickname,
+            desc:"[文件]",
+            iOSPushSound:"",
+            iOSBadgeCount:true,
+            operatorUserID:"",
+            ex:""
+          }
+        };
+        console.error("创建文件消息返回", data);
+        console.error("发送文件消息参数", sendFile);
+        return this.OpenIM.sendMessage(sendFile);
+      }).then(({ data }) => {
+        const msgList = Array.isArray(data) ? data : [data];
+        this.$store.commit('pushCurrentMessageList', msgList);
+        this.updateConversationList()
+        this.$bus.$emit('scroll-bottom');
+        this.$refs.filePicker.value = null;
+      }).catch(({ errCode, errMsg }) => {
+        console.error("发送文件失败", errCode, errMsg);
+        this.$store.commit('showMessage', {
+          message: errMsg,
+          type: 'error'
+        });
+      });
     },
     sendVideo() {
-      var customData={
-          type:this.$store.state.conversation.type,
-          imType:this.$store.state.conversation.imType,
-          orderId:this.$store.state.conversation.orderId,
-          followId:this.$store.state.conversation.followId,
-          orderType:this.$store.state.conversation.orderType
-      }
-      const message = this.tim.createVideoMessage({
-        cloudCustomData:JSON.stringify(customData),
-        to: this.toAccount,
-        conversationType: this.currentConversationType,
-        payload: {
-          file: document.getElementById('videoPicker') // 或者用event.target
-        },
-        onProgress: percent => {
-          this.$set(message, 'progress', percent) // 手动给message 实例加个响应式属性: progress
-        }
-      })
-      this.$store.commit('pushCurrentMessageList', message)
-      this.tim
-        .sendMessage(message)
-        .then(() => {
-          this.$refs.videoPicker.value = null
-        })
-        .catch(imError => {
-          this.$store.commit('showMessage', {
-            message: imError.message,
-            type: 'error'
-          })
-        })
+      const videoInput = document.getElementById('videoPicker');
+      const file = videoInput.files[0];
+      if (!file) return;
+
+      const snapshotFile = new File([new Blob()], 'snapshot.png', { type: 'image/png' });
+
+      const customData = {
+        type: this.$store.state.conversation.type,
+        imType: this.$store.state.conversation.imType,
+        orderId: this.$store.state.conversation.orderId,
+        followId: this.$store.state.conversation.followId,
+        orderType: this.$store.state.conversation.orderType,
+      };
+
+      const videoURL = URL.createObjectURL(file);
+      const video = document.createElement('video');
+      video.preload = 'metadata';
+      video.src = videoURL;
+
+      video.onloadedmetadata = () => {
+        URL.revokeObjectURL(videoURL); // 清理临时URL
+        const duration = Math.ceil(video.duration); // 视频时长,取整
+
+        // 创建视频消息
+        this.OpenIM.createVideoMessageByFile({
+          cloudCustomData: JSON.stringify(customData),
+          videoPath: file.name,
+          duration,
+          videoType: file.type,
+          snapshotPath: snapshotFile.name,
+          videoUUID: '',
+          videoUrl: '',
+          videoSize: file.size,
+          snapshotUUID: '',
+          snapshotSize: snapshotFile.size,
+          snapshotUrl: '',
+          snapshotWidth: 1024,
+          snapshotHeight: 1024,
+          snapShotType: snapshotFile.type,
+          videoFile: file,
+          snapshotFile,
+          onProgress: percent => {
+            this.$set(this, 'videoProgress', percent);
+          }
+        }).then(({ data }) => {
+          const sendVideo = {
+            message: data,
+            recvID: this.$store.getters.toAccount,
+            groupID: this.currentConversationType === 3 ? this.toAccount : "",
+            offlinePushInfo : {
+              title:data.senderNickname,
+              desc:"[视频]",
+              iOSPushSound:"",
+              iOSBadgeCount:true,
+              operatorUserID:"",
+              ex:""
+            }
+          };
+
+          this.OpenIM.sendMessage(sendVideo).then(({ data }) => {
+            const msgList = Array.isArray(data) ? data : [data];
+            this.$store.commit('pushCurrentMessageList', msgList);
+            this.updateConversationList()
+            this.$bus.$emit('scroll-bottom');
+            this.$refs.videoPicker.value = null;
+          }).catch(({ errCode, errMsg }) => {
+            console.error("发送视频失败", errCode, errMsg);
+          });
+        });
+      };
     }
+
   }
 }
 </script>

Різницю між файлами не показано, бо вона завелика
+ 719 - 652
src/components/message/trtc-calling/calling-index.vue


+ 6 - 1
src/components/message/trtc-calling/group-member-list.vue

@@ -6,7 +6,7 @@
         <div class="scroll-content">
             <div class="group-member-list">
                 <el-checkbox-group v-model="callingList" @change="handleCheckedMembersChange">
-                    <el-checkbox v-if="type === 'groupAt'"  :label="JSON.stringify({userID:this.TIM.TYPES.MSG_AT_ALL,nick:'所有人'})" >
+                    <el-checkbox v-if="type === 'groupAt'"  :label="JSON.stringify({userID:this.OpenIM.TYPES.MSG_AT_ALL,nick:'所有人'})" >
                         <div class="group-member">
                             <avatar  :src="''" />
                             <div class="member-name text-ellipsis">
@@ -35,10 +35,12 @@
 
 <script>
   import { mapState } from 'vuex'
+  import { getOpenIM } from '@/utils/openIM';
   export default {
     props:['type'],
     data() {
       return {
+        OpenIM:null,
         callingList:[],
         addGroupMemberVisible: false,
         currentMemberID: '',
@@ -47,6 +49,9 @@
     },
     components: {
     },
+    created() {
+      this.OpenIM = getOpenIM();
+    },
     computed: {
       ...mapState({
         userID: state => state.imuser.userID,

+ 11 - 10
src/components/my-profile.vue

@@ -10,16 +10,16 @@
         </el-form-item>
         <el-form-item label="性别">
           <el-radio-group v-model="form.gender">
-            <el-radio :label="TIM.TYPES.GENDER_MALE">男</el-radio>
-            <el-radio :label="TIM.TYPES.GENDER_FEMALE">女</el-radio>
-            <el-radio :label="TIM.TYPES.GENDER_UNKNOWN">不显示</el-radio>
+            <el-radio :label="1">男</el-radio>
+            <el-radio :label="0">女</el-radio>
+            <el-radio :label="3">不显示</el-radio>
           </el-radio-group>
         </el-form-item>
         <el-form-item label="好友申请">
           <el-radio-group v-model="form.allowType">
-            <el-radio :label="TIM.TYPES.ALLOW_TYPE_ALLOW_ANY">允许直接加为好友</el-radio>
-            <el-radio :label="TIM.TYPES.ALLOW_TYPE_NEED_CONFIRM">需要验证</el-radio>
-            <el-radio :label="TIM.TYPES.ALLOW_TYPE_DENY_ANY">不允许任何人添加好友</el-radio>
+            <!--<el-radio :label="OpenIM.TYPES.ALLOW_TYPE_ALLOW_ANY">允许直接加为好友</el-radio>
+            <el-radio :label="OpenIM.TYPES.ALLOW_TYPE_NEED_CONFIRM">需要验证</el-radio>
+            <el-radio :label="OpenIM.TYPES.ALLOW_TYPE_DENY_ANY">不允许任何人添加好友</el-radio>-->
           </el-radio-group>
         </el-form-item>
       </el-form>
@@ -33,7 +33,7 @@
       <!-- <i class="el-icon-setting edit-my-profile" @click="handleEdit"></i> -->
       <avatar
         slot="reference"
-        :src="currentUserProfile.avatar"
+        :src="currentUserProfile.faceURL"
         class="my-avatar"
       />
     </el-popover>
@@ -57,7 +57,8 @@ export default {
   data() {
     return {
       showEditMyProfile: false,
-      form: { avatar: '', nick: '', gender: '',allowType: '' }
+      form: { avatar: '', nick: '', gender: '',allowType: '' },
+      OpenIM:null
     }
   },
   computed: {
@@ -67,9 +68,9 @@ export default {
     }),
     gender() {
       switch (this.currentUserProfile.gender) {
-        case this.TIM.TYPES.GENDER_MALE:
+        case 1:
           return '男'
-        case this.TIM.TYPES.GENDER_FEMALE:
+        case 0:
           return '女'
         default:
           return '暂无'

+ 4 - 4
src/components/profile-card.vue

@@ -1,9 +1,9 @@
 <template>
   <div class="profile-card-wrapper">
     <div class="content">
-      <avatar :src="profile.avatar" />
+      <avatar :src="profile.faceURL" />
       <div class="basic">
-        <span class="nick text-ellipsis">医生:{{ profile.nick || profile.userID }}</span>
+        <span class="nick text-ellipsis">医生:{{ profile.nickname || profile.userID }}</span>
         <!-- <span class="iconfont" :class="genderClass"></span> -->
       </div>
       <div><span class="nick text-ellipsis">{{ profile.userID }}</span>
@@ -24,9 +24,9 @@ export default {
   computed: {
     genderClass() {
       switch (this.profile.gender) {
-        case this.TIM.TYPES.GENDER_MALE:
+        case 1:
           return 'icon-male'
-        case this.TIM.TYPES.GENDER_FEMALE:
+        case 0:
           return 'icon-female'
         default:
           return '暂无'

+ 217 - 33
src/components/user/login.vue

@@ -12,46 +12,230 @@
   </div>
 </template>
 
+
 <script>
+  import logo from '../../assets/image/logo.png'
+  import { getOpenIM,getCbEvents } from '@/utils/openIM';
+  import { imConfig } from '@/utils/im' // 引入配置
+  import { accountCheck } from '@/api/doctor';
+  export default {
+    name: 'Login',
+    data() {
+      return {
+        hasBindReadyEvent : false,
+        logo: logo,
+        loading: false,
+        OpenIM: null
+      }
+    },
+    created() {
+      if (process.env.IS_OPENIM){
+        this.OpenIM = getOpenIM();
+        this.initListener()
+      }
+    },
+    methods: {
+      submit() {
+        this.login()
+      },
+      initListener() {
+        // 登录成功后会触发 SDK_READY 事件,该事件触发后,可正常使用 SDK 接口
+        this.OpenIM.on(getCbEvents().OnConnectSuccess, () => {
+          console.log("OnConnectSuccess 事件触发!"); // 调试日志
+          this.$store.commit('toggleIsSDKReady', true);
+        });
+        this.OpenIM.on(getCbEvents().OnConnectFailed, this.onError);
+        this.OpenIM.on(getCbEvents().OnKickedOffline, this.onKickOut);
+        this.OpenIM.on(getCbEvents().OnSelfInfoUpdated, this.onSelfInfoUpdated);
+        /*this.OpenIM.on(getCbEvents().OnRecvNewMessage, (message) => {
+          console.log("收到单条消息", message);
+          this.onReceiveMessage({data: [message]}); // 包装成数组形式
+        });*/
 
-export default {
-  name: 'Login',
-  components: {
+        this.OpenIM.on(getCbEvents().OnRecvNewMessages, (data) => {
+          console.log("收到多条消息", data);
+          const msgList = []
+          data.data.forEach(msg =>{
+            if (msg.contentType!==113){
+              msgList.push(msg)
+            }
+          })
+          if (msgList.length>0){
+            this.onReceiveMessage({data: msgList});
 
-  },
-  data() {
-    return {
-      logo: require(process.env.VUE_APP_LOG_URL),
-      loading: false
-    }
-  },
-  methods: {
-    submit() {
-      this.login()
-    },
-    login() {
-      this.loading = true
-      this.tim
-        .login({
-          userID: this.$store.getters.userID,
-          userSig: this.$store.getters.userSig
-        })
-        .then(() => {
-          this.loading = false
-          this.$store.commit('toggleIsLogin', true)
-          this.$store.commit('startComputeCurrent')
-          this.$store.commit('showMessage', { type: 'success', message: '登录成功' })
-        })
-        .catch(error => {
-          this.loading = false
+          }
+        });
+        /*this.OpenIM.on(getCbEvents().OnConversationChanged, (updatedConversations) => {
+          console.log("触发OnConversationChanged事件")
+          //console.log(updatedConversations)
+          updatedConversations.data.forEach(conv => {
+            if (conv.conversationID === this.currentConversation.conversationID) {
+              context.commit('updateCurrentConversation', conv);
+            }
+          });
+        });*/
+        // SDK NOT READT
+        /*this.OpenIM.on(this.OpenIM.EVENT.SDK_NOT_READY, this.onReadyStateUpdate, this)
+        // 被踢出
+        this.OpenIM.on(this.OpenIM.EVENT.KICKED_OUT, this.onKickOut)
+        // SDK内部出错
+        this.OpenIM.on(this.OpenIM.EVENT.ERROR, this.onError)
+        // 收到新消息
+        this.OpenIM.on(this.OpenIM.EVENT.MESSAGE_RECEIVED, this.onReceiveMessage)
+        // 会话列表更新
+        this.OpenIM.on(this.OpenIM.EVENT.CONVERSATION_LIST_UPDATED, this.onUpdateConversationList)
+        // 群组列表更新
+        this.OpenIM.on(this.OpenIM.EVENT.GROUP_LIST_UPDATED, this.onUpdateGroupList)
+        // 网络监测
+        this.OpenIM.on(this.OpenIM.EVENT.NET_STATE_CHANGE, this.onNetStateChange)
+        // 已读回执
+        this.OpenIM.on(this.OpenIM.EVENT.MESSAGE_READ_BY_PEER, this.onMessageReadByPeer)
+        // 黑名单更新
+        this.OpenIM.on(this.OpenIM.EVENT.FRIEND_LIST_UPDATED, this.onFriendListUpdated)
+
+        this.OpenIM.on(this.OpenIM.EVENT.FRIEND_APPLICATION_LIST_UPDATED, this.onFriendApplicationListUpdated)
+
+        this.OpenIM.on(this.OpenIM.EVENT.FRIEND_GROUP_LIST_UPDATED, this.onFriendGroupListUpdated)*/
+
+      },
+      login() {
+        accountCheck(this.$store.getters.userID).then(response => {
+          console.log("response",response)
+          this.userToken = response.token
+          console.log("this.userToken",this.userToken)
+          console.log(this.$store.getters.userID);
+          const config = {
+            userID: this.$store.getters.userID,
+            token: this.userToken,
+            logLevel:6,
+            platformID: 5, // 使用配置的平台ID
+            apiAddr: process.env.VUE_APP_API_ADDR,
+            wsAddr: process.env.VUE_APP_WS_ADDR,
+            dataDir: '/imdata' // 添加数据存储目录
+          }
+          console.log("config",config)
+          console.log("userToken",this.userToken)
+          this.checkSDKReadyState();
+          this.OpenIM.login(config).then(() => {
+            this.$store.commit('toggleIsLogin', true);
+            this.$store.commit('startComputeCurrent');
+            this.$store.commit('showMessage', {
+              type: 'success',
+              message: 'IM 登录成功'
+            });
+          })
+            .catch((error) => {
+              this.loading = false;
+              console.error('登录失败:', error);
+              this.$store.commit('showMessage', {
+                message: `IM 登录失败:${error.message || error.errMsg || '未知错误'}`,
+                type: 'error',
+              });
+            });
+        });
+      },
+      // 添加SDK就绪状态检查
+      checkSDKReadyState() {
+        if (this.hasBindReadyEvent) return;
+        this.hasBindReadyEvent = true;
+
+        let isReady = false;
+
+        const timeout = setTimeout(() => {
+          if (!isReady) {
+            this.$store.commit('toggleIsSDKReady', false);
+            this.$store.commit('showMessage', {
+              message: 'SDK初始化超时',
+              type: 'error'
+            });
+          }
+        }, 10000);
+
+        this.OpenIM.on(getCbEvents().OnConnectSuccess, () => {
+          console.log("WebSocket连接成功");
+          clearTimeout(timeout);
+          isReady = true;
+          this.OpenIM.getSelfUserInfo().then(({ data }) => {
+            console.log("当前登录用户基本信息",data)
+            this.$store.commit('updateCurrentUserProfile', data)
+          })
+          this.$store.commit('toggleIsSDKReady', true);
+          this.$store.commit('showMessage', {
+            type: 'success',
+            message: 'SDK 初始化成功'
+          });
+          // 触发获取用户信息等操作
+          console.log("开始加载用户数据,获取会话列表")
+          console.log(this.OpenIM)
+          this.loadUserData();
+          console.log("结束加载用户数据,获取会话列表")
+        });
+
+        this.OpenIM.on(getCbEvents().OnConnectFailed, (error) => {
+          clearTimeout(timeout);
+          this.$store.commit('toggleIsSDKReady', false);
           this.$store.commit('showMessage', {
-            message: '登录失败:' + error.message,
+            message: `SDK 连接失败: ${error.message}`,
             type: 'error'
+          });
+        });
+      },
+      onReceiveMessage({ data: messageList }) {
+        // let totalUnreadCount = this.tim.getTotalUnreadMessageCount();
+
+        messageList.forEach(element => {
+          //过滤掉正在输入状态
+          if(element.sendID!=this.$store.getters.userID&&element.contentType!==113){
+            this.$notify({
+              title: '消息提示',
+              message: '您有一条新的消息',
+              type: 'success'
+            });
+          }
+        });
+        this.OpenIM.getAllConversationList()
+          .then(({ data }) => {
+            // 调用成功
+            this.$store.commit('updateConversationList', data)
           })
-        })
-    },
+          .catch(({ errCode, errMsg }) => {
+            // 调用失败
+          })
+        console.log(messageList)
+        //this.handleVideoMessage(messageList)
+        this.handleQuitGroupTip(messageList)
+        this.handleCloseGroupLive(messageList)
+        this.$store.commit('pushCurrentMessageList', messageList)
+        this.$store.commit('pushAvChatRoomMessageList', messageList)
+      },
+
+      // 添加加载用户数据方法
+      loadUserData() {
+        //查询会话列表
+        this.OpenIM.getAllConversationList()
+          .then(({ data }) => {
+            // 调用成功
+            console.log("获取到会话列表",data)
+            this.conversationList= data
+            this.$store.commit('updateConversationList', data)
+          })
+          .catch(({ errCode, errMsg }) => {
+            // 调用失败
+          })
+        //查询好友列表
+        this.OpenIM.getFriendListPage({ offset:0, count:100 })
+          .then(({ data }) => {
+            // 调用成功
+            console.log("获取到好友列表",data)
+            //this.conversationList= data
+            this.$store.commit('updateFriendList', data)
+          })
+          .catch(({ errCode, errMsg }) => {
+            // 调用失败
+          })
+      },
+    }
   }
-}
 </script>
 
 <style lang="stylus" scoped>

+ 4 - 0
src/constant/call.js

@@ -0,0 +1,4 @@
+export const CALL_TYPE = {
+  AUDIO_CALL: 'audio',
+  VIDEO_CALL: 'video'
+}

+ 90 - 0
src/im/eventListeners.js

@@ -0,0 +1,90 @@
+import { ElMessage } from 'element-plus'
+import SDK from './index'
+import emitter from '@/utils/mitt'
+import store from '@/store'
+
+export const registerIMEvents = () => {
+  SDK.on('onConnectSuccess', () => {
+    console.log('[IM] 连接成功')
+  })
+
+  SDK.on('onConnectFailed', (err) => {
+    console.error('[IM] 连接失败:', err)
+  })
+
+  SDK.on('onKickedOffline', () => {
+  console.warn('[IM] 被踢下线')
+  ElMessage.warning('您已在其他设备登录,当前设备被踢下线')
+  store.commit('app/setLoginVisible', true) // 用于触发登录弹窗
+})
+
+SDK.on('onUserSigExpired', () => {
+    console.warn('[IM] Token 过期')
+    ElMessage.warning('登录已过期,请重新登录')
+    store.commit('app/setLoginVisible', true)
+  })
+
+  SDK.on('onSelfInfoUpdated', async () => {
+    console.log('[IM] 用户信息更新')
+    await store.dispatch('user/getSelfUserInfo')
+  })
+
+  SDK.on('onMsgReceived', (data) => {
+    console.log('[IM] 接收到消息:', data)
+    store.dispatch('message/handleNewMessages', data)
+  })
+
+  SDK.on('onConversationChanged', (data) => {
+    console.log('[IM] 会话变更:', data)
+    store.dispatch('conversation/updateConversations', data)
+  })
+
+  SDK.on('onNewConversation', (data) => {
+    console.log('[IM] 新会话:', data)
+    store.dispatch('conversation/addConversations', data)
+  })
+
+  SDK.on('onTotalUnreadMessageCountChanged', (unreadCount) => {
+    console.log('[IM] 未读总数变化:', unreadCount)
+    store.commit('conversation/setTotalUnreadCount', unreadCount)
+  })
+
+  SDK.on('onGroupInfoChanged', (groupList) => {
+    console.log('[IM] 群信息变更:', groupList)
+    store.dispatch('group/updateGroupInfo', groupList)
+  })
+
+  SDK.on('onGroupMemberAdded', (data) => {
+    store.dispatch('group/handleMemberAdded', data)
+  })
+
+  SDK.on('onGroupMemberDeleted', (data) => {
+    store.dispatch('group/handleMemberDeleted', data)
+  })
+
+  SDK.on('onGroupDismissed', (data) => {
+    store.dispatch('group/handleGroupDismissed', data)
+  })
+
+  SDK.on('onGroupRecycled', (data) => {
+    store.dispatch('group/handleGroupRecycled', data)
+  })
+
+  SDK.on('onGroupMemberInfoChanged', (data) => {
+    store.dispatch('group/handleMemberInfoChanged', data)
+  })
+
+  SDK.on('onRecvCustomMessage', (data) => {
+    console.log('[IM] 收到自定义消息:', data)
+    emitter.emit('custom-message', data)
+  })
+
+  SDK.on('onRecvCustomOnlineMessage', (data) => {
+    console.log('[IM] 收到在线自定义消息:', data)
+    emitter.emit('custom-online-message', data)
+  })
+}
+
+export const removeIMEvents = () => {
+  SDK.offAll()
+}

+ 4 - 1
src/layout/components/Sidebar/Logo.vue

@@ -30,11 +30,14 @@ export default {
     },
 	sideTheme() {
       return this.$store.state.settings.sideTheme
+    },
+    title() {
+      const type = localStorage.getItem('loginType')
+      return type === '2' ? '互联网医院药剂师端' : '互联网医院医生端'
     }
   },
   data() {
     return {
-      title: '互联网医院医生端',
       logo: require(process.env.VUE_APP_LOG_URL)
     }
   }

+ 10 - 14
src/main.js

@@ -9,30 +9,26 @@ import { MessageBox, Row, Col, Button, Input, Loading, Dialog, Dropdown, Dropdow
 import Avatar from './components/avatar.vue'
 
 import store from './store'
-import tim from './tim.js'
-import TIM from 'tim-js-sdk/tim-js-friendship.js'
-import TWebLive from 'tweblive'
+//import TWebLive from 'tweblive'
 import VueClipboard from 'vue-clipboard2'
 import './assets/icon/iconfont.css'
 import './assets/icon/tim.css'
 import './assets/css/animate.css'
 
-import trtcCalling from './trtc-calling'
-import TRTCCalling from 'trtc-calling-js'
+//import trtc from '@/utils/trtc'
+//Vue.prototype.$trtc = trtc
+
+
 
-window.tim = tim
-window.TIM = TIM
-window.TRTCCalling = TRTCCalling
-window.trtcCalling = trtcCalling
 window.store = store
 Vue.prototype.$bus = new Vue() // event Bus 用于无关系组件间的通信。
-Vue.prototype.tim = tim
-Vue.prototype.TIM = TIM
-Vue.prototype.TWebLive = TWebLive
+
+//Vue.prototype.TWebLive = TWebLive
 Vue.prototype.$store = store
 Vue.prototype.$confirm = MessageBox.confirm
-Vue.prototype.trtcCalling = trtcCalling
-Vue.prototype.TRTCCalling = TRTCCalling
+
+
+
 
 
 

+ 47 - 0
src/router/index.js

@@ -81,6 +81,21 @@ export const constantRoutes = [
     ]
   },
 
+  {
+    path: '/prescribeAudit',
+    component: Layout,
+    redirect: 'noredirect',
+    children: [
+      {
+        path: 'Index',
+        component: (resolve) => require(['@/views/doctor/prescribeAudit/index'], resolve),
+        name: '处方审核',
+        meta: { title: '处方审核', icon: 'form', noCache: true, affix: false }
+      }
+    ]
+  },
+
+
   {
     path: '/order',
     component: Layout,
@@ -111,6 +126,12 @@ export const constantRoutes = [
         name: '用户处方',
         meta: { title: '用户处方', icon: 'job', noCache: true, affix: false }
       },
+      // {
+      //   path: 'refuse',
+      //   component: (resolve) => require(['@/views/his/refuse/index'], resolve),
+      //   name: '拒方列表',
+      //   meta: { title: '拒方列表', icon: 'job', noCache: true, affix: false }
+      // },
     ]
   },
 
@@ -160,6 +181,32 @@ export const constantRoutes = [
       }
     ]
   },
+  {
+    path: '/diagnosis',
+    component: Layout,
+    redirect: 'noredirect',
+    children: [
+      {
+        path: 'Index',
+        component: (resolve) => require(['@/views/diagnosis/index'], resolve),
+        name: '初诊单',
+        meta: { title: '初诊单', icon: 'form', affix: false }
+      }
+    ]
+  },
+  {
+    path: '/collection',
+    component: Layout,
+    redirect: 'noredirect',
+    children: [
+      {
+        path: 'Index',
+        component: (resolve) => require(['@/views/collection/index'], resolve),
+        name: '用户信息采集',
+        meta: { title: '用户信息采集', icon: 'form', affix: false }
+      }
+    ]
+  },
   {
     path: '/follow',
     component: Layout,

+ 1 - 1
src/store/modules/blacklist.js

@@ -1,4 +1,4 @@
-import tim from '../../tim'
+//import tim from '../../tim'
 const blacklistModule = {
   state: {
     blacklist: []

+ 208 - 62
src/store/modules/conversation.js

@@ -1,8 +1,10 @@
-import tim from '@/tim.js';
-import TIM from 'tim-js-sdk/tim-js-friendship'
 import store from '..'
 import { titleNotify } from '../../utils'
 import { filterCallingMessage } from '../../utils/common'
+import { getOpenIM } from '@/utils/openIM';
+import da from "element-ui/src/locale/lang/da";
+
+const OpenIM = getOpenIM();
 const conversationModules = {
   state: {
     currentConversation: {},
@@ -29,23 +31,16 @@ const conversationModules = {
   },
   getters: {
     toAccount: state => {
-      if (!state.currentConversation || !state.currentConversation.conversationID) {
-        return ''
-      }
-      switch (state.currentConversation.type) {
-        case 'C2C':
-          return state.currentConversation.conversationID.replace('C2C', '')
-        case 'GROUP':
-          return state.currentConversation.conversationID.replace('GROUP', '')
-        default:
-          return state.currentConversation.conversationID
+      if (!state.currentConversation) return '';
+      if (state.currentConversation.conversationID) {
+        return state.currentConversation.conversationID.split("_").find(part => part.includes('U'));
       }
     },
     currentConversationType: state => {
-      if (!state.currentConversation || !state.currentConversation.type) {
+      if (!state.currentConversation || !state.currentConversation.conversationType) {
         return ''
       }
-      return state.currentConversation.type
+      return state.currentConversation.conversationType
     },
     totalUnreadCount: state => {
       const result = state.conversationList.reduce((count, conversation) => {
@@ -60,9 +55,10 @@ const conversationModules = {
     },
     // 用于当前会话的图片预览
     imgUrlList: state => {
+      console.log("state.currentMessageList",state.currentMessageList)
       return state.currentMessageList
-        .filter(message => message.type === TIM.TYPES.MSG_IMAGE && !message.isRevoked) // 筛选出没有撤回并且类型是图片类型的消息
-        .map(message => message.payload.imageInfoArray[0].url)
+        .filter(message => message.contentType === 102) // 筛选出没有撤回并且类型是图片类型的消息
+        .map(message => message.pictureElem.sourcePicture.url)
     }
   },
   mutations: {
@@ -147,22 +143,63 @@ const conversationModules = {
     /**
      * 更新会话列表
      * 调用时机:触发会话列表更新事件时。C
-     * 
+     *
      * @param {Object} state
      * @param {Conversation[]} conversationList
      */
     updateConversationList(state, conversationList) {
       console.log("获取会话列表")
-      conversationList.forEach(function(item) {
+      console.log("conversationList",conversationList)
+      /*conversationList.forEach(function(item) {
           console.log(item)
-          if(item.lastMessage.cloudCustomData!=null&&item.lastMessage.cloudCustomData!=""){
+        const latestMsgObj = JSON.parse(item.latestMsg);
+          if(latestMsgObj.textElem.content!=null&&latestMsgObj.textElem.content!=""){
             // try{
             //   var json=JSON.parse(item.lastMessage.cloudCustomData);
             // }
             // catch(e){
             // }
           }
-      });
+      });*/
+      /*const userIds = conversationList
+        .map(conv => {
+          const parts = conv.conversationID?.split("_");
+          if (!parts) return null;
+          // 查找包含"U"的部分(假设用户ID格式类似"U123")
+          const userPart = parts.find(part => part.includes('U'));
+          return userPart || null;
+        })
+        .filter(Boolean);
+
+      if (userIds.length === 0) return;
+
+      // 批量获取用户信息
+      return OpenIM.getUsersInfo(userIds)
+        .then(({ data: userInfos }) => {
+          console.log("用户信息列表", userInfos);
+
+          // 创建用户信息映射表
+          const userMap = {};
+          userInfos.forEach(user => {
+            userMap[user.userID] = user;
+          });
+
+          // 更新会话列表中的用户信息
+          const updatedList = conversationList.map(conv => {
+            const parts = conv.conversationID?.split("_");
+            const userId = parts && parts.length > 2 ? parts.find(part => part.includes('U')) : null;
+            if (userId && userMap[userId]) {
+              return {
+                ...conv,
+                faceURL: userMap[userId].faceURL,
+                showName: userMap[userId].nickname
+              };
+            }
+            return conv;
+          });
+
+          state.conversationList = updatedList
+        });*/
       state.conversationList = conversationList
     },
     /**
@@ -182,17 +219,22 @@ const conversationModules = {
      */
     pushCurrentMessageList(state, data) {
       // 还没当前会话,则跳过
+      console.log("触发消息列表更新",state.currentConversation)
       if (!state.currentConversation.conversationID) {
         return
       }
+      console.log("会话详细信息",state.currentConversation)
+      console.log("消息详细信息",data)
       if (Array.isArray(data)) {
         // 筛选出当前会话的消息
-        const result = data.filter(item => item.conversationID === state.currentConversation.conversationID)
+        const result = data.filter(item => item.recvID===JSON.parse(state.currentConversation.latestMsg).recvID&& item.sendID === JSON.parse(state.currentConversation.latestMsg).sendID)
         console.log("收到消息1")
-        if(result[0].cloudCustomData!=null&&result[0].cloudCustomData!=""){
+        console.log("state.currentConversation.conversationID会话id",state.currentConversation.conversationID)
+        console.log("result[0]",result[0])
+        if(result[0].ex!=null&&result[0].ex!=""){
           try{
-            var json=JSON.parse(result[0].cloudCustomData);
-            console.log(json)
+            var json=JSON.parse(result[0].ex);
+            console.log("消息json",json)
             if(json.type!=null&&json.type!=""){
               state.type=json.type;
               state.orderId=json.orderId;
@@ -200,22 +242,20 @@ const conversationModules = {
               state.followId=json.followId;
               state.orderType=json.orderType;
             }
-           
-          
           }
           catch(e){
-
           }
-          
         }
         state.currentMessageList = [...state.currentMessageList, ...result]
         filterCallingMessage(state.currentMessageList)
-      } else if (data.conversationID === state.currentConversation.conversationID) {
+      //} else if ("si_"+data.sendID+"_"+data.recvID === state.currentConversation.conversationID) {
+      } else if (data.recvID===JSON.parse(state.currentConversation.latestMsg).recvID&& data.sendID === JSON.parse(state.currentConversation.latestMsg).sendID) {
+        console.log("会话id22222",state.currentConversation.conversationID)
         state.currentMessageList = [...state.currentMessageList, data]
         console.log("收到消息2")
         console.log(data)
-        if(data.cloudCustomData!=null&&data.cloudCustomData!=""){
-          var json=JSON.parse(data.cloudCustomData);
+        if(data.ex!=null&&data.ex!=""){
+          var json=JSON.parse(data.ex);
           console.log(json)
           try{
             if(json.type!=null&&json.type!=""){
@@ -225,13 +265,9 @@ const conversationModules = {
               state.followId=json.followId;
               state.orderType=json.orderType;
             }
-            
           }
           catch(e){
-
           }
-
-          
         }
         filterCallingMessage(state.currentMessageList)
       }
@@ -258,13 +294,14 @@ const conversationModules = {
     }
   },
   actions: {
+
     /**
      * 获取消息列表
      * 调用时机:打开某一会话时或下拉获取历史消息时
      * @param {Object} context
      * @param {String} conversationID
      */
-    getMessageList(context, conversationID) {
+    async getMessageList(context, conversationID) {
       if (context.state.isCompleted) {
         context.commit('showMessage', {
           message: '已经没有更多的历史消息了哦',
@@ -272,16 +309,48 @@ const conversationModules = {
         })
         return
       }
-      const { nextReqMessageID, currentMessageList } = context.state
-      tim.getMessageList({ conversationID, nextReqMessageID, count: 15 }).then(imReponse => {
-        // 更新messageID,续拉时要用到
-        context.state.nextReqMessageID = imReponse.data.nextReqMessageID
-        context.state.isCompleted = imReponse.data.isCompleted
-        // 更新当前消息列表,从头部插入
-        context.state.currentMessageList = [...imReponse.data.messageList, ...currentMessageList]
-        filterCallingMessage(context.state.currentMessageList)
 
-      })
+      const { currentMessageList, lastClientMsgID } = context.state
+
+      try {
+        // 调用 OpenIM 的历史消息接口
+        const res = await OpenIM.getAdvancedHistoryMessageList({
+          conversationID: conversationID,
+          count: 100,
+          startClientMsgID: lastClientMsgID || '', // 第一次为空
+          lastMinSeq: 0,
+          viewType:0
+        })
+
+        let messageList = res.data.messageList || []
+        //过滤掉双方成为好友通知
+        messageList = messageList.filter(message => message.contentType !== 1201)
+        console.log("q切换会话获取聊天记录",messageList)
+        if (messageList.length === 0) {
+          context.state.isCompleted = true
+          context.commit('showMessage', {
+            message: '已经没有更多的历史消息了哦',
+            type: 'info'
+          })
+          return
+        }
+
+        // 更新 lastClientMsgID 为本次最早的消息,用于下次分页
+        context.state.lastClientMsgID = messageList[0].clientMsgID
+        // 插入历史消息到前面
+        console.log("messageList",messageList)
+        context.state.currentMessageList = [...messageList, ...currentMessageList]
+
+        console.log(context.state.currentMessageList)
+        // 过滤通话相关消息(如有)
+        //filterCallingMessage(context.state.currentMessageList)
+      } catch (error) {
+        console.error('获取历史消息失败:', error)
+        context.commit('showMessage', {
+          message: '获取历史消息失败',
+          type: 'error'
+        })
+      }
     },
     /**
      * 切换会话
@@ -289,39 +358,116 @@ const conversationModules = {
      * @param {Object} context
      * @param {String} conversationID
      */
-    checkoutConversation(context, conversationID) {
+    checkoutConversation(context, conversation) {
+      console.log("conversation",conversation)
+
       context.commit('resetCurrentMemberList')
       context.commit('resetSelectedMessage', false)
       context.commit('resetFriendContent')
+      /*console.log("用户id"+context.state.currentConversation.userID)
+      console.log("会话类型"+context.state.currentConversation.conversationType)
+      console.log("会话id"+context.state.currentConversation.conversationID)*/
       // 1.切换会话前,将切换前的会话进行已读上报
-      if (context.state.currentConversation.conversationID) {
+      //if (context.state.currentConversation.conversationID) {
+        //console.log("标记切换前会话为已读",context.state.currentConversation)
         const prevConversationID = context.state.currentConversation.conversationID
-        tim.setMessageRead({ conversationID: prevConversationID })
-      }
+        OpenIM.markConversationMessageAsRead(prevConversationID).then(({ data }) => {
+          // 调用成功
+        })
+          .catch(({ errCode, errMsg }) => {
+            if (errCode !== 10303) {
+              console.error("标记已读失败");
+            }
+          });
+        /*OpenIM.getOneConversation({
+          sourceID: context.state.currentConversation.userID,
+          sessionType: context.state.currentConversation.conversationType,
+        }).then(({ data }) => {
+          console.log("当前会话未读数量",data.unreadCount)
+
+          /!*if (data.unreadCount>0){
+            OpenIM.markConversationMessageAsRead(prevConversationID)
+          }*!/
+        })
+          .catch(({ errCode, errMsg }) => {
+            // 调用失败
+          });*/
+      //}
+      //console.log("标记待切换会话为已读",conversation.conversationID)
       // 2.待切换的会话也进行已读上报
-      tim.setMessageRead({ conversationID })
+      OpenIM.markConversationMessageAsRead(conversation.conversationID).then(({ data }) => {
+        // 调用成功
+      })
+        .catch(({ errCode, errMsg }) => {
+          if (errCode !== 10303) {
+            console.error("标记已读失败");
+          }
+        });
+      /*console.log("context.state.currentConversation",context.state.currentConversation)
+      console.log("conversation",conversation)*/
+
+      /*OpenIM.getOneConversation({
+        sourceID: conversation.userID,
+        sessionType: conversation.conversationType,
+      }).then(({ data }) => {
+        console.log("下一个会话未读数量",data.unreadCount)
+
+        /!*if (data.unreadCount>0){
+
+          // 2.待切换的会话也进行已读上报
+          OpenIM.markConversationMessageAsRead(conversation.conversationID)
+        }*!/
+      })
+        .catch(({ errCode, errMsg }) => {
+          // 调用失败
+        });*/
+
       // 3. 获取会话信息
-      return tim.getConversationProfile(conversationID).then(({ data }) => {
-        console.log("获取会话信息");
+      return OpenIM.getOneConversation({
+        sourceID:conversation.userID,
+        sessionType:conversation.conversationType
+      }).then(({ data }) => {
+        console.log("获取会话信息",data);
         console.log(data);
-        if(data.conversation.lastMessage.cloudCustomData!=null){
+        context.state.lastClientMsgID = ''
+        context.state.currentMessageList = []
+        context.state.isCompleted = false
+        console.log("ex的值",data.ex)
+        if(data.ex){
           try{
-            context.state.imType=JSON.parse(data.conversation.lastMessage.cloudCustomData).imType;
-            context.state.orderId=JSON.parse(data.conversation.lastMessage.cloudCustomData).orderId;
-            context.state.orderType=JSON.parse(data.conversation.lastMessage.cloudCustomData).orderType;
-            context.state.followId=JSON.parse(data.conversation.lastMessage.cloudCustomData).followId;
-            context.state.type=JSON.parse(data.conversation.lastMessage.cloudCustomData).type;
+            context.state.imType=JSON.parse(data.ex).imType;
+            context.state.orderId=JSON.parse(data.ex).orderId;
+            context.state.orderType=JSON.parse(data.ex).orderType;
+            context.state.followId=JSON.parse(data.ex).followId;
+            context.state.type=JSON.parse(data.ex).type;
           }
           catch(e){
           }
+        }else{
+          console.log("m没有ex")
+          context.state.imType=''
+          context.state.orderId='';
+          context.state.orderType='';
+          context.state.followId='';
+          context.state.type='';
         }
+        OpenIM.getAllConversationList()
+          .then(({ data }) => {
+            // 调用成功
+            console.log("--------------------------------------------------------------------------------------------------------------------")
+            console.log(data)
+            context.commit('updateConversationList', data)
+          })
+          .catch(({ errCode, errMsg }) => {
+            // 调用失败
+          })
         // 3.1 更新当前会话
-        context.commit('updateCurrentConversation', data.conversation)
+        context.commit('updateCurrentConversation', data)
         // 3.2 获取消息列表
-        context.dispatch('getMessageList', conversationID)
+        context.dispatch('getMessageList', conversation.conversationID)
         // 3.3 拉取第一页群成员列表
-        if (data.conversation.type === TIM.TYPES.CONV_GROUP) {
-          return context.dispatch('getGroupMemberList', data.conversation.groupProfile.groupID)
+        if (data.conversationType === 4) {
+          return context.dispatch('getGroupMemberList', data.groupID)
         }
         return Promise.resolve()
       })

+ 7 - 16
src/store/modules/friend.js

@@ -1,4 +1,3 @@
-import TIM from 'tim-js-sdk/tim-js-friendship'
 const friendModules = {
   state: {
     friendList: [],
@@ -9,7 +8,7 @@ const friendModules = {
     currentMemberList: [],
     friendContent: {},
     applicationContent: {},
-    },
+  },
   mutations: {
     updateFriendList(state, friendList) {
       state.friendList = friendList
@@ -36,31 +35,24 @@ const friendModules = {
       state.friendContent = {}
     },
     deleteApplicationList(state, applicationInfo) {
-      const { to, applicationType } = applicationInfo
-      if (applicationType === TIM.TYPES.APPLICATION_TYPE_COMEIN) {
-        state.comeInApplicationList = state.comeInApplicationList.filter(item => item.to !== to)
-      }
-      if (applicationType === TIM.TYPES.APPLICATION_TYPE_SENDOUT) {
-        state.sendOutApplicationList = state.sendOutApplicationList.filter(item => item.to !== to)
-      }
+      const { userID } = applicationInfo
+      state.applicationList = state.applicationList.filter(item => item.userID !== userID)
     },
     deleteFriend(state, userID) {
       state.friendList = state.friendList.filter(item => item.userID !== userID)
     },
-    // 删除好友分组
     deleteFriendGroupList(state, groupNameList) {
-      state.friendGroupList = state.friendGroupList.filter((groupItem) => !groupNameList.includes(groupItem.groupName))
+      state.friendGroupList = state.friendGroupList.filter(
+        groupItem => !groupNameList.includes(groupItem.groupName)
+      )
     },
-    // 增加好友分组
     addFriendGroupList(state, friendGroup) {
       state.friendGroupList = [...state.friendGroupList, ...friendGroup]
     },
-    // 更新分组信息
     updateFriendGroupInfo(state, groupInfo) {
-      state.friendGroupList = state.friendGroupList.map( groupItem=> {
+      state.friendGroupList = state.friendGroupList.map(groupItem => {
         return groupItem.groupName === groupInfo.groupName ? groupInfo : groupItem
       })
-
     },
     reset(state) {
       Object.assign(state, {
@@ -81,7 +73,6 @@ const friendModules = {
       context.commit('resetCurrentConversation')
       context.commit('resetFriendContent')
       context.commit('updateApplicationContent', applicationContent)
-
     },
   }
 }

+ 3 - 3
src/store/modules/group.js

@@ -1,4 +1,4 @@
-import tim from '@/tim.js';
+//import tim from '@/tim.js';
 const groupModules = {
   state: {
     groupList: [],
@@ -40,14 +40,14 @@ const groupModules = {
       context.commit('updateGroupList', groupList)
     },
     getGroupMemberList(context, groupID) {
-      return tim.getGroupMemberList({
+      /*return tim.getGroupMemberList({
         groupID: groupID,
         offset: context.state.currentMemberList.length,
         count: 30
       }).then((imResponse) => {
         context.commit('updateCurrentMemberList', imResponse.data.memberList)
         return imResponse
-      })
+      })*/
     }
   }
 }

+ 7 - 5
src/store/modules/imuser.js

@@ -1,4 +1,5 @@
- import tim from '../../tim'
+import { getOpenIM } from '@/utils/openIM';
+const OpenIM = getOpenIM();
 const user = {
   state: {
     currentUserProfile: {},
@@ -9,7 +10,7 @@ const user = {
     sdkAppID: 0,
   },
   mutations: {
-    
+
     updateCurrentUserProfile(state, userProfile) {
       state.currentUserProfile = userProfile
     },
@@ -56,13 +57,14 @@ const user = {
     //       }
     //     })
     // },
-    
+
     imlogout(context) {
+      console.log("退出登录")
       // 若有当前会话,在退出登录时已读上报
       if (context.rootState.conversation.currentConversation.conversationID) {
-        tim.setMessageRead({ conversationID: context.rootState.conversation.currentConversation.conversationID })
+        OpenIM.markConversationMessageAsRead(context.rootState.conversation.currentConversation.conversationID)
       }
-      tim.logout().then(() => {
+      OpenIM.logout().then(() => {
         console.log("imLogout")
         context.commit('toggleIsLogin')
         context.commit('stopComputeCurrent')

+ 27 - 3
src/store/modules/permission.js

@@ -37,9 +37,33 @@ const permission = {
     // 生成路由
     getRoutes({ commit }) {
       return new Promise(resolve => {
-  var rewriteRoutes=  [ {"hidden": true,}]
-          commit('SET_ROUTES', rewriteRoutes)
-          resolve(rewriteRoutes)
+        const loginType = localStorage.getItem('loginType')
+        const isPharmacist = loginType === '2'
+
+        // 根据登录身份动态调整侧边栏菜单
+        constantRoutes.forEach(route => {
+          // 1) 仅药剂师显示“处方审核”菜单
+          if (route.path === '/prescribeAudit') {
+            route.hidden = !isPharmacist
+          }
+          // 2) 药剂师登录时“医生信息”文案改为“药剂师信息”
+          if (route.path === '/doctor') {
+            if (route.meta) {
+              route.meta.title = isPharmacist ? '药剂师信息' : '医生信息'
+            }
+            if (Array.isArray(route.children)) {
+              route.children.forEach(child => {
+                if (child.path === 'doctorInfo' && child.meta) {
+                  child.meta.title = isPharmacist ? '药剂师信息' : '医生信息'
+                }
+              })
+            }
+          }
+        })
+
+        // 不追加动态路由,直接使用调整后的 constantRoutes
+        commit('SET_ROUTES', [])
+        resolve([])
       })
     }
   }

+ 3 - 0
src/store/modules/user.js

@@ -53,6 +53,7 @@ const user = {
           // 设置请求头
           axios.defaults.headers.common['APPToken'] = res.token;
           commit('SET_DOCTOR',res.doctor)
+          commit('setUserId', "D"+res.doctor.doctorId)
           commit('SET_USER_ID', "D-"+res.doctor.doctorId)
           resolve(res)
         }).catch(error => {
@@ -73,6 +74,7 @@ const user = {
           commit('SET_NAME', doctor.doctorName)
           commit('SET_AVATAR', avatar)
           commit('SET_DOCTOR', res.doctor)
+          commit('setUserId', "D"+res.doctor.doctorId)
           commit('SET_USER_ID', "D-"+res.doctor.doctorId)
           resolve(res)
         }).catch(error => {
@@ -95,6 +97,7 @@ const user = {
     GetUserId({ state }) {
       return state.userId
     }
+
   }
 }
 

+ 13 - 13
src/tim.js

@@ -1,17 +1,17 @@
-import TIM from 'tim-js-sdk/tim-js-friendship.js'
-import TIMUploadPlugin from 'tim-upload-plugin'
-import { imConfig } from '@/utils/im'
-// 初始化 SDK 实例
+// import TIM from 'tim-js-sdk/tim-js-friendship.js'
+// import TIMUploadPlugin from 'tim-upload-plugin'
+// import { imConfig } from '@/utils/im'
+// // 初始化 SDK 实例
 
-const tim = TIM.create({
-  SDKAppID: imConfig.SDKAPPID
-})
+// const tim = TIM.create({
+//   SDKAppID: imConfig.SDKAPPID
+// })
 
-window.setLogLevel = tim.setLogLevel
+// window.setLogLevel = tim.setLogLevel
 
-// 无日志级别
-tim.setLogLevel(4)
+// // 无日志级别
+// tim.setLogLevel(4)
 
-// 注册 cos
-tim.registerPlugin({ 'tim-upload-plugin':TIMUploadPlugin })
-export default tim
+// // 注册 cos
+// tim.registerPlugin({ 'tim-upload-plugin':TIMUploadPlugin })
+// export default tim

+ 11 - 11
src/trtc-calling.js

@@ -1,14 +1,14 @@
-import  tim from './tim'
-import TRTCCalling from 'trtc-calling-js'
-import { imConfig } from '@/utils/im'
-let options = {
-  SDKAppID: imConfig.SDKAPPID,  // 接入时需要将0替换为您的云通信应用的 SDKAppID
-  tim: tim,
-}
+// import  tim from './tim'
+// import TRTCCalling from 'trtc-calling-js'
+// import { imConfig } from '@/utils/im'
+// let options = {
+//   SDKAppID: imConfig.SDKAPPID,  // 接入时需要将0替换为您的云通信应用的 SDKAppID
+//   tim: tim,
+// }
 
-const trtcCalling = new TRTCCalling(options)
+// const trtcCalling = new TRTCCalling(options)
 
-// 4 无日志级别
-trtcCalling.setLogLevel(0)
+// // 4 无日志级别
+// trtcCalling.setLogLevel(0)
 
-export default trtcCalling
+// export default trtcCalling

+ 55 - 55
src/utils/common.js

@@ -2,7 +2,7 @@
  * 通用js方法封装处理
  * Copyright (c) 2019 ruoyi
  */
-import TIM from 'tim-js-sdk/tim-js-friendship'
+//import TIM from 'tim-js-sdk/tim-js-friendship'
 
 
 export function translateGroupSystemNotice(message) {
@@ -38,7 +38,7 @@ export function translateGroupSystemNotice(message) {
 		return '自定义群系统通知: ' + message.payload.userDefinedField
 	}
   }
-  
+
   export const errorMap = {
 	500: '服务器错误',
 	602: '用户名或密码不合法',
@@ -47,57 +47,57 @@ export function translateGroupSystemNotice(message) {
 	620: '用户不存在',
 	621: '密码错误'
   }
-  export function filterCallingMessage(currentMessageList) {
-	currentMessageList.forEach((item) => {
-	  if (item.callType) {   // 对于自己伪造的消息不需要解析
-		return
-	  }
-	  if (item.type === TIM.TYPES.MSG_MERGER && item.payload.downloadKey !== '') {
-		let promise = window.tim.downloadMergerMessage(item)
-		promise.then(function(imResponse) {
-		  // 下载成功后,SDK会更新 message.payload.messageList 等信息
-		  item = imResponse
-		}).catch(function(imError) {
-		  // 下载失败
-		  console.warn('downloadMergerMessage error:', imError)
-		})
-	  }
-	  if(item.type === TIM.TYPES.MSG_CUSTOM) {
-		let payloadData = {}
-		try {
-		  payloadData = JSON.parse(item.payload.data)
-		}catch (e) {
-		  payloadData = {}
-		}
-		if(payloadData.businessID === 1) {
-		  if(item.conversationType === TIM.TYPES.CONV_GROUP) {
-			if(payloadData.actionType === 5) {
-			  item.nick = payloadData.inviteeList ? payloadData.inviteeList.join(','):item.from
-			}
-			let _text = window.trtcCalling.extractCallingInfoFromMessage(item)
-			let group_text = `${_text}`
-			item.type = TIM.TYPES.MSG_GRP_TIP
-			let customData = {
-			  operationType: 256,
-			  text: group_text,
-			  userIDList: []
-			}
-			item.payload = customData//JSON.stringify(customData)
-		  }
-		  if(item.conversationType === TIM.TYPES.CONV_C2C) {
-			let c2c_text = window.trtcCalling.extractCallingInfoFromMessage(item)
-			let customData = {
-			  text: c2c_text
-			}
-			item.payload = customData//JSON.stringify(customData)
-		  }
-		}
-	  }
-  
-	})
-  }
-  
-  
+export function filterCallingMessage(currentMessageList) {
+  currentMessageList.forEach((item) => {
+    console.log("item",item)
+    if (item.callType) {   // 对于自己伪造的消息不需要解析
+      return;
+    }
+    if (item.type === 'MSG_MERGER' && item.payload.downloadKey !== '') {
+      let promise = im.downloadMergerMessage(item);
+      promise.then(function(imResponse) {
+        // 下载成功后,SDK会更新 message.payload.messageList 等信息
+        item = imResponse;
+      }).catch(function(imError) {
+        // 下载失败
+        console.warn('downloadMergerMessage error:', imError);
+      });
+    }
+    if (item.type === 'MSG_CUSTOM') {
+      let payloadData = {};
+      try {
+        payloadData = JSON.parse(item.payload.data);
+      } catch (e) {
+        payloadData = {};
+      }
+      if (payloadData.businessID === 1) {
+        if (item.conversationType === 'GROUP') {
+          if (payloadData.actionType === 5) {
+            item.nick = payloadData.inviteeList ? payloadData.inviteeList.join(',') : item.from;
+          }
+          let _text = im.extractCallingInfoFromMessage(item);
+          let group_text = `${_text}`;
+          item.type = 'MSG_GRP_TIP';
+          let customData = {
+            operationType: 256,
+            text: group_text,
+            userIDList: []
+          };
+          item.payload = customData; // 通过 OpenIM SDK 传递自定义数据
+        }
+        if (item.conversationType === 'C2C') {
+          let c2c_text = im.extractCallingInfoFromMessage(item);
+          let customData = {
+            text: c2c_text
+          };
+          item.payload = customData; // 通过 OpenIM SDK 传递自定义数据
+        }
+      }
+    }
+  });
+}
+
+
 
 
 const baseURL = process.env.VUE_APP_BASE_API
@@ -147,7 +147,7 @@ export function cloneObject(obj) {
 	if (typeof obj != "object") return;
 	return JSON.parse(JSON.stringify(obj));
 }
- 
+
 // 表单重置
 export function resetForm(refName) {
 	if (this.$refs[refName]) {
@@ -199,7 +199,7 @@ export function selectDictLabels(datas, value, separator) {
 
 // 通用下载方法
 export function download(fileName) {
-	window.location.href = baseURL + "/common/download?fileName=" + encodeURI(fileName) + "&delete=" + true;
+	window.location.href = baseURL + "/app/common/download?fileName=" + encodeURI(fileName) + "&delete=" + true;
 }
 
 // 字符串格式化(%s )

+ 1 - 1
src/utils/decodeText.js

@@ -10,7 +10,7 @@ import { emojiMap, emojiUrl } from './emojiMap'
 export function decodeText (payload) {
   let renderDom = []
   // 文本消息
-    let temp = payload.text
+    let temp = payload.content
     let left = -1
     let right = -1
     while (temp !== '') {

+ 215 - 1
src/utils/emojiMap.js

@@ -143,7 +143,221 @@ export const emojiMap = {
   '[鼓掌]': 'emoji_140@2x.png',
   '[龇牙]': 'emoji_141@2x.png'
 }
+export const emojiCharMap = {
+  '[微笑]': '😀',
+  '[撇嘴]': '😒',
+  '[色]': '😍',
+  '[发呆]': '😳',
+  '[得意]': '😏',
+  '[流泪]': '😭',
+  '[害羞]': '😊',
+  '[闭嘴]': '🤐',
+  '[睡]': '😴',
+  '[大哭]': '😢',
+  '[尴尬]': '😅',
+  '[发怒]': '😡',
+  '[调皮]': '😜',
+  '[呲牙]': '😁',
+  '[惊讶]': '😲',
+  '[难过]': '😞',
+  '[酷]': '😎',
+  '[冷汗]': '😰',
+  '[抓狂]': '🤯',
+  '[吐]': '🤮',
+  '[偷笑]': '😏',
+  '[愉快]': '😄',
+  '[白眼]': '🙄',
+  '[傲慢]': '😤',
+  '[饥饿]': '😫',
+  '[困]': '😪',
+  '[惊恐]': '😱',
+  '[流汗]': '😓',
+  '[憨笑]': '😄',
+  '[大兵]': '💂‍♂️',
+  '[奋斗]': '💪',
+  '[咒骂]': '🖕',
+  '[疑问]': '❓',
+  '[嘘]': '🤫',
+  '[晕]': '😵',
+  '[折磨]': '😖',
+  '[衰]': '😞',
+  '[骷髅]': '💀',
+  '[敲打]': '👊',
+  '[再见]': '👋',
+  '[擦汗]': '😅',
+  '[抠鼻]': '🤭',
+  '[鼓掌]': '👏',
+  '[糗大了]': '😳',
+  '[坏笑]': '😈',
+  '[左哼哼]': '😤',
+  '[右哼哼]': '😤',
+  '[哈欠]': '🥱',
+  '[鄙视]': '😒',
+  '[委屈]': '😞',
+  '[快哭了]': '😢',
+  '[阴险]': '😏',
+  '[亲亲]': '😘',
+  '[吓]': '😱',
+  '[可怜]': '🥺',
+  '[菜刀]': '🔪',
+  '[西瓜]': '🍉',
+  '[啤酒]': '🍺',
+  '[篮球]': '🏀',
+  '[乒乓]': '🏓',
+  '[咖啡]': '☕',
+  '[饭]': '🍚',
+  '[猪头]': '🐷',
+  '[玫瑰]': '🌹',
+  '[凋谢]': '🥀',
+  '[示爱]': '💌',
+  '[爱心]': '❤️',
+  '[心碎]': '💔',
+  '[蛋糕]': '🎂',
+  '[闪电]': '⚡',
+  '[炸弹]': '💣',
+  '[刀]': '🔪',
+  '[足球]': '⚽',
+  '[瓢虫]': '🐞',
+  '[便便]': '💩',
+  '[月亮]': '🌙',
+  '[太阳]': '☀️',
+  '[礼物]': '🎁',
+  '[拥抱]': '🤗',
+  '[强]': '💪',
+  '[弱]': '🙌',
+  '[握手]': '🤝',
+  '[胜利]': '✌️',
+  '[抱拳]': '🙏',
+  '[勾引]': '😉',
+  '[拳头]': '👊',
+  '[差劲]': '👎',
+  '[爱你]': '❤️',
+  '[NO]': '🚫',
+  '[OK]': '👌',
+  '[爱情]': '💕',
+  '[飞吻]': '😘',
+  '[跳跳]': '🤸',
+  '[发抖]': '🤒',
+  '[怄火]': '😡',
+  '[转圈]': '🔄',
+  '[磕头]': '🙏',
+  '[回头]': '↩️',
+  '[跳绳]': '🤾',
+  '[投降]': '🏳️‍🌈',
+  '[激动]': '🤩',
+  '[乱舞]': '💃',
+  '[献吻]': '😘',
+  '[左太极]': '☯️',
+  '[右太极]': '☯️'
+};
 export const emojiName = [
+  '[微笑]',
+  '[撇嘴]',
+  '[色]',
+  '[发呆]',
+  '[得意]',
+  '[流泪]',
+  '[害羞]',
+  '[闭嘴]',
+  '[睡]',
+  '[大哭]',
+  '[尴尬]',
+  '[发怒]',
+  '[调皮]',
+  '[呲牙]',
+  '[惊讶]',
+  '[难过]',
+  '[酷]',
+  '[冷汗]',
+  '[抓狂]',
+  '[吐]',
+  '[偷笑]',
+  '[愉快]',
+  '[白眼]',
+  '[傲慢]',
+  '[饥饿]',
+  '[困]',
+  '[惊恐]',
+  '[流汗]',
+  '[憨笑]',
+  '[大兵]',
+  '[奋斗]',
+  '[咒骂]',
+  '[疑问]',
+  '[嘘]',
+  '[晕]',
+  '[折磨]',
+  '[衰]',
+  '[骷髅]',
+  '[敲打]',
+  '[再见]',
+  '[擦汗]',
+  '[抠鼻]',
+  '[鼓掌]',
+  '[糗大了]',
+  '[坏笑]',
+  '[左哼哼]',
+  '[右哼哼]',
+  '[哈欠]',
+  '[鄙视]',
+  '[委屈]',
+  '[快哭了]',
+  '[阴险]',
+  '[亲亲]',
+  '[吓]',
+  '[可怜]',
+  '[菜刀]',
+  '[西瓜]',
+  '[啤酒]',
+  '[篮球]',
+  '[乒乓]',
+  '[咖啡]',
+  '[饭]',
+  '[猪头]',
+  '[玫瑰]',
+  '[凋谢]',
+  '[示爱]',
+  '[爱心]',
+  '[心碎]',
+  '[蛋糕]',
+  '[闪电]',
+  '[炸弹]',
+  '[刀]',
+  '[足球]',
+  '[瓢虫]',
+  '[便便]',
+  '[月亮]',
+  '[太阳]',
+  '[礼物]',
+  '[拥抱]',
+  '[强]',
+  '[弱]',
+  '[握手]',
+  '[胜利]',
+  '[抱拳]',
+  '[勾引]',
+  '[拳头]',
+  '[差劲]',
+  '[爱你]',
+  '[NO]',
+  '[OK]',
+  '[爱情]',
+  '[飞吻]',
+  '[跳跳]',
+  '[发抖]',
+  '[怄火]',
+  '[转圈]',
+  '[磕头]',
+  '[回头]',
+  '[跳绳]',
+  '[投降]',
+  '[激动]',
+  '[乱舞]',
+  '[献吻]',
+  '[左太极]',
+  '[右太极]'
+]
+/*export const emojiName = [
   '[龇牙]',
   '[调皮]',
   '[流汗]',
@@ -282,4 +496,4 @@ export const emojiName = [
   '[纸巾]',
   '[手枪]',
   '[青蛙]'
-]
+]*/

+ 37 - 4
src/utils/im.js

@@ -1,5 +1,38 @@
-  export const imConfig = {
-	  // SDKAPPID: process.env.VUE_APP_IM_CONFIG
-	  // SDKAPPID: 1600089873
-    SDKAPPID: parseInt(process.env.VUE_APP_IM_CONFIG, 10)
+// @/utils/im.js - OpenIM 配置文件
+
+const config = {
+  // 基础配置
+  SDKAPPID: 0,
+  SECRET: 'openIM123',
+
+  // 服务器地址配置
+  // API_ADDRESS: 'https://web.im.ysya.top/api', // OpenIM API地址
+  // WS_ADDRESS: 'wss://web.im.ysya.top/msg_gateway', // WebSocket地址
+  API_ADDRESS: 'https://web.im.cdwjyyh.com/api', // OpenIM API地址
+  WS_ADDRESS: 'wss://web.im.cdwjyyh.com/msg_gateway', // WebSocket地址
+  // 平台配置
+  PLATFORM_ID: 5, // 平台ID (5=Web)
+
+  // 其他配置
+  DATA_DIR: '/imdata', // 数据存储目录
+  LOG_LEVEL: 'debug', // 日志级别
+
+  // 获取完整登录配置
+  getLoginConfig(userID, token) {
+    return {
+      userID,
+      token,
+      platformID: this.PLATFORM_ID,
+      apiAddr: this.API_ADDRESS,
+      wsAddr: this.WS_ADDRESS,
+      dataDir: this.DATA_DIR
+    }
+  },
+
+  // 生成随机用户ID (测试用)
+  genTestUserID() {
+    return `test_${Math.random().toString(36).substr(2, 8)}`
   }
+}
+
+export default config

+ 38 - 0
src/utils/openIM.js

@@ -0,0 +1,38 @@
+import { getSDK, CbEvents } from '@openim/wasm-client-sdk';
+
+let sdkInstance = null;
+let cbEvents = null;
+
+export function getOpenIM() {
+  if (!sdkInstance) {
+    sdkInstance = getSDK(); // 只调用一次
+    console.log("OpenIM SDK 初始化完成");
+  }
+  return sdkInstance;
+}
+export const getCbEvents = () => {
+  if (!cbEvents) {
+    cbEvents = CbEvents;
+  }
+  return cbEvents;
+};
+
+// 设置全局监听(只需一次)
+/*export const setupListeners = (store) => {
+  if (isListenersSetup) return;
+
+  const im = getOpenIM();
+  im.on(CbEvents.OnConnectSuccess, () => {
+    store.commit('im/setReadyState', true);
+    console.log('[OpenIM] 连接成功');
+  });
+
+  im.on(CbEvents.OnConnectFailed, (error) => {
+    store.commit('im/setError', error.message);
+    console.error('[OpenIM] 连接失败:', error);
+  });
+
+  // 其他必要的事件监听...
+  isListenersSetup = true;
+  console.log('[OpenIM] 全局监听器已设置');
+};*/

+ 1 - 0
src/utils/request.js

@@ -24,6 +24,7 @@ service.interceptors.request.use(config => {
   if (getToken() && !isToken) {
     config.headers['APPToken'] =  getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
     config.headers['X-Frontend-Type'] = 'doctor'
+    config.headers['Device-Type'] = 'web'
   }
   // get请求映射params参数
   if (config.method === 'get' && config.params) {

+ 23 - 23
src/utils/rtc-client.js

@@ -1,5 +1,5 @@
 /* eslint-disable */
-import TRTC from 'trtc-js-sdk'
+//import TRTC from 'trtc-js-sdk'
 
 class RtcClient {
   constructor(options) {
@@ -15,11 +15,11 @@ class RtcClient {
     this.ready = false
 
     // check if browser is compatible with TRTC
-    TRTC.checkSystemRequirements().then(result => {
+    /*TRTC.checkSystemRequirements().then(result => {
       if (!result) {
         alert('Your browser is not compatible with TRTC! Please download Chrome M72+');
       }
-    });
+    });*/
   }
 
   async join() {
@@ -29,12 +29,12 @@ class RtcClient {
     }
 
     // create a client for RtcClient
-    this.client_ = TRTC.createClient({
-      mode: 'videoCall', // 实时通话模式
-      sdkAppId: this.sdkAppId_,
-      userId: this.userId_,
-      userSig: this.userSig_
-    });
+    // this.client_ = TRTC.createClient({
+    //   mode: 'videoCall', // 实时通话模式
+    //   sdkAppId: this.sdkAppId_,
+    //   userId: this.userId_,
+    //   userSig: this.userSig_
+    // });
 
     // 处理 client 事件
     this.handleEvents();
@@ -152,19 +152,19 @@ class RtcClient {
     this.localStream_.unmuteVideo();
   }
 
-  async createLocalStream(options) {
-    this.localStream_ = TRTC.createStream({
-      audio: options.audio, // 采集麦克风
-      video: options.video, // 采集摄像头
-      userId: this.userId_
-      // cameraId: getCameraId(),
-      // microphoneId: getMicrophoneId()
-    });
-    // 设置视频分辨率帧率和码率
-    this.localStream_.setVideoProfile('480p');
-
-    await this.localStream_.initialize();
-  }
+  // async createLocalStream(options) {
+  //   this.localStream_ = TRTC.createStream({
+  //     audio: options.audio, // 采集麦克风
+  //     video: options.video, // 采集摄像头
+  //     userId: this.userId_
+  //     // cameraId: getCameraId(),
+  //     // microphoneId: getMicrophoneId()
+  //   });
+  //   // 设置视频分辨率帧率和码率
+  //   this.localStream_.setVideoProfile('480p');
+  //
+  //   await this.localStream_.initialize();
+  // }
 
   handleEvents() {
     // 处理 client 错误事件,错误均为不可恢复错误,建议提示用户后刷新页面
@@ -262,4 +262,4 @@ class RtcClient {
   }
 }
 
-export default RtcClient
+export default RtcClient

+ 172 - 0
src/utils/trtc.js

@@ -0,0 +1,172 @@
+import {
+  Room,
+  RoomEvent,
+  createLocalTracks,
+  createLocalAudioTrack,
+  createLocalVideoTrack,
+} from 'livekit-client'
+
+console.log("Room 构造函数:", Room)
+
+class Trtc {
+  constructor() {
+    this.room = null
+    this.localTracks = []
+  }
+
+  // 安全 attach 远端轨道
+  attachTrack(track, participant) {
+    const container = document.getElementById('video-' + participant.identity)
+    if (!container) return
+
+    // 已经 attach 过就跳过,避免闪屏
+    if (track.attachedElements.length > 0) return
+
+    const element = track.attach()
+    element.style.width = '100%'
+    element.style.height = '100%'
+    element.style.objectFit = 'cover'
+    container.appendChild(element)
+  }
+
+  async joinRoom(token, url, userId, onRemoteTrack) {
+    try {
+      // 如果已有房间,先退出
+      if (this.room) {
+        await this.leaveRoom()
+      }
+
+      this.room = new Room()
+
+      // 监听远端轨道
+      this.room.on(RoomEvent.TrackSubscribed, (track, publication, participant) => {
+        console.log('📡 TrackSubscribed:', track.kind, participant.identity)
+        if (track.kind === 'video' || track.kind === 'audio') {
+          this.attachTrack(track, participant)
+          if (typeof onRemoteTrack === 'function') {
+            onRemoteTrack(track, participant)
+          }
+        }
+      })
+
+      // 检查设备
+      this.localTracks = []
+      if (navigator.mediaDevices && navigator.mediaDevices.enumerateDevices) {
+        const devices = await navigator.mediaDevices.enumerateDevices()
+        const hasMic = devices.some(d => d.kind === 'audioinput')
+        const hasCam = devices.some(d => d.kind === 'videoinput')
+
+        if (hasMic || hasCam) {
+          this.localTracks = await createLocalTracks({
+            audio: hasMic,
+            video: hasCam,
+          })
+          console.log('本地轨道列表:', this.localTracks)
+        } else {
+          console.warn('未检测到摄像头或麦克风,将以静默方式加入房间')
+        }
+      } else {
+        console.warn('当前环境不支持 mediaDevices,将以纯接收模式加入房间')
+      }
+
+      console.log('发起连接到 LiveKit ...', url)
+
+      // 监听连接状态
+      const connectedPromise = new Promise((resolve, reject) => {
+        const timeout = setTimeout(() => {
+          reject(new Error('连接 LiveKit 超时'))
+        }, 15000)
+
+        try {
+          this.room.once(RoomEvent.Connected, () => {
+            clearTimeout(timeout)
+            console.log('成功连接到 LiveKit 房间')
+            resolve()
+
+            // 主动挂载已有轨道
+            if (this.room.participants && typeof this.room.participants.values === 'function') {
+              for (const participant of this.room.participants.values()) {
+                participant.tracks.forEach(publication => {
+                  if (publication.isSubscribed && publication.track) {
+                    this.attachTrack(publication.track, participant)
+                    if (typeof onRemoteTrack === 'function') {
+                      onRemoteTrack(publication.track, participant)
+                    }
+                  }
+                })
+              }
+            }
+          })
+
+          this.room.once(RoomEvent.ConnectionError, (err) => {
+            clearTimeout(timeout)
+            console.error('LiveKit 连接错误:', err)
+            reject(err)
+          })
+        } catch (e) {
+          console.error('LiveKit连接失败:', e)
+        }
+      })
+
+      this.room.on(RoomEvent.Disconnected, () => {
+        console.warn('与 LiveKit 断开连接')
+      })
+
+      // 发起连接
+      await this.room.connect(url, token, {
+        autoSubscribe: true,
+      })
+
+      await connectedPromise
+
+      // 发布本地轨道
+      for (const track of this.localTracks) {
+        console.log('正在发布轨道:', track.kind)
+        await this.room.localParticipant.publishTrack(track)
+      }
+
+      return this.room
+    } catch (err) {
+      console.error('joinRoom 出错:', err)
+      throw err
+    }
+  }
+
+  getLocalVideoTrack() {
+    return this.localTracks.find(t => t.kind === 'video') || null
+  }
+
+  async leaveRoom() {
+    if (this.room) {
+      console.log("Trtc 离开房间")
+
+      // 停止并释放本地轨道
+      this.localTracks.forEach(track => {
+        try {
+          track.stop()
+          track.detach()
+        } catch (e) {
+          console.warn("轨道释放失败:", e)
+        }
+      })
+
+      try {
+        await this.room.disconnect()
+      } catch (e) {
+        console.error("房间断开异常:", e)
+      }
+
+      this.room = null
+      this.localTracks = []
+    } else {
+      this.room = null
+      console.warn("离开房间 时 room 为 null")
+    }
+  }
+}
+
+export {
+  createLocalAudioTrack,
+  createLocalVideoTrack,
+}
+export default new Trtc()

+ 225 - 0
src/views/collection/index.vue

@@ -0,0 +1,225 @@
+<template>
+    <div class="app-container">
+        <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+            <el-form-item label="患者姓名" prop="userName">
+                <el-input v-model="queryParams.userName" placeholder="请输入患者姓名" clearable size="small"
+                    @keyup.enter.native="handleQuery" />
+            </el-form-item>
+            <el-form-item label="医生确认" prop="doctorConfirm">
+                <el-select v-model="queryParams.doctorConfirm" placeholder="请选择" clearable size="small">
+                    <el-option label="未确认" value="0" />
+                    <el-option label="已确认" value="1" />
+                </el-select>
+            </el-form-item>
+            <el-form-item label="患者确认" prop="userConfirm">
+                <el-select v-model="queryParams.userConfirm" placeholder="请选择" clearable size="small">
+                    <el-option label="未确认" value="0" />
+                    <el-option label="已确认" value="1" />
+                </el-select>
+            </el-form-item>
+            <el-form-item>
+                <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+                <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+            </el-form-item>
+        </el-form>
+
+        <el-table v-loading="loading" border :data="list" @selection-change="handleSelectionChange">
+            <el-table-column type="selection" width="55" align="center" />
+            <el-table-column label="患者姓名" align="center" prop="userName" />
+            <el-table-column label="患者补充说明" :show-overflow-tooltip="true" align="center" prop="userAdvice"></el-table-column>
+            <el-table-column label="患者确认状态" align="center" prop="userConfirm">
+                <template slot-scope="scope">
+                    <el-tag v-if="scope.row.userConfirm == 0">未确认</el-tag>
+                    <el-tag type="success" v-if="scope.row.userConfirm == 1">已确认</el-tag>
+                </template>
+            </el-table-column>
+            <el-table-column label="医生确认状态" align="center" prop="doctorConfirm">
+                <template slot-scope="scope">
+                    <el-tag v-if="scope.row.doctorConfirm == 0">未确认</el-tag>
+                    <el-tag type="success" v-if="scope.row.doctorConfirm == 1">已确认</el-tag>
+                </template>
+            </el-table-column>
+            <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+                <template slot-scope="scope">
+                    <el-button size="mini" type="text" @click="handleDetial(scope.row)">详情</el-button>
+                    <el-button size="mini" type="text" @click="handleAdvice(scope.row)">建议</el-button>
+                </template>
+            </el-table-column>
+        </el-table>
+        <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum"
+            :limit.sync="queryParams.pageSize" @pagination="getList" />
+
+        <el-dialog title="采集信息详情" :visible.sync="collectionOpen" width="1000px" append-to-body>
+            <el-form ref="collectionForm" :model="collectionForm" label-width="110px">
+                <div v-for="(answer, index) in collectionForm.answers">
+                    <div style="margin-bottom: 20px;margin-top: 20px;">
+                        <span style="font-size: 15px;font-weight: bold;    margin-left: 31px">{{ answer.title }}</span>
+                    </div>
+                    <div style="margin-left: 31px;">
+                        <el-checkbox-group :disabled="true" v-model="collectionForm.answers[index].value" size="mini" >
+                        <el-checkbox v-for="dict in answer.options" :label="dict.value" >{{ dict.name }}</el-checkbox>
+                    </el-checkbox-group>
+                    </div>
+                </div>
+            </el-form>
+            <div slot="footer" class="dialog-footer">
+                <el-button @click="cancel">关 闭</el-button>
+            </div>
+        </el-dialog>
+
+        <el-dialog title="医生建议"  :visible.sync="open" width="1000px" append-to-body>
+            <el-form ref="form" :rules="rules" :model="form" label-width="110px">
+                <el-form-item label="建议" prop="doctorAdvice">
+                    <el-input :autosize="{ minRows: 15, maxRows: 100}" v-model="form.doctorAdvice" type="textarea" placeholder="请输入内容" />
+                </el-form-item>
+            </el-form>
+            <div slot="footer" class="dialog-footer">
+                <el-button type="primary" @click="submitForm">确 认</el-button>
+                <el-button @click="cancelAdvice">关 闭</el-button>
+            </div>
+        </el-dialog>
+    </div>
+</template>
+
+<script>
+import { list, detail, doctorConfirm } from "@/api/collection.js";
+export default {
+    name: "collection",
+    data() {
+        return {
+            queryParams: {
+                pageNum: 1,
+                pageSize: 10,
+            },
+            // 遮罩层
+            loading: true,
+            // 导出遮罩层
+            exportLoading: false,
+            // 选中数组
+            ids: [],
+            // 非单个禁用
+            single: true,
+            // 非多个禁用
+            multiple: true,
+            // 显示搜索条件
+            showSearch: true,
+            // 总条数
+            total: 0,
+            list: [],
+            // 弹出层标题
+            title: "",
+            // 是否显示弹出层
+            open: false,
+            queryParams: {
+                pageNum: 1,
+                pageSize: 10,
+                type: "0",
+            },
+            // 表单参数
+            form: {},
+            // 表单校验
+            rules: {
+                doctorAdvice:[{ required: true, message: '请输入医生建议', trigger: 'blur' },]
+
+            },
+            collectionForm: {
+                answers:[]
+            },
+            collectionOpen : false,
+        }
+    },
+    created() {
+        this.getList();
+    },
+
+    methods: {
+        submitForm() {
+            this.$refs["form"].validate(valid => {
+                if (valid) {
+                    // fill(this.form).then(res => {
+                    //     this.msgSuccess("填写成功");
+                    //     this.open = false;
+                    //     this.getList();
+                    // })
+                }
+            });
+        },
+        handleAdvice(row){
+            this.form={};
+            this.open = true;
+            this.form.id = row.id;
+        },
+        cancelAdvice(){
+            this.open = false;
+            this.reset();
+        },
+        submitForm(){
+            this.$refs["form"].validate(valid => {
+                if (valid) {
+                    doctorConfirm(this.form).then(res => {
+                        this.msgSuccess("填写成功");
+                        this.open = false;
+                        this.getList();
+                    })
+                }
+            });
+        },
+        handleDetial(row) {
+            detail(row.id).then(res => {
+                this.collectionForm = res.data;
+
+            })
+            this.collectionOpen = true;
+        },
+        // handleFill(row) {
+        //     detail(row.id).then(res => {
+        //         this.form = res.data
+        //     })
+        //     this.open = true;
+        // },
+        getList() {
+            this.loading = true;
+            list(this.queryParams).then(response => {
+                this.list = response.data.list;
+                this.total = response.data.total;
+                this.loading = false;
+            });
+        },
+
+        // 取消按钮
+        cancel() {
+            this.collectionOpen = false;
+            this.restCollection();
+        },
+        restCollection(){
+            this.collectionForm = {
+                answers:[]
+            };
+            this.resetForm("collectionForm");
+        },
+        // 表单重置
+        reset() {
+            this.form = {
+            };
+            this.resetForm("form");
+        },
+        /** 搜索按钮操作 */
+        handleQuery() {
+            this.queryParams.pageNum = 1;
+            this.getList();
+        },
+        /** 重置按钮操作 */
+        resetQuery() {
+            this.resetForm("queryForm");
+            this.handleQuery();
+        },
+        // 多选框选中数据
+        handleSelectionChange(selection) {
+            this.ids = selection.map(item => item.patientId)
+            this.single = selection.length !== 1
+            this.multiple = !selection.length
+        }
+    }
+
+};
+</script>

+ 13 - 0
src/views/components/drugReport/addDrugReport.vue

@@ -74,6 +74,19 @@
               remark:this.form.remark
             }
             addReport(data).then(response => {
+              /*const exMassage = {
+                conversationID: `si_D${this.item.doctorId}_U${this.item.userId}`,
+                ex: JSON.stringify({
+                  type: "drugReport",
+                  orderId: this.item.orderId,
+                  followId:this.followId,
+                  imType: 2
+                })
+              };
+
+              // 4. 调用 SDK 更新会话
+              await this.OpenIM.setConversation(exMassage);
+              console.log("会话更新请求已发送");*/
               this.msgSuccess("提交成功");
             });
           }

+ 1 - 2
src/views/components/drugReport/drugReportDetails.vue

@@ -49,7 +49,6 @@
       return {
         statusOptions: [],
         item:null,
-        
       }
     },
     created() {
@@ -90,4 +89,4 @@
     max-width: 500px;
     min-width: 100px;
   }
-</style>
+</style>

+ 140 - 140
src/views/components/msg/followMsgDetails.vue

@@ -3,33 +3,33 @@
     <div class="chat-records">
       <el-card v-for="(lt, index) in msg" :key="index" class="chat-record" :class="{ 'received': lt.msgType == '1', 'sent': lt.msgType == '2' }">
         <div   :class="{ 'right': lt.msgType == '2'}" style="margin-bottom: 20px;">
-        <div class="timestamp" style="margin-bottom: 10px;">
-          <span v-if="lt.msgType=='1'"> {{patientName}}</span>
-          <span v-if="lt.msgType=='2'"> {{doctorName}}</span>
-        {{ lt.createTime }}
-        </div>
-        <div class="message">
-          <div v-if="lt.msgContentType == '1'" :class="{ 'right': lt.msgType == '2'}">{{lt.content}}</div>
-          <div v-if="lt.msgContentType == '2'" :class="{ 'right': lt.msgType == '2'}">
-            <audio ref="audioPlayer" controls>
-              <source :src="lt.content" type="audio/mp3" />
-            </audio>
-          </div>
-          <div v-if="lt.msgContentType == '4'" :class="{ 'right': lt.msgType == '2'}">
-            <video  :src="lt.content" controls style="max-width: 400px; max-height: 400px;"></video>
+          <div class="timestamp" style="margin-bottom: 10px;">
+            <span v-if="lt.msgType=='1'"> {{patientName}}</span>
+            <span v-if="lt.msgType=='2'"> {{doctorName}}</span>
+            {{ lt.createTime }}
           </div>
-          <span v-if="lt.msgContentType == '5'" :class="{ 'right': lt.msgType == '2'}"><el-link type="primary" :underline="false" @click="getPrescribe(JSON.parse(lt.content).prescribeId)">处方单</el-link></span>
-          <span v-if="lt.msgContentType == '6'" :class="{ 'right': lt.msgType == '2'}"><el-link type="primary" :underline="false" @click="report(lt.content)">问诊报告</el-link></span>
-          <span v-if="lt.msgContentType == '7'" :class="{ 'right': lt.msgType == '2'}"><el-link type="primary" :underline="false" @click="follow(lt.content)">随访</el-link></span>
-          <span v-if="lt.msgContentType == '8'" :class="{ 'right': lt.msgType == '2'}"><el-link type="primary" :underline="false" @click="drugReport(lt.content)">用药报告</el-link></span>
-          <span v-else-if="lt.msgContentType == '3'" :class="{ 'right': lt.msgType == '2'}">
+          <div class="message">
+            <div v-if="lt.msgContentType == '1'" :class="{ 'right': lt.msgType == '2'}">{{lt.content}}</div>
+            <div v-if="lt.msgContentType == '2'" :class="{ 'right': lt.msgType == '2'}">
+              <audio ref="audioPlayer" controls>
+                <source :src="lt.content" type="audio/mp3" />
+              </audio>
+            </div>
+            <div v-if="lt.msgContentType == '4'" :class="{ 'right': lt.msgType == '2'}">
+              <video  :src="lt.content" controls style="max-width: 400px; max-height: 400px;"></video>
+            </div>
+            <span v-if="lt.msgContentType == '5'" :class="{ 'right': lt.msgType == '2'}"><el-link type="primary" :underline="false" @click="getPrescribe(JSON.parse(lt.content).prescribeId)">处方单</el-link></span>
+            <span v-if="lt.msgContentType == '6'" :class="{ 'right': lt.msgType == '2'}"><el-link type="primary" :underline="false" @click="report(lt.content)">问诊报告</el-link></span>
+            <span v-if="lt.msgContentType == '7'" :class="{ 'right': lt.msgType == '2'}"><el-link type="primary" :underline="false" @click="follow(lt.content)">随访</el-link></span>
+            <span v-if="lt.msgContentType == '8'" :class="{ 'right': lt.msgType == '2'}"><el-link type="primary" :underline="false" @click="drugReport(lt.content)">用药报告</el-link></span>
+            <span v-else-if="lt.msgContentType == '3'" :class="{ 'right': lt.msgType == '2'}">
             <el-image
               style="width: 100px"
               :src="lt.content"
               :preview-src-list="[lt.content]">
             </el-image>
           </span>
-        </div>
+          </div>
         </div>
       </el-card>
     </div>
@@ -45,34 +45,34 @@
       :visible.sync="followDialogVisible"
       width="80%"
       :before-close="handleCloseSF" append-to-body >
-              <div  style="margin-left: 40px;" v-if="followList!=null">
-               <div    :class="'questionnaire_'+index"   v-for="(ite, index) in followList"   :key="index" >
-                    <div class="flex-1">
-                      <span :class="ite.require ? 'require' : 'noRequire'">{{index+1}}.{{ite.question}}</span>
-                    <div>
-                        <div v-if="'1' == ite.type">
-                           <el-row v-for="(answerItem,i) in ite.options"  :key="i">
-                              <el-col :span="23" :offset="1"> <el-radio disabled v-model="ite.answers" :label="ite.options[i]"></el-radio></el-col>
-                           </el-row>
-                        </div>
-                        <div v-if="'2' == ite.type">
-                            <el-row v-for="(answerItem,i) in ite.options"  :key="i">
-                               <el-col :span="23" :offset="1">
-                                <el-checkbox-group v-model="ite.answers">
-                                   <el-checkbox :label="ite.options[i]" disabled></el-checkbox>
-                                </el-checkbox-group>
-                              </el-col>
-                            </el-row>
-                        </div>
-                        <div v-if="'3' == ite.type">
-                            <el-row>
-                              <el-col :span="23" :offset="1"><el-input v-model="ite.answers" :disabled="true" type="textarea"></el-input></el-col>
-                            </el-row>
-                        </div>
-                      </div>
-                    </div>
-                </div>
+      <div  style="margin-left: 40px;" v-if="followList!=null">
+        <div    :class="'questionnaire_'+index"   v-for="(ite, index) in followList"   :key="index" >
+          <div class="flex-1">
+            <span :class="ite.require ? 'require' : 'noRequire'">{{index+1}}.{{ite.question}}</span>
+            <div>
+              <div v-if="'1' == ite.type">
+                <el-row v-for="(answerItem,i) in ite.options"  :key="i">
+                  <el-col :span="23" :offset="1"> <el-radio disabled v-model="ite.answers" :label="ite.options[i]"></el-radio></el-col>
+                </el-row>
               </div>
+              <div v-if="'2' == ite.type">
+                <el-row v-for="(answerItem,i) in ite.options"  :key="i">
+                  <el-col :span="23" :offset="1">
+                    <el-checkbox-group v-model="ite.answers">
+                      <el-checkbox :label="ite.options[i]" disabled></el-checkbox>
+                    </el-checkbox-group>
+                  </el-col>
+                </el-row>
+              </div>
+              <div v-if="'3' == ite.type">
+                <el-row>
+                  <el-col :span="23" :offset="1"><el-input v-model="ite.answers" :disabled="true" type="textarea"></el-input></el-col>
+                </el-row>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
 
       <span slot="footer" class="dialog-footer">
         <el-button @click="followDialogVisible = false">取 消</el-button>
@@ -176,7 +176,7 @@
         患者信息
       </div>
       <el-descriptions :column="3" border  >
-       <el-descriptions-item label="患者姓名" ><span v-if="reportItem.patientJson!=null">{{JSON.parse(reportItem.patientJson).patientName}}</span></el-descriptions-item>
+        <el-descriptions-item label="患者姓名" ><span v-if="reportItem.patientJson!=null">{{JSON.parse(reportItem.patientJson).patientName}}</span></el-descriptions-item>
         <el-descriptions-item label="患者年龄" ><span v-if="reportItem.patientJson!=null">{{JSON.parse(reportItem.patientJson).age}}</span></el-descriptions-item>
         <el-descriptions-item label="患者性别" ><span v-if="reportItem.patientJson!=null">{{JSON.parse(reportItem.patientJson).sex==1?'男':'女'}}</span></el-descriptions-item>
         <el-descriptions-item label="身份证号" ><span v-if="reportItem.patientJson!=null">{{JSON.parse(reportItem.patientJson).idCard}}</span></el-descriptions-item>
@@ -187,38 +187,38 @@
         <el-descriptions-item label="用药情况" ><span v-if="reportItem.patientJson!=null">{{JSON.parse(reportItem.patientJson).medication}}</span></el-descriptions-item>
         <el-descriptions-item label="主诉" span="3"><span v-if="reportItem.patientJson!=null">{{JSON.parse(reportItem.patientJson).title}}</span></el-descriptions-item>
         <el-descriptions-item label="面部照片" span="3" v-if="reportItem.patientJson!=null">
-         <div v-if="(JSON.parse(reportItem.patientJson)).faceImages">
-          <el-image v-if="JSON.parse(reportItem.patientJson).faceImages!=null&&JSON.parse(reportItem.patientJson).faceImages!=''" v-for="img in (JSON.parse(reportItem.patientJson).faceImages).split(',')" :key="img.id"
-              style="width: 100px"
-              :src="img"
-              :preview-src-list="[img]">
-          </el-image>
-         </div>
+          <div v-if="(JSON.parse(reportItem.patientJson)).faceImages">
+            <el-image v-if="JSON.parse(reportItem.patientJson).faceImages!=null&&JSON.parse(reportItem.patientJson).faceImages!=''" v-for="img in (JSON.parse(reportItem.patientJson).faceImages).split(',')" :key="img.id"
+                      style="width: 100px"
+                      :src="img"
+                      :preview-src-list="[img]">
+            </el-image>
+          </div>
         </el-descriptions-item>
         <el-descriptions-item label="舌苔照片" span="3" v-if="reportItem.patientJson!=null">
           <div v-if="(JSON.parse(reportItem.patientJson)).tongueImages">
             <el-image v-if="JSON.parse(reportItem.patientJson).tongueImages!=null&&JSON.parse(reportItem.patientJson).tongueImages!=''"  v-for="img in (JSON.parse(reportItem.patientJson).tongueImages).split(',')" :key="img.id"
-                style="width: 100px"
-                :src="img"
-                :preview-src-list="[img]">
+                      style="width: 100px"
+                      :src="img"
+                      :preview-src-list="[img]">
             </el-image>
-           </div>
+          </div>
         </el-descriptions-item>
         <el-descriptions-item label="检测报告或患病证书" span="3" v-if="reportItem.patientJson!=null">
           <div v-if="(JSON.parse(reportItem.patientJson)).tongueImages">
             <el-image v-if="JSON.parse(reportItem.patientJson).reportImages!=null && JSON.parse(reportItem.patientJson).reportImages!=''"  v-for="img in (JSON.parse(reportItem.patientJson).reportImages).split(',')" :key="img.id"
-                style="width: 100px"
-                :src="img"
-                :preview-src-list="[img]">
+                      style="width: 100px"
+                      :src="img"
+                      :preview-src-list="[img]">
             </el-image>
           </div>
         </el-descriptions-item>
       </el-descriptions>
       <div  v-if="reportItem.conditioningPlanJson!=null">
-            <div class="desct"> 调理方案</div>
-            <el-descriptions title="" :column="1" border >
-                <el-descriptions-item :label="form.name" v-for=" form in JSON.parse(reportItem.conditioningPlanJson)" ><div style="white-space: pre-wrap;">{{form.value}}</div></el-descriptions-item>
-            </el-descriptions>
+        <div class="desct"> 调理方案</div>
+        <el-descriptions title="" :column="1" border >
+          <el-descriptions-item :label="form.name" v-for=" form in JSON.parse(reportItem.conditioningPlanJson)" ><div style="white-space: pre-wrap;">{{form.value}}</div></el-descriptions-item>
+        </el-descriptions>
       </div>
       <span slot="footer" class="dialog-footer">
         <el-button @click="reportDialogVisible = false">取 消</el-button>
@@ -252,11 +252,11 @@
 </template>
 
 <script>
-import { getDrugReportById} from "@/api/drugReport";
-import { getInquiryOrderReport} from "@/api/inquiryOrder";
-import {getInquiryOrderMsgList} from "@/api/inquiryOrder";
-import {getPrescribe} from "@/api/prescribe";
-import {getFollowById} from "@/api/follow";
+  import { getDrugReportById} from "@/api/drugReport";
+  import { getInquiryOrderReport} from "@/api/inquiryOrder";
+  import {getInquiryOrderMsgList} from "@/api/inquiryOrder";
+  import {getPrescribe} from "@/api/prescribe";
+  import {getFollowById} from "@/api/follow";
   export default {
     name: "FollowMsgList",
     data() {
@@ -290,12 +290,12 @@ import {getFollowById} from "@/api/follow";
         orOptions:[],
         form: {
 
-          }
+        }
       }
     },
     created() {
       this.getDicts("sys_company_or").then(response => {
-          this.orOptions = response.data;
+        this.orOptions = response.data;
       });
       this.getDicts("sys_inquiry_sub_type").then(response => {
         this.inquirySubTypeOptions = response.data;
@@ -309,14 +309,14 @@ import {getFollowById} from "@/api/follow";
     },
     methods: {
       getPrescribe(orderId){
-      if(orderId==null){
-         return this.$message("未读取到处方单")
-       }
+        if(orderId==null){
+          return this.$message("未读取到处方单")
+        }
         this.prescribeDialog.open=true;
         getPrescribe(orderId).then(response => {
-            this.prescribeItem = response.data.prescribe;
-            this.usageJson=JSON.parse(this.prescribeItem.usageJson)
-            this.prod = response.data.drugs;
+          this.prescribeItem = response.data.prescribe;
+          this.usageJson=JSON.parse(this.prescribeItem.usageJson)
+          this.prod = response.data.drugs;
         });
       },
       getDetails(userId,followDoctorId,doctorName,patientName) {
@@ -329,8 +329,8 @@ import {getFollowById} from "@/api/follow";
       msgList(){
         this.msg=[];
         getInquiryOrderMsgList(this.msgForm).then(response => {
-        this.msg= response.data.list;
-        this.total = response.data.total;
+          this.msg= response.data.list;
+          this.total = response.data.total;
         });
       },
       handleClose(){
@@ -350,12 +350,12 @@ import {getFollowById} from "@/api/follow";
           return this.$message("未读取到随访")
         }
         getFollowById(row).then(response => {
-              if(response.follow.formJson!=null&&response.follow.formJson!=''&&response.follow.writeStatus==1){
-                        this.followList=JSON.parse(response.follow.formJson );
-                        this.followDialogVisible=true;
-              }else{
-               return this.$message("随访单未填写")
-              }
+          if(response.follow.formJson!=null&&response.follow.formJson!=''&&response.follow.writeStatus==1){
+            this.followList=JSON.parse(response.follow.formJson );
+            this.followDialogVisible=true;
+          }else{
+            return this.$message("随访单未填写")
+          }
         });
 
       },
@@ -367,11 +367,11 @@ import {getFollowById} from "@/api/follow";
           return this.$message("未读取到报告")
         }
         getInquiryOrderReport(row).then(response => {
-              if(response.data!=null){
-                  this.reportItem = response.data;
-              }
+          if(response.data!=null){
+            this.reportItem = response.data;
+          }
         });
-         this.reportDialogVisible=true;
+        this.reportDialogVisible=true;
       },
       drugReport(row){
         if(row==null){
@@ -379,9 +379,9 @@ import {getFollowById} from "@/api/follow";
         }
         var o= {reportId:row}
         getDrugReportById(o).then(response => {
-             this.drugReportItem = response.data;
+          this.drugReportItem = response.data;
         });
-         this.drugReportDialogVisible=true;
+        this.drugReportDialogVisible=true;
       },
     }
   }
@@ -389,12 +389,12 @@ import {getFollowById} from "@/api/follow";
 <style>
 
   .contentx{
-      height: 100%;
-      background-color: #fff;
-      padding: 0px 20px 20px;
+    height: 100%;
+    background-color: #fff;
+    padding: 0px 20px 20px;
 
 
-      margin: 20px;
+    margin: 20px;
   }
   .el-descriptions-item__label.is-bordered-label{
     font-weight: normal;
@@ -404,58 +404,58 @@ import {getFollowById} from "@/api/follow";
     min-width: 100px;
   }
   .desct{
-      padding-top: 20px;
-      padding-bottom: 20px;
-      color: #524b4a;
-      font-weight: bold;
-    }
+    padding-top: 20px;
+    padding-bottom: 20px;
+    color: #524b4a;
+    font-weight: bold;
+  }
 </style>
 <style scoped>
-.chat-records {
+  .chat-records {
 
-  overflow-y: auto;
-}
-.timestamp {
-  font-size: 12px;
-  color: #A9A9A9;
-}
-.chat-record {
-  margin: 10px;
-  flex-direction: column;
-  align-items: flex-start;
-}
-.sent {
-  background-color: #fbfdff;
-  color: #000839;
-}
-.right{
-  float: right;
-}
-.received {
-  background-color: #fbfdff;
-  color: #000000;
-}
+    overflow-y: auto;
+  }
+  .timestamp {
+    font-size: 12px;
+    color: #A9A9A9;
+  }
+  .chat-record {
+    margin: 10px;
+    flex-direction: column;
+    align-items: flex-start;
+  }
+  .sent {
+    background-color: #fbfdff;
+    color: #000839;
+  }
+  .right{
+    float: right;
+  }
+  .received {
+    background-color: #fbfdff;
+    color: #000000;
+  }
 
-.el-descriptions-item__content {
+  .el-descriptions-item__content {
     max-width: 150px;
     min-width: 100px;
-}
+  }
 </style>
 <style scoped>
-.order-content{
-  margin: 10px;
+  .order-content{
+    margin: 10px;
 
-}
+  }
 
-.operate-container {
-  background: #F2F6FC;
-  height: 60px;
-  margin: -20px -20px 0;
-  line-height: 60px;
-}
+  .operate-container {
+    background: #F2F6FC;
+    height: 60px;
+    margin: -20px -20px 0;
+    line-height: 60px;
+  }
 
-.operate-button-container {
-  float: right;
-  margin-right: 20px
-}
+  .operate-button-container {
+    float: right;
+    margin-right: 20px
+  }
 </style>

+ 141 - 141
src/views/components/msg/msgDetails.vue

@@ -3,33 +3,33 @@
     <div class="chat-records">
       <el-card v-for="(lt, index) in msg" :key="index" class="chat-record" :class="{ 'received': lt.msgType == '1', 'sent': lt.msgType == '2' }">
         <div   :class="{ 'right': lt.msgType == '2'}" style="margin-bottom: 20px;">
-        <div class="timestamp" style="margin-bottom: 10px;">
-          <span v-if="lt.msgType=='1'"> {{patientName}}</span>
-          <span v-if="lt.msgType=='2'"> {{doctorName}}</span>
-        {{ lt.createTime }}
-        </div>
-        <div class="message">
-          <div v-if="lt.msgContentType == '1'" :class="{ 'right': lt.msgType == '2'}">{{lt.content}}</div>
-          <div v-if="lt.msgContentType == '2'" :class="{ 'right': lt.msgType == '2'}">
-            <audio ref="audioPlayer" controls>
-              <source :src="lt.content" type="audio/mp3" />
-            </audio>
-          </div>
-          <div v-if="lt.msgContentType == '4'" :class="{ 'right': lt.msgType == '2'}">
-            <video  :src="lt.content" controls style="max-width: 400px; max-height: 400px;"></video>
+          <div class="timestamp" style="margin-bottom: 10px;">
+            <span v-if="lt.msgType=='1'"> {{patientName}}</span>
+            <span v-if="lt.msgType=='2'"> {{doctorName}}</span>
+            {{ lt.createTime }}
           </div>
-          <span v-if="lt.msgContentType == '5'" :class="{ 'right': lt.msgType == '2'}"><el-link type="primary" :underline="false" @click="getPrescribe(JSON.parse(lt.content).prescribeId)">处方单</el-link></span>
-          <span v-if="lt.msgContentType == '6'" :class="{ 'right': lt.msgType == '2'}"><el-link type="primary" :underline="false" @click="report(lt.content)">问诊报告</el-link></span>
-          <span v-if="lt.msgContentType == '7'" :class="{ 'right': lt.msgType == '2'}"><el-link type="primary" :underline="false" @click="follow(lt.content)">随访</el-link></span>
-          <span v-if="lt.msgContentType == '8'" :class="{ 'right': lt.msgType == '2'}"><el-link type="primary" :underline="false" @click="drugReport(lt.content)">用药报告</el-link></span>
-          <span v-else-if="lt.msgContentType == '3'" :class="{ 'right': lt.msgType == '2'}">
+          <div class="message">
+            <div v-if="lt.msgContentType == '1'" :class="{ 'right': lt.msgType == '2'}">{{lt.content}}</div>
+            <div v-if="lt.msgContentType == '2'" :class="{ 'right': lt.msgType == '2'}">
+              <audio ref="audioPlayer" controls>
+                <source :src="lt.content" type="audio/mp3" />
+              </audio>
+            </div>
+            <div v-if="lt.msgContentType == '4'" :class="{ 'right': lt.msgType == '2'}">
+              <video  :src="lt.content" controls style="max-width: 400px; max-height: 400px;"></video>
+            </div>
+            <span v-if="lt.msgContentType == '5'" :class="{ 'right': lt.msgType == '2'}"><el-link type="primary" :underline="false" @click="getPrescribe(JSON.parse(lt.content).prescribeId)">处方单</el-link></span>
+            <span v-if="lt.msgContentType == '6'" :class="{ 'right': lt.msgType == '2'}"><el-link type="primary" :underline="false" @click="report(lt.content)">问诊报告</el-link></span>
+            <span v-if="lt.msgContentType == '7'" :class="{ 'right': lt.msgType == '2'}"><el-link type="primary" :underline="false" @click="follow(lt.content)">随访</el-link></span>
+            <span v-if="lt.msgContentType == '8'" :class="{ 'right': lt.msgType == '2'}"><el-link type="primary" :underline="false" @click="drugReport(lt.content)">用药报告</el-link></span>
+            <span v-else-if="lt.msgContentType == '3'" :class="{ 'right': lt.msgType == '2'}">
             <el-image
               style="width: 100px"
               :src="lt.content"
               :preview-src-list="[lt.content]">
             </el-image>
           </span>
-        </div>
+          </div>
         </div>
       </el-card>
     </div>
@@ -45,34 +45,34 @@
       :visible.sync="followDialogVisible"
       width="80%"
       :before-close="handleCloseSF" append-to-body >
-              <div  style="margin-left: 40px;" v-if="followList!=null">
-               <div    :class="'questionnaire_'+index"   v-for="(ite, index) in followList"   :key="index" >
-                    <div class="flex-1">
-                      <span :class="ite.require ? 'require' : 'noRequire'">{{index+1}}.{{ite.question}}</span>
-                    <div>
-                        <div v-if="'1' == ite.type">
-                           <el-row v-for="(answerItem,i) in ite.options"  :key="i">
-                              <el-col :span="23" :offset="1"> <el-radio disabled v-model="ite.answers" :label="ite.options[i]"></el-radio></el-col>
-                           </el-row>
-                        </div>
-                        <div v-if="'2' == ite.type">
-                            <el-row v-for="(answerItem,i) in ite.options"  :key="i">
-                               <el-col :span="23" :offset="1">
-                                <el-checkbox-group v-model="ite.answers">
-                                   <el-checkbox :label="ite.options[i]" disabled></el-checkbox>
-                                </el-checkbox-group>
-                              </el-col>
-                            </el-row>
-                        </div>
-                        <div v-if="'3' == ite.type">
-                            <el-row>
-                              <el-col :span="23" :offset="1"><el-input v-model="ite.answers" :disabled="true" type="textarea"></el-input></el-col>
-                            </el-row>
-                        </div>
-                      </div>
-                    </div>
-                </div>
+      <div  style="margin-left: 40px;" v-if="followList!=null">
+        <div    :class="'questionnaire_'+index"   v-for="(ite, index) in followList"   :key="index" >
+          <div class="flex-1">
+            <span :class="ite.require ? 'require' : 'noRequire'">{{index+1}}.{{ite.question}}</span>
+            <div>
+              <div v-if="'1' == ite.type">
+                <el-row v-for="(answerItem,i) in ite.options"  :key="i">
+                  <el-col :span="23" :offset="1"> <el-radio disabled v-model="ite.answers" :label="ite.options[i]"></el-radio></el-col>
+                </el-row>
               </div>
+              <div v-if="'2' == ite.type">
+                <el-row v-for="(answerItem,i) in ite.options"  :key="i">
+                  <el-col :span="23" :offset="1">
+                    <el-checkbox-group v-model="ite.answers">
+                      <el-checkbox :label="ite.options[i]" disabled></el-checkbox>
+                    </el-checkbox-group>
+                  </el-col>
+                </el-row>
+              </div>
+              <div v-if="'3' == ite.type">
+                <el-row>
+                  <el-col :span="23" :offset="1"><el-input v-model="ite.answers" :disabled="true" type="textarea"></el-input></el-col>
+                </el-row>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
 
       <span slot="footer" class="dialog-footer">
         <el-button @click="followDialogVisible = false">取 消</el-button>
@@ -176,7 +176,7 @@
         患者信息
       </div>
       <el-descriptions :column="3" border  >
-       <el-descriptions-item label="患者姓名" ><span v-if="reportItem.patientJson!=null">{{JSON.parse(reportItem.patientJson).patientName}}</span></el-descriptions-item>
+        <el-descriptions-item label="患者姓名" ><span v-if="reportItem.patientJson!=null">{{JSON.parse(reportItem.patientJson).patientName}}</span></el-descriptions-item>
         <el-descriptions-item label="患者年龄" ><span v-if="reportItem.patientJson!=null">{{JSON.parse(reportItem.patientJson).age}}</span></el-descriptions-item>
         <el-descriptions-item label="患者性别" ><span v-if="reportItem.patientJson!=null">{{JSON.parse(reportItem.patientJson).sex==1?'男':'女'}}</span></el-descriptions-item>
         <el-descriptions-item label="身份证号" ><span v-if="reportItem.patientJson!=null">{{JSON.parse(reportItem.patientJson).idCard}}</span></el-descriptions-item>
@@ -187,38 +187,38 @@
         <el-descriptions-item label="用药情况" ><span v-if="reportItem.patientJson!=null">{{JSON.parse(reportItem.patientJson).medication}}</span></el-descriptions-item>
         <el-descriptions-item label="主诉" span="3"><span v-if="reportItem.patientJson!=null">{{JSON.parse(reportItem.patientJson).title}}</span></el-descriptions-item>
         <el-descriptions-item label="面部照片" span="3" v-if="reportItem.patientJson!=null">
-         <div v-if="(JSON.parse(reportItem.patientJson)).faceImages">
-          <el-image v-if="JSON.parse(reportItem.patientJson).faceImages!=null&&JSON.parse(reportItem.patientJson).faceImages!=''" v-for="img in (JSON.parse(reportItem.patientJson).faceImages).split(',')" :key="img.id"
-              style="width: 100px"
-              :src="img"
-              :preview-src-list="[img]">
-          </el-image>
-         </div>
+          <div v-if="(JSON.parse(reportItem.patientJson)).faceImages">
+            <el-image v-if="JSON.parse(reportItem.patientJson).faceImages!=null&&JSON.parse(reportItem.patientJson).faceImages!=''" v-for="img in (JSON.parse(reportItem.patientJson).faceImages).split(',')" :key="img.id"
+                      style="width: 100px"
+                      :src="img"
+                      :preview-src-list="[img]">
+            </el-image>
+          </div>
         </el-descriptions-item>
         <el-descriptions-item label="舌苔照片" span="3" v-if="reportItem.patientJson!=null">
           <div v-if="(JSON.parse(reportItem.patientJson)).tongueImages">
             <el-image v-if="JSON.parse(reportItem.patientJson).tongueImages!=null&&JSON.parse(reportItem.patientJson).tongueImages!=''"  v-for="img in (JSON.parse(reportItem.patientJson).tongueImages).split(',')" :key="img.id"
-                style="width: 100px"
-                :src="img"
-                :preview-src-list="[img]">
+                      style="width: 100px"
+                      :src="img"
+                      :preview-src-list="[img]">
             </el-image>
-           </div>
+          </div>
         </el-descriptions-item>
         <el-descriptions-item label="检测报告或患病证书" span="3" v-if="reportItem.patientJson!=null">
           <div v-if="(JSON.parse(reportItem.patientJson)).tongueImages">
             <el-image v-if="JSON.parse(reportItem.patientJson).reportImages!=null && JSON.parse(reportItem.patientJson).reportImages!=''"  v-for="img in (JSON.parse(reportItem.patientJson).reportImages).split(',')" :key="img.id"
-                style="width: 100px"
-                :src="img"
-                :preview-src-list="[img]">
+                      style="width: 100px"
+                      :src="img"
+                      :preview-src-list="[img]">
             </el-image>
           </div>
         </el-descriptions-item>
       </el-descriptions>
       <div  v-if="reportItem.conditioningPlanJson!=null">
-            <div class="desct"> 调理方案</div>
-            <el-descriptions title="" :column="1" border >
-                <el-descriptions-item :label="form.name" v-for=" form in JSON.parse(reportItem.conditioningPlanJson)" ><div style="white-space: pre-wrap;">{{form.value}}</div></el-descriptions-item>
-            </el-descriptions>
+        <div class="desct"> 调理方案</div>
+        <el-descriptions title="" :column="1" border >
+          <el-descriptions-item :label="form.name" v-for=" form in JSON.parse(reportItem.conditioningPlanJson)" ><div style="white-space: pre-wrap;">{{form.value}}</div></el-descriptions-item>
+        </el-descriptions>
       </div>
       <span slot="footer" class="dialog-footer">
         <el-button @click="reportDialogVisible = false">取 消</el-button>
@@ -252,11 +252,11 @@
 </template>
 
 <script>
-import {getInquiryOrderMsgList} from "@/api/inquiryOrder";
-import { getDrugReportById} from "@/api/drugReport";
-import { getInquiryOrderReport} from "@/api/inquiryOrder";
-import {getFollowById} from "@/api/follow";
-import {getPrescribe} from "@/api/prescribe";
+  import {getInquiryOrderMsgList} from "@/api/inquiryOrder";
+  import { getDrugReportById} from "@/api/drugReport";
+  import { getInquiryOrderReport} from "@/api/inquiryOrder";
+  import {getFollowById} from "@/api/follow";
+  import {getPrescribe} from "@/api/prescribe";
   export default {
     components:{prescribeDetails},
     name: "MsgList",
@@ -290,12 +290,12 @@ import {getPrescribe} from "@/api/prescribe";
         orOptions:[],
         form: {
 
-          }
+        }
       }
     },
     created() {
       this.getDicts("sys_company_or").then(response => {
-          this.orOptions = response.data;
+        this.orOptions = response.data;
       });
       this.getDicts("sys_inquiry_sub_type").then(response => {
         this.inquirySubTypeOptions = response.data;
@@ -312,14 +312,14 @@ import {getPrescribe} from "@/api/prescribe";
         if(orderId==null){
           return this.$message("未读取到随访")
         }
-         this.prescribeDialog.open=true;
-         getPrescribe(orderId).then(response => {
-             this.prescribeItem = response.data.prescribe;
-             this.usageJson=JSON.parse(this.prescribeItem.usageJson)
-             this.prod = response.data.drugs;
-         });
+        this.prescribeDialog.open=true;
+        getPrescribe(orderId).then(response => {
+          this.prescribeItem = response.data.prescribe;
+          this.usageJson=JSON.parse(this.prescribeItem.usageJson)
+          this.prod = response.data.drugs;
+        });
 
-       },
+      },
       getDetails(orderId,doctorName,patientName) {
         this.msgForm.orderId=orderId;
         this.doctorName = doctorName;
@@ -329,8 +329,8 @@ import {getPrescribe} from "@/api/prescribe";
       msgList(){
         this.msg=[];
         getInquiryOrderMsgList(this.msgForm).then(response => {
-        this.msg= response.data.list;
-        this.total = response.data.total;
+          this.msg= response.data.list;
+          this.total = response.data.total;
         });
       },
       handleClose(){
@@ -350,12 +350,12 @@ import {getPrescribe} from "@/api/prescribe";
           return this.$message("未读取到随访")
         }
         getFollowById(row).then(response => {
-              if(response.data.formJson!=null&&response.data.formJson!=''&&response.data.writeStatus==1){
-                        this.followList=JSON.parse(response.data.formJson );
-                        this.followDialogVisible=true;
-              }else{
-               return this.$message("随访单未填写")
-              }
+          if(response.data.formJson!=null&&response.data.formJson!=''&&response.data.writeStatus==1){
+            this.followList=JSON.parse(response.data.formJson );
+            this.followDialogVisible=true;
+          }else{
+            return this.$message("随访单未填写")
+          }
         });
 
       },
@@ -367,20 +367,20 @@ import {getPrescribe} from "@/api/prescribe";
           row=this.msgForm.orderId
         }
         getInquiryOrderReport(row).then(response => {
-              if(response.data!=null){
-                  this.reportItem = response.data;
-              }
+          if(response.data!=null){
+            this.reportItem = response.data;
+          }
         });
-         this.reportDialogVisible=true;
+        this.reportDialogVisible=true;
       },
       drugReport(row){
         if(row==null){
           return this.$message("未读取到报告")
         }
         getDrugReportById(row).then(response => {
-             this.drugReportItem = response.data;
+          this.drugReportItem = response.data;
         });
-         this.drugReportDialogVisible=true;
+        this.drugReportDialogVisible=true;
       },
     }
   }
@@ -388,12 +388,12 @@ import {getPrescribe} from "@/api/prescribe";
 <style>
 
   .contentx{
-      height: 100%;
-      background-color: #fff;
-      padding: 0px 20px 20px;
+    height: 100%;
+    background-color: #fff;
+    padding: 0px 20px 20px;
 
 
-      margin: 20px;
+    margin: 20px;
   }
   .el-descriptions-item__label.is-bordered-label{
     font-weight: normal;
@@ -403,58 +403,58 @@ import {getPrescribe} from "@/api/prescribe";
     min-width: 100px;
   }
   .desct{
-      padding-top: 20px;
-      padding-bottom: 20px;
-      color: #524b4a;
-      font-weight: bold;
-    }
+    padding-top: 20px;
+    padding-bottom: 20px;
+    color: #524b4a;
+    font-weight: bold;
+  }
 </style>
 <style scoped>
-.chat-records {
+  .chat-records {
 
-  overflow-y: auto;
-}
-.timestamp {
-  font-size: 12px;
-  color: #A9A9A9;
-}
-.chat-record {
-  margin: 10px;
-  flex-direction: column;
-  align-items: flex-start;
-}
-.sent {
-  background-color: #fbfdff;
-  color: #000839;
-}
-.right{
-  float: right;
-}
-.received {
-  background-color: #fbfdff;
-  color: #000000;
-}
+    overflow-y: auto;
+  }
+  .timestamp {
+    font-size: 12px;
+    color: #A9A9A9;
+  }
+  .chat-record {
+    margin: 10px;
+    flex-direction: column;
+    align-items: flex-start;
+  }
+  .sent {
+    background-color: #fbfdff;
+    color: #000839;
+  }
+  .right{
+    float: right;
+  }
+  .received {
+    background-color: #fbfdff;
+    color: #000000;
+  }
 
-.el-descriptions-item__content {
+  .el-descriptions-item__content {
     max-width: 150px;
     min-width: 100px;
-}
+  }
 </style>
 <style scoped>
-.order-content{
-  margin: 10px;
+  .order-content{
+    margin: 10px;
 
-}
+  }
 
-.operate-container {
-  background: #F2F6FC;
-  height: 60px;
-  margin: -20px -20px 0;
-  line-height: 60px;
-}
+  .operate-container {
+    background: #F2F6FC;
+    height: 60px;
+    margin: -20px -20px 0;
+    line-height: 60px;
+  }
 
-.operate-button-container {
-  float: right;
-  margin-right: 20px
-}
+  .operate-button-container {
+    float: right;
+    margin-right: 20px
+  }
 </style>

+ 37 - 9
src/views/components/order/inquiryOrderDetails.vue

@@ -322,18 +322,46 @@ import way from '@/utils/way.js';
        });
 
       },
-      handleFinishOrder(){
-        var data={orderId:this.item.orderId}
-        this.$confirm('确认结束订单吗?', "提示", {
+      async handleFinishOrder() {
+        try {
+          // 1. 确认弹窗
+          await this.$confirm('确认结束订单吗?', "提示", {
             confirmButtonText: "确定",
             cancelButtonText: "取消",
             type: "warning"
-          }).then(() => {
-            return finishOrder(data);
-          }).then(() => {
-            this.getDetails(this.item.orderId);
-            this.msgSuccess("操作成功");
-          }).catch(() => {});
+          });
+
+          // 2. 调用结束订单接口
+          const data = { orderId: this.item.orderId };
+          await finishOrder(data);
+
+          // 3. 更新 OpenIM 会话的扩展字段
+          const exMassage = {
+            conversationID: `si_D${this.item.doctorId}_U${this.item.userId}`,
+            ex: JSON.stringify({
+              type: "finishInquiry",
+              orderId: this.item.orderId,
+              orderType: 2,
+              imType: 0
+            })
+          };
+
+          // 4. 调用 SDK 更新会话
+          //await this.OpenIM.setConversation(exMassage);
+          console.log("会话更新请求已发送");
+
+          // 5. 刷新详情并提示成功
+          await this.getDetails(this.item.orderId);
+          this.msgSuccess("操作成功");
+
+        } catch (error) {
+          console.error("结束订单失败:", error);
+
+          // 区分用户取消和真实错误
+          if (error !== "cancel") {
+            this.msgError(error.message || "结束订单失败");
+          }
+        }
       },
       handledetails(row){
         var id = this.report.orderId

+ 50 - 3
src/views/components/order/inquiryOrderList.vue

@@ -78,6 +78,13 @@
                 icon="el-icon-share"
             >详情
           </el-button>
+          <el-button
+            size="mini"
+            type="text"
+            @click="handleWenZhendetails(scope.row)"
+            icon="el-icon-share"
+          >问诊报告
+          </el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -93,21 +100,32 @@
     <el-drawer :with-header="false" size="75%" :title="show.title" :visible.sync="show.open" append-to-body >
      <inquiryOrderDetails  ref="Details" />
    </el-drawer>
+    <el-drawer
+      :with-header="false"
+      :append-to-body="true"
+      size="75%"
+      :title="show.wenZhenTitle" :visible.sync="show.wenZhenOpen">
+      <inquiryOrderReportDetails  ref="Details" />
+    </el-drawer>
   </div>
 </template>
 
 <script>
 import { getOrderUserPhone,getCompanyList,getInquiryOrderList,receiveOrder,refuseOrder } from "@/api/inquiryOrder";
 import inquiryOrderDetails from '@/views/components/order/inquiryOrderDetails.vue';
+import inquiryOrderReportDetails from '../../components/order/inquiryOrderReportDetails.vue';
+import {mapGetters, mapState} from "vuex";
 export default {
   name: "inquiryOrder",
-  components: { inquiryOrderDetails },
+  components: { inquiryOrderDetails,inquiryOrderReportDetails },
   data() {
     return {
 
       show:{
         title:"问诊详情",
+        wenZhenTitle:'问诊报告',
         open:false,
+        wenZhenOpen:false,
           },
       companyList:[],
       startTime:null,
@@ -182,7 +200,19 @@ export default {
       orOptions:[],
     };
   },
-
+  computed: {
+    ...mapGetters(['toAccount', 'currentConversationType']),
+    ...mapState({
+      orderId:state => state.conversation.orderId,
+      followId:state => state.conversation.followId,
+      orderType:state => state.conversation.orderType,
+      imType: state => state.conversation.imType,
+      memberList: state => state.group.currentMemberList,
+      userID: state => state.imuser.userID,
+      currentConversation :state=>state.conversation.currentConversation,
+      groupProfile: state => state.conversation.currentConversation.groupProfile
+    })
+  },
   created() {
       this.getDicts("sys_inquiry_type").then(response => {
           this.inquiryTypeOptions = response.data;
@@ -252,7 +282,18 @@ export default {
           this.msgSuccess("拒单成功");
         }).catch(() => {});
     },
-
+    handleWenZhendetails(row){
+      console.log(row)
+      var id = row.orderId
+      if(id!=null){
+        this.show.wenZhenOpen=true;
+        setTimeout(() => {
+          this.$refs.Details.getDetails(id);
+        }, 1);
+      }else{
+        this.$message('问诊订单没有报告');
+      }
+    },
     handledetails(row){
 
         this.show.open=true;
@@ -264,6 +305,12 @@ export default {
     getList() {
       this.loading = true;
       this.queryParams.isReceive = 1;
+      //this.queryParams.userId = this.$store.state.conversation.currentConversation.conversationID.split("-")[1];
+      this.queryParams.userId = this.$store.state.conversation.currentConversation.conversationID
+        .split("_")
+        .filter(id => id.includes("U"))
+        .map(id => id.replace("U", "")).toString();
+      console.log("用户id",this.queryParams.userId )
       getInquiryOrderList(this.queryParams).then(response => {
         this.inquiryOrderList = response.data.list;
         this.total = response.data.total;

+ 3 - 0
src/views/components/order/inquiryOrderReportDetails.vue

@@ -106,6 +106,9 @@ export default {
       this.conditioningPlanJson=null;
       this.item = null;
       getInquiryOrderReport(orderId).then(response => {
+        if (!response.data){
+          this.$message('问诊订单没有报告');
+        }
           this.item = response.data;
           this.patientJson=JSON.parse(this.item.patientJson);
           this.formJson=JSON.parse(this.item.formJson);

+ 214 - 0
src/views/diagnosis/index.vue

@@ -0,0 +1,214 @@
+<template>
+    <div class="app-container">
+        <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+            <el-form-item label="患者姓名" prop="patientName">
+                <el-input v-model="queryParams.patientName" placeholder="请输入患者姓名" clearable size="small"
+                    @keyup.enter.native="handleQuery" />
+            </el-form-item>
+            <el-form-item>
+                <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+                <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+            </el-form-item>
+        </el-form>
+        <el-tabs type="card" v-model="queryParams.type" @tab-click="handleClickX">
+            <el-tab-pane label="全部" name="0"></el-tab-pane>
+            <el-tab-pane label="我的" name="1"></el-tab-pane>
+        </el-tabs>
+        <el-table v-loading="loading" border :data="list" @selection-change="handleSelectionChange">
+            <el-table-column type="selection" width="55" align="center" />
+            <el-table-column label="患者姓名" align="center" prop="patientName" width="120px" />
+            <el-table-column label="性别" align="center" prop="gender" width="120px">
+                <template slot-scope="scope">
+                    <el-tag v-if="scope.row.gender == 0">未知</el-tag>
+                    <el-tag v-if="scope.row.gender == 1">男</el-tag>
+                    <el-tag v-if="scope.row.gender == 2">女</el-tag>
+                </template>
+            </el-table-column>
+            <el-table-column label="患者年龄" align="center" prop="age" width="120px" />
+            <el-table-column label="患者电话" align="center" prop="phone" width="120px" />
+            <el-table-column label="日期" align="center" prop="dateTime" width="120px" />
+            <el-table-column label="身体状况" align="center" prop="physicalCondition" :show-overflow-tooltip="true" />
+            <el-table-column label="患者是否答复" align="center" prop="userStatus" width="120px">
+                <template slot-scope="scope">
+                    <el-tag v-if="scope.row.userStatus == 0">未答复</el-tag>
+                    <el-tag type="success" v-if="scope.row.userStatus == 1">已答复</el-tag>
+                </template>
+            </el-table-column>
+            <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="150px">
+                <template slot-scope="scope">
+                    <el-button :disabled="scope.row.userStatus == 1" size="mini" type="text"
+                        @click="handleFill(scope.row)">填写初诊单</el-button>
+                </template>
+            </el-table-column>
+        </el-table>
+        <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum"
+            :limit.sync="queryParams.pageSize" @pagination="getList" />
+
+        <el-dialog title="填写初诊单" :visible.sync="open" width="1000px" append-to-body>
+            <el-form ref="form" :model="form" :rules="rules" label-width="110px">
+                <el-row>
+                    <el-col :span="12">
+                        <el-form-item label="患者姓名" prop="patientName">
+                            <el-input disabled v-model="form.patientName" placeholder="请输入患者姓名" />
+                        </el-form-item>
+                    </el-col>
+                    <el-col :span="12">
+                        <el-form-item label="年龄" prop="age">
+                            <el-input disabled v-model="form.age" label="年龄"></el-input>
+                        </el-form-item>
+                    </el-col>
+                </el-row>
+                <el-row>
+                    <el-col :span="12">
+                        <el-form-item label="患者电话" prop="phone">
+                            <el-input disabled v-model="form.phone" placeholder="请输入电话" />
+                        </el-form-item>
+                    </el-col>
+                    <el-col :span="12">
+                        <el-form-item label="性别" prop="gender">
+                            <el-select disabled v-model="form.gender">
+                                <el-option label="未知" :value="0"></el-option>
+                                <el-option label="男性" :value="1"></el-option>
+                                <el-option label="女性" :value="2"></el-option>
+                            </el-select>
+                        </el-form-item>
+                    </el-col>
+                </el-row>
+                <el-form-item label="身体状况" prop="physicalCondition">
+                    <el-input v-model="form.physicalCondition" type="textarea" placeholder="请输入内容" />
+                </el-form-item>
+
+                <el-form-item label="初步诊断" prop="firstDiagnosis">
+                    <el-input v-model="form.firstDiagnosis" type="textarea" placeholder="请输入内容" />
+                </el-form-item>
+                <el-row>
+                    <el-col :span="12">
+                        <el-form-item label="日期" prop="dateTime">
+                            <el-date-picker clearable size="small" v-model="form.dateTime" type="date"
+                                value-format="yyyy-MM-dd" placeholder="选择日期">
+                            </el-date-picker>
+                        </el-form-item>
+                    </el-col>
+                </el-row>
+            </el-form>
+            <div slot="footer" class="dialog-footer">
+                <el-button type="primary" @click="submitForm">确 定</el-button>
+                <el-button @click="cancel">取 消</el-button>
+            </div>
+        </el-dialog>
+    </div>
+</template>
+
+<script>
+import { getDiagnosisList, fill, detail } from "@/api/diagnosis.js";
+export default {
+    name: "diagnosis",
+    data() {
+        return {
+            queryParams: {
+                pageNum: 1,
+                pageSize: 10,
+            },
+            // 遮罩层
+            loading: true,
+            // 导出遮罩层
+            exportLoading: false,
+            // 选中数组
+            ids: [],
+            // 非单个禁用
+            single: true,
+            // 非多个禁用
+            multiple: true,
+            // 显示搜索条件
+            showSearch: true,
+            // 总条数
+            total: 0,
+            list: [],
+            // 弹出层标题
+            title: "",
+            // 是否显示弹出层
+            open: false,
+            queryParams: {
+                pageNum: 1,
+                pageSize: 10,
+                type: "0",
+            },
+            // 表单参数
+            form: {},
+            // 表单校验
+            rules: {
+
+
+            }
+        }
+    },
+    created() {
+        this.getList();
+    },
+
+    methods: {
+        submitForm() {
+            this.$refs["form"].validate(valid => {
+                if (valid) {
+                    fill(this.form).then(res => {
+                        this.msgSuccess("填写成功");
+                        this.open = false;
+                        this.getList();
+                    })
+                }
+            });
+        },
+        // 取消按钮
+        cancel() {
+            this.open = false;
+        },
+        handleFill(row) {
+            detail(row.id).then(res => {
+                this.form = res.data
+            })
+            this.open = true;
+        },
+        getList() {
+            this.loading = true;
+            getDiagnosisList(this.queryParams).then(response => {
+                this.list = response.data.list;
+                this.total = response.data.total;
+                this.loading = false;
+            });
+        },
+        //切换
+        handleClickX(tab, event) {
+            this.queryParams.type = tab.name;
+            this.handleQuery();
+        },
+        // 取消按钮
+        cancel() {
+            this.open = false;
+            this.reset();
+        },
+        // 表单重置
+        reset() {
+            this.form = {
+            };
+            this.resetForm("form");
+        },
+        /** 搜索按钮操作 */
+        handleQuery() {
+            this.queryParams.pageNum = 1;
+            this.getList();
+        },
+        /** 重置按钮操作 */
+        resetQuery() {
+            this.resetForm("queryForm");
+            this.handleQuery();
+        },
+        // 多选框选中数据
+        handleSelectionChange(selection) {
+            this.ids = selection.map(item => item.patientId)
+            this.single = selection.length !== 1
+            this.multiple = !selection.length
+        }
+    }
+
+};
+</script>

+ 242 - 0
src/views/doctor/prescribeAudit/index.vue

@@ -0,0 +1,242 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="90px">
+      <el-form-item label="患者姓名" prop="patientName">
+        <el-input v-model="queryParams.patientName" placeholder="支持模糊查询" clearable @keyup.enter.native="handleQuery" />
+      </el-form-item>
+      <el-form-item label="审核状态" prop="auditStatus">
+        <el-select v-model="queryParams.auditStatus" placeholder="请选择审核状态" clearable>
+          <el-option label="待审核" :value="1" />
+          <el-option label="审核通过" :value="2" />
+          <el-option label="审核不通过" :value="3" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="创建时间" prop="dateRange">
+        <el-date-picker
+          v-model="dateRange"
+          type="daterange"
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          value-format="yyyy-MM-dd HH:mm:ss"
+          style="width: 350px" />
+      </el-form-item>
+
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+        <el-button type="success" icon="el-icon-check" size="mini" @click="quickFilter(2)">已审核</el-button>
+        <el-button type="warning" icon="el-icon-time" size="mini" @click="quickFilter(1)">待审核</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-table v-loading="loading" :data="list" border>
+      <el-table-column label="处方ID" prop="prescribeId" align="center" width="100" />
+      <el-table-column label="处方编号" prop="prescribeCode" align="center" width="140" />
+      <el-table-column label="患者姓名" prop="patientName" align="center" width="120" />
+      <el-table-column label="患者性别" align="center" width="100">
+        <template slot-scope="scope">
+          {{ genderFormat(scope.row.patientGender) }}
+        </template>
+      </el-table-column>
+      <el-table-column label="患者年龄" prop="patientAge" align="center" width="100" />
+      <el-table-column label="处方图片" align="center" width="140">
+        <template slot-scope="scope">
+          <el-image v-if="scope.row.prescribeImgUrl" :src="scope.row.prescribeImgUrl" :preview-src-list="[scope.row.prescribeImgUrl]" style="width: 120px; height: 120px" fit="cover" />
+          <span v-else>无</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="诊断信息" prop="diagnose" align="center" min-width="180" />
+      <el-table-column label="拒绝原因" prop="auditReason" align="center" min-width="160" />
+      <el-table-column label="审核时间" prop="auditTime" align="center" width="160" />
+      <el-table-column label="审核状态" align="center" width="120">
+        <template slot-scope="scope">
+          {{ statusFormat(scope.row.auditStatus != null ? scope.row.auditStatus : scope.row.status) }}
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="220">
+        <template slot-scope="scope">
+          <el-button size="mini" type="text" :disabled="!isPending(scope.row)" @click="handleAudit(scope.row)">审核</el-button>
+          <el-divider direction="vertical"></el-divider>
+          <el-button size="mini" type="text" @click="viewDrugs(scope.row)">处方药品</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
+
+    <el-dialog title="处方审核" :visible.sync="auditDialog.open" width="500px" append-to-body>
+      <el-form :model="auditForm" ref="auditFormRef" label-width="90px">
+        <el-form-item label="审核状态" prop="auditStatus">
+          <el-radio-group v-model="auditForm.auditStatus">
+            <el-radio :label="2">审核通过</el-radio>
+            <el-radio :label="3">审核不通过</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item v-if="auditForm.auditStatus === 3" label="拒绝原因" prop="auditReason">
+          <el-input type="textarea" v-model="auditForm.auditReason" placeholder="请填写拒绝原因" :rows="3" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="auditDialog.open=false">取 消</el-button>
+        <el-button type="primary" @click="submitAudit">确 定</el-button>
+      </div>
+    </el-dialog>
+    <el-dialog title="处方药品" :visible.sync="drugsDialog.open" width="720px" append-to-body>
+      <div v-if="drugsList && drugsList.length">
+        <el-table :data="drugsList" border size="mini">
+          <el-table-column label="药品图片" width="120" align="center">
+            <template slot-scope="scope">
+              <el-image v-if="scope.row.drugImgUrl" :src="scope.row.drugImgUrl" :preview-src-list="[scope.row.drugImgUrl]" style="width: 80px; height: 80px" fit="cover" />
+              <span v-else>无</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="药品名称" prop="drugName" min-width="180" align="center" />
+          <el-table-column label="药品规格" prop="drugSpec" min-width="160" align="center" />
+          <el-table-column label="使用方法" prop="usageMethod" min-width="160" align="center" />
+          <el-table-column label="药品数量" prop="drugNum" width="120" align="center" />
+        </el-table>
+      </div>
+      <el-empty v-else description="暂无药品数据"></el-empty>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="drugsDialog.open=false">关 闭</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { getPrescribeList, auditPrescribe } from '@/api/prescribe'
+export default {
+  name: 'PrescribeAudit',
+  data() {
+    return {
+      loading: false,
+      showSearch: true,
+      total: 0,
+      list: [],
+      dateRange: [],
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        patientName: undefined,
+        auditStatus: undefined,
+        beginCreateTime: undefined,
+        endCreateTime: undefined
+      },
+      auditDialog: { open: false },
+      auditForm: {
+        prescribeId: undefined,
+        auditStatus: 2,
+        auditReason: ''
+      },
+      drugsDialog: { open: false },
+      drugsList: [],
+    }
+  },
+  created() {
+    // 默认加载列表
+    this.getList()
+  },
+  methods: {
+    genderFormat(val) {
+      if (val === 1 || val === '1') return '男'
+      if (val === 2 || val === '2') return '女'
+      return '-'
+    },
+    statusFormat(val) {
+      if (val === 1 || val === '1') return '待审核'
+      if (val === 2 || val === '2') return '审核通过'
+      if (val === 3 || val === '3') return '审核不通过'
+      return '-'
+    },
+    handleQuery() {
+      this.queryParams.pageNum = 1
+      if (this.dateRange && this.dateRange.length === 2) {
+        this.queryParams.beginCreateTime = this.dateRange[0]
+        this.queryParams.endCreateTime = this.dateRange[1]
+      } else {
+        this.queryParams.beginCreateTime = undefined
+        this.queryParams.endCreateTime = undefined
+      }
+      this.getList()
+    },
+    resetQuery() {
+      this.dateRange = []
+      this.queryParams = {
+        pageNum: 1,
+        pageSize: 10,
+        patientName: undefined,
+        auditStatus: undefined,
+        beginCreateTime: undefined,
+        endCreateTime: undefined
+      }
+      this.getList()
+    },
+    quickFilter(auditStatus) {
+      this.queryParams.auditStatus = auditStatus
+      this.handleQuery()
+    },
+    getList() {
+      this.loading = true
+      const params = { ...this.queryParams }
+      if (params.auditStatus !== undefined) {
+        params.status = params.auditStatus
+      }
+      getPrescribeList(params).then(res => {
+        const data = res.data || res
+        this.list = data.list || data.rows || []
+        this.total = data.total || 0
+      }).finally(() => {
+        this.loading = false
+      })
+    },
+    isPending(row) {
+      const current = (row.auditStatus != null ? row.auditStatus : row.status)
+      return current === 1 || current === '1'
+    },
+    handleAudit(row) {
+      const current = (row.auditStatus != null ? row.auditStatus : row.status)
+      if (!(current === 1 || current === '1')) {
+        this.$message.warning('仅待审核记录可操作')
+        return
+      }
+      this.auditForm.prescribeId = row.prescribeId
+      this.auditForm.auditStatus = (current === 2 || current === '2') ? 2 : (current === 3 || current === '3') ? 3 : 2
+      this.auditForm.auditReason = row.auditReason || ''
+      this.auditDialog.open = true
+    },
+    submitAudit() {
+      if (this.auditForm.auditStatus === 3 && (!this.auditForm.auditReason || !this.auditForm.auditReason.trim())) {
+        this.$message.error('请填写拒绝原因')
+        return
+      }
+      const payload = { prescribeId: this.auditForm.prescribeId, status: this.auditForm.auditStatus, auditReason: this.auditForm.auditReason }
+      auditPrescribe(payload).then(() => {
+        this.$message.success('审核成功')
+        this.auditDialog.open = false
+        this.getList()
+      }).catch(() => {
+        this.$message.error('审核失败')
+      })
+    },
+    viewDrugs(row) {
+      const raw = row && row.drugs ? row.drugs : []
+      this.drugsList = (raw || []).map(d => ({
+        drugImgUrl: d.drugImgUrl || d.imgUrl || d.imageUrl || '',
+        drugName: d.drugName || d.name || '-',
+        drugSpec: d.drugSpec || d.spec || '-',
+        usageMethod: d.usageMethod || d.usage || '-',
+        drugNum: (d.drugNum != null ? d.drugNum : (d.num != null ? d.num : '-'))
+      }))
+      this.drugsDialog.open = true
+    },
+  }
+}
+</script>
+
+<style scoped>
+.app-container {
+  padding: 20px;
+}
+</style>

+ 19 - 6
src/views/his/prescribe/index.vue

@@ -148,11 +148,18 @@
       <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
         <template slot-scope="scope">
           <el-button
+            v-if="scope.row.doctorConfirm == 0"
             size="mini"
             type="text"
             icon="el-icon-edit"
             @click="handleUpdate(scope.row)"
-          >修改</el-button>
+          >开方</el-button>
+          <el-button
+            v-if="scope.row.doctorConfirm == 1"
+            size="mini"
+            type="text"
+            @click="handleUpdate(scope.row)"
+          >查看</el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -224,6 +231,7 @@
             <el-row :gutter="10" class="mb8">
               <el-col :span="1.5">
                 <el-button
+                  v-if="currentConfirm === 0"
                   type="primary"
                   icon="el-icon-plus"
                   size="mini"
@@ -239,7 +247,7 @@
               border
               max-height="400"
             >
-              <el-table-column label="药品名称" align="center" prop="drugName" width="120" />
+              <el-table-column label="药品名称" align="center" prop="drugName"  />
               <el-table-column label="规格" align="center" prop="drugSpec" width="100" />
               <el-table-column label="使用方法" align="center" prop="usageMethod" width="100" />
               <el-table-column label="频次" align="center" prop="usageFrequencyUnit" width="80" />
@@ -255,7 +263,7 @@
                   {{ scope.row.drugNum }}{{ scope.row.drugUnit }}
                 </template>
               </el-table-column>
-              <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="150">
+              <el-table-column v-if="currentConfirm === 0" label="操作" align="center" class-name="small-padding fixed-width" width="150">
                 <template slot-scope="scope">
                   <el-button
                     size="mini"
@@ -284,8 +292,8 @@
         </el-tab-pane>
       </el-tabs>
       <span slot="footer" class="dialog-footer">
-        <el-button @click="open = false">取 消</el-button>
-        <el-button type="primary" @click="submitForm">保 存</el-button>
+        <el-button @click="open = false">关 闭</el-button>
+        <el-button type="primary" @click="submitForm" v-if="currentConfirm === 0">保 存</el-button>
         <el-button type="primary" @click="confirmPrescribe" v-if="currentConfirm === 0">确认处方</el-button>
       </span>
     </el-dialog>
@@ -522,7 +530,12 @@ export default {
       getPrescribe(prescribeId).then(response => {
         this.form = response.data;
         this.open = true;
-        this.title = "修改处方";
+        if(this.currentConfirm == 0) {
+          this.title = "修改处方";
+        } else {
+          this.title = "查看处方";
+        }
+
         this.activeTab = 'basic';
         // 如果有prescribeId,则可以查询药品列表
         if (this.form.prescribeId) {

+ 744 - 0
src/views/his/refuse/index.vue

@@ -0,0 +1,744 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="处方类型" prop="prescribeType">
+        <el-select v-model="queryParams.prescribeType" placeholder="请选择处方类型" clearable>
+          <el-option label="西药" :value="1"></el-option>
+          <el-option label="中药" :value="2"></el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="处方单号" prop="prescribeCode">
+        <el-input v-model="queryParams.prescribeCode" placeholder="请输入处方单号" clearable @keyup.enter.native="handleQuery" />
+      </el-form-item>
+      <el-form-item label="订单编号" prop="orderCode">
+        <el-input v-model="queryParams.orderCode" placeholder="请输入订单编号" clearable @keyup.enter.native="handleQuery" />
+      </el-form-item>
+      <el-form-item label="患者姓名" prop="patientName">
+        <el-input v-model="queryParams.patientName" placeholder="请输入患者姓名" clearable @keyup.enter.native="handleQuery" />
+      </el-form-item>
+<!--      <el-form-item label="患者电话" prop="patientTel">-->
+<!--        <el-input v-model="queryParams.patientTel" placeholder="请输入患者电话" clearable @keyup.enter.native="handleQuery" />-->
+<!--      </el-form-item>-->
+<!--      <el-form-item label="处方状态" prop="status">-->
+<!--        <el-select v-model="queryParams.status" placeholder="请选择处方状态" clearable>-->
+<!--          <el-option label="未开" :value="0"></el-option>-->
+<!--          <el-option label="已开" :value="1"></el-option>-->
+<!--        </el-select>-->
+<!--      </el-form-item>-->
+      <!-- <el-form-item label="医生姓名" prop="doctorName">
+        <el-input v-model="queryParams.doctorName" placeholder="请输入医生姓名" clearable @keyup.enter.native="handleQuery" />
+      </el-form-item> -->
+      <!-- <el-form-item label="创建时间" prop="dateRange">
+        <el-date-picker
+          v-model="dateRange"
+          type="daterange"
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          value-format="yyyy-MM-dd"
+          @change="handleDateRangeChange"
+        ></el-date-picker>
+      </el-form-item> -->
+      <el-form-item label="审核时间" prop="auditRange">
+        <el-date-picker
+          v-model="auditRange"
+          type="daterange"
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          value-format="yyyy-MM-dd"
+          @change="handleAuditDateRangeChange"
+        ></el-date-picker>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+          v-hasPermi="['his:prescribe:add']"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="success"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          :disabled="single"
+          @click="handleUpdate"
+          v-hasPermi="['his:prescribe:edit']"
+        >修改</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="danger"
+          plain
+          icon="el-icon-delete"
+          size="mini"
+          :disabled="multiple"
+          @click="handleDelete"
+          v-hasPermi="['his:prescribe:remove']"
+        >删除</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          @click="handleExport"
+          v-hasPermi="['his:prescribe:export']"
+        >导出</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="info"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          @click="handleMessageFeedbackExport"
+          v-hasPermi="['his:prescribe:messageFeedbackExport']"
+        >导出医疗信息反馈单</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table v-loading="loading" :data="prescribeList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="处方ID" align="center" prop="prescribeId" />
+      <el-table-column label="处方单号" align="center" prop="prescribeCode" />
+      <el-table-column label="处方类型" align="center" prop="prescribeType">
+        <template slot-scope="scope">
+          <dict-tag :options="prescribeTypeOptions" :value="scope.row.prescribeType"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="订单编号" align="center" prop="orderCode" width="180" />
+      <el-table-column label="患者姓名" align="center" prop="patientName" />
+<!--      <el-table-column label="患者电话" align="center" prop="patientTel" />-->
+      <el-table-column label="医生姓名" align="center" prop="doctorName" />
+      <el-table-column label="是否确认" align="center" prop="doctorConfirm" >
+        <template slot-scope="scope">
+          <dict-tag :options="doctorConfirmOptions" :value="scope.row.doctorConfirm"/>
+        </template>
+      </el-table-column>
+<!--      <el-table-column label="状态" align="center" prop="status">-->
+<!--        <template slot-scope="scope">-->
+<!--          <dict-tag :options="statusOptions" :value="scope.row.status"/>-->
+<!--        </template>-->
+<!--      </el-table-column>-->
+      <el-table-column label="审核时间" align="center" prop="auditTime" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.auditTime, '{y}-{m}-{d}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="创建时间" align="center" prop="createTime" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+          >开方</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+    <el-dialog :title="title" :visible.sync="open" width="1000px" append-to-body @close="handleDialogClose">
+      <el-tabs v-model="activeTab" type="border-card">
+        <!-- Tab 1: 基本信息 -->
+        <el-tab-pane label="基本信息" name="basic">
+          <el-form ref="form" :model="form" :rules="rules" label-width="120px">
+            <el-form-item label="处方类型" prop="prescribeType">
+              <el-radio-group v-model="form.prescribeType">
+                <el-radio :label="1">西药</el-radio>
+                <el-radio :label="2">中药</el-radio>
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item label="订单ID" prop="inquiryOrderId">
+              <el-input v-model="form.inquiryOrderId" placeholder="请输入订单ID" disabled/>
+            </el-form-item>
+            <el-form-item label="店铺订单ID" prop="storeOrderId">
+              <el-input v-model="form.storeOrderId" placeholder="请输入店铺订单ID" disabled/>
+            </el-form-item>
+            <el-form-item label="患者姓名" prop="patientName">
+              <el-input v-model="form.patientName" placeholder="请输入患者姓名" />
+            </el-form-item>
+            <el-form-item label="患者年龄" prop="patientAge">
+              <el-input v-model="form.patientAge" placeholder="请输入患者年龄" />
+            </el-form-item>
+            <el-form-item label="患者性别" prop="patientGender">
+              <el-select v-model="form.patientGender" placeholder="请选择患者性别">
+                <el-option label="男" value="1"></el-option>
+                <el-option label="女" value="2"></el-option>
+              </el-select>
+            </el-form-item>
+<!--            <el-form-item label="患者电话" prop="patientTel">-->
+<!--              <el-input v-model="form.patientTel" placeholder="请输入患者电话" disabled/>-->
+<!--            </el-form-item>-->
+            <el-form-item label="体重" prop="weight">
+              <el-input v-model="form.weight" placeholder="请输入体重" />
+            </el-form-item>
+            <el-form-item label="是否有过敏史" prop="isHistoryAllergic">
+              <el-radio-group v-model="form.isHistoryAllergic">
+                <el-radio :label="'是'">是</el-radio>
+                <el-radio :label="'否'">否</el-radio>
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item label="过敏史" prop="historyAllergic">
+              <el-input v-model="form.historyAllergic" placeholder="请输入过敏史" />
+            </el-form-item>
+            <el-form-item label="诊断" prop="diagnose">
+              <el-input v-model="form.diagnose" placeholder="请输入诊断" />
+            </el-form-item>
+            <el-form-item label="备注" prop="remark">
+              <el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
+            </el-form-item>
+          </el-form>
+        </el-tab-pane>
+        <!-- Tab 2: 处方药品信息 -->
+        <el-tab-pane label="处方药品信息" name="drug">
+          <div class="drug-container">
+            <!-- 药品操作按钮 -->
+            <el-row :gutter="10" class="mb8">
+              <el-col :span="1.5">
+                <el-button
+                  type="primary"
+                  icon="el-icon-plus"
+                  size="mini"
+                  @click="handleAddDrug"
+                  :disabled="!form.prescribeId"
+                >新增药品</el-button>
+              </el-col>
+            </el-row>
+            <!-- 药品列表表格 -->
+            <el-table
+              v-loading="drugLoading"
+              :data="drugList"
+              border
+              max-height="400"
+            >
+              <el-table-column label="药品名称" align="center" prop="drugName"  />
+              <el-table-column label="规格" align="center" prop="drugSpec" width="100" />
+              <el-table-column label="使用方法" align="center" prop="usageMethod" width="100" />
+              <el-table-column label="频次" align="center" prop="usageFrequencyUnit" width="80" />
+              <el-table-column label="每次用药" align="center" width="120">
+                <template slot-scope="scope">
+                  {{ scope.row.usagePerUseCount }}{{ scope.row.usagePerUseUnit }}
+                </template>
+              </el-table-column>
+              <el-table-column label="天数" align="center" prop="usageDays" width="60" />
+              <el-table-column label="单价" align="center" prop="drugPrice" width="80" />
+              <el-table-column label="数量" align="center" width="100">
+                <template slot-scope="scope">
+                  {{ scope.row.drugNum }}{{ scope.row.drugUnit }}
+                </template>
+              </el-table-column>
+              <el-table-column  label="操作" align="center" class-name="small-padding fixed-width" width="150">
+                <template slot-scope="scope">
+                  <el-button
+                    size="mini"
+                    type="text"
+                    icon="el-icon-edit"
+                    @click="handleUpdateDrug(scope.row)"
+                  >编辑</el-button>
+                  <el-button
+                    size="mini"
+                    type="text"
+                    icon="el-icon-delete"
+                    @click="handleDeleteDrug(scope.row)"
+                  >删除</el-button>
+                </template>
+              </el-table-column>
+            </el-table>
+            <!-- 药品分页 -->
+            <pagination
+              v-show="drugTotal>0"
+              :total="drugTotal"
+              :page.sync="drugQueryParams.pageNum"
+              :limit.sync="drugQueryParams.pageSize"
+              @pagination="getDrugList"
+            />
+          </div>
+        </el-tab-pane>
+      </el-tabs>
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="open = false">关 闭</el-button>
+        <el-button type="primary" @click="submitForm" >保 存</el-button>
+        <el-button type="primary" @click="confirmPrescribe" >确认处方</el-button>
+      </span>
+    </el-dialog>
+    <!-- 药品新增/编辑对话框 -->
+    <el-dialog :title="drugTitle" :visible.sync="drugOpen" width="700px" append-to-body>
+      <el-form ref="drugForm" :model="drugForm" :rules="drugRules" label-width="120px">
+        <el-form-item label="药品名称" prop="drugName">
+          <el-input v-model="drugForm.drugName" placeholder="请输入药品名称" />
+        </el-form-item>
+        <el-form-item label="规格" prop="drugSpec">
+          <el-input v-model="drugForm.drugSpec" placeholder="请输入规格" />
+        </el-form-item>
+        <el-form-item label="使用方法" prop="usageMethod">
+          <el-input v-model="drugForm.usageMethod" placeholder="请输入使用方法" />
+        </el-form-item>
+        <el-form-item label="药品频次" prop="usageFrequencyUnit">
+          <el-input v-model="drugForm.usageFrequencyUnit" placeholder="请输入药品频次,如:每日3次" />
+        </el-form-item>
+        <el-form-item label="每次用药数量" prop="usagePerUseCount">
+          <el-input v-model="drugForm.usagePerUseCount" placeholder="请输入每次用药数量" />
+        </el-form-item>
+        <el-form-item label="每次用药单位" prop="usagePerUseUnit">
+          <el-input v-model="drugForm.usagePerUseUnit" placeholder="请输入每次用药单位,如:片、粒、ml" />
+        </el-form-item>
+        <el-form-item label="天数" prop="usageDays">
+          <el-input v-model="drugForm.usageDays" placeholder="请输入天数" />
+        </el-form-item>
+        <el-form-item label="药品单价" prop="drugPrice">
+          <el-input v-model="drugForm.drugPrice" placeholder="请输入药品单价" type="number" />
+        </el-form-item>
+        <el-form-item label="药品数量" prop="drugNum">
+          <el-input v-model="drugForm.drugNum" placeholder="请输入药品数量" type="number" />
+        </el-form-item>
+        <el-form-item label="药品数量单位" prop="drugUnit">
+          <el-input v-model="drugForm.drugUnit" placeholder="请输入药品数量单位,如:盒、瓶" />
+        </el-form-item>
+        <el-form-item label="药品说明书" prop="instructions">
+          <el-input v-model="drugForm.instructions" type="textarea" :rows="3" placeholder="请输入药品说明书" />
+        </el-form-item>
+      </el-form>
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="drugOpen = false">取 消</el-button>
+        <el-button type="primary" @click="submitDrugForm">确 定</el-button>
+      </span>
+    </el-dialog>
+  </div>
+</template>
+<script>
+import {
+  listPrescribe,
+  getPrescribe,
+  delPrescribe,
+  addPrescribe,
+  updatePrescribe,
+  exportPrescribe,
+  exportMessageFeedback,
+  confirmPrescribe
+} from '@/api/his/prescribe'
+import { listPrescribeDrug, addPrescribeDrug, updatePrescribeDrug, deletePrescribeDrug } from "@/api/his/prescribeDrug";
+export default {
+  name: "Prescribe",
+  data() {
+    return {
+      // 原有数据保持不变
+      loading: false,
+      ids: [],
+      single: true,
+      multiple: true,
+      showSearch: true,
+      total: 0,
+      prescribeList: [],
+      title: "",
+      open: false,
+      dateRange: [],
+      auditRange: [],
+      doctorConfirmOptions: [
+        { dictValue: '0', dictLabel: "未确认" },
+        { dictValue: '1', dictLabel: "已确认" }
+      ],
+      prescribeTypeOptions: [
+        { dictValue: '1', dictLabel: "西药" },
+        { dictValue: '2', dictLabel: "中药" }
+      ],
+      statusOptions: [
+        { dictValue: '0', dictLabel: "未开" },
+        { dictValue: '1', dictLabel: "已开" }
+      ],
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        prescribeType: undefined,
+        prescribeCode: undefined,
+        orderCode: undefined,
+        patientName: undefined,
+        patientTel: undefined,
+        status: undefined,
+        doctorName: undefined,
+        beginCreateTime: undefined,
+        endCreateTime: undefined,
+        beginAuditTime: undefined,
+        endAuditTime: undefined
+      },
+      currentConfirm: null,
+      form: {},
+      rules: {
+        prescribeType: [{ required: true, message: "处方类型不能为空", trigger: "change" }],
+        patientName: [{ required: true, message: "患者姓名不能为空", trigger: "blur" }],
+        patientAge: [{ required: true, message: "患者年龄不能为空", trigger: "blur" }],
+        patientGender: [{ required: true, message: "患者性别不能为空", trigger: "change" }],
+        diagnose: [{ required: true, message: "诊断不能为空", trigger: "blur" }],
+        doctorId: [{ required: true, message: "医生ID不能为空", trigger: "blur" }],
+        status: [{ required: true, message: "处方状态不能为空", trigger: "change" }]
+      },
+      // 新增:Tab相关
+      activeTab: 'basic',
+      // 新增:药品相关数据
+      drugLoading: false,
+      drugList: [],
+      drugTotal: 0,
+      drugOpen: false,
+      drugTitle: "",
+      drugForm: {},
+      drugQueryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        prescribeId: undefined
+      },
+      drugRules: {
+        drugName: [{ required: true, message: "药品名称不能为空", trigger: "blur" }],
+        drugSpec: [{ required: true, message: "规格不能为空", trigger: "blur" }],
+        usageMethod: [{ required: true, message: "使用方法不能为空", trigger: "blur" }],
+        usageFrequencyUnit: [{ required: true, message: "药品频次不能为空", trigger: "blur" }],
+        usagePerUseCount: [{ required: true, message: "每次用药数量不能为空", trigger: "blur" }],
+        usagePerUseUnit: [{ required: true, message: "每次用药单位不能为空", trigger: "blur" }],
+        usageDays: [{ required: true, message: "天数不能为空", trigger: "blur" }],
+        drugPrice: [{ required: true, message: "药品单价不能为空", trigger: "blur" }],
+        drugNum: [{ required: true, message: "药品数量不能为空", trigger: "blur" }],
+        drugUnit: [{ required: true, message: "药品数量单位不能为空", trigger: "blur" }],
+        instructions: [{ required: true, message: "药品说明书不能为空", trigger: "blur" }]
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  watch: {
+    // 监听Tab切换,当切换到药品Tab时加载药品列表
+    activeTab(newVal) {
+      if (newVal === 'drug' && this.form.prescribeId) {
+        this.getDrugList();
+      }
+    }
+  },
+  methods: {
+    /** 查询处方列表 */
+    getList() {
+      this.loading = true;
+      let userId = this.$store.state.user.userId;
+      let query = this.addDateRange(this.queryParams, this.dateRange, 'Create');
+      query.doctorId = userId.toString().replaceAll("D-","").toString();
+      listPrescribe(query).then(response => {
+        this.prescribeList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    /** 查询药品列表 */
+    getDrugList() {
+      if (!this.form.prescribeId) {
+        this.$message.warning("请先保存处方基本信息");
+        return;
+      }
+      this.drugLoading = true;
+      this.drugQueryParams.prescribeId = this.form.prescribeId;
+      listPrescribeDrug(this.drugQueryParams).then(response => {
+        this.drugList = response.rows;
+        this.drugTotal = response.total;
+        this.drugLoading = false;
+      });
+    },
+    // 日期范围处理
+    addDateRange(params, dateRange, type) {
+      let obj = { ...params };
+      if (dateRange != null && dateRange.length > 0) {
+        if (type === 'Create') {
+          obj.beginCreateTime = dateRange[0];
+          obj.endCreateTime = dateRange[1];
+        } else if (type === 'Audit') {
+          obj.beginAuditTime = dateRange[0];
+          obj.endAuditTime = dateRange[1];
+        }
+      }
+      return obj;
+    },
+    handleDateRangeChange(val) {
+      this.queryParams.beginCreateTime = val ? val[0] : undefined;
+      this.queryParams.endCreateTime = val ? val[1] : undefined;
+    },
+    handleAuditDateRangeChange(val) {
+      this.queryParams.beginAuditTime = val ? val[0] : undefined;
+      this.queryParams.endAuditTime = val ? val[1] : undefined;
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.dateRange = [];
+      this.auditRange = [];
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 选择条目触发事件 */
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.prescribeId);
+      this.single = selection.length !== 1;
+      this.multiple = !selection.length;
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "新增处方";
+      this.activeTab = 'basic';
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const prescribeId = row.prescribeId || this.ids[0];
+      this.currentConfirm = row.doctorConfirm;
+
+      getPrescribe(prescribeId).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "开方";
+        this.activeTab = 'basic';
+        // 如果有prescribeId,则可以查询药品列表
+        if (this.form.prescribeId) {
+          this.drugQueryParams.prescribeId = this.form.prescribeId;
+        }
+      })
+    },
+    confirmPrescribe(){
+      confirmPrescribe(this.form).then(response => {
+        if(response.code === 200) {
+          this.$message.success("确认成功!");
+          this.open = false;
+        } else {
+          this.$message.error(response.message);
+        }
+      }).finally(()=>{
+        this.getList();
+      })
+
+    },
+    /** 提交按钮操作 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.prescribeId != undefined) {
+            updatePrescribe(this.form).then(response => {
+              this.$message.success("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addPrescribe(this.form).then(response => {
+              this.$message.success("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      this.$modal.confirm('是否确认导出所有处方数据项?').then(() => {
+        this.exportLoading = true;
+        return exportPrescribe(this.queryParams);
+      }).then(response => {
+        this.$download.excel(response, '处方数据');
+        this.exportLoading = false;
+      }).catch(() => {});
+    },
+    /** 导出医疗信息反馈单 */
+    handleMessageFeedbackExport() {
+      if (this.ids.length === 0) {
+        this.$modal.msgError("请至少选择一条记录");
+        return;
+      }
+      this.$modal.confirm('是否确认导出所选处方的医疗信息反馈单?').then(() => {
+        this.exportLoading = true;
+        return exportMessageFeedback(this.ids);
+      }).then(response => {
+        this.$download.excel(response, '医疗信息反馈单');
+        this.exportLoading = false;
+      }).catch(() => {});
+    },
+    /** 对话框关闭事件 */
+    handleDialogClose() {
+      // 重置药品相关数据
+      this.drugList = [];
+      this.currentConfirm = null;
+      this.drugTotal = 0;
+      this.drugQueryParams.pageNum = 1;
+      this.activeTab = 'basic';
+    },
+    // ==================== 药品相关方法 ====================
+    /** 新增药品按钮操作 */
+    handleAddDrug() {
+      if (!this.form.prescribeId) {
+        this.$modal.msgWarning("请先保存处方基本信息后再添加药品");
+        return;
+      }
+      this.resetDrugForm();
+      this.drugOpen = true;
+      this.drugTitle = "新增药品";
+    },
+    /** 修改药品按钮操作 */
+    handleUpdateDrug(row) {
+      this.resetDrugForm();
+      this.drugForm = { ...row };
+      this.drugOpen = true;
+      this.drugTitle = "修改药品";
+    },
+    /** 提交药品表单 */
+    submitDrugForm() {
+      this.$refs["drugForm"].validate(valid => {
+        if (valid) {
+          this.drugForm.prescribeId = this.form.prescribeId;
+          if (this.drugForm.drugId != undefined) {
+            // 修改
+            updatePrescribeDrug(this.drugForm).then(response => {
+              this.$message.success("修改成功");
+              this.drugOpen = false;
+              // 确保刷新完成
+              this.getDrugList();
+            }).catch(error => {
+              // 发生错误时也要确保能正常操作
+              console.error('修改药品失败:', error);
+            });
+          } else {
+            // 新增
+            addPrescribeDrug(this.drugForm).then(response => {
+              this.$message.success("新增成功");
+              this.drugOpen = false;
+              // 新增成功后回到第一页并刷新
+              this.drugQueryParams.pageNum = 1;
+              this.getDrugList();
+            }).catch(error => {
+              console.error('新增药品失败:', error);
+            });
+          }
+        }
+      });
+    },
+    /** 删除药品按钮操作 */
+    handleDeleteDrug(row) {
+      this.$confirm('是否确认删除药品"' + row.drugName + '"?').then(() => {
+        return deletePrescribeDrug(row.drugId);
+      }).then(() => {
+        this.$message.success("删除成功");
+
+        // 处理删除后的分页逻辑
+        // 如果当前页只有一条数据,且不是第一页,则页码减1
+        if (this.drugList.length === 1 && this.drugQueryParams.pageNum > 1) {
+          this.drugQueryParams.pageNum--;
+        }
+
+        // 刷新药品列表
+        this.getDrugList();
+      }).catch(error => {
+        if (error !== 'cancel') {
+          console.error('删除药品失败:', error);
+        }
+      });
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        prescribeId: undefined,
+        prescribeCode: undefined,
+        prescribeType: 1,
+        inquiryOrderId: undefined,
+        storeOrderId: undefined,
+        orderCode: undefined,
+        patientId: undefined,
+        patientName: undefined,
+        patientAge: undefined,
+        patientGender: "1",
+        patientTel: undefined,
+        weight: undefined,
+        isHistoryAllergic: "否",
+        historyAllergic: undefined,
+        diagnose: undefined,
+        doctorId: undefined,
+        doctorName: undefined,
+        status: 0,
+        remark: undefined
+      };
+      this.resetForm("form");
+    },
+    // 药品表单重置
+    resetDrugForm() {
+      this.drugForm = {
+        drugId: undefined,
+        prescribeId: undefined,
+        drugName: undefined,
+        drugSpec: undefined,
+        usageMethod: undefined,
+        usageFrequencyUnit: undefined,
+        usagePerUseCount: undefined,
+        usagePerUseUnit: undefined,
+        usageDays: undefined,
+        drugPrice: undefined,
+        drugNum: undefined,
+        drugUnit: undefined,
+        instructions: undefined
+      };
+      this.resetForm("drugForm");
+    }
+  }
+};
+</script>
+
+<style scoped>
+.drug-container {
+  padding: 10px;
+}
+.detail-container {
+  padding: 20px;
+}
+.detail-item {
+  margin-bottom: 10px;
+  display: flex;
+}
+.detail-item .label {
+  min-width: 100px;
+  font-weight: bold;
+}
+.id-card-info {
+  display: flex;
+  flex-direction: column;
+}
+.id-card-image {
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: 20px;
+}
+.id-card-detail {
+  margin-top: 20px;
+}
+</style>

+ 233 - 66
src/views/im/index.vue

@@ -20,7 +20,7 @@
               <current-conversation />
             </el-col>
           </el-row>
-          
+
         </div>
         <calling  ref="callLayer" class="chat-wrapper"/>
         <image-previewer />
@@ -29,7 +29,7 @@
       <div class="bg"></div>
     </div>
   </div>
-  
+
 </template>
 
 <script>
@@ -46,12 +46,15 @@ import { translateGroupSystemNotice } from '@/utils/common'
 import GroupLive from '@/components/group-live/index'
 import Calling from '@/components/message/trtc-calling/calling-index'
 import { ACTION } from '@/utils/trtcCustomMessageMap'
-
+import { getOpenIM,getCbEvents } from '@/utils/openIM';
+import { accountCheck } from '@/api/doctor';
 export default {
   title: 'TIMSDK DEMO',
   data () {
     return {
-      loginType: 2 // github 登录只使用默认账号登录
+      loginType: 2, // github 登录只使用默认账号登录
+      OpenIM: null,
+      userToken:""
     }
   },
   components: {
@@ -74,6 +77,7 @@ export default {
       isSDKReady: state => state.imuser.isSDKReady,
       isBusy: state => state.video.isBusy,
       userID: state => state.imuser.userID,
+      token: state => state.imuser.token,
       userSig: state => state.imuser.userSig,
       sdkAppID: state => state.imuser.sdkAppID
     }),
@@ -83,70 +87,223 @@ export default {
     }
   },
   created() {
-    this.getTlsSig();
+    if (process.env.IS_OPENIM){
+      this.OpenIM = getOpenIM();
+      this.initListener()
+      this.getTlsSig();
+    }
   },
   mounted() {
-    // 初始化监听器
-    this.getTlsSig()
-    this.initListener()
-
+    //this.getTlsSig()
   },
 
   watch: {
   },
 
   methods: {
+    // 修改 getTlsSig 方法
     getTlsSig() {
-      console.log(this.$store.getters.userID);
-      var data={userId:this.$store.getters.userID}
-      getTlsSig(data).then(res => {
-        var sign = res.data;
-        this.$store.commit('setUserSig', sign)
-        this.tim
-        .login({
+      accountCheck(this.$store.getters.userID).then(response => {
+        this.userToken = response.token
+        const config = {
           userID: this.$store.getters.userID,
-          userSig: sign
+          token: this.userToken,
+          logLevel:6,
+          platformID: 5, // 使用配置的平台ID
+          apiAddr: process.env.VUE_APP_API_ADDR,
+          wsAddr: process.env.VUE_APP_WS_ADDR,
+          dataDir: '/imdata' // 添加数据存储目录
+        }
+
+        this.OpenIM.login(config).then(() => {
+          this.$nextTick(() => {
+            this.checkSDKReadyState();
+          });
+          this.$store.commit('toggleIsLogin', true);
+          this.$store.commit('startComputeCurrent');
+          this.$store.commit('showMessage', {
+            type: 'success',
+            message: 'IM 登录成功'
+          });
         })
-        .then(() => {
-          this.$store.commit('toggleIsLogin', true)
-          this.$store.commit('startComputeCurrent')
-          this.$store.commit('setSdkAppId', imConfig.SDKAPPID)
-          this.$store.commit('showMessage', { type: 'success', message: 'IM登录成功' })
+          .catch((error) => {
+            this.loading = false;
+            console.error('登录失败:', error);
+            this.$store.commit('showMessage', {
+              message: `IM 登录失败:${error.message || error.errMsg || '未知错误'}`,
+              type: 'error',
+            });
+          });
+
+      });
+    },
+    // 添加SDK就绪状态检查
+    checkSDKReadyState() {
+      if (this.hasBindReadyEvent) return;
+      this.hasBindReadyEvent = true;
+
+      let isReady = false;
+
+      const timeout = setTimeout(() => {
+        if (!isReady) {
+          this.$store.commit('toggleIsSDKReady', false);
+          this.$store.commit('showMessage', {
+            message: 'SDK初始化超时',
+            type: 'error'
+          });
+        }
+      }, 10000);
+
+      this.OpenIM.on(getCbEvents().OnConnectSuccess, () => {
+        clearTimeout(timeout);
+        isReady = true;
+        console.log("this.OpenIM",this.OpenIM)
+        this.OpenIM.getSelfUserInfo().then(({ data }) => {
+          this.$store.commit('updateCurrentUserProfile', data)
         })
-        .catch(error => {
-          this.loading = false
-          // this.$store.commit('showMessage', {
-          //   message: '登录失败:' + error.message,
-          //   type: 'error'
-          // })
+        this.$store.commit('toggleIsSDKReady', true);
+        this.$store.commit('showMessage', {
+          type: 'success',
+          message: 'SDK 初始化成功'
+        });
+        this.loadUserData();
+      });
+
+      this.OpenIM.on(getCbEvents().OnConnectFailed, (error) => {
+        clearTimeout(timeout);
+        this.$store.commit('toggleIsSDKReady', false);
+        this.$store.commit('showMessage', {
+          message: `SDK 连接失败: ${error.message}`,
+          type: 'error'
+        });
+      });
+    },
+    // 添加加载用户数据方法
+    loadUserData() {
+      //查询会话列表
+      this.OpenIM.getAllConversationList()
+        .then(({ data }) => {
+          // 调用成功
+          console.log("获取到会话列表",data)
+          this.conversationList= data
+          this.$store.commit('updateConversationList', data)
+        })
+        .catch(({ errCode, errMsg }) => {
+          // 调用失败
         })
+      //查询好友列表
+      this.OpenIM.getFriendListPage({ offset:0, count:100 })
+        .then(({ data }) => {
+          // 调用成功
+          console.log("获取到好友列表",data)
+          //this.conversationList= data
+          this.$store.commit('updateFriendList', data)
+        })
+        .catch(({ errCode, errMsg }) => {
+          // 调用失败
+        })
+    },
+
+    selfUpdateHandler({ data }) {
+      this.updateMessageNicknameAndFaceUrl({
+        sendID: data.userID,
+        senderNickname: data.nickname,
+        senderFaceUrl: data.faceURL,
       });
-    },  
+      this.updateSelfInfo(data);
+    },
+    // 更新昵称和头像
+    updateMessageNicknameAndFaceUrl({ sendID, senderNickname, senderFaceUrl }) {
+      // 更新消息昵称和头像的逻辑
+      console.log(sendID, senderNickname, senderFaceUrl);
+    },
+
+    // 更新个人信息
+    updateSelfInfo(data) {
+      // 更新个人信息的逻辑
+      console.log(data);
+    },
     initListener() {
       // 登录成功后会触发 SDK_READY 事件,该事件触发后,可正常使用 SDK 接口
-      this.tim.on(this.TIM.EVENT.SDK_READY, this.onReadyStateUpdate, this)
+      this.OpenIM.on(getCbEvents().OnConnectSuccess, () => {
+        console.log("OnConnectSuccess 事件触发!"); // 调试日志
+        this.$store.commit('toggleIsSDKReady', true);
+      });
+      this.OpenIM.on(getCbEvents().OnConnectFailed, this.onError);
+      this.OpenIM.on(getCbEvents().OnKickedOffline, this.onKickOut);
+      this.OpenIM.on(getCbEvents().OnSelfInfoUpdated, this.onSelfInfoUpdated);
+      this.OpenIM.on(getCbEvents().OnFriendAdded,(data)=>{
+        console.log("新增好友事件触发",data)
+        this.onFriendListUpdated(data);
+      });
+      /*this.OpenIM.on(getCbEvents().OnRecvNewMessage, (message) => {
+        console.log("收到单条消息", message);
+        this.onReceiveMessage({data: [message]}); // 包装成数组形式
+      });*/
+
+      this.OpenIM.on(getCbEvents().OnRecvNewMessages, (data) => {
+        console.log("收到多条消息", data);
+        const msgList = []
+        data.data.forEach(msg =>{
+          if (msg.contentType!==113){
+            msgList.push(msg)
+          }
+        })
+        if (msgList.length>0){
+          this.onReceiveMessage({data: msgList});
+
+        }
+      });
+      /*this.OpenIM.on(getCbEvents().OnConversationChanged, (eventData) => {
+        console.log("触发OnConversationChanged事件", eventData);
+
+        try {
+          // 1. 确保数据结构正确
+          if (!eventData || !eventData.data) {
+            console.warn("无效的会话更新数据", eventData);
+            return;
+          }
+
+          // 2. 提取更新后的会话列表
+          const updatedConversations = eventData.data;
+
+          // 3. 打印调试信息
+          console.log("更新的会话列表:", updatedConversations);
+
+          // 4. 更新到Vuex store
+          this.$store.commit('updateConversationList', updatedConversations);
+
+          // 5. 可选:检查特定会话的更新
+          updatedConversations.forEach(conv => {
+            console.log(`会话ID: ${conv.conversationID} 已更新`, conv);
+          });
+
+        } catch (error) {
+          console.error("处理会话更新时出错:", error);
+        }
+      });*/
       // SDK NOT READT
-      this.tim.on(this.TIM.EVENT.SDK_NOT_READY, this.onReadyStateUpdate, this)
+      /*this.OpenIM.on(this.OpenIM.EVENT.SDK_NOT_READY, this.onReadyStateUpdate, this)
       // 被踢出
-      this.tim.on(this.TIM.EVENT.KICKED_OUT, this.onKickOut)
+      this.OpenIM.on(this.OpenIM.EVENT.KICKED_OUT, this.onKickOut)
       // SDK内部出错
-      this.tim.on(this.TIM.EVENT.ERROR, this.onError)
+      this.OpenIM.on(this.OpenIM.EVENT.ERROR, this.onError)
       // 收到新消息
-      this.tim.on(this.TIM.EVENT.MESSAGE_RECEIVED, this.onReceiveMessage)
+      this.OpenIM.on(this.OpenIM.EVENT.MESSAGE_RECEIVED, this.onReceiveMessage)
       // 会话列表更新
-      this.tim.on(this.TIM.EVENT.CONVERSATION_LIST_UPDATED, this.onUpdateConversationList)
+      this.OpenIM.on(this.OpenIM.EVENT.CONVERSATION_LIST_UPDATED, this.onUpdateConversationList)
       // 群组列表更新
-      this.tim.on(this.TIM.EVENT.GROUP_LIST_UPDATED, this.onUpdateGroupList)
+      this.OpenIM.on(this.OpenIM.EVENT.GROUP_LIST_UPDATED, this.onUpdateGroupList)
       // 网络监测
-      this.tim.on(this.TIM.EVENT.NET_STATE_CHANGE, this.onNetStateChange)
+      this.OpenIM.on(this.OpenIM.EVENT.NET_STATE_CHANGE, this.onNetStateChange)
       // 已读回执
-      this.tim.on(this.TIM.EVENT.MESSAGE_READ_BY_PEER, this.onMessageReadByPeer)
+      this.OpenIM.on(this.OpenIM.EVENT.MESSAGE_READ_BY_PEER, this.onMessageReadByPeer)
       // 黑名单更新
-      this.tim.on(this.TIM.EVENT.FRIEND_LIST_UPDATED, this.onFriendListUpdated)
+      this.OpenIM.on(this.OpenIM.EVENT.FRIEND_LIST_UPDATED, this.onFriendListUpdated)
 
-      this.tim.on(this.TIM.EVENT.FRIEND_APPLICATION_LIST_UPDATED, this.onFriendApplicationListUpdated)
+      this.OpenIM.on(this.OpenIM.EVENT.FRIEND_APPLICATION_LIST_UPDATED, this.onFriendApplicationListUpdated)
 
-      this.tim.on(this.TIM.EVENT.FRIEND_GROUP_LIST_UPDATED, this.onFriendGroupListUpdated)
+      this.OpenIM.on(this.OpenIM.EVENT.FRIEND_GROUP_LIST_UPDATED, this.onFriendGroupListUpdated)*/
 
     },
     onFriendApplicationListUpdated(data) {
@@ -162,18 +319,28 @@ export default {
 
     onReceiveMessage({ data: messageList }) {
       // let totalUnreadCount = this.tim.getTotalUnreadMessageCount();
-      console.log("收到消息数")
+
       messageList.forEach(element => {
-        if(element.from!=this.$store.getters.userID){
+        //过滤掉正在输入状态
+        if(element.sendID!=this.$store.getters.userID&&element.contentType!==113){
           this.$notify({
             title: '消息提示',
             message: '您有一条新的消息',
             type: 'success'
           });
         }
+
       });
+      this.OpenIM.getAllConversationList()
+        .then(({ data }) => {
+          // 调用成功
+          this.$store.commit('updateConversationList', data)
+        })
+        .catch(({ errCode, errMsg }) => {
+          // 调用失败
+        })
       console.log(messageList)
-      this.handleVideoMessage(messageList)
+      //this.handleVideoMessage(messageList)
       this.handleQuitGroupTip(messageList)
       this.handleCloseGroupLive(messageList)
       this.$store.commit('pushCurrentMessageList', messageList)
@@ -192,7 +359,8 @@ export default {
 
     },
     onReadyStateUpdate({ name }) {
-      const isSDKReady = name === this.TIM.EVENT.SDK_READY ? true : false
+      console.log("当前登录用户基本信息")
+      const isSDKReady = name === getCbEvents().OnConnectSuccess ? true : false
       this.$store.commit('toggleIsSDKReady', isSDKReady)
 
 
@@ -200,9 +368,8 @@ export default {
       // console.log("收到消息数"+totalUnreadCount)
 
       if (isSDKReady) {
-        this.tim
-          .getMyProfile()
-          .then(({ data }) => {
+        this.OpenIM.getSelfUserInfo().then(({ data }) => {
+            console.log("当前登录用户基本信息",data)
             this.$store.commit('updateCurrentUserProfile', data)
           })
           .catch(error => {
@@ -211,39 +378,39 @@ export default {
               message: error.message
             })
           })
-        this.$store.dispatch('getBlacklist')
+        /*this.$store.dispatch('getBlacklist')
         // 登录trtc calling
         console.log(this.sdkAppID)
         this.trtcCalling.login({
           sdkAppID: this.sdkAppID,
           userID: this.userID,
           userSig:this.userSig
-        })
+        })*/
       }
     },
     kickedOutReason(type) {
-      switch (type) {
-        case this.TIM.TYPES.KICKED_OUT_MULT_ACCOUNT:
+      /*switch (type) {
+        case this.OpenIM.TYPES.KICKED_OUT_MULT_ACCOUNT:
           return '由于多实例登录'
-        case this.TIM.TYPES.KICKED_OUT_MULT_DEVICE:
+        case this.OpenIM.TYPES.KICKED_OUT_MULT_DEVICE:
           return '由于多设备登录'
-        case this.TIM.TYPES.KICKED_OUT_USERSIG_EXPIRED:
+        case this.OpenIM.TYPES.KICKED_OUT_USERSIG_EXPIRED:
           return '由于 userSig 过期'
         default:
           return ''
-      }
+      }*/
     },
     checkoutNetState(state) {
-      switch (state) {
-        case this.TIM.TYPES.NET_STATE_CONNECTED:
+      /*switch (state) {
+        case this.OpenIM.TYPES.NET_STATE_CONNECTED:
           return { message: '已接入网络', type: 'success' }
-        case this.TIM.TYPES.NET_STATE_CONNECTING:
+        case this.OpenIM.TYPES.NET_STATE_CONNECTING:
           return { message: '当前网络不稳定', type: 'warning' }
-        case this.TIM.TYPES.NET_STATE_DISCONNECTED:
+        case this.OpenIM.TYPES.NET_STATE_DISCONNECTED:
           return { message: '当前网络不可用', type: 'error' }
         default:
           return ''
-      }
+      }*/
     },
     onNetStateChange(event) {
       this.$store.commit('showMessage', this.checkoutNetState(event.data.state))
@@ -300,7 +467,7 @@ export default {
     },
     handleVideoMessage(messageList) {
       const videoMessageList = messageList.filter(
-        message => message.type === this.TIM.TYPES.MSG_CUSTOM && this.isJsonStr(message.payload.data)
+        message => message.contentType === 110 && this.isJsonStr(message.payload.data)
       )
       if (videoMessageList.length === 0) return
       const videoPayload = JSON.parse(videoMessageList[0].payload.data)
@@ -375,9 +542,9 @@ export default {
       // 筛选出当前会话的退群/被踢群的 groupTip
       const groupTips = messageList.filter(message => {
         return this.currentConversation.conversationID === message.conversationID &&
-          message.type === this.TIM.TYPES.MSG_GRP_TIP &&
-          (message.payload.operationType === this.TIM.TYPES.GRP_TIP_MBR_QUIT ||
-          message.payload.operationType === this.TIM.TYPES.GRP_TIP_MBR_KICKED_OUT)
+          message.contentType === 1501 &&
+          (message.payload.operationType === 1504 ||
+          message.payload.operationType === 1508)
       })
       // 清理当前会话的群成员列表
       if (groupTips.length > 0) {
@@ -394,7 +561,7 @@ export default {
      */
     handleCloseGroupLive(messageList) {
       messageList.forEach(message => {
-        if (this.currentConversation.conversationID === message.conversationID && message.type === this.TIM.TYPES.MSG_CUSTOM) {
+        if (this.currentConversation.conversationID === message.conversationID && message.contentType === 110) {
           let data = {}
           try {
             data = JSON.parse(message.payload.data)
@@ -447,7 +614,7 @@ body {
   margin-top: 100px;
 }
 
- 
+
 .container
   position relative
   // height 100vh

+ 17 - 5
src/views/login.vue

@@ -6,6 +6,12 @@
       </div>
       <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
         <h3 class="title">互联网医院医生端</h3>
+        <el-form-item>
+          <el-radio-group v-model="loginForm.type">
+            <el-radio-button label="1">医生</el-radio-button>
+            <el-radio-button label="2">药剂师</el-radio-button>
+          </el-radio-group>
+        </el-form-item>
         <el-form-item prop="account">
           <el-input v-model="loginForm.account" type="text" auto-complete="off" placeholder="账号">
             <svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" />
@@ -79,7 +85,8 @@ export default {
         password: "",
         rememberMe: false,
         code: "",
-        uuid: ""
+        uuid: "",
+        type:'1',
       },
       loginRules: {
         account: [
@@ -118,8 +125,10 @@ export default {
     getCookie() {
       const account = Cookies.get("account");
       const password = Cookies.get("password");
-      const rememberMe = Cookies.get('rememberMe')
+      const rememberMe = Cookies.get('rememberMe');
+      // 使用扩展运算符保留原来 loginForm 中的属性,包括 type
       this.loginForm = {
+        ...this.loginForm,
         account: account === undefined ? this.loginForm.account : account,
         password: password === undefined ? this.loginForm.password : decrypt(password),
         rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
@@ -138,7 +147,10 @@ export default {
             Cookies.remove("password");
             Cookies.remove('rememberMe');
           }
-          this.loginForm.type=1;
+          // 记录登录类型以便 Sidebar Logo 动态展示标题
+          try {
+            localStorage.setItem('loginType', this.loginForm.type)
+          } catch (e) {}
           this.$store
             .dispatch("Login", this.loginForm)
             .then((res) => {
@@ -174,8 +186,8 @@ export default {
     overflow: hidden;
     background: #ffffff;
     .img-box{
-      width: 420px;
-      height: 420px;
+      width: 460px;
+      height: 460px;
       img{
         width: 100%;
         height: 100%;

+ 21 - 1
src/views/order/inquiryOrder/index.vue

@@ -115,6 +115,14 @@
                 icon="el-icon-share"
             >继续问诊
           </el-button>
+          <el-button
+            v-if="scope.row.status==1"
+            size="mini"
+            type="text"
+            @click="handleCloseOrder(scope.row)"
+            icon="el-icon-share"
+          >关闭订单
+          </el-button>
           <el-button
             size="mini"
             type="text"
@@ -146,7 +154,7 @@
 </template>
 
 <script>
-import { getOrderUserPhone,getCompanyList,getInquiryOrderList,receiveOrder,refuseOrder } from "@/api/inquiryOrder";
+import { getOrderUserPhone,getCompanyList,getInquiryOrderList,receiveOrder,refuseOrder,closeOrder } from "@/api/inquiryOrder";
 import inquiryOrderDetails from '../../components/order/inquiryOrderDetails.vue';
 import msgDetails from '@/views/components/msg/followMsgDetails.vue';
 import way from '@/utils/way.js';
@@ -230,6 +238,8 @@ export default {
       { dictValue: "2", dictLabel: '待接诊' },
       { dictValue: "3", dictLabel: '问诊中' },
       { dictValue: "4", dictLabel: '已结束' },
+      { dictValue: "1", dictLabel: '待支付' },
+      { dictValue: "-1", dictLabel: '已取消' },
       ],
       inquiryTypeOptions:[],
       inquiryPayOptions:[],
@@ -267,6 +277,16 @@ export default {
     handleContinue(){
       way.$emit('open',1);
     },
+    handleCloseOrder(row){
+      console.log(row)
+      closeOrder(row.orderId).then(response => {
+        this.$message({
+          message: "关闭成功",
+          type: "success"
+        });
+        this.getList();
+      })
+    },
     followMsg(row){
       const userId = row.userId;
       const followDoctorId =row.doctorId;

+ 38 - 30
vue.config.js

@@ -1,13 +1,13 @@
 'use strict'
 const path = require('path')
-
+const WorkerPlugin = require('worker-plugin')
 function resolve(dir) {
   return path.join(__dirname, dir)
 }
 
 const name = process.env.VUE_APP_TITLE || '互联网医院医生端' // 网页标题
 
-const port = process.env.port || process.env.npm_config_port || 80 // 端口
+const port = process.env.port || process.env.npm_config_port || 86 // 端口
 
 // vue.config.js 配置说明
 //官方vue.config.js 参考文档 https://cli.vuejs.org/zh/config/#css-loaderoptions
@@ -42,7 +42,7 @@ module.exports = {
 
       // detail: https://cli.vuejs.org/config/#devserver-proxy
       [process.env.VUE_APP_BASE_API]: {
-        target: `http://localhost:8088`,
+        target: `http://localhost:8088 `,
         changeOrigin: true,
         pathRewrite: {
           ['^' + process.env.VUE_APP_BASE_API]: ''
@@ -60,10 +60,18 @@ module.exports = {
     }
   },
   chainWebpack(config) {
+    // 1. 删除预加载插件
     config.plugins.delete('preload') // TODO: need test
     config.plugins.delete('prefetch') // TODO: need test
 
-    // set svg-sprite-loader
+    // 2. 添加WorkerPlugin
+    config.plugin('worker-plugin')
+      .use(WorkerPlugin, [{
+        globalObject: 'self', // 解决webpack5兼容性问题
+        plugins: ['VueLoaderPlugin'] // 确保Vue loader正常工作
+      }])
+
+    // 3. SVG图标处理配置
     config.module
       .rule('svg')
       .exclude.add(resolve('src/assets/icons'))
@@ -80,6 +88,7 @@ module.exports = {
       })
       .end()
 
+    // 4. 生产环境优化配置
     config
       .when(process.env.NODE_ENV !== 'development',
         config => {
@@ -87,39 +96,38 @@ module.exports = {
             .plugin('ScriptExtHtmlWebpackPlugin')
             .after('html')
             .use('script-ext-html-webpack-plugin', [{
-            // `runtime` must same as runtimeChunk name. default is `runtime`
               inline: /runtime\..*\.js$/
             }])
             .end()
           config
             .optimization.splitChunks({
-              chunks: 'all',
-              cacheGroups: {
-                libs: {
-                  name: 'chunk-libs',
-                  test: /[\\/]node_modules[\\/]/,
-                  priority: 10,
-                  chunks: 'initial' // only package third parties that are initially dependent
-                },
-                elementUI: {
-                  name: 'chunk-elementUI', // split elementUI into a single package
-                  priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
-                  test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
-                },
-                commons: {
-                  name: 'chunk-commons',
-                  test: resolve('src/components'), // can customize your rules
-                  minChunks: 3, //  minimum common number
-                  priority: 5,
-                  reuseExistingChunk: true
-                }
+            chunks: 'all',
+            cacheGroups: {
+              libs: {
+                name: 'chunk-libs',
+                test: /[\\/]node_modules[\\/]/,
+                priority: 10,
+                chunks: 'initial'
+              },
+              elementUI: {
+                name: 'chunk-elementUI',
+                priority: 20,
+                test: /[\\/]node_modules[\\/]_?element-ui(.*)/
+              },
+              commons: {
+                name: 'chunk-commons',
+                test: resolve('src/components'),
+                minChunks: 3,
+                priority: 5,
+                reuseExistingChunk: true
               }
-            })
+            }
+          })
           config.optimization.runtimeChunk('single'),
-          {
-             from: path.resolve(__dirname, './public/robots.txt'), //防爬虫文件
-             to: './' //到根目录下
-          }
+            {
+              from: path.resolve(__dirname, './public/robots.txt'),
+              to: './'
+            }
         }
       )
   },

Деякі файли не було показано, через те що забагато файлів було змінено