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

@ -1,36 +1,80 @@
<template> <template>
<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"
</div> ><EditPen
<div class="input_name_container" v-else> /></el-icon>
<el-input v-model="nodeName" /> </div>
<el-button class="input_name_select" circle size="small" @click="confirmNodeName"> <div class="input_name_container" v-else>
<el-icon><Select /></el-icon> <el-input v-model="nodeName" />
</el-button> <el-button
<el-button class="input_name_closeBold" circle size="small" @click="cancelNodeName"> class="input_name_select"
<el-icon><CloseBold /></el-icon> circle
</el-button> size="small"
</div> @click="confirmNodeName"
</div> >
</div> <el-icon><Select /></el-icon>
<div class="right">
<el-button circle size="small" @click="deleteNode">
<el-icon><Delete /></el-icon>
</el-button> </el-button>
<el-button
class="input_name_closeBold"
circle
size="small"
@click="cancelNodeName"
>
<el-icon><CloseBold /></el-icon>
</el-button>
</div>
</div> </div>
</div> </div>
<div class="subTitle">{{ props.nodeDesc }}</div> <div class="right">
<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-button>
</el-tooltip>
</div>
</div> </div>
<div class="subTitle">{{ props.nodeDesc }}</div>
</div>
</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,74 +152,88 @@ 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 {
.title { .title {
display: flex;
align-items: center;
justify-content: space-between;
.left {
display: flex; display: flex;
align-items: center;
justify-content: space-between;
.left { img {
display: flex; width: 24px;
height: 24px;
img {
width: 24px;
height: 24px;
}
.text__container {
.text__title {
display: flex;
align-items: center;
.text {
font-size: 16px;
font-weight: bold;
margin: 0 12px;
}
.editIcon {
cursor: pointer;
}
}
.input_name_container {
display: flex;
align-items: center;
.el-input {
margin-left: 12px;
}
.input_name_select {
color: #00b42a;
margin-left: 4px;
border: none;
}
.input_name_closeBold {
color: #f53f3f;
margin-left: 4px;
border: none;
}
}
}
} }
.right { .text__container {
cursor: pointer; .text__title {
display: flex;
align-items: center;
.el-button { .text {
border: none; font-size: 16px;
font-weight: bold;
margin: 0 12px;
}
.editIcon {
cursor: pointer;
}
}
.input_name_container {
display: flex;
align-items: center;
.el-input {
margin-left: 12px;
}
.input_name_select {
color: #00b42a;
margin-left: 4px;
border: none;
}
.input_name_closeBold {
color: #f53f3f;
margin-left: 4px;
border: none;
}
} }
} }
} }
.subTitle { .right {
color: #737a87; cursor: pointer;
font-size: 12px;
margin: 8px 0; .el-button {
border: none;
font-size: 24px;
}
} }
} }
.subTitle {
color: #737a87;
font-size: 12px;
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,32 +1,46 @@
<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
<el-image v-if="props.properties.outputType === 'json'"
v-if="props.properties.outputType === 'img'" :value="outputJsonData"
v-for="item in outputJsonData.imageUrl" copyable
style="width: 60px; height: 60px" boxed
:src="item" sort
:preview-src-list="outputJsonData.imageUrl" theme="light"
:preview-teleported="true" />
show-progress <el-image
fit="fill" v-if="props.properties.outputType === 'img'"
v-for="item in outputJsonData.imageUrl"
style="width: 60px; height: 60px"
:src="item"
:preview-src-list="outputJsonData.imageUrl"
:preview-teleported="true"
show-progress
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"
</el-select> :min="0"
<el-input v-else v-model="property.input" placeholder="请输入" clearable /> :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-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,51 +166,53 @@
</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 class="title"> <div v-if="props.properties?.outputParams?.length > 0">
<div class="left"> <div class="title">
<div class="tag"></div> <div class="left">
<div class="text">输出</div> <div class="tag"></div>
<div class="text">输出</div>
</div>
<div class="right">
<el-button
:disabled="flowStore.disableForm"
@click="addOutputFormItem"
class="addFormItem"
>
<el-icon :size="20"><Plus /></el-icon>
</el-button>
</div>
</div> </div>
<div class="right" >
<el-button
:disabled="flowStore.disableForm"
@click="addOutputFormItem"
class="addFormItem"
>
<el-icon :size="20"><Plus /></el-icon>
</el-button>
</div>
</div>
<div class="form__container"> <div class="form__container">
<el-form <el-form
:inline="true" :inline="true"
:model="formData" :model="formData"
label-position="top" label-position="top"
label-width="auto" label-width="auto"
:rules="outputRules" :rules="outputRules"
ref="outputFormRef" ref="outputFormRef"
> >
<FormItemRecursive <FormItemRecursive
formType="output" formType="output"
:current-list="formData.outputParams" :current-list="formData.outputParams"
prop-path="outputParams" prop-path="outputParams"
:depth="0" :depth="0"
:is-first-level="true" :is-first-level="true"
:endDepth="2" :endDepth="2"
:parent-path="[]" :parent-path="[]"
@delete-item="deleteTopLevelItem" @delete-item="deleteTopLevelItem"
/> />
</el-form> </el-form>
</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) {
@ -501,11 +586,11 @@ defineExpose({
align-items: end; align-items: end;
.el-input { .el-input {
--el-input-width: 148px; --el-input-width: 148px;
} }
.el-select { .el-select {
--el-select-width: 148px; --el-select-width: 148px;
} }
.el-cascader { .el-cascader {

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">
<img src="../../icon/start.svg" alt="" /> <div class="left">
<span class="text">Start</span> <img src="../../icon/start.svg" alt="" />
<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,16 +248,26 @@ defineExpose({
.title { .title {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between;
img { .left {
width: 24px; img {
height: 24px; width: 24px;
height: 24px;
}
.text {
font-size: 16px;
font-weight: bold;
margin-left: 12px;
}
} }
.text { .right {
font-size: 16px; .el-button {
font-weight: bold; border: none;
margin-left: 12px; font-size: 24px;
}
} }
} }

View File

@ -1,107 +1,117 @@
<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>
<el-form <el-form
:inline="true" :inline="true"
:model="formData" :model="formData"
:rules="rules" :rules="rules"
ref="dynamicForm" ref="dynamicForm"
label-position="top" label-position="top"
label-width="auto" label-width="auto"
:disabled="flowStore.disableForm" :disabled="flowStore.disableForm"
> >
<div v-for="(property, index) in formData.nodeParams" :key="index"> <div v-for="(property, index) in formData.nodeParams" :key="index">
<el-row> <el-row>
<el-form-item
:label="index === 0 ? '参数名' : ''"
:prop="`nodeParams.${index}.name`"
:rules="[
{ required: true, message: '请输入参数名', trigger: 'blur' },
]"
>
<el-input
:disabled="index === 0"
v-model="property.name"
placeholder="请输入"
clearable
/>
</el-form-item>
<el-form-item
:label="index === 0 ? '参数值' : ''"
:prop="`nodeParams.${index}.type`"
>
<el-select
v-model="property.type"
@change="handleTypeChange(index)"
>
<el-option label="引用" value="quote" />
<el-option label="输入" value="input" />
</el-select>
<el-form-item <el-form-item
v-if="property.type === 'input'" :label="index === 0 ? '参数名' : ''"
:prop="`nodeParams.${index}.name`"
:rules="[ :rules="[
{ {
required: true, required: true,
message: '请输入参数', message: '请输入参数名',
trigger: 'blur', trigger: 'blur',
}, },
]" ]"
:prop="`nodeParams.${index}.input`"
> >
<el-input-number <el-input
v-model="property.input" :disabled="index === 0"
:min="0" v-model="property.name"
:controls="false"
:step-strictly="true"
placeholder="请输入" placeholder="请输入"
clearable clearable
/> />
</el-form-item> </el-form-item>
<el-form-item <el-form-item
v-if="property.type === 'quote'" :label="index === 0 ? '参数值' : ''"
:rules="[ :prop="`nodeParams.${index}.type`"
{
required: true,
message: '请选择参数值',
trigger: 'blur',
},
]"
:prop="`nodeParams.${index}.quote`"
> >
<el-cascader <el-select
:ref=" v-model="property.type"
(el) => { @change="handleTypeChange(index)"
if (el) cascaderRefs[index] = el; >
} <el-option label="引用" value="quote" />
" <el-option label="输入" value="input" />
v-model="property.quote" </el-select>
:checkStrictly="true" <el-form-item
:options="quoteOptions" v-if="property.type === 'input'"
placeholder="请选择" :rules="[
@visible-change=" {
(visible) => visibleChange(visible, index, property.quote) required: true,
" message: '请输入参数值',
@change="(value) => cascaderChange(value, index)" trigger: 'blur',
/> },
]"
:prop="`nodeParams.${index}.input`"
>
<el-input-number
v-model="property.input"
:min="0"
:controls="false"
:step-strictly="true"
placeholder="请输入"
clearable
/>
</el-form-item>
<el-form-item
v-if="property.type === 'quote'"
:rules="[
{
required: true,
message: '请选择参数值',
trigger: 'blur',
},
]"
:prop="`nodeParams.${index}.quote`"
>
<el-cascader
:ref="
(el) => {
if (el) cascaderRefs[index] = el;
}
"
v-model="property.quote"
:checkStrictly="true"
:options="quoteOptions"
placeholder="请选择"
@visible-change="
(visible) =>
visibleChange(visible, index, property.quote)
"
@change="(value) => cascaderChange(value, index)"
/>
</el-form-item>
</el-form-item> </el-form-item>
</el-form-item> </el-row>
</el-row> </div>
</div> </el-form>
</el-form> </div>
</div>
<div>循环体</div>
<div
class="child__container"
@drop="handleDrop"
@dragover="handleDragover"
>
<slot></slot>
</div> </div>
</div>
<div>循环体</div>
<div class="child__container" @drop="handleDrop" @dragover="handleDragover">
<slot></slot>
</div>
</div> </div>
</template> </template>
<script setup> <script setup>

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

@ -1,119 +1,235 @@
<template> <template>
<div class="node__container" ref="switchRef" @mouseleave="setNodeProperties"> <div class="node__container" ref="switchRef" @mouseleave="setNodeProperties">
<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 :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
<div class="condition_title_container"> :icon="switchSvg"
<div class="left"> :nodeId="props.model.id"
<div class="space"></div> :nodeProperties="props.properties"
<div class="title">所有条件</div> :nodeName="props.properties?.name || '分支'"
</div> nodeDesc="连接多个下游分支,根据设定的条件按照顺序查找的方式来匹配运行的分支,如果匹配到某条件则只运行该条件对应的分支,否则继续匹配下一条件直至结束"
<div class="right"> />
<el-button :disabled="flowStore.disableForm" @click="addFormItem" class="addFormItem"> <div class="condition_title_container">
<el-icon :size="20" ><Plus /></el-icon> <div class="left">
</el-button> <div class="space"></div>
</div> <div class="title">所有条件</div>
</div> </div>
<div> <div class="right">
<el-form :inline="true" :model="formData" :rules="rules" ref="dynamicForm" label-position="top" label-width="auto" :disabled="flowStore.disableForm"> <el-button
<div class="condition" v-for="(params, index) in formData.nodeParams" :id="params.id"> :disabled="flowStore.disableForm"
<div class="title__container"> @click="addFormItem"
<div class="left"> class="addFormItem"
<div class="space"></div> >
<div class="title">{{ index ===0 ? 'If' : 'Else If' }}</div> <el-icon :size="20"><Plus /></el-icon>
</div> </el-button>
<div class="right"> </div>
<el-button :disabled="flowStore.disableForm" circle size="small" @click="deleteFormItem(params.id, index)">
<el-icon :size="16" ><Minus /></el-icon>
</el-button>
</div>
</div>
<div class="withOr">
<div class="title">条件</div>
<div>
<el-select v-model="params.withOr" placeholder="Select" style="width: 240px">
<el-option key="and" label="AND" value="and" />
<el-option key="or" label="OR" value="or" />
</el-select>
</div>
</div>
<div class="form__container">
<el-row v-for="(property, pIndex) in params.list">
<!-- :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-input v-model="property.name" placeholder="请输入" clearable /> -->
<el-select v-model="property.nameType" @change="handleTypeChange(index, pIndex)">
<el-option label="引用" value="quote" />
<el-option label="输入" value="input" />
</el-select>
<el-form-item 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 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 :label="pIndex === 0 ? '选择条件' : ''" :prop="`nodeParams.${index}.list.${pIndex}.condition`" :rules="[{ required: true, message: '请输入变量名', trigger: 'blur' }]">
<el-select v-model="property.condition">
<el-option label="等于" value="equal" />
<el-option label="不等于" value="notEqualTo" />
<el-option label="为空" value="null" />
<el-option label="不为空" value="notNull" />
<el-option label="包含" value="include" />
<el-option label="不包含" value="notInclude" />
<el-option label="大于" value="greaterThan" />
<el-option label="大于等于" value="greaterThanOrEqual" />
<el-option label="小于" value="lessThan" />
<el-option label="小于等于" value="lessThanOrEqual" />
</el-select>
</el-form-item>
<el-form-item :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="input" />
</el-select>
<el-form-item 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 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 :label="pIndex === 0 ? '操作' : ''">
<el-button :disabled="flowStore.disableForm" circle size="small" @click="addListItem(index)">
<el-icon :size="12" ><Plus /></el-icon>
</el-button>
<el-button :disabled="flowStore.disableForm" circle size="small" @click="removeListItem(index, pIndex)">
<el-icon :size="12" ><Minus /></el-icon>
</el-button>
</el-form-item>
</el-row>
</div>
</div>
</el-form>
</div>
<div class="else" :id="''.concat(props.model.id, '_else')">
<div class="space"></div>
<div class="title">Else</div>
</div>
</div> </div>
<div>
<el-form
: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="left">
<div class="space"></div>
<div class="title">{{ index === 0 ? "If" : "Else If" }}</div>
</div>
<div class="right">
<el-button
:disabled="flowStore.disableForm"
circle
size="small"
@click="deleteFormItem(params.id, index)"
>
<el-icon :size="16"><Minus /></el-icon>
</el-button>
</div>
</div>
<div class="withOr">
<div class="title">条件</div>
<div>
<el-select
v-model="params.withOr"
placeholder="Select"
style="width: 240px"
>
<el-option key="and" label="AND" value="and" />
<el-option key="or" label="OR" value="or" />
</el-select>
</div>
</div>
<div class="form__container">
<el-row v-for="(property, pIndex) in params.list">
<!-- :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-input v-model="property.name" placeholder="请输入" clearable /> -->
<el-select
v-model="property.nameType"
@change="handleTypeChange(index, pIndex)"
>
<el-option label="引用" value="quote" />
<el-option label="输入" value="input" />
</el-select>
<el-form-item
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
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
:label="pIndex === 0 ? '选择条件' : ''"
:prop="`nodeParams.${index}.list.${pIndex}.condition`"
:rules="[
{ required: true, message: '请输入变量名', trigger: 'blur' },
]"
>
<el-select v-model="property.condition">
<el-option label="等于" value="equal" />
<el-option label="不等于" value="notEqualTo" />
<el-option label="为空" value="null" />
<el-option label="不为空" value="notNull" />
<el-option label="包含" value="include" />
<el-option label="不包含" value="notInclude" />
<el-option label="大于" value="greaterThan" />
<el-option label="大于等于" value="greaterThanOrEqual" />
<el-option label="小于" value="lessThan" />
<el-option label="小于等于" value="lessThanOrEqual" />
</el-select>
</el-form-item>
<el-form-item
: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="input" />
</el-select>
<el-form-item
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
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 :label="pIndex === 0 ? '操作' : ''">
<el-button
:disabled="flowStore.disableForm"
circle
size="small"
@click="addListItem(index)"
>
<el-icon :size="12"><Plus /></el-icon>
</el-button>
<el-button
:disabled="flowStore.disableForm"
circle
size="small"
@click="removeListItem(index, pIndex)"
>
<el-icon :size="12"><Minus /></el-icon>
</el-button>
</el-form-item>
</el-row>
</div>
</div>
</el-form>
</div>
<div class="else" :id="''.concat(props.model.id, '_else')">
<div class="space"></div>
<div class="title">Else</div>
</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;
} }
@ -423,18 +541,18 @@ onUnmounted(() => {
padding: 8px 16px; padding: 8px 16px;
.space { .space {
width: 4px; width: 4px;
height: 14px; height: 14px;
background: rgb(22, 100, 255); background: rgb(22, 100, 255);
border-radius: 0px 4px 4px 0px; border-radius: 0px 4px 4px 0px;
} }
.title { .title {
margin-left: 8px; margin-left: 8px;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
font-weight: bold; font-weight: bold;
} }
} }
} }
</style> </style>

View File

@ -1,87 +1,92 @@
<template> <template>
<div class="app-container"> <div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch"> <div ref="topContainerRef">
<el-form-item label="菜单名称" prop="menuName"> <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
<el-input <el-form-item label="菜单名称" prop="menuName">
v-model="queryParams.menuName" <el-input
placeholder="请输入菜单名称" v-model="queryParams.menuName"
clearable placeholder="请输入菜单名称"
style="width: 200px" clearable
@keyup.enter="handleQuery" style="width: 200px"
/> @keyup.enter="handleQuery"
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="菜单状态" clearable style="width: 200px">
<el-option
v-for="dict in sys_normal_disable"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/> />
</el-select> </el-form-item>
</el-form-item> <el-form-item label="状态" prop="status">
<el-form-item> <el-select v-model="queryParams.status" placeholder="菜单状态" clearable style="width: 200px">
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> <el-option
<el-button icon="Refresh" @click="resetQuery">重置</el-button> v-for="dict in sys_normal_disable"
</el-form-item> :key="dict.value"
</el-form> :label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8"> <el-row :gutter="10" class="mb8">
<el-col :span="1.5"> <el-col :span="1.5">
<el-button <el-button
type="primary" type="primary"
plain plain
icon="Plus" icon="Plus"
@click="handleAdd" @click="handleAdd"
v-hasPermi="['system:menu:add']" v-hasPermi="['system:menu:add']"
>新增</el-button> >新增</el-button>
</el-col> </el-col>
<el-col :span="1.5"> <el-col :span="1.5">
<el-button <el-button
type="info" type="info"
plain plain
icon="Sort" icon="Sort"
@click="toggleExpandAll" @click="toggleExpandAll"
>展开/折叠</el-button> >展开/折叠</el-button>
</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>
<el-table <div :style="containerHeight">
v-if="refreshTable" <el-table
v-loading="loading" v-if="refreshTable"
:data="menuList" height="100%"
row-key="menuId" v-loading="loading"
:default-expand-all="isExpandAll" :data="menuList"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }" row-key="menuId"
> :default-expand-all="isExpandAll"
<el-table-column prop="menuName" label="菜单名称" :show-overflow-tooltip="true" width="160"></el-table-column> :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
<el-table-column prop="icon" label="图标" align="center" width="100"> >
<template #default="scope"> <el-table-column prop="menuName" label="菜单名称" :show-overflow-tooltip="true" width="160"></el-table-column>
<svg-icon :icon-class="scope.row.icon" /> <el-table-column prop="icon" label="图标" align="center" width="100">
</template> <template #default="scope">
</el-table-column> <svg-icon :icon-class="scope.row.icon" />
<el-table-column prop="orderNum" label="排序" width="60"></el-table-column> </template>
<el-table-column prop="perms" label="权限标识" :show-overflow-tooltip="true"></el-table-column> </el-table-column>
<el-table-column prop="component" label="组件路径" :show-overflow-tooltip="true"></el-table-column> <el-table-column prop="orderNum" label="排序" width="60"></el-table-column>
<el-table-column prop="status" label="状态" width="80"> <el-table-column prop="perms" label="权限标识" :show-overflow-tooltip="true"></el-table-column>
<template #default="scope"> <el-table-column prop="component" label="组件路径" :show-overflow-tooltip="true"></el-table-column>
<dict-tag :options="sys_normal_disable" :value="scope.row.status" /> <el-table-column prop="status" label="状态" width="80">
</template> <template #default="scope">
</el-table-column> <dict-tag :options="sys_normal_disable" :value="scope.row.status" />
<el-table-column label="创建时间" align="center" width="160" prop="createTime"> </template>
<template #default="scope"> </el-table-column>
<span>{{ parseTime(scope.row.createTime) }}</span> <el-table-column label="创建时间" align="center" width="160" prop="createTime">
</template> <template #default="scope">
</el-table-column> <span>{{ parseTime(scope.row.createTime) }}</span>
<el-table-column label="操作" align="center" width="210" class-name="small-padding fixed-width"> </template>
<template #default="scope"> </el-table-column>
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:menu:edit']">修改</el-button> <el-table-column label="操作" align="center" width="210" class-name="small-padding fixed-width">
<el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['system:menu:add']">新增</el-button> <template #default="scope">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:menu:remove']">删除</el-button> <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:menu:edit']">修改</el-button>
</template> <el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['system:menu:add']">新增</el-button>
</el-table-column> <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:menu:remove']">删除</el-button>
</el-table> </template>
</el-table-column>
</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,98 +1,101 @@
<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
v-model="queryParams.roleName" v-model="queryParams.roleName"
placeholder="请输入角色名称" placeholder="请输入角色名称"
clearable clearable
style="width: 240px" style="width: 240px"
@keyup.enter="handleQuery" @keyup.enter="handleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item label="权限字符" prop="roleKey"> <el-form-item label="权限字符" prop="roleKey">
<el-input <el-input
v-model="queryParams.roleKey" v-model="queryParams.roleKey"
placeholder="请输入权限字符" placeholder="请输入权限字符"
clearable clearable
style="width: 240px" style="width: 240px"
@keyup.enter="handleQuery" @keyup.enter="handleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item label="状态" prop="status"> <el-form-item label="状态" prop="status">
<el-select <el-select
v-model="queryParams.status" v-model="queryParams.status"
placeholder="角色状态" placeholder="角色状态"
clearable clearable
style="width: 240px" style="width: 240px"
> >
<el-option <el-option
v-for="dict in sys_normal_disable" v-for="dict in sys_normal_disable"
:key="dict.value" :key="dict.value"
:label="dict.label" :label="dict.label"
:value="dict.value" :value="dict.value"
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="创建时间" style="width: 308px"> <el-form-item label="创建时间" style="width: 308px">
<el-date-picker <el-date-picker
v-model="dateRange" v-model="dateRange"
value-format="YYYY-MM-DD" value-format="YYYY-MM-DD"
type="daterange" type="daterange"
range-separator="-" range-separator="-"
start-placeholder="开始日期" start-placeholder="开始日期"
end-placeholder="结束日期" end-placeholder="结束日期"
></el-date-picker> ></el-date-picker>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button> <el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
<el-row :gutter="10" class="mb8"> <el-row :gutter="10" class="mb8">
<el-col :span="1.5"> <el-col :span="1.5">
<el-button <el-button
type="primary" type="primary"
plain plain
icon="Plus" icon="Plus"
@click="handleAdd" @click="handleAdd"
v-hasPermi="['system:role:add']" v-hasPermi="['system:role:add']"
>新增</el-button> >新增</el-button>
</el-col> </el-col>
<el-col :span="1.5"> <el-col :span="1.5">
<el-button <el-button
type="success" type="success"
plain plain
icon="Edit" icon="Edit"
:disabled="single" :disabled="single"
@click="handleUpdate" @click="handleUpdate"
v-hasPermi="['system:role:edit']" v-hasPermi="['system:role:edit']"
>修改</el-button> >修改</el-button>
</el-col> </el-col>
<el-col :span="1.5"> <el-col :span="1.5">
<el-button <el-button
type="danger" type="danger"
plain plain
icon="Delete" icon="Delete"
:disabled="multiple" :disabled="multiple"
@click="handleDelete" @click="handleDelete"
v-hasPermi="['system:role:remove']" v-hasPermi="['system:role:remove']"
>删除</el-button> >删除</el-button>
</el-col> </el-col>
<el-col :span="1.5"> <el-col :span="1.5">
<el-button <el-button
type="warning" type="warning"
plain plain
icon="Download" icon="Download"
@click="handleExport" @click="handleExport"
v-hasPermi="['system:role:export']" v-hasPermi="['system:role:export']"
>导出</el-button> >导出</el-button>
</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,86 +16,90 @@
<!--用户数据--> <!--用户数据-->
<pane size="84"> <pane size="84">
<el-col> <el-col>
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="90px"> <div ref="topContainerRef">
<el-form-item label="用户名称啊" prop="userName"> <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="90px">
<el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable style="width: 240px" @keyup.enter="handleQuery" /> <el-form-item label="用户名称啊" prop="userName">
</el-form-item> <el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable style="width: 240px" @keyup.enter="handleQuery" />
<el-form-item label="手机号码" prop="phonenumber"> </el-form-item>
<el-input v-model="queryParams.phonenumber" placeholder="请输入手机号码" clearable style="width: 240px" @keyup.enter="handleQuery" /> <el-form-item label="手机号码" prop="phonenumber">
</el-form-item> <el-input v-model="queryParams.phonenumber" placeholder="请输入手机号码" clearable style="width: 240px" @keyup.enter="handleQuery" />
<el-form-item label="状态" prop="status"> </el-form-item>
<el-select v-model="queryParams.status" placeholder="用户状态" clearable style="width: 240px"> <el-form-item label="状态" prop="status">
<el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" /> <el-select v-model="queryParams.status" placeholder="用户状态" clearable style="width: 240px">
</el-select> <el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-form-item> </el-select>
<el-form-item label="创建时间" style="width: 308px"> </el-form-item>
<el-date-picker v-model="dateRange" value-format="YYYY-MM-DD" type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"></el-date-picker> <el-form-item label="创建时间" style="width: 308px">
</el-form-item> <el-date-picker v-model="dateRange" value-format="YYYY-MM-DD" type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"></el-date-picker>
<el-form-item> </el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> <el-form-item>
<el-button icon="Refresh" @click="resetQuery">重置</el-button> <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
</el-form-item> <el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form> </el-form-item>
</el-form>
<el-row :gutter="10" class="mb8"> <el-row :gutter="10" class="mb8">
<el-col :span="1.5"> <el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:user:add']">新增</el-button> <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:user:add']">新增</el-button>
</el-col> </el-col>
<el-col :span="1.5"> <el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate" v-hasPermi="['system:user:edit']">修改</el-button> <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate" v-hasPermi="['system:user:edit']">修改</el-button>
</el-col> </el-col>
<el-col :span="1.5"> <el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete" v-hasPermi="['system:user:remove']">删除</el-button> <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete" v-hasPermi="['system:user:remove']">删除</el-button>
</el-col> </el-col>
<el-col :span="1.5"> <el-col :span="1.5">
<el-button type="info" plain icon="Upload" @click="handleImport" v-hasPermi="['system:user:import']">导入</el-button> <el-button type="info" plain icon="Upload" @click="handleImport" v-hasPermi="['system:user:import']">导入</el-button>
</el-col> </el-col>
<el-col :span="1.5"> <el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:user:export']">导出</el-button> <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:user:export']">导出</el-button>
</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-column type="selection" width="50" align="center" /> <el-table height="100%" v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
<el-table-column label="用户编号" align="center" key="userId" prop="userId" v-if="columns[0].visible" /> <el-table-column type="selection" width="50" align="center" />
<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="userId" prop="userId" v-if="columns[0].visible" />
<el-table-column label="用户昵称" align="center" key="nickName" prop="nickName" v-if="columns[2].visible" :show-overflow-tooltip="true" /> <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="deptName" prop="dept.deptName" v-if="columns[3].visible" :show-overflow-tooltip="true" /> <el-table-column label="用户昵称" align="center" key="nickName" prop="nickName" v-if="columns[2].visible" :show-overflow-tooltip="true" />
<el-table-column label="手机号码" align="center" key="phonenumber" prop="phonenumber" v-if="columns[4].visible" width="120" /> <el-table-column label="部门" align="center" key="deptName" prop="dept.deptName" v-if="columns[3].visible" :show-overflow-tooltip="true" />
<el-table-column label="状态" align="center" key="status" v-if="columns[5].visible"> <el-table-column label="手机号码" align="center" key="phonenumber" prop="phonenumber" v-if="columns[4].visible" width="120" />
<template #default="scope"> <el-table-column label="状态" align="center" key="status" v-if="columns[5].visible">
<el-switch <template #default="scope">
v-model="scope.row.status" <el-switch
active-value="0" v-model="scope.row.status"
inactive-value="0" active-value="0"
@change="handleStatusChange(scope.row)" inactive-value="0"
></el-switch> @change="handleStatusChange(scope.row)"
</template> ></el-switch>
</el-table-column> </template>
<el-table-column label="创建时间" align="center" prop="createTime" v-if="columns[6].visible" width="160"> </el-table-column>
<template #default="scope"> <el-table-column label="创建时间" align="center" prop="createTime" v-if="columns[6].visible" width="160">
<span>{{ parseTime(scope.row.createTime) }}</span> <template #default="scope">
</template> <span>{{ parseTime(scope.row.createTime) }}</span>
</el-table-column> </template>
<el-table-column label="操作" align="center" width="150" class-name="small-padding fixed-width"> </el-table-column>
<template #default="scope"> <el-table-column label="操作" align="center" width="150" class-name="small-padding fixed-width">
<el-tooltip content="修改" placement="top" v-if="scope.row.userId !== 1"> <template #default="scope">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:user:edit']"></el-button> <el-tooltip content="修改" placement="top" v-if="scope.row.userId !== 1">
</el-tooltip> <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:user:edit']"></el-button>
<el-tooltip content="删除" placement="top" v-if="scope.row.userId !== 1"> </el-tooltip>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:user:remove']"></el-button> <el-tooltip content="删除" placement="top" v-if="scope.row.userId !== 1">
</el-tooltip> <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:user:remove']"></el-button>
<el-tooltip content="重置密码" placement="top" v-if="scope.row.userId !== 1"> </el-tooltip>
<el-button link type="primary" icon="Key" @click="handleResetPwd(scope.row)" v-hasPermi="['system:user:resetPwd']"></el-button> <el-tooltip content="重置密码" placement="top" v-if="scope.row.userId !== 1">
</el-tooltip> <el-button link type="primary" icon="Key" @click="handleResetPwd(scope.row)" v-hasPermi="['system:user:resetPwd']"></el-button>
<el-tooltip content="分配角色" placement="top" v-if="scope.row.userId !== 1"> </el-tooltip>
<el-button link type="primary" icon="CircleCheck" @click="handleAuthRole(scope.row)" v-hasPermi="['system:user:edit']"></el-button> <el-tooltip content="分配角色" placement="top" v-if="scope.row.userId !== 1">
</el-tooltip> <el-button link type="primary" icon="CircleCheck" @click="handleAuthRole(scope.row)" v-hasPermi="['system:user:edit']"></el-button>
</template> </el-tooltip>
</el-table-column> </template>
</el-table> </el-table-column>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" /> </el-table>
<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

@ -142,88 +142,91 @@
<div :style="containerHeight"> <div :style="containerHeight">
<el-table <el-table
v-loading="loading" v-loading="loading"
height="100%" height="100%"
:data="projectList" :data="projectList"
@selection-change="handleSelectionChange" @selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="项目ID" align="center" prop="projectId" width="150" show-overflow-tooltip />
<el-table-column label="项目名称" align="center" prop="projectName" width="150" show-overflow-tooltip />
<el-table-column label="测试人员" align="center" prop="tester" />
<el-table-column
label="计划测试开始时间"
align="center"
prop="planStart"
width="180"
> >
<template #default="{ row }"> <el-table-column type="selection" width="55" align="center" />
<span>{{ parseTime(row.planStart) }}</span> <el-table-column label="项目ID" align="center" prop="projectId" width="150" show-overflow-tooltip />
</template> <el-table-column label="项目名称" align="center" prop="projectName" width="150" show-overflow-tooltip />
</el-table-column> <el-table-column label="测试人员" align="center" prop="tester" />
<el-table-column label="计划测试结束时间" align="center" prop="planEnd" width="180"> <el-table-column
<template #default="{ row }"> label="计划测试开始时间"
<span>{{ parseTime(row.planEnd) }}</span> align="center"
</template> prop="planStart"
</el-table-column> width="180"
<el-table-column label="项目描述" width="200" show-overflow-tooltip align="center" prop="description" /> >
<el-table-column label="样品名称" align="center" prop="sampleName" width="150" show-overflow-tooltip /> <template #default="{ row }">
<el-table-column label="商标" align="center" prop="trademark" width="150" show-overflow-tooltip /> <span>{{ parseTime(row.planStart) }}</span>
<el-table-column label="型号规格" align="center" prop="modelSpec" width="150" show-overflow-tooltip /> </template>
<el-table-column label="数量" align="center" prop="quantity" /> </el-table-column>
<el-table-column label="项目状态" align="center" prop="status"> <el-table-column label="计划测试结束时间" align="center" prop="planEnd" width="180">
<template #default="{ row }"> <template #default="{ row }">
{{ row.status == 0 ? '已执行': '未执行' }} <span>{{ parseTime(row.planEnd) }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" align="center" width="300px" class-name="small-padding fixed-width" fixed="right"> <el-table-column label="项目描述" width="200" show-overflow-tooltip align="center" prop="description" />
<template #default="{ row }"> <el-table-column label="样品名称" align="center" prop="sampleName" width="150" show-overflow-tooltip />
<el-button <el-table-column label="商标" align="center" prop="trademark" width="150" show-overflow-tooltip />
size="mini" <el-table-column label="型号规格" align="center" prop="modelSpec" width="150" show-overflow-tooltip />
type="text" <el-table-column label="数量" align="center" prop="quantity" />
@click="handleSetting(row)" <el-table-column label="项目状态" align="center" prop="status">
>配置 <template #default="{ row }">
</el-button> {{ row.status == 0 ? '已执行': '未执行' }}
<el-button </template>
size="mini" </el-table-column>
type="text" <el-table-column label="操作" align="center" width="300px" class-name="small-padding fixed-width" fixed="right">
v-if="row.status == 1" <template #default="{ row }">
@click="handleExecute(row)" <el-button
>执行 size="mini"
</el-button> type="text"
<el-button @click="handleSetting(row)"
icon="DataLine" v-hasPermi="['system:project:add','system:project:edit']"
v-if="row.status == 0" >配置
link </el-button>
type="primary" <el-button
@click="handleOpenDashboard(row)" size="mini"
>可视化 type="text"
</el-button> v-if="row.status == 1"
<el-button @click="handleExecute(row)"
size="mini" v-hasPermi="['system:project:add','system:project:edit']"
type="text" >执行
@click="handleUpdate(row)" </el-button>
v-hasPermi="['system:project:edit']" <el-button
>修改</el-button icon="DataLine"
> v-if="row.status == 0"
<el-button link
size="mini" type="primary"
type="text" v-hasPermi="['system:project:add','system:project:edit']"
@click="handleDelete(row)" @click="handleOpenDashboard(row)"
v-hasPermi="['system:project:remove']" >可视化
>删除</el-button </el-button>
> <el-button
</template> size="mini"
</el-table-column> type="text"
</el-table> @click="handleUpdate(row)"
v-hasPermi="['system:project:edit']"
>修改</el-button
>
<el-button
size="mini"
type="text"
@click="handleDelete(row)"
v-hasPermi="['system:project:remove']"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<pagination <pagination
v-show="total > 0" v-show="total > 0"
:total="total" :total="total"
:page.sync="queryParams.pageNum" :page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize" :limit.sync="queryParams.pageSize"
@pagination="getList" @pagination="getList"
/> />
</div> </div>
<!-- 添加或修改语料库对话框 --> <!-- 添加或修改语料库对话框 -->

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/, '')
} }