云
昨天 12250a63a8f65a37451fd49392a3cc90facbceaa
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import type { MesProBagCodeApi } from '#/api/mes/pro/bagCode';
 
import { onBeforeUnmount, ref } from 'vue';
 
import { message } from 'ant-design-vue';
 
import {
  cancelBagCodeGenerateTask,
  getBagCodeGenerateTask,
} from '#/api/mes/pro/bagCode';
 
/** 弹窗最小接口,避免依赖内部 ModalApi 类型 */
interface GenerateModalApi {
  close: () => Promise<void>;
  setState: (state: {
    showCancelButton?: boolean;
    showConfirmButton?: boolean;
  }) => unknown;
}
 
interface GenerateTaskCallbacks {
  /** 生成成功(状态 30),调用方刷新列表 */
  onSuccess: () => void;
  /** 生成失败(状态 50),调用方更换幂等键后允许重新提交 */
  onFailed?: () => void;
}
 
/** 终态:已完成 / 已取消 / 已失败 */
const FINAL_STATUSES = new Set([30, 40, 50]);
 
export function isFinalTaskStatus(status?: number) {
  return status !== undefined && FINAL_STATUSES.has(status);
}
 
/**
 * 大批量袋码后台生成:轮询进度、取消任务,并把弹窗按钮切换为进度展示态。
 */
export function useGenerateTask(
  modalApi: GenerateModalApi,
  callbacks: GenerateTaskCallbacks,
) {
  const task = ref<MesProBagCodeApi.GenerateTask>();
  const canceling = ref(false);
  let timer: ReturnType<typeof setTimeout> | undefined;
 
  function stopPolling() {
    if (timer) clearTimeout(timer);
    timer = undefined;
  }
 
  function restoreButtons() {
    modalApi.setState({ showConfirmButton: true, showCancelButton: true });
  }
 
  /** 重置到可编辑态:关闭弹窗、重新打开、失败重试前调用 */
  function reset() {
    stopPolling();
    task.value = undefined;
    canceling.value = false;
    restoreButtons();
  }
 
  /** 进入进度展示态:隐藏提交/取消按钮,进度区自行提供取消操作 */
  function start(initial: MesProBagCodeApi.GenerateTask) {
    stopPolling();
    task.value = initial;
    modalApi.setState({ showConfirmButton: false, showCancelButton: false });
    void refresh();
  }
 
  async function refresh() {
    if (!task.value) return;
    stopPolling();
    try {
      const current = await getBagCodeGenerateTask(task.value.id);
      task.value = current;
      if (isFinalTaskStatus(current.status)) {
        finish(current);
        return;
      }
    } catch {
      // 一次查询失败不终止轮询,避免网络抖动把进度卡死
    }
    timer = setTimeout(refresh, 2000);
  }
 
  function finish(current: MesProBagCodeApi.GenerateTask) {
    if (current.status === 30) {
      message.success(
        `生成完成:共 ${current.totalCount ?? 0} 条袋码` +
          (current.palletCount
            ? `,并自动生成 ${current.palletCount} 个托盘`
            : ''),
      );
      callbacks.onSuccess();
      void modalApi.close();
      return;
    }
    if (current.status === 40) {
      message.info('生成任务已取消,已生成的数据已清理');
      void modalApi.close();
      return;
    }
    // 失败:回到可编辑态,由调用方更换 requestId 后重新提交
    message.error(current.errorMsg ?? '生成失败,请重新提交');
    task.value = undefined;
    restoreButtons();
    callbacks.onFailed?.();
  }
 
  async function cancel() {
    if (!task.value || canceling.value) return;
    canceling.value = true;
    try {
      await cancelBagCodeGenerateTask(task.value.id);
      message.info('已申请取消,正在停止并清理已生成数据');
    } catch {
      message.error('取消生成失败,请稍后重试');
    } finally {
      canceling.value = false;
    }
  }
 
  onBeforeUnmount(stopPolling);
 
  return {
    task,
    canceling,
    cancel,
    reset,
    stopPolling,
    start,
  };
}