美文网首页
获取元素距离视口顶部的距离不包含滚动距离 getBounding

获取元素距离视口顶部的距离不包含滚动距离 getBounding

作者: 八妹sss | 来源:发表于2021-04-06 19:32 被阅读0次

说明: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 /^(&nbsp;)+$/.test(n) // 针对空格为&nbsp;的情况
          }) || 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

相关文章

网友评论

      本文标题:获取元素距离视口顶部的距离不包含滚动距离 getBounding

      本文链接:https://www.haomeiwen.com/subject/nxmnqltx.html