From 749cb2f4ad815c5a68c19fe961d5ca2149a84dd4 Mon Sep 17 00:00:00 2001
From: liu <2021943741@qq.com>
Date: 星期四, 17 九月 2026 11:28:54 +0800
Subject: [PATCH] feat(mes): 新增员工排班明细页面,修复计划排程菜单404
---
src/views/mes/cal/scheduleDetail/data.ts | 252 ++++++++++++++++++
src/views/mes/cal/scheduleDetail/modules/generate-form.vue | 56 ++++
src/views/mes/cal/scheduleDetail/modules/check-view.vue | 162 +++++++++++
src/views/mes/cal/scheduleDetail/modules/form.vue | 79 +++++
src/api/mes/cal/scheduleDetail/index.ts | 86 ++++++
src/views/mes/cal/scheduleDetail/index.vue | 183 +++++++++++++
6 files changed, 818 insertions(+), 0 deletions(-)
diff --git a/src/api/mes/cal/scheduleDetail/index.ts b/src/api/mes/cal/scheduleDetail/index.ts
new file mode 100644
index 0000000..0fb68e1
--- /dev/null
+++ b/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; // 缁撴潫鏃堕棿锛圚H: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; // 缁撴潫鏃堕棿锛圚H: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 },
+ );
+}
diff --git a/src/views/mes/cal/scheduleDetail/data.ts b/src/views/mes/cal/scheduleDetail/data.ts
new file mode 100644
index 0000000..64d47dc
--- /dev/null
+++ b/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',
+ },
+ },
+ ];
+}
diff --git a/src/views/mes/cal/scheduleDetail/index.vue b/src/views/mes/cal/scheduleDetail/index.vue
new file mode 100644
index 0000000..114d10e
--- /dev/null
+++ b/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>
diff --git a/src/views/mes/cal/scheduleDetail/modules/check-view.vue b/src/views/mes/cal/scheduleDetail/modules/check-view.vue
new file mode 100644
index 0000000..04a8593
--- /dev/null
+++ b/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>
diff --git a/src/views/mes/cal/scheduleDetail/modules/form.vue b/src/views/mes/cal/scheduleDetail/modules/form.vue
new file mode 100644
index 0000000..a745ab1
--- /dev/null
+++ b/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>
diff --git a/src/views/mes/cal/scheduleDetail/modules/generate-form.vue b/src/views/mes/cal/scheduleDetail/modules/generate-form.vue
new file mode 100644
index 0000000..7799cc4
--- /dev/null
+++ b/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>
--
Gitblit v1.9.3