Commit b6e4a0183c5e96681f5e60264987096143a7bd32

Authored by 王彪总
1 parent 40ba5f9a

业务线 小程序不用写死,从后端接口获取

api/work-order-manage/work-order-manage.js
... ... @@ -112,3 +112,20 @@ export const workorderCreate = (data) => {
112 112  
113 113  
114 114  
  115 +
  116 +
  117 +/**
  118 + * 获得当前任务可用的统一节点动作(工作流配置化)
  119 + * @returns {Promise}
  120 + */
  121 +export const getTaskActions = (taskId) => {
  122 + return get(`/app-api/workorder/task/${taskId}/actions`);
  123 +};
  124 +
  125 +/**
  126 + * 执行统一节点动作(工作流配置化)
  127 + * @returns {Promise}
  128 + */
  129 +export const executeTaskAction = (data) => {
  130 + return post('/app-api/workorder/task/action', data);
  131 +};
... ...
components/unified-task-actions/unified-task-actions.vue 0 → 100644
  1 +<template>
  2 + <up-popup
  3 + :show="visible"
  4 + mode="bottom"
  5 + round
  6 + closeable
  7 + :safe-area-inset-bottom="true"
  8 + @close="close"
  9 + >
  10 + <view class="unified-action-popup">
  11 + <view class="unified-action-title">{{ current ? current.name : '请选择操作' }}</view>
  12 +
  13 + <!-- 第一步:动作列表 -->
  14 + <view v-if="!current" class="unified-action-list">
  15 + <up-button
  16 + v-for="action in actions"
  17 + :key="action.code"
  18 + :type="getButtonType(action.code)"
  19 + size="large"
  20 + :customStyle="{ marginBottom: '20rpx' }"
  21 + @click="selectAction(action)"
  22 + >
  23 + {{ action.name }}
  24 + </up-button>
  25 + <view v-if="!actions.length" class="unified-action-tip">当前节点暂未配置可执行动作</view>
  26 + </view>
  27 +
  28 + <!-- 第二步:动作表单 -->
  29 + <view v-else class="unified-action-form">
  30 + <view v-if="current.requireAssignee" class="form-item">
  31 + <view class="form-label">处理人</view>
  32 + <up-radio-group v-if="workers.length" v-model="form.nextAssignee" class="worker-list">
  33 + <up-radio
  34 + v-for="worker in workers"
  35 + :key="worker.id"
  36 + :name="String(worker.id)"
  37 + :label="worker.nickname"
  38 + />
  39 + </up-radio-group>
  40 + <view v-else class="unified-action-tip">
  41 + {{ loadingWorkers ? '加载处理人中...' : '暂无可选处理人' }}
  42 + </view>
  43 + </view>
  44 +
  45 + <view class="form-item">
  46 + <view class="form-label">{{ reasonRequired ? '原因/备注(必填)' : '原因/备注' }}</view>
  47 + <up-textarea
  48 + v-model.trim="form.reason"
  49 + :placeholder="reasonRequired ? '请输入原因/备注(必填)' : '请输入原因/备注(选填)'"
  50 + :count="200"
  51 + maxlength="200"
  52 + height="120"
  53 + />
  54 + </view>
  55 +
  56 + <view v-if="current.requireAttachment" class="form-item">
  57 + <view class="form-label">附件(必填)</view>
  58 + <up-upload
  59 + :file-list="attachImgs.rawImgList.value || []"
  60 + @after-read="attachImgs.uploadImgs"
  61 + @delete="attachImgs.deleteImg"
  62 + multiple
  63 + width="70"
  64 + height="70"
  65 + :max-count="attachImgs.uploadConfig.maxCount"
  66 + :upload-text="attachImgs.uploadConfig.uploadText"
  67 + :size-type="attachImgs.uploadConfig.sizeType"
  68 + />
  69 + </view>
  70 +
  71 + <view class="unified-action-btns">
  72 + <up-button type="info" size="large" @click="backToList">返回</up-button>
  73 + <up-button type="primary" size="large" :loading="submitting" @click="submit">确定</up-button>
  74 + </view>
  75 + </view>
  76 + </view>
  77 + </up-popup>
  78 +</template>
  79 +
  80 +<script setup>
  81 +import { ref, reactive, computed } from 'vue'
  82 +import {
  83 + executeTaskAction,
  84 + getYlWorkersPage
  85 +} from '@/api/work-order-manage/work-order-manage'
  86 +import { useUploadImgs } from '@/common/utils/useUploadImgs'
  87 +
  88 +const emit = defineEmits(['success'])
  89 +
  90 +const visible = ref(false)
  91 +const actions = ref([])
  92 +const current = ref(null)
  93 +const currentItem = ref(null)
  94 +const submitting = ref(false)
  95 +const workers = ref([])
  96 +const loadingWorkers = ref(false)
  97 +
  98 +const form = reactive({
  99 + nextAssignee: '',
  100 + reason: ''
  101 +})
  102 +
  103 +const attachImgs = useUploadImgs({
  104 + maxCount: 5,
  105 + uploadText: '选择附件'
  106 +})
  107 +
  108 +const reasonRequired = computed(() => {
  109 + if (!current.value) return false
  110 + return (
  111 + current.value.requireReason === true ||
  112 + ['REJECT', 'RETURN', 'RETURN_PREV', 'TRANSFER', 'DELEGATE'].includes(current.value.code)
  113 + )
  114 +})
  115 +
  116 +/** 打开动作弹窗 */
  117 +const open = async (item, actionList) => {
  118 + currentItem.value = item
  119 + actions.value = actionList || []
  120 + current.value = null
  121 + form.nextAssignee = ''
  122 + form.reason = ''
  123 + attachImgs.clearImgs()
  124 + visible.value = true
  125 +}
  126 +
  127 +const close = () => {
  128 + visible.value = false
  129 + current.value = null
  130 + submitting.value = false
  131 +}
  132 +
  133 +const backToList = () => {
  134 + current.value = null
  135 +}
  136 +
  137 +/** 动作按钮颜色 */
  138 +const getButtonType = (code) => {
  139 + switch (code) {
  140 + case 'APPROVE':
  141 + case 'HANDLE':
  142 + case 'COMPLETE':
  143 + return 'success'
  144 + case 'REJECT':
  145 + return 'danger'
  146 + case 'RETURN':
  147 + case 'RETURN_PREV':
  148 + return 'warning'
  149 + case 'WITHDRAW':
  150 + return 'info'
  151 + default:
  152 + return 'primary'
  153 + }
  154 +}
  155 +
  156 +/** 选择动作,按需加载处理人 */
  157 +const selectAction = async (action) => {
  158 + // 退回动作必须在配置中指定目标节点(小程序端不提供节点选择)
  159 + if (action.code === 'RETURN' && !action.targetNodeKey) {
  160 + uni.showToast({ title: '该退回动作未配置目标节点,请在PC端配置', icon: 'none' })
  161 + return
  162 + }
  163 + current.value = action
  164 + form.nextAssignee = ''
  165 + form.reason = ''
  166 + attachImgs.clearImgs()
  167 + if (action.requireAssignee) {
  168 + loadWorkers()
  169 + }
  170 +}
  171 +
  172 +/** 加载处理人 */
  173 +const loadWorkers = async () => {
  174 + if (workers.value.length > 0) return
  175 + loadingWorkers.value = true
  176 + try {
  177 + const res = await getYlWorkersPage({
  178 + busiLine: currentItem.value?.busiLine,
  179 + pageNo: 1,
  180 + pageSize: 100
  181 + })
  182 + workers.value = res?.list || []
  183 + } catch (e) {
  184 + workers.value = []
  185 + } finally {
  186 + loadingWorkers.value = false
  187 + }
  188 +}
  189 +
  190 +/** 提交动作 */
  191 +const submit = async () => {
  192 + if (!current.value || !currentItem.value) return
  193 + const action = current.value
  194 + const reason = form.reason
  195 + if (reasonRequired.value && !reason) {
  196 + uni.showToast({ title: '请填写原因/备注', icon: 'none' })
  197 + return
  198 + }
  199 + if (action.requireAssignee && !form.nextAssignee) {
  200 + uni.showToast({ title: '请选择处理人', icon: 'none' })
  201 + return
  202 + }
  203 + const attachments = attachImgs.getSuccessImgUrls()
  204 + if (action.requireAttachment && attachments.length === 0) {
  205 + uni.showToast({ title: '请上传附件', icon: 'none' })
  206 + return
  207 + }
  208 +
  209 + submitting.value = true
  210 + uni.showLoading({ title: '提交中...', mask: true })
  211 + try {
  212 + const data = {
  213 + taskId: currentItem.value.taskId,
  214 + actionCode: action.code,
  215 + reason: reason || undefined,
  216 + targetNodeKey: action.targetNodeKey || undefined
  217 + }
  218 + if (action.requireAssignee) {
  219 + data.nextAssignee = Number(form.nextAssignee)
  220 + }
  221 + if (attachments.length > 0) {
  222 + data.attachments = attachments
  223 + }
  224 + await executeTaskAction(data)
  225 + uni.hideLoading()
  226 + uni.showToast({ title: '操作成功', icon: 'success', duration: 1000 })
  227 + visible.value = false
  228 + emit('success', currentItem.value)
  229 + } catch (e) {
  230 + uni.hideLoading()
  231 + // 请求层已统一提示错误信息
  232 + } finally {
  233 + submitting.value = false
  234 + }
  235 +}
  236 +
  237 +defineExpose({ open })
  238 +</script>
  239 +
  240 +<style lang="scss" scoped>
  241 +.unified-action-popup {
  242 + padding: 40rpx 40rpx calc(40rpx + env(safe-area-inset-bottom));
  243 + max-height: 80vh;
  244 + overflow-y: auto;
  245 +}
  246 +
  247 +.unified-action-title {
  248 + font-size: 34rpx;
  249 + font-weight: bold;
  250 + text-align: center;
  251 + margin-bottom: 40rpx;
  252 +}
  253 +
  254 +.unified-action-list {
  255 + display: flex;
  256 + flex-direction: column;
  257 +}
  258 +
  259 +.unified-action-tip {
  260 + color: #909399;
  261 + font-size: 26rpx;
  262 + padding: 20rpx 0;
  263 +}
  264 +
  265 +.unified-action-form .form-item {
  266 + margin-bottom: 30rpx;
  267 +}
  268 +
  269 +.form-label {
  270 + font-size: 28rpx;
  271 + color: #303133;
  272 + margin-bottom: 16rpx;
  273 +}
  274 +
  275 +.worker-list {
  276 + display: flex;
  277 + flex-wrap: wrap;
  278 + gap: 16rpx;
  279 +}
  280 +
  281 +.unified-action-btns {
  282 + display: flex;
  283 + gap: 24rpx;
  284 + margin-top: 20rpx;
  285 +
  286 + ::v-deep .u-button {
  287 + flex: 1;
  288 + }
  289 +}
  290 +</style>
... ...
pages-sub/problem/ai-manage/index.vue
... ... @@ -98,7 +98,7 @@
98 98 <view class="u-body-item u-flex u-body-item-btn-wrap common-justify-between common-item-center mt-20">
99 99 <!-- <up-button type="warning" size="mini" @click="handleReject(item)" v-show="nextStepMap[item.taskKey].backShow">回退</up-button>-->
100 100 <!-- <up-button type="success" size="mini" @click="handleRenew(item)" v-show="nextStepMap[item.taskKey].renewShow">重新提交</up-button>-->
101   - <up-button type="primary" size="large" @click="handleProcess(item)">{{ nextStepMap[item.taskKey].btnText }}</up-button>
  101 + <up-button type="primary" size="large" @click="handleUnifiedProcess(item)">{{ nextStepMap[item.taskKey].btnText }}</up-button>
102 102 <!-- <up-button type="primary" size="large" @click="handleProcess(item)" v-show="nextStepMap[item.taskKey].ysShow">验收</up-button>-->
103 103  
104 104 <up-button type="info" size="large" @click="handleDetail(item)">详情</up-button>
... ... @@ -244,6 +244,9 @@
244 244 </view>
245 245 </view>
246 246 </up-modal>
  247 +
  248 + <!-- 统一节点动作弹窗(工作流配置化,未配置时回退原有逻辑) -->
  249 + <unified-task-actions ref="unifiedActionsRef" @success="handleUnifiedSuccess" />
247 250 </view>
248 251 </template>
249 252  
... ... @@ -260,6 +263,7 @@ import {
260 263 dcyUniversalApproval,
261 264 aiUniversalApproval
262 265 } from '@/api/regional-order-manage/regional-order-manage'
  266 +import { getTaskActions } from '@/api/work-order-manage/work-order-manage'
263 267 import { useUserStore } from '@/pinia/user';
264 268 import { nextStepMap, buzStatusMap } from '@/common/utils/common'
265 269 import { useUploadImgs } from '@/common/utils/useUploadImgs'
... ... @@ -443,6 +447,38 @@ const handleRenew = (item) =&gt; {
443 447 navToOrderPage(pageUrl, { isRenew: 1, tempKey });
444 448 };
445 449  
  450 +// ========== 统一节点动作(工作流配置化) ==========
  451 +const unifiedActionsRef = ref(null)
  452 +
  453 +/** 查询当前任务是否已配置统一动作;未配置或接口异常时返回 null,回退原有逻辑 */
  454 +const queryUnifiedActions = async (item) => {
  455 + if (!item || !item.taskId) return null
  456 + try {
  457 + const data = await getTaskActions(item.taskId)
  458 + if (data && data.configured && Array.isArray(data.actions) && data.actions.length > 0) {
  459 + return data.actions
  460 + }
  461 + } catch (e) {
  462 + console.warn('查询统一节点动作失败,回退原有逻辑', e)
  463 + }
  464 + return null
  465 +}
  466 +
  467 +/** 待办-处理工单:已配置统一动作时走新逻辑,否则回退原有处理 */
  468 +const handleUnifiedProcess = async (item) => {
  469 + const actions = await queryUnifiedActions(item)
  470 + if (actions) {
  471 + unifiedActionsRef.value?.open(item, actions)
  472 + return
  473 + }
  474 + handleProcess(item)
  475 +}
  476 +
  477 +/** 统一动作执行成功后刷新列表 */
  478 +const handleUnifiedSuccess = () => {
  479 + pagingRef.value && pagingRef.value.reload()
  480 +}
  481 +
446 482 // 处理工单核心逻辑
447 483 const handleProcess = async (item) => {
448 484 if (!item) return uni.showToast({title: '工单信息异常', icon: 'none'});
... ...
pages-sub/problem/regional-order-manage/index.vue
... ... @@ -108,9 +108,9 @@
108 108  
109 109 <!-- 待办工单(0) 保留原来的操作按钮区 不变 -->
110 110 <view v-if="activeTab === 0" class="u-body-item u-body-item-btn-wrap u-flex common-justify-between common-item-center mt-20">
111   - <up-button type="warning" size="large" @click="handleReject(item)" v-show="nextStepMap[item.taskKey].backShow">回退</up-button>
  111 + <up-button type="warning" size="large" @click="handleUnifiedReject(item)" v-show="nextStepMap[item.taskKey].backShow">回退</up-button>
112 112 <up-button type="success" size="large" @click="handleRenew(item)" v-show="nextStepMap[item.taskKey].renewShow">重新提交</up-button>
113   - <up-button type="primary" size="large" @click="handleProcess(item)">{{ nextStepMap[item.taskKey].btnText }}</up-button>
  113 + <up-button type="primary" size="large" @click="handleUnifiedProcess(item)">{{ nextStepMap[item.taskKey].btnText }}</up-button>
114 114 <up-button type="info" size="large" @click="handleDetail(item)">详情</up-button>
115 115 </view>
116 116  
... ... @@ -205,6 +205,9 @@
205 205 </view>
206 206 </view>
207 207 </up-modal>
  208 +
  209 + <!-- 统一节点动作弹窗(工作流配置化,未配置时回退原有逻辑) -->
  210 + <unified-task-actions ref="unifiedActionsRef" @success="handleUnifiedSuccess" />
208 211 </view>
209 212 </template>
210 213  
... ... @@ -221,6 +224,7 @@ import {
221 224 dcyUniversalApproval,
222 225 aiUniversalApproval
223 226 } from '@/api/regional-order-manage/regional-order-manage'
  227 +import { getTaskActions } from '@/api/work-order-manage/work-order-manage'
224 228 import { useUserStore } from '@/pinia/user';
225 229 import { nextStepMap, buzStatusMap } from '@/common/utils/common'
226 230 import { useUploadImgs } from '@/common/utils/useUploadImgs'
... ... @@ -419,6 +423,55 @@ const handleRenew = (item) =&gt; {
419 423 };
420 424  
421 425 // 处理工单核心逻辑
  426 +// ========== 统一节点动作(工作流配置化) ==========
  427 +const unifiedActionsRef = ref(null)
  428 +
  429 +/** 查询当前任务是否已配置统一动作;未配置或接口异常时返回 null,回退原有逻辑 */
  430 +const queryUnifiedActions = async (item) => {
  431 + if (!item || !item.taskId) return null
  432 + try {
  433 + const data = await getTaskActions(item.taskId)
  434 + if (data && data.configured && Array.isArray(data.actions) && data.actions.length > 0) {
  435 + return data.actions
  436 + }
  437 + } catch (e) {
  438 + console.warn('查询统一节点动作失败,回退原有逻辑', e)
  439 + }
  440 + return null
  441 +}
  442 +
  443 +/** 待办-处理工单:已配置统一动作时走新逻辑,否则回退原有处理 */
  444 +const handleUnifiedProcess = async (item) => {
  445 + const actions = await queryUnifiedActions(item)
  446 + if (actions) {
  447 + unifiedActionsRef.value?.open(item, actions)
  448 + return
  449 + }
  450 + handleProcess(item)
  451 +}
  452 +
  453 +/** 待办-回退:已配置统一动作时走新逻辑,否则回退原有弹窗 */
  454 +const handleUnifiedReject = async (item) => {
  455 + const actions = await queryUnifiedActions(item)
  456 + if (actions) {
  457 + const rejectActions = actions.filter((action) =>
  458 + ['REJECT', 'RETURN', 'RETURN_PREV'].includes(action.code)
  459 + )
  460 + if (rejectActions.length > 0) {
  461 + unifiedActionsRef.value?.open(item, rejectActions)
  462 + } else {
  463 + uni.showToast({ title: '当前节点未配置回退动作', icon: 'none' })
  464 + }
  465 + return
  466 + }
  467 + handleReject(item)
  468 +}
  469 +
  470 +/** 统一动作执行成功后刷新列表 */
  471 +const handleUnifiedSuccess = () => {
  472 + pagingHandle('reload')
  473 +}
  474 +
422 475 const handleProcess = async (item) => {
423 476 if (!item) return showToast('工单信息异常');
424 477 const stepName = nextStepMap[item.taskKey]?.name;
... ...
pages-sub/problem/work-order-manage/index.vue
... ... @@ -102,7 +102,7 @@
102 102 </view>
103 103 <!-- 操作按钮行 -->
104 104 <view class="u-body-item u-body-item-btn-wrap u-flex common-justify-between common-item-center mt-20">
105   - <up-button type="warning" size="large" @click="handleReject(item)"
  105 + <up-button type="warning" size="large" @click="handleUnifiedReject(item)"
106 106 v-show="nextStepMap[item.taskKey].backShow">回退
107 107 </up-button>
108 108  
... ... @@ -110,7 +110,7 @@
110 110 v-show="nextStepMap[item.taskKey].renewShow">重新提交
111 111 </up-button>
112 112  
113   - <up-button type="primary" size="large" @click="handleProcess(item)">{{
  113 + <up-button type="primary" size="large" @click="handleUnifiedProcess(item)">{{
114 114 nextStepMap[item.taskKey].btnText
115 115 }}
116 116 </up-button>
... ... @@ -272,6 +272,9 @@
272 272 </view>
273 273 </view>
274 274 </up-modal>
  275 +
  276 + <!-- 统一节点动作弹窗(工作流配置化,未配置时回退原有逻辑) -->
  277 + <unified-task-actions ref="unifiedActionsRef" @success="handleUnifiedSuccess" />
275 278 </view>
276 279 </template>
277 280  
... ... @@ -283,7 +286,8 @@ import {
283 286 myBuzSimplePage,
284 287 todoBuzSimplePage,
285 288 doneBuzSimplePage,
286   - universalApproval
  289 + universalApproval,
  290 + getTaskActions
287 291 } from '@/api/work-order-manage/work-order-manage'
288 292 // 从用户store获取角色信息
289 293 import { useUserStore } from '@/pinia/user';
... ... @@ -486,6 +490,55 @@ const handleRenew = (item) =&gt; {
486 490 });
487 491 };
488 492  
  493 +// ========== 统一节点动作(工作流配置化) ==========
  494 +const unifiedActionsRef = ref(null)
  495 +
  496 +/** 查询当前任务是否已配置统一动作;未配置或接口异常时返回 null,回退原有逻辑 */
  497 +const queryUnifiedActions = async (item) => {
  498 + if (!item || !item.taskId) return null
  499 + try {
  500 + const data = await getTaskActions(item.taskId)
  501 + if (data && data.configured && Array.isArray(data.actions) && data.actions.length > 0) {
  502 + return data.actions
  503 + }
  504 + } catch (e) {
  505 + console.warn('查询统一节点动作失败,回退原有逻辑', e)
  506 + }
  507 + return null
  508 +}
  509 +
  510 +/** 待办-处理工单:已配置统一动作时走新逻辑,否则回退原有处理 */
  511 +const handleUnifiedProcess = async (item) => {
  512 + const actions = await queryUnifiedActions(item)
  513 + if (actions) {
  514 + unifiedActionsRef.value?.open(item, actions)
  515 + return
  516 + }
  517 + handleProcess(item)
  518 +}
  519 +
  520 +/** 待办-回退:已配置统一动作时走新逻辑,否则回退原有弹窗 */
  521 +const handleUnifiedReject = async (item) => {
  522 + const actions = await queryUnifiedActions(item)
  523 + if (actions) {
  524 + const rejectActions = actions.filter((action) =>
  525 + ['REJECT', 'RETURN', 'RETURN_PREV'].includes(action.code)
  526 + )
  527 + if (rejectActions.length > 0) {
  528 + unifiedActionsRef.value?.open(item, rejectActions)
  529 + } else {
  530 + uni.showToast({ title: '当前节点未配置回退动作', icon: 'none' })
  531 + }
  532 + return
  533 + }
  534 + handleReject(item)
  535 +}
  536 +
  537 +/** 统一动作执行成功后刷新列表 */
  538 +const handleUnifiedSuccess = () => {
  539 + paging.value?.reload()
  540 +}
  541 +
489 542 // 待办-处理工单
490 543 const handleProcess = async (item) => {
491 544 console.log(nextStepMap[item.taskKey].name)
... ...
pages-sub/problem/work-order-manage/order-detail.vue
... ... @@ -258,7 +258,7 @@
258 258 <!-- activeTab==0的时候才出现, 也就是待办 -->
259 259 <view v-if="activeTab==0&&nextStepMap[orderDetail.taskKey]" class="fixed-bottom-btn-wrap">
260 260 <view class="u-body-item u-flex common-justify-between common-item-center ">
261   - <up-button type="warning" size="normal" @click="handleReject(orderDetail)"
  261 + <up-button type="warning" size="normal" @click="handleUnifiedReject(orderDetail)"
262 262 v-show="nextStepMap[orderDetail.taskKey].backShow">回退
263 263 </up-button>
264 264  
... ... @@ -266,7 +266,7 @@
266 266 v-show="nextStepMap[orderDetail.taskKey].renewShow">重新提交
267 267 </up-button>
268 268  
269   - <up-button type="primary" size="normal" @click="handleProcess(orderDetail)">{{
  269 + <up-button type="primary" size="normal" @click="handleUnifiedProcess(orderDetail)">{{
270 270 nextStepMap[orderDetail.taskKey].btnText
271 271 }}
272 272 </up-button>
... ... @@ -358,6 +358,9 @@
358 358 </view>
359 359 </view>
360 360 </up-modal>
  361 +
  362 + <!-- 统一节点动作弹窗(工作流配置化,未配置时回退原有逻辑) -->
  363 + <unified-task-actions ref="unifiedActionsRef" @success="handleUnifiedSuccess" />
361 364 </view>
362 365 </template>
363 366  
... ... @@ -370,7 +373,8 @@ import {
370 373 getDoneTaskDetail,
371 374 getTodoTaskDetail,
372 375 getApprovalDetail,
373   - universalApproval
  376 + universalApproval,
  377 + getTaskActions
374 378 } from '@/api/work-order-manage/work-order-manage';
375 379 import {nextStepMap, buzStatusMap, calculateFormatTimeDiff} from '@/common/utils/common'
376 380 // 引入图片上传组合式函数
... ... @@ -697,6 +701,58 @@ const confirmReject = async () =&gt; {
697 701 }
698 702 };
699 703  
  704 +// ========== 统一节点动作(工作流配置化) ==========
  705 +const unifiedActionsRef = ref(null)
  706 +
  707 +/** 查询当前任务是否已配置统一动作;未配置或接口异常时返回 null,回退原有逻辑 */
  708 +const queryUnifiedActions = async (item: any) => {
  709 + if (!item || !item.taskId) return null
  710 + try {
  711 + const data = await getTaskActions(item.taskId)
  712 + if (data && data.configured && Array.isArray(data.actions) && data.actions.length > 0) {
  713 + return data.actions
  714 + }
  715 + } catch (e) {
  716 + console.warn('查询统一节点动作失败,回退原有逻辑', e)
  717 + }
  718 + return null
  719 +}
  720 +
  721 +/** 处理工单:已配置统一动作时走新逻辑,否则回退原有处理 */
  722 +const handleUnifiedProcess = async (item: any) => {
  723 + const actions = await queryUnifiedActions(item)
  724 + if (actions) {
  725 + unifiedActionsRef.value?.open(item, actions)
  726 + return
  727 + }
  728 + handleProcess(item)
  729 +}
  730 +
  731 +/** 回退:已配置统一动作时走新逻辑,否则回退原有弹窗 */
  732 +const handleUnifiedReject = async (item: any) => {
  733 + const actions = await queryUnifiedActions(item)
  734 + if (actions) {
  735 + const rejectActions = actions.filter((action: any) =>
  736 + ['REJECT', 'RETURN', 'RETURN_PREV'].includes(action.code)
  737 + )
  738 + if (rejectActions.length > 0) {
  739 + unifiedActionsRef.value?.open(item, rejectActions)
  740 + } else {
  741 + uni.showToast({ title: '当前节点未配置回退动作', icon: 'none' })
  742 + }
  743 + return
  744 + }
  745 + handleReject(item)
  746 +}
  747 +
  748 +/** 统一动作执行成功后通知列表刷新并返回 */
  749 +const handleUnifiedSuccess = () => {
  750 + if (eventChannel.value) {
  751 + eventChannel.value.emit('needRefresh')
  752 + }
  753 + uni.navigateBack({delta: 1})
  754 +}
  755 +
700 756 // ========== handleProcess 处理工单 ==========
701 757 const handleProcess = async (item: any) => {
702 758 console.log(nextStepMap[item.taskKey]?.name)
... ...