说明:Element.getBoundingClientRect() 方法返回元素的大小及其相对于视口的位置。
注意:如果是标准盒子模型,元素的尺寸等于width/height + padding + border-width的总和。如果box-sizing: border-box,元素的的尺寸等于 width/height。
以下示例用于实现,弹框跟随按钮位置显示
<template>
<div class="page-box" v-loading="loading"> <section class="cont">
<div class="table-wrapper">
<el-table
tooltip-effect="light"
:data="msgList"
ref='tableInfo'
style="width: 100%">
<el-table-column
prop="user"
label="用户头像/昵称"
width="160">
<template slot-scope="scope">
<div class="user-info">
<div class="avatar"
:style="{backgroundImage: `url('${scope.row.avatar}')`}"></div>
<div class="nickname">{{scope.row.nickName}}</div>
</div>
</template>
</el-table-column>
<el-table-column
prop="content"
label="消息"
min-width="350">
</el-table-column>
<el-table-column
prop="status"
label="状态"
min-width="80">
<template slot-scope="scope">
<div class="msg-status">{{scope.row.replyMsgId ? '已回复' : '自动回复'}}</div>
</template>
</el-table-column>
<!-- 操作 -->
<el-table-column
fixed="right"
width="140px"
label="操作">
<template slot-scope="scope">
<span class="caozuo"
@click="handleJump('msgDetail', scope.row)">详情</span>
<span class="caozuo"
v-if="scope.row.canReply"
@click="showReplayDialog($event,scope.row)">快速回复</span>
</template>
</el-table-column>
</el-table>
<el-pagination
v-show='msgList.length'
style="text-align:center;"
background
:page-size="pageSize"
:page-sizes="pageSizesG"
@size-change="handleSizeChange"
:current-page="page"
@current-change="handleCurrentChange"
layout="sizes, prev, pager, next"
:total="count">
</el-pagination>
</div>
</section>
<!-- 快速回复 -->
<div class="repaly-mask"
ref="repalyMask"
v-show="isShowReplayDialog">
<materialEdit
:keywordType="msgType"
:metInfo="meterialInfo"
:isShowBorder="false"
:tootListWidth="'330px'"
@update="updateMedia"/>
<div class="footer">
<el-button size="small" plain @click="cancelReply()">取 消</el-button>
<el-button size="small" type="primary" @click="saveReply()">确 定</el-button>
</div>
</div>
</div>
</template>
<script>
import materialEdit from '@/views/Subscription/components/materialEdit'
export default {
name: 'allMsg',
components: {
materialEdit
},
data () {
return {
loading: false,
page: 1,
pageSize: 10,
count: 0,
msgList: [],
// 自动回复相关
isShowReplayDialog: false,
isLoading: false,
currOpenId: '',
currMsgId: '',
msgType: 'text',
meterialInfo: {},
currBtnDom: null, // 记录按钮元素,在窗口变化时回复弹框位置紧跟按钮
}
},
methods: {
// 获取消息列表
fetchMsgList () {
if (!sessionStorage.getItem('wechatId')) {
return this.showWarning('请先绑定公众号')
}
if (!this.timeValue) {
this.timeValue = []
}
let startT = this.timeValue.length ? this.timeValue[0] : null
let endT = this.timeValue.length ? this.timeValue[1] : null
let startTime = startT ? this.formatDate(new Date(startT), 'yyyyMMdd') : null
let endTime = endT ? this.formatDate(new Date(endT), 'yyyyMMdd') : null
let appid = sessionStorage.getItem('appid')
let url = `${this.SERVICE_WECHAT}/msg/${appid}/msg/list`
if (this.loading) {
return
}
this.loading = true
this.get(url, {
startTime: startTime,
endTime: endTime,
msgType: this.searchMsgType === 'all' ? null : this.searchMsgType,
currentPage: this.page,
pageSize: this.pageSize
}).then(res => {
this.loading = false
if (res.data.code === 200) {
let data = res.data.data
if (data) {
this.count = +data.total
if (data.list && data.list.length) {
this.msgList = data.list
} else {
this.msgList = []
}
} else {
this.msgList = []
}
}
}).catch(e => {
this.loading = false
this.handleError(e)
})
},
hanldeSearch () {
this.page = 1
this.fetchMsgList()
},
// pageSize变化
handleSizeChange (val) {
this.page = 1
this.pageSize = val
this.fetchMsgList()
},
// 页码变化
handleCurrentChange (val) {
this.page = val
this.fetchMsgList()
},
// 点击跳转
handleJump (type, info) {
switch (type) {
case 'browseMsg':
this.linkTo({name: 'msgBrowse'})
break
case 'msgDetail':
this.linkTo({name: 'msgDetail', params: {fansId: info.fansId}, query: {openId: info.sender}})
break
}
},
// ----------------------- 选素材 -----------------------
showReplayDialog (event, info) {
this.currBtnDom = event.target
let windowH = window.innerHeight
// 回复弹框的宽高
let repalyMaskDomW = 510
let repalyMaskDomH = 268
// 事件元素距离可视区域左侧的距离
let currBtnDomLeft = this.currBtnDom.getBoundingClientRect().left
// 事件元素距离可视区域上侧的距离
let currBtnDomTop = this.currBtnDom.getBoundingClientRect().top
let left
let top
let repalyMaskDom = this.$refs.repalyMask
if (windowH - this.currBtnDom.getBoundingClientRect().top > repalyMaskDomH) {
repalyMaskDom.classList.remove('down')
repalyMaskDom.classList.add('up')
left = currBtnDomLeft - repalyMaskDomW + 48
top = currBtnDomTop + 24
} else {
repalyMaskDom.classList.remove('up')
repalyMaskDom.classList.add('down')
left = currBtnDomLeft - repalyMaskDomW + 48
top = currBtnDomTop - repalyMaskDomH - 10
}
repalyMaskDom.style.top = `${top}px`
repalyMaskDom.style.left = `${left}px`
this.currOpenId = info.sender
this.currMsgId = info.id
this.isShowReplayDialog = true
},
// 选择素材
updateMedia (msgType, info) {
this.msgType = msgType
this.meterialInfo = info
},
// 保存回复
saveReply () {
let params = {
openid: this.currOpenId,
replyMsgId: this.currMsgId,
msgType: this.msgType,
wxAppid: sessionStorage.getItem('appid')
}
switch (this.msgType) {
case 'text':
// 去除空格后字符串的内容是否为空
let blank = this.meterialInfo.content && (this.meterialInfo.content.split(' ').every(n => {
return /^( )+$/.test(n) // 针对空格为 的情况
}) || this.meterialInfo.content.trim().length === 0)
// 表情图片转code
let contStr = this.meterialInfo.content && this.imgChangeEmojiCode(this.meterialInfo.content)
if (!this.meterialInfo.content || blank) {
return this.$message({
message: '请输入文字',
type: 'warning'
})
} else if (contStr.length > 600) {
return this.$message({
message: '文本内容最多600个字',
type: 'warning'
})
}
params.content = contStr || null
break
case 'news':
if (!this.meterialInfo.newsId) {
return this.$message({
message: '请选择素材',
type: 'warning'
})
}
params.mediaId = this.meterialInfo.newsId || null
break
case 'image':
case 'video':
case 'voice':
if (!this.meterialInfo.id) {
return this.$message({
message: '请选择素材',
type: 'warning'
})
}
params.mediaId = this.meterialInfo.id || null
break
default:
break
}
if (this.isLoading) {
return
}
this.isLoading = true
let url = `${this.SERVICE_WECHAT}/msg/reply`
this.posts(url, params).then(res => {
this.isLoading = false
if (res.data.code === 200) {
this.cancelReply()
this.fetchMsgList()
}
}).catch(e => {
this.isLoading = false
this.handleError(e)
})
},
// 取消回复,重置素材
cancelReply () {
this.isShowReplayDialog = false
this.currBtnDom = null
this.currOpenId = ''
this.currMsgId = ''
this.msgType = 'text'
this.meterialInfo = {
mediaType: 'text',
content: ''
}
},
// -------------------预览图片-------------------
handlePreviewImg (image) {
if (image && image.picUrl) {
let imgSrc = this.getWxImg(image.picUrl.split('?') && image.picUrl.split('?')[0])
this.currImgSrc = imgSrc
this.showImgPreview = true
}
},
closePreviewImg () {
this.showImgPreview = false
this.currImgSrc = ''
},
// --------------------预览视频---------------------
handlePreviewVideo (video) {
if (video && (video.mediaUrl || video.url)) {
this.currVideoSrc = video.mediaUrl || video.url
this.showVideoPreview = true
}
},
closePreviewVideo () {
this.showVideoPreview = false
this.currVideoSrc = ''
},
// -------------------文本消息过滤表情--------------
// 文本消息(微信表情)
handleTextCont (msgInfo) {
return `【${msgInfo.msgTypeInfo}】${this.emojiCodeChangeImg(msgInfo.content)}`
},
// 菜单消息(触发的类型、微信表情)
handleMenuCont (msgInfo) {
// 点击菜单跳转
if (msgInfo.msgType === 'view') {
return `【${msgInfo.msgTypeInfo}】${msgInfo.menuMsg.url}`
} else {
// 点击自定义菜单
switch (msgInfo.menuMsg && msgInfo.menuMsg.msgType) {
case 'text':
return `${this.emojiCodeChangeImg(msgInfo.menuMsg.menuMsgInfo)}`
default:
return `${msgInfo.menuMsg.menuMsgInfo}`
}
}
},
replayMask () {
if (this.currBtnDom) {
let windowH = window.innerHeight
// 回复弹框的宽高
let repalyMaskDomW = 510
let repalyMaskDomH = 268
// 事件元素距离可视区域左侧的距离
let currBtnDomLeft = this.currBtnDom.getBoundingClientRect().left
// 事件元素距离可视区域上侧的距离
let currBtnDomTop = this.currBtnDom.getBoundingClientRect().top
let left
let top
let repalyMaskDom = this.$refs.repalyMask
if (windowH - this.currBtnDom.getBoundingClientRect().top > repalyMaskDomH) {
repalyMaskDom.classList.remove('down')
repalyMaskDom.classList.add('up')
left = currBtnDomLeft - repalyMaskDomW + 48
top = currBtnDomTop + 24
} else {
repalyMaskDom.classList.remove('up')
repalyMaskDom.classList.add('down')
left = currBtnDomLeft - repalyMaskDomW + 48
top = currBtnDomTop - repalyMaskDomH - 10
}
repalyMaskDom.style.top = `${top}px`
repalyMaskDom.style.left = `${left}px`
}
},
// 日期格式转换
formatDate (date, fmt) {
if (typeof date === 'string') {
return date
}
if (!fmt) fmt = 'yyyy-MM-dd hh:mm:ss'
if (!date || date == null) return null
var o = {
'M+': date.getMonth() + 1, // 月份
'd+': date.getDate(), // 日
'h+': date.getHours(), // 小时
'm+': date.getMinutes(), // 分
's+': date.getSeconds(), // 秒
'q+': Math.floor((date.getMonth() + 3) / 3), // 季度
'S': date.getMilliseconds() // 毫秒
}
if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length))
for (var k in o) {
if (new RegExp('(' + k + ')').test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)))
}
return fmt
}
},
filters: {
handleSource (type) {
switch (Number(type)) {
case 1:
return '菜单'
}
}
},
created () {
const end = new Date()
const start = new Date()
end.setTime(new Date().getTime())
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
this.timeValue = [start, end]
this.fetchMsgList()
},
mounted () {
window.addEventListener('resize', this.replayMask)
},
beforeDestroy () {
window.addEventListener('resize', this.replayMask)
}
}
</script>
<style lang="stylus" scoped>
.page-box
height 100%
overflow-y auto
box-sizing border-box
&::-webkit-scrollbar
width 0
height 0
&::-webkit-scrollbar-thumb
border-radius 4px
-webkit-box-shadow inset 0 0 5px rgba(0,0,0,0.2);
background #CCE5FF
&::-webkit-scrollbar-track
background transparent
.mt-title
padding-bottom: 20px;
padding-top: 13px;
font-weight: 700;
font-size: 14px;
background: #fff;
.cont
width 100%
min-height calc(100% - 47px)
background #f2f3f6
padded_box(border-box, 10px)
border-radius 6px
/*头部样式*/
.header
width 100%
display flex
align-items center
justify-content space-between
background #fff
padded_box(border-box, 15px 20px)
border-radius 8px
.header-left, .header-right
display flex
align-items center
.ml20
margin-left 20px
.search-select-box
width 140px
>>> .el-input__inner
height 30px
line-height 30px
>>> .el-input__icon
line-height 30px
.send-time
display flex
align-items center
.label
font-size 12px
.search-date-picker
width 240px
height 30px
.text-clicks
font-size 12px
margin-left 10px
.browse-btn
height: 30px;
line-height 30px
text-align center
background: #FFFFFF;
border-radius: 4px;
border: 1px solid #E9EAED;
padded_box(border-box, 0 16px)
font-size: 12px;
color: #5E5E66;
.table-wrapper
width 100%
background #fff
padded_box(border-box, 15px 20px)
border-radius 8px
margin-top 10px
>>> .el-table td
padding 20px 0
// 用户信息
.user-info
width 100%
padding-left 20px
.avatar
width 65px
height 65px
border-radius 4px
background-repeat no-repeat
background-position center
background-size 100%
.nickname
width 65px
line-height 14px
text-align center
font-size 14px
color #5E5E66
overflow hidden
text-overflow ellipsis
white-space nowrap
margin-top 10px
// 消息内容
.msg-box
width 100%
padded_box(border-box, 0 10px)
.msg-wrapper
width 100%
max-width 554px
min-height 90px
background: #f9f9fc;
border: 1px solid #E9EAED;
border-radius 8px
padded_box(border-box, 10px 18px)
position relative
&:hover
background #fff
&::before
content ''
display block
width 0
height 0
border-color transparent #f9f9fc transparent transparent
border-style solid
border-width 6px 8px 6px 0px
position absolute
left -6px
top 16px
z-index 2
&::after
content ''
display block
width 0
height 0
border-color transparent #E9EAED transparent transparent
border-style solid
border-width 6px 8px 6px 0px
position absolute
left -8px
top 16px
z-index 1
// 文本消息
.msg-text
width 100%
.msg-cont
width 100%
height 48px
font-size: 14px;
color: #5E5E66;
line-height: 24px;
ellipsis(2)
word-break break-all
margin-bottom 3px
>>> a
color #4c84ff
.creat-time
font-size: 14px;
color: #888B9C;
line-height: 22px;
text-align right
// 文本以外的消息
.msg-other
width 100%
display flex
justify-content space-between
position relative
.creat-time
font-size: 14px;
color: #888B9C;
line-height: 22px;
text-align right
position absolute
right 0
bottom 0
.msg-cont
width 100%
display flex
// 图片、视频图文
.bg
width 123px
height 69px
background-repeat no-repeat
background-position center
background-size cover
background-color #ddd
border-radius 4px
position relative
.mask
width 100%
height 100%
background: rgba(0, 0, 0, 0.3) url('../../assets/img/enterprise/video_ic_play@2x.png') no-repeat center / 26px;
border-radius: 3px;
border: 1px solid #EBEEF7;
// 图文
.title
width calc(100% - 123px)
max-height 44px
line-height 22px
font-size: 14px;
color: #5E5E66;
ellipsis(2)
word-break break-all
margin-left 10px
// 音频
.cont-audio
width 112px
height 35px
background #96EC69 url('~assets/img/enterprise/icon_left_audio_play3@2x.png') no-repeat 10px center/24px
border-radius 4px
margin-top 4px
position relative
&.icon-paly
animation: fadeInOut 1.5s infinite;
@keyframes fadeInOut{
0% {
background-image:url('~assets/img/enterprise/icon_left_audio_play1@2x.png');
}
25% {
background-image:url('~assets/img/enterprise/icon_left_audio_play2@2x.png');
}
50% {
background-image:url('~assets/img/enterprise/icon_left_audio_play3@2x.png');
}
75% {
background-image:url('~assets/img/enterprise/icon_left_audio_play2@2x.png');
}
100% {
background-image:url('~assets/img/enterprise/icon_left_audio_play1@2x.png');
}
}
&::before
content ''
display block
width 0
height 0
border-color transparent #96EC69 transparent transparent
border-style solid
border-width 6px 8px 6px 0px
position absolute
left -8px
top 50%
transform translate(0, -50%)
// 位置
.icon-location
width 68px
height 68px
background url('../../assets/img/icon-location@2x.png') no-repeat center/ 100%
.address
width 200px
display flex
flex-direction column
justify-content center
.area
width 100%
height: 22px;
font-size: 14px;
color: #5E5E66;
line-height: 22px;
no-wrap()
.deatil
width 100%
height: 22px;
font-size: 14px;
color: #888B9C;
line-height: 22px;
no-wrap()
.caozuo
color #409eff
cursor pointer
& + .caozuo
margin-left 20px
.repaly-mask
// width 484px
width 510px
background #fff
box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.14);
border: 1px solid #ECEEF5;
box-sizing border-box
border-radius 6px
position fixed
top 280px
right 122px
z-index 2000
&::before
content ''
display block
width 0
height 0
border-style solid
position absolute
right 16px
z-index 4
&::after
content ''
display block
width 0
height 0
border-style solid
position absolute
right 16px
&.up::before
border-color: transparent transparent #fff transparent;
border-width 0 6px 6px 6px
top -6px
&.up::after
border-color: transparent transparent #eceef5 transparent;
border-width 0 6px 6px 6px
top -8px
&.down::before
border-color: #fff transparent transparent transparent;
border-width 6px 6px 0 6px
bottom -6px
&.down::after
border-color: #eceef5 transparent transparent transparent;
border-width 6px 6px 0 6px
bottom -8px
.footer
width 100%
height 50px
line-height 50px
text-align right
padded_box(border-box, 0 18px)
position relative
&::before
content ''
width calc(100% - 36px)
height 1px
background #ECEDF2
position absolute
top 0
left 50%
transform translate(-50%, 0)
</style>
image.png
image.png
网友评论