ai.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642
  1. <template>
  2. <view>
  3. <!-- <view class="tips color_fff size_12 align_c" :class="{ 'show':ajax.loading }" @tap="getHistoryMsg">{{ajax.loadText}}</view> -->
  4. <!-- <image @click="openpopup" class="history" src="/static/img/ai.png"></image> -->
  5. <view class="box-1">
  6. <view class="talk-list">
  7. <view v-for="(item,index) in talkList" :key="index" :id="`msg-${item.id}`">
  8. <view class="item flex_col" :class=" item.type == 1 ? 'push':'pull' ">
  9. <image v-if="item.pic" :src="item.pic" mode="aspectFill" class="pic"></image>
  10. <view class="content">{{item.content}}</view>
  11. </view>
  12. </view>
  13. </view>
  14. </view>
  15. <view class="box-2">
  16. <view class="flex_col" :style="{position: 'relative',bottom:inputHeight+'px'}">
  17. <image @click="openpopup" class="history" src="/static/img/ai.png"></image>
  18. <view class="flex_grow">
  19. <input @confirm="send" :adjust-position="false" @focus="inputBindFocus"
  20. @blur="inputBindBlur" type="text" class="content" v-model="content"
  21. placeholder="请输入聊天内容" placeholder-style="color:#DDD;"
  22. :cursor-spacing="6" @click="$content_check.checkLogin" :disabled="$content_check.isDisabled"
  23. />
  24. </view>
  25. <button class="send" @tap="send">发送</button>
  26. </view>
  27. </view>
  28. <uv-popup ref="popup" mode="left" bgColor="#f5f5f5" style="overflow-y: scroll;" >
  29. <scroll-view class="popup scroll-Y" scroll-y="true">
  30. <view class="chart-con">
  31. <view class="chart-wrap">
  32. <view v-if="ai_talk_history && Object.keys(ai_talk_history).length > 0" class="flex justify-between">
  33. <view class="title">
  34. <view class="ver-line"></view>
  35. <text>AI 问诊记录</text>
  36. </view>
  37. <button @click="check_delAllHistoryMsg" class="del-button button-all center">删除所有记录</button>
  38. </view>
  39. <view v-else class="no-data center">暂无数据</view>
  40. <uv-gap height="10"></uv-gap>
  41. <view @longpress="check_delHistoryMsg(key)" @click="getHistoryMsg(key)" class="history_item" v-if="refresh" v-for="(value, key) in ai_talk_history" :key="key">
  42. <view>
  43. <text class="block">{{value['timeString']}}</text>
  44. <div class="block talk-text"><text style="color: #99d7ff;">{{name}}:</text>{{value['talkList'][1]['content']}}</div>
  45. <view class="divider"></view>
  46. <div class="block talk-text"><text style="color: #adecaf;">AI:</text>{{value['talkList'][2]['content']}}</div>
  47. <view class="divider"></view>
  48. </view>
  49. </view>
  50. <view v-else style="height: 3000px;"></view>
  51. <!-- {{ai_talk_history}} -->
  52. </view>
  53. </view>
  54. <button @click="new_talk" class="button-all center new-button">新建对话</button>
  55. </scroll-view>
  56. </uv-popup>
  57. </view>
  58. </template>
  59. <script>
  60. export default {
  61. onPullDownRefresh() {
  62. setTimeout(() => {uni.stopPullDownRefresh();}, 1000);
  63. },
  64. onShow() {
  65. // 获取全局变量
  66. const ai_history = uni.getStorageSync('ai_history');
  67. if(ai_history){
  68. uni.setStorageSync('ai_history', false);
  69. this.openpopup();
  70. }
  71. // 设置医生性别
  72. let doctor_gender = uni.getStorageSync('doctor_gender');
  73. if(doctor_gender === 0) this.doctor_gender = 0;
  74. else this.doctor_gender = 1;
  75. // 设置用户名
  76. this.name = uni.getStorageSync("name");
  77. },
  78. data() {
  79. return {
  80. doctor_woman: '/static/img/女医生.png',
  81. doctor_man: '/static/img/男医生.png',
  82. doctor_gender: 1,
  83. name: '微信用户',
  84. content: '',
  85. talkList: [],
  86. ai_talk_history: null,
  87. timestamp: null,
  88. request: 1,
  89. // ajax:{
  90. // rows:20, //每页数量
  91. // page:1, //页码
  92. // flag:true, // 请求开关
  93. // loading:true, // 加载中
  94. // loadText:'正在获取消息'
  95. // },
  96. inputHeight: 0,
  97. refresh: true,
  98. }
  99. },
  100. mounted() {
  101. this.new_talk();
  102. // this.$nextTick(()=>{
  103. // this.getHistoryMsg();
  104. // });
  105. },
  106. onPageScroll(e){
  107. // if(e.scrollTop<5){
  108. // this.getHistoryMsg();
  109. // }
  110. },
  111. methods: {
  112. new_talk(){
  113. this.timestamp = Date.now();
  114. this.talkList = [{"id":1,"content":"请问有什么需要帮助的吗?"
  115. ,"type":0,"pic":this.doctor_gender == 1 ? this.doctor_woman : this.doctor_man}];
  116. },
  117. openpopup(){
  118. this.$refs.popup.open();
  119. this.ai_talk_history = this.$util.reversedObject(uni.getStorageSync('ai_talk_history'));
  120. },
  121. inputBindFocus(e) {
  122. if(e.detail.height) {
  123. this.inputHeight = e.detail.height - this.getBottomBarHeight(); //这个高度就是软键盘的高度
  124. console.log('this.inputHeight: ',this.inputHeight);
  125. }
  126. },
  127. inputBindBlur(){
  128. this.inputHeight = 0;
  129. this.$content_check.textCheck(this.content, this);
  130. },
  131. getBottomBarHeight() {
  132. let res = uni.getSystemInfoSync();
  133. const windowHeight = res.windowHeight; // 屏幕可用高度
  134. const screenHeight = res.screenHeight; // 屏幕总高度
  135. let bottomBarHeight = screenHeight - windowHeight - getTopHeight();
  136. bottomBarHeight = bottomBarHeight > 0 ? bottomBarHeight : 0;
  137. console.log('Bottom Bar Height:',bottomBarHeight);
  138. return bottomBarHeight;
  139. function getTopHeight(){
  140. // 状态栏高度
  141. const statusBarHeight = uni.getSystemInfoSync().statusBarHeight
  142. // #ifdef MP-WEIXIN
  143. // 获取微信胶囊的位置信息 width,height,top,right,left,bottom
  144. const custom = wx.getMenuButtonBoundingClientRect()
  145. // console.log(custom)
  146. // 导航栏高度(标题栏高度) = 胶囊高度 + (顶部距离 - 状态栏高度) * 2
  147. const navigationBarHeight = custom.height + (custom.top - statusBarHeight) * 2
  148. // console.log("导航栏高度:"+navigationBarHeight)
  149. // 总体高度 = 状态栏高度 + 导航栏高度
  150. const navHeight = navigationBarHeight + statusBarHeight
  151. // #endif
  152. console.log(navHeight);
  153. return navHeight;
  154. }
  155. },
  156. check_delAllHistoryMsg(){
  157. uni.showModal({
  158. title: '清除历史纪录',
  159. content: '是否要清除全部历史纪录?',
  160. success: (res) => {
  161. if (res.confirm)
  162. this.delAllHistoryMsg();
  163. }
  164. })
  165. },
  166. delAllHistoryMsg(){
  167. this.new_talk();
  168. this.ai_talk_history = null;
  169. uni.setStorageSync('ai_talk_history', {});
  170. },
  171. check_delHistoryMsg(key){
  172. uni.showModal({
  173. title: '删除历史纪录',
  174. content: '是否要删除此历史纪录?',
  175. success: (res) => {
  176. if (res.confirm)
  177. this.delHistoryMsg(key);
  178. }
  179. })
  180. },
  181. delHistoryMsg(key){
  182. if(this.timestamp == key) this.new_talk();
  183. let ai_talk_history = uni.getStorageSync('ai_talk_history');
  184. delete ai_talk_history[key];
  185. this.ai_talk_history = ai_talk_history;
  186. uni.setStorageSync('ai_talk_history', ai_talk_history);
  187. },
  188. // 获取历史消息
  189. getHistoryMsg(key){
  190. console.log('key: ',key);
  191. this.timestamp = key;
  192. this.talkList = this.ai_talk_history[key]['talkList'];
  193. this.$refs.popup.close();
  194. },
  195. // 设置页面滚动位置
  196. setPageScrollTo(selector){
  197. let view = uni.createSelectorQuery().in(this).select(selector);
  198. view.boundingClientRect((res) => {
  199. uni.pageScrollTo({
  200. scrollTop:res.top - 30, // -30 为多显示出大半个消息的高度,示意上面还有信息。
  201. duration: 0
  202. });
  203. }).exec();
  204. },
  205. // 隐藏加载提示
  206. hideLoadTips(flag){
  207. if(flag){
  208. this.ajax.loadText = '消息获取成功';
  209. setTimeout(()=>{
  210. this.ajax.loading = false;
  211. },300);
  212. }else{
  213. this.ajax.loading = true;
  214. this.ajax.loadText = '正在获取消息';
  215. }
  216. },
  217. // 发送信息
  218. async send(){
  219. if(!this.content){
  220. uni.showToast({
  221. title:'请输入有效的内容',
  222. icon:'none'
  223. });
  224. return ;
  225. }
  226. if(this.request == 0){
  227. uni.showToast({duration:1000,icon:'none',title: '消息发送中,请稍等...'});
  228. setTimeout(() => uni.showLoading({title:'正在发送...'}), 1500);
  229. return ;
  230. }
  231. let isCheck = await this.$content_check.textCheck(this.content, this);
  232. console.log('isCheck: ',isCheck);
  233. if(isCheck !== true) return ;
  234. this.request = 0;
  235. uni.showLoading({title:'正在发送...'});
  236. // 将当前发送信息 添加到消息列表。
  237. let data = {
  238. "id":new Date().getTime(),
  239. "content":this.content,
  240. "type":1,
  241. "pic":null
  242. }
  243. this.talkList.push(data);
  244. let content = this.content;
  245. this.$nextTick(()=>{
  246. // 清空内容框中的内容
  247. this.content = '';
  248. uni.pageScrollTo({
  249. scrollTop: 999999, // 设置一个超大值,以保证滚动条滚动到底部
  250. duration: 500
  251. });
  252. });
  253. send_message_ai(content).then(res => {
  254. console.log('send: res: ',res);
  255. let data = {
  256. "id":new Date().getTime(),
  257. "content":res,
  258. "type":0,
  259. "pic":this.doctor_gender == 1 ? this.doctor_woman : this.doctor_man
  260. }
  261. this.talkList.push(data);
  262. uni.hideLoading();
  263. uni.pageScrollTo({
  264. scrollTop: 999999, // 设置一个超大值,以保证滚动条滚动到底部
  265. duration: 500
  266. });
  267. this.request = 1;
  268. this.save_talk(this.talkList, this.timestamp);
  269. }).catch(err => {
  270. console.log('send: err: ',err);
  271. uni.hideLoading();
  272. this.imgshow = false;
  273. this.request = 1;
  274. });
  275. },
  276. save_talk(talkList, timestamp){
  277. let timeString = this.$util.formatDateTime(timestamp);
  278. let talkData = {talkList, timeString};
  279. let ai_talk_history = uni.getStorageSync('ai_talk_history');
  280. if(ai_talk_history) ai_talk_history[timestamp] = talkData;
  281. else{
  282. ai_talk_history = {};
  283. ai_talk_history[timestamp] = talkData;
  284. }
  285. uni.setStorageSync('ai_talk_history', ai_talk_history);
  286. }
  287. }
  288. }
  289. </script>
  290. <style lang="scss">
  291. @import "talk.scss";
  292. page{
  293. background-color: #f5f5f5;
  294. font-size: 28rpx;
  295. }
  296. .scroll-Y {
  297. /* #ifndef MP */
  298. height: calc(100vh - 155px);
  299. /* #endif */
  300. /* #ifdef MP */
  301. height: calc(100vh - 65px);
  302. /* #endif */
  303. }
  304. .popup{
  305. padding: 10px;
  306. width: 75vw;
  307. .new-button{
  308. color: #666;
  309. position: fixed;
  310. bottom: 15px;
  311. background-color: #d4f4c2;
  312. height: 50px;
  313. width: calc(100% - 20px);
  314. font-size: 15px;
  315. font-weight: 550;
  316. border-radius: 25px;
  317. }
  318. .history_item{
  319. margin-bottom: 10px;
  320. background-color: #fff;
  321. border-radius: 20px;
  322. padding: 10px 20px 20px;
  323. color: #666;
  324. font-size: 14px;
  325. text{
  326. margin: 10px 0;
  327. }
  328. .talk-text{
  329. margin: 10px 0 5px;
  330. color: #565;
  331. font-weight: 550;
  332. font-size: 18px;
  333. overflow-wrap: break-word;
  334. text-overflow: ellipsis;
  335. overflow: hidden;
  336. white-space: nowrap;
  337. }
  338. }
  339. .history_item:active{
  340. opacity: 0.5;
  341. }
  342. }
  343. .history{
  344. width: 32px;
  345. height: 32px;
  346. margin-right: 5px;
  347. }
  348. .history:active {
  349. opacity: 0.5;
  350. }
  351. /* 加载数据提示 */
  352. .tips{
  353. position: fixed;
  354. left: 0;
  355. top:var(--window-top);
  356. width: 100%;
  357. z-index: 9;
  358. color: #333;
  359. // background-color: rgba(0,0,0,0.15);
  360. height: 72rpx;
  361. line-height: 72rpx;
  362. transform:translateY(-80rpx);
  363. transition: transform 0.3s ease-in-out 0s;
  364. &.show{
  365. transform:translateY(0);
  366. }
  367. }
  368. .box-1{
  369. width: 100%;
  370. height: auto;
  371. padding-bottom: 100rpx;
  372. box-sizing: content-box;
  373. /* 兼容iPhoneX */
  374. margin-bottom: 0;
  375. margin-bottom: constant(safe-area-inset-bottom);
  376. margin-bottom: env(safe-area-inset-bottom);
  377. }
  378. .box-2{
  379. position: fixed;
  380. left: 0;
  381. width: 100%;
  382. bottom: 0;
  383. height: auto;
  384. z-index: 2;
  385. // border-top: #e5e5e5 solid 1px;
  386. box-sizing: content-box;
  387. background-color: #f5f5f5;
  388. /* 兼容iPhoneX */
  389. padding-bottom: 0;
  390. padding-bottom: constant(safe-area-inset-bottom);
  391. padding-bottom: env(safe-area-inset-bottom);
  392. >view{
  393. padding: 0 20rpx;
  394. height: 100rpx;
  395. }
  396. .content{
  397. background-color: #fff;
  398. height: 64rpx;
  399. padding: 0 20rpx;
  400. border-radius: 32rpx;
  401. font-size: 28rpx;
  402. }
  403. .send{
  404. background-color: #b7d7a5;
  405. color: #fff;
  406. height: 64rpx;
  407. margin-left: 20rpx;
  408. border-radius: 32rpx;
  409. padding: 0;
  410. width: 120rpx;
  411. line-height: 62rpx;
  412. &:active{
  413. opacity: 1;
  414. filter: brightness(80%);
  415. transform: translateY(1px) translateX(1px);
  416. }
  417. }
  418. }
  419. .talk-list{
  420. padding-bottom: 20rpx;
  421. /* 消息项,基础类 */
  422. .item{
  423. padding: 20rpx 20rpx 0 20rpx;
  424. align-items:flex-start;
  425. align-content:flex-start;
  426. color: #333;
  427. .pic{
  428. width: 92rpx;
  429. height: 92rpx;
  430. border-radius: 50%;
  431. border: #fff solid 1px;
  432. }
  433. .content{
  434. padding: 20rpx;
  435. border-radius: 4px;
  436. max-width: 500rpx;
  437. word-break: break-all;
  438. line-height: 52rpx;
  439. position: relative;
  440. }
  441. /* 收到的消息 */
  442. &.pull{
  443. .content{
  444. margin-left: 32rpx;
  445. background-color: #fff;
  446. &::after{
  447. content: '';
  448. display: block;
  449. width: 0;
  450. height: 0;
  451. border-top: 16rpx solid transparent;
  452. border-bottom: 16rpx solid transparent;
  453. border-right: 20rpx solid #fff;
  454. position: absolute;
  455. top: 30rpx;
  456. left: -18rpx;
  457. }
  458. }
  459. }
  460. /* 发出的消息 */
  461. &.push{
  462. /* 主轴为水平方向,起点在右端。使不修改DOM结构,也能改变元素排列顺序 */
  463. flex-direction: row-reverse;
  464. .content{
  465. margin-right: 32rpx;
  466. background-color: #c7e7b5;
  467. }
  468. }
  469. }
  470. }
  471. </style>
  472. <style lang="scss" scoped>
  473. .no-data{
  474. height: 30vw;
  475. font-size: 24px;
  476. color: #777;
  477. padding: 10px;
  478. }
  479. .del-button{
  480. height: 30px;
  481. width: 250px;
  482. margin-right: 10px;
  483. color: #666;
  484. background-color: #ffffff;
  485. }
  486. .chart-con {
  487. width: 100%;
  488. box-sizing: border-box;
  489. .chart-wrap {
  490. width: 100%;
  491. box-sizing: border-box;
  492. background-color: #f5f5f5;
  493. padding: 32rpx 0rpx;
  494. border-radius: 20rpx;
  495. .title {
  496. box-sizing: border-box;
  497. width: 100%;
  498. padding: 0rpx 28rpx;
  499. display: flex;
  500. flex-direction: row;
  501. justify-content: flex-start;
  502. align-items: center;
  503. }
  504. .ver-line {
  505. height: 30rpx;
  506. width: 8rpx;
  507. border-radius: 10rpx;
  508. background-color: #4e9d77;
  509. margin-right: 5px;
  510. }
  511. .title-desc {
  512. font-size: 30rpx;
  513. color: #222222;
  514. margin-left: 22rpx;
  515. font-weight: bold;
  516. }
  517. .line-chart-con {
  518. width: 100%;
  519. box-sizing: border-box;
  520. padding: 0rpx 28rpx;
  521. .fun-tabs {
  522. margin-top: 42rpx;
  523. display: flex;
  524. flex-direction: row;
  525. justify-content: space-between;
  526. align-self: center;
  527. width: 100%;
  528. box-sizing: border-box;
  529. .tab-item {
  530. width: 200rpx;
  531. height: 120rpx;
  532. border-radius: 10rpx;
  533. padding-left: 20rpx;
  534. background: #ffffff;
  535. border: 1rpx solid #ececec;
  536. display: flex;
  537. flex-direction: column;
  538. justify-content: center;
  539. align-items: flex-start;
  540. box-sizing: border-box;
  541. .item-name {
  542. color: #6e6e6e;
  543. font-size: 20rpx;
  544. }
  545. .item-val {
  546. color: #222222;
  547. font-size: 24rpx;
  548. font-weight: bold;
  549. margin-top: 20rpx;
  550. }
  551. }
  552. .selected {
  553. background: #edf5f1 !important;
  554. border: 1rpx solid #4e9d77 !important;
  555. .item-name {
  556. color: #4e9d77 !important;
  557. }
  558. .item-val {
  559. color: #4e9d77 !important;
  560. }
  561. }
  562. }
  563. .line-chart {
  564. margin-top: 30rpx;
  565. height: 380rpx;
  566. }
  567. }
  568. }
  569. }
  570. </style>