- types/agent.ts:FormField 新增 visibleWhen / requiredWhenVisible / hiddenValue。
- FormCard.vue:
· 新增 isFieldVisible() —— 字段随上级字段取值增删(多个条件之间是「且」);
只判断显示、**不清值**(与模块页 form.formType 保留的语义一致)。
· renderFields 过滤不可见字段;missingCount 只算可见的必填项;必填星号含条件必填。
· onSubmit:必填校验只看可见字段;隐藏字段不进提交体,但后端标了 hiddenValue 时按固定值提交
(对齐 NewActivity.vue 的 `quota: receiptMode === "none" ? 0 : …`)。
This commit is contained in:
@@ -113,6 +113,27 @@ function extraFieldId(name: string) {
|
|||||||
return Number(name.slice(EXTRA_PREFIX.length));
|
return Number(name.slice(EXTRA_PREFIX.length));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 条件显示:本字段当前该不该出现在表单里。
|
||||||
|
*
|
||||||
|
* <p>后端可以在字段上标注 {@code visibleWhen}(如宿舍通知的「回执单据」只在
|
||||||
|
* 「回执方式 = 需填报」时才有、「宿管到场配合」只在「回执方式 ≠ 仅通知」时才有
|
||||||
|
* —— 与模块页 {@code NewNotice.vue} 的 {@code isFill / showReceiptOpts} 两个 v-if 一一对应)。
|
||||||
|
* 没标注的字段恒显示;多个条件之间是「且」。
|
||||||
|
*
|
||||||
|
* <p>★ 这里**只判断显示,不清值**:模块页切回「仅通知」时 {@code form.formType} 仍留在模型里,
|
||||||
|
* 只是不随包提交 —— 清掉反而会让用户切回来时白选一次。
|
||||||
|
*/
|
||||||
|
function isFieldVisible(f: FormField): boolean {
|
||||||
|
const spec = f.visibleWhen;
|
||||||
|
if (!spec) return true;
|
||||||
|
return Object.entries(spec).every(([dep, vals]) => {
|
||||||
|
const cur = values[dep];
|
||||||
|
const s = cur === undefined || cur === null ? "" : String(cur);
|
||||||
|
return (vals || []).map(String).includes(s);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 渲染哪几个字段。
|
* 渲染哪几个字段。
|
||||||
*
|
*
|
||||||
@@ -121,8 +142,8 @@ function extraFieldId(name: string) {
|
|||||||
* 锚点不存在时退化为追加到末尾。
|
* 锚点不存在时退化为追加到末尾。
|
||||||
*/
|
*/
|
||||||
const renderFields = computed(() => {
|
const renderFields = computed(() => {
|
||||||
const base = (props.form?.fields || []).filter(f => !f.mirrorOf && !hiddenSingle[f.name]);
|
const base = (props.form?.fields || []).filter(f => !f.mirrorOf && !hiddenSingle[f.name] && isFieldVisible(f));
|
||||||
const ext = extraFields.value.filter(f => !hiddenSingle[f.name]);
|
const ext = extraFields.value.filter(f => !hiddenSingle[f.name] && isFieldVisible(f));
|
||||||
if (!ext.length) return base;
|
if (!ext.length) return base;
|
||||||
const anchor = props.form?.extraFields?.before;
|
const anchor = props.form?.extraFields?.before;
|
||||||
const idx = anchor ? base.findIndex(f => f.name === anchor) : -1;
|
const idx = anchor ? base.findIndex(f => f.name === anchor) : -1;
|
||||||
@@ -912,8 +933,10 @@ const presetList = computed(() => {
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
/** 还差几项必填没填(页脚给出明确预期) */
|
/** 还差几项必填没填(页脚给出明确预期);条件字段不显示时不计入 */
|
||||||
const missingCount = computed(() => (props.form?.fields || []).filter(f => f.required).length);
|
const missingCount = computed(
|
||||||
|
() => (props.form?.fields || []).filter(f => isFieldVisible(f) && (f.required || f.requiredWhenVisible)).length
|
||||||
|
);
|
||||||
|
|
||||||
function labelOf(name: string): string {
|
function labelOf(name: string): string {
|
||||||
const f = (props.form?.fields || []).find(x => x.name === name);
|
const f = (props.form?.fields || []).find(x => x.name === name);
|
||||||
@@ -1001,7 +1024,10 @@ function onSubmit() {
|
|||||||
for (const f of [...(props.form?.fields || []), ...extraFields.value]) {
|
for (const f of [...(props.form?.fields || []), ...extraFields.value]) {
|
||||||
const v = values[f.name];
|
const v = values[f.name];
|
||||||
const empty = v === undefined || v === null || String(v).trim() === "";
|
const empty = v === undefined || v === null || String(v).trim() === "";
|
||||||
if (f.required && empty) {
|
// 条件必填:只有字段真的显示在界面上(visibleWhen 成立)才要求填 ——
|
||||||
|
// 隐藏的字段连值都不提交,报红等于让用户去填一个看不见的输入框。
|
||||||
|
const must = isFieldVisible(f) && (f.required || f.requiredWhenVisible);
|
||||||
|
if (must && empty) {
|
||||||
errors[f.name] = `请填写「${f.label}」`;
|
errors[f.name] = `请填写「${f.label}」`;
|
||||||
ok = false;
|
ok = false;
|
||||||
} else {
|
} else {
|
||||||
@@ -1021,6 +1047,17 @@ function onSubmit() {
|
|||||||
// 2) 用户填写的字段
|
// 2) 用户填写的字段
|
||||||
const rows: { label: string; text: string }[] = [];
|
const rows: { label: string; text: string }[] = [];
|
||||||
for (const f of props.form?.fields || []) {
|
for (const f of props.form?.fields || []) {
|
||||||
|
// 条件字段当前不显示 ⇒ 它的值不进提交体(模块页「仅通知」时就不发 formType,
|
||||||
|
// 见 NewNotice.vue 的 `form.receiptMode === "fill" ? form.formType : undefined`)
|
||||||
|
// ★ 例外:模块页有「控件隐藏但请求体照发」的写法(NewActivity.vue 的
|
||||||
|
// `quota: form.receiptMode === "none" ? 0 : ...`)—— 那种字段由后端标了
|
||||||
|
// hiddenValue,隐藏时仍按固定值提交,不能整键不发。
|
||||||
|
if (!isFieldVisible(f)) {
|
||||||
|
if (f.hiddenValue !== undefined && f.hiddenValue !== null) {
|
||||||
|
args[f.name] = f.hiddenValue;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
// 镜像字段(deptName 等):不带输入控件,取源字段「选中项的显示名」一起提交,与模块页一致
|
// 镜像字段(deptName 等):不带输入控件,取源字段「选中项的显示名」一起提交,与模块页一致
|
||||||
if (f.mirrorOf) {
|
if (f.mirrorOf) {
|
||||||
const src = values[f.mirrorOf];
|
const src = values[f.mirrorOf];
|
||||||
@@ -1212,7 +1249,7 @@ function reset() {
|
|||||||
</div>
|
</div>
|
||||||
<div class="field" :class="{ 'field--wide': isWide(f) }">
|
<div class="field" :class="{ 'field--wide': isWide(f) }">
|
||||||
<label v-if="widgetOf(f) !== 'switch'" class="field__label">
|
<label v-if="widgetOf(f) !== 'switch'" class="field__label">
|
||||||
<span v-if="f.required" class="field__star">*</span>{{ f.label }}
|
<span v-if="f.required || f.requiredWhenVisible" class="field__star">*</span>{{ f.label }}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div class="field__control">
|
<div class="field__control">
|
||||||
|
|||||||
@@ -137,6 +137,31 @@ export interface FormField {
|
|||||||
autoPickFirst?: boolean;
|
autoPickFirst?: boolean;
|
||||||
/** 选项只有一个时自动填入并隐藏本字段(模块页「班级」:单班时不显示) */
|
/** 选项只有一个时自动填入并隐藏本字段(模块页「班级」:单班时不显示) */
|
||||||
hideWhenSingleOption?: boolean;
|
hideWhenSingleOption?: boolean;
|
||||||
|
/**
|
||||||
|
* 条件显示:本字段**仅当**所列字段的当前取值命中给定值时渲染、校验、提交。
|
||||||
|
*
|
||||||
|
* <p>形如 `{receiptMode: ["confirm","fill"]}` —— 宿舍通知模块页「新建通知」里,
|
||||||
|
* 「回执方式」这个单选决定了下面还显示哪些参数(`NewNotice.vue` 的
|
||||||
|
* `isFill = receiptMode === "fill"` 控制「回执单据」、
|
||||||
|
* `showReceiptOpts = receiptMode !== "none"` 控制「宿管到场配合」)。
|
||||||
|
* 多个键之间是「且」;不标注 = 恒显示。
|
||||||
|
*/
|
||||||
|
visibleWhen?: Record<string, string[]>;
|
||||||
|
/**
|
||||||
|
* 条件必填:仅当字段因 `visibleWhen` 真的显示时才要求填写。
|
||||||
|
* 区别于 `required` —— 后者会进给模型的 schema,而条件必填只约束界面上这一次填写。
|
||||||
|
*/
|
||||||
|
requiredWhenVisible?: boolean;
|
||||||
|
/**
|
||||||
|
* 「隐藏时提交固定值」:本字段因 `visibleWhen` 不成立而**不显示**时,提交体里照样带上这个值。
|
||||||
|
*
|
||||||
|
* <p>模块页的隐藏有**两种口径**,别混:
|
||||||
|
* - 不提交(宿舍通知「回执单据」,`receiptMode === "fill" ? form.formType : undefined`)
|
||||||
|
* → 后端不给 `hiddenValue`,隐藏时整键不发;
|
||||||
|
* - 提交固定值(家校活动「名额上限 / 家长随行」,
|
||||||
|
* `quota: form.receiptMode === "none" ? 0 : ...`)→ 后端给 `hiddenValue: 0`。
|
||||||
|
*/
|
||||||
|
hiddenValue?: any;
|
||||||
/**
|
/**
|
||||||
* 快捷时段按钮组:`{ start, end, items: [{label,start,end}] }`。
|
* 快捷时段按钮组:`{ start, end, items: [{label,start,end}] }`。
|
||||||
* `start/end` 是**时刻**(HH:mm:ss),点按钮时套用到当前已选日期上。
|
* `start/end` 是**时刻**(HH:mm:ss),点按钮时套用到当前已选日期上。
|
||||||
|
|||||||
Reference in New Issue
Block a user