From 8ddb73449928008b487738de12625c7ac5d276ec Mon Sep 17 00:00:00 2001
From: luojie <125330818@qq.com>
Date: Wed, 7 Jan 2026 22:19:53 +0800
Subject: [PATCH] =?UTF-8?q?feat:[=E6=A8=A1=E6=9D=BF=E7=AE=A1=E7=90=86][?=
=?UTF-8?q?=E6=B2=A1=E8=BE=93=E5=85=A5=E5=86=85=E5=AE=B9=E7=9A=84=E6=97=B6?=
=?UTF-8?q?=E5=80=99=E6=A0=87=E7=BA=A2]?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/components/Template/BaseInfoFormPcakge.vue | 99 +++++++++++++++++---------
src/components/Template/CustomTable.vue | 63 ++++++++++++----
src/components/Template/DecimalInput.vue | 18 ++++-
src/components/Template/HandleFormItem.vue | 81 +++++++++++++++++++--
vue.config.js | 4 +-
5 files changed, 208 insertions(+), 57 deletions(-)
diff --git a/src/components/Template/BaseInfoFormPcakge.vue b/src/components/Template/BaseInfoFormPcakge.vue
index 57f9ef0..5544400 100644
--- a/src/components/Template/BaseInfoFormPcakge.vue
+++ b/src/components/Template/BaseInfoFormPcakge.vue
@@ -9,12 +9,12 @@
{{ sItem.label }}
+ @copy="onCopy(sItem, key)" :error="errors[key]" @update:error="errors[key] = false" />
{{ sItem.label }}
+ @copy="onCopy(sItem, key)" :error="errors[key]" @update:error="errors[key] = false" />
@@ -27,7 +27,7 @@
{{ sItem.label }}
+ @copy="onCopy(sItem, key)" @change="onSelectChange(key, $event)" :error="errors[key]" @update:error="errors[key] = false" />
@@ -36,7 +36,7 @@
其他
+ @copy="onCopy(sItem, key)" :error="errors[sItem.otherCode]" @update:error="errors[sItem.otherCode] = false" />
@@ -54,19 +54,19 @@
{{ sItem.label }}
+ @copy="onCopy(sItem, key)" :error="errors[key]" @update:error="errors[key] = false" />
+ @copy="onCopy(sItem, key)" @change="onSelectChange(key, $event)" :error="errors[key]" @update:error="errors[key] = false" />
+ @copy="onCopy(sItem, key)" :error="errors[key]" @update:error="errors[key] = false" />
+ @copy="onCopy(sItem, key)" :error="errors[key]" @update:error="errors[key] = false" />
@@ -80,24 +80,24 @@
{{ sItem.label }}
+ @copy="onCopy(sItem, key)" :error="errors[key]" @update:error="errors[key] = false" />
+ @copy="onCopy(sItem, key)" @change="onSelectChange(key, $event)" :error="errors[key]" @update:error="errors[key] = false" />
其他
+ @copy="onCopy(sItem, key)" :error="errors[sItem.otherCode]" @update:error="errors[sItem.otherCode] = false" />
+ @copy="onCopy(sItem, key)" :error="errors[key]" @update:error="errors[key] = false" />
+ @copy="onCopy(sItem, key)" @change="onSelectChange(sItem.subKey, $event)" :error="errors[sItem.subKey]" @update:error="errors[sItem.subKey] = false" />
{{ formFields[sItem.subKey] }}
@@ -108,9 +108,9 @@
+ @copy="onCopy(sItem, key)" :error="errors[key]" @update:error="errors[key] = false" />
+ @copy="onCopy(sItem, key)" @change="onSelectChange(sItem.subKey, $event)" :error="errors[sItem.subKey]" @update:error="errors[sItem.subKey] = false" />
{{ formFields[sItem.subKey] }}
@@ -152,6 +152,7 @@ export default {
return {
formFields: {},//表单绑定字段
allFieldsConfig: {},//包含config的所有字段,主要用于校验表单是否填写
+ errors: {},//存储表单错误信息,用于标红提示
};
},
watch: {
@@ -187,13 +188,25 @@ export default {
},
onInputNumberChange(key, val){
this.formFields[key] = val;
+ // 清除该表单项的错误状态
+ if (this.errors[key]) {
+ this.$set(this.errors, key, false);
+ }
},
updateFormData(key, value){
this.formFields[key] = value;
+ // 清除该表单项的错误状态
+ if (this.errors[key]) {
+ this.$set(this.errors, key, false);
+ }
},
batchUpdateFormData(data){
Object.keys(data).forEach(key => {
this.formFields[key] = data[key];
+ // 清除该表单项的错误状态
+ if (this.errors[key]) {
+ this.$set(this.errors, key, false);
+ }
})
},
handleClickable(sItem,event){
@@ -272,12 +285,12 @@ export default {
if (currentConfig.otherCode) {
const { otherCode } = currentConfig;
result[otherCode] = formData[otherCode] || '';
- config[otherCode] = { label: "其他", type: "input" }
+ config[otherCode] = { label: "其他", parentKey:key, type: "input",fillType:currentConfig.fillType,otherCode }
}
if (currentConfig.subKey) {
const { subKey } = currentConfig;
result[subKey] = formData[subKey] || '';
- config[subKey] = { label: currentConfig.label, type: currentConfig.subType }
+ config[subKey] = { label: currentConfig.label,subKey, type: currentConfig.subType,fillType:currentConfig.subFillType || currentConfig.fillType }
}
});
@@ -287,11 +300,9 @@ export default {
}
}
});
- console.log(result,"initResult")
// 更新表单字段
this.formFields = result;
this.allFieldsConfig = config;
- console.log(config,"config")
},
//判断是否禁用
getDisabled() {
@@ -313,28 +324,46 @@ export default {
getFormData() {
const { formFields, allFieldsConfig } = this;
const { templateStatus } = this.$store.state.template;
- return new Promise((resolve,reject)=>{
- for (const key in formFields) {
- console.log(key,formFields[key])
- if (!formFields[key]) {
- const o = allFieldsConfig[key];
- if (o.fillType == templateStatus && !o.disabled) {
+
+ // 重置错误状态
+ this.errors = {};
+ const errors = {};
+ let firstError = null;
+ for (const key in allFieldsConfig) {
+ if (!formFields[key]) {
+ const o = allFieldsConfig[key];
+ if (o.label === "其他" && !this.isShowOther(formFields[o.parentKey])) {
+ continue
+ }
+ if (o.fillType == templateStatus && !o.disabled) {
+ // 标记为错误状态
+ errors[key] = true;
+
+ if (!firstError) {
let prefix = "";
if (o.type === "input" || o.type === "inputNumber" || o.type === "textarea") {
- prefix = "填写"
+ prefix = "填写";
} else {
- prefix = "选择"
+ prefix = "选择";
}
- this.$message.error(`${o.label}还未${prefix}请${prefix}后再提交`);
- reject(`${o.label}还未${prefix}`);
- return;
+ firstError = { label: o.label, prefix };
}
-
}
}
- resolve(formFields)
- })
+ }
+ // 设置错误状态
+ this.errors = errors;
+
+ return new Promise((resolve, reject) => {
+ if (Object.keys(errors).length > 0) {
+ // 显示第一个错误的提示信息
+ this.$message.error(`表单内容未填完,请填写后再提交`);
+ reject(`${firstError.label}还未${firstError.prefix}`);
+ } else {
+ resolve(formFields);
+ }
+ });
},
getFormDataByKey(key){
return this.formFields[key];
@@ -355,6 +384,10 @@ export default {
this.formFields[key] = val;
// }
this.$emit("select", { key, value: val });
+ // 清除该表单项的错误状态
+ if (this.errors[key]) {
+ this.$set(this.errors, key, false);
+ }
},
//复制
onCopy(config, key) {
diff --git a/src/components/Template/CustomTable.vue b/src/components/Template/CustomTable.vue
index 8f51c80..cdd0b86 100644
--- a/src/components/Template/CustomTable.vue
+++ b/src/components/Template/CustomTable.vue
@@ -31,14 +31,14 @@
-
+
+ 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)" />
-
+
{{ row[col.prop] }}
@@ -47,7 +47,7 @@
+ v-model="row[col.bodySubKey]" @change="onBodySubValueChange(rowIndex, colIndex, $event)" :error="hasError(rowIndex, colIndex, col.bodySubKey)" @update:error="onErrorUpdate(rowIndex, colIndex, col.bodySubKey, $event)" />
{{ row[col.bodySubKey] }}
@@ -110,7 +110,8 @@ export default {
data() {
return {
localDataSource: [],
- headerSelectFields: {}
+ headerSelectFields: {},
+ formErrors: [] // 表单错误状态管理
}
},
watch: {
@@ -162,7 +163,7 @@ export default {
headerSelectFields: this.headerSelectFields,
})
}else{
- this.$message.error(validateResult.errors[0].error);
+ this.$message.error("表单内容未填完,请填写后再提交");
reject(validateResult.errors[0].error)
}
})
@@ -173,37 +174,43 @@ export default {
const templateStatus = this.$store.state.template.templateStatus;
const errors = [];
+ // 清空之前的错误状态
+ this.formErrors = [];
+
// 遍历数据行
this.localDataSource.forEach((row, rowIndex) => {
// 遍历列
this.columns.forEach((col, colIndex) => {
// 只校验 fillType 与当前模板状态匹配的字段
- if (col.bodyFillType === templateStatus || col.bodySubType === templateStatus) {
+ if (col.bodyFillType === templateStatus || col.bodySubFillType === templateStatus) {
// 检查主字段
const mainValue = row[col.prop];
if (this.isValueEmpty(mainValue) && !col.bodyDisabled) {
- console.log(col,"col")
-
- errors.push({
+ const errorItem = {
rowIndex,
colIndex,
field: col.prop,
label: col.label,
error: `请填写${col.label}`
- });
+ };
+ errors.push(errorItem);
+ this.formErrors.push(errorItem);
}
// 检查子字段(如果有)
if (col.bodySubKey&& !col.bodySubDisabled) {
const subValue = row[col.bodySubKey];
+ console.log(col,subValue,"subValue")
if (this.isValueEmpty(subValue)) {
- errors.push({
+ const errorItem = {
rowIndex,
colIndex,
field: col.bodySubKey,
label: `${col.label}单位`,
error: `请填写${col.label}单位`
- });
+ };
+ errors.push(errorItem);
+ this.formErrors.push(errorItem);
}
}
}
@@ -236,12 +243,24 @@ export default {
onBodyValueChange(rowIndex, colIndex, value) {
const col = this.columns[colIndex];
this.localDataSource[rowIndex][col.prop] = value;
+ // 输入时清除对应表单项的错误状态
+ this.formErrors = this.formErrors.filter(error =>
+ !(error.rowIndex === rowIndex &&
+ error.colIndex === colIndex &&
+ error.field === col.prop)
+ );
this.$emit('body-value-change', rowIndex, colIndex, value);
},
// 表体子值变化
onBodySubValueChange(rowIndex, colIndex, value) {
const col = this.columns[colIndex];
this.localDataSource[rowIndex][col.bodySubKey] = value;
+ // 输入时清除对应表单项的错误状态
+ this.formErrors = this.formErrors.filter(error =>
+ !(error.rowIndex === rowIndex &&
+ error.colIndex === colIndex &&
+ error.field === col.bodySubKey)
+ );
this.$emit('body-sub-value-change', rowIndex, colIndex, value);
},
getHeaderItem(col) {
@@ -303,6 +322,24 @@ export default {
console.log(this.localDataSource,"this.localDataSource")
this.localDataSource = [...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]});
},
diff --git a/src/components/Template/DecimalInput.vue b/src/components/Template/DecimalInput.vue
index f989be1..20ed979 100644
--- a/src/components/Template/DecimalInput.vue
+++ b/src/components/Template/DecimalInput.vue
@@ -18,7 +18,7 @@ export default {
},
decimalDigits: {
type: Number,
- default: 3
+ default: 6
},
placeholder: {
type: String,
@@ -136,8 +136,20 @@ export default {
return;
}
- // 在 blur 时才做 toFixed(四舍五入 + 补零)
- const formatted = num.toFixed(this.decimalDigits);
+ // 只保留用户输入的有效小数位数,不强制补零
+ let formatted = String(num);
+
+ // 如果用户输入的是整数,直接显示整数
+ if (!finalValue.includes('.')) {
+ formatted = String(Math.floor(num));
+ } else {
+ // 保留用户输入的小数位数,但不超过设定的最大值
+ const decPart = finalValue.split('.')[1];
+ const actualDecimalDigits = decPart.length;
+ const displayDecimalDigits = Math.min(actualDecimalDigits, this.decimalDigits);
+ formatted = num.toFixed(displayDecimalDigits).replace(/\.?0*$/, '');
+ }
+
this.internalValue = formatted;
// emit 数字类型(也可 emit 字符串,根据需求)
this.$emit('input', parseFloat(formatted));
diff --git a/src/components/Template/HandleFormItem.vue b/src/components/Template/HandleFormItem.vue
index ea61579..8a5eb06 100644
--- a/src/components/Template/HandleFormItem.vue
+++ b/src/components/Template/HandleFormItem.vue
@@ -3,32 +3,32 @@
+ :placeholder="getPlaceholder()" @change="onInputChange">
+ :placeholder="getPlaceholder()" @change="onInputChange">
{{ value }}
- 请选择
+ {{getPlaceholder()}}
@@ -71,6 +71,11 @@ export default {
type: [String, Number, Array],
default: ''
},
+ // 错误状态
+ error: {
+ type: Boolean,
+ default: false
+ },
},
data() {
return {
@@ -94,6 +99,10 @@ export default {
green: "green-border",
preFill: "blue-border",//预填写的边框颜色
}
+ // 如果有错误状态,返回红色边框样式,覆盖原有的边框颜色
+ if (this.error) {
+ return "error-border";
+ }
return typeObj[fillType] || ""
},
//获取question图标颜色
@@ -106,6 +115,10 @@ export default {
const value = val !== undefined ? val : this.inputValue;
this.$emit('input', value);
this.$emit('change', value);
+ // 输入时清除错误状态
+ if (this.error) {
+ this.$emit('update:error', false);
+ }
},
handleClickable(item,event){
if(item.fillType !== 'actFill'){
@@ -143,10 +156,30 @@ export default {
}
}
},
+ getPlaceholder() {
+ const { placeholder,label } = this.item;
+ const {type} = this;
+ if(this.getDisabled()){
+ return ""
+ }
+ if(type === "clickable"){
+ return "请选择"
+ }
+ let prex = "请输入";
+ if(type === "select" || type === "dateTime"){
+ prex = "请选择"
+ }
+ return placeholder ? placeholder : (prex + label)
+
+ },
onCopy() {
this.$emit("copy")
},
onBlur(val) {
+ // 输入框失去焦点时清除错误状态
+ if (this.error) {
+ this.$emit('update:error', false);
+ }
this.$emit("blur", val)
},
},
@@ -258,6 +291,42 @@ export default {
}
}
+
+.error-border {
+
+ .el-input-group__prepend,input,
+ textarea,
+ .el-select,
+ .el-date-editor {
+ border-color: #f56c6c;
+
+ &:focus {
+ border-color: #f56c6c;
+ }
+
+ &:hover {
+ border-color: #f56c6c;
+ }
+ }
+
+ // 为 el-select 和 el-date-picker 添加错误边框样式
+ .el-select .el-input__inner,
+ .el-date-editor .el-input__inner {
+ border-color: #f56c6c;
+ }
+
+ // 处理 DecimalInput 组件的错误边框样式
+ :deep(.el-input-number) {
+ .el-input__inner {
+ border-color: #f56c6c;
+ }
+ }
+
+ // 为点击式表单项添加错误边框样式
+ .clickable {
+ border-color: #f56c6c;
+ }
+}
.clickable{
cursor: pointer;
width: auto;
diff --git a/vue.config.js b/vue.config.js
index e176b9f..d474f56 100644
--- a/vue.config.js
+++ b/vue.config.js
@@ -34,8 +34,8 @@ module.exports = {
proxy: {
// detail: https://cli.vuejs.org/config/#devserver-proxy
[process.env.VUE_APP_BASE_API]: {
- target: `http://localhost:8080`,
- // target: `http://39.99.251.173:8080`,
+ // target: `http://localhost:8080`,
+ target: `http://39.99.251.173:8080`,
changeOrigin: true,
pathRewrite: {
['^' + process.env.VUE_APP_BASE_API]: ''