liu
2 天以前 749cb2f4ad815c5a68c19fe961d5ca2149a84dd4
feat(mes): 新增员工排班明细页面,修复计划排程菜单404

- 新增 API 封装 schedule-detail 分页/列表/生成/调整/合规校验五个接口
- 新增排班明细列表页:计划/班组/员工/日期搜索,来源 Tag 回显
- 新增生成排班明细、人工调整、合规校验三个弹窗

Co-Authored-By: Claude <noreply@anthropic.com>
已添加6个文件
818 ■■■■■ 文件已修改
src/api/mes/cal/scheduleDetail/index.ts 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/cal/scheduleDetail/data.ts 252 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/cal/scheduleDetail/index.vue 183 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/cal/scheduleDetail/modules/check-view.vue 162 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/cal/scheduleDetail/modules/form.vue 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/cal/scheduleDetail/modules/generate-form.vue 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/mes/cal/scheduleDetail/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,86 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace MesCalScheduleDetailApi {
  /** MES å‘˜å·¥æŽ’班明细 */
  export interface Detail {
    id?: number; // æ˜Žç»†ç¼–号
    planId?: number; // æŽ’班计划编号
    teamId?: number; // ç­ç»„编号
    userId?: number; // ç­ç»„成员用户编号
    day?: Date; // æ—¥æœŸ
    shiftId?: number; // ç­æ¬¡ç¼–号
    shiftName?: string; // ç­æ¬¡åç§°
    startTime?: string; // å¼€å§‹æ—¶é—´ï¼ˆHH:mm)
    endTime?: string; // ç»“束时间(HH:mm)
    source?: number; // æ¥æºï¼š1-自动生成 2-人工调整
    remark?: string; // å¤‡æ³¨
  }
  /** å‘˜å·¥æŽ’班明细查询参数 */
  export interface DetailPageParam extends PageParam {
    planId?: number; // æŽ’班计划编号
    teamId?: number; // ç­ç»„编号
    userId?: number; // ç­ç»„成员用户编号
    startDay?: string; // å¼€å§‹æ—¥æœŸ
    endDay?: string; // ç»“束日期
  }
  /** åˆè§„校验结果项 */
  export interface CheckItem {
    day?: string; // æ—¥æœŸ
    shiftName?: string; // ç­æ¬¡åç§°
    startTime?: string; // å¼€å§‹æ—¶é—´ï¼ˆHH:mm)
    endTime?: string; // ç»“束时间(HH:mm)
    issueType?: number; // è¿è§„类型:1-单日疑似连续工时超长 2-相邻班次间休息不足
    issue?: string; // é—®é¢˜æè¿°ï¼Œä¸ºç©ºè¡¨ç¤ºè¯¥ç­æ¬¡åˆè§„
  }
  /** åˆè§„校验参数 */
  export interface CheckParam {
    userId: number; // ç­ç»„成员用户编号
    startDay?: string; // èµ·å§‹æ—¥æœŸ
    endDay?: string; // ç»“束日期
  }
}
/** æŸ¥è¯¢å‘˜å·¥æŽ’班明细分页 */
export function getDetailPage(params: MesCalScheduleDetailApi.DetailPageParam) {
  return requestClient.get<PageResult<MesCalScheduleDetailApi.Detail>>(
    '/mes/cal/schedule-detail/page',
    { params },
  );
}
/** æŸ¥è¯¢å‘˜å·¥æŽ’班明细列表 */
export function getDetailList(
  params: MesCalScheduleDetailApi.DetailPageParam,
): Promise<MesCalScheduleDetailApi.Detail[]> {
  return requestClient.get<MesCalScheduleDetailApi.Detail[]>(
    '/mes/cal/schedule-detail/list',
    { params },
  );
}
/** æŒ‰ç­ç»„排班生成员工级排班明细 */
export function generateDetails(planId: number) {
  return requestClient.post(
    `/mes/cal/schedule-detail/generate?planId=${planId}`,
  );
}
/** æ›´æ–°å‘˜å·¥æŽ’班明细(人工微调) */
export function updateDetail(data: MesCalScheduleDetailApi.Detail) {
  return requestClient.put('/mes/cal/schedule-detail/update', data);
}
/** å‘˜å·¥æŽ’班合规校验 */
export function checkCompliance(
  params: MesCalScheduleDetailApi.CheckParam,
): Promise<MesCalScheduleDetailApi.CheckItem[]> {
  return requestClient.get<MesCalScheduleDetailApi.CheckItem[]>(
    '/mes/cal/schedule-detail/check',
    { params },
  );
}
src/views/mes/cal/scheduleDetail/data.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,252 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesCalScheduleDetailApi } from '#/api/mes/cal/scheduleDetail';
import { getPlanPage } from '#/api/mes/cal/plan';
import { getTeamList } from '#/api/mes/cal/team';
import { getSimpleUserList } from '#/api/system/user';
import { getRangePickerDefaultProps } from '#/utils';
/** ä¸‹æ‹‰é€‰é¡¹ */
export interface OptionItem {
  label: string;
  value: number;
}
/** æ¥æºï¼šè‡ªåŠ¨ç”Ÿæˆ */
export const SOURCE_AUTO = 1;
/** æ¥æºï¼šäººå·¥è°ƒæ•´ */
export const SOURCE_MANUAL = 2;
/** è¿è§„类型描述 */
export const issueTypeMap: Record<number, string> = {
  1: '单日疑似连续工时超长',
  2: '相邻班次间休息不足',
};
/** æŽ’班计划选项缓存(搜索表单与列表名称回显共用) */
let planOptionsCache: OptionItem[] | undefined;
export async function fetchPlanOptions(): Promise<OptionItem[]> {
  if (planOptionsCache) {
    return planOptionsCache;
  }
  const page = await getPlanPage({ pageNo: 1, pageSize: 100 });
  planOptionsCache = page.list.map((item) => ({
    label: item.name!,
    value: item.id!,
  }));
  return planOptionsCache;
}
/** ç­ç»„选项缓存 */
let teamOptionsCache: OptionItem[] | undefined;
export async function fetchTeamOptions(): Promise<OptionItem[]> {
  if (teamOptionsCache) {
    return teamOptionsCache;
  }
  const list = await getTeamList();
  teamOptionsCache = list.map((item) => ({
    label: item.name!,
    value: item.id!,
  }));
  return teamOptionsCache;
}
/** å‘˜å·¥é€‰é¡¹ç¼“å­˜ */
let userOptionsCache: OptionItem[] | undefined;
export async function fetchUserOptions(): Promise<OptionItem[]> {
  if (userOptionsCache) {
    return userOptionsCache;
  }
  const list = await getSimpleUserList();
  userOptionsCache = list.map((item) => ({
    label: item.nickname!,
    value: item.id!,
  }));
  return userOptionsCache;
}
/** ç”ŸæˆæŽ’班明细的表单 */
export function useGenerateFormSchema(): VbenFormSchema[] {
  return [
    {
      fieldName: 'planId',
      label: '排班计划',
      component: 'ApiSelect',
      componentProps: {
        allowClear: true,
        api: fetchPlanOptions,
        optionFilterProp: 'label',
        placeholder: '请选择排班计划',
        showSearch: true,
      },
      rules: 'selectRequired',
      help: '按该计划下的班组排班展开为员工级明细,重复生成会先清理旧明细',
    },
  ];
}
/** äººå·¥è°ƒæ•´æŽ’班明细的表单 */
export function useFormSchema(): VbenFormSchema[] {
  return [
    {
      fieldName: 'id',
      component: 'Input',
      dependencies: {
        triggerFields: [''],
        show: () => false,
      },
    },
    {
      fieldName: 'shiftId',
      component: 'Input',
      dependencies: {
        triggerFields: [''],
        show: () => false,
      },
    },
    {
      fieldName: 'shiftName',
      label: '班次名称',
      component: 'Input',
      componentProps: {
        placeholder: '请输入班次名称',
      },
      rules: 'required',
    },
    {
      fieldName: 'startTime',
      label: '开始时间',
      component: 'TimePicker',
      componentProps: {
        class: '!w-full',
        format: 'HH:mm',
        placeholder: '请选择开始时间',
        valueFormat: 'HH:mm',
      },
      rules: 'required',
    },
    {
      fieldName: 'endTime',
      label: '结束时间',
      component: 'TimePicker',
      componentProps: {
        class: '!w-full',
        format: 'HH:mm',
        placeholder: '请选择结束时间',
        valueFormat: 'HH:mm',
      },
      rules: 'required',
    },
    {
      fieldName: 'remark',
      label: '备注',
      component: 'Textarea',
      componentProps: {
        placeholder: '请输入备注',
        rows: 3,
      },
    },
  ];
}
/** åˆ—表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
  return [
    {
      fieldName: 'planId',
      label: '排班计划',
      component: 'ApiSelect',
      componentProps: {
        allowClear: true,
        api: fetchPlanOptions,
        optionFilterProp: 'label',
        placeholder: '请选择排班计划',
        showSearch: true,
      },
    },
    {
      fieldName: 'teamId',
      label: '班组',
      component: 'ApiSelect',
      componentProps: {
        allowClear: true,
        api: fetchTeamOptions,
        optionFilterProp: 'label',
        placeholder: '请选择班组',
        showSearch: true,
      },
    },
    {
      fieldName: 'userId',
      label: '员工',
      component: 'ApiSelect',
      componentProps: {
        allowClear: true,
        api: fetchUserOptions,
        optionFilterProp: 'label',
        placeholder: '请选择员工',
        showSearch: true,
      },
    },
    {
      fieldName: 'day',
      label: '日期',
      component: 'RangePicker',
      componentProps: {
        ...getRangePickerDefaultProps(),
      },
    },
  ];
}
/** åˆ—表的字段 */
export function useGridColumns(): VxeTableGridOptions<MesCalScheduleDetailApi.Detail>['columns'] {
  return [
    {
      field: 'day',
      title: '日期',
      minWidth: 120,
      formatter: 'formatDate',
    },
    {
      field: 'planId',
      title: '排班计划',
      minWidth: 150,
      slots: { default: 'planId' },
    },
    {
      field: 'teamId',
      title: '班组',
      minWidth: 120,
      slots: { default: 'teamId' },
    },
    {
      field: 'userId',
      title: '员工',
      minWidth: 100,
      slots: { default: 'userId' },
    },
    { field: 'shiftName', title: '班次名称', minWidth: 110 },
    { field: 'startTime', title: '开始时间', width: 100 },
    { field: 'endTime', title: '结束时间', width: 100 },
    {
      field: 'source',
      title: '来源',
      width: 110,
      slots: { default: 'source' },
    },
    { field: 'remark', title: '备注', minWidth: 140 },
    {
      title: '操作',
      width: 180,
      fixed: 'right',
      slots: {
        default: 'actions',
      },
    },
  ];
}
src/views/mes/cal/scheduleDetail/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,183 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesCalScheduleDetailApi } from '#/api/mes/cal/scheduleDetail';
import { onMounted, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Tag } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getDetailPage } from '#/api/mes/cal/scheduleDetail';
import {
  fetchPlanOptions,
  fetchTeamOptions,
  fetchUserOptions,
  SOURCE_MANUAL,
  useGridColumns,
  useGridFormSchema,
} from './data';
import CheckView from './modules/check-view.vue';
import Form from './modules/form.vue';
import GenerateForm from './modules/generate-form.vue';
const [FormModal, formModalApi] = useVbenModal({
  connectedComponent: Form,
  destroyOnClose: true,
});
const [GenerateModal, generateModalApi] = useVbenModal({
  connectedComponent: GenerateForm,
  destroyOnClose: true,
});
const [CheckModal, checkModalApi] = useVbenModal({
  connectedComponent: CheckView,
  destroyOnClose: true,
});
/** è®¡åˆ’/班组/员工编号 -> åç§° çš„æ˜ å°„,用于列表回显 */
const planNameMap = ref<Record<number, string>>({});
const teamNameMap = ref<Record<number, string>>({});
const userNameMap = ref<Record<number, string>>({});
onMounted(async () => {
  const [plans, teams, users] = await Promise.all([
    fetchPlanOptions(),
    fetchTeamOptions(),
    fetchUserOptions(),
  ]);
  planNameMap.value = Object.fromEntries(
    plans.map((item) => [item.value, item.label]),
  );
  teamNameMap.value = Object.fromEntries(
    teams.map((item) => [item.value, item.label]),
  );
  userNameMap.value = Object.fromEntries(
    users.map((item) => [item.value, item.label]),
  );
});
/** åˆ·æ–°è¡¨æ ¼ */
function handleRefresh() {
  gridApi.query();
}
/** ç”ŸæˆæŽ’班明细 */
function handleGenerate() {
  generateModalApi.open();
}
/** è°ƒæ•´æŽ’班明细 */
function handleEdit(row: MesCalScheduleDetailApi.Detail) {
  formModalApi.setData({ row }).open();
}
/** åˆè§„校验(默认不带人员,弹窗内自选) */
function handleCheck() {
  checkModalApi.open();
}
/** åˆè§„校验指定员工 */
function handleCheckUser(row: MesCalScheduleDetailApi.Detail) {
  checkModalApi.setData({ userId: row.userId }).open();
}
const [Grid, gridApi] = useVbenVxeGrid({
  formOptions: {
    schema: useGridFormSchema(),
  },
  gridOptions: {
    columns: useGridColumns(),
    height: 'auto',
    keepSource: true,
    proxyConfig: {
      ajax: {
        query: async ({ page }, formValues) => {
          const { day, ...rest } = formValues as { day?: string[] };
          return await getDetailPage({
            pageNo: page.currentPage,
            pageSize: page.pageSize,
            ...(rest as MesCalScheduleDetailApi.DetailPageParam),
            startDay: day?.[0],
            endDay: day?.[1],
          });
        },
      },
    },
    rowConfig: {
      keyField: 'id',
      isHover: true,
    },
    toolbarConfig: {
      refresh: true,
      search: true,
    },
  } as VxeTableGridOptions<MesCalScheduleDetailApi.Detail>,
});
</script>
<template>
  <Page auto-content-height>
    <FormModal @success="handleRefresh" />
    <GenerateModal @success="handleRefresh" />
    <CheckModal />
    <Grid table-title="员工排班明细列表">
      <template #toolbar-tools>
        <TableAction
          :actions="[
            {
              label: '生成排班明细',
              type: 'primary',
              icon: ACTION_ICON.ADD,
              auth: ['mes:cal-schedule-detail:generate'],
              onClick: handleGenerate,
            },
            {
              label: '合规校验',
              type: 'primary',
              icon: ACTION_ICON.SEARCH,
              auth: ['mes:cal-schedule-detail:query'],
              onClick: handleCheck,
            },
          ]"
        />
      </template>
      <template #planId="{ row }">
        {{ planNameMap[row.planId] ?? row.planId }}
      </template>
      <template #teamId="{ row }">
        {{ teamNameMap[row.teamId] ?? row.teamId }}
      </template>
      <template #userId="{ row }">
        {{ userNameMap[row.userId] ?? row.userId }}
      </template>
      <template #source="{ row }">
        <Tag :color="row.source === SOURCE_MANUAL ? 'orange' : 'blue'">
          {{ row.source === SOURCE_MANUAL ? '人工调整' : '自动生成' }}
        </Tag>
      </template>
      <template #actions="{ row }">
        <TableAction
          :actions="[
            {
              label: '调整',
              type: 'link',
              icon: ACTION_ICON.EDIT,
              auth: ['mes:cal-schedule-detail:update'],
              onClick: handleEdit.bind(null, row),
            },
            {
              label: '合规校验',
              type: 'link',
              auth: ['mes:cal-schedule-detail:query'],
              onClick: handleCheckUser.bind(null, row),
            },
          ]"
        />
      </template>
    </Grid>
  </Page>
</template>
src/views/mes/cal/scheduleDetail/modules/check-view.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,162 @@
<script lang="ts" setup>
import type { MesCalScheduleDetailApi } from '#/api/mes/cal/scheduleDetail';
import { onMounted, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import dayjs from 'dayjs';
import {
  Button,
  Empty,
  Form,
  FormItem,
  message,
  RangePicker,
  Select,
  Spin,
  Table,
  Tag,
} from 'ant-design-vue';
import { checkCompliance } from '#/api/mes/cal/scheduleDetail';
import { getRangePickerDefaultProps } from '#/utils';
import { fetchUserOptions, issueTypeMap, type OptionItem } from '../data';
const userId = ref<number>();
const day = ref<string[]>();
const userOptions = ref<OptionItem[]>([]);
const loading = ref(false);
const checked = ref(false);
const items = ref<MesCalScheduleDetailApi.CheckItem[]>([]);
/** æ‰§è¡Œåˆè§„校验 */
async function doCheck() {
  if (!userId.value) {
    message.warning('请选择员工');
    return;
  }
  loading.value = true;
  try {
    items.value = await checkCompliance({
      userId: userId.value,
      startDay: day.value?.[0],
      endDay: day.value?.[1],
    });
    checked.value = true;
  } finally {
    loading.value = false;
  }
}
const columns = [
  {
    title: '日期',
    dataIndex: 'day',
    key: 'day',
    width: 110,
    customRender: ({ text }: { text?: string }) =>
      text ? dayjs(text).format('YYYY-MM-DD') : '-',
  },
  { title: '班次', dataIndex: 'shiftName', key: 'shiftName', width: 100 },
  {
    title: '时间',
    key: 'time',
    width: 120,
    customRender: ({ record }: { record: MesCalScheduleDetailApi.CheckItem }) =>
      `${record.startTime ?? '-'} ~ ${record.endTime ?? '-'}`,
  },
  {
    title: '违规类型',
    dataIndex: 'issueType',
    key: 'issueType',
    width: 100,
  },
  { title: '问题描述', dataIndex: 'issue', key: 'issue' },
];
/** åŒæ—¥å¯èƒ½å¤šç­æ¬¡ï¼Œç”¨è¡Œå·ä½œä¸º key */
function rowIndexKey(_record: MesCalScheduleDetailApi.CheckItem, index?: number) {
  return index ?? 0;
}
const [Modal, modalApi] = useVbenModal({
  showConfirmButton: false,
  cancelText: '关闭',
  onOpenChange(isOpen: boolean) {
    if (!isOpen) {
      checked.value = false;
      items.value = [];
      return;
    }
    // æ”¯æŒä»Žåˆ—表行传入员工编号,自动发起校验
    const data = modalApi.getData<{ userId?: number }>();
    if (data?.userId) {
      userId.value = data.userId;
      doCheck();
    }
  },
});
onMounted(async () => {
  userOptions.value = await fetchUserOptions();
});
</script>
<template>
  <Modal title="员工排班合规校验" class="w-3/5">
    <Form layout="inline" class="mb-2.5">
      <FormItem label="员工">
        <Select
          v-model:value="userId"
          allow-clear
          show-search
          placeholder="请选择员工"
          class="!w-[200px]"
          :options="userOptions"
          :filter-option="
            (input: string, option: OptionItem) =>
              (option?.label ?? '').includes(input)
          "
        />
      </FormItem>
      <FormItem label="日期">
        <RangePicker
          v-model:value="day"
          class="!w-[260px]"
          v-bind="getRangePickerDefaultProps()"
        />
      </FormItem>
      <FormItem>
        <Button type="primary" :loading="loading" @click="doCheck">校验</Button>
      </FormItem>
    </Form>
    <Spin :spinning="loading">
      <Table
        v-if="!checked || items.length > 0"
        size="small"
        :row-key="rowIndexKey"
        :columns="columns"
        :data-source="items"
        :pagination="false"
        :scroll="{ y: 360 }"
      >
        <template #bodyCell="{ column, record }">
          <template v-if="column.key === 'issueType'">
            <Tag v-if="record.issueType" :color="record.issueType === 1 ? 'red' : 'orange'">
              {{ issueTypeMap[record.issueType as number] }}
            </Tag>
            <Tag v-else color="green">合规</Tag>
          </template>
          <template v-else-if="column.key === 'issue'">
            {{ record.issue || '无异常' }}
          </template>
        </template>
      </Table>
      <Empty v-else description="未发现违规排班记录,该员工排班合规" />
    </Spin>
  </Modal>
</template>
src/views/mes/cal/scheduleDetail/modules/form.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,79 @@
<script lang="ts" setup>
import type { MesCalScheduleDetailApi } from '#/api/mes/cal/scheduleDetail';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { updateDetail } from '#/api/mes/cal/scheduleDetail';
import { $t } from '#/locales';
import { useFormSchema } from '../data';
const emit = defineEmits(['success']);
const formData = ref<MesCalScheduleDetailApi.Detail>();
const getTitle = computed(() => {
  return formData.value
    ? `调整排班明细(${formData.value.shiftName ?? ''})`
    : '调整排班明细';
});
const [Form, formApi] = useVbenForm({
  commonConfig: {
    componentProps: {
      class: 'w-full',
    },
    formItemClass: 'col-span-1',
    labelWidth: 90,
  },
  layout: 'horizontal',
  schema: useFormSchema(),
  showDefaultActions: false,
});
const [Modal, modalApi] = useVbenModal({
  async onConfirm() {
    const { valid } = await formApi.validate();
    if (!valid) {
      return;
    }
    modalApi.lock();
    try {
      const data = (await formApi.getValues()) as MesCalScheduleDetailApi.Detail;
      await updateDetail(data);
      await modalApi.close();
      emit('success');
      message.success($t('ui.actionMessage.operationSuccess'));
    } finally {
      modalApi.unlock();
    }
  },
  async onOpenChange(isOpen: boolean) {
    if (!isOpen) {
      formData.value = undefined;
      return;
    }
    // åŠ è½½æ•°æ®
    const data = modalApi.getData<{ row: MesCalScheduleDetailApi.Detail }>();
    formData.value = data.row;
    await formApi.setValues({
      id: data.row.id,
      shiftId: data.row.shiftId,
      shiftName: data.row.shiftName,
      startTime: data.row.startTime,
      endTime: data.row.endTime,
      remark: data.row.remark,
    });
  },
});
</script>
<template>
  <Modal :title="getTitle" class="w-2/5">
    <Form class="mx-4" />
  </Modal>
</template>
src/views/mes/cal/scheduleDetail/modules/generate-form.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,56 @@
<script lang="ts" setup>
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { generateDetails } from '#/api/mes/cal/scheduleDetail';
import { $t } from '#/locales';
import { useGenerateFormSchema } from '../data';
const emit = defineEmits(['success']);
const [Form, formApi] = useVbenForm({
  commonConfig: {
    componentProps: {
      class: 'w-full',
    },
    formItemClass: 'col-span-1',
    labelWidth: 90,
  },
  layout: 'horizontal',
  schema: useGenerateFormSchema(),
  showDefaultActions: false,
});
const [Modal, modalApi] = useVbenModal({
  async onConfirm() {
    const { valid } = await formApi.validate();
    if (!valid) {
      return;
    }
    modalApi.lock();
    try {
      const { planId } = await formApi.getValues();
      await generateDetails(planId as number);
      await modalApi.close();
      emit('success');
      message.success('生成成功');
    } finally {
      modalApi.unlock();
    }
  },
  async onOpenChange(isOpen: boolean) {
    if (!isOpen) {
      await formApi.resetForm();
    }
  },
});
</script>
<template>
  <Modal title="生成员工排班明细" class="w-2/5">
    <Form class="mx-4" />
  </Modal>
</template>