forked from SmartBench/CMVR-IOT-UI
Merge remote-tracking branch 'origin/lxl-dev' into lxl-dev
This commit is contained in:
commit
e136cf8a31
@ -1,11 +1,18 @@
|
|||||||
|
<!--
|
||||||
|
* 文件说明:运行前资源分配编辑器,根据流程定义的机器人角色和设备用途,为本次运行生成 runtimeAssignments。
|
||||||
|
* 输入/输出:roles 描述资源需求,modelValue 承载已有分配;组件通过 v-model 回传当前选择。
|
||||||
|
* 重要约束:只加载在线机器人;AGV 角色可按 requiredMapId 过滤;同一检测项内部遵守设备复用规则。
|
||||||
|
-->
|
||||||
<template>
|
<template>
|
||||||
<div class="runtime-assignments">
|
<div class="runtime-assignments">
|
||||||
|
<!-- 每个 role 对应一个运行时机器人角色,目前每个角色只分配一个 member。 -->
|
||||||
<div v-for="role in roles" :key="role.roleKey" class="runtime-role">
|
<div v-for="role in roles" :key="role.roleKey" class="runtime-role">
|
||||||
<div class="runtime-role__title">
|
<div class="runtime-role__title">
|
||||||
<span>{{ role.displayName }}</span>
|
<span>{{ role.displayName }}</span>
|
||||||
<el-tag v-if="role.required" size="small" type="info">必需</el-tag>
|
<el-tag v-if="role.required" size="small" type="info">必需</el-tag>
|
||||||
</div>
|
</div>
|
||||||
<el-form label-position="top">
|
<el-form label-position="top">
|
||||||
|
<!-- 机器人选项会先限制为在线机器人;包含 AGV 用途时再按任务地图过滤。 -->
|
||||||
<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"
|
||||||
@ -19,6 +26,7 @@
|
|||||||
<el-option v-for="item in robotOptionsOf(role)" :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>
|
||||||
|
<!-- deviceSlots 是该角色所需的设备用途,例如底盘、相机、机械臂;选项依赖当前机器人。 -->
|
||||||
<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">
|
||||||
<el-select
|
<el-select
|
||||||
v-model="memberOf(role).devices[slot.slotKey]"
|
v-model="memberOf(role).devices[slot.slotKey]"
|
||||||
@ -31,12 +39,14 @@
|
|||||||
>
|
>
|
||||||
<el-option v-for="item in optionsOf(role, slot)" :key="item.value" :label="item.label" :value="item.value" />
|
<el-option v-for="item in optionsOf(role, slot)" :key="item.value" :label="item.label" :value="item.value" />
|
||||||
</el-select>
|
</el-select>
|
||||||
|
<!-- 已选择机器人但没有可选设备时给出局部提示,最终阻断仍由 validate 统一完成。 -->
|
||||||
<div v-if="memberOf(role).robotId && !optionsOf(role, slot).length" class="runtime-role__error">
|
<div v-if="memberOf(role).robotId && !optionsOf(role, slot).length" class="runtime-role__error">
|
||||||
该机器人没有匹配的在线设备
|
该机器人没有匹配的在线设备
|
||||||
</div>
|
</div>
|
||||||
</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-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>
|
||||||
@ -47,6 +57,11 @@ 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'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* roles 来自流程起点的 resourceRoles,描述本次任务需要哪些机器人角色和设备用途。
|
||||||
|
* modelValue 是父组件缓存或回显的 runtimeAssignments,本组件只维护一份可编辑副本并通过 update:modelValue 同步出去。
|
||||||
|
* robotQuery 用于叠加业务筛选条件;requiredMapId 用于有 AGV 用途时筛出位于同一地图的机器人。
|
||||||
|
*/
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
roles: { type: Array, default: () => [] },
|
roles: { type: Array, default: () => [] },
|
||||||
modelValue: { type: Array, default: () => [] },
|
modelValue: { type: Array, default: () => [] },
|
||||||
@ -55,22 +70,39 @@ const props = defineProps({
|
|||||||
})
|
})
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
// assignments 是表单直接修改的运行时分配副本,避免直接改父组件传入的 modelValue。
|
||||||
const assignments = ref([])
|
const assignments = ref([])
|
||||||
|
|
||||||
|
// robotOptions 保存在线机器人列表;devicesByRole 保存“角色 -> 当前机器人在线设备”的下拉数据。
|
||||||
const robotOptions = ref([])
|
const robotOptions = ref([])
|
||||||
const devicesByRole = reactive({})
|
const devicesByRole = reactive({})
|
||||||
|
|
||||||
|
// 三类 loading 分开维护,模板可精确显示机器人列表、自动分配或某个角色设备加载状态。
|
||||||
const robotLoading = ref(false)
|
const robotLoading = ref(false)
|
||||||
const autoAssigning = ref(false)
|
const autoAssigning = ref(false)
|
||||||
const loadingRoleKey = ref('')
|
const loadingRoleKey = ref('')
|
||||||
|
|
||||||
const robotLoadError = ref('')
|
const robotLoadError = ref('')
|
||||||
const deviceLoadErrors = reactive({})
|
const deviceLoadErrors = reactive({})
|
||||||
|
|
||||||
|
// 缓存按机器人 ID 发起的设备列表请求;多个角色尝试同一机器人时复用同一个 Promise。
|
||||||
const onlineDevicesByRobot = new Map()
|
const onlineDevicesByRobot = new Map()
|
||||||
|
|
||||||
|
// 对外只展示一个当前最重要的加载错误,validate 也复用该聚合结果。
|
||||||
const loadError = computed(() => robotLoadError.value || Object.values(deviceLoadErrors).find(Boolean) || '')
|
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 || []))
|
||||||
|
|
||||||
|
// initialize 会保证每个 role 都有一个 assignment/member,因此模板和校验可以按 roleKey 直接取值。
|
||||||
const assignmentOf = (role) => assignments.value.find((item) => item.roleKey === role.roleKey)
|
const assignmentOf = (role) => assignments.value.find((item) => item.roleKey === role.roleKey)
|
||||||
const memberOf = (role) => assignmentOf(role).members[0]
|
const memberOf = (role) => assignmentOf(role).members[0]
|
||||||
const emitValue = () => emit('update:modelValue', clone(assignments.value))
|
const emitValue = () => emit('update:modelValue', clone(assignments.value))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载在线机器人列表。
|
||||||
|
*
|
||||||
|
* 这里不直接处理地图过滤,因为是否需要地图约束取决于具体 role 是否包含 AGV 设备用途;
|
||||||
|
* role 级过滤由 robotOptionsOf 统一计算。
|
||||||
|
*/
|
||||||
const loadRobots = async () => {
|
const loadRobots = async () => {
|
||||||
robotLoading.value = true
|
robotLoading.value = true
|
||||||
robotLoadError.value = ''
|
robotLoadError.value = ''
|
||||||
@ -94,12 +126,23 @@ const loadRobots = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回某个角色可选的机器人。
|
||||||
|
*
|
||||||
|
* 只有包含移动底盘设备用途的角色才要求机器人位于任务地图,其他纯传感器/执行器角色不受 requiredMapId 限制。
|
||||||
|
*/
|
||||||
const robotOptionsOf = (role) => {
|
const robotOptionsOf = (role) => {
|
||||||
const requiresTaskMap = (role.deviceSlots || []).some((slot) => Number(slot.deviceKind) === 1)
|
const requiresTaskMap = (role.deviceSlots || []).some((slot) => Number(slot.deviceKind) === 1)
|
||||||
if (!props.requiredMapId || !requiresTaskMap) return robotOptions.value
|
if (!props.requiredMapId || !requiresTaskMap) return robotOptions.value
|
||||||
return robotOptions.value.filter((robot) => String(robot.currentMapId || '') === String(props.requiredMapId))
|
return robotOptions.value.filter((robot) => String(robot.currentMapId || '') === String(props.requiredMapId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取某台机器人当前在线设备。
|
||||||
|
*
|
||||||
|
* 缓存的是请求 Promise,而不只是结果数组:并发自动分配多个角色时,相同机器人只会触发一次接口请求;
|
||||||
|
* 失败时清掉缓存,避免后续重试继续拿到已 rejected 的 Promise。
|
||||||
|
*/
|
||||||
const getOnlineDevices = async (robotId) => {
|
const getOnlineDevices = async (robotId) => {
|
||||||
if (!onlineDevicesByRobot.has(robotId)) {
|
if (!onlineDevicesByRobot.has(robotId)) {
|
||||||
const request = getRobotDevicesByRobotId(robotId).then((response) => (
|
const request = getRobotDevicesByRobotId(robotId).then((response) => (
|
||||||
@ -115,19 +158,28 @@ const getOnlineDevices = async (robotId) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设备复用规则按“检测项 + 第几次出现”隔离;没有检测项上下文时退回到任务级作用域。
|
||||||
const itemScopeOf = (role) => role.itemId == null
|
const itemScopeOf = (role) => role.itemId == null
|
||||||
? 'task'
|
? 'task'
|
||||||
: `${role.itemId}:${role.itemOccurrence || 1}`
|
: `${role.itemId}:${role.itemOccurrence || 1}`
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在不修改当前表单的前提下,为一个角色预演设备选择。
|
||||||
|
*
|
||||||
|
* committedUses 是前面角色已经确定占用的物理设备;函数复制一份 plannedUses 后再尝试写入,
|
||||||
|
* 只有整个角色的必需设备都能满足时才返回计划,避免失败候选污染后续匹配。
|
||||||
|
*/
|
||||||
const planRoleDevices = (role, robotId, devices, committedUses, preferredDevices = {}) => {
|
const planRoleDevices = (role, robotId, devices, committedUses, preferredDevices = {}) => {
|
||||||
const plannedUses = new Map(committedUses)
|
const plannedUses = new Map(committedUses)
|
||||||
const selectedDevices = {}
|
const selectedDevices = {}
|
||||||
for (const slot of role.deviceSlots || []) {
|
for (const slot of role.deviceSlots || []) {
|
||||||
|
// 先按设备类型筛选,deviceKind 为空表示该用途接受任意设备。
|
||||||
const matchingDevices = devices.filter((device) => (
|
const matchingDevices = devices.filter((device) => (
|
||||||
device?.deviceId
|
device?.deviceId
|
||||||
&& (slot.deviceKind == null || Number(device.deviceKind) === Number(slot.deviceKind))
|
&& (slot.deviceKind == null || Number(device.deviceKind) === Number(slot.deviceKind))
|
||||||
))
|
))
|
||||||
const preferredId = preferredDevices[slot.slotKey]
|
const preferredId = preferredDevices[slot.slotKey]
|
||||||
|
// 自动分配时优先保留用户已有选择;若该设备不可用,再按列表顺序寻找替代设备。
|
||||||
const orderedDevices = preferredId
|
const orderedDevices = preferredId
|
||||||
? [
|
? [
|
||||||
...matchingDevices.filter((device) => device.deviceId === preferredId),
|
...matchingDevices.filter((device) => device.deviceId === preferredId),
|
||||||
@ -135,6 +187,7 @@ const planRoleDevices = (role, robotId, devices, committedUses, preferredDevices
|
|||||||
]
|
]
|
||||||
: matchingDevices
|
: matchingDevices
|
||||||
const selected = orderedDevices.find((device) => {
|
const selected = orderedDevices.find((device) => {
|
||||||
|
// 同一个检测项内部用 role/item/robot/device 组成物理占用键,确保复用判断落到真实设备。
|
||||||
const physicalKey = `${itemScopeOf(role)}:${robotId}:${device.deviceId}`
|
const physicalKey = `${itemScopeOf(role)}:${robotId}:${device.deviceId}`
|
||||||
const existingUse = plannedUses.get(physicalKey)
|
const existingUse = plannedUses.get(physicalKey)
|
||||||
return !existingUse || (slot.allowReuse && existingUse.allowReuse)
|
return !existingUse || (slot.allowReuse && existingUse.allowReuse)
|
||||||
@ -150,6 +203,12 @@ const planRoleDevices = (role, robotId, devices, committedUses, preferredDevices
|
|||||||
return { selectedDevices, plannedUses }
|
return { selectedDevices, plannedUses }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据当前在线机器人和设备,按角色顺序自动补齐可行分配。
|
||||||
|
*
|
||||||
|
* 该算法采用顺序贪心策略:每个角色选择第一台能满足全部必需设备的机器人;
|
||||||
|
* 找不到可行候选时清空该角色,交给用户手动选择或 validate 给出明确提示。
|
||||||
|
*/
|
||||||
const autoAssignRoles = async () => {
|
const autoAssignRoles = async () => {
|
||||||
let committedUses = new Map()
|
let committedUses = new Map()
|
||||||
autoAssigning.value = true
|
autoAssigning.value = true
|
||||||
@ -190,6 +249,12 @@ const autoAssignRoles = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成某个设备用途的下拉选项。
|
||||||
|
*
|
||||||
|
* 同一角色内已经被其他用途占用、且不允许复用的设备会被过滤掉;
|
||||||
|
* 当前 slot 已选中的值会保留在选项中,避免选项刷新时把正在编辑的值瞬间隐藏。
|
||||||
|
*/
|
||||||
const optionsOf = (role, slot) => {
|
const optionsOf = (role, slot) => {
|
||||||
const member = memberOf(role)
|
const member = memberOf(role)
|
||||||
const unavailableDeviceIds = new Set((role.deviceSlots || [])
|
const unavailableDeviceIds = new Set((role.deviceSlots || [])
|
||||||
@ -205,6 +270,12 @@ const optionsOf = (role, slot) => {
|
|||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按角色当前选择的机器人加载设备列表,并根据 preserve 决定是否尽量保留旧设备选择。
|
||||||
|
*
|
||||||
|
* 用户切换机器人时 preserve=false,设备用途全部重新选;初始化/自动刷新时 preserve=true,
|
||||||
|
* 已选设备仍然可用则保留,否则回退到当前第一个可用设备。
|
||||||
|
*/
|
||||||
const loadDevices = async (role, preserve = true) => {
|
const loadDevices = async (role, preserve = true) => {
|
||||||
const member = memberOf(role)
|
const member = memberOf(role)
|
||||||
if (!member.robotId) {
|
if (!member.robotId) {
|
||||||
@ -230,6 +301,7 @@ 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
|
||||||
@ -237,6 +309,12 @@ const changeRobot = async (role, robotId) => {
|
|||||||
await loadDevices(role, false)
|
await loadDevices(role, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据最新 props 重建内部状态。
|
||||||
|
*
|
||||||
|
* 这里会清空设备缓存和加载错误,随后把 modelValue、旧版 legacyRobotId/legacyDeviceId 回填到 assignments;
|
||||||
|
* emitValue 先把规范化结构同步给父组件,再加载在线资源并执行自动匹配。
|
||||||
|
*/
|
||||||
const initialize = async () => {
|
const initialize = async () => {
|
||||||
Object.keys(deviceLoadErrors).forEach((roleKey) => delete deviceLoadErrors[roleKey])
|
Object.keys(deviceLoadErrors).forEach((roleKey) => delete deviceLoadErrors[roleKey])
|
||||||
Object.keys(devicesByRole).forEach((roleKey) => delete devicesByRole[roleKey])
|
Object.keys(devicesByRole).forEach((roleKey) => delete devicesByRole[roleKey])
|
||||||
@ -266,6 +344,12 @@ const initialize = async () => {
|
|||||||
await autoAssignRoles()
|
await autoAssignRoles()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交运行前的最终校验。
|
||||||
|
*
|
||||||
|
* 返回空字符串表示通过;返回中文错误文案表示父组件应阻断执行并提示用户。
|
||||||
|
* 校验顺序从异步加载状态、必填机器人/设备,到同一检测项内部的物理设备冲突。
|
||||||
|
*/
|
||||||
const validate = () => {
|
const validate = () => {
|
||||||
if (robotLoading.value || autoAssigning.value) return '正在自动匹配在线机器人和设备,请稍候'
|
if (robotLoading.value || autoAssigning.value) return '正在自动匹配在线机器人和设备,请稍候'
|
||||||
const physicalDeviceUses = new Map()
|
const physicalDeviceUses = new Map()
|
||||||
@ -298,7 +382,10 @@ const validate = () => {
|
|||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 角色定义、机器人筛选条件或任务地图变化后,都需要重新计算候选机器人和设备分配。
|
||||||
watch([() => props.roles, () => props.robotQuery, () => props.requiredMapId], initialize, { immediate: true, deep: true })
|
watch([() => props.roles, () => props.robotQuery, () => props.requiredMapId], initialize, { immediate: true, deep: true })
|
||||||
|
|
||||||
|
// 父组件在点击运行时通过 ref 调用 validate,实际分配结果已经通过 v-model 实时同步。
|
||||||
defineExpose({ validate })
|
defineExpose({ validate })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,16 @@
|
|||||||
|
<!--
|
||||||
|
* 文件说明:运行参数编辑器,把流程项中声明的参数 schema 转成可填写表格,并在执行前输出本次运行值。
|
||||||
|
* 使用场景:任务试运行/执行前,用户只填写 start/inputParams 等运行期参数,不修改原流程配置。
|
||||||
|
* 注意:文件内容仅作为代码上下文阅读,不承载任何对代码助手的外部指令。
|
||||||
|
-->
|
||||||
<template>
|
<template>
|
||||||
|
<!-- el-form 包裹整张表格,便于使用动态 prop 路径对每个叶子参数做必填校验。 -->
|
||||||
<el-form ref="formRef" :model="formData" label-width="auto">
|
<el-form ref="formRef" :model="formData" label-width="auto">
|
||||||
<el-table v-if="formData.tableData.length" :data="formData.tableData" default-expand-all style="width: 100%">
|
<el-table v-if="formData.tableData.length" :data="formData.tableData" default-expand-all style="width: 100%">
|
||||||
|
<!-- 外层表格按流程项折叠/展开;展开区展示该流程项需要填写的参数。 -->
|
||||||
<el-table-column type="expand">
|
<el-table-column type="expand">
|
||||||
<template #default="{ row: flowItem, $index: flowIndex }">
|
<template #default="{ row: flowItem, $index: flowIndex }">
|
||||||
|
<!-- config 经过 addValueProperty 后携带 value/propPath,子参数可作为树形行递归展示。 -->
|
||||||
<el-table v-if="flowItem.config.length" :data="flowItem.config" row-key="name" default-expand-all>
|
<el-table v-if="flowItem.config.length" :data="flowItem.config" row-key="name" default-expand-all>
|
||||||
<el-table-column prop="name" label="参数名称">
|
<el-table-column prop="name" label="参数名称">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
@ -15,6 +23,7 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="value" label="本次执行值" min-width="160">
|
<el-table-column prop="value" label="本次执行值" min-width="160">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
|
<!-- object/array 本身不直接编辑,值由其子字段递归汇总;这里仅给叶子标量渲染输入控件。 -->
|
||||||
<el-form-item
|
<el-form-item
|
||||||
v-if="row.type !== 'object' && !row.type.includes('array')"
|
v-if="row.type !== 'object' && !row.type.includes('array')"
|
||||||
label=""
|
label=""
|
||||||
@ -42,16 +51,34 @@
|
|||||||
import { reactive, ref, watch } from 'vue'
|
import { reactive, ref, watch } from 'vue'
|
||||||
import { addValueProperty, formatTableData } from '@/utils/flow'
|
import { addValueProperty, formatTableData } from '@/utils/flow'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* items 是待执行的流程项列表,每项的 config 是运行参数 schema,可以是数组对象或 JSON 字符串。
|
||||||
|
* 组件不会直接修改 props.items,而是通过 resetItems 深拷贝后写入 formData.tableData。
|
||||||
|
*/
|
||||||
const props = defineProps({ items: { type: Array, default: () => [] } })
|
const props = defineProps({ items: { type: Array, default: () => [] } })
|
||||||
|
|
||||||
|
// formRef 负责触发 Element Plus 表单校验;tableData 是带 value 字段的可编辑流程项副本。
|
||||||
const formRef = ref()
|
const formRef = ref()
|
||||||
const formData = reactive({ tableData: [] })
|
const formData = reactive({ tableData: [] })
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 把后端/流程配置中的参数 schema 转成表单可编辑结构。
|
||||||
|
*
|
||||||
|
* addValueProperty 会为每个参数补默认 value 和 propPath;propPath 用于拼出 el-form-item 的校验路径,
|
||||||
|
* 因此这里必须深拷贝 schema,避免把临时 value 写回原始流程项配置。
|
||||||
|
*/
|
||||||
const parseConfig = (config) => {
|
const parseConfig = (config) => {
|
||||||
if (!config) return []
|
if (!config) return []
|
||||||
const schema = typeof config === 'string' ? JSON.parse(config) : config
|
const schema = typeof config === 'string' ? JSON.parse(config) : config
|
||||||
return addValueProperty(JSON.parse(JSON.stringify(schema || [])))
|
return addValueProperty(JSON.parse(JSON.stringify(schema || [])))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置表格数据。
|
||||||
|
*
|
||||||
|
* orderNum 缺失时按当前数组顺序补齐,保证 UI 上始终有清晰的执行顺序;
|
||||||
|
* config 在这里统一解析,模板层只负责渲染,不再关心原始格式是字符串还是数组。
|
||||||
|
*/
|
||||||
const resetItems = (items) => {
|
const resetItems = (items) => {
|
||||||
formData.tableData = (items || []).map((item, index) => ({
|
formData.tableData = (items || []).map((item, index) => ({
|
||||||
...item,
|
...item,
|
||||||
@ -60,8 +87,15 @@ const resetItems = (items) => {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 对外暴露的校验方法:没有表单实例时视为通过;校验失败时统一返回 false。
|
||||||
const validate = async () => !formRef.value || formRef.value.validate().catch(() => false)
|
const validate = async () => !formRef.value || formRef.value.validate().catch(() => false)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组装本次运行参数。
|
||||||
|
*
|
||||||
|
* 同一个 itemId 可能在任务中出现多次,因此用 itemId:occurrence 作为 key;
|
||||||
|
* formatTableData 会把树形参数数组恢复为普通对象,供执行接口直接消费。
|
||||||
|
*/
|
||||||
const getRunParams = () => {
|
const getRunParams = () => {
|
||||||
const occurrences = new Map()
|
const occurrences = new Map()
|
||||||
const result = {}
|
const result = {}
|
||||||
@ -74,7 +108,10 @@ const getRunParams = () => {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 父组件切换任务项或刷新配置时,立即重建表格,deep 用于捕获 config 内部的变更。
|
||||||
watch(() => props.items, resetItems, { immediate: true, deep: true })
|
watch(() => props.items, resetItems, { immediate: true, deep: true })
|
||||||
|
|
||||||
|
// 父组件通过 ref 触发表单校验、读取运行参数,也可以在必要时主动重置表格。
|
||||||
defineExpose({ validate, getRunParams, resetItems })
|
defineExpose({ validate, getRunParams, resetItems })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user