init: 迁移本地前端代码(Gitea 仓库重建后重新初始化)

This commit is contained in:
chenxing
2026-09-01 14:41:56 +08:00
commit 807ca07367
333 changed files with 27593 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
import { createProdMockServer } from "vite-plugin-mock/es/createProdMockServer";
import NoticeMock from "./noticeapi";
export function setupProdMockServer() {
createProdMockServer([...NoticeMock]);
}
+724
View File
@@ -0,0 +1,724 @@
/**
* 通知公告本地 Mock 数据(vite-plugin-mock,dev 下 VITE_IS_MOCK=true 生效)。
*
* 背景:本地无法访问 sso.yunhaixingtong.com / base.yunhaixingtong.com,
* 后端 CAS 过滤器会拦截 /api/* 请求,导致前端无法联调。
* 该文件按后端 NoticeApiController / DictController / ContactsController /
* FileController 的真实响应契约({code, msg, data})提供内存数据,
* 仅用于前端页面调试,字段结构与线上后端保持一致。
*/
/* ============================ 基础数据 ============================ */
const deptDict = [
{ SerID: 101, CodeID: "D001", Name: "校长室", Enable: 1 },
{ SerID: 102, CodeID: "D002", Name: "办公室", Enable: 1 },
{ SerID: 103, CodeID: "D003", Name: "教务处", Enable: 1 },
{ SerID: 104, CodeID: "D004", Name: "政教处", Enable: 1 },
{ SerID: 105, CodeID: "D005", Name: "总务处", Enable: 1 },
{ SerID: 106, CodeID: "D006", Name: "电教组", Enable: 1 },
{ SerID: 107, CodeID: "D007", Name: "英语组", Enable: 0 },
{ SerID: 108, CodeID: "D008", Name: "语文组", Enable: 1 },
];
let deptSeq = 109;
let nextFileId = 9001;
/** 通知存储(Create/EditNotice 会写入这里,刷新页面即还原) */
const notices = [
{
serid: 1,
title: "关于开展2026年秋季学期教学检查的通知",
DeptID: "D001",
DeptName: "校长室",
TeaName: "刘文娟",
TeaAccount: "lwj",
EditDate: "2026-08-20 10:35:00",
createTime: "2026-08-20 10:35:00",
Contents: "<p>各位老师:</p><p>定于下周开展秋季学期教学检查,请各教研组提前准备教案、听课记录等材料。</p><p>具体安排另行通知。</p>",
ReceiveList: "ALL",
allowcount: 33,
readcount: 12,
UpFile: "9001,9002",
isDsfs: false,
DsfsTime: null,
readUsers: [
{ account: "zhd", name: "朱皓东", read: true },
{ account: "lwm", name: "刘文娟", read: true },
{ account: "cy", name: "陈垠", read: true },
],
},
{
serid: 2,
title: "校园网络安全培训通知",
DeptID: "D006",
DeptName: "电教组",
TeaName: "黄瑶",
TeaAccount: "hy",
EditDate: "2026-08-22 14:22:00",
createTime: "2026-08-22 14:22:00",
Contents: "<p>定于本周五下午 15:00 在报告厅开展校园网络安全培训,请全体教职工准时参加。</p>",
ReceiveList: "ALL",
allowcount: 33,
readcount: 5,
UpFile: null,
isDsfs: false,
DsfsTime: null,
readUsers: [
{ account: "zhd", name: "朱皓东", read: true },
],
},
{
serid: 3,
title: "关于2026年教师节活动安排的通知",
DeptID: "D002",
DeptName: "办公室",
TeaName: "刘文娟",
TeaAccount: "lwj",
EditDate: "2026-08-25 09:00:00",
createTime: "2026-08-25 09:00:00",
Contents: "<p>今年教师节主题活动定于 9 月 10 日下午举行,节目报名请于 9 月 5 日前报至办公室。</p>",
ReceiveList: '["zhd","cy","hj"]',
allowcount: 3,
readcount: 1,
UpFile: null,
isDsfs: false,
DsfsTime: null,
readUsers: [
{ account: "zhd", name: "朱皓东", read: true },
],
},
{
serid: 4,
title: "学期末固定资产清点通知",
DeptID: "D005",
DeptName: "总务处",
TeaName: "测试教师4(资产保管员)4",
TeaAccount: "zcby",
EditDate: "2026-08-27 16:40:00",
createTime: "2026-08-27 16:40:00",
Contents: "<p>请各科室在放假前完成固定资产清点,并填写清点表交至总务处。</p>",
ReceiveList: "ALL",
allowcount: 33,
readcount: 0,
UpFile: null,
isDsfs: true,
DsfsTime: "2026-09-01 08:00:00",
readUsers: [],
},
{
serid: 5,
title: "教学质量月活动方案(讨论稿)",
DeptID: "D003",
DeptName: "教务处",
TeaName: "刘文娟",
TeaAccount: "lwj",
EditDate: "2026-08-28 11:05:00",
createTime: "2026-08-28 11:05:00",
Contents: "<p>附件为教学质量月活动方案讨论稿,请各位老师审阅并反馈意见。</p>",
ReceiveList: "ALL",
allowcount: 33,
readcount: 2,
UpFile: "9003",
isDsfs: false,
DsfsTime: null,
readUsers: [
{ account: "cy", name: "陈垠", read: true },
{ account: "hj", name: "何明明", read: true },
],
},
];
const filesStore = {
9001: { id: 9001, filename: "教学检查安排表.xlsx", fileTrueName: "教学检查安排表.xlsx", size: "56KB" },
9002: { id: 9002, filename: "检查评分标准.docx", fileTrueName: "检查评分标准.docx", size: "120KB" },
9003: { id: 9003, filename: "教学质量月活动方案.pdf", fileTrueName: "教学质量月活动方案.pdf", size: "800KB" },
};
/** 联系组存储(成员为用户 id,与后端 SetContactsTea 存 userId 一致) */
const groups = [
{ serid: 1, GroupName: "英语组", members: [2, 8, 9] },
{ serid: 2, GroupName: "电教组", members: [10, 11] },
{ serid: 3, GroupName: "测试组", members: [1, 4, 6] },
];
let groupSeq = 4;
/** 用户 id → 姓名(组织树叶子 / 联系组成员共用,与 schoolTree 叶子 id 对应) */
const idNames: Record<number, string> = {
1: "朱皓东",
2: "刘文娟",
3: "陈垠",
4: "测试教师5",
5: "测试教师14",
6: "何明明",
7: "测试教师1(资金确认人)",
8: "测试教师5",
9: "测试教师17",
10: "纯教2",
11: "黄瑶",
};
/** 教师账号表(组织树叶子 / 联系组成员共用) */
const teacherNames = {
zhd: "朱皓东",
lwm: "刘文娟",
cy: "陈垠",
hy: "黄瑶",
hj: "何明明",
zwj: "洛文君",
ls001: "测试教师001",
js1: "测试教师1",
js2: "测试教师2",
js5: "测试教师5",
js17: "测试教师17",
zjzr: "测试教师1(资金确认人)",
zcby: "测试教师4(资产保管员)4",
};
/** 基础平台组织树(选择用户 - 基础平台 tab),叶子为教师;isParent 区分组/用户 */
const schoolTree = [
{
id: 301,
name: "校长室",
isParent: true,
children: [
{ id: 1, name: "朱皓东", isParent: false },
{ id: 2, name: "刘文娟", isParent: false },
],
},
{
id: 302,
name: "副校长室",
isParent: true,
children: [{ id: 3, name: "陈垠", isParent: false }],
},
{
id: 303,
name: "教务处",
isParent: true,
children: [
{ id: 4, name: "测试教师5", isParent: false },
{ id: 5, name: "测试教师14", isParent: false },
{ id: 6, name: "何明明", isParent: false },
{ id: 7, name: "测试教师1(资金确认人)", isParent: false },
],
},
{
id: 304,
name: "英语组",
isParent: true,
children: [
{ id: 8, name: "测试教师5", isParent: false },
{ id: 9, name: "测试教师17", isParent: false },
],
},
{
id: 305,
name: "电教组",
isParent: true,
children: [
{ id: 10, name: "纯教2", isParent: false },
{ id: 11, name: "黄瑶", isParent: false },
],
},
];
/* ============================ 工具 ============================ */
function ok(data: any) {
return { code: 0, msg: "操作成功", data };
}
function paginate(list: any[], pageIndex: number, pageSize: number) {
const start = (pageIndex - 1) * pageSize;
return { list: list.slice(start, start + pageSize), total: list.length };
}
function resolveReceiveAccounts(receiveList: string) {
if (!receiveList || /^all$/i.test(receiveList.trim())) return [];
if (receiveList.trim().startsWith("[")) {
try {
return JSON.parse(receiveList);
} catch {
return [];
}
}
return receiveList.split(",").filter(Boolean);
}
function fileUrl(id: number) {
return `/api/base/FileDown?id=${id}`;
}
/* ============================ Mock 定义 ============================ */
export default [
/* ---------- 布局 / 用户 ---------- */
{
url: "/api/base/GetUserInfo",
method: "get",
response: () =>
ok({
id: 186532,
account: "zhd",
name: "朱皓东",
profilePicture: "",
school: { id: 3260, name: "上海鸿育初级中学" },
}),
},
{
url: "/api/base/GetUserMod",
method: "get",
response: () =>
ok([
{ id: 1, modId: 1, name: "已收到", parentId: 0, page: "/", orderNumber: 1, usable: true, children: [] },
{ id: 2, modId: 2, name: "已发送", parentId: 0, page: "/list", orderNumber: 2, usable: true, children: [] },
{ id: 3, modId: 3, name: "全部通知", parentId: 0, page: "/noticelist", orderNumber: 3, usable: true, children: [] },
{ id: 4, modId: 4, name: "通知统计", parentId: 0, page: "/stats", orderNumber: 4, usable: true, children: [] },
{ id: 21, modId: 21, name: "应用设置", parentId: 0, page: "/setting", orderNumber: 5, usable: true, children: [] },
]),
},
{
url: "/api/base/GetLayout",
method: "get",
response: () =>
ok({
ssoUrl: "",
username: "朱皓东",
pathList: [],
topName: "通知公告",
pageUrl: "",
logoUrl: "",
initUrl: "",
id: 0,
avatar: "",
}),
},
/* ---------- 发文部门字典 ---------- */
{
url: "/api/base/GetSysDic",
method: "get",
response: ({ query }: any) =>
ok(
deptDict
.filter((d) => d.Enable === 1 || query.type !== "1")
.map((d) => ({ k: d.CodeID, v: d.Name }))
),
},
{
url: "/api/base/GetDictMangeList",
method: "get",
response: () => ok(deptDict.map((d) => ({ ...d }))),
},
{
url: "/api/base/AddDeptDic",
method: "post",
response: ({ body }: any) => {
const name = new URLSearchParams(body).get("name") || "未命名";
deptDict.push({ SerID: deptSeq++, CodeID: "D" + String(deptSeq).padStart(3, "0"), Name: name, Enable: 1 });
return ok(null);
},
},
{
url: "/api/base/UpdateDeptDic",
method: "post",
response: ({ body }: any) => {
const sp = new URLSearchParams(body);
const serid = Number(sp.get("SerID"));
const item = deptDict.find((d) => d.SerID === serid);
if (item) {
if (sp.get("Name")) item.Name = sp.get("Name") as string;
if (sp.get("Enable") !== null) item.Enable = Number(sp.get("Enable"));
}
return ok(null);
},
},
{
url: "/api/base/DelDept",
method: "get",
response: ({ query }: any) => {
const i = deptDict.findIndex((d) => d.SerID === Number(query.serid));
if (i > -1) deptDict.splice(i, 1);
return ok(null);
},
},
{
url: "/api/base/OrdbyDicIds",
method: "get",
response: ({ query }: any) => {
const ids = String(query.ids || "").split(",").map(Number);
const sorted = ids.map((id) => deptDict.find((d) => d.SerID === id)).filter(Boolean);
if (sorted.length === deptDict.length) deptDict.splice(0, deptDict.length, ...(sorted as any));
return ok(null);
},
},
{
url: "/api/base/GetDeptSync",
method: "get",
response: () => ok(null),
},
{
url: "/api/base/GetSchModuleIsSms",
method: "get",
response: () => ok(true),
},
{
url: "/api/base/GetSchoolAll",
method: "get",
response: () => ok(schoolTree),
},
/* ---------- 附件 ---------- */
{
url: "/api/base/FileUpload",
method: "post",
response: () => {
const id = nextFileId++;
filesStore[id] = { id, filename: `mock附件${id}.png`, fileTrueName: `mock附件${id}.png`, size: "12KB" };
return ok({ fileid: id, name: filesStore[id].filename, host: "", fileurl: fileUrl(id) });
},
},
{
// 富文本编辑器图片上传(wangeditor customUpload)
url: "/api/base/FileUploadImage",
method: "post",
response: () => {
const svg =
"<svg xmlns='http://www.w3.org/2000/svg' width='240' height='120'><rect width='100%' height='100%' fill='#e8f1ff'/><text x='50%' y='50%' font-size='16' fill='#5c9edc' text-anchor='middle' dominant-baseline='middle'>Mock 图片</text></svg>";
const url = "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(svg)));
return ok([{ host: "", fileurl: url, name: "mock图片.png" }]);
},
},
/* ---------- 通知列表 ---------- */
{
url: "/api/notice/Notice_list/List",
method: "get",
response: ({ query }: any) => {
const pageIndex = Number(query.PageIndex || 1);
const pageSize = Number(query.PageSize || 10);
let list = notices.filter((n) => n.DsfsTime === null || n.isDsfs === false || n.EditDate <= "2026-09-09");
if (query.Name) list = list.filter((n) => n.title.includes(query.Name) || n.TeaName.includes(query.Name));
if (query.Depttype) list = list.filter((n) => n.DeptID === query.Depttype);
const me = "zhd";
const mapped = list.map((n) => ({
serid: n.serid,
title: n.title,
DeptID: n.DeptID,
DeptName: n.DeptName,
TeaName: n.TeaName,
EditDate: n.EditDate,
status: n.readUsers.some((u) => u.account === me) ? 2 : 1,
UpFile: n.UpFile,
}));
const page = paginate(mapped, pageIndex, pageSize);
const unreadCount = list.filter((n) => !n.readUsers.some((u) => u.account === me)).length;
return ok({ ...page, unreadCount, pageIndex, pageSize });
},
},
{
url: "/api/notice/Notice_list/SendList",
method: "get",
response: ({ query }: any) => {
const pageIndex = Number(query.PageIndex || 1);
const pageSize = Number(query.PageSize || 10);
let list = [...notices].sort((a, b) => (a.createTime < b.createTime ? 1 : -1));
if (query.Name) list = list.filter((n) => n.title.includes(query.Name) || n.TeaName.includes(query.Name));
if (query.Depttype) list = list.filter((n) => n.DeptID === query.Depttype);
if (query.type === "user") list = list.filter((n) => n.TeaAccount === "zhd" || n.TeaName === "刘文娟");
if (query.starttime) list = list.filter((n) => n.createTime.slice(0, 10) >= query.starttime);
if (query.endtime) list = list.filter((n) => n.createTime.slice(0, 10) <= query.endtime);
const mapped = list.map((n) => ({
serid: n.serid,
title: n.title,
DeptID: n.DeptID,
DeptName: n.DeptName,
TeaName: n.TeaName,
EditDate: n.EditDate,
readcount: n.readcount,
allowcount: n.allowcount,
UpFile: n.UpFile,
}));
return ok({ ...paginate(mapped, pageIndex, pageSize), pageIndex, pageSize });
},
},
/* ---------- 通知详情 ---------- */
{
url: "/api/notice/Notice_list/Modify",
method: "get",
response: ({ query }: any) => {
const n = notices.find((x) => x.serid === Number(query.serid));
if (!n) return { code: 4, msg: "通知不存在", data: null };
if (String(query.type) !== "0" && !n.readUsers.some((u) => u.account === "zhd")) {
n.readUsers.push({ account: "zhd", name: "朱皓东", read: true });
n.readcount += 1;
}
return ok({
title: n.title,
Contents: n.Contents,
DeptName: n.DeptName,
DeptID: n.DeptID,
TeaName: n.TeaName,
EditDate: n.EditDate,
numcount: n.allowcount,
readnumcount: n.readcount,
ReceiveList: n.ReceiveList,
DsfsTime: n.DsfsTime,
isDsfs: n.isDsfs,
ChaoSend: (n.UpFile ? n.UpFile.split(",") : []).map((id) => {
const f = filesStore[Number(id)];
return f
? { id: f.id, name: f.fileTrueName, size: f.size, fileid: f.id, filename: f.filename }
: { id: Number(id), name: "附件", fileid: Number(id), filename: "附件" };
}),
tlist: /^all$/i.test(n.ReceiveList.trim()) ? "all" : resolveReceiveAccounts(n.ReceiveList),
});
},
},
{
url: "/api/notice/Notice_list/GetNoticeDetails",
method: "get",
response: ({ query }: any) => {
const n = notices.find((x) => x.serid === Number(query.serid));
if (!n) return { code: 4, msg: "通知不存在", data: null };
return ok({
title: n.title,
Contents: n.Contents,
DeptID: n.DeptID,
DeptName: n.DeptName,
EditDate: n.EditDate,
DsfsTime: n.DsfsTime,
isDsfs: n.isDsfs,
ChaoSend: (n.UpFile ? n.UpFile.split(",") : []).map((id) => {
const f = filesStore[Number(id)];
return { id: Number(id), name: f ? f.fileTrueName : "附件", size: f ? f.size : "", fileid: Number(id), filename: f ? f.filename : "附件" };
}),
tlist: /^all$/i.test(n.ReceiveList.trim()) ? "all" : resolveReceiveAccounts(n.ReceiveList),
});
},
},
{
// 阅读情况:按学校组分组(对齐真实后端 ReadDetail:组名+组内用户,绿已读/红未读)
url: "/api/notice/ReadDetail",
method: "get",
response: ({ query }: any) => {
const n = notices.find((x) => x.serid === Number(query.serid));
if (!n) return { code: 4, msg: "通知不存在", data: null };
const readAccounts = n.readUsers.map((u) => u.account);
const receivers = resolveReceiveAccounts(n.ReceiveList);
// 已读者不在接收明细中时补充,保证已读情况完整
const accounts = [...receivers, ...readAccounts.filter((a: string) => !receivers.includes(a))];
// 姓名 → 所在组(组织树反查),查不到归「其他」
const nameGroup: Record<string, string> = {};
schoolTree.forEach((dept) => dept.children.forEach((c: any) => (nameGroup[c.name] = dept.name)));
const groupUsers: Record<string, any[]> = {};
accounts.forEach((account: string) => {
const teaname = teacherNames[account] || account;
const groupName = nameGroup[teaname] || "其他";
(groupUsers[groupName] = groupUsers[groupName] || []).push({
account,
teaname,
states: readAccounts.includes(account) ? "read" : "unread",
});
});
return ok(
Object.keys(groupUsers).map((name) => ({ GroupName: name, teaslist: groupUsers[name] }))
);
},
},
{
url: "/api/notice/Notice_list/GetNextPrevNotice",
method: "get",
response: ({ query }: any) => {
const ids = notices.map((n) => n.serid);
const i = ids.indexOf(Number(query.serid));
return ok({
prevId: i > 0 ? ids[i - 1] : 0,
nextId: i > -1 && i < ids.length - 1 ? ids[i + 1] : 0,
});
},
},
/* ---------- 通知写入操作 ---------- */
{
url: "/api/notice/Notice_list/Create",
method: "post",
response: ({ body }: any) => {
const sp = new URLSearchParams(body);
const now = new Date();
const pad = (n: number) => String(n).padStart(2, "0");
const time = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
const deptId = sp.get("DeptID") || "";
const dept = deptDict.find((d) => d.CodeID === deptId);
const receive = sp.get("ReceiveList") || "all";
const isAll = /^all$/i.test(receive.trim());
const receivers = isAll ? [] : receive.split(/[,,\s]+/).filter(Boolean);
const serid = Math.max(...notices.map((n) => n.serid)) + 1;
notices.unshift({
serid,
title: sp.get("title") || "未命名通知",
DeptID: deptId,
DeptName: dept ? dept.Name : "",
TeaName: "朱皓东",
TeaAccount: "zhd",
EditDate: time,
createTime: time,
Contents: sp.get("Contents") || "",
ReceiveList: isAll ? "ALL" : JSON.stringify(receivers),
allowcount: isAll ? 33 : receivers.length,
readcount: 0,
UpFile: sp.get("ChaoSend") || null,
isDsfs: !!sp.get("DsfsTime"),
DsfsTime: sp.get("DsfsTime") || null,
readUsers: [],
});
return ok(serid);
},
},
{
url: "/api/notice/Notice_list/EditNotice",
method: "post",
response: ({ body }: any) => {
const sp = new URLSearchParams(body);
const n = notices.find((x) => x.serid === Number(sp.get("serid")));
if (!n) return { code: 4, msg: "通知不存在", data: null };
const dept = deptDict.find((d) => d.CodeID === sp.get("DeptID"));
n.title = sp.get("title") || n.title;
n.Contents = sp.get("Contents") || "";
n.DeptID = sp.get("DeptID") || n.DeptID;
n.DeptName = dept ? dept.Name : n.DeptName;
const receive = sp.get("ReceiveList") || "all";
const isAll = /^all$/i.test(receive.trim());
n.ReceiveList = isAll ? "ALL" : JSON.stringify(receive.split(/[,,\s]+/).filter(Boolean));
n.UpFile = sp.get("ChaoSend") || null;
return ok(null);
},
},
{
url: "/api/notice/Notice_list/DeleteNotice",
method: "post",
response: ({ body }: any) => {
const ids = (new URLSearchParams(body).get("ids") || "").split(",").map(Number);
for (const id of ids) {
const i = notices.findIndex((n) => n.serid === id);
if (i > -1) notices.splice(i, 1);
}
return ok(null);
},
},
{
url: "/api/notice/Notice_list/InitializeRed",
method: "get",
response: () => {
notices.forEach((n) => {
if (!n.readUsers.some((u) => u.account === "zhd")) {
n.readUsers.push({ account: "zhd", name: "朱皓东", read: true });
n.readcount += 1;
}
});
return ok({ count: notices.length });
},
},
{
url: "/api/notice/Notice_list/UserPull",
method: "post",
response: () => ok(null),
},
{
url: "/api/notice/Notice_list/SetTopmost",
method: "post",
response: () => ok(null),
},
{
url: "/api/notice/Notice_list/Expedite",
method: "post",
response: () => ok(null),
},
{
url: "/api/notice/Notice_list/ReceiveUsers",
method: "post",
response: () => ok(null),
},
/* ---------- 联系组(自定义组) ---------- */
{
url: "/api/contacts/GetGroup",
method: "get",
response: ({ query }: any) => {
const pageIndex = Number(query.page || 1);
const pageSize = Number(query.count || 10);
const mapped = groups.map((g) => ({ serid: g.serid, GroupName: g.GroupName }));
return ok({ ...paginate(mapped, pageIndex, pageSize) });
},
},
{
url: "/api/contacts/AddGroup",
method: "post",
response: ({ body }: any) => {
const name = new URLSearchParams(body).get("name") || "未命名组";
groups.push({ serid: groupSeq++, GroupName: name, members: [] });
return ok(null);
},
},
{
url: "/api/contacts/EditGroup",
method: "post",
response: ({ body }: any) => {
const sp = new URLSearchParams(body);
const g = groups.find((x) => x.serid === Number(sp.get("id")));
if (g) g.GroupName = sp.get("name") || g.GroupName;
return ok(null);
},
},
{
url: "/api/contacts/DelGroup",
method: "post",
response: ({ body }: any) => {
const ids = (new URLSearchParams(body).get("ids") || "").split(",").map(Number);
for (const id of ids) {
const i = groups.findIndex((g) => g.serid === id);
if (i > -1) groups.splice(i, 1);
}
return ok(null);
},
},
{
url: "/api/contacts/GetContactsTea",
method: "get",
response: ({ query }: any) => {
const g = groups.find((x) => x.serid === Number(query.gid));
const members = g ? g.members : [];
const mapped = members.map((id) => ({ account: "user" + id, userId: id, name: idNames[id] || String(id) }));
// page/count 不传或为 0 时返回全量(与后端语义一致)
const pageIndex = Number(query.page || 0);
const pageSize = Number(query.count || 0);
if (!pageIndex || !pageSize) return ok({ list: mapped, total: mapped.length });
return ok({ ...paginate(mapped, pageIndex, pageSize) });
},
},
{
url: "/api/contacts/SetContactsTea",
method: "post",
response: ({ body }: any) => {
const sp = new URLSearchParams(body);
const g = groups.find((x) => x.serid === Number(sp.get("gid")));
if (g) g.members = (sp.get("ids") || "").split(",").filter(Boolean);
return ok(null);
},
},
{
url: "/api/contacts/GetGroupTree",
method: "get",
response: () =>
ok(
groups.map((g) => ({
id: 1000 + g.serid,
name: g.GroupName,
isParent: true,
children: g.members.map((id) => ({ id, name: idNames[id] || String(id), isParent: false })),
}))
),
},
];