feat(t9): P76 导出锚点判树修正 + P77 应用内详情形态 + 观察项①勾选回填 + 演练桩
build / build (push) Successful in 3m17s

P76(T8 验收遗留):快应用上下文的导出按钮此前恒按中心会话树判"导出"锚点
(Data.vue exportable 读 store.modules),锚点判定不生效(fail-closed 方向,
有授权也不画按钮)。本轮把 AppContext 拉到的**目标应用树**经 modules prop 下传,
Data.vue 的权限判据改为 permsTree(app=应用树 / center=会话树),canExport 纯函数不动。
Stats.vue 未接:本页无锚点判定也无 store.modules 引用,加 prop 是死代码(差异记 T9 报告)。

P77(T8 验收遗留):应用上下文内的"详情"不再跳回中心路由 /admin/data/detail。
形态二选一取**子路由 /app/:appCode/data/detail**(同一个 AnswerDetail.vue):
地址保留 uuid 可深链/刷新、详情页只有一份实现、back() 本就按 app 回应用数据视图,
写 parentPath 反而造假(menuRoute 的 ROUTE_PATHS 只收静态 path,应用段导航在页内轻导航)。
goDetail 按 props.context 分派;back() 改读 route.params.appCode,撤掉 query.app 双轨。

观察项①(T8 验收记档):确认跨触底页勾选**会被清空**——view-ui-plus 的 data 监听器
用 makeObjBaseData 重建 objData,_isChecked 只认行上的 _checked(点选不回写行),
整组替换后勾选视觉与 getSelection 归零而"已选 N 条"仍累计。新增 withSelectionState()
在 reload/loadMore 两条装载路径按 selected 回填 _checked,selected 为唯一事实源。

演练桩(本地件,dev:mock 生效):baseapi 增 fc-drill 快应用模块树(方案 §6.2 五行含
导入/导出两个 path 空锚点),dataapi 增 /api/form/resolve 桩(快应用数据视图唯一的
合规取数路径是 code→resolve→uuid,契约 §1)。其余外指 appCode 仍旧返回空树(P3 口径)。

门禁:npm run build ✓ / npm run type-check 0 错 ✓;
t1~t9 全量复跑:t5 13/0/1、t6 22/0/2、t7 12/0/0、t8 21/0/1、t9 12/0/1
(skip 均为 headless Edge --dump-dom 吐 0 字节的 P73 环境事实,不假通过)。
本提交含 T6~T8 三轮堆在工作区的既有改动(沿 T8 验收 §5 推送分层)。
This commit is contained in:
chenxing
2026-10-15 12:57:10 +08:00
parent fdd9aaaa3b
commit 14d45f7e3c
61 changed files with 9236 additions and 72 deletions
+116
View File
@@ -0,0 +1,116 @@
/**
* 定义域管理端接口(后端 `FormAdminController`,/api/form/admin/*,CAS 会话 + 定义管理模块行把关)。
*
* 写法沿 T5 定稿的 P49:`alovaInstance.Get<T>(url, {params})` / `Post<T>(url, body, {params})`——
* 同一个 Method 对象、同一套拦截器与信封解包(Notice 的 `new Method<T>` 在 alova 3.5 类型下不合法)。
*
* 两条参数分工照后端原样,别自己发挥:
* - `uuid` 寻址表单(库内 id 从不外露,契约 §1/V5);
* - `schId` 只是**超管的目标域**选择(含 0=平台级),校级管理员传别的值直接被 FormAdminGuard 拒;
* 本端默认不传(会话校),只有超管切平台级上下文时才显式带。
* publish/disable/enable/softDelete 的 uuid 走 query(后端是 @RequestParam,没有请求体)。
*/
import { alovaInstance } from "@/utils/alova/alova-iview";
import type {
FormConfigRequest,
FormCopyRequest,
FormCreateRequest,
FormSaveRequest,
FormView,
StatusDefsRequest,
VersionView
} from "@/types/form";
const BASE = "/api/form/admin";
/** 目标域参数:只在超管显式选择上下文时带(undefined 一律剔除,避免 query 里出现空值) */
type Scope = { schId?: number };
function paramsOf(params: Record<string, unknown>): Record<string, unknown> {
const out: Record<string, unknown> = {};
for (const [k, v] of Object.entries(params)) {
if (v === undefined || v === null || v === "") continue;
out[k] = v;
}
return out;
}
class FormApi {
/** 表单列表(头表 + 版本指针,不含 schema 正文;无服务端分页,一期按校量级够用) */
list(scope: Scope = {}, filter: { appCode?: string; status?: number } = {}) {
return alovaInstance.Get<FormView[]>(`${BASE}/list`, {
params: paramsOf({ ...scope, ...filter })
});
}
/** 单个表单头表(状态/版本指针/表单级配置/状态定义都在这里,schema 正文要另查版本行) */
get(uuid: string, scope: Scope = {}) {
return alovaInstance.Get<FormView>(`${BASE}/get`, { params: paramsOf({ ...scope, uuid }) });
}
/** 版本行清单(升版流水账:version/published/changeLog/publishTime) */
versions(uuid: string, scope: Scope = {}) {
return alovaInstance.Get<VersionView[]>(`${BASE}/versions`, { params: paramsOf({ ...scope, uuid }) });
}
/** 单个版本行的 schema 正文(编辑回显;已发布行内容逐字节冻结,V7) */
version(uuid: string, version: number, scope: Scope = {}) {
return alovaInstance.Get<VersionView>(`${BASE}/version`, {
params: paramsOf({ ...scope, uuid, version })
});
}
/** 筛选候选列:只出显式 searchable 且当前发布版本里的字段,附件两类已被后端排除(P28) */
filterFields(uuid: string, scope: Scope = {}) {
return alovaInstance.Get<string[]>(`${BASE}/filterFields`, { params: paramsOf({ ...scope, uuid }) });
}
/** 新建草稿(建草稿即生成 uuid,跨版本不变;version 从 1 起) */
create(body: FormCreateRequest, scope: Scope = {}) {
return alovaInstance.Post<FormView>(`${BASE}/create`, body, { params: paramsOf({ ...scope }) });
}
/** 保存:草稿直改;已发布表单=开草稿行,头表 status/current_version 不动(V7、P20) */
save(body: FormSaveRequest, scope: Scope = {}) {
return alovaInstance.Post<FormView>(`${BASE}/save`, body, { params: paramsOf({ ...scope }) });
}
/** 发布:schema 契约校验 + 字段身份稳定 + 字典可解析,三道都过才把草稿行钉成版本并推指针 */
publish(uuid: string, changeLog?: string, scope: Scope = {}) {
return alovaInstance.Post<FormView>(`${BASE}/publish`, {}, {
params: paramsOf({ ...scope, uuid, changeLog })
});
}
/** 停用:只断填报入口,数据可读可导(方案 §5.4) */
disable(uuid: string, scope: Scope = {}) {
return alovaInstance.Post<FormView>(`${BASE}/disable`, {}, { params: paramsOf({ ...scope, uuid }) });
}
/** 停用恢复:重新发布当前版本,不产生新版本行 */
enable(uuid: string, scope: Scope = {}) {
return alovaInstance.Post<FormView>(`${BASE}/enable`, {}, { params: paramsOf({ ...scope, uuid }) });
}
/** 软删(唯一删除形态,无物理删除接口) */
softDelete(uuid: string, scope: Scope = {}) {
return alovaInstance.Post<void>(`${BASE}/softDelete`, {}, { params: paramsOf({ ...scope, uuid }) });
}
/** 另存为副本(V41):新 code/新 uuid/version 从 1 起/schema 深拷贝,源表单零改动 */
copy(body: FormCopyRequest, scope: Scope = {}) {
return alovaInstance.Post<FormView>(`${BASE}/copy`, body, { params: paramsOf({ ...scope }) });
}
/** 表单级配置(不进版本快照,改它不升版):audience/限次/截止/报名号/分组标签 */
config(body: FormConfigRequest, scope: Scope = {}) {
return alovaInstance.Post<FormView>(`${BASE}/config`, body, { params: paramsOf({ ...scope }) });
}
/** 状态定义(V12):statusDefs 传 null=回默认「待处理/已处理」 */
statusDefs(body: StatusDefsRequest, scope: Scope = {}) {
return alovaInstance.Post<FormView>(`${BASE}/statusDefs`, body, { params: paramsOf({ ...scope }) });
}
}
export const formApi = new FormApi();