feat: 新增IM即时通讯(浮窗)、工作流、打印模块及工作台增强

- IM: 新增浮窗聊天(ImFloatWindow)、管理页(monitor/config/service/message)、SSE推送
- 工作流: 新增待办/我的流程页面及后端服务
- 打印: 新增打印模板、出库单打印(PrintPage)、模板种子脚本
- 工作台: 增强快捷入口与工作台数据
- 修复: TagsView页签关闭、CrudPage通用表格增强
- 移除导航菜单中的即时通讯入口,改为右下角浮窗
This commit is contained in:
2026-08-16 00:19:24 +08:00
parent 7235265749
commit 1bec470647
49 changed files with 5791 additions and 51 deletions
+123
View File
@@ -0,0 +1,123 @@
/**
* 任务2 种子:简易工作流
* 1) 创建「原料出库审批」流程定义 + 节点(开始 → 车间主管审批 → 结束)
* 2) 创建侧边栏菜单:工作流(目录)→ 待办中心 / 我发起的
* 用法:node server/scripts/seed-workflow.mjs
*/
const BASE = process.env.F9MES_API || 'http://localhost:5136';
const PHONE = '13800000000';
const PASSWORD = '123456';
// 默认审批人:超级管理员(用户ID=1)
const APPROVER_ID = 1;
async function api(h, method, url, body) {
const res = await fetch(BASE + url, {
method,
headers: h,
body: body ? JSON.stringify(body) : undefined,
});
const text = await res.text();
let json = null;
try { json = JSON.parse(text); } catch { /* ignore */ }
if (!res.ok || (json && json.code !== 0)) {
throw new Error(`HTTP ${res.status} ${url}: ${text.slice(0, 300)}`);
}
return json;
}
async function main() {
const login = await api({ 'Content-Type': 'application/json' }, 'POST', '/api/auth/login', {
phone: PHONE,
password: PASSWORD,
});
const token = login.data.token;
const h = { Authorization: 'Bearer ' + token, 'Content-Type': 'application/json' };
// ============ 1. 流程定义 ============
const flows = (await api(h, 'GET', '/api/workflow/definitions?bizType=RawMaterial_OutStock')).data || [];
let flow = flows.find((f) => f.code === 'RM_OUT_APPROVE');
if (flow) {
console.log('流程已存在:id=' + flow.id + ' name=' + flow.name + '(跳过创建)');
} else {
// 直接写库:Common_Workflow + Common_WorkflowNode(走通用 data 接口)
const addFlow = await api(h, 'POST', '/api/data/Common_Workflow/add', {
name: '原料出库审批',
code: 'RM_OUT_APPROVE',
bizType: 'RawMaterial_OutStock',
status: 1,
});
const flowId = addFlow.data && (addFlow.data.id ?? addFlow.data);
console.log('流程已创建 id=' + flowId);
const nodes = [
{ workflowId: flowId, name: '发起', nodeType: 0, sort: 10 },
{ workflowId: flowId, name: '车间主管审批', nodeType: 1, approverJson: JSON.stringify([APPROVER_ID]), sort: 20 },
{ workflowId: flowId, name: '结束', nodeType: 4, sort: 99 },
];
for (const n of nodes) {
const r = await api(h, 'POST', '/api/data/Common_WorkflowNode/add', n);
console.log(' 节点「' + n.name + '」id=' + (r.data && (r.data.id ?? r.data)));
}
flow = { id: flowId };
}
// ============ 2. 菜单 ============
const menus = (await api(h, 'GET', '/api/data/BaseSys_Menu/all')).data || [];
const items = (menus.items || (Array.isArray(menus) ? menus : []));
let dir = items.find((m) => m.name === '工作流' && m.menuType === 0);
if (!dir) {
const r = await api(h, 'POST', '/api/data/BaseSys_Menu/add', {
name: '工作流',
menuType: 0,
routePath: '',
component: '',
icon: 'Checked',
isVisible: 1,
isEnable: 1,
sort: 999,
parentId: 0,
level: 0,
path: '',
});
dir = { id: r.data && (r.data.id ?? r.data) };
console.log('菜单目录「工作流」已创建 id=' + dir.id);
} else {
console.log('菜单目录「工作流」已存在 id=' + dir.id);
}
const dirId = dir.id;
const childDefs = [
{ name: '待办中心', routePath: '/workflow/todo', component: 'WorkFlow/todo', icon: 'Bell', sort: 10 },
{ name: '我发起的', routePath: '/workflow/my', component: 'WorkFlow/my', icon: 'Document', sort: 20 },
];
const refresh = (await api(h, 'GET', '/api/data/BaseSys_Menu/all')).data || {};
const allMenus = refresh.items || (Array.isArray(refresh) ? refresh : []);
for (const c of childDefs) {
const exists = allMenus.find((m) => m.parentId === dirId && m.routePath === c.routePath);
if (exists) {
console.log('菜单「' + c.name + '」已存在 id=' + exists.id);
continue;
}
const r = await api(h, 'POST', '/api/data/BaseSys_Menu/add', {
name: c.name,
menuType: 1,
routePath: c.routePath,
component: c.component,
icon: c.icon,
isVisible: 1,
isEnable: 1,
sort: c.sort,
parentId: dirId,
level: 1,
path: '/' + dirId,
});
console.log('菜单「' + c.name + '」已创建 id=' + (r.data && (r.data.id ?? r.data)));
}
console.log('\n完成。流程=' + (flow.id || '?') + '(原料出库审批 RM_OUT_APPROVE');
}
main().catch((e) => {
console.error('ERR: ' + e.message);
process.exit(1);
});