使用vue3+elementplus的table自制的穿梭框(支持多列数据)
- 手机
- 2025-09-12 14:15:02

目录
一、效果图
二、介绍
三、代码区
一、效果图
话不多说,先上图
二、介绍项目需要:通过穿梭框选择人员信息,可以根据部门、岗位进行筛选,需要显示多列(不光显示姓名,还包括人员的一些基础信息);项目前端用的是vue3+element plus(Transfer 组件 | Element Plus),原来打算使用element的组件 Transfer 穿梭框,发现它只实现简单的单列穿梭,无法满足需求。最后打算自己动手用table来实现:分为左右两个区域,左边查询区+待选数据列表table,右边选中的数据列表,中间两个按钮实现数据的转移。(在看的小伙伴有其他更合适的组件/组件库可以评论区分享)
三、代码区html
<el-dialog :title="title" :lock-scroll="false" v-model="open" :fullscreen="fullScreen" :close-on-click-modal="false" :close-on-press-escape="false" align-center> <el-form ref="formRef" :model="form" :rules="rules" label-width="100px"> <el-row :gutter="10"> <el-col :lg="6"> <el-form-item label="生产线" prop="lineCode"> <el-select clearable v-model="form.lineCode" placeholder="请选择生产线" filterable @change="formLineSelectChange" style="width: 100%;"> <el-option v-for="item in options.mes_line_list" :key="item.id" :label="item.lineName" :value="item.lineCode"> <span class="fl">{{ item.innerLineCode }}</span> <span class="fr" style="color: var(--el-text-color-secondary);">{{ item.lineName }}</span> </el-option> </el-select> </el-form-item> </el-col> <el-col :lg="4"> <el-form-item label="标准工时" prop="standardWorkHours"> <el-input-number v-model="form.standardWorkHours" :min="0" :max="24" :precision="1" placeholder="请输入标准工时" style="width: 100%;" /> </el-form-item> </el-col> <el-col :lg="4"> <el-form-item label="日期" prop="workDate"> <el-date-picker v-model="form.workDate" type="date" placeholder="日期" value-format="YYYY-MM-DD" :shortcuts="shortcutsWorkDate" style="width: 100%;"> </el-date-picker> </el-form-item> </el-col> <el-col :lg="4"> <el-form-item label="班次" prop="workShift"> <el-select clearable v-model="form.workShift" placeholder="请选择生产班次" style="width: 100%;"> <el-option v-for="item in options.mes_classes_type" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue"> </el-option> </el-select> </el-form-item> </el-col> <el-col :lg="6"> <el-form-item label="备注信息" prop="remark"> <el-input v-model="form.remark" placeholder="请输入备注信息" style="width: 100%;" type="textarea" :rows="2" /> </el-form-item> </el-col> </el-row> <el-divider content-position="center">报工人员明细</el-divider> <!-- 查询条件 --> <el-form :model="queryParamsUserNav" label-position="right" inline ref="queryRefUserNav"> <el-form-item label="部门" prop="deptId"> <el-tree-select v-model="queryParamsUserNav.deptId" :data="deptOptions" :props="{ value: 'id', label: 'label', children: 'children' }" value-key="id" placeholder="请选择归属部门" check-strictly :render-after-expand="false" @change="changeDept($event)" filterable /> </el-form-item> <el-form-item label="岗位" prop="postId"> <el-select v-model="queryParamsUserNav.postId" placeholder="请选择岗位" filterable clearable @change="changePost"> <el-option v-for="item in postOptions" :key="item.postId" :label="item.postName" :value="item.postId"> <span class="fl">{{ item.postCode }}</span> <span class="fr" style="color: var(--el-text-color-secondary);">{{ item.postName }}</span> </el-option> </el-select> </el-form-item> <el-form-item label="用户" prop="userId"> <el-select v-model="queryParamsUserNav.userId" placeholder="请选择用户" filterable clearable> <el-option v-for="item in userOptions" :key="item.userId" :label="item.nickName" :value="item.userId"> <span class="fl">{{ item.nickName }}</span> <span class="fr" style="color: var(--el-text-color-secondary);">{{ item.userName }}</span> </el-option> </el-select> </el-form-item> <el-form-item> <el-button icon="search" type="primary" @click="handleQueryUserNav">{{ $t('btn.search') }}</el-button> <el-button icon="refresh" @click="resetQueryUserNav">{{ $t('btn.reset') }}</el-button> </el-form-item> </el-form> <!-- 自制穿梭框 --> <el-row> <!-- 左边table --> <el-col :lg="10" style="height: 100%;"> <div class="table-container"> <el-table :data="dataListLeft" v-loading="loadingLeft" ref="table" border header-cell-class-name="el-table-header-cell" highlight-current-row @selection-change="handleSelectionChangeLeft" :height="tableHeight - 40"> <el-table-column type="selection" width="50" align="center" /> <el-table-column type="index" width="60" label="序号" align="center" /> <el-table-column prop="deptName" label="部门" align="center" /> <el-table-column prop="userName" label="工号" align="center" /> <el-table-column prop="nickName" label="姓名" align="center" /> <el-table-column prop="postNames" label="岗位" align="center" /> </el-table> <pagination :total="totalLeft" v-model:page="queryParamsUserNav.pageNum" v-model:limit="queryParamsUserNav.pageSize" @pagination="handleQueryUserNav" /> </div> </el-col> <!-- 向左箭头 --> <el-col :lg="1" align="right" style="margin: 0.5%; display: flex; align-items: center; justify-content: center;"> <el-button type="primary" icon="arrow-left" @click="handleLeftArrow"></el-button> </el-col> <!-- 向右箭头 --> <el-col :lg="1" align="left" style="margin: 0.5%; display: flex; align-items: center; justify-content: center;"> <el-button type="primary" icon="arrow-right" @click="handleRightArrow"></el-button> </el-col> <!-- 右边table --> <el-col :lg="11" style="height: 100%;"> <div class="table-container"> <el-table :data="dataListRight" v-loading="loadingRight" ref="table" border header-cell-class-name="el-table-header-cell" highlight-current-row @selection-change="handleSelectionChangeRight" :height="tableHeight - 40"> <el-table-column type="selection" width="50" align="center" /> <el-table-column type="index" width="60" label="序号" align="center" /> <el-table-column prop="deptName" label="部门" align="center" /> <el-table-column prop="userName" label="工号" align="center" /> <el-table-column prop="nickName" label="姓名" align="center" /> <el-table-column prop="actualWorkHours" label="实际工时/h" align="center" width="150"> <template #default="scope"> <el-input-number v-model="scope.row.actualWorkHours" :min="0" :max="24" :precision="1" placeholder="实际工时/h" style="width: 100%;" controls-position="right" /> </template> </el-table-column> <el-table-column prop="standardWorkHours" label="标准工时/h" align="center" width="150" /> <el-table-column prop="postNames" label="岗位" align="center" /> </el-table> </div> </el-col> </el-row> </el-form> <template #footer> <el-button text @click="cancel">{{ $t('btn.cancel') }}</el-button> <el-button type="primary" @click="submitForm">{{ $t('btn.submit') }}</el-button> </template> </el-dialog>js
<script setup name="meshumanreport"> import { listMesHumanReport, getMesHumanReportList, addMesHumanReport, delMesHumanReport, updateMesHumanReport, updateMesHumanReportDetailPartial, getMesHumanReport, } from '@/api/dataReport/meshumanreport.js' import { getMesLineList } from '@/api/factoryManage/mesline.js' import { treeselect } from '@/api/system/dept' import { listPostOptionSelectLimit } from '@/api/system/post.js' import { getUserListByPost } from '@/api/system/user.js' import dayjs from 'dayjs'; import auth from '@/plugins/auth' const { proxy } = getCurrentInstance() const ids = ref([]) const loading = ref(false) const loadingLeft = ref(false) const loadingRight = ref(false) const queryParamsUserNav = reactive({ pageNum: 1, pageSize: 20, sortType: 'asc', deptId: undefined, postId: undefined, userId: undefined, }) const total = ref(0) const totalLeft = ref(0) const totalRight = ref(0) const dataList = ref([]) const dataListLeft = ref([]) const dataListRight = ref([]) const dataListLeftChosed = ref([]) const dataListRightChosed = ref([]) const queryRef = ref() const queryRefUserNav = ref() const deptOptions = ref([]) const postOptions = ref([]) const userOptions = ref([]) const standardDisabled = ref(true) const tableHeight = ref(570) var dictParams = [ { dictType: "mes_classes_type" }, ] proxy.getDicts(dictParams).then((response) => { response.data.forEach((element) => { state.options[element.dictType] = element.list }) }) //当前时间 const now = new Date(); // 设置:subtract往前推 add往后 const dateRangeWorkDate = ref([dayjs().subtract(1, 'day').format('YYYY-MM-DD'), dayjs().format('YYYY-MM-DD')]) const shortcutsWorkDate = [ { text: '今天', value: new Date(), }, { text: '昨天', value: () => { const date = new Date() date.setTime(date.getTime() - 3600 * 1000 * 24) return date }, }, { text: '一周前', value: () => { const date = new Date() date.setTime(date.getTime() - 3600 * 1000 * 24 * 7) return date }, }, ] //日期范围变化时,触发表单校验(因为el-date-picker 选中的值不会自动通知表单验证状态,所以这里要单独处理) function handleDateChange() { dataList.value = [] // console.log(dateRangeWorkDate.value) // queryParams.dateRangeWorkDate = dateRangeWorkDate.value // // 查询报表数据 // getList() } // 动态表头生成 // const dateRangeWorkDate = ref([]) const dynamicHeaders = computed(() => { console.log(dateRangeWorkDate.value) if (!dateRangeWorkDate.value || dateRangeWorkDate.value.length !== 2) return [] const dates = [] const start = new Date(dateRangeWorkDate.value[0]) const end = new Date(dateRangeWorkDate.value[1]) while (start <= end) { dates.push({ date: start.toISOString().split('T')[0], shifts: ['白班', '夜班'] }) start.setDate(start.getDate() + 1) } console.log(dates) return dates }) /**************************************************** form操作 ****************************************************/ const formRef = ref() const formRefEdit = ref() const formRefUserNav = ref() const title = ref('') const titleUserNav = ref('') // 操作类型 1、add 2、edit 3、view const opertype = ref(0) const open = ref(false) const openUserNav = ref(false) const openEditCell = ref(false) const state = reactive({ single: true, multiple: true, form: {}, formUserNav: {}, formEdit: {}, rules: { //产线 lineCode: [{ required: true, message: '请选择产线', trigger: 'change' },], //标准工时 standardWorkHours: [{ required: true, message: '请输入标准工时', trigger: ['change', 'blur'] },], },], workShift: [{ required: true, message: '请选择班次', trigger: 'change' },], }, rulesQueryReport: { //产线 // lineCode: [{ required: true, message: '请选择产线', trigger: 'change' },], //日期 dateRangeWorkDate: [{ required: true, message: '请选择日期范围', trigger: 'blur' },], }, rulesEdit: { lineName: [{ required: true, message: '请输入产线', trigger: 'blur' },], userName: [{ required: true, message: '请输入人员', trigger: 'blur' },], workDate: [{ required: true, message: '请选择日期', trigger: 'change' },], workShift: [{ required: true, message: '请选择班次', trigger: 'change' },], standardWorkHours: [{ required: true, message: '请输入标准工时', trigger: 'change' },], actualWorkHours: [{ required: true, message: '请输入实际工时', trigger: 'change' },], }, options: { // 产线列表 mes_line_list: [], // 班次 选项列表 格式 eg:{ dictLabel: '标签', dictValue: '0'} mes_classes_type: [], } }) const { form, formUserNav, formEdit, rules, rulesQueryReport, rulesEdit, options, single, multiple } = toRefs(state) // 添加按钮操作 function handleAdd() { reset(); open.value = true title.value = '添加人力报工' opertype.value = 1 form.value.standardWorkHours = 12 //日期班次默认值 var hour = dayjs().get('hour') if (hour >= 0 && hour < 8) { form.value.workDate = dayjs().subtract(1, 'day').format('YYYY-MM-DD') } else { form.value.workDate = dayjs().format('YYYY-MM-DD') } if (hour >= 8 && hour < 20) { form.value.workShift = '白班' } else { form.value.workShift = '夜班' } //查询用户下拉列表 getUserDropDownList() } // 添加&修改 表单提交 function submitForm() { // 校验表单 proxy.$refs["formRef"].validate((valid) => { if (valid) { // 校验明细表 if (dataListRight.value.length == 0) { proxy.$modal.msgError('请先选择人员信息') return } dataListRight.value.forEach(item => { item.workShopName = form.value.workShopName item.lineCode = form.value.lineCode item.lineName = form.value.lineName item.workDate = form.value.workDate item.workShift = form.value.workShift item.userCode = item.userName item.userName = item.nickName }) // 提交表单 console.log(dataListRight.value) form.value.mesHumanReportDetailNav = dataListRight.value addMesHumanReport(form.value).then((res) => { console.log(res) if (res.code == 200) { proxy.$modal.msgSuccess("操作成功!") open.value = false //刷新列表 getList() } else { // proxy.$modal.msgError("操作失败") } }).catch(() => { proxy.$modal.msgError("操作失败") }) } }) } // 关闭dialog function cancel() { open.value = false reset() } // 重置表单 function reset() { form.value = { id: null, workShopId: null, workShopCode: null, workShopName: null, lineId: null, lineCode: null, lineName: null, standardWorkHours: null, startTime: null, endTime: null, remark: null, }; proxy.resetForm("formRef") resetQueryUserNav() dataListLeft.value = [] dataListLeftChosed.value = [] dataListRight.value = [] dataListRightChosed.value = [] } /** * 生产线下拉变更 * @param {*} value */ function formLineSelectChange(value) { let line = state.options.mes_line_list.find(item => item.lineCode == value) console.log(line) form.value.lineId = line.id form.value.lineCode = line.lineCode form.value.lineName = line.lineName form.value.workShopId = line.workShopId form.value.workShopCode = line.workShopCode form.value.workShopName = line.workShopName } /******************************************** 人力报工-明细子表信息 ***********************************************/ const mesHumanReportDetailList = ref([]) const checkedMesHumanReportDetail = ref([]) const fullScreen = ref(true) const drawer = ref(false) /** 人力报工-明细序号 */ function rowMesHumanReportDetailIndex({ row, rowIndex }) { row.index = rowIndex + 1; } /** 复选框选中数据 */ function handleMesHumanReportDetailSelectionChange(selection) { checkedMesHumanReportDetail.value = selection.map(item => item.index) } //查询用户信息 function handleQueryUserNav() { console.log(queryParamsUserNav) loadingLeft.value = true getUserListByPost(queryParamsUserNav).then((res) => { console.log(res) const { code, data } = res if (code == 200) { dataListLeft.value = data.result totalLeft.value = data.totalNum console.log(data.result) //dataListLeft去除掉右边dataListRight的人员 dataListLeft.value = dataListLeft.value.filter(item => !dataListRight.value.some(item2 => item2.userId == item.userId)) } loadingLeft.value = false }).catch(() => { loadingLeft.value = false }) } //查询用户信息条件清空 function resetQueryUserNav() { queryParamsUserNav.deptId = undefined queryParamsUserNav.postId = undefined queryParamsUserNav.userId = undefined queryParamsUserNav.pageNum = 1 queryParamsUserNav.pageSize = 20 handleQueryUserNav() } //部门选择改变 function changeDept(e) { console.log(e) if (e) { queryParamsUserNav.deptId = e getUserDropDownList() } } //岗位选择改变 function changePost() { getUserDropDownList() } // 获取人员下拉列表 function getUserDropDownList() { queryParamsUserNav.userId = undefined var tmpQueryParams = { deptId: queryParamsUserNav.deptId, postId: queryParamsUserNav.postId, pageNum: 1, pageSize: 99999, } getUserListByPost(tmpQueryParams).then((res) => { console.log(res) const { code, data } = res if (code == 200) { userOptions.value = data.result } }) } //箭头向左,取消选中数据 function handleLeftArrow() { if (dataListRightChosed.value.length == 0) { proxy.$modal.msgError('请先勾选要取消选中的人员') } else { dataListRight.value = dataListRight.value.filter(item => !dataListRightChosed.value.some(item2 => item2.userId == item.userId)) dataListRightChosed.value = [] //左边的数据重新查询 handleQueryUserNav() } } //箭头向右,选中数据 function handleRightArrow() { if (form.value.standardWorkHours) { if (dataListLeftChosed.value.length > 0) { //右边的数据加上左边的选中数据,已经存在的不重复添加 dataListRight.value = dataListRight.value.concat(dataListLeftChosed.value.filter(item => !dataListRight.value.some(item2 => item2.userId == item.userId))) console.log(form.value.workDate) dataListRight.value.forEach(item => { item.standardWorkHours = form.value.standardWorkHours item.actualWorkHours = form.value.standardWorkHours }) dataListLeft.value = dataListLeft.value.filter(item => !dataListLeftChosed.value.some(item2 => item2.userId == item.userId)) dataListLeftChosed.value = [] } else { proxy.$modal.msgError('请先勾选要添加的人员') } } else { proxy.$modal.msgError('请先设置标准工时') } } //左侧选择改变 function handleSelectionChangeLeft(selection) { // dataListLeftChosed = selection.map(item => item.userId) dataListLeftChosed.value = selection } //右侧选择改变 function handleSelectionChangeRight(selection) { dataListRightChosed.value = selection } /** 查询部门下拉树结构 */ function getDeptTreeselect() { treeselect().then((response) => { deptOptions.value = response.data }) } function getDropDownList() { // 获取生产线列表 getMesLineList().then(res => { console.log(res) const { code, data } = res if (code == 200) { state.options.mes_line_list = data } }) // 获取岗位列表 listPostOptionSelectLimit().then((res) => { console.log(res) if (res.code == 200) { postOptions.value = res.data } }) } handleQuery() getDropDownList() getDeptTreeselect() </script>使用vue3+elementplus的table自制的穿梭框(支持多列数据)由讯客互联手机栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“使用vue3+elementplus的table自制的穿梭框(支持多列数据)”