| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621 |
- <template>
- <div class="message-wrapper col-2">
- <div class="content-wrapper">
- <!--文本消息-->
- <div class="message-container" v-if="message.contentType === 101">
- <div class="text-message" v-for="(item, index) in contentList" :key="index">
- <span :key="index" v-if="item.name === 'text'">{{ item.text }}</span>
- <img v-else-if="item.name === 'img'" :src="item.src" width="20px" height="20px" :key="index"/>
- </div>
- </div>
- <!--图片消息-->
- <div class="message-container" v-else-if="message.contentType === 102">
- <img class="image-element" :src="message.pictureElem.sourcePicture.url" @load="onImageLoaded" @click="handlePreview()" />
- </div>
- <!--文件消息-->
- <div class="message-container" v-else-if="message.type === 105">
- <div class="file-element-wrapper" title="单击下载" @click="downloadFile">
- <div class="file-box">
- <i class="el-icon-document file-icon"></i>
- <div class="file-element">
- <span class="file-name">{{ message.fileElem.fileName }}</span>
- <span class="file-size">{{ message.fileElem.fileSize }}</span>
- </div>
- </div>
- </div>
- </div>
- <!--表情消息-->
- <div class="message-container" v-else-if="message.type === 115">
- <img :src="faceUrl"/>
- </div>
- <!--视频消息-->
- <div class="message-container" v-else-if="message.contentType === 104">
- <video
- :src="message.videoElem.videoUrl"
- controls
- class="merger-video"
- @error="videoError"
- ></video>
- </div>
- <!--音频消息-->
- <div class="sound-element-wrapper" v-else-if="message.contentType === 103" :title="playStatus === 'playing' ? '单击暂停' : '单击播放'" @click="handleClick">
- <i class="iconfont icon-voice"></i>
- {{ message.soundElem.duration}}
- </div>
- <!--自定义消息-->
- <div class="message-container" v-else-if="message.contentType === 110">
- <div class="custom-element-wrapper">
- <div class="survey" v-if="this.payload.data === 'survey'">
- <div class="title">对IM DEMO的评分和建议</div>
- <el-rate
- v-model="rate"
- disabled
- show-score
- text-color="#ff9900"
- score-template="{value}">
- </el-rate>
- <div class="suggestion">{{this.payload.extension}}</div>
- </div>
- <span class="text" title="您可以自行解析自定义消息" v-else>
- <template >{{translateCustomMessage(this.payload)}}</template>
- </span>
- </div>
- </div>
- <!--合并的消息-->
- <div class="message-container" @click="mergerHandler(message)" v-else-if="message.contentType === 107">
- <div class="merger-item">
- <p class="merger-title">{{message.mergeElem.title}}</p>
- <p class="merger-text" v-for="(item, index) in message.mergeElem.abstractList" :key="index">
- {{item}}
- </p>
- </div>
- </div>
- </div>
- </div>
- </template>
- <script>
- import { mapState } from 'vuex'
- import { decodeText } from '../../../utils/decodeText'
- import { getFullDate } from '../../../utils/date'
- import { Rate } from 'element-ui'
- export default {
- name: 'MessageItem',
- props: {
- message: {
- type: Object,
- required: true
- },
- payload: {
- type: Object,
- default: () => ({})
- }
- },
- components: {
- ElRate: Rate,
- },
- data() {
- return {
- renderDom: [],
- showConversationList: false,
- relayMessage: {},
- selectedConversation: [],
- messageSelected:[],
- amr: null,
- audio: null,
- isAMR: false,
- playStatus: 'stopped' // 'playing' | 'paused' | 'stopped'
- }
- },
- computed: {
- url() {
- return this.message.soundElem.sourceUrl
- },
- second() {
- return this.message.soundElem.duration
- },
- ...mapState({
- currentConversation: state => state.conversation.currentConversation,
- currentUserProfile: state => state.imuser.currentUserProfile,
- isShowConversationList: state => state.conversation.isShowConversationList,
- }),
- // 自定义消息
- rate() {
- return parseInt(this.payload.description)
- },
- // 图片消息
- imageUrl() {
- const url = this.message.pictureElem.sourcePicture.url
- if (typeof url !== 'string') {
- return ''
- }
- return url.slice(0, 2) === '//' ? `https:${url}` : url
- },
- // showProgressBar() {
- // return this.$parent.message.status === 'unSend'
- // },
- percentage() {
- return Math.floor((this.$parent.message.progress || 0) * 100)
- },
- // 表情消息
- faceUrl() {
- let name = ''
- if (this.payload.data.indexOf('@2x') > 0) {
- name = this.payload.data
- } else {
- name = this.payload.data + '@2x'
- }
- return `https://web.sdk.qcloud.com/im/assets/face-elem/${name}.png`
- },
- // 时间换算
- date() {
- return getFullDate(new Date(this.message.time * 1000))
- },
- // 文件消息大小
- fileSize() {
- const size = this.message.fileElem.fileSize
- if (size > 1024) {
- if (size / 1024 > 1024) {
- return `${this.toFixed(size / 1024 / 1024)} Mb`
- }
- return `${this.toFixed(size / 1024)} Kb`
- }
- return `${this.toFixed(size)}B`
- },
- // 消息昵称
- from() {
- const isC2C = this.currentConversation.type === 1
- // 自己发送的用昵称渲染
- if (this.isMine) {
- return this.currentUserProfile.nick || this.currentUserProfile.userID
- }
- // 1. C2C 的消息体中还无 nick / avatar 字段,需从 conversation.userProfile 中获取
- if (isC2C) {
- return (
- this.currentConversation.userProfile.nick ||
- this.currentConversation.userProfile.userID
- )
- }
- // 2. 群组消息,用消息体中的 nick 渲染。nameCard暂时支持不完善
- return this.message.nameCard || this.message.nick || this.message.from
- },
- avatar() {
- if (this.currentConversation.type === 'C2C') {
- return this.isMine
- ? this.currentUserProfile.avatar
- : this.currentConversation.userProfile.avatar
- } else if (this.currentConversation.type === 'GROUP') {
- return this.isMine
- ? this.currentUserProfile.avatar
- : this.message.avatar
- } else {
- return ''
- }
- },
- currentConversationType() {
- return this.currentConversation.type
- },
- isMine() {
- return this.message.flow === 'out'
- },
- contentList() {
- console.log("this.payload",this.payload)
- return decodeText(this.payload)
- },
- },
- methods: {
- // 自定义消息解析
- translateCustomMessage(payload) {
- let videoPayload = {}
- try{
- videoPayload = JSON.parse(payload.data)
- } catch(e) {
- videoPayload = {}
- }
- if (payload.data === 'group_create') {
- return `${payload.extension}`
- }
- if (videoPayload.roomId) {
- videoPayload.roomId = videoPayload.roomId.toString()
- videoPayload.isFromGroupLive = 1
- return videoPayload
- }
- if(payload.text) {
- return payload.text
- }else{
- return '[自定义消息]'
- }
- },
- // 图片消息
- onImageLoaded(event) {
- this.$bus.$emit('image-loaded', event)
- },
- handlePreview() {
- this.$bus.$emit('image-preview', {
- url: this.message.pictureElem.sourcePicture.url,
- flag: true
- })
- },
- toFixed(number, precision = 2) {
- return number.toFixed(precision)
- },
- showGroupMemberProfile(event) {
- this.tim
- .getGroupMemberProfile({
- groupID: this.message.to,
- userIDList: [this.message.from]
- })
- .then(({ data: { memberList } }) => {
- if (memberList[0]) {
- this.$bus.$emit('showMemberProfile', { event, member: memberList[0] })
- }
- })
- },
- messageClick(message) {
- this.$store.commit('showConversationList', false)
- this.showConversationList = true
- this.relayMessage = message // 需要深拷贝吗?
- },
- showMergerMessage() {
- this.$bus.$emit('mergerMessage', true)
- },
- cancel() {
- this.showConversationList = false
- },
- getList(value) {
- this.selectedConversation = value
- },
- messageRelay() {
- let type = ''
- let toUserId = ''
- this.selectedConversation.forEach((item) => {
- if(item.indexOf(this.OpenIM.TYPES.CONV_C2C) !== -1) {
- type = 1
- toUserId = item.substring(3,item.length)
- }
- if(item.indexOf(3) !== -1) {
- type = 3
- toUserId = item.substring(5,item.length)
- }
- const message = this.tim.createForwardMessage({
- to: toUserId,
- conversationType: type,
- payload: this.relayMessage,
- priority: this.OpenIM.TYPES.MSG_PRIORITY_NORMAL
- })
- this.tim.sendMessage(message).catch(imError => {
- this.$store.commit('showMessage', {
- message: imError.message,
- type: 'error'
- })
- })
- this.showConversationList = false
- })
- },
- // 合并的消息
- mergerHandler(message) {
- console.log("setMergerMessage",message)
- this.$store.commit('setMergerMessage', message)
- // this.$bus.$emit('mergerMessage', message)
- },
- // 视频消息
- videoError(e) {
- this.$store.commit('showMessage', { type: 'error', message: '视频出错,错误原因:' + e.target.error.message })
- },
- // 音频消息
- play() {
- this.cleanup()
- // 默认使用 HTML5 audio 播放
- this.audio = new Audio(this.url)
- console.log(this.audio)
- this.audio.addEventListener('error', this.tryPlayAMR)
- this.audio.addEventListener('ended', this.cleanup)
- this.audio.play()
- .then(() => {
- this.playStatus = 'playing'
- })
- .catch(() => {
- // 播放失败 fallback 到 tryPlayAMR
- })
- },
- handleClick() {
- console.log("this.message.soundElem.sourceUrl",this.message.soundElem.sourceUrl)
- if (this.playStatus === 'playing') {
- this.pause()
- } else if (this.playStatus === 'paused') {
- this.resume()
- } else {
- this.play()
- }
- },
- pause() {
- if (this.isAMR && this.amr) {
- this.amr.pause()
- } else if (this.audio) {
- this.audio.pause()
- }
- this.playStatus = 'paused'
- },
- resume() {
- if (this.isAMR && this.amr) {
- this.amr.play()
- } else if (this.audio) {
- this.audio.play()
- }
- this.playStatus = 'playing'
- },
- tryPlayAMR() {
- this.isAMR = true
- const isIE = /MSIE|Trident|Edge/.test(window.navigator.userAgent)
- if (isIE) {
- this.$store.commit('showMessage', {
- message: '您的浏览器不支持该格式的语音消息播放,请尝试更换浏览器,建议使用:谷歌浏览器',
- type: 'warning'
- })
- return
- }
- if (!window.BenzAMRRecorder) {
- const script = document.createElement('script')
- script.addEventListener('load', this.playAMR)
- script.src = '/BenzAMRRecorder.js'
- document.head.appendChild(script)
- return
- }
- this.playAMR()
- },
- playAMR() {
- if (!this.amr && window.BenzAMRRecorder) {
- this.amr = new window.BenzAMRRecorder()
- this.amr.onEnded(() => {
- this.cleanup()
- })
- }
- if (this.amr.isInit()) {
- this.amr.play()
- this.playStatus = 'playing'
- } else {
- this.amr.initWithUrl(this.url).then(() => {
- this.amr.play()
- this.playStatus = 'playing'
- })
- }
- },
- cleanup() {
- if (this.audio) {
- this.audio.pause()
- this.audio = null
- }
- if (this.amr) {
- this.amr.stop()
- }
- this.playStatus = 'stopped'
- },
- // 文件消息
- downloadFile() {
- const fileUrl = this.message.fileElem.sourceUrl;
- const fileName = this.message.fileElem.fileName;
- console.log(fileUrl)
- fetch(fileUrl)
- .then(response => {
- if (!response.ok) throw new Error(`下载失败,状态码:${response.status}`);
- return response.blob();
- })
- .then(blob => {
- /*const mime = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
- const docBlob = new Blob([blob], { type: mime });*/ // 💡 明确指定 MIME 类型
- const blobUrl = window.URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = blobUrl;
- a.download = fileName;
- document.body.appendChild(a);
- a.click();
- document.body.removeChild(a);
- window.URL.revokeObjectURL(blobUrl); // 释放资源
- })
- .catch(err => {
- console.error('下载失败:', err);
- });
- }
- }
- }
- </script>
- <style lang="stylus" scoped>
- .conversation-container {
- position absolute
- top 0
- left 0px
- width 100%
- background-color #fff
- z-index 999
- }
- .conversation-list-btn {
- width 140px
- display flex
- float right
- margin 10px 0
- .conversation-btn {
- cursor pointer
- padding 6px 12px
- background #00A4FF
- color #ffffff
- font-size 14px
- border-radius 20px
- margin-left 13px
- }
- }
- .message-wrapper {
- margin: 5px 5px 10px 5px;
- .content-wrapper {
- display: flex
- align-items: center
- .message-container {
- width 100%
- .text-message {
- padding 3px 10px
- }
- .image-element {
- max-height 300px
- }
- .merger-item {
- border 1px solid #DEDEDE
- background-color #ffffff
- padding 0 10px
- border-radius 6px
- .merger-title {
- font-size 15px
- max-width 180px
- overflow hidden;
- text-overflow ellipsis;
- white-space nowrap;
- }
- .merger-text {
- color #B3B3B3
- margin 10px 0
- font-size 13px
- max-width 280px
- overflow hidden;
- text-overflow ellipsis;
- white-space nowrap;
- }
- }
- }
- }
- }
- .group-layout, .c2c-layout, .system-layout {
- display: flex;
- .col-1 {
- .avatar {
- width: 56px;
- height: 56px;
- border-radius: 50%;
- box-shadow: 0 5px 10px 0 rgba(0, 0, 0, 0.1);
- }
- }
- .group-member-avatar {
- cursor: pointer;
- }
- .col-2 {
- display: flex;
- flex-direction: column;
- // max-width 50% // 此设置可以自适应宽度,目前由bubble限制
- }
- .col-3 {
- width: 30px;
- }
- &.position-left {
- .col-2 {
- align-items: flex-start;
- }
- }
- &.position-right {
- flex-direction: row-reverse;
- .col-2 {
- align-items: flex-end;
- }
- }
- &.position-center {
- justify-content: center;
- }
- }
- .c2c-layout {
- .col-2 {
- .base {
- margin-top: 3px;
- }
- }
- }
- .group-layout {
- .col-2 {
- .chat-bubble {
- margin-top: 5px;
- outline none
- }
- }
- }
- .right {
- display: flex;
- flex-direction: row-reverse;
- }
- .left {
- display: flex;
- flex-direction: row;
- }
- .base {
- color: $secondary;
- font-size: 12px;
- }
- .name {
- padding: 0 4px;
- max-width: 100px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- .merger-video {
- width 100%
- max-height 300px
- }
- .file-box {
- display: flex;
- }
- .file-icon {
- font-size: 40px !important;
- }
- .file-element {
- display: flex;
- flex-direction: column;
- margin-left: 12px;
- }
- .file-size {
- font-size: 12px;
- padding-top 5px
- }
- .text
- font-weight bold
- .title
- font-size 16px
- font-weight 600
- padding-bottom 10px
- .survey
- background-color white
- color black
- padding 20px
- display flex
- flex-direction column
- .suggestion
- padding-top 10px
- font-size 14px
- .sound-element-wrapper {
- background-color #fff
- padding 2px 13px
- cursor pointer
- border-radius 3px
- }
- </style>
|