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;
}
相关推荐
KeroroLX25 分钟前
uniapp项目中使用echarts
javascript·uni-app·echarts
Ulyanov44 分钟前
基于Impress.js的3D概念地图设计与实现
开发语言·前端·javascript·3d·ecmascript
jiayong231 小时前
Vue 3 面试题 - TypeScript 与工程化
前端·vue.js·typescript
A南方故人1 小时前
一个用于实时检测 web 应用更新的 JavaScript 库
开发语言·前端·javascript
悟能不能悟1 小时前
postman怎么获取上一个接口执行完后的参数
前端·javascript·postman
koiy.cc1 小时前
新建 vue3 项目
前端·vue.js
qq_12498707531 小时前
基于springboot+vue的家乡特色旅游宣传推荐系统(源码+论文+部署+安装)
java·前端·vue.js·spring boot·毕业设计·计算机毕设·计算机毕业设计
pas1361 小时前
38-mini-vue 实现解析 element
前端·javascript·vue.js
奔跑的web.1 小时前
前端使用7种设计模式的核心原则
前端·javascript·设计模式·typescript·vue
子春一2 小时前
Flutter for OpenHarmony:构建一个专业级 Flutter 节拍器,深入解析定时器、状态同步与音乐节奏交互设计
javascript·flutter·交互