基于element的自定义组件实现选择多个数据项的弹出层多选组件

SelectMultiple 组件:多选弹出层的实现与使用

组件功能

SelectMultiple 组件是一个用于选择多个数据项的弹出层组件。它允许用户从一个列表中选择多个项,并可以将这些项移动到另一个列表中。用户可以通过搜索来筛选左侧的选项,并且可以将左侧的所有选项一次性移动到右侧。右侧列表显示已选中的项,用户可以选择清空这些项或确认选择。

组件效果

在这里插入图片描述

请求数据格式

在这里插入图片描述

组件相关代码

<template>
  <div>
    <div class="mask" v-show="visible"></div>
    <el-popover placement="bottom" width="500" trigger="manual" style="padding: 0" v-model="visible"
                overlay="false">
      <el-row>
        <el-col :span="12">
          <div style="border-right: 1px solid #DCDFE6;">
            <el-card class="box-card" shadow="never">
              <div slot="header" class="clearfix">
                <span>字段</span>
              </div>
              <el-input size="small"
                        style="margin-top: 2px;width: 95%"
                        prefix-icon="el-icon-search"
                        v-model="filterText"
                        clearable
                        placeholder="请输入查询内容"/>
              <div class="items" v-loading="loading">
                <template v-for="o in leftData">
                  <el-tooltip class="item" effect="dark" :content="o.label" placement="top" :open-delay="300">
                    <div v-if="!filterText || o.label.includes(filterText)"
                         @click="leftClick(o)"
                         :key="o.id"
                         :class="rightData.findIndex(i => i.id === o.id) > -1?'text textChecked':'text'">
                      {{ o.label.length > 14 ? o.label.substring(0, 14) + '...' : o.label }}
                    </div>
                  </el-tooltip>
                </template>
              </div>
            </el-card>
            <el-button size="mini" style="width: 95%;margin-bottom: 2px"
                       @click="leftClickAll">添加左侧全部字段值
            </el-button>
          </div>
        </el-col>
        <el-col :span="12">
          <div>
            <el-card class="box-card" shadow="never" style="border-bottom: 1px solid #DCDFE6;">
              <div slot="header" class="clearfix">
                <span>已添加({{ this.rightData.length }})</span>
              </div>
              <div class="items" style="height: 530px">
                <template v-for="o in rightData">
                  <el-tooltip class="item" effect="dark" :content="o.label" placement="top" :open-delay="300">
                    <div :key="o.id" class="text">
                      {{ o.label.length > 14 ? o.label.substring(0, 14) + '...' : o.label }}
                    </div>
                  </el-tooltip>
                </template>
              </div>
            </el-card>
            <el-button type="text" size="mini" icon="el-icon-delete" style="float: right;margin:3px 8px 0"
                       @click="rightClear">清空
            </el-button>
          </div>
        </el-col>
      </el-row>

      <div style="text-align: right;border-top: 1px solid #DCDFE6;padding: 10px 0 0">
        <el-button size="mini" @click="visible = false">取消</el-button>
        <el-button type="primary" size="mini" @click="handleConfirm">确定</el-button>
      </div>
      <el-input slot="reference" placeholder="请选择"
                :value="checkedName.length > 10 ? checkedName.substring(0, 10) + '...' : checkedName"
                :readonly="visible"
                clearable
                @clear="rightClear"
                @focus="visible = !visible">
      </el-input>
    </el-popover>
  </div>
</template>

<script>
import request from "@/utils/request";

/**
 * *****使用方法*****
 *
 * 在父组件中添加引用
 *
 * <select-multiple data-url="/monitor/operlog/list" :props="{ id: 'operId', label: 'title' }" v-model="ids"/>
 */
export default {
  name: 'SelectMultiple',
  props: {
    dataUrl: {
      type: String,
      default: null
    },
    props: {
      type: Object,
      default: () => {
        return {
          id: 'id',
          label: 'label'
        }
      }
    }
  },
  data() {
    return {
      visible: false,
      leftData: [],
      rightData: [],
      filterText: null,
      checkedName: '',
      loading: false,
    };
  },
  watch: {
    visible(val) {
      if (val && this.dataUrl) {
        this.getLeftData();
      }
    },
  },
  methods: {
    getLeftData() {
      this.loading = true;
      request({url: this.dataUrl, method: 'get', params: {pageNum: 1, pageSize: 1000}})
        .then(response => {
          const list = response.data;
          this.leftData = this.transformData(list, this.props);
          this.loading = false;
        })
        .catch(error => {
          console.error('Failed to fetch data:', error);
          this.loading = false;
        });
    },
    handleConfirm() {
      this.checkedName = this.rightData.map(item => item.label).join(',');
      const ids = this.rightData.map(item => item.id);
      this.$emit('input', ids)
      this.visible = false
    },
    transformData(data, fieldName) {
      if (!Array.isArray(data)) {
        console.error('Invalid data passed to transformData');
        return [];
      }
      return data.map(item => ({
        id: item[fieldName.id],
        label: '测试数据'+item[fieldName.id],
      }));
    },

    leftClick(item) {
      if (!item || typeof item !== 'object') {
        console.error('Invalid item passed to leftClick');
        return;
      }
      //存在删除,不存在添加
      const existingItemIndex = this.rightData.findIndex(i => i.id === item.id);
      if (existingItemIndex > -1) {
        this.rightData.splice(existingItemIndex, 1);
      } else {
        this.rightData.push(item);
      }
    },
    leftClickAll() {
      if (!Array.isArray(this.leftData)) {
        console.error('leftData is not an array');
        return;
      }
      this.rightData = [...this.leftData];
    },
    rightClear() {
      this.rightData = [];
      this.checkedName = '';
      this.$emit('input', [])
    }
  }
}
</script>
<style scoped>
.mask {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.1);
  z-index: 2000;
}

.text {
  color: #606266;
  font-size: 14px;
  padding: 5px 0 5px 10px;
}

.text:hover {
  background-color: #f6f9ff;
  cursor: pointer;
}

.textChecked {
  background-color: #f6f9ff;
  color: #409eff;
  font-weight: 700;
}

.items {
  height: 500px;
  overflow-y: auto;
}

.box-card {
  border: 0;
  border-radius: 0;

  > .el-card__header {
    padding: 5px 10px;
    min-height: auto;
  }

  > .el-card__body {
    padding: 0 1px 0 0;
  }
}
</style>

主要功能特点

  1. 数据加载: 当弹出层显示时,自动从指定 URL 加载数据。
  2. 数据过滤: 用户可以在左侧列表中输入搜索文本,以过滤显示的数据项。
  3. 数据移动: 用户可以点击左侧的数据项将其移动到右侧,或者点击“添加左侧全部字段值”按钮将所有左侧数据项一次性移动到右侧。
  4. 数据清除: 用户可以点击“清空”按钮来清除右侧列表中的所有数据项。
  5. 确认与取消: 用户可以选择“确定”来提交当前的选择结果,或者选择“取消”来关闭弹出层而不保存任何更改。

使用方法

引入组件

首先确保已经在项目中安装并配置了 Element UI 和 Vue.js。然后,在父组件中引入 SelectMultiple 组件。

// 在 Vue 文件中引入 SelectMultiple 组件 
import SelectMultiple from './SelectMultiple.vue';

export default {
  components: {SelectMultiple,}, data() {
    return {
      selectedIds: [], // 用于存储选中的数据项 ID 
    };
  },
};

添加组件引用

在父组件的模板中添加 SelectMultiple 组件引用,并传递必要的属性和绑定 v-model


<template>
  <div> <!-- 添加 SelectMultiple 组件 -->
    <select-multiple data-url="/select/list" :props="{ id: 'operId', label: 'title' }" v-model="selectedIds"/>
  </div>
</template>
  • data-url: 数据源 URL,用于获取左侧列表的数据。
  • props: 对象,指定数据项的 ID 和标签字段。
  • v-model: 绑定的模型数据,通常用于存储选中的数据项 ID 数组。

示例代码

下面是一个完整的示例代码,展示了如何在 Vue 组件中使用 SelectMultiple 组件。

<template>
  <div> <!-- 添加 SelectMultiple 组件 -->
    <select-multiple data-url="/monitor/operlog/list" :props="{ id: 'operId', label: 'title' }" v-model="selectedIds"/>
  </div>
</template>

<script>
import SelectMultiple from './SelectMultiple.vue';

export default {
  components: {SelectMultiple,}, data() {
    return {
      selectedIds: [], // 用于存储选中的数据项 ID
    };
  },
};
<script>

技术栈

  • Vue.js: 用于构建组件逻辑和模板。
  • Element UI: 用于 UI 组件,如 el-popoverel-input 等。
  • axios 或其他 HTTP 请求库: 用于发起 HTTP 请求获取数据。

注意事项

  • 确保 data-url 指向正确的 API 端点,以避免数据加载失败。
  • 如果数据量较大,可能需要考虑优化加载过程。
  • 确保 props 对象中的 idlabel 字段与实际数据结构相匹配。
  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值