main.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. <!--
  2. - Copyright (C) 2018-2019
  3. - All rights reserved, Designed By www.joolun.com
  4. 芋道源码:
  5. ① 移除暂时用不到的 websocket
  6. ② 代码优化,补充注释,提升阅读性
  7. -->
  8. <template>
  9. <div class="msg-main">
  10. <div class="msg-div" :id="'msg-div' + nowStr">
  11. <!-- 加载更多 -->
  12. <div v-loading="loading"></div>
  13. <div v-if="!loading">
  14. <div class="el-table__empty-block" v-if="loadMore" @click="loadingMore"><span class="el-table__empty-text">点击加载更多</span></div>
  15. <div class="el-table__empty-block" v-if="!loadMore"><span class="el-table__empty-text">没有更多了</span></div>
  16. </div>
  17. <!-- 消息列表 -->
  18. <div class="execution" v-for="item in list" :key='item.id'>
  19. <div class="avue-comment" :class="item.sendFrom === 2 ? 'avue-comment--reverse' : ''">
  20. <div class="avatar-div">
  21. <img :src="item.sendFrom === 1 ? user.avatar : mp.avatar" class="avue-comment__avatar">
  22. <div class="avue-comment__author">{{item.sendFrom === 1 ? user.nickname : mp.nickname }}</div>
  23. </div>
  24. <div class="avue-comment__main">
  25. <div class="avue-comment__header">
  26. <div class="avue-comment__create_time">{{ parseTime(item.createTime) }}</div>
  27. </div>
  28. <div class="avue-comment__body" :style="item.sendFrom === 2 ? 'background: #6BED72;' : ''">
  29. <!-- 【事件】区域 -->
  30. <div v-if="item.type === 'event' && item.event === 'subscribe'">
  31. <el-tag type="success" size="mini">关注</el-tag>
  32. </div>
  33. <div v-else-if="item.type === 'event' && item.event === 'unsubscribe'">
  34. <el-tag type="danger" size="mini">取消关注</el-tag>
  35. </div>
  36. <div v-else-if="item.type === 'event' && item.event === 'CLICK'">
  37. <el-tag size="mini">点击菜单</el-tag>【{{ item.eventKey }}】
  38. </div>
  39. <div v-else-if="item.type === 'event' && item.event === 'VIEW'">
  40. <el-tag size="mini">点击菜单链接</el-tag>【{{ item.eventKey }}】
  41. </div>
  42. <div v-else-if="item.type === 'event' && item.event === 'scancode_waitmsg'">
  43. <el-tag size="mini">扫码结果</el-tag>【{{ item.eventKey }}】
  44. </div>
  45. <div v-else-if="item.type === 'event' && item.event === 'scancode_push'">
  46. <el-tag size="mini">扫码结果</el-tag>【{{ item.eventKey }}】
  47. </div>
  48. <div v-else-if="item.type === 'event' && item.event === 'pic_sysphoto'">
  49. <el-tag size="mini">系统拍照发图</el-tag>
  50. </div>
  51. <div v-else-if="item.type === 'event' && item.event === 'pic_photo_or_album'">
  52. <el-tag size="mini">拍照或者相册</el-tag>
  53. </div>
  54. <div v-else-if="item.type === 'event' && item.event === 'pic_weixin'">
  55. <el-tag size="mini">微信相册</el-tag>
  56. </div>
  57. <div v-else-if="item.type === 'event' && item.event === 'location_select'">
  58. <el-tag size="mini">选择地理位置</el-tag>
  59. </div>
  60. <div v-else-if="item.type === 'event'">
  61. <el-tag type="danger" size="mini">未知事件类型</el-tag>
  62. </div>
  63. <!-- 【消息】区域 -->
  64. <div v-else-if="item.type === 'text'">{{ item.content }}</div>
  65. <div v-else-if="item.type === 'voice'">
  66. <wx-voice-player :url="item.mediaUrl" :content="item.recognition" />
  67. </div>
  68. <div v-else-if="item.type === 'image'">
  69. <a target="_blank" :href="item.mediaUrl">
  70. <img :src="item.mediaUrl" style="width: 100px">
  71. </a>
  72. </div>
  73. <div v-else-if="item.type === 'video' || item.type === 'shortvideo'" style="text-align: center">
  74. <wx-video-player :url="item.mediaUrl" />
  75. </div>
  76. <div v-else-if="item.type === 'link'" class="avue-card__detail">
  77. <el-link type="success" :underline="false" target="_blank" :href="item.url">
  78. <div class="avue-card__title"><i class="el-icon-link"></i>{{ item.title }}</div>
  79. </el-link>
  80. <div class="avue-card__info" style="height: unset">{{item.description}}</div>
  81. </div>
  82. <!-- TODO 芋艿:待完善 -->
  83. <div v-else-if="item.type === 'location'">
  84. <wx-location :label="item.label" :location-y="item.locationY" :location-x="item.locationX" />
  85. </div>
  86. <div v-else-if="item.type === 'news'" style="width: 300px"> <!-- TODO 芋艿:待测试;详情页也存在类似的情况 -->
  87. <wx-news :articles="item.articles" />
  88. </div>
  89. <div v-else-if="item.type === 'music'">
  90. <wx-music :title="item.title" :description="item.description" :thumb-media-url="item.thumbMediaUrl"
  91. :music-url="item.musicUrl" :hq-music-url="item.hqMusicUrl" />
  92. </div>
  93. </div>
  94. </div>
  95. </div>
  96. </div>
  97. </div>
  98. <div class="msg-send" v-loading="sendLoading">
  99. <wx-reply-select ref="replySelect" :objData="objData" />
  100. <el-button type="success" size="small" class="send-but" @click="sendMsg">发送(S)</el-button>
  101. </div>
  102. </div>
  103. </template>
  104. <script>
  105. import { getMessagePage, sendMessage } from '@/api/mp/message'
  106. import WxReplySelect from '@/views/mp/components/wx-reply/main.vue'
  107. import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue';
  108. import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue';
  109. import WxNews from '@/views/mp/components/wx-news/main.vue';
  110. import WxLocation from '@/views/mp/components/wx-location/main.vue';
  111. import WxMusic from '@/views/mp/components/wx-music/main.vue';
  112. import { getUser } from "@/api/mp/user";
  113. export default {
  114. name: "wxMsg",
  115. components: {
  116. WxReplySelect,
  117. WxVideoPlayer,
  118. WxVoicePlayer,
  119. WxNews,
  120. WxLocation,
  121. WxMusic
  122. },
  123. props: {
  124. userId: {
  125. type: Number,
  126. required: true
  127. },
  128. },
  129. data() {
  130. return {
  131. nowStr: new Date().getTime(), // 当前的时间戳,用于每次消息加载后,回到原位置;具体见 :id="'msg-div' + nowStr" 处
  132. loading: false, // 消息列表是否正在加载中
  133. loadMore: true, // 是否可以加载更多
  134. list: [], // 消息列表
  135. queryParams: {
  136. pageNo: 1, // 当前页数
  137. pageSize: 14, // 每页显示多少条
  138. accountId: undefined,
  139. },
  140. user: { // 由于微信不再提供昵称,直接使用“用户”展示
  141. nickname: '用户',
  142. avatar: require("@/assets/images/profile.jpg"),
  143. accountId: 0, // 公众号账号编号
  144. },
  145. mp: {
  146. nickname: '公众号',
  147. avatar: require("@/assets/images/wechat.png"),
  148. },
  149. // ========= 消息发送 =========
  150. sendLoading: false, // 发送消息是否加载中
  151. objData: { // 微信发送消息
  152. type: 'text',
  153. },
  154. }
  155. },
  156. created() {
  157. // 获得用户信息
  158. getUser(this.userId).then(response => {
  159. this.user.nickname = response.data.nickname && response.data.nickname.length > 0 ?
  160. response.data.nickname : this.user.nickname;
  161. this.user.avatar = response.data.avatar && this.user.avatar.length > 0 ?
  162. response.data.avatar : this.user.avatar;
  163. this.user.accountId = response.data.accountId;
  164. // 设置公众号账号编号
  165. this.queryParams.accountId = response.data.accountId;
  166. this.objData.accountId = response.data.accountId;
  167. // 加载消息
  168. console.log(this.queryParams)
  169. this.refreshChange()
  170. })
  171. },
  172. methods:{
  173. sendMsg(){
  174. if (!this.objData) {
  175. return;
  176. }
  177. // 公众号限制:客服消息,公众号只允许发送一条
  178. if (this.objData.type === 'news'
  179. && this.objData.articles.length > 1) {
  180. this.objData.articles = [this.objData.articles[0]]
  181. this.$message({
  182. showClose: true,
  183. message: '图文消息条数限制在 1 条以内,已默认发送第一条',
  184. type: 'success'
  185. })
  186. }
  187. // 执行发送
  188. this.sendLoading = true
  189. sendMessage(Object.assign({
  190. userId: this.userId
  191. }, {
  192. ...this.objData
  193. })).then(response => {
  194. this.sendLoading = false
  195. // 添加到消息列表,并滚动
  196. this.list = [...this.list , ...[response.data] ]
  197. this.scrollToBottom()
  198. // 重置 objData 状态
  199. this.$refs['replySelect'].deleteObj(); // 重置,避免 tab 的数据未清理
  200. }).catch(() => {
  201. this.sendLoading = false
  202. })
  203. },
  204. loadingMore() {
  205. this.queryParams.pageNo++
  206. this.getPage(this.queryParams)
  207. },
  208. getPage(page, params) {
  209. this.loading = true
  210. getMessagePage(Object.assign({
  211. pageNo: page.pageNo,
  212. pageSize: page.pageSize,
  213. userId: this.userId,
  214. accountId: page.accountId,
  215. }, params)).then(response => {
  216. // 计算当前的滚动高度
  217. const msgDiv = document.getElementById('msg-div' + this.nowStr);
  218. let scrollHeight = 0
  219. if(msgDiv){
  220. scrollHeight = msgDiv.scrollHeight
  221. }
  222. // 处理数据
  223. const data = response.data.list.reverse();
  224. this.list = [...data, ...this.list]
  225. this.loading = false
  226. if (data.length < this.queryParams.pageSize || data.length === 0){
  227. this.loadMore = false
  228. }
  229. this.queryParams.pageNo = page.pageNo
  230. this.queryParams.pageSize = page.pageSize
  231. // 滚动到原来的位置
  232. if(this.queryParams.pageNo === 1) { // 定位到消息底部
  233. this.scrollToBottom()
  234. } else if (data.length !== 0) { // 定位滚动条
  235. this.$nextTick(() => {
  236. if (scrollHeight !== 0){
  237. msgDiv.scrollTop = document.getElementById('msg-div'+this.nowStr).scrollHeight - scrollHeight - 100
  238. }
  239. })
  240. }
  241. })
  242. },
  243. /**
  244. * 刷新回调
  245. */
  246. refreshChange() {
  247. this.getPage(this.queryParams)
  248. },
  249. /** 定位到消息底部 */
  250. scrollToBottom: function () {
  251. this.$nextTick(() => {
  252. let div = document.getElementById('msg-div' + this.nowStr)
  253. div.scrollTop = div.scrollHeight
  254. })
  255. },
  256. }
  257. };
  258. </script>
  259. <style lang="scss" scoped>
  260. /* 因为 joolun 实现依赖 avue 组件,该页面使用了 comment.scss、card.scc */
  261. @import './comment.scss';
  262. @import './card.scss';
  263. .msg-main {
  264. margin-top: -30px;
  265. padding: 10px;
  266. }
  267. .msg-div {
  268. height: 50vh;
  269. overflow: auto;
  270. background-color: #eaeaea;
  271. margin-left: 10px;
  272. margin-right: 10px;
  273. }
  274. .msg-send {
  275. padding: 10px;
  276. }
  277. .avatar-div {
  278. text-align: center;
  279. width: 80px;
  280. }
  281. .send-but {
  282. float: right;
  283. margin-top: 8px!important;
  284. }
  285. </style>