t13:前端换形——audience 数组多选 + 冻结/匿名窗口开关,fillable 行去 canFill/myTier
- types/form.ts:AUDIENCE 改字符串码闭集(含 ANONYMOUS)+ audienceText(); FormView/FormConfigRequest 加 audience 数组、frozen、anonCookieMinutes - types/data.ts:FillableForm 去 canFill/myTier(C2,入集即可填); DATA_SCOPE_LABELS 由四枚举改两档(ALL/SELF,§19 作废-1) - FormConfigModal:面向人群改复选组(多选共集)+ 冻结开关(文案指路"喂外部业务应用的表单请开启") + 匿名防重窗口;含 ANONYMOUS 时限次置灰并在提交前挡(与后端同判据) - Forms.vue 面向列、My.vue「可填写」卡集、fill.ts/routes.ts 注记同步换形 - AppContext.vue:填报 tab 直接吃入集行;数据/统计两档的驱动位改由 T14 的 managed 端点接, 本批不留"按 myTier 猜"的兼容分支(壳零自有权限逻辑) - mock:formapi/dataapi 的 audience 数组形态 + 匿名×限次跨字段约束
This commit is contained in:
+16
-17
@@ -206,7 +206,7 @@ export default [
|
||||
appCode: queryValue(query, "appCode") || APP,
|
||||
name: "三好学生申报",
|
||||
version: 2,
|
||||
audience: 1,
|
||||
audience: ["STUDENT"],
|
||||
fillLimit: 0,
|
||||
answerNoEnabled: 0,
|
||||
deadline: null,
|
||||
@@ -317,27 +317,26 @@ export default [
|
||||
return ok(results);
|
||||
} },
|
||||
|
||||
// ==================== 可填写入口清单(P95 W3/W4,/my/fillable) ====================
|
||||
// ==================== 可填写入口清单(契约 v2 C2,/my/fillable) ====================
|
||||
/**
|
||||
* 三行种子刻意覆盖三层模型的可观测差异(冒烟脚本 p95-fill-scope-smoke.sh 就断言这三件事):
|
||||
* ① FILL:学生面向、canFill=true → 「可填写」画卡;
|
||||
* ② MANAGE:学生面向、canFill=false(教师那种"只能代填")→ 「可填写」<b>不</b>画卡,
|
||||
* 但快应用上下文的数据管理 tab 要靠它出现;
|
||||
* ③ ADMIN:教师面向、canFill=true + 最高层 → 统计 tab 只由它决定。
|
||||
* 行形状按 C2:无 canFill/myTier,`audience` 是数组。<b>入集即等价于可填</b>
|
||||
* (后端 audience 门筛过一次,不再有"进了列表但不能自填、只给代填"的中间态)。
|
||||
* 种子刻意覆盖三种人群形态:
|
||||
* ① 纯学生面向;② 学生+教师共集(C1 多值,一张表两类人都能填);③ 纯教师面向。
|
||||
* appCode 传值时按该应用收窄(缺省=全部应用),与后端 FillableFormService 同一口径。
|
||||
*/
|
||||
{ url: "/api/form/my/fillable", method: "get", response: ({ query }: any) => {
|
||||
const rows: FillableForm[] = [
|
||||
{ code: "honor_apply", name: "三好学生申报", appCode: APP, audience: 1,
|
||||
deadline: "2026-12-31 23:59:59", fillLimit: 1, groupTag: null, canFill: true, myTier: "FILL" },
|
||||
{ code: "home_visit", name: "家访登记(教师代填)", appCode: APP, audience: 1,
|
||||
deadline: null, fillLimit: 0, groupTag: "五年级", canFill: false, myTier: "MANAGE" },
|
||||
{ code: "teacher_survey", name: "教师问卷", appCode: APP, audience: 2,
|
||||
deadline: "2026-11-30 23:59:59", fillLimit: 0, groupTag: null, canFill: true, myTier: "ADMIN" },
|
||||
{ code: "drill-honour", name: "荣誉申报(演练应用)", appCode: "fc-drill", audience: 1,
|
||||
deadline: "2026-10-31 23:59:59", fillLimit: 0, groupTag: null, canFill: true, myTier: "FILL" },
|
||||
{ code: "drill_roster", name: "花名册(演练应用,教师代填)", appCode: "fc-drill", audience: 1,
|
||||
deadline: null, fillLimit: 0, groupTag: null, canFill: false, myTier: "MANAGE" }
|
||||
{ code: "honor_apply", name: "三好学生申报", appCode: APP, audience: ["STUDENT"],
|
||||
deadline: "2026-12-31 23:59:59", fillLimit: 1, groupTag: null },
|
||||
{ code: "home_visit", name: "家访登记(师生共集)", appCode: APP, audience: ["STUDENT", "TEACHER"],
|
||||
deadline: null, fillLimit: 0, groupTag: "五年级" },
|
||||
{ code: "teacher_survey", name: "教师问卷", appCode: APP, audience: ["TEACHER"],
|
||||
deadline: "2026-11-30 23:59:59", fillLimit: 0, groupTag: null },
|
||||
{ code: "drill-honour", name: "荣誉申报(演练应用)", appCode: "fc-drill", audience: ["STUDENT"],
|
||||
deadline: "2026-10-31 23:59:59", fillLimit: 0, groupTag: null },
|
||||
{ code: "drill_roster", name: "花名册(演练应用,师生共集)", appCode: "fc-drill",
|
||||
audience: ["STUDENT", "TEACHER"], deadline: null, fillLimit: 0, groupTag: null }
|
||||
];
|
||||
// 排序沿后端口径:deadline 升序、不限截止在后、同档按 code
|
||||
const app = String(query?.appCode || "").trim();
|
||||
|
||||
@@ -95,10 +95,12 @@ const forms: MockForm[] = [
|
||||
statusName: "PUBLISHED",
|
||||
currentVersion: 1,
|
||||
draftVersion: 2,
|
||||
audience: AUDIENCE.STUDENT,
|
||||
audience: [AUDIENCE.STUDENT],
|
||||
fillLimit: 1,
|
||||
deadline: null,
|
||||
answerNoEnabled: 0,
|
||||
frozen: 0,
|
||||
anonCookieMinutes: 10,
|
||||
groupTag: null,
|
||||
statusDefs: DEFAULT_STATUS_DEFS,
|
||||
createTime: now(),
|
||||
@@ -161,10 +163,12 @@ function view(f: MockForm): FormView {
|
||||
statusName: f.statusName,
|
||||
currentVersion: f.currentVersion,
|
||||
draftVersion: f.draftVersion,
|
||||
audience: f.audience,
|
||||
audience: [...f.audience],
|
||||
fillLimit: f.fillLimit,
|
||||
deadline: f.deadline,
|
||||
answerNoEnabled: f.answerNoEnabled,
|
||||
frozen: f.frozen,
|
||||
anonCookieMinutes: f.anonCookieMinutes,
|
||||
groupTag: f.groupTag,
|
||||
statusDefs: f.statusDefs ? JSON.parse(JSON.stringify(f.statusDefs)) : null,
|
||||
createTime: f.createTime,
|
||||
@@ -332,10 +336,12 @@ export default [
|
||||
statusName: "DRAFT",
|
||||
currentVersion: null,
|
||||
draftVersion: 1,
|
||||
audience: AUDIENCE.STUDENT,
|
||||
audience: [AUDIENCE.STUDENT],
|
||||
fillLimit: 0,
|
||||
deadline: null,
|
||||
answerNoEnabled: 0,
|
||||
frozen: 0,
|
||||
anonCookieMinutes: 10,
|
||||
groupTag: null,
|
||||
statusDefs: DEFAULT_STATUS_DEFS,
|
||||
createTime: now(),
|
||||
@@ -443,13 +449,31 @@ export default [
|
||||
const f = findForm(body.uuid);
|
||||
if (!f) return bizErr(2, "表单未找到");
|
||||
if (body.audience !== undefined) {
|
||||
if (body.audience !== AUDIENCE.STUDENT && body.audience !== AUDIENCE.TEACHER) return bizErr(2, "audience 只能是 1 或 2");
|
||||
f.audience = body.audience;
|
||||
const codes = Array.isArray(body.audience) ? body.audience : [];
|
||||
if (!codes.length) return bizErr(2, "audience 不能是空数组");
|
||||
for (const c of codes) {
|
||||
if (c !== AUDIENCE.STUDENT && c !== AUDIENCE.TEACHER && c !== AUDIENCE.ANONYMOUS)
|
||||
return bizErr(2, "audience 只能是 STUDENT/TEACHER/ANONYMOUS");
|
||||
}
|
||||
f.audience = codes;
|
||||
}
|
||||
if (body.fillLimit !== undefined) {
|
||||
if (body.fillLimit !== 0 && body.fillLimit !== 1) return bizErr(2, "fillLimit 只能是 0 或 1");
|
||||
f.fillLimit = body.fillLimit;
|
||||
}
|
||||
if (body.frozen !== undefined) {
|
||||
if (body.frozen !== 0 && body.frozen !== 1) return bizErr(2, "frozen 只能是 0 或 1");
|
||||
f.frozen = body.frozen;
|
||||
}
|
||||
if (body.anonCookieMinutes !== undefined) {
|
||||
const minutes = Number(body.anonCookieMinutes);
|
||||
if (!(minutes >= 1 && minutes <= 1440)) return bizErr(2, "anonCookieMinutes 需在 1~1440 之间");
|
||||
f.anonCookieMinutes = minutes;
|
||||
}
|
||||
// 与后端同一条跨字段约束(§3.2/作废-11):匿名表单无 limit_key,限次只能关着
|
||||
if (f.audience.includes(AUDIENCE.ANONYMOUS) && f.fillLimit === 1) {
|
||||
return bizErr(5, "面向人群含匿名时不支持限次:fillLimit 必须为 0");
|
||||
}
|
||||
if (body.answerNoEnabled !== undefined) {
|
||||
if (body.answerNoEnabled !== 0 && body.answerNoEnabled !== 1) return bizErr(2, "answerNoEnabled 只能是 0 或 1");
|
||||
if (body.answerNoEnabled === 1 && f.code.length > 49) return bizErr(2, "开启报名号要求 code ≤49 位");
|
||||
|
||||
Reference in New Issue
Block a user