<template>
|
|
<div class="custom-table-wrapper">
|
|
<div class="custom-table-header">
|
|
<div class="custom-table-row">
|
|
<div v-for="(col, index) in columns" :key="index" class="custom-table-cell header-cell"
|
|
:style="{ width: col.width ? col.width + 'px' : 'auto' }">
|
|
<div class="header-cell-content">
|
|
<div>{{ col.label }}</div>
|
|
<template v-if="col.headerSelectKey && col.headerOptions">
|
|
<HandleFormItem type="select" class="header-select" :item="getHeaderItem(col)"
|
|
v-model="headerSelectFields[col.headerSelectKey]" @change="onHeaderSelectChange(index, $event)" />
|
|
</template>
|
|
</div>
|
|
|
|
</div>
|
|
<!-- 默认操作栏 -->
|
|
<div class="custom-table-cell header-cell" :style="{ width: '80px' }">
|
|
<div class="header-cell-content">
|
|
<div>操作</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="custom-table-body">
|
|
<div v-for="(row, rowIndex) in localDataSource" :key="rowIndex" class="custometable-row">
|
|
<div v-for="(col, colIndex) in columns" :key="colIndex" class="custom-table-cell body-cell"
|
|
:style="{ width: col.width ? col.width + 'px' : 'auto' }">
|
|
<div class="inner-table-cell">
|
|
<div>
|
|
<template v-if="col.bodyType === 'input'">
|
|
<HandleFormItem type="input" class="body-input" :item="getBodyItem(col,rowIndex)" v-model="row[col.prop]" @change="onBodyValueChange(rowIndex, colIndex, $event)" />
|
|
</template>
|
|
<template v-else-if="col.bodyType === 'inputNumber'">
|
|
<HandleFormItem type="inputNumber" class="body-input-number" :item="getBodyItem(col,rowIndex)"
|
|
v-model="row[col.prop]" @change="onBodyValueChange(rowIndex, colIndex, $event)" />
|
|
</template>
|
|
<template v-else-if="col.bodyType === 'select'">
|
|
<HandleFormItem type="select" class="body-select" :item="getBodyItem(col,rowIndex)" v-model="row[col.prop]" @change="onBodyValueChange(rowIndex, colIndex, $event)" />
|
|
</template>
|
|
<template v-else>
|
|
{{ row[col.prop] }}
|
|
</template>
|
|
</div>
|
|
<div class="m-l-5">
|
|
<template v-if="col.bodySubType === 'inputNumber' && col.showBodySub">
|
|
<HandleFormItem type="inputNumber" :item="getBodySubItem(col)"
|
|
v-model="row[col.bodySubKey]" @change="onBodySubValueChange(rowIndex, colIndex, $event)" />
|
|
</template>
|
|
<template v-else>
|
|
{{ row[col.bodySubKey] }}
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- 默认操作栏 -->
|
|
<div class="custom-table-cell body-cell" :style="{ width: '80px' }">
|
|
<div class="inner-table-cell">
|
|
<el-popconfirm
|
|
confirm-button-text='确认'
|
|
cancel-button-text='取消'
|
|
icon="el-icon-info"
|
|
icon-color="red"
|
|
title="确认删除当前数据?"
|
|
@confirm = "deleteRow(rowIndex)"
|
|
>
|
|
<el-button slot="reference" type="text" size="small" class="delete-button" >
|
|
删除
|
|
</el-button>
|
|
</el-popconfirm>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import HandleFormItem from "./HandleFormItem.vue"
|
|
export default {
|
|
name: 'CustomTable',
|
|
components: {
|
|
HandleFormItem
|
|
},
|
|
props: {
|
|
columns: {
|
|
type: Array,
|
|
required: true,
|
|
// 示例格式:
|
|
// [
|
|
// { label: '姓名', prop: 'name' },
|
|
// { label: '状态', prop: 'status', type: 'select', options: [{value:1,label:'启用'},...], selected: null }
|
|
// ]
|
|
},
|
|
formData: {
|
|
type: Object,
|
|
default:()=>{
|
|
return {
|
|
stepTableFormData: [],
|
|
headerSelectFields: {}
|
|
}
|
|
}
|
|
},
|
|
},
|
|
data() {
|
|
return {
|
|
localDataSource: [],
|
|
headerSelectFields: {}
|
|
}
|
|
},
|
|
watch: {
|
|
formData:{
|
|
immediate: true,
|
|
handler(newData) {
|
|
console.log(newData,"newData")
|
|
const {stepTableFormData = [], headerSelectFields = {}} = newData;
|
|
this.updateDataSource(stepTableFormData);
|
|
this.headerSelectFields = JSON.parse(JSON.stringify(headerSelectFields))
|
|
}
|
|
},
|
|
},
|
|
mounted() {
|
|
this.initHeaderSelectValues();
|
|
},
|
|
methods: {
|
|
// 初始化表头选择器值
|
|
initHeaderSelectValues() {
|
|
const headerSelectObj = {};
|
|
this.columns.map(col => {
|
|
if(col.headerSelectKey){
|
|
headerSelectObj[col.headerSelectKey] = col.defaultValue || col.headerOptions[0].value || ""
|
|
}
|
|
});
|
|
this.headerSelectFields = headerSelectObj;
|
|
},
|
|
// 获取最新数据
|
|
getFormData() {
|
|
// 合并表头选择器值到 columns
|
|
|
|
|
|
// 数据校验
|
|
const validateResult = this.validateFormData();
|
|
return new Promise((resolve,reject)=>{
|
|
if(validateResult.valid){
|
|
resolve({
|
|
stepTableFormData: [...this.localDataSource],
|
|
headerSelectFields: this.headerSelectFields,
|
|
})
|
|
}else{
|
|
this.$message.error(validateResult.errors[0].error);
|
|
reject(validateResult.errors[0].error)
|
|
}
|
|
})
|
|
|
|
},
|
|
// 表单数据校验
|
|
validateFormData() {
|
|
const templateStatus = this.$store.state.template.templateStatus;
|
|
const errors = [];
|
|
|
|
// 遍历数据行
|
|
this.localDataSource.forEach((row, rowIndex) => {
|
|
// 遍历列
|
|
this.columns.forEach((col, colIndex) => {
|
|
// 只校验 fillType 与当前模板状态匹配的字段
|
|
if (col.bodyFillType === templateStatus || col.bodySubType === templateStatus) {
|
|
// 检查主字段
|
|
const mainValue = row[col.prop];
|
|
if (this.isValueEmpty(mainValue)) {
|
|
errors.push({
|
|
rowIndex,
|
|
colIndex,
|
|
field: col.prop,
|
|
label: col.label,
|
|
error: `请填写${col.label}`
|
|
});
|
|
}
|
|
|
|
// 检查子字段(如果有)
|
|
if (col.bodySubKey) {
|
|
const subValue = row[col.bodySubKey];
|
|
if (this.isValueEmpty(subValue)) {
|
|
errors.push({
|
|
rowIndex,
|
|
colIndex,
|
|
field: col.bodySubKey,
|
|
label: `${col.label}单位`,
|
|
error: `请填写${col.label}单位`
|
|
});
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
return {
|
|
valid: errors.length === 0,
|
|
errors: errors
|
|
};
|
|
},
|
|
// 判断值是否为空
|
|
isValueEmpty(value) {
|
|
if (value === null || value === undefined || value === '') {
|
|
return true;
|
|
}
|
|
if (typeof value === 'string' && value.trim() === '') {
|
|
return true;
|
|
}
|
|
if (Array.isArray(value) && value.length === 0) {
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
// 表头选择器变化
|
|
onHeaderSelectChange(colIndex, value) {
|
|
this.headerSelectValues[colIndex] = value;
|
|
this.$emit('header-select-change', colIndex, value);
|
|
},
|
|
// 表体值变化
|
|
onBodyValueChange(rowIndex, colIndex, value) {
|
|
const col = this.columns[colIndex];
|
|
this.localDataSource[rowIndex][col.prop] = value;
|
|
this.$emit('body-value-change', rowIndex, colIndex, value);
|
|
},
|
|
// 表体子值变化
|
|
onBodySubValueChange(rowIndex, colIndex, value) {
|
|
const col = this.columns[colIndex];
|
|
this.localDataSource[rowIndex][col.bodySubKey] = value;
|
|
this.$emit('body-sub-value-change', rowIndex, colIndex, value);
|
|
},
|
|
getHeaderItem(col) {
|
|
return {
|
|
fillType: col.fillType,
|
|
options: col.headerOptions,
|
|
label: ""
|
|
}
|
|
},
|
|
getBodyItem(col,rowIndex) {
|
|
const currentItem = this.localDataSource[rowIndex];
|
|
const item = {
|
|
fillType: col.bodyFillType,
|
|
options: col.bodyOptions,
|
|
maxlength: col.bodyMaxlength,
|
|
label: col.label,
|
|
precision: currentItem[col.bodyPrecisionKey] || col.precision || 0,
|
|
};
|
|
if(col.bodyDisabled){
|
|
item.disabled = col.bodyDisabled;
|
|
}
|
|
return item
|
|
},
|
|
getBodySubItem(col) {
|
|
const item = {
|
|
fillType: col.bodySubFillType,
|
|
options: col.bodySubOptions,
|
|
maxlength: col.bodySubMaxlength,
|
|
label: "",
|
|
placeholder:col.bodySubPlaceholder||"请输入"
|
|
}
|
|
if(col.bodySubDisabled){
|
|
item.disabled = col.bodySubDisabled;
|
|
}
|
|
},
|
|
// 删除行
|
|
deleteRow(rowIndex) {
|
|
this.localDataSource.splice(rowIndex, 1);
|
|
this.$emit('row-delete', rowIndex);
|
|
},
|
|
// 更新数据方法,可在formData变更时调用,也可由父组件调用
|
|
updateDataSource(dataSource = []) {
|
|
// 深拷贝数据以避免直接修改原始数据
|
|
this.localDataSource = JSON.parse(JSON.stringify(dataSource || []));
|
|
}
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.custom-table-wrapper {
|
|
border: 1px solid #ebeef5;
|
|
border-radius: 4px;
|
|
overflow: hidden;
|
|
font-size: 14px;
|
|
color: #606266;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.inner-table-cell{
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
.m-l-5{
|
|
margin-left: 5px;
|
|
}
|
|
.sub-input-number{
|
|
width: 145px;
|
|
.el-input-number--mini{
|
|
width: 145px;
|
|
}
|
|
}
|
|
|
|
/* 表头 */
|
|
.custom-table-header {
|
|
background-color: #f5f7fa;
|
|
border-bottom: 1px solid #ebeef5;
|
|
white-space: nowrap;
|
|
display: block;
|
|
}
|
|
|
|
.custom-table-body {
|
|
max-height: 300px;
|
|
/* 可根据需要调整或由父组件控制 */
|
|
}
|
|
|
|
.header-cell-content {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
/* 共同行样式 */
|
|
.custom-table-row {
|
|
display: table;
|
|
width: 100%;
|
|
table-layout: fixed;
|
|
}
|
|
|
|
.custometable-row {
|
|
display: table;
|
|
width: 100%;
|
|
table-layout: fixed;
|
|
&:not(:last-child) {
|
|
border-bottom: 1px solid #ebeef5;
|
|
}
|
|
}
|
|
|
|
/* 单元格 */
|
|
.custom-table-cell {
|
|
display: table-cell;
|
|
padding: 12px 10px;
|
|
text-align: left;
|
|
vertical-align: middle;
|
|
border-right: 1px solid #ebeef5;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.custom-table-cell:last-child {
|
|
border-right: none;
|
|
}
|
|
|
|
.header-cell {
|
|
font-weight: bold;
|
|
color: #909399;
|
|
background-color: #f5f7fa;
|
|
}
|
|
|
|
.body-cell {
|
|
color: #606266;
|
|
background-color: #fff;
|
|
}
|
|
|
|
/* select 样式(模仿 Element UI) */
|
|
.header-cell select {
|
|
width: 100%;
|
|
padding: 4px 8px;
|
|
border: 1px solid #dcdfe6;
|
|
border-radius: 4px;
|
|
outline: none;
|
|
background-color: #fff;
|
|
font-size: 13px;
|
|
color: #606266;
|
|
appearance: none;
|
|
/* 隐藏默认箭头(可选) */
|
|
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6,9 12,15 18,9'%3e%3c/polyline%3e%3c/svg%3e");
|
|
background-repeat: no-repeat;
|
|
background-position: right 8px center;
|
|
background-size: 14px;
|
|
padding-right: 28px;
|
|
}
|
|
|
|
/* 滚动容器:如果整体宽度超限,显示横向滚动条 */
|
|
.custom-table-wrapper {
|
|
display: flex;
|
|
flex-direction: column;
|
|
max-width: 100%;
|
|
/* 父容器决定宽度 */
|
|
overflow-x: auto;
|
|
}
|
|
|
|
.custom-table-header,
|
|
.custom-table-body {
|
|
min-width: 100%;
|
|
}
|
|
|
|
.header-select {
|
|
width: 100px;
|
|
margin-left: 5px;
|
|
}
|
|
.delete-button{
|
|
color: red;
|
|
}
|
|
</style>
|