一、前言

穿梭框普遍都只显示一个字段,想显示多个字段,需要自己用表格搭建,我就是这么干的。

二、element ui vue2代码

<template>
  <div class="rongqi">
    <div class="transtable">
      <div>
        <div style="display: flex;">
          <el-input v-model="leftinput" size="mini" style="width:66%;"></el-input>
          <el-select v-model="leftselect" filterable placeholder="请选择" size="mini"  style="width:33%;">
            <el-option
                v-for="item in leftoptions"
                :key="item.value"
                :label="item.label"
                :value="item.value">
            </el-option>
          </el-select>
          <el-button size="mini" type="primary">重置</el-button>
        </div>
        <el-table
            :data="_newdata"
            ref="lefttable"
            @selection-change="leftSelectionChange"
            tooltip-effect="dark"
            height="400"
            border>
          <el-table-column type="selection" width="40" align="center" :selectable="selectablefunc"></el-table-column>
          <el-table-column v-for="(item,index) in columnsleft" :label="item.label" :prop="item.prop"
                           :key="index+'right'"
                           :width="item.width || null" :fixed="item.fixed || false"></el-table-column>
        </el-table>
      </div>
      <div class="action">
        <el-button type="primary" style="margin-left:10px;margin-bottom: 10px;" :disabled="rightSelectArray.length===0"
                   @click="toleftaction()">&lt;
        </el-button>
        <el-button type="primary" :disabled="leftSelectArray.length===0" @click="torightaction">&gt;</el-button>
      </div>
      <div>
        <div style="display: flex;">
          <el-input v-model="rightinput" size="mini"  style="width:66%;"></el-input>
          <el-select v-model="rightselect" filterable placeholder="请选择" size="mini"  style="width:33%;">
            <el-option
                v-for="item in rightoptions"
                :key="item.value"
                :label="item.label"
                :value="item.value">
            </el-option>
          </el-select>
          <el-button size="mini" type="primary" @click="resetright">重置</el-button>
        </div>
        <el-table
            :data="_olddata"
            ref="righttable"
            @selection-change="rightSelectionChange"
            tooltip-effect="dark"
            height="400"
            border>
          <el-table-column type="selection" width="40" align="center"></el-table-column>
          <el-table-column v-for="(item,index) in columnsright" :label="item.label" :prop="item.prop"
                           :key="index+'right'"
                           :width="item.width || null" :fixed="item.fixed || false"></el-table-column>
        </el-table>
      </div>
    </div>
    <div class="error">
      <span>{{ errortext }}</span>
    </div>
  </div>
</template>

<script>
export default {
  name: "newtranstable",
  props: {
    // 待选的数据
    data: {
      type: Array,
      default(shuju) {
        return Array.isArray(shuju) || []
      },
    },
    // 显示的字段
    colums: {
      type: Array,
      default(shuju) {
        return Array.isArray(shuju) || []
      }
    },
    // 左侧显示的字段
    colnumleft: {
      type: Array,
      default(shuju) {
        return Array.isArray(shuju) || []
      },
    },
    // 右侧显示的字段
    colnumright: {
      type: Array,
      default(shuju) {
        return Array.isArray(shuju) || []
      },
    },
    // 必填校验
    required: {
      type: Boolean,
      default: true,
    },
    // 默认显示的数据
    defaultData: {
      type: Array,
      default(shuju) {
        return Array.isArray(shuju) || []
      },
    },
    // 最大选择数量
    max: {
      type: Number,
      default: 0,
    },
    // 判断不可选的回调函数
    disableFun: {
      type: Function,
      default: null,
    },
    // 区分同一数据的依据,默认是ID
    bond:{
      type:String,
      default:"id",
    },
  },
  data() {
    return {
      newdata: [], // 左侧被选中的数据列表
      leftSelectArray: [], // 左侧被选中的字段
      rightSelectArray: [], // 左侧被选中的字段

      errortext: "", // 错误信息

      formdata: {},  // 过滤的关键字

      leftinput: "",   // 左侧的搜索关键字
      leftselect: "",  // 左侧的过滤key
      rightinput: "",   // 右侧的搜索关键字
      rightselect: "",  // 右侧的过滤key
    }
  },
  // 监听外层最新数据,显示到组件内部
  watch: {
    defaultData(newValue) {
      this.newdata = newValue
    },
  },
  computed: {
    // 左侧字段
    columnsleft() {
      if (this.colnumleft.length > 0) {
        return this.colnumleft
      } else {
        return this.colums
      }
    },
    // 右侧字段
    columnsright() {
      if (this.colnumright.length > 0) {
        return this.colnumright
      } else {
        return this.colums
      }
    },
    // 将已经选择的数据过滤掉
    _olddata() {
      const linshi1 = this.data.filter(item => {
        const jieguo = this.newdata.find(el=>el[this.bond].toString()===item[this.bond].toString())
        return !jieguo
      })
      if(this.rightinput && this.rightselect){
        return linshi1.filter(item=>{
          return item[this.rightselect] && item[this.rightselect].indexOf(this.rightinput) > -1
        })
      }else{
        return linshi1
      }
    },
    _newdata(){
      if(this.leftinput && this.leftselect){
        return this.newdata.filter(item=>{
          return item[this.leftselect] && item[this.leftselect].indexOf(this.leftinput) > -1
        })
      }else{
        return this.newdata
      }
    },
    // 右侧key列表
    rightoptions(){
      let oldlist  = []
      if(this.colnumright.length > 0){
        oldlist = this.colnumright
      }else{
        oldlist = this.colums
      }
      return oldlist.filter(item=>{
        return item.prop
      }).map(item=>{
        return {label: item.label,value: item.prop}
      })
    },
    // 左侧key列表
    leftoptions(){
      let oldlist  = []
      if(this.colnumleft.length > 0){
        oldlist = this.colnumleft
      }else{
        oldlist = this.colums
      }
      return oldlist.filter(item=>{
        return item.prop
      }).map(item=>{
        return {label: item.label,value: item.prop}
      })
    },
  },
  methods: {
    // 右侧表格选择
    rightSelectionChange(selectitems) {
      console.log("被选中的key数组", selectitems)
      this.rightSelectArray = selectitems
    },
    // 左侧表格选择
    leftSelectionChange(selectitems) {
      console.log("被选中的key数组", selectitems)
      this.leftSelectArray = selectitems
    },
    // 点击向左转移
    toleftaction() {
      this.newdata = [...this.newdata, ...this.rightSelectArray]
      this.checkerror()
    },
    // 点击向右转移
    torightaction() {
      this.newdata = this.newdata.filter(item => {
        const jieguo = this.leftSelectArray.find(el => el.id === item.id)
        return !jieguo
      })
      this.checkerror()
    },
    // 校验错误
    checkerror() {
      if (this.required && this.newdata.length === 0) {
        this.errortext = "请选择一条数据"
      } else if (this.max && this.newdata.length > this.max) {
        this.errortext = `最多选择${this.max}条数据`
      } else {
        this.errortext = ""
      }
    },
    validate(func) {
      this.checkerror()
      func(this.error, this.newdata)
    },
    reset() {
      this.newdata = []
      this.leftSelectArray = []
      this.rightSelectArray = []
      this.errortext = ""
      this.formdata = {}
      console.log("主动初始化穿梭框一次")
    },
    // 判断是否可以选中的
    selectablefunc(row, index) {
      if (this.disableFun) {
        return this.disableFun(row, index)
      } else {
        return row.checkbox === undefined || row.checkbox === null ? true : !!row.checked
      }
    },
    // 右侧重置搜索
    resetright(){
      this.rightinput = ""
    },
    // 左侧重置搜索
    resetleft(){
      this.leftinput = ""
    },
  },

}
</script>

<style scoped lang="less">
.rongqi {
  width: 100%;
  display: flex;
  flex-direction: column;

  .transtable {
    width: 100%;
    display: flex;

    .action {
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
    }
  }

  .error {
    height: 20px;
    width: 100%;
    color: #8a2424;

    span {
      line-height: 20px;
    }
  }
}

</style>

三、naive ui vue3 代码

<template>
  <div class="flex juzhong1">
    <div>
      <div>
        <n-input-group>
          <n-input :style="{ width: '66%' }" v-model:value="leftinputtext" placeholder="请输入关键字"/>
          <n-select :style="{ width: '33%' }" v-model:value="leftselectkey" :options="leftoptions"/>
          <n-button type="info">重置</n-button>
        </n-input-group>
      </div>
      <n-data-table
          class="min-h-240px"
          :columns="columnleft"
          :data="leftalllist"
          :row-key="row => row[props.bondkey]"
          @update:checked-row-keys="handleleftCheck"
          v-model:checked-row-keys="leftlist"
      />
      <div>{{ errorMsg }}</div>
    </div>
    <div class="juzhong1 flex-col">
      <n-button :disabled="rightbtn" class="m-1" @click="moveleft" type="info">{{ "<<" }}</n-button>
      <n-button :disabled="leftbtn" class="m-1" @click="moveright" type="info">{{ ">>" }}</n-button>
    </div>
    <div>
      <div>
        <n-input-group>
          <n-input :style="{ width: '66%' }" v-model:value="rightinputtext" placeholder="请输入关键字"/>
          <n-select :style="{ width: '33%' }" v-model:value="rightselectkey" :options="rightoptions"/>
          <n-button type="info">重置</n-button>
        </n-input-group>
      </div>
      <n-data-table
          class="min-h-240px"
          :columns="colnumright"
          :data="rightalllist"
          :row-key="row => row[props.bondkey]"
          @update:checked-row-keys="handlerightCheck"
          v-model:checked-row-keys="rightlist"
      />
    </div>
  </div>
</template>

<script setup name="transfer">
import {useRoute, useRouter} from "vue-router";
import {defineProps} from "vue";

const router = useRouter()
const route = useRoute()
const props = defineProps({
  // 右边的字段
  colnumright: {
    type: Array,
    default: [],
  },
  // 左边的字典
  colnumleft: {
    type: Array,
    default: [],
  },
  // 默认的字段
  colnum: {
    type: Array,
    default: [],
  },
  // 备选的数据
  origindata: {
    type: Array,
  },
  // 被选中的数据
  default: {
    type: Array,
    default: [],
  },
  // 识别的唯一键
  bondkey: {
    type: String,
    default: 'id',
  },
  // 必填
  required: {
    type: Boolean,
    default: false,
  },
  // 最大数量
  max: {
    type: Number,
    default: 0,
  },
})
// 错误
const errorMsg = ref("")
// 字段
const columnleft = computed(() => {
  const ziduanlist = props.colnumleft.length > 0 ? props.colnumleft : props.colnum
  const check = ziduanlist.find(el => el.type === "selection")
  if (!check) {
    return [{
      type: 'selection', width: 60, disabled(row, index) {
        return row.checkbox === undefined || row.checkbox === null ? true : !!row.checked
      },
    }, ...ziduanlist]
  } else {
    return ziduanlist
  }
})
const colnumright = computed(() => {
  const ziduanlist = props.colnumright.length > 0 ? props.colnumright : props.colnum
  const check = ziduanlist.find(el => el.type === "selection")
  if (!check) {
    return [{
      type: 'selection', width: 60, disabled(row, index) {
        return row.checkbox === undefined || row.checkbox === null ? true : !!row.checked
      },
    }, ...ziduanlist]
  } else {
    return ziduanlist
  }
})
const leftoptions = computed(() => {
  let oldlist = []
  if (props.colnumleft.length > 0) {
    oldlist = props.colnumleft
  } else {
    oldlist = props.colnum
  }
  return oldlist.filter((item) => {
    return item.type !== "selection" && item.key
  }).map(item => {
    return {label: item.title, value: item.key,}
  })
})
const rightoptions = computed(() => {
  let oldlist = []
  if (props.colnumleft.length > 0) {
    oldlist = props.colnumleft
  } else {
    oldlist = props.colnum
  }
  return oldlist.filter((item) => {
    return item.type !== "selection" && item.key
  }).map(item => {
    return {label: item.title, value: item.key,}
  })
})
// 左边的数据
const leftinputtext = ref("")   // 手动输入的过滤关键字
const leftselectkey = ref("")   // 过滤的key
const resultlist = ref([])      // 所有左侧的数据
const leftalllist = computed(() => {  // 显示左侧的数据
  if (leftinputtext.value && leftselectkey.value) {
    return resultlist.value.filter(item => {
      return item[leftselectkey.value] && item[leftselectkey.value].indexOf(leftinputtext.value) > -1
    })
  } else {
    return resultlist.value
  }
})
const leftlist = ref([])    // 左侧被选中的数据
const leftbtn = computed(() => {  // 向右移动的按钮
  return leftlist.value.length === 0
})

// 右边数据
const rightinputtext = ref("")    // 右侧过滤的关键字
const rightselectkey = ref("")    // 右侧过滤的key
const rightalllist = computed(() => {   // 右侧显示的数据
  // 先过滤已经移到左边的
  const linshilist = props.origindata.filter(item => {
    const jieguo = resultlist.value.find(el => el[props.bondkey] === item[props.bondkey])
    return !jieguo
  })
  // 再过滤关键字
  if (rightinputtext.value && rightselectkey.value) {
    return linshilist.filter(item => {
      return item[rightselectkey.value] && item[rightselectkey.value].indexOf(rightinputtext.value) > -1
    })
  } else {
    return linshilist
  }
})
const rightlist = ref([])   // 右侧被选中的数据
const rightbtn = computed(() => {   // 向左移动的按钮
  return rightlist.value.length === 0
})
// 选择回调
const handleleftCheck = (selectlist) => {
  console.log(selectlist)
  leftlist.value = selectlist
}
const handlerightCheck = (selectlist) => {
  console.log(selectlist)
  rightlist.value = selectlist
}

// 点击向左移动
const moveleft = () => {
  rightlist.value.forEach(item => {
    const obj = props.origindata.find(el => el[props.bondkey] === item)
    console.log("找到了,", obj)
    if (obj) {
      resultlist.value.push(obj)
    }
  })
  // leftlist.value = []
  rightlist.value = []
}
// 点击向右移动
const moveright = () => {
  resultlist.value = resultlist.value.filter(item => {
    return !leftlist.value.includes(item[props.bondkey])
  })
  leftlist.value = []
  // rightlist.value = []
}

// 校验回调
const jiaoyan = (funcitem) => {
  if (props.required) {
    if (resultlist.value.length > 0) {
      funcitem(resultlist.value)
    } else {
      errorMsg.value = "最少选择一条数据"
    }
  } else {
    funcitem(resultlist.value)
  }
}
// 重置
const reset = () => {
  resultlist.value = []
}
// 生命周期区域
onMounted(()=>{
  rightselectkey.value = rightoptions.value[0].value
  leftselectkey.value = leftoptions.value[0].value
})

defineExpose({jiaoyan, reset})
</script>

<style scoped>

</style>

四、navie ui vue3 效果

在这里插入图片描述

Logo

前往低代码交流专区

更多推荐