chat.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797
  1. <template>
  2. <view>
  3. <image class="page-bg" src="https://cos.his.cdwjyyh.com/fs/20250506/730750385fab4dd5a8a268b1550c81d6.png"
  4. mode="widthFix" style="width: 100%;"></image>
  5. <view class="nav-bar" :style="{height: `calc(88rpx + ${statusBarHeight}px)`}">
  6. <view class="statusBar" :style="{height: statusBarHeight + 'px'}"></view>
  7. <image class="nav-bg" src="https://cos.his.cdwjyyh.com/fs/20250506/730750385fab4dd5a8a268b1550c81d6.png"
  8. mode="widthFix"></image>
  9. <view class="nav-bar-box">
  10. <view class="uni-page-head-hd es-mr-24" @click="$navBack()">
  11. <uni-icons type="left" size="26" color="#000000"></uni-icons>
  12. </view>
  13. <view class="nav-bar-left">
  14. <image
  15. :src="roleInfo.avatar||'https://fs-1319721001.cos.ap-chongqing.myqcloud.com/fs/20240229/be32b8d2ae9f497297d10327656bb43c.png'"
  16. mode="aspectFill"></image>
  17. <view class="nav-bar-head">
  18. <view class="nav-bar-name textOne">{{roleName}}</view>
  19. <view class="textOne" v-show="roleInfo.title">{{roleInfo.title}}</view>
  20. </view>
  21. <!-- <uni-icons type="more-filled" size="26" style="flex-shrink: 0;" color="#000000"></uni-icons> -->
  22. </view>
  23. </view>
  24. </view>
  25. <scroll-view class="msg-scroll" :scroll-top="scrollTop" scroll-y="true" :scroll-with-animation="true">
  26. <view class="container-body" :style="{paddingTop: `calc(100rpx + ${statusBarHeight}px)`}">
  27. <view class="banner-box" v-if="roleInfo.imageUrl||roleInfo.textDescription">
  28. <image :src="roleInfo.imageUrl" mode="aspectFill"></image>
  29. <view class="banner-txt" v-show="roleInfo.textDescription">{{roleInfo.textDescription}}</view>
  30. </view>
  31. <view class="ques-box" v-if="quesList&&quesList.length>0">
  32. <view v-for="(ques,i) in quesList" :key="i" @click="handleQues(ques)">{{ques}}</view>
  33. </view>
  34. <view class="TUI-message-list es-mt-24" @touchstart="handleTouchStart">
  35. <view v-for="(item, index) in historyList" :key="item.id" :id="'view' + item.id">
  36. <view :class="item.type == 1 ? 'msg-item my-msg':'msg-item ai-msg'">
  37. <image v-if="item.type == 1" class="avatar"
  38. :src="userInfo.avatar||'https://fs-1319721001.cos.ap-chongqing.myqcloud.com/fs/20240229/1d7eb0607a074892964dd32e8735e540.jpg'"
  39. mode="aspectFill"></image>
  40. <image v-if="item.type != 1" class="avatar"
  41. :src="roleInfo.avatar||'https://fs-1319721001.cos.ap-chongqing.myqcloud.com/fs/20240229/be32b8d2ae9f497297d10327656bb43c.png'"
  42. mode="aspectFill"></image>
  43. <view>
  44. <view class="msg-text">
  45. <uaMarkdown :source="item.content" :showLine="false" style="font-size: 32rpx" />
  46. </view>
  47. <view class="x-f" style="align-items: center;">
  48. <view v-if="item.type != 1&&item.content" class="x-c copybtn" @click="copyData(item.content)"><u-icon
  49. name="file-text" color="#FF5C03" size="20"
  50. style="margin-right: 5rpx;"></u-icon>复制</view>
  51. </view>
  52. </view>
  53. </view>
  54. </view>
  55. <view v-for="(item, index) in msgs" :key="item.id" :id="'view' + item.id">
  56. <view :class="item.type == 1 ? 'msg-item my-msg':'msg-item ai-msg'">
  57. <image v-if="item.type == 1" class="avatar"
  58. :src="userInfo.avatar||'https://fs-1319721001.cos.ap-chongqing.myqcloud.com/fs/20240229/1d7eb0607a074892964dd32e8735e540.jpg'"
  59. mode="aspectFill"></image>
  60. <image v-if="item.type != 1" class="avatar"
  61. :src="roleInfo.avatar||'https://fs-1319721001.cos.ap-chongqing.myqcloud.com/fs/20240229/be32b8d2ae9f497297d10327656bb43c.png'"
  62. mode="aspectFill"></image>
  63. <view>
  64. <view class="msg-text">
  65. <uaMarkdown :source="item.content" :showLine="false" style="font-size: 32rpx" />
  66. </view>
  67. <view class="x-f" style="align-items: center;">
  68. <view v-if="item.type != 1&&item.content" class="x-c copybtn" @click="copyData(item.content)"><u-icon
  69. name="file-text" color="#FF5C03" size="20"
  70. style="margin-right: 5rpx;"></u-icon>复制</view>
  71. <view style="margin-top: 6px;margin-left: 10rpx;" v-if="item.retry==1" @click="handleInput()">
  72. <u-icon name="reload" color="#999" size="20" labelPos="right" labelSize="24rpx" label="重试"></u-icon>
  73. </view>
  74. </view>
  75. </view>
  76. </view>
  77. </view>
  78. </view>
  79. </view>
  80. </scroll-view>
  81. <view class="chatinput">
  82. <u--input placeholder="请输入想要咨询的问题" border="none" v-model.trim="inputText" clearable
  83. @confirm="handleInputClick"></u--input>
  84. <!-- <image src="/static/images/update_pic_icon.png" mode="aspectFill" class="icon" @click="choosePic">
  85. </image> -->
  86. <image src="/static/images/send_message_icon.png" mode="aspectFill" class="icon2"
  87. :style="{opacity:isFirst==0&&inputText?1:0.7}" @click="handleInputClick"></image>
  88. </view>
  89. </view>
  90. </template>
  91. <script>
  92. import uaMarkdown from "@/components/ua2-markdown/ua-markdown.vue"
  93. import {
  94. getSessionDetailInfo,
  95. replyAppUserContent,
  96. getAiJsonMsgCount
  97. } from "@/api/ai.js"
  98. export default {
  99. components: {
  100. uaMarkdown
  101. },
  102. data() {
  103. return {
  104. sessionId: '',
  105. statusBarHeight: uni.getSystemInfoSync().statusBarHeight,
  106. roleName: '',
  107. chatList: [],
  108. quesList: [],
  109. roleInfo: {},
  110. inputText: '',
  111. triggered: false,
  112. isCompleted: false,
  113. scrollTop: 0,
  114. scrollInto: '', // 用于 scroll-into-view
  115. oldMessageTime: 0,
  116. roleId: null,
  117. msgTimes: null, // 5 分钟无消息定时器
  118. isSocketOpen: false,
  119. socket: null,
  120. isSend: true,
  121. userInfo: {},
  122. doctorId: '',
  123. msgEnd: false,
  124. msgs: [],
  125. source: '',
  126. maxReconnect: 1,
  127. reconnectCount: 2,
  128. reconnecting: false,
  129. needResend: false, // 是否有待重发消息
  130. resendText: '', // 待重发内容
  131. idleClose: false, //是否5分钟自动断开
  132. isFirst: 0,
  133. idleTimer: null,
  134. reportPlaceholderId:'',
  135. replyId:'',
  136. startTime: '',
  137. endTime: '',
  138. deviceId: '',
  139. isRefresh: '',
  140. historyList:[],
  141. isInitReportAi: 0
  142. }
  143. },
  144. onLoad(options) {
  145. this.source = options.source || ''
  146. // if (this.source !== 'healthWeekReport') uni.removeStorageSync('aiSuggestion')
  147. this.roleName = options.roleName || ''
  148. this.roleId = options.roleId || ''
  149. this.userInfo = uni.getStorageSync('userInfo') ? JSON.parse(uni.getStorageSync('userInfo')) : {}
  150. this.getSessionDetailInfo(options.sessionId)
  151. this.sessionId = options.sessionId || null
  152. this.isFirst = this.source == 'healthWeekReport'?1:0
  153. if(this.source == 'healthWeekReport') {
  154. const id = 'report_placeholder_' + Date.now()
  155. this.reportPlaceholderId = id
  156. this.startTime = options.startTime
  157. this.endTime = options.endTime
  158. this.deviceId = options.deviceId
  159. this.isRefresh = options.isRefresh
  160. // this.sessionId = null
  161. this.getAiJsonMsgCount(id)
  162. } else {
  163. this.initSocket(1)
  164. }
  165. uni.onKeyboardHeightChange(this.boardHeightChange);
  166. },
  167. onReady() {
  168. // this.scrollToBottom()
  169. },
  170. onUnload() {
  171. uni.offKeyboardHeightChange(this.boardHeightChange);
  172. if (this.socket) {
  173. this.maxReconnect = 0
  174. this.socket.close();
  175. this.socket = null
  176. }
  177. clearTimeout(this.idleTimer) // 原 clearInterval(this.msgTimes) // 会话结束清占位
  178. },
  179. onBackPress() {
  180. if (this.socket) {
  181. this.maxReconnect = 0
  182. this.socket.close();
  183. this.socket = null
  184. }
  185. clearTimeout(this.idleTimer)
  186. },
  187. methods: {
  188. boardHeightChange:function(res){
  189. this.scrollToBottom()
  190. },
  191. /* ---------- 页面交互 ---------- */
  192. back() {
  193. uni.navigateBack()
  194. },
  195. getAiJsonMsgCount(id) {
  196. const param = {
  197. startTime: this.startTime,
  198. endTime: this.endTime,
  199. message: uni.getStorageSync('aiSuggestion')||'',
  200. userId: this.userInfo.userId,
  201. roleId: this.roleId,
  202. }
  203. getAiJsonMsgCount(param).then(res=>{
  204. if(res.code==200&&res.count<1) {
  205. this.isInitReportAi = 1
  206. this.addMsg(2, '报告解读中...', 1, 0,id)
  207. this.initSocket(1)
  208. }else {
  209. this.isFirst = 0
  210. this.isInitReportAi = 0
  211. this.reportPlaceholderId = ''
  212. }
  213. })
  214. },
  215. handleQues(ques) {
  216. this.inputText = ques
  217. this.handleInputClick()
  218. },
  219. handleInputClick(){
  220. if(this.isFirst==1){
  221. uni.showToast({
  222. title: '报告解读中,请稍后',
  223. icon: 'none'
  224. });
  225. return
  226. }
  227. this.handleInput()
  228. },
  229. handleInput() {
  230. // 1. 输入为空直接 return
  231. if (!this.inputText.trim()&&this.isFirst !=1) return;
  232. // 2. 连接断了 -> 走原有重连逻辑
  233. if (!this.isSocketOpen) {
  234. this.isSend = true
  235. this.idleClose = false
  236. this.trySend();
  237. return;
  238. }
  239. // 3. 连接正常,但对方还在回复
  240. if (!this.isSend) {
  241. uni.showToast({
  242. title: '对方正在回复,请稍等',
  243. icon: 'none'
  244. });
  245. return; // 直接阻断,不继续往下走
  246. }
  247. // 4. 真正发消息
  248. this.trySend();
  249. },
  250. /* ---------- 发送控制 ---------- */
  251. trySend() {
  252. const text = this.isFirst ==1&&this.source=='healthWeekReport' ? uni.getStorageSync('aiSuggestion') :this.inputText.trim() || this.resendText
  253. if (!text) return
  254. // 1. 链路不通 → 立即重连
  255. if (!this.isSocketOpen) {
  256. if (this.reconnecting) {
  257. uni.showToast({
  258. title: '正在重连,请稍候',
  259. icon: 'none'
  260. })
  261. return
  262. }
  263. this.reconnecting = true
  264. uni.showToast({
  265. title: '网络断开,正在重连…',
  266. icon: 'none'
  267. })
  268. if (this.socket) {
  269. this.socket.close();
  270. this.socket = null
  271. }
  272. this.initSocket()
  273. return
  274. }
  275. // 2. 有待重发内容优先发
  276. if (this.needResend) {
  277. this.resendText = ''
  278. this.needResend = false
  279. this.sendMsg(text)
  280. } else {
  281. this.sendMsg(text)
  282. }
  283. },
  284. /* ---------- WebSocket ---------- */
  285. initSocket(type) {
  286. let that = this
  287. if (this.socket) return // 并发短路
  288. const userId = this.userInfo.userId
  289. let url = this.source == 'healthWeekReport' ? '/app/replyAppUserWebSocket/' : '/app/interestAiWebSocket/'
  290. this.socket = uni.connectSocket({
  291. url: getApp().globalData.aiWSUrl + url + userId,
  292. multiple: true,
  293. success: res => {
  294. console.log('WebSocket连接已打开1!', Date.now());
  295. that.isSocketOpen = true
  296. },
  297. error: res => {
  298. console.log(res)
  299. },
  300. })
  301. this.socket.onOpen(() => {
  302. console.log('WebSocket 已打开2', Date.now())
  303. this.isSocketOpen = true
  304. this.reconnectCount = 0
  305. this.reconnecting = false
  306. // console.log("this.isFirst!=1&&this.replyId=",this.isFirst,this.replyId)
  307. if(this.isFirst ==1&&this.source=='healthWeekReport'&&this.isInitReportAi==1) {
  308. // console.log('healthWeekReport')
  309. this.handleInput()
  310. }
  311. if(this.isFirst!=1&&this.replyId){
  312. // console.log("网络异常,请重新发送")
  313. this.isSend = true
  314. this.addMsg(2, '网络异常,请重新发送', 2)
  315. }
  316. })
  317. this.socket.onMessage(res => {
  318. if (!res.data || res.data.trim() === '') return
  319. if(res.data=='[无视]') {
  320. return
  321. }
  322. // console.log('WebSocket onMessage2=========', res)
  323. this.isSend = true
  324. let data = res.data
  325. this.addMsg(2, data, 2)
  326. })
  327. this.socket.onClose(() => {
  328. this.isSocketOpen = false
  329. console.log('WebSocket 已关闭', Date.now())
  330. this.socket = null
  331. this.reconnecting = false
  332. this.isSend = true
  333. if (this.idleTimer) clearTimeout(this.idleTimer)
  334. if(this.isFirst ==1&&this.source=='healthWeekReport') {
  335. this.addMsg(2, '报告解读失败', 2,1,this.reportPlaceholderId)
  336. return
  337. }
  338. if (this.idleClose) return;
  339. if(this.maxReconnect == 0) return;
  340. this.reconnectCount++
  341. console.log("this.reconnectCount",this.reconnectCount)
  342. if (this.reconnectCount <= this.maxReconnect) {
  343. setTimeout(() => this.initSocket(), 5000)
  344. } else {
  345. // if(this.isFirst ==1&&this.source=='healthWeekReport') {
  346. // this.addMsg(2, '报告解读失败', 2,1,this.reportPlaceholderId)
  347. // }
  348. uni.showToast({
  349. title: '网络异常,请手动重试',
  350. icon: 'none',
  351. duration: 3000
  352. })
  353. }
  354. })
  355. this.socket.onError(() => {
  356. console.log('WebSocket 错误', Date.now())
  357. this.isSocketOpen = false
  358. this.socket = null
  359. this.reconnecting = false
  360. this.isSend = true
  361. // this.clearHeart()
  362. if (this.msgTimes) {
  363. clearInterval(this.msgTimes);
  364. this.msgTimes = null
  365. }
  366. })
  367. },
  368. /* ---------- 发送/重试 ---------- */
  369. sendMsg(text) {
  370. if (!this.isSocketOpen || !this.isSend) return
  371. const data = {
  372. userId: this.userInfo.userId,
  373. roleId: this.roleId,
  374. nickName: this.userInfo.nickName,
  375. avatar: this.userInfo.avatar,
  376. roleName: this.roleName,
  377. message: text,
  378. sessionId: this.sessionId,
  379. // isFirst: this.isFirst,//去掉
  380. startTime: this.startTime,
  381. endTime: this.endTime,
  382. deviceId: this.deviceId,
  383. isRefresh: this.isRefresh,
  384. }
  385. // console.log('data报告解读中', JSON.stringify(data))
  386. this.socket.send({
  387. data: JSON.stringify(data),
  388. success: () => {
  389. if(this.isFirst !=1) {
  390. this.addMsg(1, text, 1)
  391. const id = 'replyId_' + Date.now()
  392. this.replyId = id
  393. this.addMsg(2, '正在思考中…', 1, 0)
  394. }
  395. this.isSend = false
  396. this.inputText = '' // 只有成功才清空
  397. },
  398. fail: () => {
  399. // 进入待重发模式
  400. this.needResend = true
  401. this.resendText = text
  402. this.isSend = true
  403. if(this.isFirst ==1&&this.source=='healthWeekReport') {
  404. this.addMsg(2, '报告解读失败', 2,1,this.reportPlaceholderId)
  405. uni.showToast({
  406. title: '发送失,请重试',
  407. icon: 'none'
  408. })
  409. return
  410. }
  411. uni.showToast({
  412. title: '发送失,请重新发送',
  413. icon: 'none'
  414. })
  415. }
  416. })
  417. },
  418. addHistoryList(type, content, inputType, retry = 0,pid) {
  419. // console.log("=addMsg===inputType==",content,inputType)
  420. const id = pid||Date.now() + '_' + Math.random().toString(36).slice(2, 8)
  421. const obj = {
  422. type,
  423. content,
  424. retry,
  425. id
  426. }
  427. this.historyList.push(obj)
  428. this.scrollToBottom()
  429. },
  430. addMsg(type, content, inputType, retry = 0,pid) {
  431. // console.log("=addMsg===inputType==",content,inputType)
  432. const id = pid||Date.now() + '_' + Math.random().toString(36).slice(2, 8)
  433. const obj = {
  434. type,
  435. content,
  436. retry,
  437. id
  438. }
  439. if (inputType == 2) {
  440. if(this.reportPlaceholderId) {
  441. // console.log("===pid",pid)
  442. this.isFirst = pid?1:0
  443. const idx = this.msgs.findIndex(m => m.id == this.reportPlaceholderId)
  444. if (idx > -1) {
  445. this.$set(this.msgs, idx, {
  446. ...this.msgs[idx],
  447. retry:retry,
  448. content:content
  449. })
  450. }
  451. this.reportPlaceholderId = pid?this.reportPlaceholderId:''
  452. } else {
  453. // console.log("=33333==",obj,this.msgs)
  454. if (this.msgs.length) this.msgs.pop();
  455. this.msgs.push(obj);
  456. this.replyId = ''
  457. }
  458. } else if (inputType == 1) {
  459. this.msgs.push(obj)
  460. }
  461. this.scrollToBottom()
  462. // 重置 5 分钟空闲计时
  463. if (this.idleTimer) clearTimeout(this.idleTimer)
  464. this.idleTimer = setTimeout(() => {
  465. this.idleClose = true
  466. if (this.socket) this.socket.close();
  467. }, 5 * 60 * 1000)
  468. },
  469. /* ---------- 初始数据 ---------- */
  470. getSessionDetailInfo(sessionId) {
  471. getSessionDetailInfo(sessionId, this.roleId).then(res => {
  472. if (res.code == 200) {
  473. const chatList = res.chatList || []
  474. this.roleInfo = res.roleInfo || {}
  475. this.roleName = this.roleInfo.roleName || ''
  476. this.quesList = this.roleInfo.wordList ? this.roleInfo.wordList.split('||') : []
  477. // if (this.source !== 'healthWeekReport') {
  478. const list = []
  479. if (this.source !== 'healthWeekReport'&&this.roleInfo.welcomeMessage) {
  480. list.push({
  481. sendType: 2,
  482. content: this.roleInfo.welcomeMessage
  483. })
  484. }
  485. list.push(...chatList)
  486. list.forEach(v => this.addHistoryList(v.sendType, v.content, 1))
  487. // }
  488. } else {
  489. uni.showToast({
  490. title: res.msg,
  491. icon: 'none'
  492. })
  493. this.scrollToBottom()
  494. }
  495. }).catch(() => {
  496. this.scrollToBottom()
  497. })
  498. },
  499. /* ---------- 工具 ---------- */
  500. clearAllTimer() {
  501. clearTimeout(this.idleTimer)
  502. this.idleTimer = null
  503. },
  504. handleTouchStart() {
  505. // uni.hideKeyboard()
  506. // this.$nextTick(() => this.scrollToBottom())
  507. },
  508. /* ---------- 滚动 ---------- */
  509. scrollToBottom() {
  510. setTimeout(() => {
  511. uni.createSelectorQuery()
  512. .in(this)
  513. .select('.msg-scroll') // scroll-view 本身
  514. .boundingClientRect(scrollRect => {
  515. uni.createSelectorQuery()
  516. .in(this)
  517. .select('.container-body') // 内容区
  518. .boundingClientRect(bodyRect => {
  519. if (scrollRect && bodyRect) {
  520. // 内容高 - 滚动容器高 = 需要滚动的距离
  521. const max = bodyRect.height - scrollRect.height;
  522. this.scrollTop = max < 0 ? 0 : max;
  523. }
  524. })
  525. .exec();
  526. })
  527. .exec();
  528. }, 500);
  529. },
  530. /* ---------- 剪切板 ---------- */
  531. copyData(data) {
  532. uni.setClipboardData({
  533. data,
  534. success: () => uni.showToast({
  535. title: '复制成功',
  536. icon: 'none'
  537. }),
  538. fail: () => uni.showToast({
  539. title: '复制失败',
  540. icon: 'none'
  541. })
  542. })
  543. },
  544. }
  545. }
  546. </script>
  547. <style scoped lang="scss">
  548. @mixin u-flex($flexD, $alignI, $justifyC) {
  549. display: flex;
  550. flex-direction: $flexD;
  551. align-items: $alignI;
  552. justify-content: $justifyC;
  553. }
  554. ::v-deep .msg-text {
  555. p {
  556. white-space: pre-line;
  557. }
  558. }
  559. .copybtn {
  560. margin-top: 12rpx;
  561. font-family: PingFang SC, PingFang SC;
  562. font-weight: 500;
  563. font-size: 28rpx;
  564. color: #FF5C03;
  565. background-color: #ffe2d1;
  566. padding: 4rpx 10rpx;
  567. border-radius: 10rpx;
  568. display: inline-flex;
  569. }
  570. .nav-bar {
  571. position: fixed;
  572. z-index: 9999;
  573. top: 0;
  574. left: 0;
  575. width: 100%;
  576. overflow: hidden;
  577. .nav-bg {
  578. width: 100%;
  579. height: 100%;
  580. position: absolute;
  581. left: 0;
  582. top: 0;
  583. z-index: 1;
  584. background-color: #fff;
  585. }
  586. &-box {
  587. position: relative;
  588. padding: 0 24rpx;
  589. @include u-flex(row, center, flex-start);
  590. height: 88rpx;
  591. box-sizing: border-box;
  592. z-index: 3;
  593. }
  594. &-left {
  595. width: 100%;
  596. @include u-flex(row, center, flex-start);
  597. overflow: hidden;
  598. image {
  599. flex-shrink: 0;
  600. width: 64rpx;
  601. height: 64rpx;
  602. border-radius: 12rpx 12rpx 12rpx 12rpx;
  603. }
  604. }
  605. &-name {
  606. font-family: PingFang SC, PingFang SC;
  607. font-weight: 600;
  608. font-size: 28rpx;
  609. color: #222222;
  610. }
  611. &-head {
  612. flex: 1;
  613. overflow: hidden;
  614. margin-left: 22rpx;
  615. margin-right: 22rpx;
  616. font-family: PingFang SC, PingFang SC;
  617. font-weight: 400;
  618. font-size: 23rpx;
  619. color: #999999;
  620. }
  621. }
  622. .page-bg {
  623. position: absolute;
  624. top: 0;
  625. left: 0;
  626. }
  627. .container-body {
  628. position: relative;
  629. padding: 32rpx 30rpx;
  630. z-index: 2;
  631. @include u-flex(column, flex-star, center);
  632. .banner-box {
  633. width: 100%;
  634. min-width: 686rpx;
  635. overflow: hidden;
  636. background: rgba(255, 255, 255, 0.9);
  637. border-radius: 24rpx 24rpx 24rpx 24rpx;
  638. border: 4rpx solid #FFFFFF;
  639. box-sizing: border-box;
  640. image {
  641. width: 100%;
  642. min-width: 690rpx;
  643. height: 264rpx;
  644. background: #f7f7f7;
  645. }
  646. }
  647. .banner-txt {
  648. padding: 32rpx;
  649. font-family: PingFang SC, PingFang SC;
  650. font-weight: 400;
  651. font-size: 32rpx;
  652. color: #333333;
  653. word-break: break-all;
  654. }
  655. .ques-box {
  656. @include u-flex(row, flex-star, flex-star);
  657. flex-wrap: wrap;
  658. margin: 22rpx -8rpx -10rpx;
  659. view {
  660. padding: 12rpx 36rpx;
  661. background: #FFFFFF;
  662. border-radius: 16rpx 16rpx 16rpx 16rpx;
  663. border: 1rpx solid #ECECEC;
  664. font-family: PingFang SC, PingFang SC;
  665. font-weight: 500;
  666. font-size: 28rpx;
  667. color: #333333;
  668. margin: 10rpx 8rpx;
  669. }
  670. }
  671. }
  672. .chatinput {
  673. position: fixed;
  674. left: 32rpx;
  675. right: 32rpx;
  676. z-index: 999;
  677. bottom: calc(var(--window-bottom) + 24rpx);
  678. height: 96rpx;
  679. background-color: green;
  680. background: #FFFFFF;
  681. box-shadow: 0rpx 8rpx 21rpx 0rpx rgba(0, 0, 0, 0.1);
  682. border-radius: 24rpx 24rpx 24rpx 24rpx;
  683. @include u-flex(row, center, center);
  684. padding: 0 24rpx;
  685. box-sizing: border-box;
  686. .icon {
  687. height: 48rpx;
  688. width: 48rpx;
  689. margin-left: 32rpx;
  690. }
  691. .icon2 {
  692. height: 56rpx;
  693. width: 56rpx;
  694. margin-left: 32rpx;
  695. }
  696. }
  697. .msg-scroll {
  698. height: calc(100vh - var(--window-bottom) - 120rpx);
  699. }
  700. .TUI-message-list {
  701. width: 100%;
  702. box-sizing: border-box;
  703. .time-container {
  704. font-family: PingFang SC, PingFang SC;
  705. font-weight: 400;
  706. font-size: 24rpx;
  707. color: #999999;
  708. padding: 36rpx 0;
  709. margin: 10px;
  710. text-align: center;
  711. }
  712. .avatar {
  713. flex-shrink: 0;
  714. width: 88rpx;
  715. height: 88rpx;
  716. background: #FFFFFF;
  717. border-radius: 12rpx 12rpx 12rpx 12rpx;
  718. }
  719. .msg-item {
  720. margin-bottom: 24rpx;
  721. }
  722. .ai-msg {
  723. @include u-flex(row, flex-start, flex-start);
  724. .avatar {
  725. margin-right: 24rpx;
  726. }
  727. .msg-text {
  728. border-radius: 0rpx 24rpx 24rpx 24rpx;
  729. }
  730. }
  731. .my-msg {
  732. @include u-flex(row-reverse, flex-start, flex-start);
  733. .avatar {
  734. margin-left: 24rpx;
  735. }
  736. .msg-text {
  737. border-radius: 24rpx 0 24rpx 24rpx;
  738. background: #FEC75C;
  739. }
  740. }
  741. .msg-text {
  742. padding: 24rpx 24rpx 0 24rpx;
  743. background: #FFFFFF;
  744. overflow: hidden;
  745. }
  746. }
  747. </style>