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