华西海圻ELN前端工程
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

1455 lines
48 KiB

<template>
<div>
<div class="custom-table-wrapper" :class="{ 'no-border': !isBorder }">
<div class="custom-table-header no-break" v-if="isBorder">
<div class="custom-table-row">
<div v-if="showSort" class="custom-table-cell header-cell sort-cell">
序号
</div>
<div class="custom-table-cell header-cell c-cell" v-if="showCheckAll">
<div class="checkbox-item">
<el-checkbox v-model="checkAll" :indeterminate="isIndeterminate"
@change="handleCheckAllChange"></el-checkbox>
</div>
</div>
<div v-for="(col, colIndex) in columns" :key="colIndex" class="custom-table-cell header-cell no-break"
:style="getCellWidth(col)">
<div class="header-cell-content" v-if="col.headerColumns && col.headerColumns.length > 0">
<div class="header-columns-grid"
:style="{ 'grid-template-columns': `repeat(${col.span || 2}, 1fr)` }">
<div v-for="(headerCol, headerIndex) in col.headerColumns" :key="headerIndex"
class="header-column-item" >
<template v-if="headerCol.type === 'span'">
<div class="span-content">{{ $t(headerCol.label) }}</div>
</template>
<template v-else-if="isRegent(headerCol)">
<HandleFormItem
:fieldKey="prefixKey + colIndex + '_' + headerCol.key + '_' + headerIndex"
:fieldItemLabel="fieldItemLabel" :type="headerCol.type"
class="body-clickable" sourceFrom="customTable"
:item="getHeaderColumnItem(headerCol)"
:value="headerFields[`${colIndex}_${headerIndex}`]"
:error="hasHeaderError(colIndex, headerIndex, headerCol.key)"
@update:error="onHeaderColumnErrorUpdate(colIndex, headerIndex, headerCol.key, $event)"
@onRegentSubmit="(data, inputValue) => onHeaderRegentSubmit(data, inputValue, colIndex, headerIndex)" />
</template>
<template
v-else-if="headerCol.type === 'input' || headerCol.type === 'select' || headerCol.type === 'inputNumber'">
<HandleFormItem
:fieldKey="prefixKey + '_header_' + colIndex + '_' + headerIndex"
:fieldItemLabel="fieldItemLabel" :type="headerCol.type"
:item="getHeaderColumnItem(headerCol)"
v-model="headerFields[`${colIndex}_${headerIndex}`]"
@change="onHeaderColumnChange(colIndex, headerIndex, headerCol, $event)"
:error="hasHeaderError(colIndex, headerIndex, headerCol.key)"
@update:error="onHeaderColumnErrorUpdate(colIndex, headerIndex, headerCol.key, $event)" />
</template>
</div>
</div>
</div>
<div class="header-cell-content" v-else>
<div>{{ $t(col.label) }}</div>
<template
v-if="col.headerSelectKey && col.headerOptions && (showHeaderSelect || templateFillType === 'preFill')">
<HandleFormItem :fieldKey="prefixKey + '_' + col.headerSelectKey"
:fieldItemLabel="fieldItemLabel" type="select" class="header-select"
:item="getHeaderItem(col)" v-model="headerSelectFields[col.headerSelectKey]"
@change="onHeaderSelectChange(col, $event)"
:error="hasError(-1, colIndex, col.headerSelectKey)"
@update:error="onErrorUpdate(-1, colIndex, col.headerSelectKey, $event)" />
</template>
<div v-else-if="headerSelectFields[col.headerSelectKey]" class="fill-type-icon"
:style="{ width: (templateFillType !== 'actFill') ? '60px' : 'auto' }">({{
headerSelectFields[col.headerSelectKey] }})</div>
</div>
</div>
<!-- 默认操作栏 -->
<div class="custom-table-cell header-cell" :style="{ width: operationWidth }" v-if="showOperation">
<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 no-break">
<div v-if="showSort" class="custom-table-cell body-cell sort-cell">
{{ rowIndex + 1 }}
</div>
<div class="custom-table-cell body-cell c-cell" v-if="showCheckAll">
<div class="checkbox-item">
<el-checkbox v-model="row._checked" @change="handleCheckChange(row, $event)"></el-checkbox>
</div>
</div>
<div v-for="(col, colIndex) in columns" :key="colIndex" class="custom-table-cell body-cell no-break"
:style="getCellWidth(col)">
<div class="inner-table-cell">
<div class="flex1" :class="{ 'item-center': !isBorder && col.label }">
<div v-if="!isBorder && col.label" class="mr-5">
{{ $t(col.label) }}
</div>
<template
v-if="col.bodyType === 'input' || col.bodyType === 'inputNumber' || col.bodyType === 'select' || col.bodyType === 'dateTimeRange' || col.bodyType === 'radio'">
<div class="flex flex1">
<HandleFormItem :fieldKey="prefixKey + '_' + col.prop + '_' + row.id"
:fieldItemLabel="fieldItemLabel" :type="col.bodyType"
@blur="onBlur(rowIndex, col.prop, $event)" @copy="onCopy(rowIndex, col)"
class="body-input" :item="getBodyItem(col, rowIndex)"
v-model="row[col.prop]"
:ref = "col.prop+rowIndex"
@change="onBodyValueChange(rowIndex, colIndex, $event, row, col.bodyType)"
:error="hasError(rowIndex, colIndex, col.prop)"
@update:error="onErrorUpdate(rowIndex, colIndex, col.prop, $event)"
@beforeSaveRecord="(data, callback) => beforeSaveRecord(data, callback, rowIndex, col, row)"
:orange-bg="hasOrangeBg(rowIndex, colIndex, col.prop)" />
</div>
</template>
<div v-else-if = "col.bodyType === 'checkboxTree'">
<HandleFormItem
:field-item-label="fieldItemLabel" :field-key="prefixKey + '_' + col.prop+ rowIndex"
type="checkboxTree" :item="getBodyItem(col, rowIndex)" :value="row[col.prop]"
@change="(e) => onBodyValueChange(rowIndex, colIndex, e, row, col.bodyType)"
:error="hasError(rowIndex, colIndex, col.prop)" @update:error="onErrorUpdate(rowIndex, colIndex, col.prop, $event)"
:orange-bg="hasOrangeBg(rowIndex, colIndex, col.prop)" />
</div>
<div v-else-if="col.bodyType === 'operableInput'" class="flex flex1">
<div class="flex1 grid-container">
<div class="flex"
:class="{ 'full-row': row[col.prop] && row[col.prop].length == 1 }"
v-for="(opItem, itemIndex) in row[col.prop]" :key="itemIndex">
<HandleFormItem
:fieldKey="prefixKey + '_' + col.prop + '_' + row.id + '_' + itemIndex"
:fieldItemLabel="fieldItemLabel" type="input"
@blur="onOperableInputBlur(opItem, $event)" class="body-input"
:item="getBodyItem(col, rowIndex)" :value="opItem.value"
:error="hasError(rowIndex, colIndex, rowIndex+col.prop+itemIndex)"
@update:error="onErrorUpdate(rowIndex, colIndex, rowIndex+col.prop+itemIndex, $event)"
:orange-bg="hasOrangeBg(rowIndex, colIndex, rowIndex+col.prop+itemIndex)" />
<el-popconfirm confirm-button-text='确认' cancel-button-text='取消'
icon="el-icon-info" icon-color="red" title="确认删除当前输入框?"
@confirm="removeOperableInput(rowIndex, colIndex, col.prop, itemIndex)">
<i slot="reference" class="el-icon-remove-outline remove-icon"
v-if="itemIndex > 0 && templateFillType === 'actFill' && !row.isComplete"></i>
</el-popconfirm>
</div>
</div>
<i class="el-icon-circle-plus add-icon" v-if="templateFillType === 'actFill' && !row.isComplete"
@click="addOperableInput(rowIndex, colIndex, col.prop)"></i>
</div>
<div class="flex flex1" v-else-if="col.bodyType === 'clickable'">
<HandleFormItem :fieldKey="prefixKey + '_' + col.prop + '_' + row.id"
:fieldItemLabel="fieldItemLabel" type="clickable" class="body-clickable"
:item="getBodyItem(col, rowIndex)" :value="row[col.prop]"
:error="hasError(rowIndex, colIndex, col.prop)"
@clickable="handleClickable(col, rowIndex, colIndex, row)"
@resetRecord="resetRecord(rowIndex, colIndex, col.prop)"
@update:error="onErrorUpdate(rowIndex, colIndex, col.prop, $event)"
:orange-bg="hasOrangeBg(rowIndex, colIndex, col.prop)" />
</div>
<div class="flex flex1" v-else-if="isRegent(col, 'bodyType')">
<HandleFormItem :fieldKey="prefixKey + '_' + col.prop + '_' + row.id"
:fieldItemLabel="fieldItemLabel" :type="col.bodyType" class="body-clickable"
sourceFrom="customTable" :item="getBodyItem(col, rowIndex)"
:value="row[col.prop]" :error="hasError(rowIndex, colIndex, col.prop)"
@onRegentSubmit="(data, inputValue) => onRegentSubmit(data, inputValue, col, rowIndex, colIndex, row, col.prop)"
@beforeReagentSubmit="(data, callback) => onBeforeReagentSubmit(data, callback, col, row)"
@update:error="onErrorUpdate(rowIndex, colIndex, col.prop, $event)"
:orange-bg="hasOrangeBg(rowIndex, colIndex, col.prop)" />
</div>
<template v-else-if="col.bodyType === 'span'">
<div class="body-span">
{{ row[col.prop] }}
</div>
</template>
<template v-else-if="col.bodyType === 'checkboxTag'">
<div class="flex flex-wrap"
:class="{ 'row-error-border': hasError(rowIndex, colIndex, col.prop) }">
<HandleFormItem :fieldKey="prefixKey + '_' + col.prop + '_' + row.id"
:fieldItemLabel="fieldItemLabel" type="checkboxTag" :value="row[col.prop]"
:item="getBodyItem(col, rowIndex)"
@change="onCheckboxTagChange(rowIndex, colIndex, col, $event)"
@deleteTag="onDeleteCheckboxTag(rowIndex, col, $event)"
:error="hasError(rowIndex, colIndex, col.prop)"
@update:error="onErrorUpdate(rowIndex, colIndex, col.prop, $event)" />
</div>
</template>
<template v-else-if="col.bodyType === 'checkbox'">
<HandleFormItem :fieldKey="prefixKey + '_' + col.prop + '_' + row.id"
:fieldItemLabel="fieldItemLabel" type="checkbox" v-model="row[col.prop]"
:item="getBodyItem(col, rowIndex)"
@change="onCheckboxChange(rowIndex, colIndex, col, $event)"
:error="hasError(rowIndex, colIndex, col.prop)"
@update:error="onErrorUpdate(rowIndex, colIndex, col.prop, $event)" />
</template>
</div>
<div v-show="isShowOther(row[col.prop], col)" class="flex flex1">
<div class="other-title">{{ col.otherLabel ? $t(col.otherLabel) :
$t("template.common.other") }}
</div>
<div class="flex flex1">
<HandleFormItem :field-item-label="fieldItemLabel"
:field-key="prefixKey + '_' + col.otherCode"
@blur="onBlur(rowIndex, col.prop, $event)" :item="getOtherItem(col)"
v-model="row[col.otherCode]"
:error="hasError(rowIndex, colIndex, col.otherCode)"
@update:error="onErrorUpdate(rowIndex, colIndex, col.otherCode, $event)"
:orange-bg="hasOrangeBg(rowIndex, colIndex, col.otherCode)" />
</div>
</div>
<div class="m-l-5 flex" :class="{ 'flex1': (col.bodySubType !== 'button' && col.bodySubType !== 'span') }"
v-if="isShowBodySub(col, row)">
<template
v-if="col.bodySubType === 'inputNumber' || col.bodySubType === 'input' || col.bodySubType === 'select'">
<HandleFormItem :fieldKey="prefixKey + '_' + col.bodySubKey + '_' + row.id"
:fieldItemLabel="fieldItemLabel" :type="col.bodySubType"
@blur="onSubBlur(rowIndex, col.bodySubKey, $event)"
@copy="onCopy(rowIndex, col)" :item="getBodySubItem(col)"
v-model="row[col.bodySubKey]"
@change="onBodySubValueChange(rowIndex, colIndex, $event, row, col.bodySubType)"
:error="hasError(rowIndex, colIndex, col.bodySubKey)"
@update:error="onErrorUpdate(rowIndex, colIndex, col.bodySubKey, $event)"
:orange-bg="hasOrangeBg(rowIndex, colIndex, col.bodySubKey)" />
</template>
<template v-else-if="col.bodySubType === 'span'">
<div class="body-span">
{{ row[col.bodySubKey] }}
</div>
</template>
<template v-else-if="col.bodySubType === 'button'">
<HandleFormItem class="ml-10" type="button" :item="getBodyButtonItem(col, rowIndex)"
:value="row[col.bodySubKey]"
:fieldKey="prefixKey + '_' + col.bodySubKey + '_' + row.id"
@clickButton="(e,val, data) => handleClickButton(e, data, col.bodySubKey, rowIndex, colIndex)" />
</template>
<div class="flex flex1" v-else-if="isRegent(col, 'bodySubType')">
<HandleFormItem :fieldKey="prefixKey + '_' + col.bodySubKey + '_' + row.id"
:fieldItemLabel="fieldItemLabel" :type="col.bodySubType" class="body-clickable"
sourceFrom="customTable" :item="getBodySubItem(col, rowIndex)"
:value="row[col.bodySubKey]"
:error="hasError(rowIndex, colIndex, col.bodySubKey)"
@onRegentSubmit="(data, inputValue) => onRegentSubmit(data, inputValue, col, rowIndex, colIndex, row, col.bodySubKey)"
@beforeReagentSubmit="(data, callback) => onBeforeReagentSubmit(data, callback, col, row)"
@update:error="onErrorUpdate(rowIndex, colIndex, col.bodySubKey, $event)"
:orange-bg="hasOrangeBg(rowIndex, colIndex, col.bodySubKey)" />
</div>
<template v-if="col.bodyThirdType === 'button'">
<HandleFormItem class="ml-10" type="button" :item="getBodyThirdButtonItem(col, rowIndex)"
:value="row[col.bodyThirdKey]"
:fieldKey="prefixKey + '_' + col.bodyThirdKey + '_' + row.id"
@clickButton="(e,val, data) => handleClickButton(e, data, col.bodyThirdKey, rowIndex, colIndex)" />
</template>
</div>
</div>
</div>
<!-- 默认操作栏 -->
<div class="custom-table-cell body-cell" :style="{ width: isBorder ? operationWidth : 'auto' }"
v-if="showOperation">
<div class="inner-table-cell">
<slot name="operation" :row="row" :rowIndex="rowIndex" :columns="getOperationColumns()">
</slot>
</div>
</div>
</div>
</div>
<div v-if="localDataSource.length == 0">
<div class="no-data">暂无数据</div>
</div>
</div>
<div class="add-row" v-if="isShowAddRos()">
<el-button type="primary" plain @click="onAddRow">添加行</el-button>
</div>
</div>
</template>
<script>
import HandleFormItem from "./HandleFormItem.vue";
import { isEqual } from "@/utils/index.js";
import { isShowOther } from "@/utils/formPackageCommon.js";
import { EventBus } from "@/utils/eventBus";
import { getuuid, justUpdateFilledFormData } from "@/utils/index.js";
import { isRegent } from "@/utils/index.js";
import { isValueEmpty } from '@/utils/index.js';
import _ from "lodash";
export default {
inject: ['templateFillType', 'getZdxgjl', 'updateZdxgjl'],
name: 'CustomTable',
components: {
HandleFormItem
},
props: {
operationWidth: {
type: String,
default: '245px',
},
// 是否显示表头选择器
showHeaderSelect: {
type: Boolean,
default: false,
},
showAddRow: {
type: Boolean,
default: undefined,
},
// 是否显示操作栏
showOperation: {
type: Boolean,
default: true,
},
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: {},
}
}
},
fieldItemLabel: {
type: String,
default: '',
},
//循环组件的情况下需要用这个来区分字段
prefixKey: {
type: String,
default: "",
},
isBorder: {//是否无边框,无边框的没有表头和border
type: Boolean,
default: true,
},
// 是否显示全选
showCheckAll: {
type: Boolean,
default: false,
},
// 是否显示排序
showSort: {
type: Boolean,
default: false,
},
},
data() {
return {
localDataSource: [],
headerSelectFields: {},
headerFields: {}, // 存储 headerColumns 的数据
formErrors: [], // 表单错误状态管理
orangeBgCells: {}, // 存储需要橙色背景的单元格 {rowIndex-colIndex: true/false}
isShowOther,
oldLocalDataSource: [],
uuid: getuuid(),
isRegent,
selectedRows: [], // 存储选中的行
isIndeterminate: false, // 半选状态
checkAll: false, // 全选状态
}
},
watch: {
formData: {
immediate: true,
handler(newData) {
const { stepTableFormData = [], headerSelectFields = {}, headerFields = {} } = newData;
this.updateDataSource(stepTableFormData);
this.headerSelectFields = JSON.parse(JSON.stringify(headerSelectFields));
this.headerFields = JSON.parse(JSON.stringify(headerFields));
// 在数据加载后检查 compareTo 逻辑
this.checkCompareToOnDataLoad();
}
},
localDataSource: {
immediate: true,
deep: true,
handler(newVal, oldVal) {
// if(newVal.length == 0){
// return
// }
// this.localDataSource = [...newVal];
}
}
},
mounted() {
},
unmounted() {
this.oldLocalDataSource = [];
},
methods: {
// 删除operableInput
removeOperableInput(rowIndex, colIndex, prop, itemIndex) {
this.localDataSource[rowIndex][prop].splice(itemIndex, 1);
justUpdateFilledFormData();
},
// 添加operableInput
addOperableInput(rowIndex, colIndex, prop) {
this.localDataSource[rowIndex][prop].push({ value: undefined });
justUpdateFilledFormData();
},
onOperableInputBlur(opItem, e) {
opItem.value = e;
},
getHeaderColumnItem(headerCol) {
return {
label: headerCol.label || '',
fillType: headerCol.fillType,
options: headerCol.options,
maxlength: headerCol.maxlength,
checkType: headerCol.checkType,
regentFillType: headerCol.regentFillType,
type: headerCol.type,
};
},
onHeaderColumnChange(colIndex, headerIndex, headerCol, value) {
const fieldKey = `${colIndex}_${headerIndex}`;
this.headerFields[fieldKey] = value;
this.$emit('headerColumnChange', {
colIndex,
headerIndex,
key: fieldKey,
value,
headerFields: this.headerFields
});
},
hasHeaderError(colIndex, headerIndex, key) {
return this.formErrors.some(error =>
error.rowIndex === -1 &&
error.colIndex === colIndex &&
error.headerIndex === headerIndex &&
error.field === key
);
},
onHeaderColumnErrorUpdate(colIndex, headerIndex, key, isError) {
if (!isError) {
this.formErrors = this.formErrors.filter(error =>
!(error.rowIndex === -1 &&
error.colIndex === colIndex &&
error.headerIndex === headerIndex &&
error.field === key)
);
}
},
// 删除checkboxTag
onDeleteCheckboxTag(rowIndex, col, tagIndex) {
this.localDataSource[rowIndex][col.prop].splice(tagIndex, 1);
this.$emit("onDeleteTag", rowIndex, col, tagIndex);
justUpdateFilledFormData();
},
onCheckboxTagChange(rowIndex, colIndex, col, value) {
// value 现在是整个数组
this.localDataSource[rowIndex][col.prop] = value;
// 根据校验规则判断是否清除错误状态
let isValid = false;
if (this.templateFillType === "actFill") {
// actFill时,检查是否有checked为true的项
isValid = value && value.some(tag => tag.checked === true);
} else if (this.templateFillType === "preFill") {
// preFill时,检查所有tagValue是否不为空
isValid = value && value.every(tag => tag.tagValue && (tag.tagValue+'').trim() !== '');
}
this.onErrorUpdate(rowIndex, colIndex, col.prop, !isValid);
this.$emit("onCheckboxTagChange", rowIndex, col, value)
},
// checkbox变化
onCheckboxChange(rowIndex, colIndex, col, value) {
this.localDataSource[rowIndex][col.prop] = value;
// 输入时清除对应表单项的错误状态
this.formErrors = this.formErrors.filter(error =>
!(error.rowIndex === rowIndex &&
error.colIndex === colIndex &&
error.field === col.prop)
);
this.$emit("onCheckboxChange", rowIndex, col, value);
justUpdateFilledFormData();
},
handleClickButton(e, data, key, rowIndex, colIndex) {
this.$emit("clickButton", key, rowIndex, colIndex, e, data,)
},
beforeSaveRecord(data, callback, rowIndex, col, row) {
this.$emit("beforeSaveRecord", { inputData: data, callback, rowIndex, key: col.prop, rowData: row, dataSource: this.localDataSource })
},
getCellWidth(col) {
const { templateFillType } = this;
let width = col.width ? col.width + 'px' : 'auto';
if (templateFillType !== "actFill" && templateFillType !== "preFill") {
width = (col.showWidth) ? col.showWidth + 'px' : (col.width ? col.width + 'px' : 'auto')
}
return { width }
},
//取消按钮 重置记录
resetRecord(rowIndex, colIndex,) {
if (this.localDataSource.length) {
this.localDataSource = [...this.oldLocalDataSource];
this.oldLocalDataSource = [];
}
},
//获取操作栏的列
getOperationColumns() {
return { columnsData: this.columns, headerSelectFields: this.headerSelectFields,fieldItemLabel: this.fieldItemLabel }
},
//获取其他下拉框的配制
getOtherItem(sItem) {
return {
label: sItem.otherLabel ? this.$t(sItem.otherLabel) : this.$t("template.common.other"),
fillType: sItem.bodyFillType,
maxlength: sItem.otherMaxlength || 50,
parentLabel: sItem.label,
type: "input"
}
},
isShowBodySub(col, row) {
if (col.hasOwnProperty("showBodySub")) {
return col.showBodySub
} else if (col.bodySubType === 'span' && !row[col.bodySubKey]) {//如果是span没有值的话就隐藏
return false;
}
return col.bodySubType && col.bodySubKey;
},
// 点击事件
handleClickable(col, rowIndex, colIndex, row) {
// if (this.templateFillType !== 'actFill') {
// return
// }
this.$emit("clickable", col, rowIndex, row)
},
onBeforeReagentSubmit(data, callback, col, row) {
if (this.templateFillType !== 'actFill') {
return
}
this.$emit("beforeReagentSubmit", { selectData: data, callback, key: col.prop, rowData: row })
},
onHeaderRegentSubmit(data, inputValue, colIndex, headerIndex) {
this.headerFields[`${colIndex}_${headerIndex}`] = inputValue;
this.$emit("onHeaderRegentSubmit", { selectInfo: data, headerIndex, colIndex, headerFields: this.headerFields })
},
onRegentSubmit(data, inputValue, col, rowIndex, colIndex, row, key) {
// if (this.templateFillType !== 'actFill') {
// return
// }
this.updateDataSourceByRowIndex(rowIndex, { [key]: inputValue })
this.$emit("onRegentSubmit", { selectInfo: data, key, col, rowIndex, colIndex, rowData: row })
},
isShowAddRos() {
if (this.showAddRow !== undefined) {
return this.showAddRow
}
return this.templateFillType === 'preFill';
},
// 复制值
onCopy(rowIndex, col) {
if (col.copyFrom) {
if (isValueEmpty(this.localDataSource[rowIndex][col.copyFrom])) {//没有值就不用复制了
return
}
this.updateDataSourceByRowIndex(rowIndex, { [col.prop]: this.localDataSource[rowIndex][col.copyFrom] }, "clickable")
this.onBlur(rowIndex, col.prop, this.localDataSource[rowIndex][col.prop]);
}
},
// 初始化表头选择器值
initHeaderSelectValues() {
const headerSelectObj = {};
this.columns.map(col => {
if (col.headerSelectKey) {
headerSelectObj[col.headerSelectKey] = col.defaultValue || col.headerOptions[0].value || ""
}
});
this.headerSelectFields = headerSelectObj;
},
// 直接获取表单数据,不做校验
getFilledFormData() {
return {
stepTableFormData: [...this.localDataSource],
headerSelectFields: this.headerSelectFields,
headerFields: this.headerFields,
};
},
// 获取最新数据
getFormData() {
// 合并表头选择器值到 columns
// 数据校验
const validateResult = this.validateFormData();
return new Promise((resolve, reject) => {
if (validateResult.valid) {
resolve({
stepTableFormData: [...this.localDataSource],
headerSelectFields: this.headerSelectFields,
headerFields: this.headerFields,
})
} else {
// this.$message.error("表单内容未填完,请填写后再提交");
reject(validateResult.errors[0].error)
}
})
},
// 表单数据校验
validateFormData() {
const errors = [];
// 清空之前的错误状态
this.formErrors = [];
// 校验表头的 HandleFormItem
this.columns.forEach((col, colIndex) => {
if (col.headerSelectKey && col.headerOptions && col.fillType === this.templateFillType) {
const headerValue = this.headerSelectFields[col.headerSelectKey];
if (isValueEmpty(headerValue)) {
const errorItem = {
rowIndex: -1, // 表头特殊标记
colIndex,
field: col.headerSelectKey,
label: this.$t(col.label),
error: `请选择${this.$t(col.label)}`
};
errors.push(errorItem);
this.formErrors.push(errorItem);
}
} else if (col.headerColumns && col.headerColumns.length > 0) {
col.headerColumns.forEach((headerCol, headerColIndex) => {
const headerValue = this.headerFields[`${colIndex}_${headerColIndex}`];
if (headerCol.fillType === this.templateFillType) {
if (isValueEmpty(headerValue) && headerCol.type !== "span") {
const errorItem = {
rowIndex: -1, // 表头特殊标记
colIndex,
field: headerCol.key,
label: this.$t(headerCol.label),
headerIndex: headerColIndex,
error: `请选择${this.$t(headerCol.label)}`
};
errors.push(errorItem);
this.formErrors.push(errorItem);
}
}
});
}
});
// 遍历数据行
this.localDataSource.forEach((row, rowIndex) => {
// 遍历列
this.columns.forEach((col, colIndex) => {
// 只校验 fillType 与当前模板状态匹配的字段
if (col.bodyFillType === this.templateFillType || col.bodySubFillType === this.templateFillType) {
// 检查主字段
const mainValue = row[col.prop];
if (col.bodyType === "checkboxTag") {
// checkboxTag类型的校验逻辑
if (this.templateFillType === "actFill") {
// actFill时,检查是否有checked为true的项
const hasChecked = mainValue && mainValue.some(tag => tag.checked === true);
if (!hasChecked && !col.bodyDisabled) {
const errorItem = {
rowIndex,
colIndex,
field: col.prop,
label: this.$t(col.label),
error: `请勾选${this.$t(col.label)}`
};
errors.push(errorItem);
this.formErrors.push(errorItem);
}
} else if (this.templateFillType === "preFill") {
// preFill时,检查所有tagValue是否不为空
const allTagValuesFilled = mainValue && mainValue.every(tag => tag.tagValue && (tag.tagValue+'').trim() !== '');
if (!allTagValuesFilled && !col.bodyDisabled) {
const errorItem = {
rowIndex,
colIndex,
field: col.prop,
label: this.$t(col.label),
error: `请填写${this.$t(col.label)}`
};
errors.push(errorItem);
this.formErrors.push(errorItem);
}
}
} else if (col.bodyType === "checkbox") {
// checkbox类型的校验逻辑
// checkbox只在actFill时进行必填校验
if (!col.bodyDisabled && this.templateFillType === 'actFill' && !col.isNeedCheck) {
// 单个checkbox:值必须为true
// checkbox组:至少选中一个
const hasChecked = Array.isArray(mainValue) ? mainValue.length > 0 : mainValue === true;
if (!hasChecked) {
const errorItem = {
rowIndex,
colIndex,
field: col.prop,
label: this.$t(col.label),
error: `请勾选${this.$t(col.label)}`
};
errors.push(errorItem);
this.formErrors.push(errorItem);
}
}
} else if(col.bodyType === "operableInput"){
mainValue.forEach((itemItem, itemIndex) => {
if (isValueEmpty(itemItem.value)) {
const errorItem = {
rowIndex,
colIndex,
field: rowIndex+col.prop+itemIndex,
label: this.$t(col.label),
error: `请填写${this.$t(col.label)}`
};
errors.push(errorItem);
this.formErrors.push(errorItem);
}
})
}else {
if (isValueEmpty(mainValue) && !col.bodyDisabled && col.bodyType !== 'span' && col.bodyType !== 'button') {
const errorItem = {
rowIndex,
colIndex,
field: col.prop,
label: this.$t(col.label),
error: `请填写${this.$t(col.label)}`
};
errors.push(errorItem);
this.formErrors.push(errorItem);
}
// 检查子字段(如果有)
if (col.bodySubKey && !col.bodySubDisabled && col.bodySubFillType === this.templateFillType && col.bodySubType !== 'span' && col.bodySubType !== "button") {
const subValue = row[col.bodySubKey];
if (isValueEmpty(subValue)) {
const errorItem = {
rowIndex,
colIndex,
field: col.bodySubKey,
label: `${this.$t(col.label)}单位`,
error: `请填写${this.$t(col.label)}单位`
};
errors.push(errorItem);
this.formErrors.push(errorItem);
}
}
}
console.log(col.otherCode, "col.otherCode")
// 检查其他输入框
if (col.otherCode) {
const isSelectedOther = this.isShowOther(mainValue);
if (!isSelectedOther) {
return;
}
const otherValue = row[col.otherCode];
if (isValueEmpty(otherValue)) {
const errorItem = {
rowIndex,
colIndex,
field: col.otherCode,
label: `${this.$t(col.label)}单位`,
error: `请填写${this.$t(col.otherLabel) ? this.$t(col.otherLabel) : '其他'}信息`
};
errors.push(errorItem);
this.formErrors.push(errorItem);
}
}
}
});
});
console.log(errors, this.localDataSource, "errors")
return {
valid: errors.length === 0,
errors: errors
};
},
// 表头选择器变化
onHeaderSelectChange(col, value) {
if (col.headerSelectTo) {
this.headerSelectFields[col.headerSelectTo] = value;
}
this.headerSelectFields[col.headerSelectKey] = value;
this.$emit('headerSelectChange', { key: col.headerSelectKey, headerSelectFields: this.headerSelectFields, dataSource: this.localDataSource });
// 输入时清除对应表单项的错误状态
this.formErrors = this.formErrors.filter(error =>
!(error.rowIndex === -1 &&
error.field === col.headerSelectKey)
);
},
// 检查并应用 compareTo 逻辑
checkCompareToLogic(rowIndex, colIndex, colKey, value) {
const col = this.columns[colIndex];
// 检查主字段的 compareTo 逻辑
if (col && col.bodyFillType === "actFill" && col.compareTo) {
const compareToValue = this.localDataSource[rowIndex][col.compareTo];
// 比较当前值和compareTo值,如果不相等则设置橙色背景
if (!isEqual(value, compareToValue)) {
this.setOrangeBg(rowIndex, colIndex, colKey, true);
} else {
// 相等则移除橙色背景
this.setOrangeBg(rowIndex, colIndex, colKey, false);
}
}
},
// 在数据加载时检查 compareTo 逻辑
checkCompareToOnDataLoad() {
// 遍历所有行和列,检查 compareTo 逻辑
this.localDataSource.forEach((row, rowIndex) => {
this.columns.forEach((col, colIndex) => {
const currentValue = row[col.prop];
const compareToValue = row[col.compareTo];
if (col.compareTo && !isValueEmpty(currentValue) && !isValueEmpty(compareToValue)) {
// 比较当前值和compareTo值,如果不相等则设置橙色背景
if (!isEqual(currentValue, compareToValue)) {
this.setOrangeBg(rowIndex, colIndex, col.prop, true);
} else {
// 相等则移除橙色背景
this.setOrangeBg(rowIndex, colIndex, col.prop, false);
}
}
// 检查子字段的 compareTo 逻辑
if (col.bodySubFillType === "actFill" && col.bodySubCompareTo) {
const currentValue = row[col.bodySubKey];
const compareToValue = row[col.bodySubCompareTo];
if (!isValueEmpty(currentValue) && !isValueEmpty(compareToValue)) {
// 比较当前值和compareTo值,如果不相等则设置橙色背景
if (!isEqual(currentValue, compareToValue)) {
this.setOrangeBg(rowIndex, colIndex, col.bodySubKey, true);
} else {
// 相等则移除橙色背景
this.setOrangeBg(rowIndex, colIndex, col.bodySubKey, false);
}
}
}
});
});
},
// 表体值变化
onBodyValueChange(rowIndex, colIndex, value, row, type) {
const col = this.columns[colIndex];
this.localDataSource[rowIndex][col.prop] = value;
// 检查并应用 compareTo 逻辑
this.checkCompareToLogic(rowIndex, colIndex, col.prop, value);
// 输入时清除对应表单项的错误状态
this.formErrors = this.formErrors.filter(error =>
!(error.rowIndex === rowIndex &&
error.colIndex === colIndex &&
error.field === col.prop)
);
if (type === "select") {
this.$emit('bodySelectChange', { rowIndex, item: row, colIndex, value, key: col.prop, dataSource: this.localDataSource, headerSelectFields: this.headerSelectFields });
}
},
// 表体子值变化
onBodySubValueChange(rowIndex, colIndex, value, row, type) {
const col = this.columns[colIndex];
this.localDataSource[rowIndex][col.bodySubKey] = value;
// 检查子字段的 compareTo 逻辑
if (col && col.bodySubFillType === "actFill" && col.bodySubCompareTo) {
const compareToValue = this.localDataSource[rowIndex][col.bodySubCompareTo];
// 比较当前值和compareTo值,如果不相等则设置橙色背景
if (value !== compareToValue) {
this.setOrangeBg(rowIndex, colIndex, col.bodySubKey, true);
} else {
// 相等则移除橙色背景
this.setOrangeBg(rowIndex, colIndex, col.bodySubKey, false);
}
}
// 输入时清除对应表单项的错误状态
this.formErrors = this.formErrors.filter(error =>
!(error.rowIndex === rowIndex &&
error.colIndex === colIndex &&
error.field === col.bodySubKey)
);
if (type === "select") {
this.$emit('bodySelectChange', { rowIndex, item: row, colIndex, value, key: col.bodySubKey, dataSource: this.localDataSource, headerSelectFields: this.headerSelectFields });
}
},
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: this.$t(col.label),
precision: currentItem[col.bodyPrecisionKey] || col.precision,
copyFrom: col.copyFrom || "",
compareTo: col.compareTo, // 添加 compareTo 字段
type: col.bodyType || "input",
filledCodes: col.filledCodes,
};
if (col.bodyDisabled) {
item.disabled = col.bodyDisabled;
}
if (col.qxbdType) {
item.qxbdType = col.qxbdType;
}
if (col.regentFillType) {
item.regentFillType = col.regentFillType;
}
if (col.checkType) {
item.checkType = col.checkType;
}
// 支持动态checkboxLabel - 从行数据中获取
const dynamicLabelKey = col.prop + 'Label';
if (currentItem && currentItem[dynamicLabelKey]) {
// 优先从行数据中获取动态label(如jzbh1Label)
item.checkboxLabel = currentItem[dynamicLabelKey];
} else if (col.checkboxLabel !== undefined && col.checkboxLabel !== '') {
// 否则使用列配制的checkboxLabel
item.checkboxLabel = this.$t(col.checkboxLabel);
}
if (col.bodyType === "operableInput" ) {
if(currentItem.isComplete || this.templateFillType !== 'actFill'){
item.disabled = true;
}else{
item.disabled = false;
}
}
if(col.noBorder){
item.noBorder = true;
}
if(col.bodyLayout){
item.layout = col.bodyLayout;
}
return item
},
getBodyButtonItem(col,) {
return {
buttonName: col.bodySubButtonName,
fillType: col.bodySubFillType,
type: "button",
}
},
getBodyThirdButtonItem(col, rowIndex) {
return {
buttonName: col.bodyThirdButtonName,
fillType: col.bodyThirdFillType,
type: "button",
}
},
getBodySubItem(col) {
const item = {
fillType: col.bodySubFillType,
options: col.bodySubOptions,
maxlength: col.bodySubMaxlength || 10,
label: "",
placeholder: col.bodySubPlaceholder || (col.bodySubType === 'select' ? '请选择' : '请输入'),
precision: col.subPrecision,
compareTo: col.bodySubCompareTo, // 添加 compareTo 字段
type: col.bodySubType || "input",
}
if (col.bodySubDisabled) {
item.disabled = col.bodySubDisabled;
}
return item
},
// 删除行
deleteRow(rowIndex) {
this.localDataSource.splice(rowIndex, 1);
this.$emit('row-delete', rowIndex);
},
deleteRows(rowsIndex) {
rowsIndex.sort((a, b) => b - a);
rowsIndex.forEach(index => {
this.localDataSource.splice(index, 1);
this.$emit('row-delete', index);
});
},
deleteSelectedRows(rowsIndex) {
this.deleteRows(rowsIndex);
this.selectedRows = [];
this.isIndeterminate = false;
this.$emit('selectionChange', this.selectedRows);
},
updateHeaderSelectFields(fields) {
this.headerSelectFields = { ...this.headerSelectFields, ...fields };
},
// 更新数据方法,可在formData变更时调用,也可由父组件调用
updateDataSource(dataSource = []) {
this.oldLocalDataSource = JSON.parse(JSON.stringify(this.localDataSource));
// 深拷贝数据以避免直接修改原始数据
this.localDataSource = JSON.parse(JSON.stringify(dataSource || [])).map(row => ({
...row,
_checked: false // 初始化选中状态为 false
}));
this.updateCheckStatus();
this.checkCompareToOnDataLoad();
},
// 根据行索引更新数据 autoUpdateRecord 是否自动更新记录
updateDataSourceByRowIndex(rowIndex, data,updateFieldsInfo={}) {
const {signData,updateFields = []} = updateFieldsInfo;
this.oldLocalDataSource = JSON.parse(JSON.stringify(this.localDataSource));
this.localDataSource[rowIndex] = { ...this.localDataSource[rowIndex], ...data };
this.localDataSource = [...this.localDataSource];
if(updateFields.length > 0){
updateFields.map((key)=>{
const ref = this.$refs[key+rowIndex];
if(ref){
ref[0].handleUpdateRecord(signData, { oldValue: this.oldLocalDataSource[rowIndex][key], inputValue: data[key] });
}
})
}
this.checkCompareToOnDataLoad();
// justUpdateFilledFormData();
},
pushDataSource(data=[]) {
this.localDataSource.push(...data);
this.localDataSource = [...this.localDataSource];
this.checkCompareToOnDataLoad();
justUpdateFilledFormData();
},
// 比较newData和oldData的值是否相等,只要有一对不相等就返回false
compareOldAndCurrentFormFields(newData, oldData) {
for (const key in newData) {
const oldValue = newData[key];
const currentValue = oldData[key];
if (JSON.stringify(oldValue) !== JSON.stringify(currentValue)) {
return false;
} else {
return false;
}
}
return true;
},
// 处理全选
handleCheckAllChange(val) {
this.localDataSource.forEach(row => {
row._checked = val;
});
this.updateCheckStatus();
this.$emit('selectionChange', this.selectedRows);
},
// 处理单个 checkbox 变化
handleCheckChange(row, val) {
row._checked = val;
this.updateCheckStatus();
this.$emit('selectionChange', this.selectedRows);
},
// 更新选中状态和半选状态
updateCheckStatus() {
const totalRows = this.localDataSource.length;
const checkedRows = this.localDataSource.filter(row => row._checked).length;
this.checkAll = checkedRows === totalRows && totalRows > 0;
this.isIndeterminate = checkedRows > 0 && checkedRows < totalRows;
// 记录选中的行数据和对应的行索引
this.selectedRows = this.localDataSource.map((row, rowIndex) => ({
...row,
rowIndex
})).filter(item => item._checked);
},
onAddRow() {
if (this.$listeners && this.$listeners['onAddRow']) {
this.$emit('onAddRow',{dataSource:this.localDataSource});
return;
}
this.addRow({
actSolutionVolumePrecision: 3,//小数点精度默认为3
actSolutionConcentrationPrecision: 3,//小数点精度默认为3
targetDiluentVolumePrecision: 3,//小数点精度默认为3
targetStartSolutionVolumePrecision: 3,//小数点精度默认为3
id:getuuid(),
rowIndex:this.localDataSource.length,
});
justUpdateFilledFormData()
},
// 添加行
addRow(row = {}) {
this.localDataSource.push({
...row,
_checked: false // 初始化选中状态为 false
});
this.updateCheckStatus();
},
addRows(rows = []) {
this.localDataSource.push(...rows.map(row => ({
...row,
_checked: false // 初始化选中状态为 false
})));
this.updateCheckStatus();
},
getDataSource() {
return this.localDataSource;
},
// 判断表单项是否有错误
hasError(rowIndex, colIndex, field) {
return this.formErrors.some(error =>
error.rowIndex === rowIndex &&
error.colIndex === colIndex &&
error.field === field
);
},
// 处理错误状态更新
onErrorUpdate(rowIndex, colIndex, field, isError) {
if (!isError) {
this.formErrors = this.formErrors.filter(error =>
!(error.rowIndex === rowIndex &&
error.colIndex === colIndex &&
error.field === field)
);
}
},
// onSubBlur(rowIndex, colKey, value) {
// this.$emit("blur", { rowIndex, colKey, value, item: this.localDataSource[rowIndex] });
// },
// 检查是否需要橙色背景
hasOrangeBg(rowIndex, colIndex, field) {
const key = `${rowIndex}-${colIndex}-${field}`;
return this.orangeBgCells[key] || false;
},
// 设置橙色背景状态
setOrangeBg(rowIndex, colIndex, field, status) {
const key = `${rowIndex}-${colIndex}-${field}`;
this.$set(this.orangeBgCells, key, status);
},
onBlur(rowIndex, colKey) {
const value = this.localDataSource[rowIndex][colKey];
// 查找对应的列配制
const col = this.columns.find(c => c.prop === colKey);
if (col && col.bodyFillType === "actFill" && col.compareTo) {
const compareToValue = this.localDataSource[rowIndex][col.compareTo];
// 比较当前值和compareTo值,如果不相等则设置橙色背景
if (value !== compareToValue) {
this.setOrangeBg(rowIndex, this.columns.findIndex(c => c.prop === colKey), colKey, true);
} else {
// 相等则移除橙色背景
this.setOrangeBg(rowIndex, this.columns.findIndex(c => c.prop === colKey), colKey, false);
}
}
this.$emit("blur", { rowIndex, colKey, value, dataSource: this.localDataSource, headerSelectFields: this.headerSelectFields, item: this.localDataSource[rowIndex] });
},
onSubBlur(rowIndex, colKey, value) {
// 查找对应的列配制
const col = this.columns.find(c => c.bodySubKey === colKey);
if (col && col.bodySubFillType === "actFill" && col.bodySubCompareTo) {
const compareToValue = this.localDataSource[rowIndex][col.bodySubCompareTo];
// 比较当前值和compareTo值,如果不相等则设置橙色背景
if (value !== compareToValue) {
this.setOrangeBg(rowIndex, this.columns.findIndex(c => c.bodySubKey === colKey), colKey, true);
} else {
// 相等则移除橙色背景
this.setOrangeBg(rowIndex, this.columns.findIndex(c => c.bodySubKey === colKey), colKey, false);
}
}
this.$emit("blur", { rowIndex, colKey, value, item: this.localDataSource[rowIndex] });
}
}
};
</script>
<style scoped>
.custom-table-wrapper {
border: 1px solid #ebeef5;
border-radius: 4px;
overflow: hidden;
font-size: 14px;
color: #606266;
margin-top: 20px;
&.no-border {
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
border-radius: 5px 5px;
border: none;
.custom-table-cell {
border-right: none;
}
.custometable-row {
display: flex;
border-bottom: none;
}
}
}
.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: 500px; */
/* overflow-y: auto; */
/* 可根据需要调整或由父组件控制 */
}
.header-cell-content {
display: flex;
align-items: center;
justify-content: center;
}
.header-columns-grid {
display: grid;
gap: 10px;
}
.header-column-item {
display: flex;
align-items: center;
padding: 0 5px;
}
/* 共同行样式 */
.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;
page-break-inside: avoid;
box-sizing: border-box;
}
.custom-table-cell:last-child {
border-right: none;
}
.header-cell {
color: #909399;
background-color: #f5f7fa;
font-size: 12px;
word-break: break-word;
white-space: normal;
}
.body-cell {
color: #606266;
page-break-inside: avoid;
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: auto;
max-height: 500px;
}
.custom-table-header,
.custom-table-body {
min-width: 100%;
}
.header-select {
width: 100px;
margin-left: 5px;
}
.no-data {
text-align: center;
padding: 20px 0;
color: rgb(144, 147, 153)
}
.add-row {
display: flex;
justify-content: center;
padding: 20px 0;
margin-top: 20px;
}
.flex1 {
flex: 1;
}
.flex {
display: flex;
}
.other-title {
text-align: right;
margin: 0 10px;
font-size: 14px;
font-weight: normal;
color: #606266;
width: auto;
}
.body-span {
text-align: center;
}
.item-center {
display: flex;
align-items: center;
}
.mr-5 {
margin-right: 5px;
}
.sort-cell {
text-align: center;
width: 100px;
}
.flex-wrap {
flex-wrap: wrap;
gap: 10px;
}
.row-error-border {
box-shadow: 0 0 6px #ffc3c3;
padding: 8px;
border-radius: 4px;
border: 1px solid #ff5d5d;
}
.checkbox-item {
/* width: 50px; */
display: flex;
align-items: center;
justify-content: center;
}
.c-cell {
width: 50px;
}
.span-content {
width: -webkit-fill-available;
text-align: center;
}
.add-icon {
color: #409eff;
font-size: 20px;
margin-left: 10px;
cursor: pointer;
}
.remove-icon {
color: #ff4949;
font-size: 20px;
margin-left: 5px;
cursor: pointer;
}
.full-row {
grid-column: span 2;
}
.grid-container {
display: grid;
grid-template-columns: repeat(2, 1fr);
/* 默认2列 */
gap: 5px;
/* 防止网格容器被分割到不同页面 */
page-break-inside: avoid;
break-inside: avoid;
}
</style>