vue3+elementui+js自定义穿梭框布局

自定义穿梭框布局--两侧各自有分页

HTML

html 复制代码
  <!-- 自定义穿梭框布局 -->
        <div class="custom-transfer-container">
          <!-- 左侧面板:待选设备(分页) -->
          <div class="transfer-panel left-panel">
            <div class="panel-header">
              <h3>待选设备</h3>
              <el-input
                v-model="leftFilterText"
                placeholder="输入设备名"
                clearable
                size="small"
                style="width: 200px"
              >
                <template #prefix>
                  <Search />
                </template>
              </el-input>
            </div>
            <div class="panel-body">
              <el-table
                ref="leftTableRef"
                :data="filteredLeftData"
                height="420"
                @selection-change="handleLeftSelectionChange"
              >
                <el-table-column type="selection" width="55" />
                <el-table-column prop="deviceName" label="设备名称" />
              </el-table>
            </div>
          </div>
          <!-- 中间操作按钮 -->
          <div class="transfer-buttons">
            <el-button
              type="primary"
              :disabled="leftSelected.length === 0"
              size="small"
              @click="addToRight"
            >
              <el-icon>
                <ArrowRightBold />
              </el-icon>
            </el-button>
            <el-button
              type="primary"
              :disabled="rightSelected.length === 0"
              size="small"
              @click="removeFromRight"
            >
              <el-icon>
                <ArrowLeftBold />
              </el-icon>
            </el-button>
          </div>
          <!-- 右侧面板:已选设备(不分页,滚动) -->
          <div class="transfer-panel right-panel">
            <div class="panel-header">
              <h3>已选设备</h3>
            </div>
            <div class="panel-body">
              <el-table
                ref="rightTableRef"
                :data="rightPanelData"
                height="420"
                @selection-change="handleRightSelectionChange"
              >
                <el-table-column type="selection" width="55" />
                <el-table-column prop="deviceName" label="设备名称" />
              </el-table>
            </div>
          </div>
        </div>
        <!-- 左侧分页 -->
        <div style="display: flex; justify-content: space-between; width: 100%">
          <div class="left-pagination">
            <el-pagination
              v-if="associationTotal > 0"
              :current-page="associationPage"
              :page-size="associationPageSize"
              :total="associationTotal"
              layout="total, prev, pager, next, sizes"
              size="small"
              @current-change="
                (page) => {
                  associationPage = page;
                }
              "
              @size-change="
                (size) => {
                  associationPageSize = size;
                  associationPage = 1;
                }
              "
            />
          </div>
          <!-- 右侧分页 -->
          <div class="right-pagination">
            <el-pagination
              v-if="rightPanelTotal > 0"
              :current-page="rightPage"
              :page-size="rightPageSize"
              :total="rightPanelTotal"
              layout="total, prev, pager, next, sizes"
              size="small"
              @current-change="
                (page) => {
                  rightPage = page;
                }
              "
              @size-change="
                (size) => {
                  rightPageSize = size;
                  rightPage = 1;
                }
              "
            />
          </div>
        </div>

JS

html 复制代码
const dataShuttle = ref<DevicePageVO[]>([]); // 待选设备的数据
const valueShuttle = ref<number[]>([]); // 已选设备的 ID 列表

// 左侧面板相关
const leftTableRef = ref();
const leftFilterText = ref("");
const leftSelected = ref<DevicePageVO[]>([]);

// 右侧面板相关
const rightTableRef = ref();
const rightSelected = ref<DevicePageVO[]>([]);

// 计算左侧面板数据(分页后的数据)
const leftPanelData = computed(() => {
  const startIndex = (associationPage.value - 1) * associationPageSize.value;
  const endIndex = startIndex + associationPageSize.value;
  return dataShuttle.value.slice(startIndex, endIndex);
});

// 计算过滤后的左侧数据
const filteredLeftData = computed(() => {
  if (!leftFilterText.value) return leftPanelData.value;
  return leftPanelData.value.filter((item) =>
    item.deviceName?.toLowerCase().includes(leftFilterText.value.toLowerCase())
  );
});

// 计算右侧面板数据(分页后的已选择设备)
const rightPanelData = computed(() => {
  const allRightData = dataShuttle.value.filter((item) => valueShuttle.value.includes(item.id));
  const startIndex = (rightPage.value - 1) * rightPageSize.value;
  const endIndex = startIndex + rightPageSize.value;
  return allRightData.slice(startIndex, endIndex);
});

// 计算右侧面板总数
const rightPanelTotal = computed(() => {
  return dataShuttle.value.filter((item) => valueShuttle.value.includes(item.id)).length;
});

// 左侧面板选择变化
const handleLeftSelectionChange = (selection: DevicePageVO[]) => {
  leftSelected.value = selection;
};

// 右侧面板选择变化
const handleRightSelectionChange = (selection: DevicePageVO[]) => {
  rightSelected.value = selection;
};

// 添加到右侧
const addToRight = () => {
  const newIds = leftSelected.value
    .map((item) => item.id)
    .filter((id) => id !== undefined) as number[];
  valueShuttle.value = [...valueShuttle.value, ...newIds];
  leftSelected.value = [];
  leftTableRef.value?.clearSelection();
};

// 从右侧移除
const removeFromRight = () => {
  const removeIds = rightSelected.value
    .map((item) => item.id)
    .filter((id) => id !== undefined) as number[];
  valueShuttle.value = valueShuttle.value.filter((id) => !removeIds.includes(id));
  rightSelected.value = [];
  rightTableRef.value?.clearSelection();
};
// 表格数据
async function loadAssociationTable() {
  loadingDialog.value = true;
  loadingAssociationTable.value = true;
  try {
    // 获取所有数据,不分页,用于穿梭框显示
    const res = await DeviceAPI.getPage({
      roomIds: searchKeyword.value,
      pageNum: 1,
      pageSize: 1000, // 设置一个较大的值来获取所有数据
    });

    dataShuttle.value = res.list.map((item) => ({
      ...item,
      key: item.id,
    })) as DevicePageVO[];

    // 设置已选中的设备
    valueShuttle.value = dataShuttle.value.filter((item) => item.isMaster).map((item) => item.id);

    // 设置总数为实际数据总数
    associationTotal.value = dataShuttle.value.length;

    // console.log("dataShuttle.value", dataShuttle.value);
  } finally {
    loadingAssociationTable.value = false; // 如果有加载状态变量
    loadingDialog.value = false;
  }
}
// 自定义渲染函数
const renderContent = (h: any, option: DevicePageVO) => {
  // 使用 h 函数创建多列布局
  return h("div", { class: "custom-transfer-item" }, [
    h("span", { class: "column device-name" }, option.deviceName), // 设备名称
  ]);
};

style

html 复制代码
.device-dialog-content {
  max-height: 600px;
  margin-bottom: 30px;
  // overflow-x: hidden;
}
.search-area {
  margin-top: 10px;
  margin-bottom: 20px;
}/* 自定义穿梭框容器 */
.custom-transfer-container {
  display: flex;
  gap: 20px;
  align-items: flex-start;
  margin: 20px 0;
}
/* 穿梭框面板 */
.transfer-panel {
  flex: 1;
  border: 1px solid var(--el-border-color);
  border-radius: 6px;
  // background: #fff;
  background-color: var(--el-bg-color);
}/* 面板头部 */
.panel-header {
  padding: 12px 16px;
  border-bottom: 1px solid var(--el-border-color);
  // background: #f5f7fa;
  background-color: var(--el-bg-color);
  display: flex;
  justify-content: space-between;
  align-items: center;
}/* 面板内容 */
.panel-body {
  padding: 0;
}/* 中间按钮区域 */
.transfer-buttons {
  display: flex;
  flex-direction: column;
  gap: 10px;
  justify-content: center;
  padding: 20px 0;
  margin-top: 18%;
}

.transfer-buttons .el-button {
  width: 32px;
  height: 32px;
  padding: 0;
  margin-left: 0px;
}
/* 分页样式 */
.left-pagination {
  text-align: center;
  float: left;
}
相关推荐
徐同保19 分钟前
上传文件,在前端用 pdf.js 提取 上传的pdf文件中的图片
前端·javascript·pdf
怕浪猫20 分钟前
React从入门到出门第四章 组件通讯与全局状态管理
前端·javascript·react.js
博主花神21 分钟前
【React】扩展知识点
javascript·react.js·ecmascript
内存不泄露26 分钟前
基于Spring Boot和Vue 3的智能心理健康咨询平台设计与实现
vue.js·spring boot·后端
欧阳天风27 分钟前
用setTimeout代替setInterval
开发语言·前端·javascript
EndingCoder31 分钟前
箭头函数和 this 绑定
linux·前端·javascript·typescript
沐墨染34 分钟前
大型数据分析组件前端实践:多维度检索与实时交互设计
前端·elementui·数据挖掘·数据分析·vue·交互
xkxnq37 分钟前
第一阶段:Vue 基础入门(第 11 天)
前端·javascript·vue.js
小oo呆43 分钟前
【自然语言处理与大模型】LangGraphV1.0入门指南:核心组件Nodes
前端·javascript·easyui
行走的陀螺仪1 小时前
在UniApp H5中,实现路由栈的持久化
前端·javascript·uni-app·路由持久化·路由缓存策略