<template>
|
|
<div>
|
|
<div class="custom-table-wrapper" :class="{'no-border': !isBorder}">
|
|
<div class="custom-table-header" v-if="isBorder">
|
|
<div class="custom-table-row">
|
|
<div v-if="showSort" class="custom-table-cell header-cell sort-cell">
|
|
序号
|
|
</div>
|
|
<div v-for="(col, index) in columns" :key="index" class="custom-table-cell header-cell"
|
|
:style="getCellWidth(col)">
|
|
<div class="header-cell-content">
|
|
<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, index, col.headerSelectKey)"
|
|
@update:error="onErrorUpdate(-1, index, 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: '245px' }" 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">
|
|
<div v-if="showSort" class="custom-table-cell body-cell sort-cell">
|
|
{{ rowIndex + 1 }}
|
|
</div>
|
|
<div v-for="(col, colIndex) in columns" :key="colIndex" class="custom-table-cell body-cell"
|
|
: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'">
|
|
<HandleFormItem :fieldKey="prefixKey + '_' + col.prop + '_' + rowIndex"
|
|
:fieldItemLabel="fieldItemLabel" type="input"
|
|
@blur="onBlur(rowIndex, col.prop, $event)" @copy="onCopy(rowIndex, col)"
|
|
class="body-input" :item="getBodyItem(col, rowIndex)" v-model="row[col.prop]"
|
|
@change="onBodyValueChange(rowIndex, colIndex, $event)"
|
|
:error="hasError(rowIndex, colIndex, col.prop)"
|
|
@update:error="onErrorUpdate(rowIndex, colIndex, col.prop, $event)"
|
|
:orange-bg="hasOrangeBg(rowIndex, colIndex, col.prop)" />
|
|
</template>
|
|
<template v-else-if="col.bodyType === 'inputNumber'">
|
|
<HandleFormItem :fieldKey="prefixKey + '_' + col.prop + '_' + rowIndex"
|
|
:fieldItemLabel="fieldItemLabel" type="inputNumber"
|
|
@copy="onCopy(rowIndex, col)" class="body-input-number"
|
|
:item="getBodyItem(col, rowIndex)" v-model="row[col.prop]"
|
|
@blur="onBlur(rowIndex, col.prop, $event)"
|
|
@change="onBodyValueChange(rowIndex, colIndex, $event)"
|
|
:error="hasError(rowIndex, colIndex, col.prop)"
|
|
@update:error="onErrorUpdate(rowIndex, colIndex, col.prop, $event)"
|
|
:orange-bg="hasOrangeBg(rowIndex, colIndex, col.prop)" />
|
|
</template>
|
|
<template v-else-if="col.bodyType === 'select'">
|
|
<div class="flex flex1">
|
|
<HandleFormItem :fieldKey="prefixKey + '_' + col.prop + '_' + rowIndex"
|
|
:fieldItemLabel="fieldItemLabel" type="select" class="body-select"
|
|
@blur="onBlur(rowIndex, col.prop, $event)"
|
|
:item="getBodyItem(col, rowIndex)" v-model="row[col.prop]"
|
|
@change="onBodyValueChange(rowIndex, colIndex, $event,row,'select')"
|
|
:error="hasError(rowIndex, colIndex, col.prop)"
|
|
@update:error="onErrorUpdate(rowIndex, colIndex, col.prop, $event)"
|
|
:orange-bg="hasOrangeBg(rowIndex, colIndex, col.prop)" />
|
|
|
|
</div>
|
|
|
|
</template>
|
|
<div class="flex flex1" v-else-if="col.bodyType === 'clickable'">
|
|
<HandleFormItem :fieldKey="prefixKey + '_' + col.prop + '_' + rowIndex"
|
|
: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 + '_' + rowIndex"
|
|
: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)=>onRegentSubmit(data,col, rowIndex, colIndex, row)"
|
|
@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 === 'span'">
|
|
<div class="body-span">
|
|
{{ row[col.prop] }}
|
|
</div>
|
|
</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 flex1" v-if="isShowBodySub(col, row)">
|
|
<template v-if="col.bodySubType === 'inputNumber'">
|
|
<HandleFormItem :fieldKey="prefixKey + '_' + col.bodySubKey + '_' + rowIndex"
|
|
:fieldItemLabel="fieldItemLabel" type="inputNumber"
|
|
@blur="onSubBlur(rowIndex, col.bodySubKey, $event)"
|
|
@copy="onCopy(rowIndex, col)" :item="getBodySubItem(col)"
|
|
v-model="row[col.bodySubKey]"
|
|
@change="onBodySubValueChange(rowIndex, colIndex,$event,)"
|
|
: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 === 'select'">
|
|
<HandleFormItem :fieldKey="prefixKey + '_' + col.bodySubKey + '_' + rowIndex"
|
|
:fieldItemLabel="fieldItemLabel" type="select" class="body-select"
|
|
@blur="onSubBlur(rowIndex, col.bodySubKey, $event)"
|
|
:item="getBodySubItem(col, rowIndex)" v-model="row[col.bodySubKey]"
|
|
@change="onBodySubValueChange(rowIndex, colIndex, $event,row,'select')"
|
|
: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>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 默认操作栏 -->
|
|
<div class="custom-table-cell body-cell" :style="{ width: isBorder ? '245px' : '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 } from "@/utils/index.js";
|
|
import { isRegent } from "@/utils/index.js";
|
|
import moment from "moment";
|
|
import _ from "lodash";
|
|
export default {
|
|
inject: ['templateFillType', 'getZdxgjl', 'updateZdxgjl'],
|
|
name: 'CustomTable',
|
|
components: {
|
|
HandleFormItem
|
|
},
|
|
props: {
|
|
// 是否显示表头选择器
|
|
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,
|
|
},
|
|
showSort: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
},
|
|
data() {
|
|
return {
|
|
localDataSource: [],
|
|
headerSelectFields: {},
|
|
formErrors: [], // 表单错误状态管理
|
|
orangeBgCells: {}, // 存储需要橙色背景的单元格 {rowIndex-colIndex: true/false}
|
|
isShowOther,
|
|
oldLocalDataSource: [],
|
|
uuid: getuuid(),
|
|
isRegent,
|
|
}
|
|
},
|
|
watch: {
|
|
formData: {
|
|
immediate: true,
|
|
handler(newData) {
|
|
const { stepTableFormData = [], headerSelectFields = {} } = newData;
|
|
this.updateDataSource(stepTableFormData);
|
|
this.headerSelectFields = JSON.parse(JSON.stringify(headerSelectFields));
|
|
// 在数据加载后检查 compareTo 逻辑
|
|
this.checkCompareToOnDataLoad();
|
|
}
|
|
},
|
|
localDataSource: {
|
|
immediate: true,
|
|
deep: true,
|
|
handler(newVal, oldVal) {
|
|
// if(newVal.length == 0){
|
|
// return
|
|
// }
|
|
// this.localDataSource = [...newVal];
|
|
}
|
|
}
|
|
},
|
|
mounted() {
|
|
},
|
|
unmounted() {
|
|
this.oldLocalDataSource = [];
|
|
},
|
|
methods: {
|
|
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 }
|
|
},
|
|
//获取其他下拉框的配置
|
|
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) {
|
|
console.log("clickable", rowIndex, colIndex, col, 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})
|
|
},
|
|
onRegentSubmit(data, col, rowIndex, colIndex, row){
|
|
if (this.templateFillType !== 'actFill') {
|
|
return
|
|
}
|
|
this.updateDataSourceByRowIndex(rowIndex,{[col.prop]:data.selectedId})
|
|
this.$emit("onRegentSubmit", {selectInfo:data,key:col.prop, col, rowIndex, colIndex, rowData:row})
|
|
},
|
|
isShowAddRos() {
|
|
if (this.showAddRow !== undefined) {
|
|
return this.showAddRow
|
|
}
|
|
return this.templateFillType === 'preFill';
|
|
},
|
|
// 复制值
|
|
onCopy(rowIndex, col) {
|
|
|
|
if (col.copyFrom) {
|
|
if (this.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,
|
|
};
|
|
},
|
|
// 获取最新数据
|
|
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("表单内容未填完,请填写后再提交");
|
|
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 (this.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);
|
|
}
|
|
}
|
|
});
|
|
|
|
// 遍历数据行
|
|
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 (this.isValueEmpty(mainValue) && !col.bodyDisabled && col.bodyType !== 'span') {
|
|
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.bodySubType !== 'span') {
|
|
const subValue = row[col.bodySubKey];
|
|
console.log(col, subValue, "subValue")
|
|
if (this.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 (this.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
|
|
};
|
|
},
|
|
// 判断值是否为空
|
|
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(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 (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 && !this.isValueEmpty(currentValue) && !this.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 (!this.isValueEmpty(currentValue) && !this.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",
|
|
};
|
|
if (col.bodyDisabled) {
|
|
item.disabled = col.bodyDisabled;
|
|
}
|
|
return item
|
|
},
|
|
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);
|
|
},
|
|
// 更新数据方法,可在formData变更时调用,也可由父组件调用
|
|
updateDataSource(dataSource = []) {
|
|
this.oldLocalDataSource = JSON.parse(JSON.stringify(this.localDataSource));
|
|
// 深拷贝数据以避免直接修改原始数据
|
|
this.localDataSource = JSON.parse(JSON.stringify(dataSource || []));
|
|
this.checkCompareToOnDataLoad();
|
|
},
|
|
// 根据行索引更新数据 autoUpdateRecord 是否自动更新记录
|
|
updateDataSourceByRowIndex(rowIndex, data) {
|
|
this.oldLocalDataSource = JSON.parse(JSON.stringify(this.localDataSource));
|
|
this.localDataSource[rowIndex] = { ...this.localDataSource[rowIndex], ...data };
|
|
this.localDataSource = [...this.localDataSource];
|
|
this.checkCompareToOnDataLoad();
|
|
},
|
|
// 比较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;
|
|
},
|
|
onAddRow() {
|
|
this.addRow({
|
|
actSolutionVolumePrecision: 3,//小数点精度默认为3
|
|
actSolutionConcentrationPrecision: 3,//小数点精度默认为3
|
|
targetDiluentVolumePrecision: 3,//小数点精度默认为3
|
|
targetStartSolutionVolumePrecision: 3,//小数点精度默认为3
|
|
});
|
|
},
|
|
// 添加行
|
|
addRow(row = {}) {
|
|
this.localDataSource.push(row);
|
|
},
|
|
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: 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;
|
|
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-x: auto;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
</style>
|