consultation.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588
  1. <template>
  2. <view class="container">
  3. <view class="head-box">
  4. <view class="chose-patient">
  5. <view class="title-box" @click="addPatient()" v-if="patient==null">
  6. <text class="title">选择就诊人</text>
  7. <view class="right">
  8. <text class="value">请点击添加</text>
  9. <image src="/static/images/arrow_gray.png" mode=""></image>
  10. </view>
  11. </view>
  12. <view class="patient" @click="addPatient()" v-if="patient!=null">
  13. <view class="left">
  14. <view class="name">{{patient.patientName}}</view>
  15. <view class="info">
  16. <text class="text" v-if="patient.sex==1">男</text>
  17. <text class="text" v-if="patient.sex==2">女</text>
  18. <text class="text">{{$getAge(patient.birthday)}}岁</text>
  19. <text class="text">{{$parseIdCard(patient.idCard)}}</text>
  20. </view>
  21. </view>
  22. <view class="right">
  23. <image src="/static/images/arrow_gray.png" mode=""></image>
  24. </view>
  25. </view>
  26. </view>
  27. <view class="aitips">欢迎使用我的智能体。但我的智能体诊断和建议不能全权替代我本人诊断结论,仅供参考</view>
  28. </view>
  29. <scroll-view class="msg-scroll" :style="{height: height}" :scroll-top="scrollTop" scroll-y="true"
  30. :scroll-with-animation="true">
  31. <view class="msgs">
  32. <view class="msg-item" v-for="(item,index) in msgs">
  33. <view class="left" v-if="item.type==1">
  34. <image class="img" src="@/static/images/AI_Doctor_image_font_icon48.png"></image>
  35. <view class="msg-box">
  36. <view class="msg-content">{{item.content}}</view>
  37. <!-- <view class="msg-btn-box">
  38. <view class="btn" @click="editMsgStatus(item)"
  39. v-if="item.msg!=null&&item.msg.status==0">回答不满意</view>
  40. <view class="btn" v-if="item.msg!=null" @click="copyMsg(item.content)">复制内容</view>
  41. </view> -->
  42. </view>
  43. </view>
  44. <view class="right" v-if="item.type==2">
  45. <view class="msg-content">{{item.content}}</view>
  46. <image class="img" :src="avatar"></image>
  47. </view>
  48. </view>
  49. <!-- <view class="msgEnd" v-show="msgEnd"><u-divider text="会话结束" :hairline="true"></u-divider></view> -->
  50. </view>
  51. </scroll-view>
  52. <view class="footer x-bc">
  53. <u-input placeholder="请输入想要咨询的问题..." border="surround" v-model="inputText" @confirm="handleInput"></u-input>
  54. <view class="send-box">
  55. <view class="send-btn x-ac" @click="handleInput()">发送</view>
  56. </view>
  57. </view>
  58. </view>
  59. </template>
  60. <script>
  61. import { aiChatListByUser } from "@/api/index.js"
  62. export default {
  63. data() {
  64. return {
  65. avatar: '',
  66. patient: null,
  67. statusBarHeight: uni.getSystemInfoSync().statusBarHeight,
  68. windowHeight: uni.getSystemInfoSync().screenHeight,
  69. height: '50vh',
  70. scrollTop: 0,
  71. msgs: [],
  72. inputText:"",
  73. roleId:null,
  74. msgTimes:null,
  75. isSocketOpen:false,
  76. socket:null,
  77. isSend:true,
  78. userInfo: {},
  79. doctorId: '',
  80. sessionId: undefined,
  81. msgEnd: false,
  82. doctorName: ""
  83. }
  84. },
  85. onLoad(option) {
  86. this.doctorName = option.doctorName || ''
  87. uni.setNavigationBarTitle({
  88. title: this.doctorName + "(智能体)"
  89. })
  90. this.doctorId = option.doctorId || ''
  91. this.height =
  92. `calc(${this.windowHeight}px - ${this.statusBarHeight}px - 196px)`
  93. this.roleId=option.roleId;
  94. this.roleName=option.roleName;
  95. this.userInfo = uni.getStorageSync("userInfo") ? JSON.parse(uni.getStorageSync("userInfo")) : {}
  96. this.avatar= this.userInfo.avatar || '';
  97. this.getMsgList();
  98. this.initSocket();
  99. uni.$on('refreshOrderPatient', (res) => {
  100. if(this.patient&&this.patient.patientId == res.patientId) {
  101. return
  102. }
  103. this.msgEnd = false
  104. this.patient = res
  105. this.getMsgList();
  106. })
  107. },
  108. onShow() {
  109. this.userInfo = uni.getStorageSync("userInfo") ? JSON.parse(uni.getStorageSync("userInfo")) : {}
  110. },
  111. methods: {
  112. // getTips() {
  113. // const that = this
  114. // uni.showModal({
  115. // title: '风险提示',
  116. // content: '内容由AI生成,仅供您参考使用,不作为疾病诊断治疗依据',
  117. // showCancel: false,
  118. // success: function (res) {
  119. // if (res.confirm) {
  120. // that.getMsgList();
  121. // that.initSocket();
  122. // }
  123. // }
  124. // });
  125. // },
  126. getHeight() {
  127. const query = uni.createSelectorQuery().in(this);
  128. query
  129. .select(".head-box")
  130. .boundingClientRect((data) => {
  131. this.height =
  132. `calc(${this.windowHeight}px - ${this.statusBarHeight}px - 94px - ${data.height}px)`
  133. })
  134. .exec();
  135. },
  136. addPatient() {
  137. uni.navigateTo({
  138. url: '/pages/user/patient'
  139. })
  140. },
  141. handleInput() {
  142. if(this.patient && this.patient.patientId) {
  143. if(this.msgEnd || !this.isSocketOpen) {
  144. // 重新发起会话
  145. this.initSocket()
  146. } else {
  147. this.sendMsg();
  148. }
  149. } else {
  150. uni.showToast({
  151. title: '请选择就诊人',
  152. icon: "none"
  153. })
  154. }
  155. },
  156. getMsgList() {
  157. if(this.patient && this.patient.patientId) {
  158. const that = this;
  159. const userId = this.userInfo.userId;
  160. const param = {
  161. doctorId: this.doctorId,
  162. userId: this.userInfo.userId,
  163. patientId: this.patient.patientId,
  164. }
  165. aiChatListByUser(param).then(res=>{
  166. setTimeout(()=>{
  167. this.getHeight()
  168. },200)
  169. let list = [{
  170. msgType: 2,
  171. content: '您好,我是'+this.doctorName+'医生智能体。我学习了海量的医学书籍、药品知识以及临床病例,我可以为您提供健康咨询、营养指导及医疗信息参考,如果您有健康方面的问题,可以随时向我咨询。'
  172. }]
  173. if(res.data&&res.data.length > 0) {
  174. list = list.concat(res.data)
  175. that.sessionId = res.data&&res.data.length > 0 ? res.data[0].sessionId : undefined
  176. }
  177. list.forEach(function(value, index, array) {
  178. that.addMsg(value.msgType == 1 ? 2 : 1, value.content, value, 1);
  179. });
  180. }).catch(()=>{
  181. setTimeout(()=>{
  182. this.getHeight()
  183. },200)
  184. })
  185. }
  186. },
  187. initSocket() {
  188. //创建一个socket连接
  189. var userId = this.userInfo.userId;
  190. var that = this;
  191. if (this.socket) {
  192. this.socket.close()
  193. this.socket = null;
  194. }
  195. this.socket = uni.connectSocket({
  196. url: getApp().globalData.wsUrl + "/app/webSocket/" + userId,
  197. multiple: true,
  198. success: res => {
  199. console.log('WebSocket连接已打开1!');
  200. that.isSocketOpen = true
  201. },
  202. error: res => {
  203. console.log(res)
  204. },
  205. })
  206. this.socket.onMessage((res) => {
  207. console.log("收到消息",res)
  208. that.isSend = true;
  209. that.addMsg(1, res.data, null, 2);
  210. })
  211. //监听socket打开
  212. this.socket.onOpen(() => {
  213. console.log('WebSocket连接已打开2!',that.msgEnd);
  214. that.isSocketOpen = true
  215. if(that.msgEnd) {
  216. // 重新发起会话
  217. that.isSend = true;
  218. that.sendMsg();
  219. }
  220. })
  221. //监听socket关闭
  222. this.socket.onClose(() => {
  223. that.isSocketOpen = false
  224. that.socket = null
  225. console.log('WebSocket连接已关闭!');
  226. that.msgEnd = true
  227. setTimeout(()=> {
  228. const query = uni.createSelectorQuery().in(this);
  229. query
  230. .select(".msgs")
  231. .boundingClientRect((res) => {
  232. if(res&&res.height) {
  233. const scrollH = res.height;
  234. that.scrollTop = scrollH;
  235. }
  236. })
  237. .exec();
  238. }, 500)
  239. if(that.msgTimes) {
  240. clearInterval(that.msgTimes)
  241. that.msgTimes = null
  242. }
  243. })
  244. //监听socket错误
  245. this.socket.onError(() => {
  246. that.isSocketOpen = false
  247. that.socket = null
  248. that.msgEnd = true
  249. console.log('WebSocket连接打开失败');
  250. if(that.msgTimes) {
  251. clearInterval(that.msgTimes)
  252. that.msgTimes = null
  253. }
  254. })
  255. },
  256. sendMsg() {
  257. if (this.inputText == "") {
  258. return;
  259. }
  260. if (!this.isSend) {
  261. return;
  262. }
  263. if (this.isSocketOpen) {
  264. var userId = this.userInfo.userId;
  265. var data = {
  266. userId: this.userInfo.userId,
  267. doctorId: this.doctorId,
  268. message: this.inputText,
  269. sessionId: this.sessionId,
  270. patientId: this.patient.patientId
  271. };
  272. this.socket.send({
  273. data: JSON.stringify(data),
  274. success: () => {
  275. console.log("发送成功")
  276. this.addMsg(2, this.inputText, null, 1);
  277. this.addMsg(1, "正在思考中...", null, 1);
  278. this.isSend = false;
  279. },
  280. fail: () => {
  281. console.log("发送失败")
  282. }
  283. });
  284. }
  285. },
  286. addMsg(type, content, msg, inputType) {
  287. this.msgEnd = false
  288. var obj = {
  289. type: type,
  290. content: content,
  291. msg: msg
  292. }
  293. if (inputType == 2) {
  294. this.msgs.splice(-1);
  295. this.msgs.push(obj);
  296. } else if (inputType == 1) {
  297. this.msgs.push(obj)
  298. }
  299. this.inputText = ""
  300. var that = this;
  301. setTimeout(()=> {
  302. const query = uni.createSelectorQuery().in(this);
  303. query
  304. .select(".msgs")
  305. .boundingClientRect((res) => {
  306. if(res&&res.height) {
  307. const scrollH = res.height;
  308. that.scrollTop = scrollH;
  309. }
  310. })
  311. .exec();
  312. }, 500)
  313. //先确保清除了之前的消息定时器
  314. if(that.msgTimes) {
  315. clearInterval(that.msgTimes)
  316. that.msgTimes = null
  317. }
  318. // 5分钟无消息自动结束
  319. that.msgTimes=setInterval(()=>{
  320. console.log("5分钟无消息自动结束")
  321. clearInterval(that.msgTimes)
  322. if(this.socket!=null){
  323. this.socket.close()
  324. }
  325. that.msgTimes = null
  326. },300000)
  327. },
  328. },
  329. onUnload() {
  330. this.msgEnd = true
  331. if(this.socket!=null){
  332. this.socket.close()
  333. }
  334. if(this.msgTimes) {
  335. clearInterval(this.msgTimes)
  336. this.msgTimes = null
  337. }
  338. uni.$off('refreshOrderPatient')
  339. }
  340. }
  341. </script>
  342. <style lang="scss" scoped>
  343. .head-box {
  344. padding: 15rpx 15rpx 0 15rpx;
  345. }
  346. .chose-patient {
  347. // margin: 15rpx 15rpx;
  348. margin-bottom: 15rpx;
  349. padding: 30rpx 40rpx;
  350. box-shadow: 0px 0px 5px 2px rgba(0, 0, 0, 0.05);
  351. background-color: #fff;
  352. border-radius: 15rpx;
  353. .title-box {
  354. display: flex;
  355. align-items: center;
  356. justify-content: space-between;
  357. .title {
  358. font-size: 32upx;
  359. font-family: PingFang SC;
  360. font-weight: bold;
  361. color: #111111;
  362. }
  363. .right {
  364. height: 100%;
  365. display: flex;
  366. align-items: center;
  367. justify-content: center;
  368. .value {
  369. font-size: 28upx;
  370. font-family: PingFang SC;
  371. color: #999;
  372. margin-right: 10rpx;
  373. }
  374. image {
  375. width: 15upx;
  376. height: 30upx;
  377. }
  378. }
  379. }
  380. .patient {
  381. display: flex;
  382. align-items: center;
  383. justify-content: space-between;
  384. height: 110upx;
  385. .left {
  386. .name {
  387. font-size: 30upx;
  388. line-height: 1;
  389. font-family: PingFang SC;
  390. font-weight: bold;
  391. color: #111111;
  392. }
  393. .info {
  394. margin-top: 30rpx;
  395. display: flex;
  396. align-items: center;
  397. .text {
  398. font-size: 26upx;
  399. font-family: PingFang SC;
  400. line-height: 1;
  401. font-weight: 500;
  402. color: #999;
  403. margin-right: 19upx;
  404. }
  405. }
  406. }
  407. .right {
  408. display: flex;
  409. align-items: center;
  410. image {
  411. width: 15upx;
  412. height: 30upx;
  413. }
  414. }
  415. }
  416. }
  417. .aitips {
  418. font-size: 24rpx;
  419. font-family: PingFang SC;
  420. color: #999;
  421. text-align: center;
  422. padding: 20rpx;
  423. box-sizing: border-box;
  424. }
  425. .msgEnd {
  426. padding: 20rpx 100rpx;
  427. }
  428. .footer {
  429. padding: 10rpx;
  430. width: 100%;
  431. background-color: #fff;
  432. padding-bottom: calc(var(--window-bottom) + 10rpx);
  433. position: fixed;
  434. bottom: 0;
  435. left: 0;
  436. .send-btn {
  437. margin-left: 20rpx;
  438. padding: 20rpx 40rpx;
  439. min-height: 76rpx;
  440. border-radius: 10rpx;
  441. box-sizing: border-box;
  442. font-family: PingFang SC, PingFang SC;
  443. font-weight: 500;
  444. font-size: 30rpx;
  445. color: #FFFFFF;
  446. background-color: #FF5C03;
  447. }
  448. }
  449. .msg-scroll {
  450. height: calc(100vh - 120px);
  451. .msgs {
  452. width: 100%;
  453. .msg-item {
  454. padding: 15rpx;
  455. box-sizing: border-box;
  456. display: flex;
  457. flex-direction: column;
  458. justify-content: center;
  459. align-items: flex-start;
  460. width: 100%;
  461. .left {
  462. width: 100%;
  463. display: flex;
  464. justify-content: flex-start;
  465. align-items: flex-start;
  466. .img {
  467. min-width: 50px;
  468. width: 50px;
  469. height: 50px;
  470. border-radius: 50%;
  471. image {
  472. width: 100%;
  473. height: 100%;
  474. }
  475. }
  476. .msg-box {
  477. max-width: 80%;
  478. // margin-top: 10px;
  479. .msg-content {
  480. max-width: 100%;
  481. border-radius: 0 10px 10px 10px;
  482. padding: 10px;
  483. margin-top: 15px;
  484. margin-left: 10px;
  485. background-color: #fff;
  486. color: #111;
  487. font-size: 16px;
  488. font-family: PingFang SC;
  489. word-wrap: break-word;
  490. /* 旧版浏览器支持 */
  491. overflow-wrap: break-word;
  492. /* 现代浏览器支持 */
  493. white-space: normal;
  494. /* 确保连续文本不会超出容器 */
  495. }
  496. .msg-btn-box {
  497. max-width: 100%;
  498. margin-top: 10px;
  499. display: flex;
  500. justify-content: flex-start;
  501. align-items: flex-start;
  502. .btn {
  503. margin-right: 15px;
  504. display: flex;
  505. justify-content: center;
  506. align-items: center;
  507. border-radius: 10px;
  508. padding: 10px 20px;
  509. background-color: #fff;
  510. color: #FF5C03;
  511. font-size: 14px;
  512. }
  513. }
  514. }
  515. }
  516. .right {
  517. width: 100%;
  518. display: flex;
  519. justify-content: flex-end;
  520. align-items: flex-start;
  521. .msg-content {
  522. max-width: 80%;
  523. margin-top: 15px;
  524. margin-right: 10px;
  525. border-radius: 10px 0 10px 10px;
  526. padding: 10px;
  527. background-color: #FF5C03;
  528. color: #fff;
  529. font-size: 16px;
  530. font-family: PingFang SC;
  531. word-wrap: break-word;
  532. /* 旧版浏览器支持 */
  533. overflow-wrap: break-word;
  534. /* 现代浏览器支持 */
  535. white-space: normal;
  536. /* 确保连续文本不会超出容器 */
  537. }
  538. .img {
  539. min-width: 50px;
  540. width: 50px;
  541. height: 50px;
  542. border-radius: 50%;
  543. image {
  544. width: 100%;
  545. height: 100%;
  546. }
  547. }
  548. }
  549. }
  550. }
  551. }
  552. </style>