message.vue 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. <template>
  2. <div class="chat-box-message">
  3. <div
  4. class="chat-box-message__item"
  5. v-for="item in flist"
  6. :key="item.id || item.uid"
  7. :class="[item.type == 0 ? `is-right` : `is-left`, `is-${item.mode}`]"
  8. >
  9. <div class="date" v-if="item._date">
  10. <span>{{ item._date }}</span>
  11. </div>
  12. <div class="main">
  13. <div class="avatar" @tap="toUserDetail(item)">
  14. <el-image :src="item.avatarUrl"></el-image>
  15. </div>
  16. <div class="det">
  17. <span class="name">{{ item.nickName }}</span>
  18. <div
  19. class="content"
  20. v-loading="item.loading"
  21. :element-loading-text="item.progress"
  22. @click="tapItem(item)"
  23. >
  24. <!-- 文本 -->
  25. <template v-if="item.mode === 'text'">{{ item.content.text }}</template>
  26. <!-- 图片 -->
  27. <template v-else-if="item.mode === 'image'">
  28. <el-image
  29. :key="item.uid"
  30. :src="item.content.imageUrl"
  31. :preview-src-list="[item.content.imageUrl]"
  32. ></el-image>
  33. </template>
  34. <!-- 表情 -->
  35. <template v-else-if="item.mode === 'emoji'">
  36. <img :src="item.content.imageUrl" />
  37. </template>
  38. <!-- 语音 -->
  39. <template v-else-if="item.mode === 'voice'">
  40. <icon-voice :play="item.isPlay"></icon-voice>
  41. <span class="duration">{{ item.content.duration | duration }}"</span>
  42. </template>
  43. <!-- 视频 -->
  44. <template v-else-if="item.mode === 'video'">
  45. <div class="item">
  46. <video
  47. :poster="item.content.videoUrl | video_poster"
  48. :src="item.content.videoUrl"
  49. controls
  50. ></video>
  51. </div>
  52. </template>
  53. <!-- 未知 -->
  54. <template v-else>
  55. <span>待扩展消息类型</span>
  56. <i class="el-icon-warning-outline"></i>
  57. </template>
  58. </div>
  59. </div>
  60. </div>
  61. </div>
  62. <!-- voice -->
  63. <div class="voice">
  64. <audio style="display: none" ref="voice" :src="voice.url" controls></audio>
  65. </div>
  66. </div>
  67. </template>
  68. <script>
  69. import dayjs from "dayjs";
  70. import IconVoice from "./icon-voice";
  71. export default {
  72. components: {
  73. IconVoice
  74. },
  75. props: {
  76. list: Array
  77. },
  78. data() {
  79. return {
  80. player: {},
  81. voice: {
  82. url: "",
  83. timer: null
  84. }
  85. };
  86. },
  87. filters: {
  88. duration(val) {
  89. return Math.ceil((val || 1) / 1000);
  90. }
  91. },
  92. destroyed() {
  93. clearTimeout(this.voice.timer);
  94. this.list.map((e) => {
  95. e.isPlay = false;
  96. });
  97. },
  98. computed: {
  99. flist() {
  100. let date = "";
  101. return this.list.map((e) => {
  102. e._date = date
  103. ? dayjs(e.createTime).isBefore(dayjs(date).add(1, "minute"))
  104. ? ""
  105. : e.createTime
  106. : e.createTime;
  107. date = e.createTime;
  108. return e;
  109. });
  110. }
  111. },
  112. methods: {
  113. tapItem(item) {
  114. if (item.mode == "voice") {
  115. this.list.map((e) => {
  116. this.$set(e, "isPlay", e.id == item.id ? e.isPlay : false);
  117. });
  118. item.isPlay = !item.isPlay;
  119. if (item.isPlay) {
  120. this.voice.url = item.content.voiceUrl;
  121. this.$nextTick(() => {
  122. this.$refs["voice"].play();
  123. });
  124. } else {
  125. this.$refs["voice"].pause();
  126. item.isPlay = false;
  127. }
  128. clearTimeout(this.voice.timer);
  129. this.voice.timer = setTimeout(() => {
  130. item.isPlay = false;
  131. }, item.content.duration);
  132. }
  133. }
  134. }
  135. };
  136. </script>
  137. <style lang="scss" scoped>
  138. .chat-box-message {
  139. &__item {
  140. margin-bottom: 20px;
  141. .date {
  142. text-align: center;
  143. margin: 10px 0;
  144. span {
  145. background-color: #dadada;
  146. font-size: 12px;
  147. color: #fff;
  148. border-radius: 3px;
  149. padding: 2px 5px;
  150. letter-spacing: 1px;
  151. }
  152. }
  153. .main {
  154. display: flex;
  155. .avatar {
  156. flex-shrink: 0;
  157. height: 40px;
  158. width: 40px;
  159. .el-image {
  160. border-radius: 3px;
  161. }
  162. }
  163. .det {
  164. display: flex;
  165. flex-direction: column;
  166. max-width: 60%;
  167. .name {
  168. margin-bottom: 5px;
  169. }
  170. .content {
  171. display: inline-block;
  172. border-radius: 8px;
  173. box-sizing: border-box;
  174. font-size: 12px;
  175. }
  176. }
  177. }
  178. &.is-left {
  179. .main {
  180. .det {
  181. margin-left: 10px;
  182. align-items: flex-start;
  183. .content {
  184. border-top-left-radius: 0;
  185. background-color: #fff;
  186. }
  187. }
  188. }
  189. &.is-voice {
  190. .content {
  191. justify-content: flex-start;
  192. }
  193. }
  194. }
  195. &.is-right {
  196. .main {
  197. flex-direction: row-reverse;
  198. .det {
  199. margin-right: 10px;
  200. align-items: flex-end;
  201. .content {
  202. border-top-right-radius: 0;
  203. background-color: $color-main;
  204. color: #fff;
  205. }
  206. }
  207. }
  208. &.is-voice {
  209. .content {
  210. justify-content: flex-end;
  211. }
  212. }
  213. }
  214. &.is-text {
  215. .content {
  216. max-width: 100%;
  217. min-width: 40px;
  218. word-wrap: break-word;
  219. }
  220. }
  221. &.is-text,
  222. &.is-voice {
  223. .content {
  224. padding: 10px;
  225. line-height: 20px;
  226. letter-spacing: 1px;
  227. }
  228. }
  229. &.is-emoji {
  230. .content {
  231. padding: 10px;
  232. img {
  233. height: 20px;
  234. width: 20px;
  235. }
  236. }
  237. }
  238. &.is-voice {
  239. .content {
  240. display: flex;
  241. align-items: center;
  242. width: 65px;
  243. cursor: pointer;
  244. &:hover {
  245. opacity: 0.8;
  246. }
  247. }
  248. }
  249. &.is-video {
  250. .item {
  251. video {
  252. max-width: 300px;
  253. max-height: 300px;
  254. }
  255. }
  256. }
  257. &.is-image {
  258. .main {
  259. .det {
  260. .content {
  261. background-color: #fff;
  262. /deep/.el-image {
  263. display: block;
  264. border-radius: 6px;
  265. max-width: 200px;
  266. min-width: 80px;
  267. }
  268. }
  269. }
  270. }
  271. }
  272. &.is-undefined {
  273. .main {
  274. .det {
  275. .content {
  276. display: flex;
  277. align-items: center;
  278. padding: 10px;
  279. letter-spacing: 1px;
  280. background-color: #f56c6c;
  281. color: #fff;
  282. .el-icon-warning-outline {
  283. font-size: 15px;
  284. margin-left: 4px;
  285. }
  286. }
  287. }
  288. }
  289. }
  290. }
  291. }
  292. </style>