左右布局拖拽(VUE2)

javascript 复制代码
<!-- 通用布局:左右拖拽 -->
<template>
  <div :class="className">
    <div class="box">
      <div v-show="isExpand" ref="leftPane" :style="{ width: leftPaneWidth + 'px' }" class="pane left-pane">
        <div class="el-icon-s-fold shrinkage" @click="setExpandState" />
        <slot name="left" />
      </div>

      <div v-show="isExpand" ref="divider" class="divider" @mousedown="startDrag" />

      <div v-show="!isExpand" class="el-icon-s-unfold openThe2" @click="setExpandState" />

      <div class="pane right-pane">
        <slot name="right" />
      </div>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    className: {
      type: String,
      default: 'boss'
    },
    // 左侧布局最小宽度
    minLeftWidth: {
      type: Number,
      default: 100
    },
    // 左侧初始化宽度
    defaultLeftWidth: {
      type: [Number, String],
      default: 260
    },
    // 右侧布局最小宽度
    minRightWidth: {
      type: Number,
      default: 150
    }
  },
  data() {
    return {
      isExpand: true, // 展开收缩state
      dragging: false,
      startDragX: 0,
      // 添加一个变量来记录上次更新的时间戳
      lastUpdate: 0,
      startLeftPaneWidth: 0,
      startRightPaneWidth: 0
    }
  },
  computed: {
    leftPaneWidth() {
      return Math.max(this.startLeftPaneWidth, this.minLeftWidth)
    },
    rightPaneWidth() {
      const availableWidth = this.$el.offsetWidth - this.$refs.divider.offsetWidth - this.leftPaneWidth
      return Math.max(availableWidth, this.minRightWidth, 0)
    }
  },
  watch: {
    defaultLeftWidth(newVal) {
      this.startLeftPaneWidth = this.calculateWidth(newVal)
    }
  },
  mounted() {
    this.initView()
  },
  beforeUnmount() {
    document.removeEventListener('mousemove', this.handleDrag)
    document.removeEventListener('mouseup', this.stopDrag)

    this.$refs.divider.removeEventListener('mouseup', this.dividerMouseupBlur)
  },
  methods: {
    initView() {
      this.startLeftPaneWidth = this.calculateWidth(this.defaultLeftWidth)
      this.startRightPaneWidth = this.$el.offsetWidth - this.$refs.divider.offsetWidth - this.startLeftPaneWidth
      document.addEventListener('mousemove', this.handleDrag)
      document.addEventListener('mouseup', this.stopDrag)

      this.$refs.divider.addEventListener('mouseup', this.dividerMouseupBlur)
    },
    // 设置展开收缩状态
    setExpandState() {
      this.isExpand = !this.isExpand
    },
    calculateWidth(value) {
      if (typeof value === 'number') {
        return value
      } else if (typeof value === 'string' && value.endsWith('%')) {
        return (this.$el.offsetWidth * parseFloat(value)) / 100
      } else {
        throw new Error('Invalid defaultLeftWidth value. Please use a valid number or a percentage string (e.g., "50%" or 100).')
      }
    },

    /**
     * 开始拖拽
     * @param e
     */
    startDrag(e) {
      this.dragging = true
      this.startDragX = e.clientX
      this.startLeftPaneWidth = this.$refs.leftPane.offsetWidth
      this.startRightPaneWidth = this.$el.offsetWidth - this.$refs.divider.offsetWidth - this.startLeftPaneWidth
    },

    /**
     * 拖动中
     * @param e
     */
    handleDrag(e) {
      if (!this.dragging) return

      const currentTime = performance.now()
      const deltaTime = currentTime - this.lastUpdate

      // 使用节流控制更新频率为8.33ms(120帧/秒)
      // 使用节流控制更新频率为6.67ms(150帧/秒)
      // 使用节流控制更新频率为16ms(60帧/秒)
      if (deltaTime >= 8.33) {
        this.lastUpdate = currentTime

        // 使用requestAnimationFrame在下一帧执行更新
        requestAnimationFrame(() => {
          const deltaX = e.clientX - this.startDragX
          const containerWidth = this.$el.offsetWidth
          const newLeftPaneWidth = this.startLeftPaneWidth + deltaX
          const newRightPaneWidth = this.startRightPaneWidth - deltaX

          const maxLeftPaneWidth = containerWidth - this.minLeftWidth
          const minRightPaneWidth = this.minRightWidth

          if (newLeftPaneWidth >= this.minLeftWidth && newLeftPaneWidth <= maxLeftPaneWidth && newRightPaneWidth >= minRightPaneWidth) {
            this.startLeftPaneWidth = newLeftPaneWidth
            this.startRightPaneWidth = newRightPaneWidth
            this.startDragX = e.clientX
          }
        })
      }
    },

    /**
     * 拖动结束
     */
    stopDrag() {
      this.dragging = false
    },

    /**
     * 拖动完成页面失焦
     */
    dividerMouseupBlur() {
      document.activeElement.blur()
    }
  }
}
</script>

<style lang="scss" scoped>
.boss {
  height: calc(100vh - 128px);
}

.box {
  padding-bottom: 0 !important;
  display: flex;
  width: 100%;
  height: 100%;
  background-color: white;
  .b1 {
    padding: 15px;
    width: 100%;
  }
}

.pane {
  flex-grow: 1;
  overflow: hidden;
  height: 100%;

  position: relative;
}

.left-pane {
  flex-grow: 0;
  flex-shrink: 0;
}

.divider {
  width: 4px;
  background-color: #f2f2f2;
  cursor: ew-resize;
  flex-shrink: 0;
}

/* 展开收起按钮 */
.el-icon-s-fold,
.el-icon-s-unfold {
  cursor: pointer;
  font-size: 20px;
}

.el-icon-s-fold {
  position: absolute;
  top: 13px;
  right: 14px;
  z-index: 99;
}

.el-icon-s-unfold {
  padding: 13px 4px 0;
}
</style>

使用方式:

html 复制代码
// DragLeftRight是引入的左右栏组件名称
<template>
  <DragLeftRight>
    <template slot="left">
      <div class="header">
        名称
      </div>
      <div class="simline">
        <div class="point" />
        <div class="point" />
        <div class="point" />
        <div class="line" />
      </div>
      <el-scrollbar style="height: calc(100vh - 135px); overflow: hidden;" :noresize="true">
        <el-tree
          ref="tree"
          :data="data"
          :highlight-current="true"
          node-key="label"
          :props="defaultProps"
          :default-expand-all="true"
          @node-click="handleNodeClick"
        />
      </el-scrollbar>
    </template>
    <template slot="right">
      <div style="padding: 10px 2px;">
        <div class="btn">
          <div />
          <div style="margin-left: 10px; margin-bottom: 10px;" class="expbtn">
            <el-button theme="lcs" type="primary" icon="el-icon-plus" plain @click="PLAdd">新增</el-button>
          </div>
        </div>
        <!-- 表格 -->
        <div class="tableDiv">
          <el-table
            ref="table"
            class="custom-header-table"
            theme="lcs"
            :data="tableDataList"
            :header-cell-style="{ background: '#F5F7FA', height: '30px' }"
            style="width: 100%; margin: 0 auto"
            height="100%"
            align="center"
            row-key="node_code"
            stripe
            border
          >
            <el-table-column type="index" align="center" label="序号" />
            <template>
              <el-table-column
                v-for="(item, index) in viewColumns"
                :key="index"
                :fixed="item.fixed"
                :prop="item.prop"
                :align="item.align"
                :sortable="item.sortable"
                :label="item.label"
                :min-width="item.width"
                :show-overflow-tooltip="true"
              />
            </template>
            <el-table-column
              align="center"
              label="操作"
              width="80"
              fixed="right"
            >
              <template slot-scope="scope">
                <div>
                  <a class="mc" title="修改" @click="editdiaopen(scope.row)"><em class="el-icon-edit" />修改</a>
                </div>
              </template>
            </el-table-column>
          </el-table>
        </div>
      </div>
    </template>
  </DragLeftRight>
</template>
css 复制代码
<style lang="scss" scoped>
.header {
  line-height: 40px;
  font-size: 14px;
  font-weight: 700;
  margin-left: 5px;
}
.simline {
  overflow: hidden;
  display: flex;
  flex-wrap: nowrap;
  // margin-top: 10px;
  .point {
    width: 3px;
    height: 3px;
    border-radius: 50%;
    background: #00b4e1;
    margin-right: 3px;
  }
  .line {
    width: 100%;
    height: 2px;
    background: #00b4e1;
  }
}

::v-deep{
  .el-scrollbar__wrap{
    overflow-x: hidden !important;
  }
  .tableDiv{
    height: calc(100vh - 183px);
  }
}
.pageblock{
  display: flex;
  justify-content: flex-end;
}
</style>
相关推荐
拉不动的猪4 小时前
移动端调试工具VConsole初始化时的加载阻塞问题
前端·javascript·微信小程序
大金乄7 小时前
封装一个vue2的elementUI 表格组件(包含表格编辑以及多级表头)
前端·javascript
Lee川9 小时前
解锁 JavaScript 的灵魂:深入浅出原型与原型链
javascript·面试
swipe9 小时前
从原理到手写:彻底吃透 call / apply / bind 与 arguments 的底层逻辑
前端·javascript·面试
Lee川12 小时前
探索JavaScript的秘密令牌:独一无二的`Symbol`数据类型
javascript·面试
Lee川12 小时前
深入浅出JavaScript事件机制:从捕获冒泡到事件委托
前端·javascript
光影少年12 小时前
async/await和Promise的区别?
前端·javascript·掘金·金石计划
codingWhat12 小时前
如何实现一个「万能」的通用打印组件?
前端·javascript·vue.js
前端Hardy14 小时前
别再无脑用 `JSON.parse()` 了!这个安全漏洞你可能每天都在触发
前端·javascript·vue.js
前端Hardy14 小时前
别再让 `console.log` 上线了!它正在悄悄拖垮你的生产系统
前端·javascript·vue.js