feat(flow): 添加流程资源配置和执行目标管理功能
- 创建 flowResources 工具模块,包含资源角色创建、设备槽位管理等功能 - 实现机器人执行目标选择组件,支持角色和设备分配设置 - 开发运行时分配编辑器,提供机器人和设备绑定界面 - 定义设备类型元数据和资源验证逻辑 - 实现流程图资源模式标准化和迁移功能
This commit is contained in:
parent
0df5280ebf
commit
289dd678d0
261
src/utils/flowResources.js
Normal file
261
src/utils/flowResources.js
Normal file
@ -0,0 +1,261 @@
|
||||
import { getDeviceKindForAction } from '@/utils/robotDevice'
|
||||
|
||||
export const FLOW_SCHEMA_VERSION = 2
|
||||
export const DEFAULT_ROLE_KEY = 'default_executor'
|
||||
|
||||
const DEVICE_KIND_META = Object.freeze({
|
||||
1: { key: 'agv', name: '移动底盘' },
|
||||
2: { key: 'arm', name: '机械臂' },
|
||||
4: { key: 'bio_head', name: '仿生头' },
|
||||
5: { key: 'camera', name: '摄像头' },
|
||||
9: { key: 'microphone', name: '麦克风' },
|
||||
12: { key: 'robot', name: '机器人本体' },
|
||||
13: { key: 'speaker', name: '扬声器' },
|
||||
})
|
||||
|
||||
const clone = (value) => JSON.parse(JSON.stringify(value ?? null))
|
||||
const normalizedText = (value) => String(value || '').trim()
|
||||
|
||||
export const createResourceKey = (prefix) => {
|
||||
const random = globalThis.crypto?.randomUUID?.().replace(/-/g, '').slice(0, 10)
|
||||
|| `${Date.now()}${Math.random().toString(16).slice(2, 8)}`
|
||||
return `${prefix}_${random}`
|
||||
}
|
||||
|
||||
export const createDefaultRobotRole = (legacyRobotId = '') => ({
|
||||
roleKey: DEFAULT_ROLE_KEY,
|
||||
resourceType: 'ROBOT',
|
||||
displayName: '执行机器人',
|
||||
required: true,
|
||||
quantity: { min: 1, max: 1 },
|
||||
deviceSlots: [],
|
||||
...(legacyRobotId ? { legacyRobotId } : {}),
|
||||
})
|
||||
|
||||
export const createDeviceSlot = (deviceKind, existingSlots = [], displayName = '') => {
|
||||
const meta = DEVICE_KIND_META[Number(deviceKind)] || { key: 'device', name: '执行设备' }
|
||||
const usedKeys = new Set(existingSlots.map((slot) => slot.slotKey))
|
||||
let index = 1
|
||||
let slotKey = meta.key
|
||||
while (usedKeys.has(slotKey)) slotKey = `${meta.key}_${++index}`
|
||||
const sameKindCount = existingSlots.filter((slot) => Number(slot.deviceKind) === Number(deviceKind)).length
|
||||
return {
|
||||
slotKey,
|
||||
displayName: displayName || `${meta.name}${sameKindCount ? sameKindCount + 1 : ''}`,
|
||||
deviceKind: deviceKind == null ? null : Number(deviceKind),
|
||||
required: true,
|
||||
allowReuse: false,
|
||||
}
|
||||
}
|
||||
|
||||
export const getStartNode = (graph = globalThis.lf?.getGraphData?.()) => (
|
||||
graph?.nodes?.find((node) => node.type === 'start')
|
||||
)
|
||||
|
||||
export const getResourceRoles = (graph = globalThis.lf?.getGraphData?.()) => (
|
||||
clone(getStartNode(graph)?.properties?.resourceRoles || []) || []
|
||||
)
|
||||
|
||||
const isRobotNode = (node) => node?.properties?.nodeType === 'EDGE'
|
||||
export const actionRequiresDevice = (action) => (
|
||||
getDeviceKindForAction(action) != null || action === 'DEVICE_EXECUTE_JSON_COMMAND'
|
||||
)
|
||||
export const nodeRequiresDevice = (node) => actionRequiresDevice(node?.properties?.action)
|
||||
|
||||
const resolveLegacyRobotId = (startProperties) => (
|
||||
startProperties?.inputParams?.find((item) => item.name === 'robotId')?.input
|
||||
|| startProperties?.robotId
|
||||
|| ''
|
||||
)
|
||||
|
||||
const findOrCreateLegacySlot = (role, node, legacySlotByIdentity, legacyDeviceId = '') => {
|
||||
const kind = getDeviceKindForAction(node.properties?.action)
|
||||
const normalizedDeviceId = normalizedText(legacyDeviceId)
|
||||
const identity = `${role.roleKey}:${kind ?? 'any'}:${normalizedDeviceId || 'default'}`
|
||||
if (legacySlotByIdentity.has(identity)) return legacySlotByIdentity.get(identity)
|
||||
|
||||
const slot = createDeviceSlot(kind, role.deviceSlots)
|
||||
if (normalizedDeviceId) slot.legacyDeviceId = normalizedDeviceId
|
||||
role.deviceSlots.push(slot)
|
||||
legacySlotByIdentity.set(identity, slot)
|
||||
return slot
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads old graphs as Schema V2 without mutating the source object. Physical IDs are
|
||||
* retained only as migration hints and are consumed by the trial-run assignment form.
|
||||
*/
|
||||
export const normalizeFlowResourceSchema = (sourceGraph) => {
|
||||
const graph = clone(sourceGraph || { nodes: [], edges: [] })
|
||||
const start = getStartNode(graph)
|
||||
if (!start) return graph
|
||||
|
||||
const startProperties = start.properties || (start.properties = {})
|
||||
const legacyRobotId = resolveLegacyRobotId(startProperties)
|
||||
const roles = Array.isArray(startProperties.resourceRoles) && startProperties.resourceRoles.length
|
||||
? startProperties.resourceRoles
|
||||
: [createDefaultRobotRole(legacyRobotId)]
|
||||
|
||||
roles.forEach((role, index) => {
|
||||
role.roleKey ||= index === 0 ? DEFAULT_ROLE_KEY : createResourceKey('robot_role')
|
||||
role.resourceType = 'ROBOT'
|
||||
role.displayName ||= `机器人角色${index + 1}`
|
||||
role.required = role.required !== false
|
||||
role.quantity ||= { min: 1, max: 1 }
|
||||
role.deviceSlots = Array.isArray(role.deviceSlots) ? role.deviceSlots : []
|
||||
})
|
||||
|
||||
startProperties.schemaVersion = FLOW_SCHEMA_VERSION
|
||||
startProperties.resourceRoles = roles
|
||||
startProperties.inputParams = (startProperties.inputParams || []).filter((item) => item.name !== 'robotId')
|
||||
|
||||
const defaultRole = roles[0]
|
||||
const legacySlotByIdentity = new Map()
|
||||
roles.forEach((role) => role.deviceSlots.forEach((slot) => {
|
||||
const identity = `${role.roleKey}:${slot.deviceKind ?? 'any'}:${slot.legacyDeviceId || 'default'}`
|
||||
legacySlotByIdentity.set(identity, slot)
|
||||
}))
|
||||
|
||||
graph.nodes.forEach((node) => {
|
||||
if (!isRobotNode(node)) return
|
||||
const properties = node.properties || (node.properties = {})
|
||||
const target = properties.executionTarget || {
|
||||
roleKey: defaultRole.roleKey,
|
||||
dispatchPolicy: 'ONE',
|
||||
}
|
||||
target.roleKey ||= defaultRole.roleKey
|
||||
target.dispatchPolicy ||= 'ONE'
|
||||
target.deviceSlotKey ||= target.deviceBindings?.[0]?.slotKey || ''
|
||||
|
||||
const targetRole = roles.find((role) => role.roleKey === target.roleKey) || defaultRole
|
||||
const deviceParam = properties.nodeParams?.find((param) => param.name === 'deviceId')
|
||||
if (nodeRequiresDevice(node) && !target.deviceSlotKey) {
|
||||
const slot = findOrCreateLegacySlot(targetRole, node, legacySlotByIdentity, deviceParam?.input)
|
||||
if (slot) target.deviceSlotKey = slot.slotKey
|
||||
}
|
||||
delete target.deviceBindings
|
||||
properties.nodeParams = (properties.nodeParams || []).filter((param) => param.name !== 'deviceId')
|
||||
properties.executionTarget = target
|
||||
})
|
||||
|
||||
return graph
|
||||
}
|
||||
|
||||
export const ensureNodeExecutionResources = (sourceGraph, nodeId) => {
|
||||
const graph = normalizeFlowResourceSchema(sourceGraph)
|
||||
const node = graph.nodes.find((item) => item.id === nodeId)
|
||||
return { graph, node, start: getStartNode(graph) }
|
||||
}
|
||||
|
||||
export const validateFlowResources = (sourceGraph) => {
|
||||
const graph = normalizeFlowResourceSchema(sourceGraph)
|
||||
const roles = getResourceRoles(graph)
|
||||
const errors = []
|
||||
const roleKeys = new Set()
|
||||
|
||||
roles.forEach((role) => {
|
||||
if (!normalizedText(role.displayName)) errors.push({ nodeId: getStartNode(graph)?.id, message: '机器人角色名称不能为空' })
|
||||
if (roleKeys.has(role.roleKey)) errors.push({ nodeId: getStartNode(graph)?.id, message: `机器人角色标识重复:${role.roleKey}` })
|
||||
roleKeys.add(role.roleKey)
|
||||
const slotKeys = new Set()
|
||||
;(role.deviceSlots || []).forEach((slot) => {
|
||||
if (!normalizedText(slot.displayName)) errors.push({ nodeId: getStartNode(graph)?.id, message: `${role.displayName}存在未命名的设备用途` })
|
||||
if (slotKeys.has(slot.slotKey)) errors.push({ nodeId: getStartNode(graph)?.id, message: `${role.displayName}的设备用途标识重复` })
|
||||
slotKeys.add(slot.slotKey)
|
||||
})
|
||||
})
|
||||
|
||||
graph.nodes.filter(isRobotNode).forEach((node) => {
|
||||
const target = node.properties?.executionTarget
|
||||
const role = roles.find((item) => item.roleKey === target?.roleKey)
|
||||
if (!role) {
|
||||
errors.push({ nodeId: node.id, message: `${node.properties?.name || node.id}未选择有效的机器人角色` })
|
||||
return
|
||||
}
|
||||
if (target.deviceSlotKey && !(role.deviceSlots || []).some((slot) => slot.slotKey === target.deviceSlotKey)) {
|
||||
errors.push({ nodeId: node.id, message: `${node.properties?.name || node.id}引用的设备用途不存在` })
|
||||
}
|
||||
if (nodeRequiresDevice(node)
|
||||
&& !target.deviceSlotKey) {
|
||||
errors.push({ nodeId: node.id, message: `${node.properties?.name || node.id}未选择设备用途` })
|
||||
}
|
||||
})
|
||||
return { graph, errors }
|
||||
}
|
||||
|
||||
export const sanitizeFlowResourceDefinition = (sourceGraph) => {
|
||||
const graph = normalizeFlowResourceSchema(sourceGraph)
|
||||
const start = getStartNode(graph)
|
||||
;(start?.properties?.resourceRoles || []).forEach((role) => {
|
||||
delete role.legacyRobotId
|
||||
;(role.deviceSlots || []).forEach((slot) => delete slot.legacyDeviceId)
|
||||
})
|
||||
graph.nodes.filter(isRobotNode).forEach((node) => {
|
||||
node.properties.nodeParams = (node.properties?.nodeParams || []).filter((param) => param.name !== 'deviceId')
|
||||
})
|
||||
return graph
|
||||
}
|
||||
|
||||
export const mergeCopiedResourceRoles = (targetGraph, sourceRoles = [], copiedNodes = []) => {
|
||||
const graph = normalizeFlowResourceSchema(targetGraph)
|
||||
const start = getStartNode(graph)
|
||||
const targetRoles = start.properties.resourceRoles
|
||||
let addedRoles = 0
|
||||
let addedSlots = 0
|
||||
|
||||
copiedNodes.filter(isRobotNode).forEach((node) => {
|
||||
const target = node.properties?.executionTarget
|
||||
if (!target?.roleKey) return
|
||||
const sourceRole = sourceRoles.find((role) => role.roleKey === target.roleKey)
|
||||
let targetRole = targetRoles.find((role) => role.roleKey === target.roleKey)
|
||||
|| targetRoles.find((role) => sourceRole && role.displayName === sourceRole.displayName)
|
||||
|
||||
if (!targetRole && sourceRole) {
|
||||
targetRole = clone(sourceRole)
|
||||
delete targetRole.legacyRobotId
|
||||
;(targetRole.deviceSlots || []).forEach((slot) => delete slot.legacyDeviceId)
|
||||
targetRoles.push(targetRole)
|
||||
addedRoles += 1
|
||||
addedSlots += targetRole.deviceSlots?.length || 0
|
||||
}
|
||||
targetRole ||= targetRoles[0]
|
||||
target.roleKey = targetRole.roleKey
|
||||
|
||||
if (target.deviceSlotKey && !targetRole.deviceSlots?.some((slot) => slot.slotKey === target.deviceSlotKey)) {
|
||||
const sourceSlot = sourceRole?.deviceSlots?.find((slot) => slot.slotKey === target.deviceSlotKey)
|
||||
const sameSlot = targetRole.deviceSlots?.find((slot) => (
|
||||
sourceSlot && slot.displayName === sourceSlot.displayName && Number(slot.deviceKind) === Number(sourceSlot.deviceKind)
|
||||
))
|
||||
if (sameSlot) {
|
||||
target.deviceSlotKey = sameSlot.slotKey
|
||||
return
|
||||
}
|
||||
const newSlot = sourceSlot ? clone(sourceSlot) : createDeviceSlot(getDeviceKindForAction(node.properties?.action), targetRole.deviceSlots)
|
||||
delete newSlot.legacyDeviceId
|
||||
targetRole.deviceSlots.push(newSlot)
|
||||
target.deviceSlotKey = newSlot.slotKey
|
||||
addedSlots += 1
|
||||
}
|
||||
})
|
||||
|
||||
return { graph, start, addedRoles, addedSlots }
|
||||
}
|
||||
|
||||
export const getDeviceKindName = (deviceKind) => DEVICE_KIND_META[Number(deviceKind)]?.name || '任意设备'
|
||||
|
||||
export const mergeResourceRoleDefinitions = (roleGroups = []) => {
|
||||
const merged = []
|
||||
roleGroups.flat().forEach((sourceRole) => {
|
||||
let role = merged.find((item) => item.roleKey === sourceRole.roleKey)
|
||||
if (!role) {
|
||||
merged.push(clone(sourceRole))
|
||||
return
|
||||
}
|
||||
;(sourceRole.deviceSlots || []).forEach((sourceSlot) => {
|
||||
if (!role.deviceSlots.some((slot) => slot.slotKey === sourceSlot.slotKey)) {
|
||||
role.deviceSlots.push(clone(sourceSlot))
|
||||
}
|
||||
})
|
||||
})
|
||||
return merged
|
||||
}
|
||||
157
src/views/flow/components/RuntimeAssignmentsEditor.vue
Normal file
157
src/views/flow/components/RuntimeAssignmentsEditor.vue
Normal file
@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<div class="runtime-assignments">
|
||||
<div v-for="role in roles" :key="role.roleKey" class="runtime-role">
|
||||
<div class="runtime-role__title">
|
||||
<span>{{ role.displayName }}</span>
|
||||
<el-tag v-if="role.required" size="small" type="info">必需</el-tag>
|
||||
</div>
|
||||
<el-form label-position="top">
|
||||
<el-form-item label="执行机器人" :required="role.required">
|
||||
<el-select
|
||||
:model-value="memberOf(role).robotId"
|
||||
filterable
|
||||
:loading="robotLoading"
|
||||
placeholder="请选择机器人"
|
||||
style="width: 100%"
|
||||
@change="(robotId) => changeRobot(role, robotId)"
|
||||
>
|
||||
<el-option v-for="item in robotOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-for="slot in role.deviceSlots" :key="slot.slotKey" :label="slot.displayName" :required="slot.required">
|
||||
<el-select
|
||||
v-model="memberOf(role).devices[slot.slotKey]"
|
||||
filterable
|
||||
:loading="loadingRoleKey === role.roleKey"
|
||||
:disabled="!memberOf(role).robotId"
|
||||
:placeholder="memberOf(role).robotId ? '请选择设备' : '请先选择机器人'"
|
||||
style="width: 100%"
|
||||
@change="emitValue"
|
||||
>
|
||||
<el-option v-for="item in optionsOf(role, slot)" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
<div v-if="memberOf(role).robotId && !optionsOf(role, slot).length" class="runtime-role__error">
|
||||
该机器人没有匹配的在线设备
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<el-empty v-if="!roles.length" :image-size="52" description="当前任务不需要机器人" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, watch } from 'vue'
|
||||
import { getRobotList, getRobotDevicesByRobotId } from '@/api/inspection/robot'
|
||||
import { toDeviceOptions } from '@/utils/robotDevice'
|
||||
|
||||
const props = defineProps({
|
||||
roles: { type: Array, default: () => [] },
|
||||
modelValue: { type: Array, default: () => [] },
|
||||
})
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const assignments = ref([])
|
||||
const robotOptions = ref([])
|
||||
const devicesByRole = reactive({})
|
||||
const robotLoading = ref(false)
|
||||
const loadingRoleKey = ref('')
|
||||
const clone = (value) => JSON.parse(JSON.stringify(value || []))
|
||||
|
||||
const assignmentOf = (role) => assignments.value.find((item) => item.roleKey === role.roleKey)
|
||||
const memberOf = (role) => assignmentOf(role).members[0]
|
||||
const emitValue = () => emit('update:modelValue', clone(assignments.value))
|
||||
|
||||
const loadRobots = async () => {
|
||||
robotLoading.value = true
|
||||
try {
|
||||
const response = await getRobotList({ pageNum: 1, pageSize: 1000 })
|
||||
robotOptions.value = (response.rows || []).map((robot) => ({
|
||||
label: `${robot.robotName || robot.robotId} (${robot.robotId})`,
|
||||
value: robot.robotId,
|
||||
}))
|
||||
} finally {
|
||||
robotLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const optionsOf = (role, slot) => toDeviceOptions((devicesByRole[role.roleKey] || []).filter((device) => (
|
||||
slot.deviceKind == null || Number(device.deviceKind) === Number(slot.deviceKind)
|
||||
)))
|
||||
|
||||
const loadDevices = async (role, preserve = true) => {
|
||||
const member = memberOf(role)
|
||||
if (!member.robotId) return
|
||||
loadingRoleKey.value = role.roleKey
|
||||
try {
|
||||
const response = await getRobotDevicesByRobotId(member.robotId)
|
||||
devicesByRole[role.roleKey] = Array.isArray(response.data) ? response.data : []
|
||||
role.deviceSlots.forEach((slot) => {
|
||||
const options = optionsOf(role, slot)
|
||||
if (!preserve || !options.some((option) => option.value === member.devices[slot.slotKey])) {
|
||||
member.devices[slot.slotKey] = options.length === 1 ? options[0].value : ''
|
||||
}
|
||||
})
|
||||
emitValue()
|
||||
} finally {
|
||||
loadingRoleKey.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
const changeRobot = async (role, robotId) => {
|
||||
const member = memberOf(role)
|
||||
member.robotId = robotId
|
||||
member.devices = Object.fromEntries(role.deviceSlots.map((slot) => [slot.slotKey, '']))
|
||||
await loadDevices(role, false)
|
||||
}
|
||||
|
||||
const initialize = async () => {
|
||||
const existing = clone(props.modelValue)
|
||||
assignments.value = props.roles.map((role) => existing.find((item) => item.roleKey === role.roleKey) || ({
|
||||
roleKey: role.roleKey,
|
||||
members: [{
|
||||
memberKey: `${role.roleKey}_1`,
|
||||
robotId: role.legacyRobotId || '',
|
||||
devices: Object.fromEntries((role.deviceSlots || []).map((slot) => [slot.slotKey, slot.legacyDeviceId || ''])),
|
||||
}],
|
||||
}))
|
||||
emitValue()
|
||||
await loadRobots()
|
||||
await Promise.all(props.roles.filter((role) => memberOf(role).robotId).map((role) => loadDevices(role)))
|
||||
}
|
||||
|
||||
const validate = () => {
|
||||
for (const role of props.roles) {
|
||||
const member = memberOf(role)
|
||||
if (role.required && !member.robotId) return `请为“${role.displayName}”选择机器人`
|
||||
for (const slot of role.deviceSlots || []) {
|
||||
if (slot.required && !member.devices[slot.slotKey]) return `请为“${role.displayName}”选择“${slot.displayName}”`
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
watch(() => props.roles, initialize, { immediate: true, deep: true })
|
||||
defineExpose({ validate })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.runtime-role {
|
||||
padding: 12px 0 4px;
|
||||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||
}
|
||||
|
||||
.runtime-role__title {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.runtime-role__error {
|
||||
margin-top: 5px;
|
||||
color: var(--el-color-danger);
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
117
src/views/flow/components/params/RobotExecutionTarget.vue
Normal file
117
src/views/flow/components/params/RobotExecutionTarget.vue
Normal file
@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<div class="execution-target">
|
||||
<div class="execution-target__heading">执行设置</div>
|
||||
<el-form label-position="top">
|
||||
<el-form-item label="由谁执行" required>
|
||||
<el-select v-model="localTarget.roleKey" style="width: 100%" @change="handleRoleChange">
|
||||
<el-option
|
||||
v-for="role in roles"
|
||||
:key="role.roleKey"
|
||||
:label="role.displayName"
|
||||
:value="role.roleKey"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="hasDeviceParameter" label="使用设备" required>
|
||||
<el-select
|
||||
v-model="deviceSlotKey"
|
||||
style="width: 100%"
|
||||
:placeholder="matchingSlots.length ? '请选择设备用途' : '请先在开始节点配置设备用途'"
|
||||
@change="emitTarget"
|
||||
>
|
||||
<el-option
|
||||
v-for="slot in matchingSlots"
|
||||
:key="slot.slotKey"
|
||||
:label="`${slot.displayName} · ${getDeviceKindName(slot.deviceKind)}`"
|
||||
:value="slot.slotKey"
|
||||
/>
|
||||
</el-select>
|
||||
<div v-if="!matchingSlots.length" class="execution-target__warning">
|
||||
当前角色没有匹配的设备用途,请先打开开始节点添加。
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, reactive, ref, watch } from 'vue'
|
||||
import { getDeviceKindForAction } from '@/utils/robotDevice'
|
||||
import { actionRequiresDevice, getDeviceKindName, getResourceRoles } from '@/utils/flowResources'
|
||||
|
||||
const props = defineProps({
|
||||
data: { type: Object, required: true },
|
||||
modelValue: { type: Object, default: () => ({}) },
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const roles = ref([])
|
||||
const localTarget = reactive({ roleKey: '', dispatchPolicy: 'ONE', deviceSlotKey: '' })
|
||||
const deviceSlotKey = ref('')
|
||||
|
||||
const hasDeviceParameter = computed(() => (
|
||||
actionRequiresDevice(props.data.properties?.action)
|
||||
))
|
||||
const requiredDeviceKind = computed(() => getDeviceKindForAction(props.data.properties?.action))
|
||||
const selectedRole = computed(() => roles.value.find((role) => role.roleKey === localTarget.roleKey))
|
||||
const matchingSlots = computed(() => (selectedRole.value?.deviceSlots || []).filter((slot) => (
|
||||
requiredDeviceKind.value == null
|
||||
|| slot.deviceKind == null
|
||||
|| Number(slot.deviceKind) === Number(requiredDeviceKind.value)
|
||||
)))
|
||||
|
||||
const emitTarget = () => {
|
||||
emit('update:modelValue', {
|
||||
roleKey: localTarget.roleKey,
|
||||
dispatchPolicy: 'ONE',
|
||||
deviceSlotKey: hasDeviceParameter.value ? deviceSlotKey.value : '',
|
||||
})
|
||||
}
|
||||
|
||||
const selectDefaultSlot = () => {
|
||||
const currentValid = matchingSlots.value.some((slot) => slot.slotKey === deviceSlotKey.value)
|
||||
if (!currentValid) deviceSlotKey.value = matchingSlots.value.length === 1 ? matchingSlots.value[0].slotKey : ''
|
||||
}
|
||||
|
||||
const handleRoleChange = () => {
|
||||
deviceSlotKey.value = ''
|
||||
selectDefaultSlot()
|
||||
emitTarget()
|
||||
}
|
||||
|
||||
const init = () => {
|
||||
roles.value = getResourceRoles()
|
||||
const value = props.modelValue || {}
|
||||
localTarget.roleKey = roles.value.some((role) => role.roleKey === value.roleKey)
|
||||
? value.roleKey
|
||||
: roles.value[0]?.roleKey || ''
|
||||
localTarget.dispatchPolicy = value.dispatchPolicy || 'ONE'
|
||||
deviceSlotKey.value = value.deviceSlotKey || value.deviceBindings?.[0]?.slotKey || ''
|
||||
selectDefaultSlot()
|
||||
emitTarget()
|
||||
}
|
||||
|
||||
watch(() => props.data.id, init, { immediate: true })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.execution-target {
|
||||
margin: 12px 0 2px;
|
||||
padding: 14px 0 2px;
|
||||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||
}
|
||||
|
||||
.execution-target__heading {
|
||||
margin-bottom: 12px;
|
||||
color: var(--el-text-color-primary);
|
||||
font-size: 14px;
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.execution-target__warning {
|
||||
margin-top: 6px;
|
||||
color: var(--el-color-warning-dark-2);
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in New Issue
Block a user