完整代码:
1.组件
cpp
<template>
<div class="mult_table_box">
<el-form ref="formRef" :model="formData" :rules="formRules">
<el-table
ref="tableRef"
:data="formData.tableDataValue"
:header-cell-style="{ 'text-align': 'center' }"
@row-click="handleEdit"
@selection-change="handleSelectionChange"
class="mult_table_"
max-height="540"
>
<el-table-column
type="selection"
width="55"
align="center"
v-if="showCheckbox"
:selectable="checkSelectable"
/>
<el-table-column type="index" label="序号" width="80" align="center" />
<el-table-column
v-for="(column, index) in columns"
:key="index"
:prop="column.prop"
:label="column.label"
:width="column.width"
:show-overflow-tooltip="
typeof column.showTooltip === 'boolean' ? column.showTooltip : showTooltip
"
align="center"
>
<template #header>
<div class="flex_ flex_cetner"
><span class="red_" v-if="column.required">*</span
><span> {{ column.label }}</span></div
>
</template>
<!-- 多级表头 -->
<el-table-column
v-for="(column2, cIndex) in column.children"
:key="cIndex"
:prop="column2.prop"
:label="column2.label"
:width="column2.width"
:show-overflow-tooltip="
typeof column.showTooltip === 'boolean' ? column.showTooltip : showTooltip
"
>
<template #header>
<div class="flex_ flex_cetner"
><span class="red_" v-if="column2.required">*</span
><span> {{ column2.label }}</span></div
>
</template>
<template #default="scope">
<template v-if="scope.row.isEdit && !column2.disabled">
<el-form-item
:prop="'tableDataValue[' + scope.$index + '].' + column.prop"
:rules="[
{ required: column2.required, message: '', trigger: 'blur' }
]"
>
<!-- 输入框 -->
<el-input
v-if="column2.type == 'input'"
v-model="scope.row[column2.prop]"
:placeholder="`请输入${column2.label}`"
/>
<!-- 下拉 -->
<el-select
v-else-if="column2.type == 'select'"
v-model="scope.row[column2.prop]"
>
<el-option
v-for="option in getColumnOptions(column2)"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
<!-- 自定义 -->
<div v-else-if="column2.type === 'slot'" class="w100">
<slot :name="column2.prop + 'Edit'" :row="scope.row" :column="column2"></slot>
</div>
</el-form-item>
</template>
<!-- 不编辑展示状态-多层表头 -->
<template v-else>
<el-form-item prop="operation">
<div v-if="column2.type == 'switch'">
<el-switch
v-model="scope.row[column2.prop]"
active-text=""
inactive-text=""
disabled
/>
</div>
<div
v-else-if="column2.type == 'dictSelect' || column2.type == 'dictSelect_string'"
>
<div v-if="column2.propLable">{{ scope.row[column2.propLable] }}</div>
<div v-else>
{{ getDictLabel(scope.row[column2.prop], column2.dictName) }}
</div>
</div>
<div v-else-if="column2.type == 'datetime'">
{{ formatDate(scope.row[column2.prop]) }}
</div>
<div v-else-if="column2.type === 'slot'">
<slot :name="column2.prop" :row="scope.row" :column="column2"></slot>
</div>
<div v-else>{{ scope.row[column2.prop] }}</div>
</el-form-item>
</template>
</template>
</el-table-column>
<template #default="scope" v-if="!column.children?.length">
<template v-if="scope.row.isEdit && !column.disabled">
<el-form-item
:prop="'tableDataValue[' + scope.$index + '].' + column.prop"
:rules="[{ required: column.required, message: '', trigger: 'blur' }]"
>
<!-- 输入框 -->
<el-input
v-if="column.type == 'input'"
v-model="scope.row[column.prop]"
:placeholder="`请输入${column.label}`"
/>
<!-- numner输入框(小数) -->
<div @click.stop>
<el-input-number
v-if="column.type == 'input-number'"
:placeholder="`请输入${column.label}`"
v-model="scope.row[column.prop]"
:precision="1"
:step="0.1"
/>
</div>
<!-- number输入框(整数) -->
<div @click.stop>
<el-input-number
v-if="column.type == 'number'"
:placeholder="`请输入${column.label}`"
v-model="scope.row[column.prop]"
:precision="0"
:step="1"
/>
</div>
<!-- 文本域 -->
<el-input
v-if="column.type == 'textarea'"
type="textarea"
v-model="scope.row[column.prop]"
:placeholder="`请输入${column.label}`"
resize="vertical"
:autosize="{ minRows: 2, maxRows: 4 }"
clearable
/>
<!-- 下拉 -->
<el-select
v-else-if="column.type == 'select'"
v-model="scope.row[column.prop]"
clearable
>
<el-option
v-for="option in getColumnOptions(column)"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
<!-- 字典下拉-value为string -->
<el-select
v-else-if="column.type == 'dictSelect_string'"
v-model="scope.row[column.prop]"
clearable
>
<el-option
v-for="dict in getDictOptions(DICT_TYPE[column.dictName])"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
<!-- 字典下拉-value为number -->
<el-select
v-else-if="column.type == 'dictSelect'"
v-model="scope.row[column.prop]"
clearable
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE[column.dictName])"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
<el-date-picker
v-else-if="column.type === 'date'"
v-model="scope.row[column.prop]"
type="date"
:placeholder="`请选择${column.label}`"
value-format="YYYY-MM-DD"
format="YYYY-MM-DD"
style="width: 100%"
/>
<el-date-picker
v-else-if="column.type === 'datetime'"
v-model="scope.row[column.prop]"
type="datetime"
:placeholder="`请选择${column.label}`"
value-format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
<el-date-picker
v-else-if="column.type === 'timestamp'"
v-model="scope.row[column.prop]"
value-format="x"
type="datetime"
:placeholder="`请选择${column.label}`"
style="width: 100%"
/>
<el-switch
v-else-if="column.type === 'switch'"
v-model="scope.row[column.prop]"
active-text=""
inactive-text=""
/>
<div v-else-if="column.type == 'uploadFile'" @click.stop>
<UploadFile
:is-show-tip="false"
v-model="scope.row[column.prop]"
:modelValueIsString="true"
/>
</div>
<div v-else-if="column.type == 'uploadImgs'" @click.stop>
<UploadImgs
v-model="scope.row[column.prop]"
:modelValueIsString="true"
:limit="9"
height="50px"
width="50px"
/>
</div>
<div v-else-if="column.type === 'slot'" @click.stop class="w100">
<slot :name="column.prop + 'Edit'" :row="scope.row" :column="column"></slot>
</div>
</el-form-item>
</template>
<!-- 不编辑展示状态 -->
<template v-else>
<el-form-item prop="operation">
<div v-if="column.type == 'switch'">
<el-switch
v-model="scope.row[column.prop]"
active-text=""
inactive-text=""
disabled
/>
</div>
<div v-else-if="column.type == 'datetime'">
{{ formatDate(scope.row[column.prop]) }}
</div>
<div v-else-if="column.type == 'timestamp'">
{{ formatDate(scope.row[column.prop]) }}
</div>
<div v-else-if="column.type == 'uploadFile'">
<UploadFile
:is-show-tip="false"
v-model="scope.row[column.prop]"
:disabled="true"
/>
</div>
<div v-else-if="column.type == 'uploadImgs'">
<UploadImgs
v-model="scope.row[column.prop]"
:limit="9"
:modelValueIsString="true"
height="50px"
width="50px"
:disabled="true"
/>
</div>
<div v-else-if="column.type == 'dictSelect' || column.type == 'dictSelect_string'">
<div v-if="column.propLable">{{ scope.row[column.propLable] }}</div>
<div v-else>
{{ getDictLabel(scope.row[column.prop], column.dictName) }}
</div>
</div>
<div v-else-if="column.type === 'slot'">
<div v-if="column.propLable">
{{ scope.row[column.propLable] }}
</div>
<slot v-else :name="column.prop" :row="scope.row" :column="column"></slot>
</div>
<div v-else>{{ scope.row[column.prop] }}</div>
</el-form-item>
</template>
</template>
</el-table-column>
<el-table-column
fixed="right"
label="操作"
prop="city"
min-width="160"
v-if="showOperationColumn"
:width="operationWidth"
>
<!-- :width="setOperationBoxWidth()" -->
<template #default="scope">
<el-form-item prop="operation">
<div class="table_btn_box" @click.stop>
<!-- 表格编辑默认的按钮 -->
<span id="operationBox" v-if="allowEdit">
<el-button
link
type="primary"
size="small"
:icon="EditPen"
v-if="!scope.row.isEdit"
@click.stop="handleEdit(scope.row)"
>编辑</el-button
>
<el-button
link
type="success"
size="small"
:icon="Check"
v-if="scope.row.isEdit"
@click.stop="handleSave(scope.row)"
>保存</el-button
>
<el-button
link
type="warning"
size="small"
class="mr12px"
:icon="Close"
v-if="scope.row.isEdit"
@click.stop="handleCancel(scope)"
>取消</el-button
>
<el-button
v-if=" !scope.row.isEdit && scope.row?.id"
link
type="danger"
class="mr12px"
:icon="Delete"
size="small"
@click.stop="handleDelete(scope.row)"
>删除</el-button
>
</span>
<!-- 需要把编辑emit出去的按钮 使用弹窗类 -->
<!-- 其他需要自定义的按钮 -->
<slot name="operation" :row="scope.row"></slot>
</div>
</el-form-item>
</template>
</el-table-column>
</el-table>
</el-form>
</div>
</template>
<script lang="ts" setup>
import { Plus, View, Close, EditPen, Delete, Check } from '@element-plus/icons-vue'
import { columnsType } from '@/api/common/types'
import { ElMessageBox, ElMessage, FormInstance } from 'element-plus'
import { formatDate } from '@/utils/formatTime'
import { getDictOptions, getIntDictOptions, getDictObj, DICT_TYPE } from '@/utils/dict'
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
import { cloneDeep } from 'lodash-es'
import ProcessUserSelect from '@/components/approvalComponent/ProcessUserSelect.vue'
import approvalHistoryDialog from '@/views/equipmentManagement/ledger/equipmentLedger/abnormalMovementManagement/approvalHistoryDialog.vue'
import { useRouter } from 'vue-router'
const message = useMessage() // 消息弹窗
const router = useRouter()
const emit = defineEmits([
'handleEdit',
'handleEditEmit',
'handleDelete',
'handleSave',
'handleProcessSubmit',
'handleRefresh'
])
const props = defineProps({
columns: {
type: Array as PropType<columnsType[]>,
required: true,
default: () => []
},
tableData: {
type: Array,
required: true,
default: () => []
},
allOptionList: {
type: Object,
required: false,
default: () => {}
},
allowEdit: {
//表格编辑
type: Boolean,
default: true
},
showOperationColumn: {
type: Boolean,
default: true
},
operationWidth: {
type: String,
default: '160'
},
showTooltip: {
type: Boolean,
default: true
},
showCheckbox: {
type: Boolean,
default: true
},
})
const formRules = reactive({
rule: [{ required: true, message: '邮箱不能为空', trigger: 'blur' }]
})
let formRef = ref()
const formData = reactive({
rule: '',
tableDataValue: []
})
// const tableDataValue = ref(props.tableData)
let dataLength = ref(0)
// 监听data变化
watch(
() => props.tableData,
(newVal) => {
// 初次进入给dataLength赋值即可
if (!dataLength.value) {
dataLength.value = props.tableData.length
formData.tableDataValue = newVal
return
}
// 新增、判断是否有正在编辑的行 是-取消新增
if (dataLength.value == newVal?.length - 1) {
let dataLenIndex = formData.tableDataValue.length - 1
if (EditRow.value?.isEdit) {
formData.tableDataValue.splice(dataLenIndex, 1)
message.warning('有正在编辑的行,请取消编辑再尝试')
return
}
let newRow = newVal[dataLenIndex]
EditRow.value = newRow
}
// 新增后滚动到底部
if (dataLength.value && dataLength.value == newVal?.length - 1) {
scrollToBottom()
}
dataLength.value = formData.tableDataValue.length
formData.tableDataValue = newVal
},
{ deep: true, immediate: true }
)
onMounted(() => {
initData()
})
//获取数据
const initData = async () => {}
// 获取列选项
const getColumnOptions = (column: columnsType) => {
if (column.optionApi) {
return props.allOptionList[column.optionApi] || []
}
// return column.options || []
}
// 表格操作
const multipleSelection = ref([])
const handleSelectionChange = (rows) => {
multipleSelection.value = rows.map((item) => {
return item.id
})
}
//编辑
let originalRow = reactive({}) //原始行,作用:取消编辑时,数据恢复
let EditRow = ref()
const handleEdit = (row: any) => {
if (EditRow.value?.isEdit && EditRow.value != row) {
message.warning('有正在编辑的行,请取消编辑再尝试')
return
}
if (props.allowEdit&& props.showOperationColumn) {
row.isEdit = true
let nRow = cloneDeep(toRaw(row))
originalRow = { ...nRow }
EditRow.value = row
}
}
const handleSave = async (row) => {
const valid = await formRef.value.validate()
if (!valid) return
row.isEdit = false
EditRow.value = row
emit('handleSave', row)
}
const handleDelete = (row: any) => {
ElMessageBox.confirm('确认删除该行数据吗?', '提示', {
confirmButtonText: '确 认',
cancelButtonText: '取 消'
})
.then(() => {
//确认
emit('handleDelete', row)
})
.catch(() => console.info('操作取消'))
}
//取消编辑
const handleCancel = (scope) => {
let index = scope.$index
if (!scope.row.id) {
scope.row.isEdit = false
formData.tableDataValue.splice(index, 1)
} else {
originalRow.isEdit = false
EditRow.value.isEdit = false
formData.tableDataValue.splice(index, 1, originalRow)
}
}
const handleEditEmit = (row) => {
emit('handleEditEmit', row)
}
//#endregion
// 是否可以勾选-ge
const checkSelectable = (row) => {
if (row.id) {
return true
} else {
return false
}
}
//获取字典lable
const getDictLabel = (value, dictName) => {
let label = ''
if (value != null) {
let option = getDictObj(DICT_TYPE[dictName], value)
label = option?.label || ''
}
return label
}
// 表格滚动到最底部
const tableRef = ref()
const scrollToBottom = async () => {
nextTick(() => {
tableRef.value?.setScrollTop(tableRef.value?.$refs.tableBody.scrollHeight)
})
}
defineExpose({
multipleSelection
})
</script>
<style lang="scss" scoped>
.mult_table_box {
margin-top: 10px;
margin-bottom: 20px;
.mr10 {
margin-right: 10px;
}
#operationBox {
// white-space: nowrap;
// width: auto;
// overflow: hidden;
}
.mr12px {
// margin-right: 12px;
}
.ml15 {
margin-left: 12px;
}
.table_btn_box {
// display: flex;
.el-button {
margin-left: 12px !important;
margin-right: 0px !important;
}
}
.flex_ {
display: flex;
align-items: center;
}
.flex_cetner{
justify-content: center;
}
.red_ {
color: red;
font-size: 14px;
margin-right: 5px;
}
}
</style>
<style lang="scss">
.mult_table_box {
.el-select {
.el-select__wrapper {
height: 30px !important;
}
}
.el-form-item {
margin-bottom: 1px !important;
.el-form-item__content {
font-size: 12px !important;
}
}
.el-date-editor,
.el-input-number {
width: 100%;
}
.el-scrollbar__bar {
display: block !important;
}
.el-select,
.w100 {
width: 100%;
}
}
</style>
2.使用的表头格式为:
cpp
export interface columnsType {
prop: string
propLable?: string
label: string
width?: string
type:
| 'input'
| 'textarea'//文本域
| 'select'//下拉
| 'date'//yyyy-mmm-hh格式
| 'datetime'//yyyy-mmm-hh dd:hh:ss格式
| 'timestamp'//时间戳格式
| 'switch'//开关
| 'number'//整数
| 'input-number'//一位小数点
| 'getValue'
| 'parentKksCode'
| 'uploadFile'//上传文件
| 'uploadImgs'//上传图片
| 'dictSelect'//字典下拉-value为numner
| 'dictSelect_string'//字典下拉-value为string
| 'slot' // 自定义
optionApi?: string//废弃?
dictName?: string//字典名称
disabled?: boolean
children?: columnsType[]
showTooltip?: boolean
required?: boolean
}
例:
cpp
const kksFuntionColumns: columnsType[] = [
{
prop: 'name',
label: 'KKS编码字典',
type: 'input',
width: '',
children: [
{
prop: 'codePrefix',
label: '编码前缀',
type: 'input',
width: '80',
disabled: true
},
{
prop: 'code1',
label: '全厂码(F0)',
type: 'input',
width: '280'
},
]
},
{
prop: 'isClosed',
label: '是否关闭',
type: 'slot',//如果为slot 可以在使用组件的页面 自定义编辑或者展示时的形式 solt的name为prop的值,编辑时solt的name为'prop的值+Edit' 下面有例子
width: '240',
disabled: true
},
]
3.使用
cpp
<!-- 表格 -->
<multi-table
ref="muliTableRef"
:columns="personnelPassColumns"
:tableData="tableData"
:showTooltip="true"
v-loading="loading"
@handle-save="handleSave"
@handle-delete="handleDelete"
@handle-refresh="getTableData"
operationWidth="200"
>
<!-- 是否关闭 -->
<template #isClosed="scope">
{{ scope.row.isClosed ? '是' : '否' }}
</template>
<template #isClosedEdit="scope">
自定义编辑的时候如何展示
</template>
<!-- 操作 -->
<template #operation="scope">
<el-button
link
type="info"
size="small"
@click.stop="handleClose(scope.row)"
>
测试按钮
</el-button>
</template>
</multi-table>