chat.vue 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. <template>
  2. <view class="page">
  3. <!-- 空盒子用来防止消息过少时 拉起键盘会遮盖消息 -->
  4. <view :animation="anData" style="height:0;">
  5. </view>
  6. <!-- 消息体 -->
  7. <scroll-view scroll-with-animation scroll-y="true" @touchmove="hideKey" style="width: 750rpx;"
  8. :style="{'height':srcollHeight}" :scroll-top="go">
  9. <!-- 用来获取消息体高度 -->
  10. <view id="okk" scroll-with-animation>
  11. <!-- 消息 -->
  12. <view class="flex-column-start" v-for="(x,i) in msgList" :key="i">
  13. <!-- 用户消息 头像可选加入-->
  14. <view v-if="x.my" class="flex justify-end padding-right one-show align-start padding-top">
  15. <!-- <image v-if="!x.my" class="chat-img" src="../../static/..." mode="aspectFill" ></image> -->
  16. <view class="flex justify-end" style="width: 400rpx;">
  17. <view class="margin-left padding-chat bg-blue" style="border-radius: 6px;">
  18. <text style="word-break: break-all;">{{x.msg}}</text>
  19. </view>
  20. </view>
  21. </view>
  22. <!-- 机器人消息 -->
  23. <view v-if="!x.my" class=" margin-left margin-top one-show">
  24. <view class="dis j-c a-c mt-1 mb-3 ">
  25. <text class="initTime">{{x.initTime}}</text>
  26. </view>
  27. <view class="flex" style="max-width: 650rpx;">
  28. <view class=" chatBox dis j-s a-c" style="">
  29. <image v-if="i==0" src="/static/img/customerService1.png" mode=""
  30. style="width: 47px;height: 50px;position: absolute;left: 0;bottom: 0;">
  31. </image>
  32. <text :style="{ marginLeft: i === 0 ? '40px' : '0px' }">{{x.msg}}</text>
  33. </view>
  34. </view>
  35. </view>
  36. </view>
  37. <!-- loading是显示 -->
  38. <view v-show="msgLoad" class="flex-row-start margin-left margin-top">
  39. <!-- <view class="chat-img flex-row-center">
  40. <image style="height: 75rpx;width: 75rpx;" src="../../static/image/robt.png" mode="aspectFit">
  41. </image>
  42. </view> -->
  43. <view class="flex" style="width: 500rpx;">
  44. <view class="margin-left padding-chat flex-column-start"
  45. style="border-radius: 35rpx;background-color: #f9f9f9;">
  46. <view class="cuIcon-loading turn-load" style="font-size: 35rpx;color: #0081ff;">
  47. </view>
  48. </view>
  49. </view>
  50. </view>
  51. <!-- 防止消息底部被遮 -->
  52. <view style="height: 100px;">
  53. </view>
  54. </view>
  55. </scroll-view>
  56. <!-- 底部导航栏 -->
  57. <view class="flex-column-center" style="position: fixed;bottom: -180px;" :animation="animationData">
  58. <view class="bottom-dh-char flex-row-around" style="font-size: 55rpx;">
  59. <!-- vue无法使用软键盘"发送" -->
  60. <input v-model="msg" class="dh-input" type="text" style="background-color: #f0f0f0;" @confirm="sendMsg"
  61. confirm-type="search" placeholder-class="my-neirong-sm" placeholder="请描述您的问题" />
  62. <view class="bg-blue "
  63. style="border-radius: 6px;font-size: 12px;padding: 5px 10px;box-sizing: border-box;"
  64. @click="sendMsg">
  65. 发送
  66. </view>
  67. </view>
  68. <!-- 附加栏(自定义) -->
  69. <view class="box-normal flex-row-around flex-wrap">
  70. <view class="tb-text">
  71. <view class="cuIcon-form"></view>
  72. <text>问题反馈</text>
  73. </view>
  74. <view class="tb-text">
  75. <view class="cuIcon-form"></view>
  76. <text>人工客服</text>
  77. </view>
  78. </view>
  79. </view>
  80. </view>
  81. </template>
  82. <script>
  83. // rpx和px的比率
  84. var l
  85. // 可用窗口高度
  86. var wh
  87. // 顶部空盒子的高度
  88. var mgUpHeight
  89. export default {
  90. onLoad() {
  91. // 如果需要缓存消息缓存msgList即可
  92. // 监听键盘拉起
  93. // 因为无法控制键盘拉起的速度,所以这里尽量以慢速处理
  94. uni.onKeyboardHeightChange(res => {
  95. const query = uni.createSelectorQuery()
  96. query.select('#okk').boundingClientRect(data => {
  97. // 若消息体没有超过2倍的键盘则向下移动差值,防止遮住消息体
  98. var up = res.height * 2 - data.height - l * 110
  99. console.log(up)
  100. if (up > 0) {
  101. // 动态改变空盒子高度
  102. this.msgMove(up, 300)
  103. // 记录改变的值,若不收回键盘且发送了消息用来防止消息过多被遮盖
  104. mgUpHeight = up
  105. }
  106. // 收回
  107. if (res.height == 0) {
  108. this.msgMove(0, 0)
  109. }
  110. }).exec();
  111. })
  112. var query = uni.getSystemInfoSync()
  113. l = query.screenWidth / 750
  114. wh = query.windowHeight
  115. this.srcollHeight = query.windowHeight + "px"
  116. },
  117. data() {
  118. return {
  119. realTime: "",
  120. msgLoad: false,
  121. anData: {},
  122. animationData: {},
  123. showTow: false,
  124. // 消息体,定义机器人初次的消息(或者自定义出现时机)
  125. // my->谁发的消息 msg->消息文本 type->客服消息模板类型 questionList->快速获取问题答案的问题列表
  126. msgList: [{
  127. my: false,
  128. msg: "您好,掌柜智能客服助理为您服务!",
  129. initTime: this.getFormattedTime(),
  130. }],
  131. msg: "",
  132. go: 0,
  133. srcollHeight: 0
  134. }
  135. },
  136. methods: {
  137. getFormattedTime() {
  138. const date = new Date();
  139. const year = date.getFullYear();
  140. const month = (date.getMonth() + 1).toString().padStart(2, '0');
  141. const day = date.getDate().toString().padStart(2, '0');
  142. const hours = date.getHours().toString().padStart(2, '0');
  143. const minutes = date.getMinutes().toString().padStart(2, '0');
  144. return `${year}-${month}-${day} ${hours}:${minutes}`;
  145. },
  146. // 切换输入法时移动输入框(按照官方的上推页面的原理应该会自动适应不同的键盘高度-->官方bug)
  147. goPag(kh) {
  148. this.upTowmn(0, 250)
  149. if (this.keyHeight != 0) {
  150. if (kh - this.keyHeight > 0) {
  151. this.upTowmn(this.keyHeight - kh, 250)
  152. }
  153. }
  154. },
  155. // 移动顶部的空盒子
  156. msgMove(x, t) {
  157. var animation = uni.createAnimation({
  158. duration: t,
  159. timingFunction: 'linear',
  160. })
  161. this.animation = animation
  162. animation.height(x).step()
  163. this.anData = animation.export()
  164. },
  165. // 保持消息体可见
  166. msgGo() {
  167. const query = uni.createSelectorQuery()
  168. // 延时100ms保证是最新的高度
  169. setTimeout(() => {
  170. // 获取消息体高度
  171. query.select('#okk').boundingClientRect(data => {
  172. // 如果超过scorll高度就滚动scorll
  173. if (data.height - wh > 0) {
  174. this.go = data.height - wh
  175. }
  176. // 保证键盘第一次拉起时消息体能保持可见
  177. var moveY = wh - data.height
  178. // 超出页面则缩回空盒子
  179. if (moveY - mgUpHeight < 0) {
  180. // 小于0则视为0
  181. if (moveY < 0) {
  182. this.msgMove(0, 200)
  183. } else {
  184. // 否则缩回盒子对应的高度
  185. this.msgMove(moveY, 200)
  186. }
  187. }
  188. }).exec();
  189. }, 100)
  190. },
  191. // 回答问题的业务逻辑
  192. answer(id) {
  193. // 这里应该传入问题的id,模拟就用index代替了
  194. console.log(id)
  195. },
  196. sendMsg() {
  197. // 消息为空不做任何操作
  198. if (this.msg == "") {
  199. return 0;
  200. }
  201. // 显示消息 msg消息文本,my鉴别是谁发的消息(不能用俩个消息数组循环,否则消息不会穿插)
  202. this.msgList.push({
  203. "msg": this.msg,
  204. "my": true
  205. })
  206. // 保证消息可见
  207. this.msgGo()
  208. // 回答问题
  209. this.msgKf(this.msg)
  210. // 清除消息
  211. this.msg = ""
  212. },
  213. async msgKf(x) {
  214. // loading
  215. this.msgLoad = true
  216. // 这里连接服务器获取答案
  217. let res = await this.$http.post('/customer/ai/sendMessage', {
  218. message: this.msg
  219. });
  220. if (res.code == 200) {
  221. // 下面模拟请求
  222. setTimeout(() => {
  223. // 取消loading
  224. this.msgLoad = false
  225. const newMessage = {
  226. my: false,
  227. msg: '',
  228. initTime: res.data.date
  229. };
  230. this.msgList.push(newMessage);
  231. this.msgGo();
  232. // 使用typeWriter效果逐个字符显示
  233. this.typeWriter(res.data.message, newMessage);
  234. }, 400)
  235. }
  236. },
  237. // 不建议输入框聚焦时操作此动画
  238. ckAdd() {
  239. if (!this.showTow) {
  240. this.upTowmn(-180, 350)
  241. } else {
  242. this.upTowmn(0, 200)
  243. }
  244. this.showTow = !this.showTow
  245. },
  246. hideKey() {
  247. uni.hideKeyboard()
  248. },
  249. // 拉起/收回附加栏
  250. upTowmn(x, t) {
  251. var animation = uni.createAnimation({
  252. duration: t,
  253. timingFunction: 'ease',
  254. })
  255. this.animation = animation
  256. animation.translateY(x).step()
  257. this.animationData = animation.export()
  258. },
  259. typeWriter(text, messageObj) {
  260. let index = 0;
  261. const speed = 20;
  262. const addCharacter = () => {
  263. if (index < text.length) {
  264. messageObj.msg += text.charAt(index);
  265. index++;
  266. setTimeout(addCharacter, speed);
  267. } else {
  268. this.msgGo();
  269. }
  270. };
  271. addCharacter();
  272. },
  273. }
  274. }
  275. </script>
  276. <style lang="scss" scoped>
  277. @import "./css/icon.css";
  278. @import "./css/main.css";
  279. @import "./css/index-app.css";
  280. .chatBox {
  281. padding: 8px 14px;
  282. border-radius: 35rpx;
  283. background-color: #fff;
  284. box-shadow: 0px 2px 5px 0px #DAE3F4;
  285. border-radius: 6px;
  286. font-size: 12px;
  287. position: relative;
  288. text {
  289. color: #000;
  290. word-break: break-all;
  291. }
  292. }
  293. .initTime {
  294. background: rgba(207, 207, 207, 0.2);
  295. border-radius: 20px;
  296. width: auto;
  297. padding: 2px 8px;
  298. color: #000;
  299. font-size: 12px;
  300. }
  301. .page {
  302. background-color: #F8FAFE;
  303. }
  304. .bottom-dh-char {
  305. background: #FFFFFF;
  306. box-shadow: 0px -4px 10px 0px rgba(218, 227, 244, 0.2);
  307. border-radius: 0px 0px 0px 0px;
  308. width: 750rpx;
  309. height: 110rpx;
  310. }
  311. .center-box {
  312. width: 720rpx;
  313. padding-left: 25rpx;
  314. }
  315. .hui-box {
  316. width: 750rpx;
  317. height: 100%;
  318. }
  319. .dh-input {
  320. width: 80%;
  321. height: 33px;
  322. border-radius: 6px;
  323. padding-left: 15rpx;
  324. background-color: transparent !important;
  325. border: 1px solid #EBEBEB;
  326. }
  327. .box-normal {
  328. width: 750rpx;
  329. height: 180px;
  330. background-color: #FFFFFF;
  331. }
  332. .tb-text view {
  333. font-size: 65rpx;
  334. }
  335. .tb-text text {
  336. font-size: 25rpx;
  337. color: #737373;
  338. }
  339. .chat-img {
  340. border-radius: 50%;
  341. width: 100rpx;
  342. height: 100rpx;
  343. background-color: #f7f7f7;
  344. }
  345. .padding-chat {
  346. padding: 17rpx 20rpx;
  347. }
  348. .tb-nv {
  349. width: 50rpx;
  350. height: 50rpx;
  351. }
  352. </style>