123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412 |
- <template>
- <div v-if="repealMsg" class="msg-repeal-item">
- {{repealStr}}
- </div>
- <div v-else-if="joinMsg" class="msg-join-item">
- <span>{{joinMsg}}</span>
- </div>
- <redPack-tip
- v-else-if="msgItem && msgItem.redPackTip"
- :info="msgItem">
- </redPack-tip>
- <div class="msg-item clearfix" :class="type" v-else>
- <msg-time :timestamp="timestamp" v-if="timeMsg"></msg-time>
- <img v-if="avatarUrl" class="user-avatar avatar" src="../../assets/loading.gif" :originurl="avatarUrl" @click="clickInfo" alt>
- <div v-else
- class="avatar"
- :class="'avatar_bg' + userId % 9"
- :data-name="name && name.slice(0,2).toUpperCase()"
- @click="clickInfo"
- ></div>
- <div class="content">
- <div class="metabar">
- <span class="name" @contextmenu.prevent="onToolBtn($event,'username')">{{name}}</span>
- <span class="admin" v-if="creator == userId">
- <i class="icon-creator" v-if="type === 'me'"></i>
- {{$t('public.owner')}}
- <i class="icon-creator" v-if="type === 'you'"></i>
- </span>
- <span class="admin" v-else-if="adminList.includes(Number(userId))">
- <i class="el-icon-star-on" v-if="type === 'me'"></i>
- {{$t('public.admin')}}
- <i class="el-icon-star-on" v-if="type === 'you'"></i>
- </span>
- <i class="icon-tele" v-if="msgItem.ext_info && msgItem.ext_info.is_tg"></i>
- <span class="time">{{timestamp|formatTimestamp}}</span>
- </div>
- <red-packet
- v-if="msg_type == 4 && msgItem"
- @click.native="$packetGet(msgItem)"
- :info="msgItem">
- </red-packet>
- <template v-else>
- <bubble-wrap
- :isMobile="isMobile"
- :showToolbar="showToolbar"
- @onTouchStartToolBtn="onTouchStartToolBtn"
- @onTouchEndToolBtn="onTouchEndToolBtn"
- @onToolBtn="onToolBtn"
- >
- <i class="loading-icon" v-if="loading"></i>
- <i class="error-icon" v-if="fail" @click="reSend"></i>
- <a :href="content" target="_blank" v-if="msg_type == 1 && meechatType=='mini' && !isMobile">
- <img class="img-msg"
- :style="{width:width,height:height}"
- src="" :originurl="formatUploadImg(content)"
- >
- <i class="pic-loading"></i>
- </a>
- <div v-else-if="msg_type == 1">
- <img
- @click="$showImgPreview(content)"
- class="img-msg"
- :style="{width:width,height:height}"
- src="" :originurl="formatUploadImg(content)"
- >
- <i class="pic-loading"></i>
- </div>
- <video
- class="video-msg"
- :class="{'limit-height': msg_type == 3}"
- controls="controls"
- preload="meta"
- v-else-if="msg_type == 2 || msg_type == 3"
- :src="content"
- ></video>
- <pre v-else class="text" v-html="content"></pre>
- <template v-if="toolBtnType=='username'">
- <ul @touchstart.stop class="pub-pop-toolbar ext-username" v-show="showToolbar">
- <li @click.prevent="handleCopy">{{$t('chat.copy')}}</li>
- </ul>
- </template>
- <template v-else>
- <ul @touchstart.stop class="pub-pop-toolbar username" v-show="showToolbar">
- <li @click.prevent="handleQuote" v-if="msg_type == 0 || msg_type == 4">{{$t('chat.quote')}}</li>
- <li @click.prevent="handleCopy">{{$t('chat.copy')}}</li>
- <!-- <li @click.prevent="handleDel">删除</li> -->
- <li class="split-line" v-if="(isAdmin && type === 'you') || (isAdmin || revoke)"></li>
- <li @click.prevent="handlePingMsg" v-if="isAdmin">{{$t('chat.sticky')}}</li>
- <li @click.prevent="handleBlock" v-if="isAdmin && type === 'you'">{{block?$t('chat.liftaBan'):$t('public.ban')}}</li>
- <li @click.prevent="handleRevoke" v-if="isAdmin || revoke">{{$t('chat.revoke')}}</li>
- </ul>
- </template>
- </bubble-wrap>
- </template>
- </div>
- </div>
- </template>
- <script>
- import dayjs from 'dayjs'
- import msgTime from '@/components/msgItem/time'
- import redPacket from '@/components/msgItem/redPacket'
- import redPackTip from '@/components/msgItem/redPackTip'
- import bubbleWrap from '@/components/msgItem/bubbleWrap'
- import { mapMutations, mapActions, mapState } from 'vuex'
- import { getMeechatType } from '@/util/util'
- export default {
- name: 'msgItem',
- components: {
- msgTime,
- redPacket,
- redPackTip,
- bubbleWrap
- },
- props: {
- msgItem: Object,
- isPrivate: Boolean,
- repealMsg: Boolean,
- joinMsg: String,
- from: [String, Number],
- timeMsg: Boolean,
- avatar: {
- type: String
- },
- name: {
- type: String
- },
- timestamp: [String, Number],
- hash: String,
- content: {
- type: [String, Number, Object]
- },
- userId: [String, Number],
- /**
- * 消息来源 {me: 我发的, you: 其他人发的}
- */
- type: {
- type: String
- },
- /**
- * 消息种类 (1 => 图片, 2 => 视频, 3 => 音频, 4 => 链接, 5 => 红包)
- */
- msg_type: {
- type: [Number, String]
- },
- createTime: [Number],
- loading: [Boolean],
- fail: [Boolean],
- res: [File, Blob],
- isMobile: Boolean,
- isAdmin: Boolean
- },
- data () {
- return {
- showToolbar: false,
- revoke: false,
- block: false,
- revokeTimeAllow: false,
- width: 'auto',
- height: 'auto',
- longTapTimer: null,
- meechatType: getMeechatType(), // meechat版本
- toolBtnType: ''
- }
- },
- watch: {
- content (val, oldVal) {
- if (this.msg_type != 1 && val == oldVal) return
- this.countPicSize()
- }
- },
- computed: {
- ...mapState({
- curSession: state => state.curSession,
- myId: state => state.userId,
- userInfo: state => state.group.userInfo,
- blockList: state => state.group.blockList,
- adminList: state => state.group.adminList,
- members: state => state.group.members,
- creator: state => state.group.creator
- }),
- isLogin () {
- return !!this.myId
- },
- repealStr () {
- if (this.repealMsg) {
- if (!this.from || this.from == this.userId) {
- return `${this.type == 'me' ? this.$t('public.you') : this.name}${this.$t('chat.revokeMsg')}`
- } else if (this.from != this.userId) {
- let admin = this.members[this.from]
- let adminName = admin ? admin.nick_name : this.$t('public.admin')
- return `${adminName}${this.$t('chat.revoked')}${this.name}${this.$t('chat.aMsg')}`
- } else {
- return `${this.name}${this.$t('chat.revokeMsg')}`
- }
- } else {
- return ''
- }
- },
- avatarUrl () {
- let membersCover = this.members[this.userId] && this.members[this.userId].cover_photo
- return membersCover || this.avatar || ''
- }
- },
- beforeMount () {
- if (this.msg_type == 1) {
- this.countPicSize()
- }
- },
- created () {
- },
- methods: {
- ...mapMutations(['setCopyText', 'updateChatInputFocus', 'reSendChatItem', 'setSessionRepeal']),
- ...mapActions([
- 'doRepealPersonMsg',
- 'doRepealGroupMsg',
- 'doBlockUser',
- 'doUnBlockUser',
- 'doPinMsg',
- 'doSendMsg',
- 'doSendFile'
- ]),
- clickInfo () {
- if (!this.isLogin) return
- if (this.meechatType == 'h5') {
- let infoUrl = this.type === 'me' ? '/me' : `/other/${this.userId}`
- this.$router.push(infoUrl)
- } else {
- this.type === 'me' ? this.$showUserInfo() : this.$showOtherInfo(this.userId)
- }
- },
- // 计算图片尺寸
- countPicSize () {
- let rect = /_size([0-9]+)x([0-9]+)/.exec(this.content)
- if (rect) {
- let originalWidth = parseInt(rect[1])
- let originalHeight = parseInt(rect[2])
- let holderWidth = (document.body.offsetWidth - 35) * 0.84
- let scaleX =
- originalWidth > holderWidth ? holderWidth / originalWidth : 1
- let scaleY = originalHeight > 250 ? 250 / originalHeight : 1
- let scale = Math.min(scaleX, scaleY)
- this.width = scale * originalWidth + 'px'
- this.height = scale * originalHeight + 'px'
- }
- },
- formatUploadImg (val) {
- if (/^data:image/.test(val)) return val
- else return `${val}?imageview/0/w/400`
- },
- hideToolbar (event) {
- if (this.showToolbar !== false) {
- this.showToolbar = false
- document.body.removeEventListener('touchstart', this.hideToolbar, false)
- document.body.removeEventListener('click', this.hideToolbar, false)
- document.body.removeEventListener('contextmenu', this.hideToolbar, false)
- }
- },
- /**
- * @des 触发自定义右键菜单
- * @param {string} type [{'username':仅复制}]
- */
- onToolBtn (event, type) {
- this.toolBtnType = type
- if (this.showToolbar) {
- this.hideToolbar(event)
- return
- }
- if (!this.isMobile) {
- setTimeout(() => {
- document.body.addEventListener('click', this.hideToolbar, false)
- document.body.addEventListener('contextmenu', this.hideToolbar, false)
- }, 0)
- }
- this.showToolbar = true
- this.block = this.blockList.some(id => id == this.userId)
- this.revokeTimeAllow =
- Date.now() - parseInt(this.timestamp) < 1e3 * 60 * 3
- this.revoke = this.type === 'me' && this.revokeTimeAllow
- },
- onTouchStartToolBtn (event) {
- clearTimeout(this.longTapTimer)
- this.longTapTimer = setTimeout(() => {
- this.onToolBtn(event)
- }, 800)
- },
- onTouchEndToolBtn (event) {
- clearTimeout(this.longTapTimer)
- setTimeout(() => {
- document.body.addEventListener('touchstart', this.hideToolbar, false)
- document.body.addEventListener('click', this.hideToolbar, false)
- }, 0)
- },
- replaceEmoji (content) {
- let emojiReg = /<img class="emoji" .+?\/>/gi
- return content.replace(emojiReg, function (match) {
- let emoji = match.match(/alt=.+?&*"/g)
- let emojiCont = emoji && emoji[0].replace(/"|alt=|/g, '')
- return emojiCont
- })
- },
- handleQuote () {
- let { name, content } = this
- let newCont = this.replaceEmoji(content)
- let quoteStr = `「${name}:${newCont}」\n- - - - - - - - - - - - - - -\n`
- this.$emit('quoteMsg', quoteStr)
- this.$nextTick(() => {
- this.updateChatInputFocus(true)
- })
- },
- handleCopy () {
- let userSelection
- let selectedText = ''
- if (window.getSelection) { // 现代浏览器
- userSelection = window.getSelection()
- selectedText = userSelection.toString()
- } else if (document.selection) { // IE浏览器 考虑到Opera,应该放在后面
- userSelection = document.selection.createRange()
- selectedText = userSelection.text
- }
- let copyTxt = this.replaceEmoji(selectedText || this.content)
- this.$copyText(copyTxt).then(
- e => {
- this.updateChatInputFocus(true)
- },
- e => {
- console.log('Can not copy')
- }
- )
- this.setCopyText(copyTxt)
- },
- handleShare () {
- this.$showInvite(this.content)
- },
- handleDel () {
- this.$emit('deleteMsg', this.hash)
- },
- handlePingMsg () {
- this.doPinMsg({ hash: this.hash })
- },
- handleRevoke () {
- if (this.isPrivate) {
- this.doRepealPersonMsg({ hash: this.hash }).then((data) => {
- this.$store.commit('setSessionRepeal', {
- me: true,
- sessionId: this.curSession
- })
- this.$store.commit('repealChatItem', {
- hash: this.hash,
- from: this.from
- })
- })
- } else {
- this.doRepealGroupMsg({ hash: this.hash })
- }
- },
- handleBlock () {
- if (this.block) {
- this.doUnBlockUser({ id: this.userId })
- } else {
- this.doBlockUser({ id: this.userId })
- }
- },
- reSend () {
- if (this.msg_type == 0 || this.msg_type == 4) {
- let opt = {
- type: 0,
- msg: this.content,
- createTime: this.createTime
- }
- this.reSendChatItem({ createTime: this.createTime })
- if (this.isPrivate) {
- this.doSendPrivateMsg(opt)
- } else {
- this.doSendMsg(opt)
- }
- } else {
- let opt = {
- res: this.res,
- createTime: this.createTime
- }
- this.reSendChatItem({ createTime: this.createTime })
- this.doSendFile(opt)
- }
- }
- },
- filters: {
- formatTimestamp (val) {
- if (!val) return ''
- return dayjs(val * 1).format('HH:mm')
- }
- }
- }
- </script>
- <style lang="scss">
- @import "./style.scss"
- </style>
|