feat: 完成图像源节点表单的静态交互

master
donghao 1 month ago
parent 1e35f89481
commit 21f381bd35

@ -0,0 +1,122 @@
/*
* @Author: donghao donghao@supervision.ltd
* @Date: 2025-07-29 10:48:37
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2025-07-30 14:32:45
* @FilePath: \Robot-Al-Platform-Web\src\renderer\src\components\Form\switch.tsx
* @Description: ,`customMade`, koroFileHeader : https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import { defineComponent, h, withDefaults, computed, normalizeClass } from 'vue'
import { ElSwitch } from 'element-plus'
import type { SwitchProps } from 'element-plus'
import { spawn } from 'child_process'
interface DsSwitchProps extends SwitchProps {
/**
* switch
*/
label?: string
/**
* switch
*/
prefixRender?: (...ags: any[]) => any // 前缀内容 render渲染
/**
* switch
*/
suffixRender?: (...ags: any[]) => any // 后缀内容 render渲染
/**
* container
*/
containerClassName?: string
/**
*
*/
componentClassName?: string
}
export default defineComponent({
name: 'DsSwitch',
props: {
modelValue: {
type: [Boolean, String, Number],
default: false
},
label: {
type: String,
default: ''
},
suffixRender: {
type: Function as DsSwitchProps['suffixRender'],
default: null
},
containerClassName: {
type: String,
default: ''
},
componentClassName: {
type: String,
default: ''
},
...ElSwitch.props // 继承 ElSwitch 的所有 props
},
emits: ['update:modelValue', 'change'],
setup(props, { emit, attrs }) {
const { containerClassName, componentClassName, ...restProps } = props
// 处理自定义类名和 Vue 的 class 属性
const componentClasses = computed(() => {
const classes = ['flex items-center ds-switch-box', componentClassName]
// 处理 Vue 的 class 属性(支持字符串/数组/对象)
if (attrs.class) {
classes.push(normalizeClass(attrs.class))
}
return classes.filter((c) => c).join(' ')
})
// 处理自定义类名和 Vue 的 class 属性
const containerClasses = computed(() => {
const classes = ['flex items-center justify-between w-[136px]', containerClassName]
// 处理 Vue 的 class 属性(支持字符串/数组/对象)
if (attrs.class) {
classes.push(normalizeClass(attrs.class))
}
return classes.filter((c) => c).join(' ')
})
const handleChange = (val: boolean | string | number) => {
emit('update:modelValue', val)
emit('change', val)
}
// 渲染自定义前缀内容
const renderPrefix = () => {
if (props.prefixRender) {
return props?.prefixRender?.()
}
return (
<span class="ds-switch-label">
{props?.label}
</span>
)
}
// 渲染自定义后缀内容
const renderSuffix = () => {
if (props.suffixRender) {
return props?.suffixRender?.()
}
return null
}
return () => (
<div className={componentClasses.value}>
<div className={containerClasses.value}>
{/* 添加前缀插槽 */}
{renderPrefix()}
<ElSwitch {...restProps} modelValue={props.modelValue} onChange={handleChange} />
</div>
{/* 添加后缀插槽 */}
{renderSuffix()}
</div>
)
}
})

@ -2,9 +2,11 @@
* @Author: donghao donghao@supervision.ltd
* @Date: 2025-07-28 10:07:16
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2025-07-28 16:54:10
* @LastEditTime: 2025-07-30 11:29:49
* @FilePath: \Robot-Al-Platform-Web\src\renderer\src\components\Form\index.ts
* @Description: ,`customMade`, koroFileHeader : https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import inputNumber from './inputNumber'
export const DSInputNumber = inputNumber
import dsSwitch from './dsSwitch'
export const DSInputNumber = inputNumber
export const DsSwitch = dsSwitch

@ -1,107 +0,0 @@
/*
* @Author: donghao donghao@supervision.ltd
* @Date: 2025-07-29 10:49:30
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2025-07-29 10:50:25
* @FilePath: \Robot-Al-Platform-Web\src\renderer\src\components\Form\inputNumber copy.tsx
* @Description: ,`customMade`, koroFileHeader : https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import { defineComponent, ref, watch, computed } from 'vue'
import { ElInputNumber, ElButton } from 'element-plus'
export default defineComponent({
name: 'DSInputNumber',
props: {
modelValue: {
type: Number,
default: 0
},
min: {
type: Number,
default: 0
},
max: {
type: Number,
default: 100000
},
placeholder: {
type: String,
default: '请输入'
},
disabled: {
type: Boolean,
default: false
},
controls: {
type: Boolean,
default: true
},
label: {
type: String,
default: ''
},
randomValue: {
type: Number,
default: 10
},
controlsPosition: {
type: String,
default: 'top'
},
icon: {
type: String || Object,
default: ''
}
},
emits: ['update:modelValue', 'change'],
setup(props, { slots, emit }) {
const innerValue = ref(props?.modelValue || props?.min || 0)
watch(
() => props.modelValue,
(newVal) => {
innerValue.value = newVal
}
)
const handleChange = (value: number) => {
console.log('handleChange_ds_inputNumber', value)
emit('update:modelValue', value)
emit('change', value)
}
// const setRandomValue = () => {
// const randomVal = props.randomValue
// innerValue.value = randomVal
// emit('update:modelValue', randomVal)
// emit('change', randomVal)
// }
const containerClass = computed(() => [
'input-number-container',
props.disabled ? 'disabled-state' : ''
])
return () => (
<div class={props.containerClass}>
<div class="input-group flex items-center">
<ElInputNumber
modelValue={innerValue.value}
onUpdate:modelValue={(val) => (innerValue.value = val)}
min={props.min}
max={props.max}
controls-position={props.controlsPosition}
placeholder={props.placeholder}
disabled={props.disabled}
controls={props.controls}
onChange={handleChange}
/>
<ElButton type="primary" icon={props.icon} circle disabled={props.disabled} />
</div>
</div>
)
}
})

@ -2,28 +2,32 @@
* @Author: donghao donghao@supervision.ltd
* @Date: 2025-07-28 16:45:11
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2025-07-30 09:32:36
* @LastEditTime: 2025-07-30 14:30:36
* @FilePath: \Robot-Al-Platform-Web\src\renderer\src\components\Form\inputNumber.tsx
* @Description: ,`customMade`, koroFileHeader : https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import { defineComponent, getCurrentInstance, ComputedRef, computed } from 'vue'
import { ELIcon, ElInputNumber, inputNumberProps, InputNumberProps } from 'element-plus'
import { ElInputNumber, inputNumberProps, InputNumberProps } from 'element-plus'
import type { InputNumberEmits } from 'element-plus'
import { Refresh } from '@element-plus/icons-vue'
import { RefreshRight } from '@element-plus/icons-vue'
// 定义组件属性类型(保留所有 ElInputNumber 属性)
type DSInputNumberProps = InputNumberProps & {
// 可在此处添加自定义属性
prefixContent?: () => JSX.Element
suffixIconType?: string
prefixRender?: (...ags: any[]) => any // 前置内容 render渲染
suffixRender?: (...ags: any[]) => any // 后置内容 render渲染
}
export default defineComponent({
name: 'DSInputNumber',
props: {
...inputNumberProps, // 继承所有 ElInputNumber 属性
prefixContent: Function as DSInputNumberProps['prefixContent'],
suffixIconType: String as 'plus' | 'default'
prefixRender: Function as DSInputNumberProps['prefixRender'],
suffixRender: Function as DSInputNumberProps['suffixRender'],
controlsPosition: {
type: String,
default: 'right'
}
},
emits: [
'update:modelValue',
@ -44,7 +48,7 @@ export default defineComponent({
// 创建计算属性以包含所有属性
const inputNumberAttrs: ComputedRef<Record<string, any>> = computed(() => {
const { prefixContent, suffixIconType, ...propsRest } = props
const { prefixRender, suffixRender, ...propsRest } = props
return {
...attrs,
...propsRest,
@ -58,34 +62,35 @@ export default defineComponent({
}
})
// 渲染自定义后缀图标
const renderSuffixIcon = () => {
// if (props.loading) {
// return <span class="ds-button__loading-icon"></span>
// }
if (props?.suffixIconType === 'default') {
return
// 渲染自定义前缀内容
const renderPrefix = () => {
// props?.suffixRender &&
if (props.prefixRender) {
return props?.prefixRender?.()
}
return null
}
// 渲染自定义后缀内容
const renderSuffix = () => {
// props?.suffixRender &&
if (props.suffixRender) {
return props?.suffixRender?.()
}
return (
<div className="flex items-center justify-center ds-dialog-icon-box ">
<RefreshRight />
</div>
)
}
return () => (
<div class="ds-input-number flex items-center">
{/* 添加前缀插槽 */}
{/* {props?.prefixContent && props?.prefixContent()} */}
{renderPrefix()}
<ElInputNumber {...inputNumberAttrs.value} class="enhanced-input-number"></ElInputNumber>
{/* 图标区 */}
<div className="w-[32px]">
<img src="@/assets/electron.svg" className="w-[32px]" alt="" />
</div>
{props?.suffixIconType && renderSuffixIcon()}
{/* 添加后缀插槽 */}
{/* {props.suffixContent && (
<div class="suffix-slot">
<span>test</span>
{props.suffixContent()}
</div>
)} */}
{renderSuffix()}
</div>
)
}

@ -35,5 +35,15 @@
.el-dialog__header {
height: var(--ds-dialog-header-height);
}
//
.ds-dialog-icon-box {
width: 48px;
height: 32px;
background-color: var(--ds-dialog-icon-background-color);
font-size: 14px !important;
cursor: pointer;
svg {
width: 28px;
}
}
}

@ -1,7 +1,7 @@
//
.ds-dialog-form-container {
.el-select__wrapper {
background: var(--ds-dialog-form-background-color) !important;
background: var(--ds-form-item-background-color) !important;
.el-select__selected-item {
color: var(--ds-color-info) !important;
}
@ -28,4 +28,24 @@
min-height: 32px;
padding: 4px 16px;
}
// el-switch
.el-switch--large .el-switch__core {
border-radius: 16px;
height: 20px;
line-height: 20;
min-width: 40px;
width: 40px;
}
}
.el-input__wrapper {
background: var(--ds-form-item-background-color) !important;
}
//
.ds-form-des-text {
color: var(--ds-des-text-1);
}
//
.ds-form-normal-text {
color: var(--ds-color-info);
}

@ -13,6 +13,12 @@
--el-border-radius-base: var(--ds-border-radius-base); //
--el-collapse-header-height: 28px; //
--el-component-size-large: 32px; //
--el-text-color-regular: var(--ds-text-color-regular); //
--el-border-color: var(--ds-border-color); //
--el-border-width: var(--ds-border-width); //
--el-border-style: var(--ds-border-style); //
--el-box-shadow: var(--ds-box-shadow); //
--el-border: var(-ds-border-width) var(--ds-border-style) var(--ds-border-color);
}
/* button */
.el-button--info {
@ -53,8 +59,6 @@
height: 20px;
}
/* tabs */
.ds-tabs {
background-color: transparent;

@ -12,12 +12,14 @@
--ds-clort-text-1: #333; // 1
--ds-clort-text-2: #666; // 2
--ds-clort-text-3: #999; // 3
--ds-text-color-regular: #333; // 4
--ds-button-border-radius: 2px;
--ds-button-border-color: #c9cdd4;
--ds-button-info-border-color: #dcdcdc;
--ds-background-color: #363940; //
--ds-dialog-background-color: #363940; //
--ds-dialog-form-background-color: #303136; //
--ds-form-item-background-color: #303136; //
--ds-dialog-icon-background-color: #4e5969; //
--ds-dialog-header-padding: 24px; //
--ds-dialog-header-height: 54px; //
--ds-bg-color-light-1: #151515; //
@ -27,6 +29,12 @@
--ds-bg-color-light-5: #565656; //
// #4E5969
--ds-light-0: #00b429; // 1
--ds-des-text-1: #86909c; // 8
--ds-border-width: 1px; //
--ds-border-color: #4e5969; //
--ds-border-style: solid; //
--ds-box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1); //
// --ds-light-2: #eaeaea; // 2
// --ds-light-3: #dcdcdc; // 3
// --ds-light-4: #c9cdd4; // 4

@ -245,7 +245,7 @@
border: none;
}
.t_module_form .scroll_wrap .t_detail .el-collapse .el-collapse-item {
background-color: var(--el-bg-color);
// background-color: var(--el-bg-color);
border: none;
margin-top: 10px;
}

@ -2,7 +2,7 @@
* @Author: donghao donghao@supervision.ltd
* @Date: 2025-07-03 11:12:04
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2025-07-30 09:44:01
* @LastEditTime: 2025-07-30 15:52:28
* @FilePath: \electron-project\Robot-Al\Robot-Al-Platform-Web\src\renderer\src\views\Design\FlowImagesCapture\imageNodeModel.vue
* @Description: 图像源节点
-->
@ -112,32 +112,29 @@ const formOpts: any = reactive({
opts: {
labelPosition: 'top', //
formData: {
// id: "", // *ID
imageSource: '本地图像', // *
/**基础参数-本地图像字段**/
pixelFormat: '', // *
captureInterval: '', // *
displayImageName: false,
snInitialValue: 10,
imageCache: '',
stitchingEnabled: false,
startHeight: 0,
stitchingHeight: 0,
triggerClear: false,
triggerVariable: '',
autoSwitch: false,
stopOnLast: false,
characterTriggerFilter: false,
inputCharacter: '',
triggerCharacter: '',
// TODO
password: null, // *
name: null, // *
sex: null, // *: 0: 1:
hobby: [], // *: 0: 1:
accountType: null, // *: 0: 1: 2:
status: null // *: 01(1)',
displayImageName: false, // *
displaySaveImage: false, // *
snInitialValue: 10, // *SN
imageCache: '', // *
stitchingEnabled: false, // *使
startHeight: 0, // *
stitchingHeight: 0, // *
triggerClear: false, // *
triggerVariable: '', // *
/**基础参数-相机字段**/
relatedCamera: '选择相机', // *
controlExposure: '', // *
controlGain: '', // *
outputMono8: true // *Mono8
},
fieldList: [
/**基础参数-表单项**/
//
{
label: '图像源',
value: 'imageSource',
@ -151,86 +148,253 @@ const formOpts: any = reactive({
change: (val: any) => {
if (val === '本地图像') {
formOpts.baseConfig.widthSize = 3
formOpts.eventConfig.widthSize = 3
} else {
formOpts.baseConfig.widthSize = 2
formOpts.eventConfig.widthSize = 2
}
console.log(val, 'selectChange_imageSource')
}
},
bind: { clearable: false }
},
//
{
label: '像素格式',
value: 'pixelFormat',
type: 'select-arr',
comp: 'el-select',
list: 'testList',
arrLabel: 'key',
arrLabel: 'label',
arrKey: 'value',
placeholder: '请选择像素格式',
isHideItem: (data) => {
return data.imageSource === '本地图像'
return data.imageSource === '本地图像'
}
},
//
{
label: '取图间隔',
value: 'captureInterval',
placeholder: '测试自定义组件1',
comp: 'DSInputNumber',
isSelfCom: true,
bind: {
min: 1,
max: 10
},
eventHandle: {
change: (val: any) => {}
},
isHideItem: (data) => {
return data.imageSource === '本地图像'
}
},
//
{
value: 'displayImageName',
comp: 'DsSwitch',
isSelfCom: true,
widthSize: 1,
bind: {
label: '显示图像名称'
},
isHideItem: (data) => {
return data.imageSource === '本地图像'
}
},
//
{
value: 'displaySaveImage',
comp: 'DsSwitch',
isSelfCom: true,
widthSize: 1,
bind: {
label: '方案存图',
suffixRender: () => {
return <div className="ds-form-des-text pl-[12px]">最多保存120张</div>
}
},
isHideItem: (data) => {
return data.imageSource === '本地图像'
}
},
// sdk
{
labelRender: () => {
return (
<div>
<span>关联相机</span>
<span className="ds-form-des-text">
(请先在<span className="ds-form-normal-text">相机管理</span>中新建相机)
</span>
</div>
)
},
value: 'relatedCamera',
type: 'select-arr',
comp: 'el-select',
list: 'testList',
arrLabel: 'key',
arrLabel: 'label',
arrKey: 'value',
placeholder: '请选择取图间隔',
placeholder: '请选择关联相机',
isHideItem: (data) => {
return data.imageSource !== '本地图像'
}
},
// sdk
{
label: '控制曝光',
value: 'controlExposure',
type: 'input',
comp: 'el-input',
bind: { maxlength: 100 },
isHideItem: (data) => {
return data.imageSource !== '本地图像'
}
},
// sdk
{
label: '控制增益',
value: 'controlGain',
type: 'input',
comp: 'el-input',
bind: { maxlength: 100 },
isHideItem: (data) => {
return data.imageSource !== '本地图像'
}
},
//
{
placeholder: '请输入SN初始值',
labelRender: () => {
return (
<div>
<span>SN初始值</span>
<span className="ds-form-des-text">上限不超过100000</span>
</div>
)
},
value: 'snInitialValue',
comp: 'DSInputNumber',
isSelfCom: true,
bind: {
min: 0,
max: 99999
}
},
// sdk
{
value: 'outputMono8',
comp: 'DsSwitch',
isSelfCom: true,
widthSize: 1,
bind: {
label: '输出Mono8'
},
isHideItem: (data) => {
return data.imageSource !== '本地图像'
}
},
//
{
label: '图片缓存',
value: 'imageCache',
type: 'input',
comp: 'el-input',
bind: { maxlength: 100 },
isHideItem: (data) => {
return data.imageSource === '本地图像'
}
},
//
{
label: '',
value: 'accountType',
type: 'select-obj',
comp: 'el-switch',
list: 'accountTypeList',
value: 'stitchingEnabled',
comp: 'DsSwitch',
isSelfCom: true,
widthSize: 1,
append: 'ms'
bind: {
label: '拼接使能'
}
},
//
{
label: '状态',
value: 'status',
type: 'select-arr',
list: 'statusList',
comp: 'el-select',
arrLabel: 'key',
arrKey: 'value'
placeholder: '请输入起始高度',
labelRender: () => {
return (
<div>
<span>起始高度</span>
<span className="ds-form-des-text">上限不超过20000</span>
</div>
)
},
value: 'startHeight',
comp: 'DSInputNumber',
isSelfCom: true,
bind: {
min: 0,
max: 19999
},
isHideItem: (data) => {
return data.stitchingEnabled
}
},
//
{
placeholder: '请输入',
placeholder: '拼接高度',
labelRender: () => {
return <div style="color:red;">SN初始值 (上限不超过100000)</div>
return (
<div>
<span>拼接高度</span>
<span className="ds-form-des-text">上限不超过20000</span>
</div>
)
},
value: 'snInitialValue',
type: 'input-number',
comp: 'el-input-number',
value: 'stitchingHeight',
comp: 'DSInputNumber',
isSelfCom: true,
bind: {
min: 0,
max: 99999,
controlsPosition: 'right',
clearable: false,
suffix: () => {
return <span>ms</span>
}
max: 19999
},
isHideItem: (data) => {
return data.stitchingEnabled
}
},
//
{
label: '爱好',
value: 'hobby',
type: 'checkbox',
comp: 'el-checkbox-group',
list: 'hobbyList',
event: 'checkbox',
arrKey: 'value',
widthSize: 1
value: 'triggerClear',
comp: 'DsSwitch',
isSelfCom: true,
widthSize: 1,
bind: {
label: '触发清空'
},
isHideItem: (data) => {
return data.stitchingEnabled
}
},
//
{
label: '触发变量',
value: 'triggerVariable',
type: 'input',
comp: 'el-input',
bind: { maxlength: 100 },
isHideItem: (data) => {
return data.stitchingEnabled && data.triggerClear
}
},
//
{
label: '触发值',
value: 'triggerValue',
type: 'input',
comp: 'el-input',
bind: { maxlength: 100 },
isHideItem: (data) => {
return data.stitchingEnabled && data.triggerClear
}
}
],
//
@ -240,26 +404,6 @@ const formOpts: any = reactive({
{ key: '本地图像', value: '本地图像' },
{ key: '相机', value: '相机' },
{ key: 'SDK', value: 'SDK' }
],
hobbyList: [
{ label: '吉他', value: '0' },
{ label: '看书', value: '1' },
{ label: '美剧', value: '2' },
{ label: '旅游', value: '3' },
{ label: '音乐', value: '4' }
],
sexList: [
{ key: '女', value: 1 },
{ key: '男', value: 0 }
],
accountTypeList: {
0: '手机用户',
1: '论坛用户',
2: '平台用户'
},
statusList: [
{ key: '启用', value: 1 },
{ key: '停用', value: 0 }
]
}
}
@ -271,74 +415,66 @@ const formOpts: any = reactive({
opts: {
labelPosition: 'top', //
formData: {
phone: null, //
createDate: null, //
valDate: null, // el
wechat: 0, //
qq: null, // qq
email: null, //
desc: null, //
number: 2 //
/**触发设置-本地图像字段**/
autoSwitch: false, // *
stopOnLast: false, // *
characterTriggerFilter: false, // *
inputCharacter: '', // *
triggerCharacter: '' // *
},
fieldList: [
{
label: '手机号码',
value: 'phone',
type: 'input',
comp: 'el-input',
bind: { maxlength: 11 }
},
{
label: '创建时间',
value: 'createDate',
type: 'year',
bind: { valueFormat: 'YYYY' },
comp: 'el-date-picker'
value: 'autoSwitch',
comp: 'DsSwitch',
isSelfCom: true,
widthSize: 3,
bind: {
label: '自动切换'
},
isHideItem: (data) => {
return formOpts.baseConfig.opts.formData.imageSource === '本地图像'
}
},
{
label: 'element日期',
value: 'valDate',
type: 'daterange',
comp: 'el-date-picker',
value: 'stopOnLast',
comp: 'DsSwitch',
isSelfCom: true,
widthSize: 3,
bind: {
rangeSeparator: '-',
startPlaceholder: '开始日期',
endPlaceholder: '结束日期',
valueFormat: 'YYYY-MM-DD'
label: '最后一张停止'
},
isHideItem: (data) => {
return formOpts.baseConfig.opts.formData.imageSource === '本地图像' && data.autoSwitch
}
},
{
label: '测试自定义组件1',
value: 'wechat',
placeholder: '测试自定义组件1',
comp: 'DSInputNumber',
value: 'characterTriggerFilter',
comp: 'DsSwitch',
isSelfCom: true,
widthSize: 1,
bind: {
min: 1,
max: 10,
controlsPosition: 'right',
clearable: false,
suffixIconType: 'default'
},
eventHandle: {
change: (val: any) => {}
label: '字符触发过滤'
}
},
{ label: 'QQ', value: 'qq', type: 'input', comp: 'el-input' },
{ label: '邮箱', value: 'email', type: 'input', comp: 'el-input' },
{
label: '计数器',
value: 'number',
type: 'inputNumber',
bind: { controlsPosition: 'right', min: 2, max: 99, append: '123' },
comp: 'el-input-number'
label: '输入字符',
value: 'inputCharacter',
type: 'input',
comp: 'el-input',
bind: { maxlength: 100 },
isHideItem: (data) => {
return data.characterTriggerFilter
}
},
{
label: '描述',
value: 'desc',
type: 'textarea',
label: '触发字符',
value: 'triggerCharacter',
type: 'input',
comp: 'el-input',
widthSize: 1
bind: { maxlength: 100 },
isHideItem: (data) => {
return data.characterTriggerFilter
}
}
]
}

Loading…
Cancel
Save