tuqin
3 天以前 0cd08f4a03daa6516d7600e409c2e15859d237d8
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 的转换逻辑。
已修改4个文件
102 ■■■■■ 文件已修改
src/api/hrm/attendance/summary/index.ts 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/bpm/components/bpmn-process-designer/package/designer/plugins/defaultEmpty.js 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hrm/attendance/summary/data.ts 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hrm/attendance/summary/index.vue 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/hrm/attendance/summary/index.ts
@@ -28,10 +28,21 @@
    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 },
src/views/bpm/components/bpmn-process-designer/package/designer/plugins/defaultEmpty.js
@@ -1,8 +1,24 @@
/**
 * 生成默认的 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"?>
@@ -12,12 +28,49 @@
  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>`;
src/views/hrm/attendance/summary/data.ts
@@ -10,16 +10,16 @@
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: ['开始日期', '结束日期'],
      },
    },
    {
@@ -56,7 +56,7 @@
  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 },
    {
src/views/hrm/attendance/summary/index.vue
@@ -24,6 +24,20 @@
  gridApi.query();
}
/**
 * 把表单里的日期区间拆成后端需要的 startDate / endDate
 *
 * 不选日期时后端走「月度汇总表」查询;选了日期则按日区间实时统计打卡记录。
 */
function buildQueryParams(formValues: any) {
  const { dateRange, ...rest } = formValues || {};
  return {
    ...rest,
    startDate: dateRange?.[0],
    endDate: dateRange?.[1],
  };
}
/** 生成统计 */
function handleGenerate() {
  generateModalApi.open();
@@ -31,7 +45,9 @@
/** 导出考勤统计 */
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 });
}
@@ -49,7 +65,7 @@
          await getAttendanceSummaryPage({
            pageNo: page.currentPage,
            pageSize: page.pageSize,
            ...formValues,
            ...buildQueryParams(formValues),
          }),
      },
    },