feat: 多图采集模块初始化

master
donghao 2 months ago
parent badb802edc
commit 24b390daa0

@ -9,6 +9,8 @@ declare global {
const EffectScope: typeof import('vue')['EffectScope']
const ElButton: typeof import('element-plus/es')['ElButton']
const ElDialog: typeof import('element-plus/es')['ElDialog']
const ElIcon: typeof import('element-plus/es')['ElIcon']
const ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
const ElMessage: typeof import('element-plus/es')['ElMessage']
const computed: typeof import('vue')['computed']
const createApp: typeof import('vue')['createApp']

@ -2,11 +2,13 @@
* @Author: donghao donghao@supervision.ltd
* @Date: 2025-07-02 13:17:44
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2025-07-02 16:29:18
* @LastEditTime: 2025-07-25 16:44:43
* @FilePath: \electron-project\Robot-Al\Robot-Al-Platform-Web\src\renderer\src\App.vue
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
<script setup lang="ts"></script>
<script setup lang="ts">
document.documentElement.classList.add('dark-mode')
</script>
<template>
<router-view></router-view>

@ -103,7 +103,7 @@ export default defineComponent({
class={buttonClasses.value}
style={props.style}
disabled={props.disabled || props.loading}
type={props.nativeType}
type={props.type}
onClick={handleClick}
>
{renderContent()}

@ -0,0 +1,55 @@
/* 弹框 */
.ds-dialog {
--el-dialog-padding-primary: 0; //
--el-dialog-bg-color: var(--ds-dialog-background-color) !important; //
padding: 0 !important;
overflow: hidden;
.el-dialog__header {
background-color: var(--ds-color-primary);
.el-dialog__title {
color: var(--ds-color-info);
line-height: var(--ds-dialog-header-height);
padding-left: var(--ds-dialog-header-padding);
}
}
.el-dialog__body {
color: var(--ds-color-info);
}
.el-dialog__footer {
--el-dialog-padding-primary: 0; //
}
.el-dialog__headerbtn,
.el-dialog__headerbtn:hover {
width: 68px;
height: var(--ds-dialog-header-height);
.ds-dialog-header {
height: var(--ds-dialog-header-height);
}
.el-dialog__close {
font-size: 20px;
color: var(--ds-color-info);
}
}
.el-dialog__header {
height: var(--ds-dialog-header-height);
}
}
//
.ds-dialog-form-container {
.el-select__wrapper {
background: var(--ds-dialog-form-background-color) !important;
.el-select__selected-item {
color: var(--ds-color-info) !important;
}
}
.el-input-number {
width: 100% !important;
.el-input-number__increase,
.el-input-number__decrease {
background: transparent !important;
color: var(--ds-color-info) !important;
border: none !important;
}
}
}

@ -46,43 +46,7 @@
height: 20px;
}
/* 弹框 */
.ds-dialog {
--el-dialog-padding-primary: 0; //
--el-dialog-bg-color: var(--ds-dialog-background-color) !important; //
padding: 0 !important;
overflow: hidden;
.el-dialog__header {
background-color: var(--ds-color-primary);
.el-dialog__title {
color: var(--ds-color-info);
line-height: var(--ds-dialog-header-height);
padding-left: var(--ds-dialog-header-padding);
}
}
.el-dialog__body {
color: var(--ds-color-info);
}
.el-dialog__footer {
--el-dialog-padding-primary: 0; //
}
.el-dialog__headerbtn,
.el-dialog__headerbtn:hover {
width: 68px;
height: var(--ds-dialog-header-height);
.ds-dialog-header {
height: var(--ds-dialog-header-height);
}
.el-dialog__close {
font-size: 20px;
color: var(--ds-color-info);
}
}
.el-dialog__header {
height: var(--ds-dialog-header-height);
}
}
/* tabs */
.ds-tabs {

@ -16,7 +16,8 @@
--ds-button-border-color: #c9cdd4;
--ds-button-info-border-color: #dcdcdc;
--ds-background-color: #363940; //
--ds-dialog-background-color: #363940; //
--ds-dialog-background-color: #363940; //
--ds-dialog-form-background-color: #303136; //
--ds-dialog-header-padding: 24px; //
--ds-dialog-header-height: 54px; //
--ds-bg-color-light-1: #151515; //
@ -24,9 +25,7 @@
--ds-bg-color-light-3: #373737; //
--ds-bg-color-light-4: #474747; //
--ds-bg-color-light-5: #565656; //
// #4E5969
--ds-light-0: #00b429; // 1
// --ds-light-2: #eaeaea; // 2
// --ds-light-3: #dcdcdc; // 3

@ -1,4 +1,5 @@
@import url("./base.scss");
@import url("./global.scss");
@import url("./element-plus.scss");
@import url("./ds-dialog.scss");
@import url("./design.scss");

@ -0,0 +1,130 @@
<!--
* @Author: donghao donghao@supervision.ltd
* @Date: 2025-07-03 11:12:04
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2025-07-24 13:44:05
* @FilePath: \electron-project\Robot-Al\Robot-Al-Platform-Web\src\renderer\src\views\Design\FlowImagesCapture\imageNodeModel.vue
* @Description:
-->
<template>
<DSDialog
v-model="show"
width="800"
@close="handleClose"
class="settings-dialog ds-dialog"
title="图像源"
>
<div class="settings-container">
<div class="settings-sidebar">
<div
class="flex flex-col items-center justify-center sidebar-item"
:class="{ active: activeTab === v.value }"
@click="setActiveTab(v)"
v-for="(v, k) in settings"
:key="k"
>
<img src="@/assets/electron.svg" alt="" class="w-[32px]" />
<span>{{ v.label }}</span>
</div>
</div>
<div class="settings-line"></div>
<ul class="settings-content">
<li v-if="activeTab === 'permission'"></li>
<!-- Add other tabs' content here -->
<li v-if="activeTab === 'software'">
<p>Software Settings Content</p>
</li>
<li v-if="activeTab === 'plan'">
<p>Plan Settings Content</p>
</li>
<li v-if="activeTab === 'strategy'">
<p>Strategy Settings Content</p>
</li>
</ul>
</div>
<template #footer>
<span class="flex justify-end pt-0 dialog-footer">
<DSButton type="primary" size="default" @click="handleConfirm" class="mr-[24px] mb-[24px]"
>确定</DSButton
>
</span>
</template>
</DSDialog>
</template>
<script setup lang="ts">
// import ThemeSwitcher from '@/components/Theme/themeSwitcher.vue'
import { DSButton } from '@/components/Button'
import { DSDialog } from '@/components/Dialog'
interface Props {
/** 弹窗显隐 */
value: boolean
info: Record<string, any>
}
interface Emits {
(e: 'update:value', val: boolean): void
}
const props = withDefaults(defineProps<Props>(), {
value: false
})
const emit = defineEmits<Emits>()
//
const handleClose = () => {
// emits('close');
}
const show = computed({
get() {
return props.value
},
set(val: boolean) {
emit('update:value', val)
}
})
const activeTab = ref('permission')
interface SettingItem {
label: string
value: string //
icon?: string // URL
}
const settings: SettingItem[] = [
{
label: '权限设置',
value: 'permission',
icon: 'fa fa-cog' //
},
{
label: '软件设置',
value: 'software',
icon: 'fa fa-file-code-o' //
},
{
label: '方案设置',
value: 'scheme',
icon: 'fa fa-clipboard' //
},
{
label: '运行策略',
value: 'strategy',
icon: 'fa fa-play-circle-o' //
}
]
const setActiveTab = (tab: SettingItem) => {
activeTab.value = tab.value
}
const handleConfirm = () => {
console.log('Confirm')
// Implement confirm logic here (e.g., save the changes)
show.value = false // Close the dialog
}
</script>
<style scoped lang="scss">
@import url('./imageNodeModel.scss');
</style>

@ -0,0 +1,14 @@
/*
* @Author: donghao donghao@supervision.ltd
* @Date: 2025-07-23 16:19:16
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2025-07-23 16:37:17
* @FilePath: \Robot-Al-Platform-Web\src\renderer\src\views\Design\FlowImagesCapture\index.ts
* @Description: ,`customMade`, koroFileHeader : https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import imageNodeModel from './imageNodeModel.vue'
import multiImageNodeModel from './multiImageNodeModel.vue'
export const FlowImageNodeModel = imageNodeModel
export const FlowMultiImageNodeModel = multiImageNodeModel

@ -0,0 +1,40 @@
/* 弹框整体样式 */
.controllerManagement-container {
position: relative;
width: 100%;
height: 766px;
box-sizing: border-box;
display: flex;
.controllerManagement-content {
flex: 1;
height: 100%;
overflow: auto;
box-sizing: border-box;
padding: 16px 24px 24px;
.form-device-btn {
width: 80px;
padding: 0 12px;
}
}
.custom-table {
.customList-table {
width: 100%;
height: 190px;
background: #363940;
}
}
.table-add-btn {
/* 按钮样式 */
box-sizing: border-box;
width: 128px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
}

@ -0,0 +1,328 @@
<script setup lang="ts">
/**
* @交互说明
* 1. 开始弹出设备选择对话框
* 2. 选择本地图像后展示相关内容
* 3. 选择相机后展示相关内容
*/
defineOptions({
name: 'FlowMultiImageNodeModel'
})
import { DSButton } from '@/components/Button'
import { DSDialog } from '@/components/Dialog'
import { RefreshRight } from '@element-plus/icons-vue'
interface Props {
/** import DeviceDialog from './deviceDialog.vue';
弹窗显隐 */
value: boolean
}
interface Emits {
(e: 'update:value', val: boolean): void
}
const props = withDefaults(defineProps<Props>(), {
value: false
})
const emit = defineEmits<Emits>()
console.log(props.value)
const show = computed({
get() {
Object.assign(deviceBars, deviceList)
return props.value
},
set(val: boolean) {
emit('update:value', val)
}
})
const isDeviceDialog = ref(false)
// tab
const deviceTab = ref('') // device/receive/send/heartbeat/response
const deviceBars = ref([])
const imageSourceOptions = [
{
value: '本地图像',
label: '本地图像'
},
{
value: '相机',
label: '相机'
}
]
const options = [
{
value: 'DPS2',
label: 'DPS2'
},
{
value: 'MV-AP1024-2T',
label: 'MV-AP1024-2T'
},
{
value: 'MV-LEVD',
label: 'MV-LEVD'
},
{
value: 'VB2200',
label: 'VB2200'
},
{
value: 'VC3000(Light)',
label: 'VC3000(Light)'
},
{
value: 'VB3000(IO)',
label: 'VB3000(IO)'
},
{
value: 'VC4000',
label: 'VC4000'
},
{
value: 'GPIO',
label: 'GPIO'
},
{
value: 'VB2230',
label: 'VB2230'
},
{
value: 'VC2000/2100',
label: 'VC2000/2100'
},
{
value: 'VC3000H/X',
label: 'VC3000H/X'
},
{
value: 'MV-LEVD-200',
label: 'MV-LEVD-200'
},
{
value: 'LE200',
label: 'LE200'
},
{
value: 'VC5000',
label: 'VC5000'
}
]
//
const deviceList = ref([
{
name: '1 Light-1',
enabled: false,
info: {
imageSource: '',
deviceName: '',
serialPort: '',
baudRate: '',
dataBits: '',
checkBit: '',
stopBit: '',
outputType: '',
sendInterval: '',
IOPort: '',
delayTime: '',
shakeTime: '',
devices: '',
reconnect: false,
polling: false,
reversal: false,
state: false,
endInfo: '',
channelBrightness1: 0,
channelBrightness2: 0,
channelBrightness3: 0,
channelBrightness4: 0,
channelBrightness5: 0,
channelBrightness6: 0,
pollingInterval: 0,
lightChannel: 0,
lightChannelStatus: '',
tableData: []
}
},
{ name: '2 Light-2', enabled: false }
])
//
const formDeviceInfo = reactive({
imageSource: '本地图像',
deviceName: '',
imageConfigs: [
{
distributionAngle: 90,
illuminationAngle: 0
}
],
reconnect: false,
polling: false,
outputPolarity: '', //
sendInterval: '',
IOPort: '',
delayTime: '', // IO
controlDelayTime: '', // IO
flickerDelayTime: '', //
endInfo: '',
channelBrightness1: 0,
channelBrightness2: 0
})
//
const handleAddImageConfig = () => {
formDeviceInfo.imageConfigs.push({
distributionAngle: 0,
illuminationAngle: 0
})
}
const handleDelImageConfig = (index) => {
formDeviceInfo.imageConfigs.splice(index, 1)
}
//
const handleClose = () => {}
</script>
<template>
<!-- 控制器管理弹框 -->
<DSDialog
title="多图采集"
v-model="show"
width="1200"
class="controllerManagement-dialog ds-dialog"
@close="handleClose"
>
<div class="controllerManagement-container ds-dialog-form-container">
<div class="controllerManagement-content">
<el-form label-width="auto" size="default" :model="formDeviceInfo" style="max-width: 100%">
<el-row :gutter="24">
<el-col :span="8">
<el-form-item label="图像源" label-position="top">
<el-select
v-model="formDeviceInfo.imageSource"
placeholder="请选择"
class="device-select"
>
<el-option
v-for="item in imageSourceOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8" v-if="formDeviceInfo.imageSource === '相机'">
<el-form-item label="关联相机 (请先在“相机管理”中新建相机)" label-position="top">
<el-select
v-model="formDeviceInfo.imageSource"
placeholder="请选择"
class="device-select"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="取图数量 (上限不超过8)" label-position="top">
<div class="flex">
<el-input-number
class="flex-1 w-full"
v-model="formDeviceInfo.deviceName"
:min="1"
:max="8"
controls-position="right"
@change="handleChange"
>
</el-input-number>
<span>
<el-icon class="el-input__icon"><Folder /></el-icon>
</span>
</div>
</el-form-item>
</el-col>
<el-col :span="8" v-if="formDeviceInfo.imageSource === '本地图像'">
<el-form-item label="图像路径" label-position="top">
<el-input v-model="formDeviceInfo.deviceName">
<template #suffix>
<el-icon class="el-input__icon"><Folder /></el-icon>
</template>
</el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24" v-if="formDeviceInfo.imageSource === '本地图像'">
<el-col :span="24">
<el-form-item
v-for="(domain, index) in formDeviceInfo.imageConfigs"
:key="domain.key"
label-position="top"
label=""
:prop="'imageConfigs.' + index + '.value'"
>
<template #label>
<div class="flex items-center">
<p>{{ '图像配置' + (index + 1) }}</p>
<!-- // TODO -->
<DSButton
class="mx-[16px]"
type="text"
@click.prevent="handleDelImageConfig(index)"
>
<el-icon class="el-input__icon"><Delete /></el-icon>
</DSButton>
</div>
</template>
<div class="w-full">
<el-row :gutter="24">
<el-col :span="8">
<el-form-item label="分布角" label-position="top">
<el-input-number
v-model="domain.distributionAngle"
:min="0"
:max="360"
controls-position="right"
@change="handleChange"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="照射角" label-position="top">
<el-input-number
v-model="domain.illuminationAngle"
:min="0"
:max="360"
controls-position="right"
@change="handleChange"
/>
</el-form-item>
</el-col>
</el-row>
</div>
</el-form-item>
</el-col>
</el-row>
<div class="table-add-btn" @click="handleAddImageConfig">
<img
src="@/assets/images/common/table_addBtn.png"
alt=""
class="w-[16px] h-[16px] mr-2"
/>
</div>
</el-form>
</div>
</div>
</DSDialog>
</template>
<style scoped lang="scss">
@import url('./multiImageNodeModel.scss');
</style>

@ -2,7 +2,7 @@
* @Author: donghao donghao@supervision.ltd
* @Date: 2025-07-03 10:27:47
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2025-07-23 16:09:34
* @LastEditTime: 2025-07-23 16:40:56
* @FilePath: \electron-project\Robot-Al\Robot-Al-Platform-Web\src\renderer\src\views\Design\Workflow\logicFlowView.vue
* @Description: 流程设计器面板
-->
@ -31,7 +31,10 @@
</div>
</div>
<div class="attr-model-box" v-if="showAttribute"></div>
<div class="attr-model-box" v-if="showAttribute">
<FlowImageNodeModel v-model:value="isOpenFlowImage" />
<FlowMultiImageNodeModel v-model:value="isOpenFlowMultiImage" />
</div>
</div>
</template>
@ -46,20 +49,33 @@ import { OperationNode, ImageNode } from '@/components/LogicFlowNodes'
import { useLogicFlow } from '@/hooks/useLogicFlow'
import { logicFlowConf, logicFlowThemeConf } from '@/config/designCanvas'
import { getNodeOutputCount, getNodeInputCount } from '@/utils/forData/arr'
import { FlowImageNodeModel, FlowMultiImageNodeModel } from '../FlowImagesCapture'
const showLf = ref<boolean>(false) //
const showTop = ref<boolean>(false) //
const showAttribute = ref<boolean>(false) //
const nodeData = ref(null)
const currNodeData = ref({}) //
const { graphData } = useLogicFlow()
// 1.
const lf = ref<LogicFlow | null>(null)
const flowDetail = reactive({})
const isOpenFlowImage = ref<boolean>(false) //
const isOpenFlowMultiImage = ref<boolean>(false) //
//
const hideAttrPanel = () => {
showAttribute.value = false
const changeAttrPanelStatus = (isOpen) => {
switch (currNodeData.value?.type) {
case 'image-node':
isOpenFlowImage.value = true
break
case 'multi-image-node':
isOpenFlowMultiImage.value = true
break
}
nextTick(() => {
showAttribute.value = isOpen || !showAttribute.value
})
}
//
@ -110,27 +126,25 @@ const setEdgeType = (data) => {
//
const initEvents = () => {
lf.value.on('node:click', ({ data }) => {
nodeData.value = data
console.log('node:click-nodeData', nodeData.value)
if (['imageNode'].includes(data.type)) {
showAttribute.value = true
}
// currNodeData.value = data
console.log('node:click-currNodeData', currNodeData.value)
// changeAttrPanelStatus(true)
})
lf.value.on('node:dbclick', ({ data }) => {
nodeData.value = data
console.log('node:dbclick-nodeData', nodeData.value)
})
lf.value.on('edge:dbclick', ({ data }) => {
nodeData.value = data
showAttribute.value = true
currNodeData.value = data
console.log('node:dbclick-currNodeData', currNodeData.value)
changeAttrPanelStatus(true)
})
// lf.value.on('edge:dbclick', ({ data }) => {
// currNodeData.value = data
// })
//
lf.value.on('edge:app-config', (res) => {
nodeData.value = res
currNodeData.value = res
showAttribute.value = true
})
lf.value.on('element:click', () => {
hideAttrPanel() //
// hideAttrPanel() //
// console.log(lf.value.getGraphData(), 'getGraphData_click', JSON.stringify(lf.value.getGraphData()))
})
lf.value.on('blank:contextmenu', (e, position) => {

Loading…
Cancel
Save