Merge branch 'dev' of 192.168.0.100:smart_bench/cmvr-iot-ui into dev

This commit is contained in:
lixiaolong 2025-11-20 16:04:51 +08:00
commit 6558135b8e
24 changed files with 4736 additions and 3737 deletions

View File

@ -17,6 +17,14 @@ export function flowExecute(data) {
}) })
} }
export function flowAction(data) {
return request({
url: '/flow/action',
method: 'post',
data: data
})
}
export function flowExecuteTrial(data) { export function flowExecuteTrial(data) {
return request({ return request({
url: '/flow/executeTrial', url: '/flow/executeTrial',

View File

@ -0,0 +1,46 @@
import request from '@/utils/request'
// 开始录音
export function startMicrophoneApi(data) {
return request({
url: '/api/Microphone/start',
method: 'post',
params: data
})
}
// 暂停录音
export function pauseMicrophoneApi(data) {
return request({
url: '/api/Microphone/pause',
method: 'post',
params: data
})
}
// 恢复录音
export function resumeMicrophoneApi(data) {
return request({
url: '/api/Microphone/resume',
method: 'post',
params: data
})
}
// 停止录音
export function stopMicrophoneApi(data) {
return request({
url: '/api/Microphone/stop',
method: 'post',
params: data
})
}
// 播放音频
export function playSpeaker(data) {
return request({
url: '/api/Speaker/play',
method: 'post',
params: data
})
}

View File

@ -1,6 +1,7 @@
import { createWebHistory, createRouter } from "vue-router"; import { createWebHistory, createRouter } from "vue-router";
/* Layout */ /* Layout */
import Layout from "@/layout"; import Layout from "@/layout";
import path from "path";
/** /**
* Note: 路由配置项 * Note: 路由配置项
@ -92,7 +93,14 @@ export const constantRoutes = [
name: "灵巧手示教", name: "灵巧手示教",
meta: { title: "灵巧手详情" }, meta: { title: "灵巧手详情" },
hidden: true, hidden: true,
}, },{
path: "microphone/:id",
component: () =>
import("@/views/device/register/components/Microphone/index.vue"),
name: "麦克风示教",
meta: { title: "麦克风详情" },
hidden: true,
}
], ],
}, },
{ {

View File

@ -127,6 +127,34 @@ export const formatTableData = (arr, data = {}) => {
} }
export const addNewEdge = (nodeId) => { export const addNewEdge = (nodeId) => {
const { edges } = lf.getGraphData();
const ttt = edges.slice(); // 浅拷贝(比 JSON 深拷贝高效 10x+
const edgesToDelete = ttt.filter(_edge =>
_edge.sourceNodeId === nodeId || _edge.targetNodeId === nodeId
);
edgesToDelete.forEach(edge => lf.deleteEdge(edge.id));
if (window.addNewEdgeTimer) {
clearTimeout(window.addNewEdgeTimer); // 清理上一次未执行的定时器
}
window.addNewEdgeTimer = setTimeout(() => {
edgesToDelete.forEach(item => {
lf.addEdge({
type: "bezier",
sourceNodeId: item.sourceNodeId,
targetNodeId: item.targetNodeId,
sourceAnchorId: item.sourceAnchorId,
targetAnchorId: item.targetAnchorId
});
})
delete window.addNewEdgeTimer;
}, 50)
}
export const newEdge = (nodeId) => {
const { edges } = lf.getGraphData(); const { edges } = lf.getGraphData();
const arr = JSON.parse(JSON.stringify(edges)) const arr = JSON.parse(JSON.stringify(edges))
arr.forEach(_edge => { arr.forEach(_edge => {
@ -144,6 +172,22 @@ export const addNewEdge = (nodeId) => {
}) })
} }
export const initNodeZoom = (modelId, nodeZoom, className, init=false) => {
const nodes = document.getElementsByClassName(modelId)
if (nodes.length > 0) {
const node = nodes[0]
const parent = node.closest(className);
if (nodeZoom) {
parent.style.width = '680px'
} else {
parent.style.width = '380px'
}
if (!init) {
addNewEdge(modelId)
}
}
}
export const getInputNumber = (nodeId) => { export const getInputNumber = (nodeId) => {
const data = lf.getGraphData(); const data = lf.getGraphData();
let flag = false let flag = false

View File

@ -1,7 +1,5 @@
<template> <template>
<div class="app-container"> <div class="app-container">
<TableSearch <TableSearch
:queryParams="queryParams" :queryParams="queryParams"
:showSearch="showSearch" :showSearch="showSearch"

View File

@ -0,0 +1,225 @@
<template>
<div class="microphone-container">
<div class="title">远程麦克风</div>
<div class="audiosList" v-if="audiosList.length > 0">
<div class="speaker-container">
<div class="speaker-title">选择扬声器</div>
<el-select v-model="speaker" size="small">
<el-option
v-for="item in speakerOptions"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</div>
<div class="audioItem" v-for="item in audiosList">
<div>{{ item }}</div>
<div>
<el-button
circle
size="small"
:icon="VideoPlay"
@click="handlePlay(item)"
></el-button>
</div>
</div>
</div>
<div class="btn-container">
<el-tooltip
class="box-item"
effect="dark"
content="点击开始录音"
placement="top-start"
v-if="recordStatus === 'end'"
>
<el-icon class="btn" :size="40" @click="startRecording"><Microphone /></el-icon>
</el-tooltip>
<el-tooltip
class="box-item"
effect="dark"
content="点击暂停录音"
placement="top-start"
v-if="recordStatus === 'recording'"
>
<el-icon class="btn" :size="40" @click="pauseRecording"><VideoPause /></el-icon>
</el-tooltip>
<el-tooltip
class="box-item"
effect="dark"
content="点击恢复录音"
placement="top-start"
v-if="recordStatus === 'pause'"
>
<el-icon class="btn" :size="40" @click="recoverRecording"><Mic /></el-icon>
</el-tooltip>
<el-tooltip
class="box-item"
effect="dark"
content="点击结束录音"
placement="top-start"
v-if="recordStatus !== 'end'"
>
<el-icon class="btn" :size="40" @click="stopRecording"><Mute /></el-icon>
</el-tooltip>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import { Microphone, VideoPause, Mute, Mic, VideoPlay } from "@element-plus/icons-vue";
import {
startMicrophoneApi,
pauseMicrophoneApi,
resumeMicrophoneApi,
stopMicrophoneApi,
playSpeaker
} from '@/api/device/microphone'
import { useRoute } from 'vue-router'
import { ElMessage } from "element-plus";
const route = useRoute()
const terminalId = route.query.terminalId
const deviceId = route.query.deviceId
const recordStatus = ref('end');
const isRecording = ref(false)
const list = []
const audiosList = ref([])
const speaker = ref('spk1')
const speakerOptions = ref(['spk1'])
/**
* 开始录音
*/
const startRecording = async () => {
const fileName = new Date().getTime() + '.wav'
isRecording.value = true
const res = await startMicrophoneApi({
terminalId,
deviceId,
filePath: `/home/share/record/audios/${terminalId}_${deviceId}_${fileName}`
})
if (res.code === 200) {
recordStatus.value = 'recording'
list.push(`${terminalId}_${deviceId}_${fileName}`)
}
};
/**
* 暂停录音
*/
const pauseRecording = async () => {
const res = await pauseMicrophoneApi({
terminalId,
deviceId
})
if (res.code === 200) {
recordStatus.value = 'pause'
}
};
/**
* 恢复录音
*/
const recoverRecording = async () => {
const res = await resumeMicrophoneApi({
terminalId,
deviceId
})
if (res.code === 200) {
recordStatus.value = 'recording'
}
}
/**
* 结束录音
*/
const stopRecording = async () => {
const res = await stopMicrophoneApi({
terminalId,
deviceId
})
if (res.code === 200) {
recordStatus.value = 'end'
audiosList.value.unshift(list[list.length - 1])
}
isRecording.value = false
};
/**
* 播放录音
*/
const handlePlay = async (audioPath) => {
const res = await playSpeaker({
terminalId,
deviceId: speaker.value,
audioPath: `/home/share/record/audios/${audioPath}`
})
if (res.code === 200) {
ElMessage.success('播放成功')
}
}
</script>
<style lang="scss" scoped>
.microphone-container {
display: flex;
flex: 1;
flex-direction: column;
max-width: 800px;
width: 100%;
height: calc(100vh - 125px);
background-color: #fff;
border-radius: 20px;
margin: auto;
.audiosList {
margin: 20px;
max-height: 280px;
overflow-y: auto;
.speaker-container {
margin-bottom: 12px;
display: flex;
align-items: center;
gap: 12px;
.speaker-title {
width: 80px;
}
.el-select {
width: 200px;
}
}
.audioItem {
display: flex;
align-items: center;
gap: 20px;
}
}
.title {
padding-top: 20px;
font-size: 20px;
text-align: center;
color: #333;
}
.btn-container {
margin-top: 20px;
display: flex;
align-items: center;
justify-content: center;
.btn {
margin-right: 10px;
cursor: pointer;
}
}
}
</style>

View File

@ -378,6 +378,10 @@ function handleControl(row) {
const fullPath = `${row.deviceModel}/${row.id}`; // const fullPath = `${row.deviceModel}/${row.id}`; //
router.push({ router.push({
path: fullPath, path: fullPath,
query: {
terminalId: row.idDeDeviceTerminalConfig,
deviceId: row.deviceCode
}
}); // 使 }); // 使
// intoControlPage(`/${row.deviceModel}`, `${row.id}`) // intoControlPage(`/${row.deviceModel}`, `${row.id}`)
} }

View File

@ -2,27 +2,71 @@
<div class="title__container"> <div class="title__container">
<div class="title"> <div class="title">
<div class="left"> <div class="left">
<img :src="icon" alt=""> <img :src="icon" alt="" />
<div class="text__container"> <div class="text__container">
<div class="text__title" v-if="showTextTitle"> <div class="text__title" v-if="showTextTitle">
<span class="text">{{ nodeName }}</span> <span class="text">{{ nodeName }}</span>
<el-icon class="editIcon" @click="showTextTitle = false"><EditPen /></el-icon> <el-icon class="editIcon" @click="showTextTitle = false"
><EditPen
/></el-icon>
</div> </div>
<div class="input_name_container" v-else> <div class="input_name_container" v-else>
<el-input v-model="nodeName" /> <el-input v-model="nodeName" />
<el-button class="input_name_select" circle size="small" @click="confirmNodeName"> <el-button
class="input_name_select"
circle
size="small"
@click="confirmNodeName"
>
<el-icon><Select /></el-icon> <el-icon><Select /></el-icon>
</el-button> </el-button>
<el-button class="input_name_closeBold" circle size="small" @click="cancelNodeName"> <el-button
class="input_name_closeBold"
circle
size="small"
@click="cancelNodeName"
>
<el-icon><CloseBold /></el-icon> <el-icon><CloseBold /></el-icon>
</el-button> </el-button>
</div> </div>
</div> </div>
</div> </div>
<div class="right"> <div class="right">
<el-button circle size="small" @click="deleteNode"> <el-tooltip
class="box-item"
effect="dark"
content="执行"
placement="top"
>
<el-button v-if="nodeType && nodeType !=='NONE'" circle @click="execute">
<el-icon>
<VideoPlay />
</el-icon>
</el-button>
</el-tooltip>
<el-tooltip
class="box-item"
effect="dark"
:content="zoomState ? '缩小' : '放大'"
placement="top"
>
<el-button v-if="showZoom" circle @click="zoom">
<el-icon>
<ZoomOut v-if="zoomState" />
<ZoomIn v-else />
</el-icon>
</el-button>
</el-tooltip>
<el-tooltip
class="box-item"
effect="dark"
content="删除"
placement="top"
>
<el-button circle @click="deleteNode">
<el-icon><Delete /></el-icon> <el-icon><Delete /></el-icon>
</el-button> </el-button>
</el-tooltip>
</div> </div>
</div> </div>
<div class="subTitle">{{ props.nodeDesc }}</div> <div class="subTitle">{{ props.nodeDesc }}</div>
@ -30,7 +74,7 @@
</template> </template>
<script setup lang="js"> <script setup lang="js">
import { ref } from 'vue' import { ref } from 'vue'
import { EditPen, Select, CloseBold, Delete } from '@element-plus/icons-vue' import { EditPen, Select, CloseBold, Delete, ZoomOut, ZoomIn, VideoPlay } from '@element-plus/icons-vue'
import { ElMessageBox } from 'element-plus' import { ElMessageBox } from 'element-plus'
const props = defineProps({ const props = defineProps({
@ -57,9 +101,19 @@ const props = defineProps({
nodeType: { nodeType: {
type: String, type: String,
default: '' default: ''
},
showZoom: {
type: Boolean,
default: true
},
zoomState: {
type: Boolean,
default: false
} }
}) })
const emits = defineEmits(['zoom'])
const showTextTitle = ref(true) const showTextTitle = ref(true)
const nodeName = ref(props.nodeProperties.name || props.nodeName) const nodeName = ref(props.nodeProperties.name || props.nodeName)
@ -98,6 +152,19 @@ const deleteNode = () => {
lf.deleteNode(props.nodeId); lf.deleteNode(props.nodeId);
}) })
} }
const zoom = () => {
emits('zoom', !props.zoomState)
}
const execute = () => {
const myEvent = new CustomEvent('singleNodeExecution', {
bubbles: false, // false
cancelable: false, // preventDefault() false
detail: { ...props.nodeProperties } // event.detail
});
document.dispatchEvent(myEvent);
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.title__container { .title__container {
@ -158,6 +225,7 @@ const deleteNode = () => {
.el-button { .el-button {
border: none; border: none;
font-size: 24px;
} }
} }
} }
@ -167,5 +235,5 @@ const deleteNode = () => {
font-size: 12px; font-size: 12px;
margin: 8px 0; margin: 8px 0;
} }
} }
</style> </style>

View File

@ -16,6 +16,8 @@ import planSvg from './icon/plan.svg'
import awakenSvg from './icon/awaken.svg' import awakenSvg from './icon/awaken.svg'
import dialogueSvg from './icon/dialogue.svg' import dialogueSvg from './icon/dialogue.svg'
import audioSvg from './icon/audio.svg' import audioSvg from './icon/audio.svg'
import touchSvg from './icon/touch.svg'
import expressionSvg from './icon/expression.svg'
import { v4 as randomUUID } from 'uuid' import { v4 as randomUUID } from 'uuid'
@ -40,6 +42,11 @@ export const lfConfig = {
// foldSize: 30, // 折叠后显示的图标尺寸 // foldSize: 30, // 折叠后显示的图标尺寸
// }, // },
edgeType: "bezier", edgeType: "bezier",
zoomConfig: {
min: 0.05, // 最小缩放比例(支持更小值,如 0.01,但不建议过小)
max: 3, // 最大缩放比例(可自定义)
step: 0.1 // 每次滚轮缩放的步长(默认 0.1
},
style: { style: {
anchor: { anchor: {
show: true, // 强制全局锚点显示 show: true, // 强制全局锚点显示
@ -73,11 +80,39 @@ export const registerCustomizeNode = (lf) => {
}; };
const deviceOptions = () => { const deviceOptions = () => {
return ['cam1', 'cam2', 'cam3', 'cam4'] return [{
value: 'cam1',
label: 'cam1'
}, {
value: 'cam2',
label: 'cam2'
}, {
value: 'cam3',
label: 'cam3'
}, {
value: 'cam4',
label: 'cam4'
}]
} }
const audioOptions = () => { const audioOptions = () => {
return ['spk1'] return [{
value: 'spk1',
label: 'spk1'
}]
}
const expressOptions = () => {
return [{
value: 1,
label: '高兴'
}, {
value: 2,
label: '惊讶'
}, {
value: 3,
label: '疲惫'
}]
} }
export const collapseList = [ export const collapseList = [
@ -90,6 +125,7 @@ export const collapseList = [
type: "serviceNode", type: "serviceNode",
desc: "在获取相机照片时,需要先启动相机", desc: "在获取相机照片时,需要先启动相机",
action: 'CAMERA_START', action: 'CAMERA_START',
nodeType: 'EDGE',
nodeParams: [ nodeParams: [
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true } { name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true }
], ],
@ -101,6 +137,7 @@ export const collapseList = [
type: "serviceNode", type: "serviceNode",
desc: "获取相机拍摄的照片", desc: "获取相机拍摄的照片",
action: 'CAMERA_GETRGBIMAGE', action: 'CAMERA_GETRGBIMAGE',
nodeType: 'EDGE',
nodeParams: [ nodeParams: [
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true } { name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true }
], ],
@ -113,6 +150,7 @@ export const collapseList = [
type: "serviceNode", type: "serviceNode",
desc: "获取相机照片后,关闭该相机", desc: "获取相机照片后,关闭该相机",
action: 'CAMERA_STOP', action: 'CAMERA_STOP',
nodeType: 'EDGE',
nodeParams: [ nodeParams: [
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true } { name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true }
], ],
@ -129,6 +167,7 @@ export const collapseList = [
type: "serviceNode", type: "serviceNode",
desc: "开启摄像头,并开始录像", desc: "开启摄像头,并开始录像",
action: 'CAMERA_RECORDING_START', action: 'CAMERA_RECORDING_START',
nodeType: 'EDGE',
nodeParams: [ nodeParams: [
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true } { name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true }
], ],
@ -140,6 +179,7 @@ export const collapseList = [
type: "serviceNode", type: "serviceNode",
desc: "停止录像,并关闭摄像头", desc: "停止录像,并关闭摄像头",
action: 'CAMERA_RECORDING_STOP', action: 'CAMERA_RECORDING_STOP',
nodeType: 'EDGE',
nodeParams: [ nodeParams: [
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true } { name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true }
], ],
@ -224,6 +264,7 @@ export const collapseList = [
type: "serviceNode", type: "serviceNode",
desc: "用于启动麦克风的节点", desc: "用于启动麦克风的节点",
action: 'MICROPHONE_START', action: 'MICROPHONE_START',
nodeType: 'EDGE',
nodeParams: [ nodeParams: [
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true } { name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true }
], ],
@ -235,6 +276,7 @@ export const collapseList = [
type: "serviceNode", type: "serviceNode",
desc: "用于停止麦克风的节点", desc: "用于停止麦克风的节点",
action: 'MICROPHONE_START', action: 'MICROPHONE_START',
nodeType: 'EDGE',
nodeParams: [ nodeParams: [
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true } { name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true }
], ],
@ -262,6 +304,7 @@ export const collapseList = [
type: "serviceNode", type: "serviceNode",
desc: "用于获取触控坐标的节点", desc: "用于获取触控坐标的节点",
action: 'TOUCH_COORDINATES', action: 'TOUCH_COORDINATES',
nodeType: "LLM",
nodeParams: [ nodeParams: [
{ name: "targetFeature", type: "input", input: "运动模式", disabled: true }, { name: "targetFeature", type: "input", input: "运动模式", disabled: true },
{ name: "manufacturer", type: "input", input: "xiaomi", disabled: true }, { name: "manufacturer", type: "input", input: "xiaomi", disabled: true },
@ -275,6 +318,7 @@ export const collapseList = [
type: "serviceNode", type: "serviceNode",
desc: "将指令文本(text)处理合成语音,输出语音路径(audioPath)", desc: "将指令文本(text)处理合成语音,输出语音路径(audioPath)",
action: 'GENERATE_ADVANCED_AUDIO', action: 'GENERATE_ADVANCED_AUDIO',
nodeType: "LLM",
outputType: 'json', outputType: 'json',
nodeParams: [{ name: 'text', type: "input", input: "", disabled: true }], nodeParams: [{ name: 'text', type: "input", input: "", disabled: true }],
outputParams: [{ name: 'audioPath', type: 'string', desc: '语音路径', disabled: true}] outputParams: [{ name: 'audioPath', type: 'string', desc: '语音路径', disabled: true}]
@ -284,6 +328,7 @@ export const collapseList = [
type: "serviceNode", type: "serviceNode",
desc: "意图识别,输出类型(type) 1: 语音交互 2: 触控交互 3: 闲聊 4: 知识问答", desc: "意图识别,输出类型(type) 1: 语音交互 2: 触控交互 3: 闲聊 4: 知识问答",
action: 'INTENT_RECOGNITION', action: 'INTENT_RECOGNITION',
nodeType: "LLM",
outputType: 'json', outputType: 'json',
nodeParams: [{ name: 'text', type: "input", input: "", disabled: true }], nodeParams: [{ name: 'text', type: "input", input: "", disabled: true }],
outputParams: [{ name: 'type', type: 'string', desc: '1语音交互 2触控交互 3: 闲聊 4:知识问答', disabled: true}] outputParams: [{ name: 'type', type: 'string', desc: '1语音交互 2触控交互 3: 闲聊 4:知识问答', disabled: true}]
@ -312,6 +357,7 @@ export const collapseList = [
type: "serviceNode", type: "serviceNode",
desc: "唤醒语料处理,输入唤醒语料(wakeCorpus),输出语音路径(audioPath)", desc: "唤醒语料处理,输入唤醒语料(wakeCorpus),输出语音路径(audioPath)",
action: 'VI_CORPUS_WAKE', action: 'VI_CORPUS_WAKE',
nodeType: "EDGE",
outputType: 'json', outputType: 'json',
nodeParams: [{ name: 'wakeCorpus', type: "input", input: "", disabled: true }], nodeParams: [{ name: 'wakeCorpus', type: "input", input: "", disabled: true }],
outputParams: [{ name: 'audioPath', type: 'string', desc: '语音路径', disabled: true}] outputParams: [{ name: 'audioPath', type: 'string', desc: '语音路径', disabled: true}]
@ -322,6 +368,7 @@ export const collapseList = [
type: "serviceNode", type: "serviceNode",
desc: "测试语料-单次对话语料处理,输入单次对话语料(testCorpus),输出语音路径(audioPath)", desc: "测试语料-单次对话语料处理,输入单次对话语料(testCorpus),输出语音路径(audioPath)",
action: 'VI_CORPUS_SINGLE', action: 'VI_CORPUS_SINGLE',
nodeType: "EDGE",
outputType: 'json', outputType: 'json',
nodeParams: [{ name: 'testCorpus', type: "input", input: "", disabled: true }], nodeParams: [{ name: 'testCorpus', type: "input", input: "", disabled: true }],
outputParams: [{ name: 'audioPath', type: 'string', desc: '语音路径', disabled: true}] outputParams: [{ name: 'audioPath', type: 'string', desc: '语音路径', disabled: true}]
@ -332,6 +379,7 @@ export const collapseList = [
type: "serviceNode", type: "serviceNode",
desc: "测试语料-连续对话语料处理,输入连续对话语料(testCorpus),输出语音路径(audioPath)", desc: "测试语料-连续对话语料处理,输入连续对话语料(testCorpus),输出语音路径(audioPath)",
action: 'VI_CORPUS_CONTINUOUS', action: 'VI_CORPUS_CONTINUOUS',
nodeType: "EDGE",
outputType: 'json', outputType: 'json',
nodeParams: [{ name: 'testCorpus', type: "input", input: "", disabled: true }], nodeParams: [{ name: 'testCorpus', type: "input", input: "", disabled: true }],
outputParams: [{ name: 'audioPath', type: 'string', desc: '语音路径', disabled: true}] outputParams: [{ name: 'audioPath', type: 'string', desc: '语音路径', disabled: true}]
@ -342,6 +390,7 @@ export const collapseList = [
type: "serviceNode", type: "serviceNode",
desc: "用于播放语料的节点,需要输入语音路径(audioPath)和播放语音的设备id(deviceId)", desc: "用于播放语料的节点,需要输入语音路径(audioPath)和播放语音的设备id(deviceId)",
action: 'VI_PLAY_CORPUS', action: 'VI_PLAY_CORPUS',
nodeType: "EDGE",
outputType: 'json', outputType: 'json',
nodeParams: [ nodeParams: [
{ name: 'audioPath', type: "input", input: "", disabled: true }, { name: 'audioPath', type: "input", input: "", disabled: true },
@ -350,4 +399,59 @@ export const collapseList = [
} }
], ],
}, },
{
collapseTitle: "触控交互",
nodeList: [
{
icon: touchSvg,
name: "触控",
type: "serviceNode",
desc: "去触控坐标的点位",
action: 'TOUCH',
nodeType: "EDGE",
nodeParams: [
{ name: "deviceId", type: "input", input: "", disabled: true },
{ name: "touchParams", type: "input", input: "", disabled: true }
],
outputType: 'json',
},
{
icon: expressionSvg,
name: "开始说话",
type: "serviceNode",
desc: "控制机器人说话",
action: 'BIO_HEAD_SPEAK_START',
nodeType: "EDGE",
nodeParams: [
{ name: "deviceId", type: "input", input: "", disabled: true }
],
outputType: 'json',
},
{
icon: expressionSvg,
name: "停止说话",
type: "serviceNode",
desc: "停止机器人说话",
action: 'BIO_HEAD_SPEAK_STOP',
nodeType: "EDGE",
nodeParams: [
{ name: "deviceId", type: "input", input: "", disabled: true }
],
outputType: 'json',
},
{
icon: expressionSvg,
name: "表情",
type: "serviceNode",
desc: "控制机器人的面部表情",
action: 'BIO_HEAD_SPECIAL_EXPRESSION',
nodeType: "EDGE",
nodeParams: [
{ name: "deviceId", type: "input", input: "", disabled: true },
{ name: "expressKey", type: "input",componentType: 'select', selectOptions: expressOptions(), disabled: true },
],
outputType: 'json',
},
],
},
] ]

View File

@ -1 +1 @@
<svg t="1758526744847" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8113" width="200" height="200"><path d="M512 117.76c218.065455 0 395.636364 135.68 395.636364 302.545455s-177.570909 302.545455-395.636364 302.545454a144.058182 144.058182 0 0 0-50.501818 10.472727 791.272727 791.272727 0 0 0-162.909091 99.374546c-4.421818-128.232727-23.272727-168.96-56.087273-191.534546C162.909091 584.145455 116.363636 503.621818 116.363636 420.538182c0-167.098182 177.570909-302.545455 395.636364-302.545455m0-69.818182c-256 0-465.454545 166.632727-465.454545 372.363637 0 110.312727 59.810909 209.454545 155.22909 277.643636 23.272727 16.756364 27.694545 116.363636 27.694546 192.232727a41.192727 41.192727 0 0 0 40.494545 41.89091 40.029091 40.029091 0 0 0 23.272728-8.61091c58.414545-45.149091 141.730909-105.425455 192-124.50909a74.007273 74.007273 0 0 1 26.763636-6.05091c256 0 465.454545-166.865455 465.454545-372.363636s-209.454545-372.363636-465.454545-372.363636z" fill="#65BEE0" p-id="8114"></path><path d="M663.272727 325.818182h-302.545454a34.909091 34.909091 0 0 0 0 69.818182h302.545454a34.909091 34.909091 0 0 0 0-69.818182zM616.727273 488.727273h-209.454546a34.909091 34.909091 0 0 0 0 69.818182h209.454546a34.909091 34.909091 0 0 0 0-69.818182z" fill="#65BEE0" p-id="8115"></path></svg> <svg t="1762763163012" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="23408" width="200" height="200"><path d="M0 0m234.057143 0l555.885714 0q234.057143 0 234.057143 234.057143l0 555.885714q0 234.057143-234.057143 234.057143l-555.885714 0q-234.057143 0-234.057143-234.057143l0-555.885714q0-234.057143 234.057143-234.057143Z" fill="#65BEE0" p-id="23409" data-spm-anchor-id="a313x.search_index.0.i15.2cb83a81yhcKKA" class=""></path><path d="M735.319771 277.942857H376.451657A69.266286 69.266286 0 0 0 307.2 347.194514v197.953829A69.266286 69.266286 0 0 0 376.451657 614.4H446.171429s3.657143 43.885714-25.6 58.514286c0 0 72.206629-14.628571 94.6176-58.514286h220.130742A69.266286 69.266286 0 0 0 804.571429 545.148343V347.194514A69.266286 69.266286 0 0 0 735.319771 277.942857zM446.171429 482.742857a29.257143 29.257143 0 0 1-29.257143-29.257143 29.257143 29.257143 0 0 1 29.257143-29.257143 29.257143 29.257143 0 0 1 29.257142 29.257143 29.257143 29.257143 0 0 1-29.257142 29.257143z m109.714285 0a29.257143 29.257143 0 0 1-29.257143-29.257143 29.257143 29.257143 0 0 1 29.257143-29.257143 29.257143 29.257143 0 0 1 29.257143 29.257143 29.257143 29.257143 0 0 1-29.257143 29.257143z m109.714286 0a29.257143 29.257143 0 0 1-29.257143-29.257143 29.257143 29.257143 0 0 1 29.257143-29.257143 29.257143 29.257143 0 0 1 29.257143 29.257143 29.257143 29.257143 0 0 1-29.257143 29.257143z" fill="#ffffff" opacity=".34" p-id="23410" data-spm-anchor-id="a313x.search_index.0.i17.2cb83a81yhcKKA" class="selected"></path><path d="M662.176914 351.085714H303.3088A69.266286 69.266286 0 0 0 234.057143 420.337371v197.953829A69.266286 69.266286 0 0 0 303.3088 687.542857H373.028571s3.657143 43.885714-25.6 58.514286c0 0 72.206629-14.628571 94.6176-58.514286h220.130743A69.266286 69.266286 0 0 0 731.428571 618.2912V420.337371A69.266286 69.266286 0 0 0 662.176914 351.085714zM373.028571 555.885714a29.257143 29.257143 0 0 1-29.257142-29.257143 29.257143 29.257143 0 0 1 29.257142-29.257142 29.257143 29.257143 0 0 1 29.257143 29.257142 29.257143 29.257143 0 0 1-29.257143 29.257143z m109.714286 0a29.257143 29.257143 0 0 1-29.257143-29.257143 29.257143 29.257143 0 0 1 29.257143-29.257142 29.257143 29.257143 0 0 1 29.257143 29.257142 29.257143 29.257143 0 0 1-29.257143 29.257143z m109.714286 0a29.257143 29.257143 0 0 1-29.257143-29.257143 29.257143 29.257143 0 0 1 29.257143-29.257142 29.257143 29.257143 0 0 1 29.257143 29.257142 29.257143 29.257143 0 0 1-29.257143 29.257143z" fill="#ffffff" p-id="23411" data-spm-anchor-id="a313x.search_index.0.i14.2cb83a81yhcKKA" class="selected"></path></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1 @@
<svg t="1762999534829" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4760" width="200" height="200"><path d="M512 979C263.472 979 62 777.528 62 529S263.472 79 512 79s450 201.472 450 450-201.472 450-450 450zM337 479c41.421 0 75-33.579 75-75s-33.579-75-75-75-75 33.579-75 75 33.579 75 75 75z m350 0c41.421 0 75-33.579 75-75s-33.579-75-75-75-75 33.579-75 75 33.579 75 75 75zM312 629c0 110.457 89.543 200 200 200s200-89.543 200-200H312z" fill="#65BEE0" p-id="4761"></path></svg>

After

Width:  |  Height:  |  Size: 520 B

View File

@ -0,0 +1 @@
<svg t="1762762024042" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7115" width="200" height="200"><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#65BEE0" p-id="7116" data-spm-anchor-id="a313x.search_index.0.i4.2cb83a81yhcKKA" class="selected"></path><path d="M440.018824 310.452706c6.625882 0 13.703529 2.198588 20.781176 7.68 11.294118 8.884706 17.709176 22.588235 17.769412 36.954353l0.391529 108.242823 0.120471 43.248942 0.150588 31.442823s20.239059-27.648 49.935059-27.648c15.932235 0 34.484706 7.890824 54.091294 32.165647 0 0 18.191059-30.539294 47.435294-30.539294 15.36 0 33.731765 8.342588 54.091294 33.912471 0 0 17.588706-34.424471 41.562353-34.424471 8.071529 0 16.865882 3.915294 25.99153 14.486588 10.752 12.739765 16.564706 29.063529 16.564705 45.808941v230.098824s-0.722824 5.963294-7.168 14.938353a63.608471 63.608471 0 0 1-51.83247 26.352941l-239.555765 0.060235a73.396706 73.396706 0 0 1-57.313882-27.557647l-131.373177-164.111059a46.019765 46.019765 0 0 1-6.595764-46.983529c4.517647-10.390588 13.251765-19.275294 29.635764-19.275294 8.432941 0 18.763294 2.349176 31.62353 7.951059 42.736941 19.154824 58.006588 59.542588 58.006588 59.542588l0.692706-146.100706 0.210823-43.158588 0.512-105.682824c0.060235-14.546824 6.716235-28.400941 18.31153-37.255529 7.228235-5.571765 16.263529-10.149647 25.961412-10.149647zM436.705882 180.705882a165.647059 165.647059 0 0 1 123.723294 275.787294 38.068706 38.068706 0 0 1-29.455058 12.468706h-1.987765a8.794353 8.794353 0 0 1-8.734118-8.734117l-0.090353-15.450353a15.058824 15.058824 0 0 1 4.336942-10.541177l0.090352-0.090353a123.392 123.392 0 0 0 36.352-87.792941c0-33.189647-12.950588-64.391529-36.352-87.883294A123.663059 123.663059 0 0 0 436.705882 222.117647c-33.189647 0-64.391529 12.950588-87.883294 36.352a123.663059 123.663059 0 0 0-36.352 87.883294c0 33.189647 12.950588 64.391529 36.352 87.883294l0.090353 0.060236c3.162353 3.162353 4.969412 7.499294 4.909177 11.956705a21.353412 21.353412 0 0 1-36.74353 14.697412A165.647059 165.647059 0 0 1 436.705882 180.705882z" fill="#ffffff" p-id="7117" data-spm-anchor-id="a313x.search_index.0.i3.2cb83a81yhcKKA" class=""></path></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,18 @@
<template> <template>
<div class="node__container"> <div class="node__container" :class="props.model.id">
<NodeState :state="nodeOperatingStatus"> <NodeState :state="nodeOperatingStatus">
<template #input> <template #input>
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" /> <JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
</template> </template>
<template #output> <template #output>
<JsonViewer v-if="props.properties.outputType === 'json'" :value="outputJsonData" copyable boxed sort theme="light" /> <JsonViewer
v-if="props.properties.outputType === 'json'"
:value="outputJsonData"
copyable
boxed
sort
theme="light"
/>
<el-image <el-image
v-if="props.properties.outputType === 'img'" v-if="props.properties.outputType === 'img'"
v-for="item in outputJsonData.imageUrl" v-for="item in outputJsonData.imageUrl"
@ -16,17 +23,24 @@
show-progress show-progress
fit="fill" fit="fill"
/> />
<IPlayer v-if="props.properties.outputType === 'video'" v-for="item in outputJsonData.videoUrl" :videoUrl="item" /> <IPlayer
v-if="props.properties.outputType === 'video'"
v-for="item in outputJsonData.videoUrl"
:videoUrl="item"
/>
</template> </template>
</NodeState> </NodeState>
<NodeTitle <NodeTitle
:icon="props.properties.icon" :icon="props.properties.icon"
:nodeId="props.model.id" :nodeId="props.model.id"
:nodeProperties="props.properties" :nodeProperties="props.properties"
:nodeType="props.properties.nodeType || 'NONE'"
:nodeName="props.properties.name" :nodeName="props.properties.name"
:nodeDesc="props.properties.desc" :nodeDesc="props.properties.desc"
:zoom-state="nodeZoom"
@zoom="zoom"
/> />
<div class="input__container"> <div class="input__container" v-show="nodeZoom">
<div class="title"> <div class="title">
<div class="left"> <div class="left">
<div class="tag"></div> <div class="tag"></div>
@ -57,11 +71,14 @@
<el-form-item <el-form-item
:label="index === 0 ? '参数名' : ''" :label="index === 0 ? '参数名' : ''"
:prop="`nodeParams.${index}.name`" :prop="`nodeParams.${index}.name`"
:rules="[{ required: true, message: '请输入参数名', trigger: 'blur' }]" :rules="[
{ required: true, message: '请输入参数名', trigger: 'blur' },
]"
> >
<el-input <el-input
:disabled="property?.disabled || false" :disabled="property?.disabled || false"
v-model="property.name" v-model="property.name"
@keydown="handleInputKeydown"
placeholder="请输入" placeholder="请输入"
clearable clearable
/> />
@ -70,33 +87,76 @@
:label="index === 0 ? '参数值' : ''" :label="index === 0 ? '参数值' : ''"
:prop="`nodeParams.${index}.type`" :prop="`nodeParams.${index}.type`"
> >
<el-select v-model="property.type" @change="handleTypeChange(index)"> <el-select
v-model="property.type"
@change="handleTypeChange(index)"
>
<el-option label="引用" value="quote" /> <el-option label="引用" value="quote" />
<el-option label="输入" value="input" /> <el-option label="输入" value="input" />
</el-select> </el-select>
<el-form-item <el-form-item
v-if="property.type === 'input'" v-if="property.type === 'input'"
:rules="[{ required: true, message: '请输入参数值', trigger: 'blur' }]" :rules="[
{
required: true,
message: '请输入参数值',
trigger: 'blur',
},
]"
:prop="`nodeParams.${index}.input`" :prop="`nodeParams.${index}.input`"
> >
<el-input-number v-if="property.componentType === 'number'" v-model="property.input" :min="0" :controls="false" :step-strictly="true" placeholder="请输入" clearable /> <el-input-number
<el-select v-model="property.input" v-else-if="property.componentType === 'select'" > v-if="property.componentType === 'number'"
<el-option v-for="item in property.selectOptions" :key="item" :label="item" :value="item" /> v-model="property.input"
:min="0"
:controls="false"
:step-strictly="true"
placeholder="请输入"
clearable
/>
<el-select
v-model="property.input"
v-else-if="property.componentType === 'select'"
>
<el-option
v-for="item in property.selectOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select> </el-select>
<el-input v-else v-model="property.input" placeholder="请输入" clearable /> <el-input
@keydown="handleInputKeydown"
v-else
v-model="property.input"
placeholder="请输入"
clearable
/>
</el-form-item> </el-form-item>
<el-form-item <el-form-item
v-if="property.type === 'quote'" v-if="property.type === 'quote'"
:rules="[{ required: true, message: '请选择参数值', trigger: 'blur' }]" :rules="[
{
required: true,
message: '请选择参数值',
trigger: 'blur',
},
]"
:prop="`nodeParams.${index}.quote`" :prop="`nodeParams.${index}.quote`"
> >
<el-cascader <el-cascader
:ref="el => { if (el) cascaderRefs[index] = el }" :ref="
(el) => {
if (el) cascaderRefs[index] = el;
}
"
v-model="property.quote" v-model="property.quote"
:checkStrictly="true" :checkStrictly="true"
:options="quoteOptions" :options="quoteOptions"
placeholder="请选择" placeholder="请选择"
@visible-change="(visible) => visibleChange(visible, index, property.quote)" @visible-change="
(visible) => visibleChange(visible, index, property.quote)
"
@change="(value) => cascaderChange(value, index)" @change="(value) => cascaderChange(value, index)"
/> />
</el-form-item> </el-form-item>
@ -106,13 +166,14 @@
</el-form> </el-form>
</div> </div>
</div> </div>
<div class="output__container" v-if="props.properties?.outputParams?.length > 0"> <div class="output__container" v-show="nodeZoom">
<div v-if="props.properties?.outputParams?.length > 0">
<div class="title"> <div class="title">
<div class="left"> <div class="left">
<div class="tag"></div> <div class="tag"></div>
<div class="text">输出</div> <div class="text">输出</div>
</div> </div>
<div class="right" > <div class="right">
<el-button <el-button
:disabled="flowStore.disableForm" :disabled="flowStore.disableForm"
@click="addOutputFormItem" @click="addOutputFormItem"
@ -146,11 +207,12 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</template> </template>
<script setup> <script setup>
import { ref, reactive, onMounted, onUnmounted } from "vue"; import { ref, reactive, onMounted, onUnmounted, nextTick } from "vue";
import { getInput } from "@/utils/flow"; import { getInput, initNodeZoom } from "@/utils/flow";
import { useFlowStore } from "@/store/modules/flow"; import { useFlowStore } from "@/store/modules/flow";
import { Plus } from "@element-plus/icons-vue"; import { Plus } from "@element-plus/icons-vue";
import NodeTitle from "../../components/NodeTitle.vue"; import NodeTitle from "../../components/NodeTitle.vue";
@ -171,7 +233,7 @@ const flowStore = useFlowStore();
const formData = reactive({ const formData = reactive({
nodeParams: [], nodeParams: [],
outputParams: [] outputParams: [],
}); });
const rules = reactive({}); const rules = reactive({});
@ -189,31 +251,31 @@ const handleTypeChange = (index) => {
} }
}; };
const cascaderRefs = ref([]) const cascaderRefs = ref([]);
const outputRules = reactive({}); const outputRules = reactive({});
const cascaderChange = (value, index) => { const cascaderChange = (value, index) => {
const selectedOptions = cascaderRefs.value[index].getCheckedNodes(true); const selectedOptions = cascaderRefs.value[index].getCheckedNodes(true);
formData.nodeParams[index].quote = value formData.nodeParams[index].quote = value;
formData.nodeParams[index].quoteType = selectedOptions[0].data.type formData.nodeParams[index].quoteType = selectedOptions[0].data.type;
} };
const addOutputFormItem = () => { const addOutputFormItem = () => {
formData.outputParams.push({ formData.outputParams.push({
name: '', name: "",
type: '', type: "",
desc: '', desc: "",
children: [] children: [],
}) });
} };
const dynamicForm = ref(); const dynamicForm = ref();
const outputFormRef = ref() const outputFormRef = ref();
const setNodeProperties = async () => { const setNodeProperties = async () => {
try { try {
const result = await dynamicForm.value.validate(); const result = await dynamicForm.value.validate();
let flag = true let flag = true;
if (outputFormRef.value) { if (outputFormRef.value) {
flag = await outputFormRef.value.validate() flag = await outputFormRef.value.validate();
} }
if (result && flag) { if (result && flag) {
@ -222,6 +284,7 @@ const setNodeProperties = async () => {
lf.setProperties(props.model.id, { lf.setProperties(props.model.id, {
...properties, ...properties,
...data, ...data,
zoom: nodeZoom.value
}); });
emits("contentChange"); emits("contentChange");
} }
@ -286,7 +349,7 @@ const addFormItem = () => {
// //
const deleteTopLevelItem = (fullPath) => { const deleteTopLevelItem = (fullPath) => {
// //
let currentLevel = formData.outputParams; let currentLevel = formData.outputParams;
// //
@ -300,6 +363,12 @@ const deleteTopLevelItem = (fullPath) => {
const lastIndex = fullPath[fullPath.length - 1]; const lastIndex = fullPath[fullPath.length - 1];
currentLevel.splice(lastIndex, 1); currentLevel.splice(lastIndex, 1);
emits("contentChange"); emits("contentChange");
};
const nodeZoom = ref(props?.properties?.zoom ?? true)
const zoom = (flag) => {
nodeZoom.value = flag
initNodeZoom(props.model.id, nodeZoom.value, '.node__box')
} }
watch( watch(
@ -311,8 +380,15 @@ watch(
if (option) { if (option) {
quoteOptions.value = option; quoteOptions.value = option;
} }
nextTick(() => {
initNodeZoom(props.model.id, props?.properties?.zoom ?? true, '.node__box', true)
})
} }
if (props.properties.outputParams && props.properties.outputParams.length > 0) { if (
props.properties.outputParams &&
props.properties.outputParams.length > 0
) {
formData.outputParams = props.properties.outputParams; formData.outputParams = props.properties.outputParams;
} }
}, },
@ -322,6 +398,15 @@ watch(
} }
); );
const handleInputKeydown = (e) => {
// Logic Flow
e.stopPropagation();
// Ctrl+V
if ((e.ctrlKey || e.metaKey) && e.key === "v") {
e.returnValue = true; //
}
};
onMounted(() => { onMounted(() => {
emitter.on("changeNodeState", (data) => { emitter.on("changeNodeState", (data) => {
if (data.nodeId === props.model.id) { if (data.nodeId === props.model.id) {

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="node__container"> <div class="node__container" :class="props.model.id">
<keep-alive> <keep-alive>
<NodeState :state="nodeOperatingStatus"> <NodeState :state="nodeOperatingStatus">
<template #input> <template #input>
@ -12,12 +12,29 @@
</keep-alive> </keep-alive>
<div class="title__container"> <div class="title__container">
<div class="title"> <div class="title">
<div class="left">
<img src="../../icon/start.svg" alt="" /> <img src="../../icon/start.svg" alt="" />
<span class="text">Start</span> <span class="text">Start</span>
</div>
<div class="right">
<el-tooltip
class="box-item"
effect="dark"
:content="nodeZoom ? '缩小' : '放大'"
placement="top"
>
<el-button circle @click="zoom">
<el-icon>
<ZoomOut v-if="nodeZoom" />
<ZoomIn v-else />
</el-icon>
</el-button>
</el-tooltip>
</div>
</div> </div>
<div class="subTitle">工作流的起始节点用于设定启动工作流需要的信息</div> <div class="subTitle">工作流的起始节点用于设定启动工作流需要的信息</div>
</div> </div>
<div class="input__container"> <div class="input__container" v-show="nodeZoom">
<div class="title"> <div class="title">
<div class="left"> <div class="left">
<div class="tag"></div> <div class="tag"></div>
@ -60,9 +77,10 @@
</template> </template>
<script setup> <script setup>
import { watch, reactive, toRaw, ref, onMounted, onUnmounted } from "vue"; import { watch, reactive, toRaw, ref, onMounted, onUnmounted, nextTick } from "vue";
import { Plus } from "@element-plus/icons-vue"; import { Plus, ZoomOut, ZoomIn } from "@element-plus/icons-vue";
import { useFlowStore } from "@/store/modules/flow"; import { useFlowStore } from "@/store/modules/flow";
import { initNodeZoom } from "@/utils/flow";
import NodeState from "../../components/NodeState.vue"; import NodeState from "../../components/NodeState.vue";
import "vue3-json-viewer/dist/index.css"; import "vue3-json-viewer/dist/index.css";
import { emitter } from "@/utils/eventBus"; import { emitter } from "@/utils/eventBus";
@ -112,6 +130,7 @@ const setNodeProperties = async () => {
lf.setProperties(props.model.id, { lf.setProperties(props.model.id, {
...props.properties, ...props.properties,
...data, ...data,
zoom: nodeZoom.value
}); });
emits("contentChange"); emits("contentChange");
} }
@ -129,6 +148,9 @@ watch(
() => { () => {
if (props.properties.inputParams && props.properties.inputParams.length > 0) { if (props.properties.inputParams && props.properties.inputParams.length > 0) {
formData.inputParams = props.properties.inputParams; formData.inputParams = props.properties.inputParams;
nextTick(() => {
initNodeZoom(props.model.id, props?.properties?.zoom ?? true, '.node__box', true)
})
} }
}, },
{ {
@ -200,6 +222,12 @@ const validateForm = async () => {
} }
}; };
const nodeZoom = ref(props?.properties?.zoom ?? true)
const zoom = () => {
nodeZoom.value = !nodeZoom.value
initNodeZoom(props.model.id, nodeZoom.value, '.node__box')
}
onUnmounted(() => { onUnmounted(() => {
emitter.off("changeNodeState"); emitter.off("changeNodeState");
emitter.off("contentChange"); emitter.off("contentChange");
@ -220,7 +248,9 @@ defineExpose({
.title { .title {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between;
.left {
img { img {
width: 24px; width: 24px;
height: 24px; height: 24px;
@ -233,6 +263,14 @@ defineExpose({
} }
} }
.right {
.el-button {
border: none;
font-size: 24px;
}
}
}
.subTitle { .subTitle {
color: #737a87; color: #737a87;
font-size: 12px; font-size: 12px;

View File

@ -1,11 +1,12 @@
<template> <template>
<div class="group__container" @mouseleave="setNodeProperties"> <div class="group__container" :class="props.model.id" @mouseleave="setNodeProperties">
<NodeTitle <NodeTitle
:icon="loop" :icon="loop"
:nodeId="props.model.id" :nodeId="props.model.id"
:nodeProperties="props.properties" :nodeProperties="props.properties"
:nodeName="props.properties?.name || '循环'" :nodeName="props.properties?.name || '循环'"
nodeDesc="循环执行一系列任务,直至输出所有结果" nodeDesc="循环执行一系列任务,直至输出所有结果"
:showZoom="false"
/> />
<div class="loop__container"> <div class="loop__container">
<div> <div>
@ -24,7 +25,11 @@
:label="index === 0 ? '参数名' : ''" :label="index === 0 ? '参数名' : ''"
:prop="`nodeParams.${index}.name`" :prop="`nodeParams.${index}.name`"
:rules="[ :rules="[
{ required: true, message: '请输入参数名', trigger: 'blur' }, {
required: true,
message: '请输入参数名',
trigger: 'blur',
},
]" ]"
> >
<el-input <el-input
@ -87,7 +92,8 @@
:options="quoteOptions" :options="quoteOptions"
placeholder="请选择" placeholder="请选择"
@visible-change=" @visible-change="
(visible) => visibleChange(visible, index, property.quote) (visible) =>
visibleChange(visible, index, property.quote)
" "
@change="(value) => cascaderChange(value, index)" @change="(value) => cascaderChange(value, index)"
/> />
@ -99,7 +105,11 @@
</div> </div>
</div> </div>
<div>循环体</div> <div>循环体</div>
<div class="child__container" @drop="handleDrop" @dragover="handleDragover"> <div
class="child__container"
@drop="handleDrop"
@dragover="handleDragover"
>
<slot></slot> <slot></slot>
</div> </div>
</div> </div>

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="node__container"> <div class="node__container" :class="props.model.id">
<NodeState :state="nodeOperatingStatus"> <NodeState :state="nodeOperatingStatus">
<template #input> <template #input>
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" /> <JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
@ -25,8 +25,10 @@
:nodeProperties="props.properties" :nodeProperties="props.properties"
:nodeName="props.properties.name" :nodeName="props.properties.name"
:nodeDesc="props.properties.desc" :nodeDesc="props.properties.desc"
:zoom-state="nodeZoom"
@zoom="zoom"
/> />
<div class="input__container"> <div class="input__container" v-show="nodeZoom">
<div class="title"> <div class="title">
<div class="left"> <div class="left">
<div class="tag"></div> <div class="tag"></div>
@ -42,7 +44,7 @@
</el-button> </el-button>
</div> </div>
</div> </div>
<div class="form__container"> <div class="form__container" v-show="nodeZoom">
<el-form <el-form
:inline="true" :inline="true"
:model="formData" :model="formData"
@ -134,8 +136,8 @@
</template> </template>
<script setup> <script setup>
import { ref, reactive, onMounted, onUnmounted } from "vue"; import { ref, reactive, onMounted, onUnmounted, nextTick } from "vue";
import { getInput } from "@/utils/flow"; import { getInput, initNodeZoom } from "@/utils/flow";
import { useFlowStore } from "@/store/modules/flow"; import { useFlowStore } from "@/store/modules/flow";
import { Plus } from "@element-plus/icons-vue"; import { Plus } from "@element-plus/icons-vue";
import NodeTitle from "../../components/NodeTitle.vue"; import NodeTitle from "../../components/NodeTitle.vue";
@ -183,6 +185,7 @@ const setNodeProperties = async () => {
lf.setProperties(props.model.id, { lf.setProperties(props.model.id, {
...properties, ...properties,
...data, ...data,
zoom: nodeZoom.value
}); });
emits("contentChange"); emits("contentChange");
} }
@ -228,6 +231,12 @@ const addFormItem = () => {
emits("contentChange"); emits("contentChange");
}; };
const nodeZoom = ref(props?.properties?.zoom ?? true)
const zoom = (flag) => {
nodeZoom.value = flag
initNodeZoom(props.model.id, nodeZoom.value, '.node__box')
}
watch( watch(
() => props.properties, () => props.properties,
() => { () => {
@ -237,6 +246,9 @@ watch(
if (option) { if (option) {
quoteOptions.value = option; quoteOptions.value = option;
} }
nextTick(() => {
initNodeZoom(props.model.id, props?.properties?.zoom ?? true, '.node__box', true)
})
} }
}, },
{ {

View File

@ -8,36 +8,67 @@
<JsonViewer :value="outputJsonData" copyable boxed sort theme="light" /> <JsonViewer :value="outputJsonData" copyable boxed sort theme="light" />
</template> </template>
</NodeState> </NodeState>
<NodeTitle :icon="switchSvg" :nodeId="props.model.id" :nodeProperties="props.properties" :nodeName="props.properties?.name || '分支'" nodeDesc="连接多个下游分支,根据设定的条件按照顺序查找的方式来匹配运行的分支,如果匹配到某条件则只运行该条件对应的分支,否则继续匹配下一条件直至结束" /> <NodeTitle
:icon="switchSvg"
:nodeId="props.model.id"
:nodeProperties="props.properties"
:nodeName="props.properties?.name || '分支'"
nodeDesc="连接多个下游分支,根据设定的条件按照顺序查找的方式来匹配运行的分支,如果匹配到某条件则只运行该条件对应的分支,否则继续匹配下一条件直至结束"
/>
<div class="condition_title_container"> <div class="condition_title_container">
<div class="left"> <div class="left">
<div class="space"></div> <div class="space"></div>
<div class="title">所有条件</div> <div class="title">所有条件</div>
</div> </div>
<div class="right"> <div class="right">
<el-button :disabled="flowStore.disableForm" @click="addFormItem" class="addFormItem"> <el-button
<el-icon :size="20" ><Plus /></el-icon> :disabled="flowStore.disableForm"
@click="addFormItem"
class="addFormItem"
>
<el-icon :size="20"><Plus /></el-icon>
</el-button> </el-button>
</div> </div>
</div> </div>
<div> <div>
<el-form :inline="true" :model="formData" :rules="rules" ref="dynamicForm" label-position="top" label-width="auto" :disabled="flowStore.disableForm"> <el-form
<div class="condition" v-for="(params, index) in formData.nodeParams" :id="params.id"> :inline="true"
:model="formData"
:rules="rules"
ref="dynamicForm"
label-position="top"
label-width="auto"
:disabled="flowStore.disableForm"
>
<div
class="condition"
v-for="(params, index) in formData.nodeParams"
:id="params.id"
>
<div class="title__container"> <div class="title__container">
<div class="left"> <div class="left">
<div class="space"></div> <div class="space"></div>
<div class="title">{{ index ===0 ? 'If' : 'Else If' }}</div> <div class="title">{{ index === 0 ? "If" : "Else If" }}</div>
</div> </div>
<div class="right"> <div class="right">
<el-button :disabled="flowStore.disableForm" circle size="small" @click="deleteFormItem(params.id, index)"> <el-button
<el-icon :size="16" ><Minus /></el-icon> :disabled="flowStore.disableForm"
circle
size="small"
@click="deleteFormItem(params.id, index)"
>
<el-icon :size="16"><Minus /></el-icon>
</el-button> </el-button>
</div> </div>
</div> </div>
<div class="withOr"> <div class="withOr">
<div class="title">条件</div> <div class="title">条件</div>
<div> <div>
<el-select v-model="params.withOr" placeholder="Select" style="width: 240px"> <el-select
v-model="params.withOr"
placeholder="Select"
style="width: 240px"
>
<el-option key="and" label="AND" value="and" /> <el-option key="and" label="AND" value="and" />
<el-option key="or" label="OR" value="or" /> <el-option key="or" label="OR" value="or" />
</el-select> </el-select>
@ -46,20 +77,61 @@
<div class="form__container"> <div class="form__container">
<el-row v-for="(property, pIndex) in params.list"> <el-row v-for="(property, pIndex) in params.list">
<!-- :prop="`nodeParams.${index}.list.${pIndex}.name`" :rules="[{ required: true, message: '请输入变量名', trigger: 'blur' }]" --> <!-- :prop="`nodeParams.${index}.list.${pIndex}.name`" :rules="[{ required: true, message: '请输入变量名', trigger: 'blur' }]" -->
<el-form-item :label="pIndex === 0 ? '引用变量' : ''" :prop="`nodeParams.${index}.list.${pIndex}.nameType`"> <el-form-item
:label="pIndex === 0 ? '引用变量' : ''"
:prop="`nodeParams.${index}.list.${pIndex}.nameType`"
>
<!-- <el-input v-model="property.name" placeholder="请输入" clearable /> --> <!-- <el-input v-model="property.name" placeholder="请输入" clearable /> -->
<el-select v-model="property.nameType" @change="handleTypeChange(index, pIndex)"> <el-select
v-model="property.nameType"
@change="handleTypeChange(index, pIndex)"
>
<el-option label="引用" value="quote" /> <el-option label="引用" value="quote" />
<el-option label="输入" value="input" /> <el-option label="输入" value="input" />
</el-select> </el-select>
<el-form-item v-if="property.nameType === 'input'" :rules="[{ required: true, message: '请输入参数值', trigger: 'blur' }]" :prop="`nodeParams.${index}.list.${pIndex}.name`"> <el-form-item
<el-input v-model="property.name" placeholder="请输入" clearable /> v-if="property.nameType === 'input'"
:rules="[
{
required: true,
message: '请输入参数值',
trigger: 'blur',
},
]"
:prop="`nodeParams.${index}.list.${pIndex}.name`"
>
<el-input
v-model="property.name"
placeholder="请输入"
clearable
/>
</el-form-item> </el-form-item>
<el-form-item v-if="property.nameType === 'quote'" :rules="[{ required: true, message: '请选择参数值', trigger: 'blur' }]" :prop="`nodeParams.${index}.list.${pIndex}.nameQuote`"> <el-form-item
<el-cascader v-model="property.nameQuote" :options="quoteOptions" placeholder="请选择" @visible-change="visibleChange" /> v-if="property.nameType === 'quote'"
:rules="[
{
required: true,
message: '请选择参数值',
trigger: 'blur',
},
]"
:prop="`nodeParams.${index}.list.${pIndex}.nameQuote`"
>
<el-cascader
v-model="property.nameQuote"
:options="quoteOptions"
placeholder="请选择"
@visible-change="visibleChange"
/>
</el-form-item> </el-form-item>
</el-form-item> </el-form-item>
<el-form-item :label="pIndex === 0 ? '选择条件' : ''" :prop="`nodeParams.${index}.list.${pIndex}.condition`" :rules="[{ required: true, message: '请输入变量名', trigger: 'blur' }]"> <el-form-item
:label="pIndex === 0 ? '选择条件' : ''"
:prop="`nodeParams.${index}.list.${pIndex}.condition`"
:rules="[
{ required: true, message: '请输入变量名', trigger: 'blur' },
]"
>
<el-select v-model="property.condition"> <el-select v-model="property.condition">
<el-option label="等于" value="equal" /> <el-option label="等于" value="equal" />
<el-option label="不等于" value="notEqualTo" /> <el-option label="不等于" value="notEqualTo" />
@ -73,27 +145,71 @@
<el-option label="小于等于" value="lessThanOrEqual" /> <el-option label="小于等于" value="lessThanOrEqual" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item :label="pIndex === 0 ? '比较值' : ''" :prop="`nodeParams.${index}.list.${pIndex}.type`" > <el-form-item
<el-select v-model="property.type" @change="handleTypeChange(index, pIndex)"> :label="pIndex === 0 ? '比较值' : ''"
:prop="`nodeParams.${index}.list.${pIndex}.type`"
>
<el-select
v-model="property.type"
@change="handleTypeChange(index, pIndex)"
>
<el-option label="引用" value="quote" /> <el-option label="引用" value="quote" />
<el-option label="输入" value="input" /> <el-option label="输入" value="input" />
</el-select> </el-select>
<el-form-item v-if="property.type === 'input'" :rules="[{ required: true, message: '请输入参数值', trigger: 'blur' }]" :prop="`nodeParams.${index}.list.${pIndex}.input`"> <el-form-item
<el-input v-model="property.input" placeholder="请输入" clearable /> v-if="property.type === 'input'"
:rules="[
{
required: true,
message: '请输入参数值',
trigger: 'blur',
},
]"
:prop="`nodeParams.${index}.list.${pIndex}.input`"
>
<el-input
v-model="property.input"
placeholder="请输入"
clearable
/>
</el-form-item> </el-form-item>
<el-form-item v-if="property.type === 'quote'" :rules="[{ required: true, message: '请选择参数值', trigger: 'blur' }]" :prop="`nodeParams.${index}.list.${pIndex}.quote`"> <el-form-item
<el-cascader v-model="property.quote" :options="quoteOptions" placeholder="请选择" @visible-change="visibleChange" /> v-if="property.type === 'quote'"
:rules="[
{
required: true,
message: '请选择参数值',
trigger: 'blur',
},
]"
:prop="`nodeParams.${index}.list.${pIndex}.quote`"
>
<el-cascader
v-model="property.quote"
:options="quoteOptions"
placeholder="请选择"
@visible-change="visibleChange"
/>
</el-form-item> </el-form-item>
</el-form-item> </el-form-item>
<el-form-item :label="pIndex === 0 ? '操作' : ''"> <el-form-item :label="pIndex === 0 ? '操作' : ''">
<el-button :disabled="flowStore.disableForm" circle size="small" @click="addListItem(index)"> <el-button
<el-icon :size="12" ><Plus /></el-icon> :disabled="flowStore.disableForm"
circle
size="small"
@click="addListItem(index)"
>
<el-icon :size="12"><Plus /></el-icon>
</el-button> </el-button>
<el-button :disabled="flowStore.disableForm" circle size="small" @click="removeListItem(index, pIndex)"> <el-button
<el-icon :size="12" ><Minus /></el-icon> :disabled="flowStore.disableForm"
circle
size="small"
@click="removeListItem(index, pIndex)"
>
<el-icon :size="12"><Minus /></el-icon>
</el-button> </el-button>
</el-form-item> </el-form-item>
</el-row> </el-row>
</div> </div>
</div> </div>
@ -106,14 +222,14 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import { nextTick, onMounted, onUnmounted, reactive, ref } from 'vue' import { nextTick, onMounted, onUnmounted, reactive, ref } from "vue";
import NodeTitle from '../../components/NodeTitle.vue' import NodeTitle from "../../components/NodeTitle.vue";
import NodeState from "../../components/NodeState.vue"; import NodeState from "../../components/NodeState.vue";
import switchSvg from '../../icon/switch.svg' import switchSvg from "../../icon/switch.svg";
import { v4 as randomUUID } from 'uuid' import { v4 as randomUUID } from "uuid";
import { useFlowStore } from '@/store/modules/flow' import { useFlowStore } from "@/store/modules/flow";
import { Plus, Minus } from '@element-plus/icons-vue' import { Plus, Minus } from "@element-plus/icons-vue";
import { getInput, addNewEdge, removeSwitchEdge } from '@/utils/flow' import { getInput, addNewEdge, removeSwitchEdge } from "@/utils/flow";
import { emitter } from "@/utils/eventBus"; import { emitter } from "@/utils/eventBus";
const props = defineProps({ const props = defineProps({
@ -123,130 +239,137 @@ const props = defineProps({
nowTime: Number, nowTime: Number,
}); });
const emits = defineEmits(['addAnchor', 'removeAnchor', 'bindRef', 'changeAnchor']) const emits = defineEmits([
"addAnchor",
"removeAnchor",
"bindRef",
"changeAnchor",
]);
const switchRef = ref() const switchRef = ref();
const dynamicForm = ref() const dynamicForm = ref();
const flowStore = useFlowStore() const flowStore = useFlowStore();
const formData = reactive({ const formData = reactive({
nodeParams: props.properties?.conditions || [] nodeParams: props.properties?.conditions || [],
}) });
const addFormItem = () => { const addFormItem = () => {
const rect = switchRef.value.getBoundingClientRect(); const rect = switchRef.value.getBoundingClientRect();
addCondition(rect.height - 60) addCondition(rect.height - 60);
} };
const deleteFormItem = (id, index) => { const deleteFormItem = (id, index) => {
if (index === 0) { if (index === 0) {
return return;
} }
formData.nodeParams.splice(index, 1) formData.nodeParams.splice(index, 1);
emits('removeAnchor', id) emits("removeAnchor", id);
removeSwitchEdge(props.model.id, id) removeSwitchEdge(props.model.id, id);
nextTick(() => { nextTick(() => {
changeFormItemHeight() changeFormItemHeight();
}) });
} };
const quoteOptions = ref([]) const quoteOptions = ref([]);
const handleTypeChange = (pIndex, index) => { const handleTypeChange = (pIndex, index) => {
if (formData.nodeParams[pIndex].list[index].type === 'input') { if (formData.nodeParams[pIndex].list[index].type === "input") {
formData.nodeParams[pIndex].list[index].quote = "" formData.nodeParams[pIndex].list[index].quote = "";
} else { } else {
formData.nodeParams[pIndex].list[index].input = "" formData.nodeParams[pIndex].list[index].input = "";
const option = getInput(props.model.id, false) const option = getInput(props.model.id, false);
if (option) { if (option) {
quoteOptions.value = option quoteOptions.value = option;
} }
} }
} };
const addCondition = (height) => { const addCondition = (height) => {
const id = randomUUID().replace(/-/g, '') const id = randomUUID().replace(/-/g, "");
formData.nodeParams.push({ formData.nodeParams.push({
id, id,
withOr: 'and', withOr: "and",
list: [{ list: [
name: '', {
nameQuote: '', name: "",
nameType: 'input', nameQuote: "",
type: 'input', nameType: "input",
input: '', type: "input",
quote: '' input: "",
}] quote: "",
}) },
emits('addAnchor', height, id) ],
});
emits("addAnchor", height, id);
setTimeout(() => { setTimeout(() => {
addNewEdge(props.model.id) addNewEdge(props.model.id);
}, 50) }, 50);
nextTick(() => { nextTick(() => {
changeFormItemHeight() changeFormItemHeight();
}) });
} };
const addListItem = (pIndex, cIndex) => { const addListItem = (pIndex, cIndex) => {
formData.nodeParams[pIndex].list.push({ formData.nodeParams[pIndex].list.push({
name: '', name: "",
nameQuote: '', nameQuote: "",
nameType: 'input', nameType: "input",
type: 'input', type: "input",
input: '', input: "",
quote: '' quote: "",
}) });
nextTick(() => { nextTick(() => {
changeFormItemHeight() changeFormItemHeight();
}) });
} };
const removeListItem = (pIndex, cIndex) => { const removeListItem = (pIndex, cIndex) => {
if (cIndex === 0) { if (cIndex === 0) {
return return;
} }
formData.nodeParams[pIndex].list.splice(cIndex, 1) formData.nodeParams[pIndex].list.splice(cIndex, 1);
nextTick(() => { nextTick(() => {
changeFormItemHeight() changeFormItemHeight();
}) });
} };
const changeFormItemHeight = () => { const changeFormItemHeight = () => {
formData.nodeParams.forEach(item => { formData.nodeParams.forEach((item) => {
const node = document.getElementById(item.id) const node = document.getElementById(item.id);
emits('changeAnchor', node.offsetTop, item.id) emits("changeAnchor", node.offsetTop, item.id);
}); });
const node = document.getElementById("".concat(props.model.id, "_else")) const node = document.getElementById("".concat(props.model.id, "_else"));
emits('changeAnchor', node.offsetTop, "".concat(props.model.id, "_else")) emits("changeAnchor", node.offsetTop, "".concat(props.model.id, "_else"));
} };
const setNodeProperties = async () => { const setNodeProperties = async () => {
try { try {
const valid = await dynamicForm.value.validate() const valid = await dynamicForm.value.validate();
if (valid) { if (valid) {
const data = toRaw(formData) const data = toRaw(formData);
const properties = lf.getProperties(props.model.id) const properties = lf.getProperties(props.model.id);
lf.setProperties(props.model.id, { lf.setProperties(props.model.id, {
...properties, ...properties,
...data ...data,
}) });
emits('contentChange') emits("contentChange");
} }
} catch (error) { } catch (error) {
dynamicForm.value.clearValidate() dynamicForm.value.clearValidate();
} }
} };
const visibleChange = (value) => { const visibleChange = (value) => {
if (value) { if (value) {
const option = getInput(props.model.id) const option = getInput(props.model.id);
if (option) { if (option) {
quoteOptions.value = option quoteOptions.value = option;
} }
} }
} };
const nodeOperatingStatus = ref("NORMAL"); const nodeOperatingStatus = ref("NORMAL");
const inputJsonData = ref({}); const inputJsonData = ref({});
@ -256,22 +379,23 @@ watch(
() => props.properties, () => props.properties,
() => { () => {
if (props.properties.nodeParams && props.properties.nodeParams.length > 0) { if (props.properties.nodeParams && props.properties.nodeParams.length > 0) {
formData.nodeParams = props.properties.nodeParams formData.nodeParams = props.properties.nodeParams;
} else { } else {
if (formData.nodeParams.length === 0) { if (formData.nodeParams.length === 0) {
addCondition(100) addCondition(100);
} }
} }
}, { },
{
immediate: true, immediate: true,
deep: true deep: true,
} }
); );
onMounted(() => { onMounted(() => {
emits('addAnchor', 280, "".concat(props.model.id, "_else")) emits("addAnchor", 280, "".concat(props.model.id, "_else"));
emits('bindRef', dynamicForm.value) emits("bindRef", dynamicForm.value);
emitter.on("changeNodeState", (data) => { emitter.on("changeNodeState", (data) => {
if (data.nodeId === props.model.id) { if (data.nodeId === props.model.id) {
@ -301,15 +425,12 @@ onMounted(() => {
emits("contentChange"); emits("contentChange");
} }
}); });
});
})
onUnmounted(() => { onUnmounted(() => {
emitter.off("changeNodeState"); emitter.off("changeNodeState");
emitter.off("contentChange"); emitter.off("contentChange");
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.node__container { .node__container {
@ -346,8 +467,6 @@ onUnmounted(() => {
} }
} }
.el-button { .el-button {
border: none; border: none;
} }
@ -397,7 +516,6 @@ onUnmounted(() => {
} }
:deep(.form__container) { :deep(.form__container) {
.el-form-item { .el-form-item {
margin-right: 20px; margin-right: 20px;
} }

View File

@ -1,5 +1,6 @@
<template> <template>
<div class="app-container"> <div class="app-container">
<div ref="topContainerRef">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch"> <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
<el-form-item label="菜单名称" prop="menuName"> <el-form-item label="菜单名称" prop="menuName">
<el-input <el-input
@ -46,9 +47,12 @@
</el-col> </el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar> <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row> </el-row>
</div>
<div :style="containerHeight">
<el-table <el-table
v-if="refreshTable" v-if="refreshTable"
height="100%"
v-loading="loading" v-loading="loading"
:data="menuList" :data="menuList"
row-key="menuId" row-key="menuId"
@ -82,6 +86,7 @@
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
</div>
<!-- 添加或修改菜单对话框 --> <!-- 添加或修改菜单对话框 -->
<el-dialog :title="title" v-model="open" width="680px" append-to-body> <el-dialog :title="title" v-model="open" width="680px" append-to-body>
@ -292,6 +297,10 @@
import { addMenu, delMenu, getMenu, listMenu, updateMenu } from "@/api/system/menu"; import { addMenu, delMenu, getMenu, listMenu, updateMenu } from "@/api/system/menu";
import SvgIcon from "@/components/SvgIcon"; import SvgIcon from "@/components/SvgIcon";
import IconSelect from "@/components/IconSelect"; import IconSelect from "@/components/IconSelect";
import { useContainerHeight } from "@/hooks/tableHeight";
const topContainerRef = ref();
const containerHeight = useContainerHeight(topContainerRef);
const { proxy } = getCurrentInstance(); const { proxy } = getCurrentInstance();
const { sys_show_hide, sys_normal_disable } = proxy.useDict("sys_show_hide", "sys_normal_disable"); const { sys_show_hide, sys_normal_disable } = proxy.useDict("sys_show_hide", "sys_normal_disable");

View File

@ -1,5 +1,6 @@
<template> <template>
<div class="app-container"> <div class="app-container">
<div ref="topContainerRef">
<el-form :model="queryParams" ref="queryRef" v-show="showSearch" :inline="true" label-width="68px"> <el-form :model="queryParams" ref="queryRef" v-show="showSearch" :inline="true" label-width="68px">
<el-form-item label="角色名称" prop="roleName"> <el-form-item label="角色名称" prop="roleName">
<el-input <el-input
@ -90,9 +91,11 @@
</el-col> </el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar> <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row> </el-row>
</div>
<div :style="containerHeight">
<!-- 表格数据 --> <!-- 表格数据 -->
<el-table v-loading="loading" :data="roleList" @selection-change="handleSelectionChange"> <el-table height="100%" v-loading="loading" :data="roleList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" /> <el-table-column type="selection" width="55" align="center" />
<el-table-column label="角色编号" prop="roleId" width="120" /> <el-table-column label="角色编号" prop="roleId" width="120" />
<el-table-column label="角色名称" prop="roleName" :show-overflow-tooltip="true" width="150" /> <el-table-column label="角色名称" prop="roleName" :show-overflow-tooltip="true" width="150" />
@ -139,6 +142,8 @@
@pagination="getList" @pagination="getList"
/> />
</div>
<!-- 添加或修改角色配置对话框 --> <!-- 添加或修改角色配置对话框 -->
<el-dialog :title="title" v-model="open" width="500px" append-to-body> <el-dialog :title="title" v-model="open" width="500px" append-to-body>
<el-form ref="roleRef" :model="form" :rules="rules" label-width="100px"> <el-form ref="roleRef" :model="form" :rules="rules" label-width="100px">
@ -244,6 +249,10 @@
<script setup name="Role"> <script setup name="Role">
import { addRole, changeRoleStatus, dataScope, delRole, getRole, listRole, updateRole, deptTreeSelect } from "@/api/system/role"; import { addRole, changeRoleStatus, dataScope, delRole, getRole, listRole, updateRole, deptTreeSelect } from "@/api/system/role";
import { roleMenuTreeselect, treeselect as menuTreeselect } from "@/api/system/menu"; import { roleMenuTreeselect, treeselect as menuTreeselect } from "@/api/system/menu";
import { useContainerHeight } from "@/hooks/tableHeight";
const topContainerRef = ref();
const containerHeight = useContainerHeight(topContainerRef);
const router = useRouter(); const router = useRouter();
const { proxy } = getCurrentInstance(); const { proxy } = getCurrentInstance();

View File

@ -16,6 +16,7 @@
<!--用户数据--> <!--用户数据-->
<pane size="84"> <pane size="84">
<el-col> <el-col>
<div ref="topContainerRef">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="90px"> <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="90px">
<el-form-item label="用户名称啊" prop="userName"> <el-form-item label="用户名称啊" prop="userName">
<el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable style="width: 240px" @keyup.enter="handleQuery" /> <el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable style="width: 240px" @keyup.enter="handleQuery" />
@ -55,8 +56,10 @@
</el-col> </el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList" :columns="columns"></right-toolbar> <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
</el-row> </el-row>
</div>
<el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange"> <div :style="containerHeight">
<el-table height="100%" v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="50" align="center" /> <el-table-column type="selection" width="50" align="center" />
<el-table-column label="用户编号" align="center" key="userId" prop="userId" v-if="columns[0].visible" /> <el-table-column label="用户编号" align="center" key="userId" prop="userId" v-if="columns[0].visible" />
<el-table-column label="用户名称" align="center" key="userName" prop="userName" v-if="columns[1].visible" :show-overflow-tooltip="true" /> <el-table-column label="用户名称" align="center" key="userName" prop="userName" v-if="columns[1].visible" :show-overflow-tooltip="true" />
@ -96,6 +99,7 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" /> <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</div>
</el-col> </el-col>
</pane> </pane>
</splitpanes> </splitpanes>
@ -220,6 +224,11 @@ import { changeUserStatus, listUser, resetUserPwd, delUser, getUser, updateUser,
import { Splitpanes, Pane } from "splitpanes" import { Splitpanes, Pane } from "splitpanes"
import "splitpanes/dist/splitpanes.css" import "splitpanes/dist/splitpanes.css"
import { useContainerHeight } from "@/hooks/tableHeight";
const topContainerRef = ref();
const containerHeight = useContainerHeight(topContainerRef);
const router = useRouter(); const router = useRouter();
const appStore = useAppStore() const appStore = useAppStore()
const { proxy } = getCurrentInstance(); const { proxy } = getCurrentInstance();

View File

@ -48,7 +48,7 @@
<el-table-column prop="value" label="入参值"> <el-table-column prop="value" label="入参值">
<template #default="{ row, column, $index }"> <template #default="{ row, column, $index }">
<el-form-item v-if="row.type !== 'object' && !row.type.includes('array')" label="" :prop="`formData.tableData.${pIndex}.config.${row.propPath}.value`" :rules="row.required ? [{ required: true, message: '请输入', trigger: 'blur' }] : []"> <el-form-item v-if="row.type !== 'object' && !row.type.includes('array')" label="" :prop="`formData.tableData.${pIndex}.config${row.propPath}value`" :rules="row.required ? [{ required: true, message: '请输入', trigger: 'blur' }] : []">
<el-input v-if="row.type === 'string'" v-model="row.value" /> <el-input v-if="row.type === 'string'" v-model="row.value" />
<el-input-number v-if="row.type === 'number'" :controls="false" v-model="row.value" /> <el-input-number v-if="row.type === 'number'" :controls="false" v-model="row.value" />
<el-switch v-if="row.type === 'boolean'" v-model="row.value" /> <el-switch v-if="row.type === 'boolean'" v-model="row.value" />

View File

@ -182,6 +182,7 @@
size="mini" size="mini"
type="text" type="text"
@click="handleSetting(row)" @click="handleSetting(row)"
v-hasPermi="['system:project:add','system:project:edit']"
>配置 >配置
</el-button> </el-button>
<el-button <el-button
@ -189,6 +190,7 @@
type="text" type="text"
v-if="row.status == 1" v-if="row.status == 1"
@click="handleExecute(row)" @click="handleExecute(row)"
v-hasPermi="['system:project:add','system:project:edit']"
>执行 >执行
</el-button> </el-button>
<el-button <el-button
@ -196,6 +198,7 @@
v-if="row.status == 0" v-if="row.status == 0"
link link
type="primary" type="primary"
v-hasPermi="['system:project:add','system:project:edit']"
@click="handleOpenDashboard(row)" @click="handleOpenDashboard(row)"
>可视化 >可视化
</el-button> </el-button>

View File

@ -35,8 +35,8 @@ export default defineConfig(({mode, command}) => {
//杨 http://192.168.0.10:13080 //杨 http://192.168.0.10:13080
//赵 http://10.148.108.58:13080 //赵 http://10.148.108.58:13080
// dev http://10.148.20.34:13080 // dev http://10.148.20.34:13080
// target: command === 'build' ? VITE_API_URL : 'http://10.148.20.34:13080', target: VITE_API_URL,
target: command === 'build' ? VITE_API_URL : 'http://192.168.0.10:13080', // target: command === 'build' ? VITE_API_URL : 'http://192.168.0.10:13080',
changeOrigin: true, changeOrigin: true,
rewrite: (p) => p.replace(/^\/dev-api/, '') rewrite: (p) => p.replace(/^\/dev-api/, '')
} }