feat(表单): 正文接入模块页同款富文本,公告/宿舍的接收对象接入自绘控件
build / build (push) Successful in 2m43s

承接上一提交(通知公告的两个控件),把其余模块里同类控件一次搬到位。

新增组件(都照搬对应模块页的实现,只把「数据从哪来」换成后端下发的 pickerSources)
  · ScopePickerInput.vue ← 校园公告 components/ClassScopeInput.vue
    (全部班级 / 指定班级 + 只读回显 + 弹窗班级树;v-model 是 scope 对象数组)
  · RoomPickerInput.vue ← 宿舍通知 views/dorm/NewNotice.vue 的「选择接收宿舍」弹窗
    (左楼栋列表 / 右按楼层分组的房间勾选 / 底部已选间数与住宿生人数;v-model 是字符串)
  · utils/picker-tree.ts:只剥 {code,msg,data} 外壳、**不拍平**
    (弹窗要的是原始嵌套树;拍平是下拉字典的口径)

改造
  · RichTextEditor:后端没下发正文图片上传地址时,把工具栏的「图片」整组摘掉
    —— 专业发展的编辑器就是这样(该模块没有 UploadImage)
  · UserCheckInput:抽出公共 parseTree,三个自绘控件共用一份外壳剥离口径
  · FormCard:接入 scopepicker / roompicker(初值、序列化、isWide 独占整行)
  · types/agent.ts:widget 联合类型补 scopepicker / roompicker
This commit is contained in:
lijing
2026-10-14 16:56:50 +08:00
parent 6610623cf6
commit b0f31608d4
7 changed files with 954 additions and 55 deletions
+1 -28
View File
@@ -36,6 +36,7 @@
*/
import UserCheckModal from "@/components/UserCheckModal.vue";
import { callModuleApi } from "@/utils/module-session";
import { parseTree } from "@/utils/picker-tree";
import type { BrowserToolCall } from "@/types/agent";
/** 后端下发的数据源页签(每个 call 都已带模块前缀) */
@@ -44,34 +45,6 @@ interface PickerSource {
call: BrowserToolCall;
}
/**
* 把模块返回的原始响应解成**组织树数组**。
*
* <p>与 `parseOptionItems` 分开写:那个是给下拉用的,会把树**拍平成叶子**
* (下拉里当然不该出现「年级」这一层)。选人弹窗要的恰恰是层级本身,
* 所以这里只做「剥掉 {code,msg,data} 外壳」,不动结构。
*/
function parseTree(body?: string): any[] {
if (!body) return [];
let payload: any;
try {
payload = JSON.parse(body);
} catch {
return [];
}
if (payload && typeof payload === "object" && !Array.isArray(payload) && "code" in payload) {
if (Number(payload.code) !== 0) return [];
payload = payload.data;
}
if (Array.isArray(payload)) return payload;
// 少数版本把数组包在 data.list / data.records 里,容错取第一个数组
if (payload && typeof payload === "object") {
const arr = Object.values(payload).find(v => Array.isArray(v));
if (arr) return arr as any[];
}
return [];
}
export default {
name: "UserCheckInput",
components: { UserCheckModal },