feat(bpm/hrm): 流程模型默认流程图、考勤统计按日筛选
一、流程模型「修改 → 第三步流程设计」画布空白(客户需求 2)
根因:defaultEmpty.js 生成的 BPMN 里 <process> 是空的,没有任何节点;
而中亿装配 20 个流程模型中有 16 个没有 bpmnXml(act_re_model.EDITOR_SOURCE_VALUE_ID_ 为 NULL),
打开时走 defaultEmpty 兜底 → 得到空流程 → 画布一片空白。
修复:默认流程改为可用骨架「发起人 → 审批人 → 结束」,含完整 BPMNDI 图形信息,
并补上 flowable/camunda/activiti 扩展属性命名空间声明。
审批节点不预置办理人,由客户在右侧属性面板选择。
二、考勤统计支持按日区间筛选(客户需求 3,确认「按日」)
- 搜索条件由「年月(月份选择器)」改为「统计日期(日期范围)」;
- 新增 buildQueryParams,把 dateRange 拆成 startDate/endDate 传给后端;
- 列表查询与导出共用同一套参数转换;
- 列「年月」改为「统计区间」(选日期时显示 2026-10-01 ~ 2026-10-15)。
- 不选日期时后端仍查月度汇总表,行为兼容。
验证:
- defaultEmpty 产物用 bpmn-moddle 真实解析:0 warnings,
节点 StartEvent(发起人) → UserTask(审批人) → EndEvent(结束),DI 3 shapes + 2 edges;
- 考勤统计 chunk 已包含 dateRange → startDate/endDate 的转换逻辑。
| | |
| | | deptId?: number; |
| | | userId?: number; |
| | | } |
| | | |
| | | /** 考勤统计查询参数(传了 startDate/endDate 则按日区间实时统计) */ |
| | | export interface SummaryPageParams extends PageParam { |
| | | userId?: number; |
| | | deptId?: number; |
| | | yearMonth?: string; |
| | | startDate?: string; |
| | | endDate?: string; |
| | | } |
| | | } |
| | | |
| | | /** 查询考勤统计分页列表 */ |
| | | export function getAttendanceSummaryPage(params: PageParam) { |
| | | export function getAttendanceSummaryPage( |
| | | params: HrmAttendanceSummaryApi.SummaryPageParams, |
| | | ) { |
| | | return requestClient.get<PageResult<HrmAttendanceSummaryApi.AttendanceSummary>>( |
| | | '/hrm/attendance/summary/page', |
| | | { params }, |
| | |
| | | /** |
| | | * 生成默认的 BPMN 流程图 XML |
| | | * |
| | | * 历史问题:这里原本只生成一个空的 <bpmn2:process>,没有任何节点, |
| | | * 打开「流程模型 → 修改 → 流程设计」时画布一片空白,客户反馈「没有流程模型,需要配置一下」。 |
| | | * |
| | | * 现改为生成一个可用的最小流程骨架:发起人 → 审批人 → 结束, |
| | | * 客户打开即可在画布上直接选中节点配置审批人,或继续增删节点。 |
| | | * 注意:审批节点故意不预置办理人,由客户在右侧属性面板里选择。 |
| | | */ |
| | | function defaultEmpty(key, name, type) { |
| | | if (!type) type = 'camunda'; |
| | | const TYPE_TARGET = { |
| | | activiti: 'http://activiti.org/bpmn', |
| | | camunda: 'http://bpmn.io/schema/bpmn', |
| | | flowable: 'http://flowable.org/bpmn', |
| | | }; |
| | | // 扩展属性(如 flowable:assignee)的命名空间,camunda 与 bpmn.io 的 targetNamespace 不同 |
| | | const TYPE_EXTENSION_NS = { |
| | | activiti: 'http://activiti.org/bpmn', |
| | | camunda: 'http://camunda.org/schema/1.0/bpmn', |
| | | flowable: 'http://flowable.org/bpmn', |
| | | }; |
| | | return `<?xml version="1.0" encoding="UTF-8"?> |
| | |
| | | xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" |
| | | xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" |
| | | xmlns:di="http://www.omg.org/spec/DD/20100524/DI" |
| | | xmlns:${type}="${TYPE_EXTENSION_NS[type]}" |
| | | id="diagram_${key}" |
| | | targetNamespace="${TYPE_TARGET[type]}"> |
| | | <bpmn2:process id="${key}" name="${name}" isExecutable="true"> |
| | | <bpmn2:startEvent id="StartEvent_1" name="发起人"> |
| | | <bpmn2:outgoing>Flow_1</bpmn2:outgoing> |
| | | </bpmn2:startEvent> |
| | | <bpmn2:sequenceFlow id="Flow_1" sourceRef="StartEvent_1" targetRef="Activity_1" /> |
| | | <bpmn2:userTask id="Activity_1" name="审批人"> |
| | | <bpmn2:incoming>Flow_1</bpmn2:incoming> |
| | | <bpmn2:outgoing>Flow_2</bpmn2:outgoing> |
| | | </bpmn2:userTask> |
| | | <bpmn2:sequenceFlow id="Flow_2" sourceRef="Activity_1" targetRef="EndEvent_1" /> |
| | | <bpmn2:endEvent id="EndEvent_1" name="结束"> |
| | | <bpmn2:incoming>Flow_2</bpmn2:incoming> |
| | | </bpmn2:endEvent> |
| | | </bpmn2:process> |
| | | <bpmndi:BPMNDiagram id="BPMNDiagram_1"> |
| | | <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="${key}"> |
| | | <bpmndi:BPMNShape id="StartEvent_1_di" bpmnElement="StartEvent_1"> |
| | | <dc:Bounds x="180" y="160" width="36" height="36" /> |
| | | <bpmndi:BPMNLabel> |
| | | <dc:Bounds x="187" y="203" width="22" height="14" /> |
| | | </bpmndi:BPMNLabel> |
| | | </bpmndi:BPMNShape> |
| | | <bpmndi:BPMNShape id="Activity_1_di" bpmnElement="Activity_1"> |
| | | <dc:Bounds x="280" y="138" width="100" height="80" /> |
| | | <bpmndi:BPMNLabel /> |
| | | </bpmndi:BPMNShape> |
| | | <bpmndi:BPMNShape id="EndEvent_1_di" bpmnElement="EndEvent_1"> |
| | | <dc:Bounds x="432" y="160" width="36" height="36" /> |
| | | <bpmndi:BPMNLabel> |
| | | <dc:Bounds x="439" y="203" width="22" height="14" /> |
| | | </bpmndi:BPMNLabel> |
| | | </bpmndi:BPMNShape> |
| | | <bpmndi:BPMNEdge id="Flow_1_di" bpmnElement="Flow_1"> |
| | | <di:waypoint x="216" y="178" /> |
| | | <di:waypoint x="280" y="178" /> |
| | | </bpmndi:BPMNEdge> |
| | | <bpmndi:BPMNEdge id="Flow_2_di" bpmnElement="Flow_2"> |
| | | <di:waypoint x="380" y="178" /> |
| | | <di:waypoint x="432" y="178" /> |
| | | </bpmndi:BPMNEdge> |
| | | </bpmndi:BPMNPlane> |
| | | </bpmndi:BPMNDiagram> |
| | | </bpmn2:definitions>`; |
| | |
| | | export function useGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'yearMonth', |
| | | label: '年月', |
| | | component: 'DatePicker', |
| | | // 按日区间筛选:传了日期就实时统计区间内的考勤;不传则看已生成的月度汇总 |
| | | fieldName: 'dateRange', |
| | | label: '统计日期', |
| | | component: 'RangePicker', |
| | | componentProps: { |
| | | picker: 'month', |
| | | allowClear: true, |
| | | valueFormat: 'YYYY-MM', |
| | | format: 'YYYY-MM', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | format: 'YYYY-MM-DD', |
| | | style: { width: '100%' }, |
| | | placeholder: '选择年月', |
| | | placeholder: ['开始日期', '结束日期'], |
| | | }, |
| | | }, |
| | | { |
| | |
| | | return [ |
| | | { field: 'userName', title: '员工姓名', minWidth: 100 }, |
| | | { field: 'deptName', title: '部门', minWidth: 120 }, |
| | | { field: 'yearMonth', title: '年月', width: 100 }, |
| | | { field: 'yearMonth', title: '统计区间', width: 190 }, |
| | | { field: 'workDays', title: '应出勤天数', width: 100 }, |
| | | { field: 'actualDays', title: '实际出勤天数', width: 110 }, |
| | | { |
| | |
| | | gridApi.query(); |
| | | } |
| | | |
| | | /** |
| | | * 把表单里的日期区间拆成后端需要的 startDate / endDate |
| | | * |
| | | * 不选日期时后端走「月度汇总表」查询;选了日期则按日区间实时统计打卡记录。 |
| | | */ |
| | | function buildQueryParams(formValues: any) { |
| | | const { dateRange, ...rest } = formValues || {}; |
| | | return { |
| | | ...rest, |
| | | startDate: dateRange?.[0], |
| | | endDate: dateRange?.[1], |
| | | }; |
| | | } |
| | | |
| | | /** 生成统计 */ |
| | | function handleGenerate() { |
| | | generateModalApi.open(); |
| | |
| | | |
| | | /** 导出考勤统计 */ |
| | | async function handleExport() { |
| | | const data = await exportAttendanceSummary(await gridApi.formApi.getValues()); |
| | | const data = await exportAttendanceSummary( |
| | | buildQueryParams(await gridApi.formApi.getValues()), |
| | | ); |
| | | downloadFileFromBlobPart({ fileName: '考勤统计.xls', source: data }); |
| | | } |
| | | |
| | |
| | | await getAttendanceSummaryPage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | ...formValues, |
| | | ...buildQueryParams(formValues), |
| | | }), |
| | | }, |
| | | }, |