chat.vue 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  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/image/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. if (up > 0) {
  100. // 动态改变空盒子高度
  101. this.msgMove(up, 300)
  102. // 记录改变的值,若不收回键盘且发送了消息用来防止消息过多被遮盖
  103. mgUpHeight = up
  104. }
  105. // 收回
  106. if (res.height == 0) {
  107. this.msgMove(0, 0)
  108. }
  109. }).exec();
  110. })
  111. var query = uni.getSystemInfoSync()
  112. l = query.screenWidth / 750
  113. wh = query.windowHeight
  114. this.srcollHeight = query.windowHeight + "px"
  115. },
  116. data() {
  117. return {
  118. realTime: "",
  119. msgLoad: false,
  120. anData: {},
  121. animationData: {},
  122. showTow: false,
  123. // 消息体,定义机器人初次的消息(或者自定义出现时机)
  124. // my->谁发的消息 msg->消息文本 type->客服消息模板类型 questionList->快速获取问题答案的问题列表
  125. msgList: [{
  126. my: false,
  127. msg: "您好,掌柜智能客服助理为您服务!",
  128. initTime: this.getFormattedTime(),
  129. }],
  130. msg: "",
  131. go: 0,
  132. srcollHeight: 0
  133. }
  134. },
  135. methods: {
  136. getFormattedTime() {
  137. const date = new Date();
  138. const year = date.getFullYear();
  139. const month = (date.getMonth() + 1).toString().padStart(2, '0');
  140. const day = date.getDate().toString().padStart(2, '0');
  141. const hours = date.getHours().toString().padStart(2, '0');
  142. const minutes = date.getMinutes().toString().padStart(2, '0');
  143. return `${year}-${month}-${day} ${hours}:${minutes}`;
  144. },
  145. // 切换输入法时移动输入框(按照官方的上推页面的原理应该会自动适应不同的键盘高度-->官方bug)
  146. goPag(kh) {
  147. this.upTowmn(0, 250)
  148. if (this.keyHeight != 0) {
  149. if (kh - this.keyHeight > 0) {
  150. this.upTowmn(this.keyHeight - kh, 250)
  151. }
  152. }
  153. },
  154. // 移动顶部的空盒子
  155. msgMove(x, t) {
  156. var animation = uni.createAnimation({
  157. duration: t,
  158. timingFunction: 'linear',
  159. })
  160. this.animation = animation
  161. animation.height(x).step()
  162. this.anData = animation.export()
  163. },
  164. // 保持消息体可见
  165. msgGo() {
  166. const query = uni.createSelectorQuery()
  167. // 延时100ms保证是最新的高度
  168. setTimeout(() => {
  169. // 获取消息体高度
  170. query.select('#okk').boundingClientRect(data => {
  171. // 如果超过scorll高度就滚动scorll
  172. if (data.height - wh > 0) {
  173. this.go = data.height - wh
  174. }
  175. // 保证键盘第一次拉起时消息体能保持可见
  176. var moveY = wh - data.height
  177. // 超出页面则缩回空盒子
  178. if (moveY - mgUpHeight < 0) {
  179. // 小于0则视为0
  180. if (moveY < 0) {
  181. this.msgMove(0, 200)
  182. } else {
  183. // 否则缩回盒子对应的高度
  184. this.msgMove(moveY, 200)
  185. }
  186. }
  187. }).exec();
  188. }, 100)
  189. },
  190. // 回答问题的业务逻辑
  191. answer(id) {
  192. // 这里应该传入问题的id,模拟就用index代替了
  193. },
  194. sendMsg() {
  195. // 消息为空不做任何操作
  196. if (this.msg == "") {
  197. return 0;
  198. }
  199. // 显示消息 msg消息文本,my鉴别是谁发的消息(不能用俩个消息数组循环,否则消息不会穿插)
  200. this.msgList.push({
  201. "msg": this.msg,
  202. "my": true
  203. })
  204. // 保证消息可见
  205. this.msgGo()
  206. // 回答问题
  207. this.msgKf(this.msg)
  208. // 清除消息
  209. this.msg = ""
  210. },
  211. async msgKf(x) {
  212. // loading
  213. this.msgLoad = true
  214. // 这里连接服务器获取答案
  215. let res = await this.$http.post('/customer/ai/sendMessage', {
  216. message: this.msg
  217. });
  218. if (res.code == 200) {
  219. // 下面模拟请求
  220. setTimeout(() => {
  221. // 取消loading
  222. this.msgLoad = false
  223. const newMessage = {
  224. my: false,
  225. msg: '',
  226. initTime: res.data.date
  227. };
  228. this.msgList.push(newMessage);
  229. this.msgGo();
  230. // 使用typeWriter效果逐个字符显示
  231. this.typeWriter(res.data.message, newMessage);
  232. }, 400)
  233. }
  234. },
  235. // 不建议输入框聚焦时操作此动画
  236. ckAdd() {
  237. if (!this.showTow) {
  238. this.upTowmn(-180, 350)
  239. } else {
  240. this.upTowmn(0, 200)
  241. }
  242. this.showTow = !this.showTow
  243. },
  244. hideKey() {
  245. uni.hideKeyboard()
  246. },
  247. // 拉起/收回附加栏
  248. upTowmn(x, t) {
  249. var animation = uni.createAnimation({
  250. duration: t,
  251. timingFunction: 'ease',
  252. })
  253. this.animation = animation
  254. animation.translateY(x).step()
  255. this.animationData = animation.export()
  256. },
  257. typeWriter(text, messageObj) {
  258. let index = 0;
  259. const speed = 20;
  260. const addCharacter = () => {
  261. if (index < text.length) {
  262. messageObj.msg += text.charAt(index);
  263. index++;
  264. setTimeout(addCharacter, speed);
  265. } else {
  266. this.msgGo();
  267. }
  268. };
  269. addCharacter();
  270. },
  271. }
  272. }
  273. </script>
  274. <style lang="scss" scoped>
  275. @import "./css/icon.css";
  276. @import "./css/main.css";
  277. @import "./css/index-app.css";
  278. .chatBox {
  279. padding: 8px 14px;
  280. border-radius: 35rpx;
  281. background-color: #fff;
  282. box-shadow: 0px 2px 5px 0px #DAE3F4;
  283. border-radius: 6px;
  284. font-size: 12px;
  285. position: relative;
  286. text {
  287. color: #000;
  288. word-break: break-all;
  289. }
  290. }
  291. .initTime {
  292. background: rgba(207, 207, 207, 0.2);
  293. border-radius: 20px;
  294. width: auto;
  295. padding: 2px 8px;
  296. color: #000;
  297. font-size: 12px;
  298. }
  299. .page {
  300. background-color: #F8FAFE;
  301. }
  302. .bottom-dh-char {
  303. background: #FFFFFF;
  304. box-shadow: 0px -4px 10px 0px rgba(218, 227, 244, 0.2);
  305. border-radius: 0px 0px 0px 0px;
  306. width: 750rpx;
  307. height: 110rpx;
  308. }
  309. .center-box {
  310. width: 720rpx;
  311. padding-left: 25rpx;
  312. }
  313. .hui-box {
  314. width: 750rpx;
  315. height: 100%;
  316. }
  317. .dh-input {
  318. width: 80%;
  319. height: 33px;
  320. border-radius: 6px;
  321. padding-left: 15rpx;
  322. background-color: transparent !important;
  323. border: 1px solid #EBEBEB;
  324. }
  325. .box-normal {
  326. width: 750rpx;
  327. height: 180px;
  328. background-color: #FFFFFF;
  329. }
  330. .tb-text view {
  331. font-size: 65rpx;
  332. }
  333. .tb-text text {
  334. font-size: 25rpx;
  335. color: #737373;
  336. }
  337. .chat-img {
  338. border-radius: 50%;
  339. width: 100rpx;
  340. height: 100rpx;
  341. background-color: #f7f7f7;
  342. }
  343. .padding-chat {
  344. padding: 17rpx 20rpx;
  345. }
  346. .tb-nv {
  347. width: 50rpx;
  348. height: 50rpx;
  349. }
  350. </style>