Commit b6e4a0183c5e96681f5e60264987096143a7bd32

Authored by 王彪总
1 parent 40ba5f9a

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

api/work-order-manage/work-order-manage.js
@@ -112,3 +112,20 @@ export const workorderCreate = (data) => { @@ -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,7 +98,7 @@
98 <view class="u-body-item u-flex u-body-item-btn-wrap common-justify-between common-item-center mt-20"> 98 <view class="u-body-item u-flex u-body-item-btn-wrap common-justify-between common-item-center mt-20">
99 <!-- <up-button type="warning" size="mini" @click="handleReject(item)" v-show="nextStepMap[item.taskKey].backShow">回退</up-button>--> 99 <!-- <up-button type="warning" size="mini" @click="handleReject(item)" v-show="nextStepMap[item.taskKey].backShow">回退</up-button>-->
100 <!-- <up-button type="success" size="mini" @click="handleRenew(item)" v-show="nextStepMap[item.taskKey].renewShow">重新提交</up-button>--> 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 <!-- <up-button type="primary" size="large" @click="handleProcess(item)" v-show="nextStepMap[item.taskKey].ysShow">验收</up-button>--> 102 <!-- <up-button type="primary" size="large" @click="handleProcess(item)" v-show="nextStepMap[item.taskKey].ysShow">验收</up-button>-->
103 103
104 <up-button type="info" size="large" @click="handleDetail(item)">详情</up-button> 104 <up-button type="info" size="large" @click="handleDetail(item)">详情</up-button>
@@ -244,6 +244,9 @@ @@ -244,6 +244,9 @@
244 </view> 244 </view>
245 </view> 245 </view>
246 </up-modal> 246 </up-modal>
  247 +
  248 + <!-- 统一节点动作弹窗(工作流配置化,未配置时回退原有逻辑) -->
  249 + <unified-task-actions ref="unifiedActionsRef" @success="handleUnifiedSuccess" />
247 </view> 250 </view>
248 </template> 251 </template>
249 252
@@ -260,6 +263,7 @@ import { @@ -260,6 +263,7 @@ import {
260 dcyUniversalApproval, 263 dcyUniversalApproval,
261 aiUniversalApproval 264 aiUniversalApproval
262 } from '@/api/regional-order-manage/regional-order-manage' 265 } from '@/api/regional-order-manage/regional-order-manage'
  266 +import { getTaskActions } from '@/api/work-order-manage/work-order-manage'
263 import { useUserStore } from '@/pinia/user'; 267 import { useUserStore } from '@/pinia/user';
264 import { nextStepMap, buzStatusMap } from '@/common/utils/common' 268 import { nextStepMap, buzStatusMap } from '@/common/utils/common'
265 import { useUploadImgs } from '@/common/utils/useUploadImgs' 269 import { useUploadImgs } from '@/common/utils/useUploadImgs'
@@ -443,6 +447,38 @@ const handleRenew = (item) =&gt; { @@ -443,6 +447,38 @@ const handleRenew = (item) =&gt; {
443 navToOrderPage(pageUrl, { isRenew: 1, tempKey }); 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 const handleProcess = async (item) => { 483 const handleProcess = async (item) => {
448 if (!item) return uni.showToast({title: '工单信息异常', icon: 'none'}); 484 if (!item) return uni.showToast({title: '工单信息异常', icon: 'none'});
pages-sub/problem/regional-order-manage/index.vue
@@ -108,9 +108,9 @@ @@ -108,9 +108,9 @@
108 108
109 <!-- 待办工单(0) 保留原来的操作按钮区 不变 --> 109 <!-- 待办工单(0) 保留原来的操作按钮区 不变 -->
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"> 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 <up-button type="success" size="large" @click="handleRenew(item)" v-show="nextStepMap[item.taskKey].renewShow">重新提交</up-button> 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 <up-button type="info" size="large" @click="handleDetail(item)">详情</up-button> 114 <up-button type="info" size="large" @click="handleDetail(item)">详情</up-button>
115 </view> 115 </view>
116 116
@@ -205,6 +205,9 @@ @@ -205,6 +205,9 @@
205 </view> 205 </view>
206 </view> 206 </view>
207 </up-modal> 207 </up-modal>
  208 +
  209 + <!-- 统一节点动作弹窗(工作流配置化,未配置时回退原有逻辑) -->
  210 + <unified-task-actions ref="unifiedActionsRef" @success="handleUnifiedSuccess" />
208 </view> 211 </view>
209 </template> 212 </template>
210 213
@@ -221,6 +224,7 @@ import { @@ -221,6 +224,7 @@ import {
221 dcyUniversalApproval, 224 dcyUniversalApproval,
222 aiUniversalApproval 225 aiUniversalApproval
223 } from '@/api/regional-order-manage/regional-order-manage' 226 } from '@/api/regional-order-manage/regional-order-manage'
  227 +import { getTaskActions } from '@/api/work-order-manage/work-order-manage'
224 import { useUserStore } from '@/pinia/user'; 228 import { useUserStore } from '@/pinia/user';
225 import { nextStepMap, buzStatusMap } from '@/common/utils/common' 229 import { nextStepMap, buzStatusMap } from '@/common/utils/common'
226 import { useUploadImgs } from '@/common/utils/useUploadImgs' 230 import { useUploadImgs } from '@/common/utils/useUploadImgs'
@@ -419,6 +423,55 @@ const handleRenew = (item) =&gt; { @@ -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 const handleProcess = async (item) => { 475 const handleProcess = async (item) => {
423 if (!item) return showToast('工单信息异常'); 476 if (!item) return showToast('工单信息异常');
424 const stepName = nextStepMap[item.taskKey]?.name; 477 const stepName = nextStepMap[item.taskKey]?.name;
pages-sub/problem/work-order-manage/index.vue
@@ -102,7 +102,7 @@ @@ -102,7 +102,7 @@
102 </view> 102 </view>
103 <!-- 操作按钮行 --> 103 <!-- 操作按钮行 -->
104 <view class="u-body-item u-body-item-btn-wrap u-flex common-justify-between common-item-center mt-20"> 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 v-show="nextStepMap[item.taskKey].backShow">回退 106 v-show="nextStepMap[item.taskKey].backShow">回退
107 </up-button> 107 </up-button>
108 108
@@ -110,7 +110,7 @@ @@ -110,7 +110,7 @@
110 v-show="nextStepMap[item.taskKey].renewShow">重新提交 110 v-show="nextStepMap[item.taskKey].renewShow">重新提交
111 </up-button> 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 nextStepMap[item.taskKey].btnText 114 nextStepMap[item.taskKey].btnText
115 }} 115 }}
116 </up-button> 116 </up-button>
@@ -272,6 +272,9 @@ @@ -272,6 +272,9 @@
272 </view> 272 </view>
273 </view> 273 </view>
274 </up-modal> 274 </up-modal>
  275 +
  276 + <!-- 统一节点动作弹窗(工作流配置化,未配置时回退原有逻辑) -->
  277 + <unified-task-actions ref="unifiedActionsRef" @success="handleUnifiedSuccess" />
275 </view> 278 </view>
276 </template> 279 </template>
277 280
@@ -283,7 +286,8 @@ import { @@ -283,7 +286,8 @@ import {
283 myBuzSimplePage, 286 myBuzSimplePage,
284 todoBuzSimplePage, 287 todoBuzSimplePage,
285 doneBuzSimplePage, 288 doneBuzSimplePage,
286 - universalApproval 289 + universalApproval,
  290 + getTaskActions
287 } from '@/api/work-order-manage/work-order-manage' 291 } from '@/api/work-order-manage/work-order-manage'
288 // 从用户store获取角色信息 292 // 从用户store获取角色信息
289 import { useUserStore } from '@/pinia/user'; 293 import { useUserStore } from '@/pinia/user';
@@ -486,6 +490,55 @@ const handleRenew = (item) =&gt; { @@ -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 const handleProcess = async (item) => { 543 const handleProcess = async (item) => {
491 console.log(nextStepMap[item.taskKey].name) 544 console.log(nextStepMap[item.taskKey].name)
pages-sub/problem/work-order-manage/order-detail.vue
@@ -258,7 +258,7 @@ @@ -258,7 +258,7 @@
258 <!-- activeTab==0的时候才出现, 也就是待办 --> 258 <!-- activeTab==0的时候才出现, 也就是待办 -->
259 <view v-if="activeTab==0&&nextStepMap[orderDetail.taskKey]" class="fixed-bottom-btn-wrap"> 259 <view v-if="activeTab==0&&nextStepMap[orderDetail.taskKey]" class="fixed-bottom-btn-wrap">
260 <view class="u-body-item u-flex common-justify-between common-item-center "> 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 v-show="nextStepMap[orderDetail.taskKey].backShow">回退 262 v-show="nextStepMap[orderDetail.taskKey].backShow">回退
263 </up-button> 263 </up-button>
264 264
@@ -266,7 +266,7 @@ @@ -266,7 +266,7 @@
266 v-show="nextStepMap[orderDetail.taskKey].renewShow">重新提交 266 v-show="nextStepMap[orderDetail.taskKey].renewShow">重新提交
267 </up-button> 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 nextStepMap[orderDetail.taskKey].btnText 270 nextStepMap[orderDetail.taskKey].btnText
271 }} 271 }}
272 </up-button> 272 </up-button>
@@ -358,6 +358,9 @@ @@ -358,6 +358,9 @@
358 </view> 358 </view>
359 </view> 359 </view>
360 </up-modal> 360 </up-modal>
  361 +
  362 + <!-- 统一节点动作弹窗(工作流配置化,未配置时回退原有逻辑) -->
  363 + <unified-task-actions ref="unifiedActionsRef" @success="handleUnifiedSuccess" />
361 </view> 364 </view>
362 </template> 365 </template>
363 366
@@ -370,7 +373,8 @@ import { @@ -370,7 +373,8 @@ import {
370 getDoneTaskDetail, 373 getDoneTaskDetail,
371 getTodoTaskDetail, 374 getTodoTaskDetail,
372 getApprovalDetail, 375 getApprovalDetail,
373 - universalApproval 376 + universalApproval,
  377 + getTaskActions
374 } from '@/api/work-order-manage/work-order-manage'; 378 } from '@/api/work-order-manage/work-order-manage';
375 import {nextStepMap, buzStatusMap, calculateFormatTimeDiff} from '@/common/utils/common' 379 import {nextStepMap, buzStatusMap, calculateFormatTimeDiff} from '@/common/utils/common'
376 // 引入图片上传组合式函数 380 // 引入图片上传组合式函数
@@ -697,6 +701,58 @@ const confirmReject = async () =&gt; { @@ -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 // ========== handleProcess 处理工单 ========== 756 // ========== handleProcess 处理工单 ==========
701 const handleProcess = async (item: any) => { 757 const handleProcess = async (item: any) => {
702 console.log(nextStepMap[item.taskKey]?.name) 758 console.log(nextStepMap[item.taskKey]?.name)