From a2d9fcb259621829bf4ec9680b5dbeac95ab5805 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期四, 15 十月 2026 16:19:54 +0800
Subject: [PATCH] fix(crm): 销售目标达成分析默认查当前季度
---
src/views/mes/pro/task/modules/auto-schedule.vue | 180 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 180 insertions(+), 0 deletions(-)
diff --git a/src/views/mes/pro/task/modules/auto-schedule.vue b/src/views/mes/pro/task/modules/auto-schedule.vue
new file mode 100644
index 0000000..69d81dd
--- /dev/null
+++ b/src/views/mes/pro/task/modules/auto-schedule.vue
@@ -0,0 +1,180 @@
+<script setup lang="ts">
+import type { BatchSchedulePreview, BatchScheduleRequest } from '#/api/mes/pro/task';
+import { computed, ref, watch } from 'vue';
+import { useVbenModal } from '@vben/common-ui';
+import { Alert, Button, Checkbox, DatePicker, Descriptions, Table, Tag, message } from 'ant-design-vue';
+import dayjs from 'dayjs';
+import { executeBatchSchedule, previewBatchSchedule } from '#/api/mes/pro/task';
+
+const emit = defineEmits(['success']);
+const ids = ref<number[]>([]);
+function defaultStartTime() {
+ let value = dayjs().add(5, 'minute').startOf('minute');
+ if (value.hour() >= 18) value = value.add(1, 'day').hour(9).minute(0).second(0);
+ else if (value.hour() < 9) value = value.hour(9).minute(0).second(0);
+ while (value.day() === 0 || value.day() === 6) value = value.add(1, 'day').hour(9).minute(0).second(0);
+ return value;
+}
+const startTime = ref(defaultStartTime());
+const preview = ref<BatchSchedulePreview>();
+const loading = ref(false);
+const executing = ref(false);
+const acceptLate = ref(false);
+const errorText = ref('');
+let sequence = 0;
+let open = false;
+const canConfirm = computed(() => !!preview.value?.canExecute && !loading.value && !executing.value
+ && (!preview.value.warnings.length || acceptLate.value));
+
+const [Modal, modalApi] = useVbenModal({
+ confirmText: '纭鎺掍骇',
+ confirmDisabled: true,
+ onBeforeClose: () => !executing.value,
+ onConfirm: execute,
+ async onOpenChange(isOpen) {
+ open = isOpen;
+ sequence++;
+ if (!isOpen) {
+ preview.value = undefined;
+ ids.value = [];
+ loading.value = false;
+ errorText.value = '';
+ return;
+ }
+ ids.value = modalApi.getData<{ workOrderIds: number[] }>().workOrderIds;
+ startTime.value = defaultStartTime();
+ await calculate();
+ },
+});
+
+watch(canConfirm, (value) => modalApi.setState({ confirmDisabled: !value }));
+watch(startTime, () => {
+ sequence++;
+ preview.value = undefined;
+ acceptLate.value = false;
+ loading.value = false;
+ errorText.value = '';
+}, { flush: 'sync' });
+
+function request(): BatchScheduleRequest {
+ return { workOrderIds: [...ids.value], startTime: startTime.value.format('YYYY-MM-DD HH:mm:ss') };
+}
+
+async function calculate() {
+ if (executing.value || !startTime.value || !ids.value.length) return;
+ const current = ++sequence;
+ preview.value = undefined;
+ acceptLate.value = false;
+ errorText.value = '';
+ loading.value = true;
+ try {
+ const result = await previewBatchSchedule(request());
+ if (open && current === sequence) preview.value = result;
+ } catch (error) {
+ if (open && current === sequence) errorText.value = error instanceof Error ? error.message : '棰勮澶辫触锛岃绋嶅悗閲嶈瘯';
+ } finally {
+ if (current === sequence) loading.value = false;
+ }
+}
+
+async function execute() {
+ if (!canConfirm.value || !preview.value) return;
+ executing.value = true;
+ errorText.value = '';
+ modalApi.lock();
+ try {
+ const result = await executeBatchSchedule({ ...request(), previewHash: preview.value.previewHash,
+ previewTime: preview.value.previewTime, allowLate: acceptLate.value });
+ message.success(`鎺掍骇鎴愬姛锛�${ids.value.length} 涓伐鍗曪紝鐢熸垚 ${result.taskIds.length} 涓换鍔);
+ executing.value = false;
+ await modalApi.close();
+ emit('success');
+ } catch (error) {
+ errorText.value = error instanceof Error ? error.message : '鎵ц缁撴灉鏈‘璁わ紝璇峰埛鏂板垪琛ㄥ苟閲嶆柊棰勮';
+ // 璇锋眰澶辫触鎴栫粨鏋滄湭纭鍚庡繀椤婚噸绠楋紱鏃ч瑙堜笉鑳界洿鎺ュ啀娆℃彁浜ゃ��
+ preview.value = undefined;
+ acceptLate.value = false;
+ emit('success'); // 鍒锋柊宸ュ崟鍜岀敇鐗瑰浘锛屾牳瀵硅秴鏃惰姹傛槸鍚﹀凡缁忔彁浜ゃ��
+ } finally {
+ executing.value = false;
+ modalApi.unlock();
+ }
+}
+
+const orderColumns = [
+ { title: '宸ュ崟缂栫爜', dataIndex: 'workOrderCode', width: 170 },
+ { title: '鐢熶骇鏁伴噺', dataIndex: 'quantity', width: 100 },
+ { title: '鏂板浠诲姟', dataIndex: 'taskCount', width: 90 },
+ { title: '闇�姹傛棩鏈�', dataIndex: 'requestDate', width: 120 },
+ { title: '棰勮瀹屾垚', dataIndex: 'endTime', width: 175 },
+ { title: '鏍¢獙缁撴灉', dataIndex: 'error', width: 300 },
+];
+const taskColumns = [
+ { title: '宸ュ崟', dataIndex: 'workOrderCode', width: 160 },
+ { title: '宸ュ簭', dataIndex: 'processName', width: 130 },
+ { title: '宸ヤ綔绔�', dataIndex: 'workstationName', width: 150 },
+ { title: '璁惧', dataIndex: 'machineryNames', width: 180 },
+ { title: '鏈鏁伴噺', dataIndex: 'quantity', width: 100 },
+ { title: '灏忔椂浜ц兘', dataIndex: 'hourlyCapacity', width: 100 },
+ { title: '宸ユ椂(h)', dataIndex: 'duration', width: 85 },
+ { title: '寮�宸ユ椂闂�', dataIndex: 'startTime', width: 180 },
+ { title: '瀹屾垚鏃堕棿', dataIndex: 'endTime', width: 180 },
+];
+</script>
+
+<template>
+ <Modal title="涓�閿帓浜�" class="w-[min(96vw,1200px)]">
+ <div class="space-y-5 px-1">
+ <Alert type="info" show-icon message="鍏堟牎楠屼笌棰勮锛屽啀纭鎺掍骇"
+ description="鎸変氦鏈熶紭鍏堝強宸ュ簭椤哄簭瀹夋帓宸ヤ綔绔欎笌璁惧锛涙瘡澶�09:00鈥�18:00锛屾帓闄ゅ懆鏈拰宸查厤缃妭鍋囨棩锛岄潪宸ヤ綔鏃堕棿鑷姩椤哄欢銆傚凡閮ㄥ垎鎺掍骇鐨勫伐鍗曞彧琛ラ綈鍓╀綑鏁伴噺锛屾渶澶�30涓伐鍗曘��200涓柊浠诲姟銆�" />
+ <div class="flex flex-wrap items-end gap-3">
+ <label class="flex flex-col gap-2">
+ <span class="font-medium">鏈�鏃╁紑宸ユ椂闂�</span>
+ <DatePicker v-model:value="startTime" show-time :allow-clear="false" :disabled="executing"
+ format="YYYY-MM-DD HH:mm:ss" :disabled-date="(date) => date.endOf('day').isBefore(dayjs())" />
+ </label>
+ <Button :loading="loading" :disabled="executing" @click="calculate">
+ {{ preview ? '閲嶆柊璁$畻棰勮' : '鐢熸垚鎺掍骇棰勮' }}
+ </Button>
+ <span class="text-sm text-muted-foreground">宸查�夋嫨 {{ ids.length }} 涓伐鍗�</span>
+ </div>
+ <Alert v-if="errorText" type="error" show-icon :message="errorText" />
+ <template v-if="preview">
+ <Descriptions :column="{ xs: 1, sm: 3 }" size="small" bordered>
+ <Descriptions.Item label="宸ュ崟鏁�">{{ preview.orders.length }}</Descriptions.Item>
+ <Descriptions.Item label="鏂板浠诲姟鏁�">{{ preview.tasks.length }}</Descriptions.Item>
+ <Descriptions.Item label="棰勮鏈夋晥鏈�">15鍒嗛挓锛屾彁浜ゆ椂閲嶆柊鏍¢獙</Descriptions.Item>
+ </Descriptions>
+ <Alert v-if="!preview.canExecute" type="error" show-icon message="閮ㄥ垎宸ュ崟鏈�氳繃鏍¢獙锛屾湰鎵规涓嶈兘鎻愪氦"
+ description="璇锋牴鎹笅鏂规牎楠岀粨鏋滀慨姝e伐鍗曘�佽澶囨垨宸叉湁浠诲姟鐨勯厤缃紝鍐嶉噸鏂拌绠楅瑙堛��" />
+ <div>
+ <h3 class="mb-2 font-medium">宸ュ崟鏍¢獙</h3>
+ <Table :columns="orderColumns" :data-source="preview.orders" row-key="workOrderId"
+ :pagination="false" size="small" :scroll="{ x: 1055 }">
+ <template #bodyCell="{ column, record, text }">
+ <template v-if="column.dataIndex === 'error'">
+ <span v-if="record.error" class="text-destructive">{{ record.error }}</span>
+ <Tag v-else :color="record.late ? 'orange' : 'green'">{{ record.late ? '鏈夊欢鏈熼闄�' : '閫氳繃' }}</Tag>
+ </template>
+ <template v-else-if="column.dataIndex === 'requestDate'">{{ text ? String(text).slice(0, 10) : '鏈寚瀹�' }}</template>
+ </template>
+ </Table>
+ </div>
+ <div v-if="preview.tasks.length">
+ <h3 class="mb-2 font-medium">浠诲姟瀹夋帓</h3>
+ <Table :columns="taskColumns" :data-source="preview.tasks"
+ :row-key="(row) => `${row.workOrderId}-${row.processId}`" :pagination="{ pageSize: 10, showSizeChanger: false }"
+ size="small" :scroll="{ x: 1365 }" />
+ </div>
+ <Alert v-if="preview.warnings.length" type="warning" show-icon message="璇锋牳瀵逛氦鏈熼闄�">
+ <template #description>
+ <p v-for="warning in preview.warnings" :key="warning">{{ warning }}</p>
+ <Checkbox v-model:checked="acceptLate" :disabled="executing" class="mt-2">
+ 鎴戝凡鏍稿骞舵帴鍙椾互涓婁氦鏈熷欢鏈熼闄�
+ </Checkbox>
+ </template>
+ </Alert>
+ </template>
+ </div>
+ </Modal>
+</template>
--
Gitblit v1.9.3