使用vue3+element plus 的table自制的穿梭框(支持多列数据)
目录
一、效果图
二、介绍
三、代码区
一、效果图
话不多说,先上图
二、介绍
项目需要:通过穿梭框选择人员信息,可以根据部门、岗位进行筛选,需要显示多列(不光显示姓名,还包括人员的一些基础信息);项目前端用的是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>