feat(mes): 新增员工排班明细页面,修复计划排程菜单404
- 新增 API 封装 schedule-detail 分页/列表/生成/调整/合规校验五个接口
- 新增排班明细列表页:计划/班组/员工/日期搜索,来源 Tag 回显
- 新增生成排班明细、人工调整、合规校验三个弹窗
Co-Authored-By: Claude <noreply@anthropic.com>
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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 }, |
| | | ); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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', |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |