feat(flow): 新增工作流执行资源配置功能

- 添加了 flowExecution 工具模块用于处理工作流运行时资源配置
- 实现了工作流角色定义的加载、合并和验证功能
- 集成了机器人和设备分配逻辑到任务执行界面
- 更新了 RuntimeAssignmentsEditor 组件以支持动态设备分配
- 移除了静态机器人选择,改为基于流程角色的动态分配
- 优化了工作流资源定义合并逻辑,支持插槽复用配置
- 增强了设备冲突检测和错误处理机制
- 统一了多个视图中的工作流执行资源配置流程
This commit is contained in:
lixiaolong 2026-08-12 09:31:45 +08:00
parent e3a2761683
commit c54f1da126
9 changed files with 251 additions and 187 deletions

View File

@ -0,0 +1,82 @@
import { getFlowParamsApi } from '@/api/device/terminal'
import { getDetect } from '@/api/test/detect'
import {
getResourceRoles,
mergeResourceRoleDefinitions,
normalizeFlowResourceSchema,
validateFlowResources,
} from '@/utils/flowResources'
const clone = (value) => JSON.parse(JSON.stringify(value || []))
const parseFlowData = (itemId, flowData) => {
if (!flowData) throw new Error(`流程项[${itemId}]还没有配置工作流`)
try {
const graph = typeof flowData === 'string' ? JSON.parse(flowData) : flowData
const normalizedGraph = normalizeFlowResourceSchema(graph)
const validation = validateFlowResources(normalizedGraph)
if (validation.errors.length) {
throw new Error(validation.errors[0].message)
}
return validation.graph
} catch (error) {
throw new Error(`流程项[${itemId}]不可执行:${error.message || '工作流数据无法解析'}`)
}
}
const assertCompatibleDefinitions = (roleGroups) => {
const rolesByKey = new Map()
roleGroups.flat().forEach((role) => {
const existing = rolesByKey.get(role.roleKey)
if (!existing) {
rolesByKey.set(role.roleKey, clone([role])[0])
return
}
const slotsByKey = new Map((existing.deviceSlots || []).map((slot) => [slot.slotKey, slot]))
;(role.deviceSlots || []).forEach((slot) => {
const existingSlot = slotsByKey.get(slot.slotKey)
if (existingSlot && String(existingSlot.deviceKind ?? '') !== String(slot.deviceKind ?? '')) {
throw new Error(`机器人角色“${existing.displayName}”的设备用途“${slot.displayName}”在不同流程中类型不一致`)
}
if (!existingSlot) existing.deviceSlots.push(clone([slot])[0])
})
})
}
export const loadFlowItemRuntimeRoles = async (items = []) => {
if (items.some((item) => !item?.itemId)) throw new Error('任务编排中存在缺少流程项ID的数据')
const uniqueItems = Array.from(new Map(
items.filter((item) => item?.itemId).map((item) => [item.itemId, item]),
).values())
if (!uniqueItems.length) throw new Error('当前任务没有编排任何流程项')
const roleGroups = await Promise.all(uniqueItems.map(async (item) => {
const response = await getDetect(item.itemId)
const graph = parseFlowData(item.itemName || item.itemId, response.data?.flowData)
return getResourceRoles(graph)
}))
assertCompatibleDefinitions(roleGroups)
return mergeResourceRoleDefinitions(roleGroups)
}
export const loadTaskRuntimeRoles = async (taskId) => {
if (!taskId) throw new Error('任务没有关联流程编排')
const response = await getFlowParamsApi(taskId)
return loadFlowItemRuntimeRoles(response.rows || [])
}
export const createRuntimeAssignments = (roles = [], primaryRobotId = '') => roles.map((role, index) => ({
roleKey: role.roleKey,
members: [{
memberKey: `${role.roleKey}_1`,
robotId: index === 0 ? primaryRobotId || role.legacyRobotId || '' : role.legacyRobotId || '',
devices: Object.fromEntries((role.deviceSlots || []).map((slot) => [
slot.slotKey,
slot.legacyDeviceId || '',
])),
}],
}))
export const firstAssignedRobotId = (assignments = []) => (
clone(assignments).flatMap((item) => item.members || []).find((member) => member.robotId)?.robotId || ''
)

View File

@ -313,10 +313,15 @@ export const mergeResourceRoleDefinitions = (roleGroups = []) => {
merged.push(clone(sourceRole)) merged.push(clone(sourceRole))
return return
} }
role.required = role.required !== false || sourceRole.required !== false
;(sourceRole.deviceSlots || []).forEach((sourceSlot) => { ;(sourceRole.deviceSlots || []).forEach((sourceSlot) => {
if (!role.deviceSlots.some((slot) => slot.slotKey === sourceSlot.slotKey)) { const slot = role.deviceSlots.find((item) => item.slotKey === sourceSlot.slotKey)
if (!slot) {
role.deviceSlots.push(clone(sourceSlot)) role.deviceSlots.push(clone(sourceSlot))
return
} }
slot.required = slot.required !== false || sourceSlot.required !== false
slot.allowReuse = slot.allowReuse === true && sourceSlot.allowReuse === true
}) })
}) })
return merged return merged

View File

@ -9,13 +9,14 @@
<el-form-item label="执行机器人" :required="role.required"> <el-form-item label="执行机器人" :required="role.required">
<el-select <el-select
:model-value="memberOf(role).robotId" :model-value="memberOf(role).robotId"
clearable
filterable filterable
:loading="robotLoading" :loading="robotLoading"
placeholder="请选择机器人" placeholder="请选择机器人"
style="width: 100%" style="width: 100%"
@change="(robotId) => changeRobot(role, robotId)" @change="(robotId) => changeRobot(role, robotId)"
> >
<el-option v-for="item in robotOptions" :key="item.value" :label="item.label" :value="item.value" /> <el-option v-for="item in robotOptionsOf(role)" :key="item.value" :label="item.label" :value="item.value" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item v-for="slot in role.deviceSlots" :key="slot.slotKey" :label="slot.displayName" :required="slot.required"> <el-form-item v-for="slot in role.deviceSlots" :key="slot.slotKey" :label="slot.displayName" :required="slot.required">
@ -36,18 +37,21 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
<el-alert v-if="loadError" :title="loadError" type="error" :closable="false" show-icon />
<el-empty v-if="!roles.length" :image-size="52" description="当前任务不需要机器人" /> <el-empty v-if="!roles.length" :image-size="52" description="当前任务不需要机器人" />
</div> </div>
</template> </template>
<script setup> <script setup>
import { reactive, ref, watch } from 'vue' import { computed, reactive, ref, watch } from 'vue'
import { getRobotList, getRobotDevicesByRobotId } from '@/api/inspection/robot' import { getRobotList, getRobotDevicesByRobotId } from '@/api/inspection/robot'
import { toDeviceOptions } from '@/utils/robotDevice' import { toDeviceOptions } from '@/utils/robotDevice'
const props = defineProps({ const props = defineProps({
roles: { type: Array, default: () => [] }, roles: { type: Array, default: () => [] },
modelValue: { type: Array, default: () => [] }, modelValue: { type: Array, default: () => [] },
robotQuery: { type: Object, default: () => ({}) },
requiredMapId: { type: [String, Number], default: '' },
}) })
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
@ -56,6 +60,9 @@ const robotOptions = ref([])
const devicesByRole = reactive({}) const devicesByRole = reactive({})
const robotLoading = ref(false) const robotLoading = ref(false)
const loadingRoleKey = ref('') const loadingRoleKey = ref('')
const robotLoadError = ref('')
const deviceLoadErrors = reactive({})
const loadError = computed(() => robotLoadError.value || Object.values(deviceLoadErrors).find(Boolean) || '')
const clone = (value) => JSON.parse(JSON.stringify(value || [])) const clone = (value) => JSON.parse(JSON.stringify(value || []))
const assignmentOf = (role) => assignments.value.find((item) => item.roleKey === role.roleKey) const assignmentOf = (role) => assignments.value.find((item) => item.roleKey === role.roleKey)
@ -64,35 +71,69 @@ const emitValue = () => emit('update:modelValue', clone(assignments.value))
const loadRobots = async () => { const loadRobots = async () => {
robotLoading.value = true robotLoading.value = true
robotLoadError.value = ''
try { try {
const response = await getRobotList({ pageNum: 1, pageSize: 1000 }) const response = await getRobotList({
pageNum: 1,
pageSize: 1000,
connectStatus: '1',
...props.robotQuery,
})
robotOptions.value = (response.rows || []).map((robot) => ({ robotOptions.value = (response.rows || []).map((robot) => ({
label: `${robot.robotName || robot.robotId} (${robot.robotId})`, label: `${robot.robotName || robot.robotId} (${robot.robotId})`,
value: robot.robotId, value: robot.robotId,
currentMapId: robot.currentMapId,
})) }))
} catch (error) {
robotOptions.value = []
robotLoadError.value = '机器人列表加载失败,请稍后重试'
} finally { } finally {
robotLoading.value = false robotLoading.value = false
} }
} }
const optionsOf = (role, slot) => toDeviceOptions((devicesByRole[role.roleKey] || []).filter((device) => ( const robotOptionsOf = (role) => {
slot.deviceKind == null || Number(device.deviceKind) === Number(slot.deviceKind) const requiresTaskMap = (role.deviceSlots || []).some((slot) => Number(slot.deviceKind) === 1)
))) if (!props.requiredMapId || !requiresTaskMap) return robotOptions.value
return robotOptions.value.filter((robot) => String(robot.currentMapId || '') === String(props.requiredMapId))
}
const optionsOf = (role, slot) => {
const member = memberOf(role)
const unavailableDeviceIds = new Set((role.deviceSlots || [])
.filter((otherSlot) => (
otherSlot.slotKey !== slot.slotKey
&& (!slot.allowReuse || !otherSlot.allowReuse)
))
.map((otherSlot) => member.devices[otherSlot.slotKey])
.filter(Boolean))
return toDeviceOptions((devicesByRole[role.roleKey] || []).filter((device) => (
(slot.deviceKind == null || Number(device.deviceKind) === Number(slot.deviceKind))
&& (!unavailableDeviceIds.has(device.deviceId) || member.devices[slot.slotKey] === device.deviceId)
)))
}
const loadDevices = async (role, preserve = true) => { const loadDevices = async (role, preserve = true) => {
const member = memberOf(role) const member = memberOf(role)
if (!member.robotId) return if (!member.robotId) {
devicesByRole[role.roleKey] = []
return
}
loadingRoleKey.value = role.roleKey loadingRoleKey.value = role.roleKey
delete deviceLoadErrors[role.roleKey]
try { try {
const response = await getRobotDevicesByRobotId(member.robotId) const response = await getRobotDevicesByRobotId(member.robotId)
devicesByRole[role.roleKey] = Array.isArray(response.data) ? response.data : [] devicesByRole[role.roleKey] = Array.isArray(response.data) ? response.data : []
role.deviceSlots.forEach((slot) => { ;(role.deviceSlots || []).forEach((slot) => {
const options = optionsOf(role, slot) const options = optionsOf(role, slot)
if (!preserve || !options.some((option) => option.value === member.devices[slot.slotKey])) { if (!preserve || !options.some((option) => option.value === member.devices[slot.slotKey])) {
member.devices[slot.slotKey] = options.length === 1 ? options[0].value : '' member.devices[slot.slotKey] = options.length === 1 ? options[0].value : ''
} }
}) })
emitValue() emitValue()
} catch (error) {
devicesByRole[role.roleKey] = []
deviceLoadErrors[role.roleKey] = `“${role.displayName}”的设备列表加载失败,请重新选择机器人`
} finally { } finally {
loadingRoleKey.value = '' loadingRoleKey.value = ''
} }
@ -101,37 +142,61 @@ const loadDevices = async (role, preserve = true) => {
const changeRobot = async (role, robotId) => { const changeRobot = async (role, robotId) => {
const member = memberOf(role) const member = memberOf(role)
member.robotId = robotId member.robotId = robotId
member.devices = Object.fromEntries(role.deviceSlots.map((slot) => [slot.slotKey, ''])) member.devices = Object.fromEntries((role.deviceSlots || []).map((slot) => [slot.slotKey, '']))
await loadDevices(role, false) await loadDevices(role, false)
} }
const initialize = async () => { const initialize = async () => {
Object.keys(deviceLoadErrors).forEach((roleKey) => delete deviceLoadErrors[roleKey])
const existing = clone(props.modelValue) const existing = clone(props.modelValue)
assignments.value = props.roles.map((role) => existing.find((item) => item.roleKey === role.roleKey) || ({ assignments.value = props.roles.map((role) => {
roleKey: role.roleKey, const existingAssignment = existing.find((item) => item.roleKey === role.roleKey)
members: [{ const existingMember = existingAssignment?.members?.[0]
memberKey: `${role.roleKey}_1`, return {
robotId: role.legacyRobotId || '', roleKey: role.roleKey,
devices: Object.fromEntries((role.deviceSlots || []).map((slot) => [slot.slotKey, slot.legacyDeviceId || ''])), members: [{
}], ...existingMember,
})) memberKey: `${role.roleKey}_1`,
robotId: existingMember?.robotId || role.legacyRobotId || '',
devices: Object.fromEntries((role.deviceSlots || []).map((slot) => [
slot.slotKey,
existingMember?.devices?.[slot.slotKey] || slot.legacyDeviceId || '',
])),
}],
}
})
emitValue() emitValue()
await loadRobots() await loadRobots()
await Promise.all(props.roles.filter((role) => memberOf(role).robotId).map((role) => loadDevices(role))) await Promise.all(props.roles.filter((role) => memberOf(role).robotId).map((role) => loadDevices(role)))
} }
const validate = () => { const validate = () => {
const physicalDeviceUses = new Map()
for (const role of props.roles) { for (const role of props.roles) {
const member = memberOf(role) const member = memberOf(role)
if (role.required && !member.robotId) return `请为“${role.displayName}”选择机器人` if (role.required && !member.robotId) return `请为“${role.displayName}”选择机器人`
if (!member.robotId) continue
for (const slot of role.deviceSlots || []) { for (const slot of role.deviceSlots || []) {
if (slot.required && !member.devices[slot.slotKey]) return `请为“${role.displayName}”选择“${slot.displayName}”` if (slot.required && !member.devices[slot.slotKey]) return `请为“${role.displayName}”选择“${slot.displayName}”`
const deviceId = member.devices[slot.slotKey]
if (!deviceId) continue
const physicalKey = `${member.robotId}:${deviceId}`
const existingUse = physicalDeviceUses.get(physicalKey)
if (existingUse && (!slot.allowReuse || !existingUse.allowReuse)) {
return `“${existingUse.roleName} / ${existingUse.slotName}”和“${role.displayName} / ${slot.displayName}”不能使用同一个设备`
}
physicalDeviceUses.set(physicalKey, {
allowReuse: slot.allowReuse,
roleName: role.displayName,
slotName: slot.displayName,
})
} }
} }
if (loadError.value) return loadError.value
return '' return ''
} }
watch(() => props.roles, initialize, { immediate: true, deep: true }) watch([() => props.roles, () => props.robotQuery, () => props.requiredMapId], initialize, { immediate: true, deep: true })
defineExpose({ validate }) defineExpose({ validate })
</script> </script>

View File

@ -20,7 +20,7 @@
@click="removeTask(task)" /> @click="removeTask(task)" />
</div> </div>
</div> </div>
<div class="robot">机器人:{{ task.robotName }}</div> <div class="robot">主要机器人:{{ task.robotName || '执行时安排' }}</div>
<el-progress :color="customColorMethod" <el-progress :color="customColorMethod"
:percentage="parseFloat(taskRunningInfo[task.id]?.progress?.toFixed(2)) || 0" /> :percentage="parseFloat(taskRunningInfo[task.id]?.progress?.toFixed(2)) || 0" />
</div> </div>
@ -44,7 +44,7 @@
<div class="id">ID: {{ runTask.taskId }}</div> <div class="id">ID: {{ runTask.taskId }}</div>
</div> </div>
<div class="robot"> <div class="robot">
<div>机器人:{{ runTask.robotName }}</div> <div>主要机器人:{{ runTask.robotName || '按流程角色安排' }}</div>
<div>开始时间: {{ formatDate(runTask.startTime) }}</div> <div>开始时间: {{ formatDate(runTask.startTime) }}</div>
</div> </div>
</div> </div>
@ -84,7 +84,9 @@
</el-tag> </el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="机器人" prop="robotName" show-overflow-tooltip /> <el-table-column align="center" label="主要机器人" prop="robotName" show-overflow-tooltip>
<template #default="scope">{{ scope.row.robotName || '按流程角色安排' }}</template>
</el-table-column>
<el-table-column align="center" label="开始时间" prop="startTime"> <el-table-column align="center" label="开始时间" prop="startTime">
<template #default="scope"> <template #default="scope">
{{ formatDate(scope.row.startTime) }} {{ formatDate(scope.row.startTime) }}
@ -117,17 +119,10 @@
<el-dialog title="添加任务" v-model="open" width="500px" append-to-body> <el-dialog title="添加任务" v-model="open" width="500px" append-to-body>
<el-form ref="runTaskRef" :model="form" :rules="rules" label-width="100px"> <el-form ref="runTaskRef" :model="form" :rules="rules" label-width="100px">
<el-form-item label="选择任务" prop="taskId"> <el-form-item label="选择任务" prop="taskId">
<el-select v-model="form.taskId" placeholder="请选择任务" clearable style="width: 100%" <el-select v-model="form.taskId" placeholder="请选择任务" clearable style="width: 100%">
@change="taskChange">
<el-option v-for="task in taskList" :key="task.id" :label="task.taskName" :value="task.id" /> <el-option v-for="task in taskList" :key="task.id" :label="task.taskName" :value="task.id" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="选择机器人" prop="robotId">
<el-select v-model="form.robotId" placeholder="请选择机器人" clearable style="width: 100%">
<el-option v-for="robot in robotList" :key="robot.id" :label="robot.robotName"
:value="robot.id" />
</el-select>
</el-form-item>
<el-form-item label="备注" prop="remark"> <el-form-item label="备注" prop="remark">
<el-input type="textarea" v-model="form.remark" /> <el-input type="textarea" v-model="form.remark" />
</el-form-item> </el-form-item>
@ -145,7 +140,12 @@
</el-dialog> </el-dialog>
<el-dialog title="本次执行安排" v-model="startDialogVisible" width="560px" append-to-body> <el-dialog title="本次执行安排" v-model="startDialogVisible" width="560px" append-to-body>
<RuntimeAssignmentsEditor ref="runtimeEditorRef" v-model="runtimeAssignments" :roles="runtimeRoles" /> <RuntimeAssignmentsEditor
ref="runtimeEditorRef"
v-model="runtimeAssignments"
:roles="runtimeRoles"
:required-map-id="runtimeMapId"
/>
<template #footer> <template #footer>
<el-button @click="startDialogVisible = false">取消</el-button> <el-button @click="startDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="startSubmitting" @click="confirmStartTask">开始执行</el-button> <el-button type="primary" :loading="startSubmitting" @click="confirmStartTask">开始执行</el-button>
@ -159,16 +159,17 @@ import taskSteps from "./task-steps.vue";
import taskLog from "./task-log.vue"; import taskLog from "./task-log.vue";
import { useContainerHeight } from "@/hooks/tableHeight"; import { useContainerHeight } from "@/hooks/tableHeight";
import { getTaskList, getWaypointByTaskId } from "@/api/inspection/task"; import { getTaskList, getWaypointByTaskId } from "@/api/inspection/task";
import { getRobotList } from "@/api/inspection/robot";
import { onMounted, onUnmounted } from "vue"; import { onMounted, onUnmounted } from "vue";
import { addRunTask, getRunTaskList, startRunTask, pauseRunTask, stopRunTask, resumeRunTask, deleteRunTask, getTaskLogListApi } from "@/api/inspection/runTask"; import { addRunTask, getRunTaskList, startRunTask, pauseRunTask, stopRunTask, resumeRunTask, deleteRunTask, getTaskLogListApi } from "@/api/inspection/runTask";
import { ElMessage, ElMessageBox } from "element-plus"; import { ElMessage, ElMessageBox } from "element-plus";
import { formatDate } from "@/utils/index"; import { formatDate } from "@/utils/index";
import { debounce } from 'lodash'; // 引入 lodash 的防抖函数 import { debounce } from 'lodash'; // 引入 lodash 的防抖函数
import { getFlowParamsApi } from '@/api/device/terminal'
import { getDetect } from '@/api/test/detect'
import RuntimeAssignmentsEditor from '@/views/flow/components/RuntimeAssignmentsEditor.vue' import RuntimeAssignmentsEditor from '@/views/flow/components/RuntimeAssignmentsEditor.vue'
import { getResourceRoles, mergeResourceRoleDefinitions, normalizeFlowResourceSchema } from '@/utils/flowResources' import {
createRuntimeAssignments,
firstAssignedRobotId,
loadTaskRuntimeRoles,
} from '@/utils/flowExecution'
const topContainerRef = ref(); const topContainerRef = ref();
const containerHeight = useContainerHeight(topContainerRef); const containerHeight = useContainerHeight(topContainerRef);
@ -179,6 +180,7 @@ const pendingStartTask = ref(null)
const runtimeRoles = ref([]) const runtimeRoles = ref([])
const runtimeAssignments = ref([]) const runtimeAssignments = ref([])
const runtimeEditorRef = ref() const runtimeEditorRef = ref()
const runtimeMapId = ref('')
const taskStatusMap = { const taskStatusMap = {
'0': '已完成', '0': '已完成',
@ -191,17 +193,13 @@ const taskStatusMap = {
const form = reactive({ const form = reactive({
taskId: null, taskId: null,
robotId: null,
}); });
const rules = reactive({ const rules = reactive({
taskId: [{ required: true, message: '请选择任务', trigger: 'change' }], taskId: [{ required: true, message: '请选择任务', trigger: 'change' }],
robotId: [{ required: true, message: '请选择机器人', trigger: 'change' }],
}); });
const taskList = ref([]); const taskList = ref([]);
const robotList = ref([]);
const runTaskList = ref([]); const runTaskList = ref([]);
const getTaskData = () => { const getTaskData = () => {
@ -213,30 +211,12 @@ const getTaskData = () => {
}) })
} }
const getRobotData = (mapId = null) => {
getRobotList({
pageNum: 1,
pageSize: 10000,
currentMapId: mapId
}).then((res) => {
robotList.value = res.rows;
})
}
const open = ref(false); const open = ref(false);
const addTask = () => { const addTask = () => {
open.value = true; open.value = true;
} }
const taskChange = (value) => {
const task = taskList.value.find(item => item.id === value)
const mapId = task.mapId
form.robotId = null;
robotList.value = []
getRobotData(mapId)
}
const runTaskRef = ref(); const runTaskRef = ref();
/** /**
* 确定添加执行任务 * 确定添加执行任务
@ -347,25 +327,16 @@ const startTask = async (task) => {
ElMessage.warning('当前任务没有关联流程编排') ElMessage.warning('当前任务没有关联流程编排')
return return
} }
const response = await getFlowParamsApi(taskDefinition.taskConfigId) try {
const definitions = await Promise.all((response.rows || []).map(async (item) => { const roles = await loadTaskRuntimeRoles(taskDefinition.taskConfigId)
const flowResponse = await getDetect(item.itemId) runtimeAssignments.value = createRuntimeAssignments(roles, task.robotIdentity)
return flowResponse.data?.flowData runtimeMapId.value = taskDefinition.mapId || ''
? getResourceRoles(normalizeFlowResourceSchema(JSON.parse(flowResponse.data.flowData))) runtimeRoles.value = roles
: [] pendingStartTask.value = task
})) startDialogVisible.value = true
const roles = mergeResourceRoleDefinitions(definitions) } catch (error) {
runtimeAssignments.value = roles.map((role, index) => ({ ElMessage.error(error.message || '工作流执行资源加载失败')
roleKey: role.roleKey, }
members: [{
memberKey: `${role.roleKey}_1`,
robotId: index === 0 ? task.robotId || '' : '',
devices: {}
}]
}))
runtimeRoles.value = roles
pendingStartTask.value = task
startDialogVisible.value = true
} }
} }
@ -377,7 +348,7 @@ const confirmStartTask = async () => {
} }
startSubmitting.value = true startSubmitting.value = true
try { try {
const robotId = runtimeAssignments.value.flatMap(item => item.members).find(member => member.robotId)?.robotId || '' const robotId = firstAssignedRobotId(runtimeAssignments.value)
const res = await startRunTask(pendingStartTask.value.id, { const res = await startRunTask(pendingStartTask.value.id, {
robotId, robotId,
runtimeAssignments: runtimeAssignments.value runtimeAssignments: runtimeAssignments.value
@ -534,7 +505,6 @@ const handleOpenLog = async (row) => {
} }
onMounted(() => { onMounted(() => {
getTaskData(); getTaskData();
getRobotData();
getList(); getList();
getHistoryList(); getHistoryList();
subscribeDebounced(true) subscribeDebounced(true)

View File

@ -183,10 +183,12 @@ import {
import { ElMessage, ElMessageBox } from "element-plus"; import { ElMessage, ElMessageBox } from "element-plus";
import { formatDate } from "@/utils/index"; import { formatDate } from "@/utils/index";
import { debounce } from 'lodash'; // 引入 lodash 的防抖函数 import { debounce } from 'lodash'; // 引入 lodash 的防抖函数
import { getFlowParamsApi } from '@/api/device/terminal'
import { getDetect } from '@/api/test/detect'
import RuntimeAssignmentsEditor from '@/views/flow/components/RuntimeAssignmentsEditor.vue' import RuntimeAssignmentsEditor from '@/views/flow/components/RuntimeAssignmentsEditor.vue'
import { getResourceRoles, mergeResourceRoleDefinitions, normalizeFlowResourceSchema } from '@/utils/flowResources' import {
createRuntimeAssignments,
firstAssignedRobotId,
loadTaskRuntimeRoles,
} from '@/utils/flowExecution'
const topContainerRef = ref(); const topContainerRef = ref();
const containerHeight = useContainerHeight(topContainerRef); const containerHeight = useContainerHeight(topContainerRef);
@ -367,17 +369,15 @@ const startTask = async (task) => {
ElMessage.warning('当前任务没有关联流程编排') ElMessage.warning('当前任务没有关联流程编排')
return return
} }
const response = await getFlowParamsApi(taskDefinition.taskConfigId) try {
const definitions = await Promise.all((response.rows || []).map(async (item) => { const roles = await loadTaskRuntimeRoles(taskDefinition.taskConfigId)
const flowResponse = await getDetect(item.itemId) runtimeAssignments.value = createRuntimeAssignments(roles)
return flowResponse.data?.flowData runtimeRoles.value = roles
? getResourceRoles(normalizeFlowResourceSchema(JSON.parse(flowResponse.data.flowData))) pendingStartTask.value = task
: [] startDialogVisible.value = true
})) } catch (error) {
runtimeRoles.value = mergeResourceRoleDefinitions(definitions) ElMessage.error(error.message || '工作流执行资源加载失败')
runtimeAssignments.value = [] }
pendingStartTask.value = task
startDialogVisible.value = true
} }
} }
@ -389,7 +389,7 @@ const confirmStartTask = async () => {
} }
startSubmitting.value = true startSubmitting.value = true
try { try {
const robotId = runtimeAssignments.value.flatMap(item => item.members).find(member => member.robotId)?.robotId || '' const robotId = firstAssignedRobotId(runtimeAssignments.value)
const res = await startTaskinstance(pendingStartTask.value.id, { const res = await startTaskinstance(pendingStartTask.value.id, {
robotId, robotId,
runtimeAssignments: runtimeAssignments.value runtimeAssignments: runtimeAssignments.value

View File

@ -68,9 +68,11 @@ import { ref, watch } from "vue";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { addValueProperty, formatTableData } from "@/utils/flow"; import { addValueProperty, formatTableData } from "@/utils/flow";
import { getExecuteRunParams, projectExecute } from "@/api/is/project/index.js"; import { getExecuteRunParams, projectExecute } from "@/api/is/project/index.js";
import { getDetect } from '@/api/test/detect'
import RuntimeAssignmentsEditor from '@/views/flow/components/RuntimeAssignmentsEditor.vue' import RuntimeAssignmentsEditor from '@/views/flow/components/RuntimeAssignmentsEditor.vue'
import { getResourceRoles, mergeResourceRoleDefinitions, normalizeFlowResourceSchema } from '@/utils/flowResources' import {
firstAssignedRobotId,
loadFlowItemRuntimeRoles,
} from '@/utils/flowExecution'
const props = defineProps({ const props = defineProps({
@ -96,13 +98,8 @@ const getProjectParams = async () => {
if (res.code === 200) { if (res.code === 200) {
const data = parseConfig(res.data) const data = parseConfig(res.data)
formData.value.tableData = data formData.value.tableData = data
const definitions = await Promise.all(data.map(async (item) => { runtimeAssignments.value = []
const response = await getDetect(item.itemId) roles.value = await loadFlowItemRuntimeRoles(data)
return response.data?.flowData
? getResourceRoles(normalizeFlowResourceSchema(JSON.parse(response.data.flowData)))
: []
}))
roles.value = mergeResourceRoleDefinitions(definitions)
} }
}; };
@ -131,7 +128,7 @@ const confirm = () => {
ElMessage.warning(assignmentError) ElMessage.warning(assignmentError)
return return
} }
const robotId = runtimeAssignments.value.flatMap(item => item.members).find(member => member.robotId)?.robotId || '' const robotId = firstAssignedRobotId(runtimeAssignments.value)
const res = await projectExecute({ const res = await projectExecute({
runParams, runParams,
projectId: props.projectId, projectId: props.projectId,
@ -160,7 +157,11 @@ watch(
() => props.drawer, () => props.drawer,
(newVal) => { (newVal) => {
if (newVal) { if (newVal) {
getProjectParams() getProjectParams().catch((error) => {
roles.value = []
runtimeAssignments.value = []
ElMessage.error(error.message || '工作流执行资源加载失败')
})
} }
} }
); );

View File

@ -68,9 +68,11 @@ import { emitter } from "@/utils/eventBus";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { getFlowParamsApi } from "@/api/device/terminal.js"; import { getFlowParamsApi } from "@/api/device/terminal.js";
import { taskExecute } from "@/api/test/config" import { taskExecute } from "@/api/test/config"
import { getDetect } from '@/api/test/detect'
import RuntimeAssignmentsEditor from '@/views/flow/components/RuntimeAssignmentsEditor.vue' import RuntimeAssignmentsEditor from '@/views/flow/components/RuntimeAssignmentsEditor.vue'
import { getResourceRoles, mergeResourceRoleDefinitions, normalizeFlowResourceSchema } from '@/utils/flowResources' import {
firstAssignedRobotId,
loadFlowItemRuntimeRoles,
} from '@/utils/flowExecution'
const props = defineProps({ const props = defineProps({
drawer: Boolean, drawer: Boolean,
@ -101,12 +103,8 @@ const getFlowParams = async () => {
if (res.code === 200) { if (res.code === 200) {
const data = parseConfig(res.rows) const data = parseConfig(res.rows)
formData.value.tableData = data formData.value.tableData = data
const definitions = await Promise.all(data.map(async (item) => { runtimeAssignments.value = []
const response = await getDetect(item.itemId) roles.value = await loadFlowItemRuntimeRoles(data)
if (!response.data?.flowData) return []
return getResourceRoles(normalizeFlowResourceSchema(JSON.parse(response.data.flowData)))
}))
roles.value = mergeResourceRoleDefinitions(definitions)
} }
}; };
@ -135,7 +133,7 @@ const confirm = () => {
ElMessage.warning(assignmentError) ElMessage.warning(assignmentError)
return return
} }
const robotId = runtimeAssignments.value.flatMap(item => item.members).find(member => member.robotId)?.robotId || '' const robotId = firstAssignedRobotId(runtimeAssignments.value)
const res = await taskExecute({ const res = await taskExecute({
runParams, runParams,
taskId: props.currentRow.id, taskId: props.currentRow.id,
@ -159,7 +157,11 @@ watch(
() => props.drawer, () => props.drawer,
(newVal) => { (newVal) => {
if (newVal) { if (newVal) {
getFlowParams() getFlowParams().catch((error) => {
roles.value = []
runtimeAssignments.value = []
ElMessage.error(error.message || '工作流执行资源加载失败')
})
} }
} }
); );

View File

@ -341,17 +341,14 @@ import {
getTaskParamsById, getTaskParamsById,
listConfig, listConfig,
taskConfig, taskConfig,
taskExecute,
updateConfig, updateConfig,
} from "@/api/test/config"; } from "@/api/test/config";
import { flowExecute } from "@/api/flow/flow";
import TableSearch from "@/components/TableSearch/index.vue"; import TableSearch from "@/components/TableSearch/index.vue";
import draggable from "vuedraggable"; import draggable from "vuedraggable";
import { listDetect } from "@/api/test/detect.js"; import { listDetect } from "@/api/test/detect.js";
import { Tools, Delete } from "@element-plus/icons-vue"; import { Tools, Delete } from "@element-plus/icons-vue";
import { listGroup } from "@/api/system/group.js"; import { listGroup } from "@/api/system/group.js";
import { listTerminal } from "@/api/device/terminal.js";
import Run from "./Run.vue"; import Run from "./Run.vue";
import { ref, reactive, toRefs, onMounted } from "vue"; import { ref, reactive, toRefs, onMounted } from "vue";
@ -415,7 +412,6 @@ function cancel() {
taskExecuteOpen.value = false; taskExecuteOpen.value = false;
taskConfigOpen.value = false; taskConfigOpen.value = false;
executeOpen.value = false; executeOpen.value = false;
selectedTerminal.value = "";
reset(); reset();
} }
@ -590,46 +586,6 @@ const submitTaskForm = () => {
}); });
}; };
const formatCode = (arr) => {
return arr.map((item) => ({
itemId: item.detectId,
params: item.params.reduce((acc, param) => {
acc[param.paramEnName] = param.sourceValue;
return acc;
}, {}),
}));
};
const handleTaskExecute = (formEl) => {
if (!selectedTerminal.value) {
proxy.$modal.msgError("请选择执行终端");
return;
}
const data = Object.assign(
{},
{ runParams: { robotId: selectedTerminal.value } },
{ taskId: currentRow.value.id }
);
// taskExecute(data).then((res) => {
// proxy.$modal.msgSuccess("下发成功")
// resetTaskList()
// executeOpen.value = false
// taskExecuteOpen.value = false
// currentRow.value = undefined
// selectedTerminal.value = ""
// })
flowExecute(data).then((res) => {
console.log("res", res);
proxy.$modal.msgSuccess("下发成功");
resetTaskList();
executeOpen.value = false;
taskExecuteOpen.value = false;
currentRow.value = undefined;
selectedTerminal.value = "";
});
};
const handleTaskExecuteOpen = (row) => { const handleTaskExecuteOpen = (row) => {
currentRow.value = row; currentRow.value = row;
executeOpen.value = true; executeOpen.value = true;
@ -661,24 +617,6 @@ function handleGetSystemGroup(row) {
}); });
} }
const terminalGroup = ref([]);
const selectedTerminal = ref("");
/** 获取设备终端列表 */
function handleGetTerminalGroup(row) {
listTerminal({
pageNum: 1,
pageSize: 1000,
}).then((response) => {
terminalGroup.value = response.rows?.map((item) => {
return {
label: `${item.name}(${item.host}:${item.port})`,
value: item.id,
};
});
});
}
const removeFromB = (id) => { const removeFromB = (id) => {
const index = taskRightList.value.findIndex(item => item.id === id) const index = taskRightList.value.findIndex(item => item.id === id)
if (index !== -1) taskRightList.value.splice(index, 1) if (index !== -1) taskRightList.value.splice(index, 1)

View File

@ -68,9 +68,11 @@ import { ref, watch } from "vue";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { addValueProperty, formatTableData } from "@/utils/flow"; import { addValueProperty, formatTableData } from "@/utils/flow";
import { getExecuteRunParams, projectExecute } from "@/api/vi/project.js" import { getExecuteRunParams, projectExecute } from "@/api/vi/project.js"
import { getDetect } from '@/api/test/detect'
import RuntimeAssignmentsEditor from '@/views/flow/components/RuntimeAssignmentsEditor.vue' import RuntimeAssignmentsEditor from '@/views/flow/components/RuntimeAssignmentsEditor.vue'
import { getResourceRoles, mergeResourceRoleDefinitions, normalizeFlowResourceSchema } from '@/utils/flowResources' import {
firstAssignedRobotId,
loadFlowItemRuntimeRoles,
} from '@/utils/flowExecution'
const props = defineProps({ const props = defineProps({
drawer: Boolean, drawer: Boolean,
@ -94,13 +96,8 @@ const getProjectParams = async () => {
if (res.code === 200) { if (res.code === 200) {
const data = parseConfig(res.data) const data = parseConfig(res.data)
formData.value.tableData = data formData.value.tableData = data
const definitions = await Promise.all(data.map(async (item) => { runtimeAssignments.value = []
const response = await getDetect(item.itemId) roles.value = await loadFlowItemRuntimeRoles(data)
return response.data?.flowData
? getResourceRoles(normalizeFlowResourceSchema(JSON.parse(response.data.flowData)))
: []
}))
roles.value = mergeResourceRoleDefinitions(definitions)
} }
}; };
@ -129,7 +126,7 @@ const confirm = () => {
ElMessage.warning(assignmentError) ElMessage.warning(assignmentError)
return return
} }
const robotId = runtimeAssignments.value.flatMap(item => item.members).find(member => member.robotId)?.robotId || '' const robotId = firstAssignedRobotId(runtimeAssignments.value)
const res = await projectExecute({ const res = await projectExecute({
runParams, runParams,
projectId: props.projectId, projectId: props.projectId,
@ -158,7 +155,11 @@ watch(
() => props.drawer, () => props.drawer,
(newVal) => { (newVal) => {
if (newVal) { if (newVal) {
getProjectParams() getProjectParams().catch((error) => {
roles.value = []
runtimeAssignments.value = []
ElMessage.error(error.message || '工作流执行资源加载失败')
})
} }
} }
); );