|
|
@ -2,43 +2,79 @@ |
|
|
<div class="custom-table-wrapper"> |
|
|
<div class="custom-table-wrapper"> |
|
|
<div class="custom-table-header"> |
|
|
<div class="custom-table-header"> |
|
|
<div class="custom-table-row"> |
|
|
<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 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> |
|
|
<div>{{ col.label }}</div> |
|
|
<template v-if="col.showSelect&& col.options"> |
|
|
|
|
|
<select v-model="col.selected" @change="onHeaderSelectChange(index, $event)"> |
|
|
|
|
|
<option value="" disabled>{{ col.placeholder || '请选择' }}</option> |
|
|
|
|
|
<option |
|
|
|
|
|
v-for="opt in col.options" |
|
|
|
|
|
:key="opt.value" |
|
|
|
|
|
:value="opt.value" |
|
|
|
|
|
> |
|
|
|
|
|
{{ opt.label }} |
|
|
|
|
|
</option> |
|
|
|
|
|
</select> |
|
|
|
|
|
</template> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<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> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="custom-table-body"> |
|
|
<div class="custom-table-body"> |
|
|
<div |
|
|
|
|
|
v-for="(row, rowIndex) in dataSource" |
|
|
|
|
|
: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' }" |
|
|
|
|
|
> |
|
|
|
|
|
{{ row[col.prop] }} |
|
|
|
|
|
|
|
|
<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)" 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)" |
|
|
|
|
|
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)" 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'"> |
|
|
|
|
|
<HandleFormItem type="inputNumber" :item="getBodySubItem(col)" |
|
|
|
|
|
v-model="row[col.bodySubKey]" @change="onBodySubValueChange(rowIndex, colIndex, $event)" /> |
|
|
|
|
|
</template> |
|
|
|
|
|
<!-- 预填才显示精度输入框 --> |
|
|
|
|
|
<template v-else-if="col.bodySubType === 'precisionNumber'&& $store.state.template.templateStatus === 'preFill'"> |
|
|
|
|
|
<HandleFormItem type="inputNumber" class="sub-input-number" :item="getPrecisionNumberItem(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> |
|
|
</div> |
|
|
</div> |
|
|
@ -46,8 +82,12 @@ |
|
|
</template> |
|
|
</template> |
|
|
|
|
|
|
|
|
<script> |
|
|
<script> |
|
|
|
|
|
import HandleFormItem from "./HandleFormItem.vue" |
|
|
export default { |
|
|
export default { |
|
|
name: 'CustomTable', |
|
|
name: 'CustomTable', |
|
|
|
|
|
components: { |
|
|
|
|
|
HandleFormItem |
|
|
|
|
|
}, |
|
|
props: { |
|
|
props: { |
|
|
columns: { |
|
|
columns: { |
|
|
type: Array, |
|
|
type: Array, |
|
|
@ -63,9 +103,168 @@ export default { |
|
|
required: true |
|
|
required: true |
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
|
|
|
data() { |
|
|
|
|
|
return { |
|
|
|
|
|
localDataSource: [], |
|
|
|
|
|
headerSelectFields: {} |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
|
|
|
watch: { |
|
|
|
|
|
dataSource: { |
|
|
|
|
|
immediate: true, |
|
|
|
|
|
handler(newData) { |
|
|
|
|
|
this.localDataSource = JSON.parse(JSON.stringify(newData)); |
|
|
|
|
|
// 初始化表头选择器值 |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
|
|
|
columns: { |
|
|
|
|
|
immediate: true, |
|
|
|
|
|
handler(newColumns) { |
|
|
|
|
|
this.initHeaderSelectValues(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
methods: { |
|
|
methods: { |
|
|
onHeaderSelectChange(colIndex, event) { |
|
|
|
|
|
this.$emit('header-select-change', colIndex, event.target.value); |
|
|
|
|
|
|
|
|
// 初始化表头选择器值 |
|
|
|
|
|
initHeaderSelectValues() { |
|
|
|
|
|
const headerSelectObj = {}; |
|
|
|
|
|
this.columns.map(col => { |
|
|
|
|
|
if(col.headerSelectKey){ |
|
|
|
|
|
headerSelectObj[col.headerSelectKey] = col.defaultValue || col.headerOptions[0].value || "" |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
console.log(headerSelectObj,"headerSelectObj") |
|
|
|
|
|
this.headerSelectFields = headerSelectObj; |
|
|
|
|
|
}, |
|
|
|
|
|
// 获取最新数据 |
|
|
|
|
|
getFormData() { |
|
|
|
|
|
// 合并表头选择器值到 columns |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 数据校验 |
|
|
|
|
|
const validateResult = this.validateFormData(); |
|
|
|
|
|
return new Promise((resolve,reject)=>{ |
|
|
|
|
|
if(validateResult.valid){ |
|
|
|
|
|
resolve({ |
|
|
|
|
|
dataSource: [...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) { |
|
|
|
|
|
return { |
|
|
|
|
|
fillType: col.bodyFillType, |
|
|
|
|
|
options: col.bodyOptions, |
|
|
|
|
|
maxLength: col.bodyMaxLength, |
|
|
|
|
|
label: "" |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
|
|
|
getBodySubItem(col) { |
|
|
|
|
|
return { |
|
|
|
|
|
fillType: col.bodySubFillType, |
|
|
|
|
|
options: col.bodySubOptions, |
|
|
|
|
|
label: "", |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
|
|
|
getPrecisionNumberItem(col) { |
|
|
|
|
|
return { |
|
|
|
|
|
fillType: col.bodySubFillType, |
|
|
|
|
|
options: col.bodySubOptions, |
|
|
|
|
|
label: "", |
|
|
|
|
|
placeholder:"请输入保留小数位数" |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
|
|
|
// 删除行 |
|
|
|
|
|
deleteRow(rowIndex) { |
|
|
|
|
|
this.localDataSource.splice(rowIndex, 1); |
|
|
|
|
|
this.$emit('row-delete', rowIndex); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
}; |
|
|
}; |
|
|
@ -78,6 +277,22 @@ export default { |
|
|
overflow: hidden; |
|
|
overflow: hidden; |
|
|
font-size: 14px; |
|
|
font-size: 14px; |
|
|
color: #606266; |
|
|
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; |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/* 表头 */ |
|
|
/* 表头 */ |
|
|
@ -89,7 +304,14 @@ export default { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.custom-table-body { |
|
|
.custom-table-body { |
|
|
max-height: 300px; /* 可根据需要调整或由父组件控制 */ |
|
|
|
|
|
|
|
|
max-height: 300px; |
|
|
|
|
|
/* 可根据需要调整或由父组件控制 */ |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.header-cell-content { |
|
|
|
|
|
display: flex; |
|
|
|
|
|
align-items: center; |
|
|
|
|
|
justify-content: center; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/* 共同行样式 */ |
|
|
/* 共同行样式 */ |
|
|
@ -103,6 +325,9 @@ export default { |
|
|
display: table; |
|
|
display: table; |
|
|
width: 100%; |
|
|
width: 100%; |
|
|
table-layout: fixed; |
|
|
table-layout: fixed; |
|
|
|
|
|
&:not(:last-child) { |
|
|
|
|
|
border-bottom: 1px solid #ebeef5; |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/* 单元格 */ |
|
|
/* 单元格 */ |
|
|
@ -140,7 +365,8 @@ export default { |
|
|
background-color: #fff; |
|
|
background-color: #fff; |
|
|
font-size: 13px; |
|
|
font-size: 13px; |
|
|
color: #606266; |
|
|
color: #606266; |
|
|
appearance: none; /* 隐藏默认箭头(可选) */ |
|
|
|
|
|
|
|
|
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-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-repeat: no-repeat; |
|
|
background-position: right 8px center; |
|
|
background-position: right 8px center; |
|
|
@ -152,7 +378,8 @@ export default { |
|
|
.custom-table-wrapper { |
|
|
.custom-table-wrapper { |
|
|
display: flex; |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
flex-direction: column; |
|
|
max-width: 100%; /* 父容器决定宽度 */ |
|
|
|
|
|
|
|
|
max-width: 100%; |
|
|
|
|
|
/* 父容器决定宽度 */ |
|
|
overflow-x: auto; |
|
|
overflow-x: auto; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -160,4 +387,12 @@ export default { |
|
|
.custom-table-body { |
|
|
.custom-table-body { |
|
|
min-width: 100%; |
|
|
min-width: 100%; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.header-select { |
|
|
|
|
|
width: 100px; |
|
|
|
|
|
margin-left: 5px; |
|
|
|
|
|
} |
|
|
|
|
|
.delete-button{ |
|
|
|
|
|
color: red; |
|
|
|
|
|
} |
|
|
</style> |
|
|
</style> |