Browse Source

chore: classify legacy structure and vue administration

彭峰 1 tháng trước cách đây
mục cha
commit
0cd6dd21ce

+ 3 - 1
docs/migration/admin-migration-status.md

@@ -46,6 +46,8 @@
 - 旧 `Design/Resource` 的历史资源已从线上 `DesignRes` 恢复到 Angular“可视化设计资源”页:`designResources` 提供帐套隔离、分页、搜索和 `ztype` 精确筛选,`saveDesignResource` 只允许更新名称、类型、用途、状态及 `style/use/fun` 元数据,`designResourceBatch` 覆盖单条和批量删除。`id/vpath/previewimg/cdate/userid/sourceKey` 均锁为只读;线上 Parse 文件适配器仍错误要求 MongoDB 连接串,而当前项目使用 PostgreSQL,因此新增及替换原文件/预览图继续以 `migration_blocked` 明确拒绝,不会伪造可用资源路径。
 - 旧 `Design/Resource` 的历史资源已从线上 `DesignRes` 恢复到 Angular“可视化设计资源”页:`designResources` 提供帐套隔离、分页、搜索和 `ztype` 精确筛选,`saveDesignResource` 只允许更新名称、类型、用途、状态及 `style/use/fun` 元数据,`designResourceBatch` 覆盖单条和批量删除。`id/vpath/previewimg/cdate/userid/sourceKey` 均锁为只读;线上 Parse 文件适配器仍错误要求 MongoDB 连接串,而当前项目使用 PostgreSQL,因此新增及替换原文件/预览图继续以 `migration_blocked` 明确拒绝,不会伪造可用资源路径。
 - 旧 `User/Role` 的 9 个管理员角色、3 个用户角色与 `ARoleAuth` 权限记录已迁到 Angular“系统角色”页及 `roles/saveRole/roleAuthorization/saveRoleAuthorization/roleBatch`:支持角色类型切换、搜索、分页、名称防重、原子 `roleId`、12 个旧权限域完整编辑及权限码格式校验。角色类型创建后不得在 `admin/user` 之间迁移;删除前同时检查 `Manager.adminRole` 和 `_User.legacyUserData.UserRole`,无账号引用时才级联删除 `ARoleAuth`。Parse 原生 `_Role` 为空,`Permission` 是另一套网页设计师模型,二者都没有被误当作旧 `ZL_Role`。交付程序中 `B_ARoleAuth.Check/CheckEx/AuthCheckEx` 均固定返回 `true`,因此保留的旧权限码不用来绕过当前云函数的独立管理员会话与帐套校验。
 - 旧 `User/Role` 的 9 个管理员角色、3 个用户角色与 `ARoleAuth` 权限记录已迁到 Angular“系统角色”页及 `roles/saveRole/roleAuthorization/saveRoleAuthorization/roleBatch`:支持角色类型切换、搜索、分页、名称防重、原子 `roleId`、12 个旧权限域完整编辑及权限码格式校验。角色类型创建后不得在 `admin/user` 之间迁移;删除前同时检查 `Manager.adminRole` 和 `_User.legacyUserData.UserRole`,无账号引用时才级联删除 `ARoleAuth`。Parse 原生 `_Role` 为空,`Permission` 是另一套网页设计师模型,二者都没有被误当作旧 `ZL_Role`。交付程序中 `B_ARoleAuth.Check/CheckEx/AuthCheckEx` 均固定返回 `true`,因此保留的旧权限码不用来绕过当前云函数的独立管理员会话与帐套校验。
 - `Extend/Safe` 的 15 个入口已完成数据与运行时审计:线上全部 Schema 仅命中不相关的 `SafeMobile`,没有旧 `Safe_IP`、非法词库、扫描结果或站点保护配置。旧聚合 `API` 还依赖服务器站点文件、五秒后返回的 Razor 局部页、`ContentWordHelper` 常驻线程与进度缓存;`SiteProtect_Submit` 只更新旧 `SiteConfig.Safe_ContentCheck_Models`,`ContentCheckConfig_Submit` 直接写 `IllegalWordsVerify.keywordsVPath` 服务器文件。当前内容写入链不消费这些配置,单纯存入 Parse Config 会伪造安全能力;因此 IP 三项按数据缺失、其余按基础设施缺失由 `legacySafeBlocker` 返回逐项 `migration_blocked`。
 - `Extend/Safe` 的 15 个入口已完成数据与运行时审计:线上全部 Schema 仅命中不相关的 `SafeMobile`,没有旧 `Safe_IP`、非法词库、扫描结果或站点保护配置。旧聚合 `API` 还依赖服务器站点文件、五秒后返回的 Razor 局部页、`ContentWordHelper` 常驻线程与进度缓存;`SiteProtect_Submit` 只更新旧 `SiteConfig.Safe_ContentCheck_Models`,`ContentCheckConfig_Submit` 直接写 `IllegalWordsVerify.keywordsVPath` 服务器文件。当前内容写入链不消费这些配置,单纯存入 Parse Config 会伪造安全能力;因此 IP 三项按数据缺失、其余按基础设施缺失由 `legacySafeBlocker` 返回逐项 `migration_blocked`。
+- 旧组织架构 14 个动作依赖 `ZL_Structure` 的父子树、排序、权限、提醒、成员/管理员 ID 集合、结构经验,以及 `ZL_User.StructureID` 双向关系。线上 110 个 Schema 不存在 `Structure`/`ZL_Structure`,`Profile` 与 `Group` 均不是这套结构数据,不能用现有部门标签伪造历史组织关系;全部动作由 `legacyStructureBlocker` 返回逐项数据阻塞。
+- 旧 Vue 模板后台依赖未迁入的 `ZL_Vue_Tlp`,预览与发布还要读取 `SiteOption.TemplateDir` 下的服务器模板、运行 `B_CreateHtml` 标签渲染、套用 `/Config/CodeTlp/vue.tlp` 并复制到 `SiteOption.VUE_DirCopy`。目标既无模板数据,也无这些文件与发布运行时;5 个动作由 `legacyVueBlocker` 分别返回数据或基础设施阻塞。旧工作量页的 `GroupDayCount` 必须将 `ZL_CommonModel` 与缺失的 `ZL_Comment` 按 `GeneralID` 内连接后逐日统计内容、评论和点击;目标只有 `CommonModel`,不能把内容数伪装成旧工作量报表,`Default` 由 `legacyWorkloadBlocker` 明确数据阻塞。
 - 旧 `Design/Scene` 的普通场景管理已迁到 Angular“可视化场景”页:`designScenes` 复刻 `ZType=0、TlpID=0` 及正常/回收站状态范围,提供帐套隔离、搜索和分页;`saveDesignScene` 只更新旧编辑页允许的标题、用户、积分、状态、搜索标记、预览图、缩略图、说明和 Meta,并从同帐套 `_User` 同步用户名。`designSceneBatch` 覆盖回收、恢复与彻底删除;`createDesignSceneTemplate` 完整复制布局/组件/资源数据,分别用 PostgreSQL advisory lock 原子分配 `DesignTlp.id` 和模板副本 `DesignScence.id`,任一步失败都会补偿清理。普通场景的新建仍依赖已丢失的可视化设计器初始化协议,因此专用保存接口只接受现有场景。
 - 旧 `Design/Scene` 的普通场景管理已迁到 Angular“可视化场景”页:`designScenes` 复刻 `ZType=0、TlpID=0` 及正常/回收站状态范围,提供帐套隔离、搜索和分页;`saveDesignScene` 只更新旧编辑页允许的标题、用户、积分、状态、搜索标记、预览图、缩略图、说明和 Meta,并从同帐套 `_User` 同步用户名。`designSceneBatch` 覆盖回收、恢复与彻底删除;`createDesignSceneTemplate` 完整复制布局/组件/资源数据,分别用 PostgreSQL advisory lock 原子分配 `DesignTlp.id` 和模板副本 `DesignScence.id`,任一步失败都会补偿清理。普通场景的新建仍依赖已丢失的可视化设计器初始化协议,因此专用保存接口只接受现有场景。
 - 旧 `Design/Template` 的模板列表、回收站、表单和生命周期已迁到 Angular“可视化模板”页:`designTemplates` 支持帐套、状态、原始 `classId` 和关键词筛选;`saveDesignTemplate` 白名单维护名称、分类编号、价格、预览图、积分、状态、来源和说明,新建时原子分配模板及空模板场景编号,失败会补偿清理;`designTemplateBatch` 覆盖回收、恢复及模板与关联场景的完整删除。目标没有 `DesignTlpClass`,因此页面保留历史 `classId` 数字而不伪造分类名称。`designTemplateEditorTarget` 可解析真实场景 GUID 与旧 `/design/default?id=...` 地址,但旧可视化编辑器的前端资产、布局交互和渲染运行时未交付,所以 `GoDesign` 保持明确的部分迁移状态。
 - 旧 `Design/Template` 的模板列表、回收站、表单和生命周期已迁到 Angular“可视化模板”页:`designTemplates` 支持帐套、状态、原始 `classId` 和关键词筛选;`saveDesignTemplate` 白名单维护名称、分类编号、价格、预览图、积分、状态、来源和说明,新建时原子分配模板及空模板场景编号,失败会补偿清理;`designTemplateBatch` 覆盖回收、恢复及模板与关联场景的完整删除。目标没有 `DesignTlpClass`,因此页面保留历史 `classId` 数字而不伪造分类名称。`designTemplateEditorTarget` 可解析真实场景 GUID 与旧 `/design/default?id=...` 地址,但旧可视化编辑器的前端资产、布局交互和渲染运行时未交付,所以 `GoDesign` 保持明确的部分迁移状态。
 - 旧 `NodeAdd_Submit`、`Node_API` 与 `UnionNode_Move` 的核心栏目生命周期已迁为专用 `saveNode/nodeBatch`:新增栏目以临时来源键完成 Parse 初始写入,再使用 PostgreSQL 事务锁原子分配 `nodeId/orderId` 并替换为旧 `sourceKey`;保存会校验同级名称/目录重复、父栏目帐套和回收状态,移动会阻断自身/下级循环并递归修正所有后代深度。回收、恢复和每批 1–100 个栏目迁移已接入 Angular;永久删除仅在没有内容、下级栏目、节点权限和模型模板引用时开放,通用保存与删除不能绕过。
 - 旧 `NodeAdd_Submit`、`Node_API` 与 `UnionNode_Move` 的核心栏目生命周期已迁为专用 `saveNode/nodeBatch`:新增栏目以临时来源键完成 Parse 初始写入,再使用 PostgreSQL 事务锁原子分配 `nodeId/orderId` 并替换为旧 `sourceKey`;保存会校验同级名称/目录重复、父栏目帐套和回收状态,移动会阻断自身/下级循环并递归修正所有后代深度。回收、恢复和每批 1–100 个栏目迁移已接入 Angular;永久删除仅在没有内容、下级栏目、节点权限和模型模板引用时开放,通用保存与删除不能绕过。
@@ -83,7 +85,7 @@
 - 旧 `Config` 中除已迁移的节假日与另行审计的 API/快捷键/奖励页外,本批 18 个站点配置动作均依赖 ASP.NET `SiteConfig/OrderConfig/SMSConfig/WaterModuleConfig`、XML/单例文件、`web.config` 与宿主重启。目标没有签约的统一配置模型,也没有能同时消费站点、上传、SMTP、注册奖励、订单文案、短信、水印缩略图、MIME/错误页与重新安装设置的云运行时。不把值单纯塞入 Parse Config 伪装成“已生效”;`legacySiteConfigBlocker` 对每个页面/提交动作返回具体基础设施原因。
 - 旧 `Config` 中除已迁移的节假日与另行审计的 API/快捷键/奖励页外,本批 18 个站点配置动作均依赖 ASP.NET `SiteConfig/OrderConfig/SMSConfig/WaterModuleConfig`、XML/单例文件、`web.config` 与宿主重启。目标没有签约的统一配置模型,也没有能同时消费站点、上传、SMTP、注册奖励、订单文案、短信、水印缩略图、MIME/错误页与重新安装设置的云运行时。不把值单纯塞入 Parse Config 伪装成“已生效”;`legacySiteConfigBlocker` 对每个页面/提交动作返回具体基础设施原因。
 - 旧百科后台的主词条审查已映射到 `Baike`:`baikeEntries` 提供帐套、关键词、审核/未审核和推荐/未推荐筛选,`baikeBatch` 支持审核、取消审核、推荐和取消推荐,Angular 新增百科审核入口和只读详情。线上仅保留 1 条 `Baike` 主记录,完全没有旧 `BaikeEdit` 版本表,因此 `BKVersionList/Version_API/BKList` 及 `BK_API` 的版本删除/应用部分明确数据阻塞。`GuestConfig.BKOption` 百科创建/编辑/推荐权限与积分奖励消费者也缺失,`Config/Config_Submit` 按基础设施阻塞登记;推荐操作不伪造已发放奖励。
 - 旧百科后台的主词条审查已映射到 `Baike`:`baikeEntries` 提供帐套、关键词、审核/未审核和推荐/未推荐筛选,`baikeBatch` 支持审核、取消审核、推荐和取消推荐,Angular 新增百科审核入口和只读详情。线上仅保留 1 条 `Baike` 主记录,完全没有旧 `BaikeEdit` 版本表,因此 `BKVersionList/Version_API/BKList` 及 `BK_API` 的版本删除/应用部分明确数据阻塞。`GuestConfig.BKOption` 百科创建/编辑/推荐权限与积分奖励消费者也缺失,`Config/Config_Submit` 按基础设施阻塞登记;推荐操作不伪造已发放奖励。
 - `_User`、`CommonModel`、`Node` 与 `Special` 表格已补齐当前页全选和批量工具栏,可直接批量停用/解锁/移动用户组,审核、回收、恢复、移动内容节点,回收、恢复、移动栏目,以及移动专题;翻页或重新查询会清空选择,避免误操作隐藏页记录。
 - `_User`、`CommonModel`、`Node` 与 `Special` 表格已补齐当前页全选和批量工具栏,可直接批量停用/解锁/移动用户组,审核、回收、恢复、移动内容节点,回收、恢复、移动栏目,以及移动专题;翻页或重新查询会清空选择,避免误操作隐藏页记录。
-- 迁移状态页已接入从反编译基线自动生成的 1,042 项旧后台动作台账,可按模块、action、原因与状态搜索筛选;当前保守登记为 97 项已实现、65 项部分实现、176 项数据阻塞、237 项基础设施阻塞、467 项待逐项核验。生成器和 `admin-coverage:check` 会阻止台账与源码基线漂移。
+- 迁移状态页已接入从反编译基线自动生成的 1,042 项旧后台动作台账,可按模块、action、原因与状态搜索筛选;当前保守登记为 157 项已实现、104 项部分实现、379 项数据阻塞、325 项基础设施阻塞、77 项待逐项核验。生成器和 `admin-coverage:check` 会阻止台账与源码基线漂移。
 - Parse 登录只在浏览器保存当前 `sessionToken`;`masterKey` 只在部署进程中使用。
 - Parse 登录只在浏览器保存当前 `sessionToken`;`masterKey` 只在部署进程中使用。
 - 云函数网关强制校验管理员身份、`company` 帐套、类白名单、字段白名单和敏感字段过滤。
 - 云函数网关强制校验管理员身份、`company` 帐套、类白名单、字段白名单和敏感字段过滤。
 - `_Session` 不开放,`Function` 源码不开放通用查询或编辑;用户密码只允许专用重置操作。
 - `_Session` 不开放,`Function` 源码不开放通用查询或编辑;用户密码只允许专用重置操作。

+ 32 - 0
scripts/deploy-admin-functions.mjs

@@ -1637,6 +1637,38 @@ async function handler(request, response) {
         SiteCheck: '旧站点检查依赖服务器站点文件、五秒后局部视图与丢失的检查规则,云函数无法还原'
         SiteCheck: '旧站点检查依赖服务器站点文件、五秒后局部视图与丢失的检查规则,云函数无法还原'
       }; if (!Object.prototype.hasOwnProperty.call(blockers, action)) fail(400, '不支持的旧站点安全动作'); fail(501, 'migration_blocked: ' + blockers[action]);
       }; if (!Object.prototype.hasOwnProperty.call(blockers, action)) fail(400, '不支持的旧站点安全动作'); fail(501, 'migration_blocked: ' + blockers[action]);
     }
     }
+    if (operation === 'legacyStructureBlocker') {
+      const action = String(input.action || ''); const blockers = {
+        Default: '旧入口只重定向组织结构树;目标 Schema 缺少 ZL_Structure/Structure 主表,无法提供等价落点',
+        StructList: '目标 Schema 缺少 ZL_Structure 的 Name、ParentID、OrderID、UserIDS、ManagerIDS、Auth、Remind 与 Struct_Exp 历史数据',
+        order: '目标没有可按父节点读取并连续重排 OrderID 的组织结构记录',
+        order_submit: '目标没有可按旧结构 ID 更新 OrderID 的组织结构记录',
+        Struct_API: '旧删除、成员增删和管理员设置依赖 ZL_Structure.UserIDS/ManagerIDS 及 ZL_User.StructureID 双向关系;目标两侧均未迁入',
+        StructByTree: '目标没有 ParentID 层级、组织成员与管理员关系,不能还原组织结构树',
+        Struct_ListByTree: '目标没有可递归展开并按 OrderID 排序的 ZL_Structure 记录',
+        StructAdd: '目标没有可编辑或新增的组织名称、上级、提醒、权限、成员和管理员记录',
+        StructAdd_Submit: '目标缺少 ZL_Structure 持久层及与用户 StructureID 同步的事务链',
+        StructMember: '目标缺少 UserIDS/ManagerIDS 组织成员关系,Profile 与 Group 均不是旧组织结构成员表',
+        Structure_update: '目标没有可供树形拖动和批量重排的组织结构记录',
+        Structure_Merge: '目标缺少可原子合并 UserIDS、ManagerIDS、Struct_Exp 并可选删除来源结构的数据与用户关系',
+        Structure_Move: '目标没有可校验循环并更新 ParentID 的组织结构树',
+        Structure_List: '目标 Schema 缺少旧 ZL_Structure 平铺结构列表及其历史记录'
+      }; if (!Object.prototype.hasOwnProperty.call(blockers, action)) fail(400, '不支持的旧组织架构动作'); fail(501, 'migration_blocked: ' + blockers[action]);
+    }
+    if (operation === 'legacyVueBlocker') {
+      const action = String(input.action || ''); const blockers = {
+        Index: '目标 Schema 缺少旧 ZL_Vue_Tlp 模板代码表与历史模板记录',
+        TlpView: '目标既缺少 ZL_Vue_Tlp.tlp 模板路径,也没有旧 SiteOption.TemplateDir 文件、B_CreateHtml 标签渲染器和 Razor 预览运行时',
+        VUE_API: '旧 list/save/del 依赖缺失的 ZL_Vue_Tlp;release/cli 还需服务器模板文件、标签渲染器、vue.tlp 与目录复制权限,云函数无法等价执行',
+        Config: '目标没有 SiteOption.VUE_DirCopy 配置模型及使用该目录的服务器发布运行时',
+        Config_Submit: '仅保存目录字符串不会产生 Vue 发布能力;云函数不能更新旧 SiteConfig 或复制服务器目录'
+      }; if (!Object.prototype.hasOwnProperty.call(blockers, action)) fail(400, '不支持的旧 Vue 模板动作'); fail(501, 'migration_blocked: ' + blockers[action]);
+    }
+    if (operation === 'legacyWorkloadBlocker') {
+      const action = String(input.action || ''); const blockers = {
+        Default: '旧 GroupDayCount 对 ZL_CommonModel 与 ZL_Comment 按 GeneralID 做内连接,再按年月日、栏目、模型和录入人聚合内容数、评论数与点击数;目标仅有 CommonModel,完全缺少 Comment 主表与历史评论关系,不能用内容数量伪造原报表'
+      }; if (!Object.prototype.hasOwnProperty.call(blockers, action)) fail(400, '不支持的旧工作量统计动作'); fail(501, 'migration_blocked: ' + blockers[action]);
+    }
     if (operation === 'legacyLogBlocker') {
     if (operation === 'legacyLogBlocker') {
       const action = String(input.action || ''); const blockers = {
       const action = String(input.action || ''); const blockers = {
         TxtLog: '云函数运行时没有旧 /Log/{LogType}/ 本地日志目录', TxtLogContent: '云函数不能按文件名读取旧服务器文本日志', TxtLog_Down: '云函数没有旧服务器日志文件可下载',
         TxtLog: '云函数运行时没有旧 /Log/{LogType}/ 本地日志目录', TxtLogContent: '云函数不能按文件名读取旧服务器文本日志', TxtLog_Down: '云函数没有旧服务器日志文件可下载',

+ 20 - 0
scripts/generate-admin-coverage.mjs

@@ -335,6 +335,26 @@ const explicit = new Map([
   ['User / Role/ARoleAdd_Submit', ['implemented', 'saveRole 已覆盖管理员角色名称、说明、类型固化和原子 roleId']],
   ['User / Role/ARoleAdd_Submit', ['implemented', 'saveRole 已覆盖管理员角色名称、说明、类型固化和原子 roleId']],
   ['User / Role/ARoleAuth', ['implemented', '已在角色详情中完整展示 model/content/shop/page/exam/user/system/office/portable/sites/other/extend']],
   ['User / Role/ARoleAuth', ['implemented', '已在角色详情中完整展示 model/content/shop/page/exam/user/system/office/portable/sites/other/extend']],
   ['User / Role/ARoleAuth_Submit', ['implemented', 'saveRoleAuthorization 已覆盖 12 域权限码去重校验、新建/更新和原子权限编号']],
   ['User / Role/ARoleAuth_Submit', ['implemented', 'saveRoleAuthorization 已覆盖 12 域权限码去重校验、新建/更新和原子权限编号']],
+  ['User / Struct/Default', ['blocked-data', '旧入口跳转组织树;目标缺少 ZL_Structure 主表,无法提供等价落点']],
+  ['User / Struct/StructList', ['blocked-data', '目标缺少组织名称、父级、排序、成员、管理员、权限、提醒和经验历史数据']],
+  ['User / Struct/order', ['blocked-data', '目标没有可按父节点连续重排 OrderID 的组织结构记录']],
+  ['User / Struct/order_submit', ['blocked-data', '目标没有可按旧结构 ID 更新 OrderID 的组织结构记录']],
+  ['User / Struct/Struct_API', ['blocked-data', '目标缺少 ZL_Structure.UserIDS/ManagerIDS 与 ZL_User.StructureID 双向成员关系']],
+  ['User / Struct/StructByTree', ['blocked-data', '目标没有 ParentID 层级及组织成员、管理员关系,不能还原结构树']],
+  ['User / Struct/Struct_ListByTree', ['blocked-data', '目标没有可递归展开并按 OrderID 排序的组织结构记录']],
+  ['User / Struct/StructAdd', ['blocked-data', '目标没有可编辑或新增的组织结构主记录']],
+  ['User / Struct/StructAdd_Submit', ['blocked-data', '目标缺少 ZL_Structure 持久层及用户结构关系同步事务']],
+  ['User / Struct/StructMember', ['blocked-data', '目标缺少 UserIDS/ManagerIDS 组织成员关系;Profile 与 Group 语义不等价']],
+  ['User / Struct/Structure_update', ['blocked-data', '目标没有可供树形拖动和批量重排的组织结构记录']],
+  ['User / Struct/Structure_Merge', ['blocked-data', '目标缺少成员、管理员、经验合并与来源结构删除所需数据']],
+  ['User / Struct/Structure_Move', ['blocked-data', '目标没有可校验循环并更新 ParentID 的组织结构树']],
+  ['User / Struct/Structure_List', ['blocked-data', '目标 Schema 缺少旧 ZL_Structure 平铺列表及历史记录']],
+  ['Label / Vue/Index', ['blocked-data', '目标 Schema 缺少旧 ZL_Vue_Tlp 模板代码表与历史记录']],
+  ['Label / Vue/TlpView', ['blocked-infrastructure', '目标缺少模板路径数据、服务器模板文件和 B_CreateHtml 标签预览运行时']],
+  ['Label / Vue/VUE_API', ['blocked-infrastructure', '目标缺少 ZL_Vue_Tlp,云函数也没有模板文件、标签渲染、vue.tlp 和目录复制运行时']],
+  ['Label / Vue/Config', ['blocked-infrastructure', '目标没有 SiteOption.VUE_DirCopy 配置及其服务器发布消费者']],
+  ['Label / Vue/Config_Submit', ['blocked-infrastructure', '云函数不能更新旧 SiteConfig 或按配置复制服务器目录']],
+  ['Workload/Default', ['blocked-data', '旧逐日工作量统计必须内连接 ZL_Comment;目标仅有 CommonModel,缺少评论主表与 GeneralID 关系']],
   ['Extend / Safe/Default', ['blocked-infrastructure', '旧默认入口只跳转 SiteSwitch;目标没有静态 Angular 站点的安全开关控制面']],
   ['Extend / Safe/Default', ['blocked-infrastructure', '旧默认入口只跳转 SiteSwitch;目标没有静态 Angular 站点的安全开关控制面']],
   ['Extend / Safe/API', ['blocked-infrastructure', '旧聚合 API 依赖缺失 Safe_IP、服务器站点检查局部页和 ContentWordHelper 常驻线程']],
   ['Extend / Safe/API', ['blocked-infrastructure', '旧聚合 API 依赖缺失 Safe_IP、服务器站点检查局部页和 ContentWordHelper 常驻线程']],
   ['Extend / Safe/SiteSwitch', ['blocked-infrastructure', '目标没有站点上下线状态、下线页文件或前端发布开关']],
   ['Extend / Safe/SiteSwitch', ['blocked-infrastructure', '目标没有站点上下线状态、下线页文件或前端发布开关']],

+ 4 - 1
scripts/smoke-admin-functions.mjs

@@ -750,6 +750,9 @@ try {
   for (const action of ['Default','Default_Submit','Mobile','Mobile_Submit','Offline','Offline_Submit']) { const blocked = await callFunctionError('xiaoshu/admin/gateway', login.sessionToken, { operation: 'legacyPWABlocker', action }, 501); if (!String(blocked.message).includes('migration_blocked')) throw new Error(`旧 PWA 动作 ${action} 未返回明确迁移阻塞`); }
   for (const action of ['Default','Default_Submit','Mobile','Mobile_Submit','Offline','Offline_Submit']) { const blocked = await callFunctionError('xiaoshu/admin/gateway', login.sessionToken, { operation: 'legacyPWABlocker', action }, 501); if (!String(blocked.message).includes('migration_blocked')) throw new Error(`旧 PWA 动作 ${action} 未返回明确迁移阻塞`); }
   for (const action of ['TaskList','ViewLog','TaskAdd','TaskEdit','TaskUpdate','ExecuteNow','TaskDelete','TaskDeleteRange','TaskPause','TaskResume']) { const blocked = await callFunctionError('xiaoshu/admin/gateway', login.sessionToken, { operation: 'legacyQuartzTaskBlocker', action }, 501); if (!String(blocked.message).includes('migration_blocked')) throw new Error(`旧 Quartz 任务动作 ${action} 未返回明确迁移阻塞`); }
   for (const action of ['TaskList','ViewLog','TaskAdd','TaskEdit','TaskUpdate','ExecuteNow','TaskDelete','TaskDeleteRange','TaskPause','TaskResume']) { const blocked = await callFunctionError('xiaoshu/admin/gateway', login.sessionToken, { operation: 'legacyQuartzTaskBlocker', action }, 501); if (!String(blocked.message).includes('migration_blocked')) throw new Error(`旧 Quartz 任务动作 ${action} 未返回明确迁移阻塞`); }
   for (const action of ['Default','API','SiteSwitch','SiteIP','SiteIPAdd','SiteIPAdd_Submit','ContentProtect','SiteAssess','SiteProtect','SiteProtect_Submit','ContentCheck','ContentCheckConfig','ContentCheckConfig_Submit','ContentFilter','SiteCheck']) { const blocked = await callFunctionError('xiaoshu/admin/gateway', login.sessionToken, { operation: 'legacySafeBlocker', action }, 501); if (!String(blocked.message).includes('migration_blocked')) throw new Error(`旧站点安全动作 ${action} 未返回明确迁移阻塞`); }
   for (const action of ['Default','API','SiteSwitch','SiteIP','SiteIPAdd','SiteIPAdd_Submit','ContentProtect','SiteAssess','SiteProtect','SiteProtect_Submit','ContentCheck','ContentCheckConfig','ContentCheckConfig_Submit','ContentFilter','SiteCheck']) { const blocked = await callFunctionError('xiaoshu/admin/gateway', login.sessionToken, { operation: 'legacySafeBlocker', action }, 501); if (!String(blocked.message).includes('migration_blocked')) throw new Error(`旧站点安全动作 ${action} 未返回明确迁移阻塞`); }
+  for (const action of ['Default','StructList','order','order_submit','Struct_API','StructByTree','Struct_ListByTree','StructAdd','StructAdd_Submit','StructMember','Structure_update','Structure_Merge','Structure_Move','Structure_List']) { const blocked = await callFunctionError('xiaoshu/admin/gateway', login.sessionToken, { operation: 'legacyStructureBlocker', action }, 501); if (!String(blocked.message).includes('migration_blocked')) throw new Error(`旧组织架构动作 ${action} 未返回明确迁移阻塞`); }
+  for (const action of ['Index','TlpView','VUE_API','Config','Config_Submit']) { const blocked = await callFunctionError('xiaoshu/admin/gateway', login.sessionToken, { operation: 'legacyVueBlocker', action }, 501); if (!String(blocked.message).includes('migration_blocked')) throw new Error(`旧 Vue 模板动作 ${action} 未返回明确迁移阻塞`); }
+  for (const action of ['Default']) { const blocked = await callFunctionError('xiaoshu/admin/gateway', login.sessionToken, { operation: 'legacyWorkloadBlocker', action }, 501); if (!String(blocked.message).includes('migration_blocked')) throw new Error(`旧工作量统计动作 ${action} 未返回明确迁移阻塞`); }
   for (const action of ['TxtLog','TxtLogContent','TxtLog_Down','TaskList','TaskAdd','TaskAdd_API','TaskAdd_Submit','Task_API','TaskCenter']) {
   for (const action of ['TxtLog','TxtLogContent','TxtLog_Down','TaskList','TaskAdd','TaskAdd_API','TaskAdd_Submit','Task_API','TaskCenter']) {
     const blocked = await callFunctionError('xiaoshu/admin/gateway', login.sessionToken, { operation: 'legacyLogBlocker', action }, 501); if (!String(blocked.message).includes('migration_blocked')) throw new Error(`旧日志任务动作 ${action} 未返回明确迁移阻塞`);
     const blocked = await callFunctionError('xiaoshu/admin/gateway', login.sessionToken, { operation: 'legacyLogBlocker', action }, 501); if (!String(blocked.message).includes('migration_blocked')) throw new Error(`旧日志任务动作 ${action} 未返回明确迁移阻塞`);
   }
   }
@@ -950,7 +953,7 @@ try {
   const revokedPayload = await revokedResponse.json().catch(() => ({}));
   const revokedPayload = await revokedResponse.json().catch(() => ({}));
   if (revokedResponse.status !== 401 || !String(revokedPayload.message || revokedPayload.error || '').toLowerCase().includes('sessiontoken')) throw new Error(`已退出管理员会话仍可使用: ${revokedResponse.status}`);
   if (revokedResponse.status !== 401 || !String(revokedPayload.message || revokedPayload.error || '').toLowerCase().includes('sessiontoken')) throw new Error(`已退出管理员会话仍可使用: ${revokedResponse.status}`);
 
 
-  console.log('Cloud smoke passed: admin auth/logout/tenant/account lifecycle/group sync/node/special/guest-category/bar/exam-class/exam-point/knowledge/exam-teacher/dictionary/grade-dictionary/currency/holiday/search-navigation/advertising/font-shapes/design-scenes/design-templates/surveys/customer-service/stores/content-tags/shop-vendor-blockers/user-level/money-ledgers/user-addon-blockers/ask-blockers/biao-calc-blockers/office-chart-blocker/chat-blockers/design-class-blockers/com-blockers/common-blockers/site-config-blockers/baike/api-catalog/config-blockers/model/guestbook lifecycle/content workflow/CRUD reads, CMS projections, normalized learning joins, app action coverage, public content, session scope, explicit blocked APIs.');
+  console.log('Cloud smoke passed: admin auth/logout/tenant/account lifecycle/group sync/node/special/guest-category/bar/exam-class/exam-point/knowledge/exam-teacher/dictionary/grade-dictionary/currency/holiday/search-navigation/advertising/font-shapes/design-scenes/design-templates/surveys/customer-service/stores/content-tags/shop-vendor-blockers/user-level/money-ledgers/user-addon-blockers/ask-blockers/biao-calc-blockers/office-chart-blocker/chat-blockers/design-class-blockers/structure-blockers/vue-blockers/workload-blocker/com-blockers/common-blockers/site-config-blockers/baike/api-catalog/config-blockers/model/guestbook lifecycle/content workflow/CRUD reads, CMS projections, normalized learning joins, app action coverage, public content, session scope, explicit blocked APIs.');
 } finally {
 } finally {
   if (adZoneRestore) await jsonRequest(`${PARSE_URL}/classes/AdZone/${adZoneRestore.objectId}`, { method: 'PUT', body: JSON.stringify({ remind: adZoneRestore.remind, zstatus: adZoneRestore.zstatus }) }, true).catch((error) => { console.error(`广告位冒烟数据恢复失败:${error.message}`); process.exitCode = 1; });
   if (adZoneRestore) await jsonRequest(`${PARSE_URL}/classes/AdZone/${adZoneRestore.objectId}`, { method: 'PUT', body: JSON.stringify({ remind: adZoneRestore.remind, zstatus: adZoneRestore.zstatus }) }, true).catch((error) => { console.error(`广告位冒烟数据恢复失败:${error.message}`); process.exitCode = 1; });
   if (adInfoRestore) await jsonRequest(`${PARSE_URL}/classes/AdInfo/${adInfoRestore.objectId}`, { method: 'PUT', body: JSON.stringify({ remind: adInfoRestore.remind, zstatus: adInfoRestore.zstatus }) }, true).catch((error) => { console.error(`广告内容冒烟数据恢复失败:${error.message}`); process.exitCode = 1; });
   if (adInfoRestore) await jsonRequest(`${PARSE_URL}/classes/AdInfo/${adInfoRestore.objectId}`, { method: 'PUT', body: JSON.stringify({ remind: adInfoRestore.remind, zstatus: adInfoRestore.zstatus }) }, true).catch((error) => { console.error(`广告内容冒烟数据恢复失败:${error.message}`); process.exitCode = 1; });

+ 40 - 40
src/app/admin/legacy-admin-coverage.generated.ts

@@ -5893,99 +5893,99 @@ export const LEGACY_ADMIN_COVERAGE: readonly LegacyAdminCoverageEntry[] = [
     "module": "User / Struct",
     "module": "User / Struct",
     "action": "Default",
     "action": "Default",
     "occurrence": 1,
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "旧入口跳转组织树;目标缺少 ZL_Structure 主表,无法提供等价落点"
   },
   },
   {
   {
     "module": "User / Struct",
     "module": "User / Struct",
     "action": "StructList",
     "action": "StructList",
     "occurrence": 1,
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标缺少组织名称、父级、排序、成员、管理员、权限、提醒和经验历史数据"
   },
   },
   {
   {
     "module": "User / Struct",
     "module": "User / Struct",
     "action": "order",
     "action": "order",
     "occurrence": 1,
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标没有可按父节点连续重排 OrderID 的组织结构记录"
   },
   },
   {
   {
     "module": "User / Struct",
     "module": "User / Struct",
     "action": "order_submit",
     "action": "order_submit",
     "occurrence": 1,
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标没有可按旧结构 ID 更新 OrderID 的组织结构记录"
   },
   },
   {
   {
     "module": "User / Struct",
     "module": "User / Struct",
     "action": "Struct_API",
     "action": "Struct_API",
     "occurrence": 1,
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标缺少 ZL_Structure.UserIDS/ManagerIDS 与 ZL_User.StructureID 双向成员关系"
   },
   },
   {
   {
     "module": "User / Struct",
     "module": "User / Struct",
     "action": "StructByTree",
     "action": "StructByTree",
     "occurrence": 1,
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标没有 ParentID 层级及组织成员、管理员关系,不能还原结构树"
   },
   },
   {
   {
     "module": "User / Struct",
     "module": "User / Struct",
     "action": "Struct_ListByTree",
     "action": "Struct_ListByTree",
     "occurrence": 1,
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标没有可递归展开并按 OrderID 排序的组织结构记录"
   },
   },
   {
   {
     "module": "User / Struct",
     "module": "User / Struct",
     "action": "StructAdd",
     "action": "StructAdd",
     "occurrence": 1,
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标没有可编辑或新增的组织结构主记录"
   },
   },
   {
   {
     "module": "User / Struct",
     "module": "User / Struct",
     "action": "StructAdd_Submit",
     "action": "StructAdd_Submit",
     "occurrence": 1,
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标缺少 ZL_Structure 持久层及用户结构关系同步事务"
   },
   },
   {
   {
     "module": "User / Struct",
     "module": "User / Struct",
     "action": "StructMember",
     "action": "StructMember",
     "occurrence": 1,
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标缺少 UserIDS/ManagerIDS 组织成员关系;Profile 与 Group 语义不等价"
   },
   },
   {
   {
     "module": "User / Struct",
     "module": "User / Struct",
     "action": "Structure_update",
     "action": "Structure_update",
     "occurrence": 1,
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标没有可供树形拖动和批量重排的组织结构记录"
   },
   },
   {
   {
     "module": "User / Struct",
     "module": "User / Struct",
     "action": "Structure_Merge",
     "action": "Structure_Merge",
     "occurrence": 1,
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标缺少成员、管理员、经验合并与来源结构删除所需数据"
   },
   },
   {
   {
     "module": "User / Struct",
     "module": "User / Struct",
     "action": "Structure_Move",
     "action": "Structure_Move",
     "occurrence": 1,
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标没有可校验循环并更新 ParentID 的组织结构树"
   },
   },
   {
   {
     "module": "User / Struct",
     "module": "User / Struct",
     "action": "Structure_List",
     "action": "Structure_List",
     "occurrence": 1,
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标 Schema 缺少旧 ZL_Structure 平铺列表及历史记录"
   },
   },
   {
   {
     "module": "Extend / Survey",
     "module": "Extend / Survey",
@@ -6887,36 +6887,36 @@ export const LEGACY_ADMIN_COVERAGE: readonly LegacyAdminCoverageEntry[] = [
     "module": "Label / Vue",
     "module": "Label / Vue",
     "action": "Index",
     "action": "Index",
     "occurrence": 1,
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标 Schema 缺少旧 ZL_Vue_Tlp 模板代码表与历史记录"
   },
   },
   {
   {
     "module": "Label / Vue",
     "module": "Label / Vue",
     "action": "TlpView",
     "action": "TlpView",
     "occurrence": 1,
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-infrastructure",
+    "reason": "目标缺少模板路径数据、服务器模板文件和 B_CreateHtml 标签预览运行时"
   },
   },
   {
   {
     "module": "Label / Vue",
     "module": "Label / Vue",
     "action": "VUE_API",
     "action": "VUE_API",
     "occurrence": 1,
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-infrastructure",
+    "reason": "目标缺少 ZL_Vue_Tlp,云函数也没有模板文件、标签渲染、vue.tlp 和目录复制运行时"
   },
   },
   {
   {
     "module": "Label / Vue",
     "module": "Label / Vue",
     "action": "Config",
     "action": "Config",
     "occurrence": 1,
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-infrastructure",
+    "reason": "目标没有 SiteOption.VUE_DirCopy 配置及其服务器发布消费者"
   },
   },
   {
   {
     "module": "Label / Vue",
     "module": "Label / Vue",
     "action": "Config_Submit",
     "action": "Config_Submit",
     "occurrence": 1,
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-infrastructure",
+    "reason": "云函数不能更新旧 SiteConfig 或按配置复制服务器目录"
   },
   },
   {
   {
     "module": "Mobile / WeiXin",
     "module": "Mobile / WeiXin",
@@ -7223,8 +7223,8 @@ export const LEGACY_ADMIN_COVERAGE: readonly LegacyAdminCoverageEntry[] = [
     "module": "Workload",
     "module": "Workload",
     "action": "Default",
     "action": "Default",
     "occurrence": 1,
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "旧逐日工作量统计必须内连接 ZL_Comment;目标仅有 CommonModel,缺少评论主表与 GeneralID 关系"
   },
   },
   {
   {
     "module": "WritePearl",
     "module": "WritePearl",