刘光辉
4 小时以前 cf11e63b659afcc9792ab3e395f1b45d844431a8
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
<script setup lang="ts">
import type { DevApprovalProgress } from '#/api/x/development/approval';
 
import { onBeforeUnmount, ref, watch } from 'vue';
 
import { useMessage } from '@jnpf/hooks';
 
import { getDevApprovalRun, inspectDevApproval, startDevApprovalRun, stopDevApprovalRun } from '#/api/x/development/approval';
import { onlineUtils } from '#/utils/jnpf';
 
const props = defineProps<{ disabled?: boolean; flowState?: number | string; taskId?: string }>();
const emit = defineEmits<{ busy: [value: boolean]; finished: [] }>();
const { createMessage } = useMessage();
const available = ref(false);
const availabilityError = ref('');
const checking = ref(false);
const running = ref(false);
const progress = ref('');
const runId = ref('');
const stopping = ref(false);
let generation = 0;
let timer: ReturnType<typeof setTimeout> | undefined;
 
function cancelPolling() {
  if (timer) clearTimeout(timer);
  timer = undefined;
}
function setBusy(value: boolean) {
  running.value = value;
  emit('busy', value);
}
function schedulePoll(taskId: string, version: number) {
  cancelPolling();
  timer = setTimeout(() => poll(taskId, version), 2000);
}
function applyProgress(data: DevApprovalProgress, taskId: string, version: number, notify: boolean) {
  if (version !== generation) return;
  const wasRunning = running.value;
  runId.value = data.runId || '';
  stopping.value = data.status === 'STOPPING';
  const active = data.status === 'RUNNING' || data.status === 'STOPPING';
  setBusy(active);
  progress.value = `${stopping.value ? '当前节点完成后停止' : '后台审批'} · 已完成 ${data.completed} 个待办${data.nodeName ? ` · ${data.nodeName}` : ''}`;
  if (active) {
    schedulePoll(taskId, version);
  } else {
    cancelPolling();
    if (data.status === 'PASSED') available.value = false;
    if (notify && data.status === 'IDLE' && wasRunning) {
      createMessage.warning('未找到本轮后台任务记录,请刷新流程核对已完成节点后重新签名;服务重启不会自动续跑。', 10);
      emit('finished');
    }
    if (notify && data.status !== 'IDLE') {
      if (data.status === 'PASSED') createMessage.success('流程已审核通过;文件正式生效仍按原生效日期执行');
      else createMessage.warning(data.message || '后台审批已停止,请查看流程状态', 10);
      emit('finished');
    }
  }
}
async function poll(taskId: string, version: number) {
  try {
    const response = await getDevApprovalRun(taskId);
    applyProgress(response.data, taskId, version, true);
  } catch {
    if (version !== generation) return;
    progress.value = '进度暂时不可用,正在重新连接;后台任务不受页面连接影响';
    schedulePoll(taskId, version);
  }
}
async function checkAvailability() {
  const taskId = props.taskId;
  const version = ++generation;
  cancelPolling();
  available.value = false;
  availabilityError.value = '';
  checking.value = false;
  runId.value = '';
  stopping.value = false;
  progress.value = '';
  if (running.value) setBusy(false);
  if (!import.meta.env.DEV || !taskId || taskId === '0' || String(props.flowState) !== '1') return;
  checking.value = true;
  try {
    const result = await inspectDevApproval(taskId);
    if (version !== generation) return;
    available.value = result.data.enabled && result.data.status === 'READY';
    if (available.value) {
      const response = await getDevApprovalRun(taskId);
      applyProgress(response.data, taskId, version, false);
    }
  } catch (error) {
    if (version === generation) availabilityError.value = error instanceof Error ? error.message : '请求失败';
  } finally {
    if (version === generation) checking.value = false;
  }
}
watch(() => [props.taskId, props.flowState], checkAvailability, { immediate: true });
onBeforeUnmount(() => {
  generation += 1;
  cancelPolling();
  // Deliberately do not send a stop request: the server owns execution.
});
async function execute() {
  const taskId = props.taskId;
  const version = generation;
  if (!taskId || running.value || props.disabled || String(props.flowState) !== '1') return;
  setBusy(true);
  let submitted = false;
  try {
    progress.value = '等待电子签名';
    const signId = await new Promise<string | undefined>((resolve) => {
      onlineUtils.sign({
        title: '开发一键审批电子签名(本轮复用)',
        isFaceToFace: false,
        metaData: {
          biz_button: '开发一键审批',
          biz_data: [taskId],
          biz_module: 'workflow-dev-approval',
          biz_title: '本流程后续审批复用此电子签名和签名用户',
          is_biz_form: true,
        },
        onSubmit: (data) => resolve(data?.biz_sign),
        onCancel: () => resolve(undefined),
      });
    });
    if (!signId || version !== generation) return;
    submitted = true;
    const response = await startDevApprovalRun(taskId, signId);
    if (version !== generation) return;
    applyProgress(response.data, taskId, version, true);
  } catch (error) {
    if (version !== generation) return;
    createMessage.warning(`启动结果暂未确认:${error instanceof Error ? error.message : '请求失败'}。正在查询后台状态,请勿重复启动。`, 10);
    if (submitted) schedulePoll(taskId, version);
  } finally {
    if (!submitted && version === generation) setBusy(false);
  }
}
async function stop() {
  if (!props.taskId || !runId.value || stopping.value) return;
  const version = generation;
  try {
    const response = await stopDevApprovalRun(props.taskId, runId.value);
    applyProgress(response.data, props.taskId, version, true);
  } catch (error) {
    if (version !== generation) return;
    createMessage.warning(`停止请求未确认:${error instanceof Error ? error.message : '请求失败'},请查看后台进度`, 10);
  }
}
</script>
 
<template>
  <a-space v-if="availabilityError || checking" class="mr-[10px]">
    <span :title="availabilityError">{{ checking ? '正在检查一键审核' : '一键审核入口检查失败' }}</span>
    <a-button v-if="availabilityError" size="small" :disabled="disabled" @click="checkAvailability">重试</a-button>
  </a-space>
  <a-space v-if="available" class="mr-[10px]">
    <a-button :loading="running" :disabled="disabled && !running" @click="execute">开发一键审核</a-button>
    <template v-if="running">
      <span>{{ progress }}</span>
      <a-button v-if="runId" size="small" :disabled="stopping" @click="stop">停止后续审核</a-button>
    </template>
  </a-space>
</template>