修改组态化

master
夜笙歌 2 months ago
parent 0f4bfcc4a1
commit 8ac8b45fd5

@ -20,6 +20,9 @@
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "2.3.1", "@element-plus/icons-vue": "2.3.1",
"@highlightjs/vue-plugin": "2.1.0", "@highlightjs/vue-plugin": "2.1.0",
"@vue-flow/background": "^1.3.2",
"@vue-flow/core": "^1.43.1",
"@vue-flow/node-resizer": "^1.4.0",
"@vueup/vue-quill": "1.2.0", "@vueup/vue-quill": "1.2.0",
"@vueuse/core": "10.9.0", "@vueuse/core": "10.9.0",
"animate.css": "4.1.1", "animate.css": "4.1.1",
@ -31,7 +34,7 @@
"diagram-js": "12.3.0", "diagram-js": "12.3.0",
"didi": "9.0.2", "didi": "9.0.2",
"echarts": "5.5.0", "echarts": "5.5.0",
"element-plus": "2.7.8", "element-plus": "2.9.0",
"file-saver": "2.0.5", "file-saver": "2.0.5",
"fuse.js": "7.0.0", "fuse.js": "7.0.0",
"highlight.js": "11.9.0", "highlight.js": "11.9.0",

@ -3,6 +3,9 @@ import { createApp, provide } from 'vue';
import 'virtual:uno.css'; import 'virtual:uno.css';
import '@/assets/styles/index.scss'; import '@/assets/styles/index.scss';
import 'element-plus/theme-chalk/dark/css-vars.css'; import 'element-plus/theme-chalk/dark/css-vars.css';
import '@vue-flow/core/dist/style.css';
import '@vue-flow/core/dist/theme-default.css';
import '@vue-flow/node-resizer/dist/style.css';
// App、router、store // App、router、store
import App from './App.vue'; import App from './App.vue';

@ -10,7 +10,7 @@ import useSettingsStore from '@/store/modules/settings';
import usePermissionStore from '@/store/modules/permission'; import usePermissionStore from '@/store/modules/permission';
NProgress.configure({ showSpinner: false }); NProgress.configure({ showSpinner: false });
const whiteList = ['/board1','/board2','/login', '/register', '/social-callback']; const whiteList = ['/boardGenerate', '/boardView', '/boardConstruction', '/board1', '/board2', '/login', '/register', '/social-callback'];
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from, next) => {
NProgress.start(); NProgress.start();
@ -41,7 +41,14 @@ router.beforeEach(async (to, from, next) => {
} }
}); });
// @ts-ignore // @ts-ignore
next({ path: to.path, replace: true, params: to.params, query: to.query, hash: to.hash, name: to.name as string }); // hack方法 确保addRoutes已完成 next({
path: to.path,
replace: true,
params: to.params,
query: to.query,
hash: to.hash,
name: to.name as string
}); // hack方法 确保addRoutes已完成
} }
} else { } else {
next(); next();

@ -26,6 +26,21 @@ import Layout from '@/layout/index.vue';
// 公共路由 // 公共路由
export const constantRoutes: RouteRecordRaw[] = [ export const constantRoutes: RouteRecordRaw[] = [
{
path: '/boardGenerate',
hidden: true,
component: () => import('@/views/boardGenerate/index.vue')
},
{
path: '/boardView',
hidden: true,
component: () => import('@/views/boardGenerate/view.vue')
},
{
path: '/boardConstruction',
hidden: true,
component: () => import('@/views/boardGenerate/construction.vue')
},
{ {
path: '/board1', path: '/board1',
hidden: true, hidden: true,

File diff suppressed because one or more lines are too long

@ -0,0 +1,661 @@
<template>
<div class="leftPanel">
<el-tabs v-model="leftPanelState" class="demo-tabs" type="border-card">
<el-tab-pane label="图表组件" name="1">
<el-card class="moduleCard" shadow="never" :draggable="true"
@dragstart="onDragStart($event, 'line')" :style="{display:'inline-block',margin:'0 4px 4px 0'}"
:body-style="{padding:'4px 0'}">
<template #header>
<StarFilled />
</template>
<div class="moduleText">折线</div>
</el-card>
<el-card class="moduleCard" shadow="never" :draggable="true"
@dragstart="onDragStart($event, 'multiLines')" :style="{display:'inline-block',margin:'0 4px 4px 0'}"
:body-style="{padding:'4px 0'}">
<template #header>
<StarFilled />
</template>
<div class="moduleText">多折线</div>
</el-card>
<el-card class="moduleCard" shadow="never" :draggable="true"
@dragstart="onDragStart($event, 'curve')" :style="{display:'inline-block',margin:'0 4px 4px 0'}"
:body-style="{padding:'4px 0'}">
<template #header>
<StarFilled />
</template>
<div class="moduleText">曲线</div>
</el-card>
<el-card class="moduleCard" shadow="never" :draggable="true"
@dragstart="onDragStart($event, 'multiCurves')" :style="{display:'inline-block',margin:'0 4px 4px 0'}"
:body-style="{padding:'4px 0'}">
<template #header>
<StarFilled />
</template>
<div class="moduleText">多曲线
</div>
</el-card>
<el-card class="moduleCard" shadow="never" :draggable="true"
@dragstart="onDragStart($event, 'bar')" :style="{display:'inline-block',margin:'0 4px 4px 0'}"
:body-style="{padding:'4px 0'}">
<template #header>
<StarFilled />
</template>
<div class="moduleText">柱状图</div>
</el-card>
<el-card class="moduleCard" shadow="never" :draggable="true"
@dragstart="onDragStart($event, 'multiBars')" :style="{display:'inline-block',margin:'0 4px 4px 0'}"
:body-style="{padding:'4px 0'}">
<template #header>
<StarFilled />
</template>
<div class="moduleText">多柱状图
</div>
</el-card>
<el-card class="moduleCard" shadow="never" :draggable="true"
@dragstart="onDragStart($event, 'pie')" :style="{display:'inline-block',margin:'0 4px 4px 0'}"
:body-style="{padding:'4px 0'}">
<template #header>
<StarFilled />
</template>
<div class="moduleText">饼图</div>
</el-card>
<template v-for="i in customBoard">
<el-card class="moduleCard" shadow="never" :draggable="true"
@dragstart="onDragStart($event, 'customBoard',i)"
:style="{display:'inline-block',margin:'0 4px 4px 0'}"
:body-style="{padding:'4px 0'}">
<template #header>
<StarFilled />
</template>
<div class="moduleText">{{ i.name }}</div>
</el-card>
</template>
</el-tab-pane>
<el-tab-pane label="数据源" name="2">
<el-card class="moduleCard" shadow="never" :draggable="true"
@dragstart="onDragStart($event, 'map')" :style="{display:'inline-block',margin:'0 4px 4px 0'}"
:body-style="{padding:'4px 0'}">
<template #header>
<StarFilled />
</template>
<div class="moduleText">映射</div>
</el-card>
<el-card class="moduleCard" shadow="never" :draggable="true"
@dragstart="onDragStart($event, 'data')" :style="{display:'inline-block',margin:'0 4px 4px 0'}"
:body-style="{padding:'4px 0'}">
<template #header>
<StarFilled />
</template>
<div class="moduleText">设备数据</div>
</el-card>
<template v-for="i in customData">
<el-card class="moduleCard" shadow="never" :draggable="true"
@dragstart="onDragStart($event, 'customData',i)"
:style="{display:'inline-block',margin:'0 4px 4px 0'}"
:body-style="{padding:'4px 0'}">
<template #header>
<StarFilled />
</template>
<div class="moduleText">{{ i.name }}</div>
</el-card>
</template>
</el-tab-pane>
<el-tab-pane label="表单组件" name="3">
<el-card class="moduleCard" shadow="never" :draggable="true"
@dragstart="onDragStart($event, 'text')" :style="{display:'inline-block',margin:'0 4px 4px 0'}"
:body-style="{padding:'4px 0'}">
<template #header>
<StarFilled />
</template>
<div class="moduleText">文字</div>
</el-card>
<el-card class="moduleCard" shadow="never" :draggable="true"
@dragstart="onDragStart($event, 'inputNode')" :style="{display:'inline-block',margin:'0 4px 4px 0'}"
:body-style="{padding:'4px 0'}">
<template #header>
<StarFilled />
</template>
<div class="moduleText">输入框</div>
</el-card>
<el-card class="moduleCard" shadow="never" :draggable="true"
@dragstart="onDragStart($event, 'time')" :style="{display:'inline-block',margin:'0 4px 4px 0'}"
:body-style="{padding:'4px 0'}">
<template #header>
<StarFilled />
</template>
<div class="moduleText">时间</div>
</el-card>
<el-card class="moduleCard" shadow="never" :draggable="true"
@dragstart="onDragStart($event, 'img')" :style="{display:'inline-block',margin:'0 4px 4px 0'}"
:body-style="{padding:'4px 0'}">
<template #header>
<StarFilled />
</template>
<div class="moduleText">图片</div>
</el-card>
</el-tab-pane>
</el-tabs>
</div>
<div class="content" @drop="onDrop">
<div class="pageSetting">
<span class="pageTitle">{{ pageTitle }}</span>
<div class="btns">
<el-button type="primary" text :icon="Setting" @click="pageSetting"></el-button>
<el-button type="primary" text :icon="Check" @click="save"></el-button>
<el-button type="primary" text :icon="Close" @click="clear"></el-button>
</div>
</div>
<div class="flowArea">
<VueFlow :min-zoom="0.01" ref="flowRef" v-model:nodes="nodes" v-model:edges="edges" fit-view-on-init
default-marker-color="#409EFF"
@connect="logEvent('connect', $event)"
@node-click="logEvent('click', $event)"
@pane-click="logEvent('paneClick', $event)"
@node-drag-start="logEvent('nodeDrag', $event)"
@node-contextMenu="logEvent('contextmenu', $event)"
@dragover="onDragOver"
>
<Background :size="1" :gap="20" pattern-color="#BDBDBD" style="background-color: #000" />
<template #node-area="areaNodeProps">
<AreaNode v-bind="areaNodeProps" :pageData="pageData"></AreaNode>
</template>
<template #node-customBoard="customBoardNodeProps">
<CustomBoardNode :inputData=getInputData(customBoardNodeProps.id) v-bind="customBoardNodeProps"
@resize="resize"></CustomBoardNode>
</template>
<template #node-customData="customDataNodeProps">
<CustomDataNode :inputData=getInputData(customDataNodeProps.id) v-bind="customDataNodeProps"
@resize="resize"></CustomDataNode>
</template>
<template #node-line="lineNodeProps">
<LineNode :inputData=getInputData(lineNodeProps.id) v-bind="lineNodeProps"
@resize="resize"></LineNode>
</template>
<template #node-multiLines="multiLinesNodeProps">
<MultiLinesNode :inputData=getInputData(multiLinesNodeProps.id) v-bind="multiLinesNodeProps"
@resize="resize"></MultiLinesNode>
</template>
<template #node-curve="curveNodeProps">
<CurveNode :inputData=getInputData(curveNodeProps.id) v-bind="curveNodeProps"
@resize="resize"></CurveNode>
</template>
<template #node-multiCurves="multiCurvesNodeProps">
<MultiCurvesNode :inputData=getInputData(multiCurvesNodeProps.id) v-bind="multiCurvesNodeProps"
@resize="resize"></MultiCurvesNode>
</template>
<template #node-bar="barNodeProps">
<BarNode :inputData=getInputData(barNodeProps.id) v-bind="barNodeProps"
@resize="resize"></BarNode>
</template>
<template #node-multiBars="multiBarsNodeProps">
<MultiBarsNode :inputData=getInputData(multiBarsNodeProps.id) v-bind="multiBarsNodeProps"
@resize="resize"></MultiBarsNode>
</template>
<template #node-pie="pieNodeProps">
<PieNode :inputData=getInputData(pieNodeProps.id) v-bind="pieNodeProps"
@resize="resize"></PieNode>
</template>
<template #node-data="dataNodeProps">
<DataNode :inputData=getInputData(dataNodeProps.id) v-bind="dataNodeProps"
@resize="resize"></DataNode>
</template>
<template #node-text="textNodeProps">
<TextNode :inputData=getInputData(textNodeProps.id) v-bind="textNodeProps"
@resize="resize"></TextNode>
</template>
<template #node-img="imgNodeProps">
<ImgNode :inputData=getInputData(imgNodeProps.id) v-bind="imgNodeProps"
@resize="resize"></ImgNode>
</template>
<template #node-inputNode="inputNodeProps">
<InputNode :inputData=getInputData(inputNodeProps.id) v-bind="inputNodeProps"
@resize="resize"></InputNode>
</template>
<template #node-time="timeNodeProps">
<TimeNode :inputData=getInputData(timeNodeProps.id) v-bind="timeNodeProps"
@resize="resize"></TimeNode>
</template>
<template #node-map="mapNodeProps">
<MapNode :inputData=getInputData(mapNodeProps.id) v-bind="mapNodeProps"
@resize="resize"></MapNode>
</template>
</VueFlow>
</div>
</div>
<div class="rightPanel">
<el-form :model="nodeAttrForm" label-width="auto" style="max-width: 600px" v-if="nodeAttrForm">
<el-form-item label="默认内容" v-if="Object.keys(nodeAttrForm).includes('defaultInput')">
<el-input v-model="nodeAttrForm.defaultInput" style="width: 100%" />
</el-form-item>
<el-form-item label="默认日期" v-if="Object.keys(nodeAttrForm).includes('defaultTime')">
<el-date-picker
v-model="nodeAttrForm.defaultTime"
type="datetimerange"
range-separator="到"
start-placeholder="开始时间"
end-placeholder="结束时间"
style="width: 100%;height: 100%"
/>
</el-form-item>
<el-form-item label="日期格式" v-if="Object.keys(nodeAttrForm).includes('format')">
<el-input v-model="nodeAttrForm.format" style="width: 100%" />
</el-form-item>
<el-form-item label="输出字段名" v-if="Object.keys(nodeAttrForm).includes('field')">
<el-input v-model="nodeAttrForm.field" />
</el-form-item>
<el-form-item label="开始时间字段名" v-if="Object.keys(nodeAttrForm).includes('startTimeId')">
<el-input v-model="nodeAttrForm.startTimeId" />
</el-form-item>
<el-form-item label="结束时间字段名" v-if="Object.keys(nodeAttrForm).includes('endTimeId')">
<el-input v-model="nodeAttrForm.endTimeId" />
</el-form-item>
<el-form-item label="数据映射" v-if="Object.keys(nodeAttrForm).includes('dataMap')">
<el-table :data="nodeAttrForm.dataMap" style="width: 100%">
<el-table-column label="源字段" min-width="120">
<template #default="scope">
<el-input v-model="scope.row.source" style="width: 100%" />
</template>
</el-table-column>
<el-table-column label="映射字段" min-width="120">
<template #default="scope">
<el-input v-model="scope.row.target" style="width: 100%" />
</template>
</el-table-column>
<el-table-column label="操作" min-width="120">
<template #default="scope">
<el-button link type="primary" size="small" @click="nodeAttrForm.dataMap.splice(scope.$index, 1)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<el-button style="width: 100%" @click="nodeAttrForm.dataMap.push({})">
添加映射
</el-button>
</el-form-item>
<el-form-item label="内容" v-if="Object.keys(nodeAttrForm).includes('text')">
<el-input v-model="nodeAttrForm.text" style="width: 100%" />
</el-form-item>
<el-form-item label="对齐方式" v-if="Object.keys(nodeAttrForm).includes('align')">
<el-select
v-model="nodeAttrForm.align"
placeholder="Select"
style="width: 100%"
>
<el-option
label="左对齐"
value="left"
/>
<el-option
label="居中对齐"
value="center"
/>
<el-option
label="右对齐"
value="right"
/>
</el-select>
</el-form-item>
<el-form-item label="文字颜色" v-if="Object.keys(nodeAttrForm).includes('color')">
<el-color-picker v-model="nodeAttrForm.color" show-alpha />
</el-form-item>
<el-form-item label="图片路径" v-if="Object.keys(nodeAttrForm).includes('imgSrc')">
<el-input v-model="nodeAttrForm.imgSrc" style="width: 100%" />
</el-form-item>
<el-form-item label="标题" v-if="Object.keys(nodeAttrForm).includes('title')">
<el-input v-model="nodeAttrForm.title" style="width: 100%" />
</el-form-item>
<el-form-item label="数据名称" v-if="Object.keys(nodeAttrForm).includes('yNames')">
<el-input-tag v-model="nodeAttrForm.yNames" placeholder="回车确认" />
</el-form-item>
</el-form>
</div>
<el-dialog v-model="pageSettingVisible" title="页面设置" width="500">
<el-form :model="pageSettingForm">
<el-form-item label="页面大小" label-width="80px">
<el-input v-model="pageSettingForm.width" style="width: calc(50% - 10px)" autocomplete="off"
placeholder="缺省则为不限制" />
<span style="width: 20px;text-align: center">
X
</span>
<el-input v-model="pageSettingForm.height" style="width: calc(50% - 10px)" autocomplete="off"
placeholder="缺省则为不限制" />
</el-form-item>
<el-form-item label="页面背景" label-width="80px">
<el-upload
class="avatar-uploader"
action="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15"
:show-file-list="false"
:limit="1"
accept=".jpg,.png"
:before-upload="pageBgUploadSuccess"
>
<img v-if="pageSettingForm.bg" :src="pageSettingForm.bg" class="avatar" />
<el-icon v-else class="avatar-uploader-icon">
<Plus />
</el-icon>
</el-upload>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="pageSettingVisible = false">取消</el-button>
<el-button type="primary" @click="setPageData">
确定
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { v4 as uuidv4 } from 'uuid';
import { Setting, Check, Close, Plus } from '@element-plus/icons-vue';
import { MarkerType, VueFlow, useVueFlow } from '@vue-flow/core';
import { Background } from '@vue-flow/background';
import { StarFilled } from '@element-plus/icons-vue';
import LineNode from './nodes/board/lineNode.vue';
import MultiLinesNode from './nodes/board/multiLinesNode.vue';
import CurveNode from './nodes/board/curveNode.vue';
import MultiCurvesNode from './nodes/board/multiCurvesNode.vue';
import BarNode from './nodes/board/barNode.vue';
import MultiBarsNode from './nodes/board/multiBarsNode.vue';
import CustomBoardNode from './nodes/board/customBoardNode.vue';
import PieNode from './nodes/board/pieNode.vue';
import DataNode from './nodes/data/dataNode.vue';
import CustomDataNode from './nodes/data/customDataNode.vue';
import MapNode from './nodes/form/mapNode.vue';
import InputNode from './nodes/form/inputNode.vue';
import TimeNode from './nodes/form/timeNode.vue';
import TextNode from './nodes/form/textNode.vue';
import ImgNode from './nodes/form/imgNode.vue';
import AreaNode from './nodes/other/areaNode.vue';
import tool from './tool';
// tool
const { onDragStart, onDrop, onDragOver } = tool();
// flow
const {
addEdges
} = useVueFlow();
const pageSettingVisible = ref(false);
const pageSettingForm = ref({});
const pageTitle = ref('页面名称');
const leftPanelState = ref('1');
const pageData = ref({});
const nodes = ref([{
id: `area_${uuidv4().replaceAll('-', '_')}`,
name: 'area',
type: 'area',
position: {
x: 0,
y: 0
},
data: {}
}]);
const edges = ref([]);
const customData = ref([]);
const customBoard = ref([]);
onMounted(async () => {
customData.value = JSON.parse(localStorage.getItem('DATANODE') || '[]');
customBoard.value = JSON.parse(localStorage.getItem('BOARDNODE') || '[]');
await nextTick();
nodes.value = reactive(JSON.parse(localStorage.getItem('NODES') || '[]'));
edges.value = JSON.parse(localStorage.getItem('EDGES') || '[]');
pageData.value = JSON.parse(localStorage.getItem('PAGEDATA'));
});
const nodeAttrForm = ref({});
const logEvent = async (eventname, event) => {
switch (eventname) {
case 'connect':
if (!edges.value.some(r => r.id === `${event.source}---${event.target}`)) {
addEdges({
id: `${event.source}---${event.target}`,
source: event.source,
target: event.target,
type: 'bezier',
animated: true,
markerEnd: MarkerType.ArrowClosed,
sourceHandle: event.sourceHandle,
style: { stroke: '#409EFF' }
});
}
break;
case 'paneClick':
nodeAttrForm.value = {};
break;
case 'click':
nodeAttrForm.value = event.node.data.options;
break;
case 'nodeDrag':
if (event.nodes.length === 1) {
nodeAttrForm.value = event.node.data.options;
} else {
nodeAttrForm.value = {};
}
break;
case 'contextmenu':
console.log('contextmenu', event);
}
};
const resize = (e, id) => {
nodes.value.forEach((item) => {
if (item.selected && item.id !== id) {
item.dimensions = {
width: e.params.width,
height: e.params.height
};
}
});
};
const getInputData = (e) => {
let outputData = {};
let nodeIds = edges.value.map(v => {
if (v.target === e) {
return v.source;
}
});
nodes.value.forEach(v => {
if (nodeIds.includes(v.id)) {
outputData = {
...outputData,
...v.data.outputData
};
}
});
return outputData;
};
const pageSetting = () => {
pageSettingVisible.value = true;
pageSettingForm.value = JSON.parse(localStorage.getItem('PAGEDATA'));
};
const save = () => {
localStorage.setItem('NODES', JSON.stringify(nodes.value.map(e => {
let data = {};
let savaField = ['customData', 'options'];
Object.keys(e.data).forEach((key) => {
if (savaField.includes(key)) {
data[key] = e.data[key];
} else {
if (Array.isArray(e.data[key])) {
data[key] = [];
} else if (e.data[key] && typeof e.data[key] === 'object' && !Array.isArray(e.data[key])) {
data[key] = {};
} else {
data[key] = null;
}
}
});
return {
id: e.id,
name: e.name,
dimensions: e.dimensions,
position: e.position,
type: e.type,
data: data
};
})));
localStorage.setItem('EDGES', JSON.stringify(edges.value.map(e => {
return {
id: e.id,
source: e.source,
target: e.target,
type: e.type,
animated: e.animated,
markerEnd: e.markerEnd,
targetHandle: e.targetHandle,
sourceHandle: e.sourceHandle,
style: e.style
};
})));
};
const clear = () => {
nodes.value = [{
id: `area_${uuidv4().replaceAll('-', '_')}`,
name: 'area',
type: 'area',
position: {
x: 0,
y: 0
},
data: {}
}];
edges.value = [];
};
const pageBgUploadSuccess = (file) => {
const getFileText = (file) => {
const reader = new FileReader();
reader.onload = function(e) {
const fileContent = e.target.result;
console.log(fileContent);
pageSettingForm.value.bg = fileContent;
};
reader.readAsDataURL(file);
};
getFileText(file);
return false;
};
const setPageData = () => {
pageData.value = JSON.parse(JSON.stringify(pageSettingForm.value));
localStorage.setItem('PAGEDATA', JSON.stringify(pageData.value));
pageSettingVisible.value = false;
};
</script>
<style lang="less" scoped>
:deep(.vue-flow__node-area) {
z-index: -1 !important;
pointer-events: none !important;
}
:deep(.avatar-uploader) .el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
}
:deep(.avatar-uploader) .el-upload:hover {
border-color: var(--el-color-primary);
}
:deep(.el-icon.avatar-uploader-icon) {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
text-align: center;
}
.moduleCard {
width: 80px;
vertical-align: top;
:deep(.el-card__body) {
height: 40px;
padding: 0 !important;
}
.moduleText {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
width: 100%;
text-align: center;
font-size: 12px;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
}
.leftPanel {
position: absolute;
top: 0;
left: 0;
width: 300px;
height: 100%;
border-right: 1px solid #409EFF;
overflow: auto;
}
.content {
position: absolute;
top: 0;
left: 300px;
width: calc(100% - 300px - 300px);
height: 100%;
.pageSetting {
width: 100%;
height: 50px;
border-bottom: 1px solid #409EFF;
position: relative;
.pageTitle {
line-height: 50px;
margin-left: 8px;
}
.btns {
position: absolute;
right: 8px;
display: inline-block;
line-height: 50px;
button {
vertical-align: inherit;
}
}
}
.flowArea {
width: 100%;
height: calc(100% - 50px);
}
}
.rightPanel {
position: absolute;
top: 0;
right: 0;
width: 300px;
height: 100%;
border-left: 1px solid #409EFF;
overflow: auto;
}
.avatar {
width: 384px;
height: 216px
}
</style>

@ -0,0 +1,224 @@
<template>
<div
:style="{width:props.dimensions.width+'px',height:props.dimensions.height+'px'}">
<NodeResizer color="#fff" v-if="!props.isView && !props.isHideHandle && props.selected" @resize="resize" />
<div class="custom-node"
:style="{width:props.dimensions.width+'px',height:props.dimensions.height+'px',pointerEvents:props.isView?'auto': 'none'}">
<div style="width: 100%;height: 100%" ref="chartRef" />
</div>
<Handle v-if="!props.isView" :id="`${props.id}.-t`" type="target" :position="Position.Left" />
</div>
</template>
<script setup>
import { defineEmits, defineProps, ref } from 'vue';
import { NodeResizer } from '@vue-flow/node-resizer';
import { Handle, Position } from '@vue-flow/core';
import * as echarts from 'echarts';
const props = defineProps({
isView: {
type: Boolean,
required: false
},
inputData: {
type: Object,
required: false
},
id: {
type: String,
required: true
},
isHideHandle: {
type: Boolean,
required: false
},
selected: {
type: Boolean,
required: false
},
data: {
type: Object,
required: true
},
dimensions: {
type: Object,
required: true
}
});
const chartRef = ref();
let chart = null;
const colorList = ['#9E87FF', '#73DDFF', '#fe9a8b', '#F56948', '#9E87FF'];
const getOption = () => {
const chartOption = {
title: {
text: props.data.options.title || '设备运行数量',
textStyle: {
fontSize: 12,
fontWeight: 400,
color: '#fff'
},
left: '0',
top: '5%'
},
legend: {
icon: 'circle',
top: '5%',
right: '5%',
itemWidth: 6,
itemGap: 20,
textStyle: {
color: '#fff'
}
},
tooltip: {
trigger: 'axis',
axisPointer: {
label: {
show: true,
backgroundColor: '#fff',
color: '#000',
borderColor: 'rgba(0,0,0,0)',
shadowColor: 'rgba(0,0,0,0)',
shadowOffsetY: 0
},
lineStyle: {
width: 0
}
},
backgroundColor: '#fff',
textStyle: {
color: '#000'
},
padding: [10, 10],
extraCssText: 'box-shadow: 1px 0 2px 0 rgba(163,163,163,0.5)'
},
grid: {
top: '30%',
bottom: '10%'
},
xAxis: [
{
type: 'category',
axisLine: {
lineStyle: {
color: '#DCE2E8'
}
},
axisTick: {
show: true
},
axisLabel: {
interval: 0,
textStyle: {
color: '#fff'
},
// x
fontSize: 12,
// margin:x
margin: 3
},
axisPointer: {
label: {
padding: [0, 0, 0, 0],
margin: 0,
//
fontSize: 12
}
},
boundaryGap: true
}
],
yAxis: [
{
type: 'value',
axisTick: {
show: false
},
axisLine: {
show: true,
lineStyle: {
color: '#fff'
}
},
axisLabel: {
textStyle: {
color: '#fff'
}
},
splitLine: {
show: false
}
}
],
series: [
{
type: 'bar',
itemStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 1, 0, 0, [{
offset: 0,
color: '#0372FF'
},
{
offset: 1,
color: '#75ECFF'
}
])
}
},
barMaxWidth: 50,
label: {
show: true,
position: 'top',
color: '#fff',
fontSize: 16
}
}
]
};
let xData = [props.inputData?.x1 || []];
let yData = [props.inputData?.y1 || []];
let length = Math.min(...xData.map(e => e.length), ...yData.map(e => e.length));
let source = [['product', ...[props.data.options?.yNames?.[0] || '数量']]];
Array(length).fill(0).forEach((_, i) => {
let item = [];
xData.forEach(e => {
item.push(e[i]);
});
yData.forEach(e => {
item.push(e[i]);
});
source.push(item);
});
return {
...chartOption,
dataset: {
source
}
};
};
onMounted(() => {
chart = echarts.init(chartRef.value, 'macarons', {
renderer: 'svg'
});
chart.setOption(getOption(), true);
});
watch(() => [JSON.parse(JSON.stringify(props.inputData)), JSON.parse(JSON.stringify(props.data.options))], (obj1, obj2) => {
if (JSON.stringify(obj1) !== JSON.stringify(obj2)) {
chart && chart.setOption(getOption(), true);
}
}, { deep: true, immediate: true });
const emit = defineEmits(['resize']);
const resize = (e) => {
chart.resize();
emit('resize', e, props.id);
};
</script>
<style scoped>
.custom-node {
width: 100%;
height: 100%;
}
</style>

@ -0,0 +1,231 @@
<template>
<div
:style="{width:props.dimensions.width+'px',height:props.dimensions.height+'px'}">
<NodeResizer color="#fff" v-if="!props.isView && !props.isHideHandle && props.selected" @resize="resize" />
<div class="custom-node"
:style="{width:props.dimensions.width+'px',height:props.dimensions.height+'px',pointerEvents:props.isView?'auto': 'none'}">
<div style="width: 100%;height: 100%" ref="chartRef" />
</div>
<Handle v-if="!props.isView" :id="`${props.id}.-t`" type="target" :position="Position.Left" />
</div>
</template>
<script setup>
import { defineEmits, defineProps, ref } from 'vue';
import { NodeResizer } from '@vue-flow/node-resizer';
import { Handle, Position } from '@vue-flow/core';
import * as echarts from 'echarts';
const props = defineProps({
isView: {
type: Boolean,
required: false
},
inputData: {
type: Object,
required: false
},
id: {
type: String,
required: true
},
isHideHandle: {
type: Boolean,
required: false
},
selected: {
type: Boolean,
required: false
},
data: {
type: Object,
required: true
},
dimensions: {
type: Object,
required: true
}
});
const chartRef = ref();
let chart = null;
const colorList = ['#9E87FF', '#73DDFF', '#fe9a8b', '#F56948', '#9E87FF'];
const getOption = () => {
const chartOption = {
title: {
text: props.data.options.title || '设备运行数量',
textStyle: {
fontSize: 12,
fontWeight: 400,
color: '#fff'
},
left: '0',
top: '5%'
},
legend: {
icon: 'circle',
top: '5%',
right: '5%',
itemWidth: 6,
itemGap: 20,
textStyle: {
color: '#fff'
}
},
tooltip: {
trigger: 'axis',
axisPointer: {
label: {
show: true,
backgroundColor: '#fff',
color: '#000',
borderColor: 'rgba(0,0,0,0)',
shadowColor: 'rgba(0,0,0,0)',
shadowOffsetY: 0
},
lineStyle: {
width: 0
}
},
backgroundColor: '#fff',
textStyle: {
color: '#000'
},
padding: [10, 10],
extraCssText: 'box-shadow: 1px 0 2px 0 rgba(163,163,163,0.5)'
},
grid: {
top: '30%',
bottom: '10%'
},
xAxis: [
{
type: 'category',
axisLine: {
lineStyle: {
color: '#DCE2E8'
}
},
axisTick: {
show: true
},
axisLabel: {
interval: 0,
textStyle: {
color: '#fff'
},
// x
fontSize: 12,
// margin:x
margin: 3
},
axisPointer: {
label: {
padding: [0, 0, 0, 0],
margin: 0,
//
fontSize: 12
}
},
boundaryGap: true
}
],
yAxis: [
{
type: 'value',
axisTick: {
show: false
},
axisLine: {
show: true,
lineStyle: {
color: '#fff'
}
},
axisLabel: {
textStyle: {
color: '#fff'
}
},
splitLine: {
show: false
}
}
],
series: [
{
type: 'line',
symbolSize: 1,
symbol: 'circle',
smooth: true,
yAxisIndex: 0,
showSymbol: false,
lineStyle: {
width: 1,
color: new echarts.graphic.LinearGradient(0, 1, 0, 0, [
{
offset: 0,
color: '#9effff'
},
{
offset: 1,
color: '#9E87FF'
}
]),
shadowColor: 'rgba(158,135,255, 0.3)',
shadowBlur: 10,
shadowOffsetY: 20
},
itemStyle: {
normal: {
color: colorList[0],
borderColor: colorList[0]
}
}
}
]
};
let xData = [props.inputData?.x1 || []];
let yData = [props.inputData?.y1 || []];
let length = Math.min(...xData.map(e => e.length), ...yData.map(e => e.length));
let source = [['product', ...[props.data.options?.yNames?.[0] || '数量']]];
Array(length).fill(0).forEach((_, i) => {
let item = [];
xData.forEach(e => {
item.push(e[i]);
});
yData.forEach(e => {
item.push(e[i]);
});
source.push(item);
});
return {
...chartOption,
dataset: {
source
}
};
};
onMounted(() => {
chart = echarts.init(chartRef.value, 'macarons', {
renderer: 'svg'
});
chart.setOption(getOption(), true);
});
watch(() => [JSON.parse(JSON.stringify(props.inputData)), JSON.parse(JSON.stringify(props.data.options))], (obj1, obj2) => {
if (JSON.stringify(obj1) !== JSON.stringify(obj2)) {
chart && chart.setOption(getOption(), true);
}
}, { deep: true, immediate: true });
const emit = defineEmits(['resize']);
const resize = (e) => {
chart.resize();
emit('resize', e, props.id);
};
</script>
<style scoped>
.custom-node {
width: 100%;
height: 100%;
}
</style>

@ -0,0 +1,109 @@
<template>
<div
:style="{width:props.dimensions.width+'px',height:props.dimensions.height+'px'}">
<NodeResizer color="#fff" v-if="!props.isView && !props.isHideHandle && props.selected" @resize="resize" />
<div class="custom-node"
:style="{width:props.dimensions.width+'px',height:props.dimensions.height+'px',pointerEvents:props.isView?'auto': 'none'}">
<div style="width: 100%;height: 100%" ref="chartRef" />
</div>
<Handle v-if="!props.isView" :id="`${props.id}.-t`" type="target" :position="Position.Left" />
</div>
</template>
<script setup>
import { defineEmits, defineProps, ref } from 'vue';
import { NodeResizer } from '@vue-flow/node-resizer';
import { Handle, Position } from '@vue-flow/core';
import * as echarts from 'echarts';
const props = defineProps({
isView: {
type: Boolean,
required: false
},
inputData: {
type: Object,
required: false
},
id: {
type: String,
required: true
},
isHideHandle: {
type: Boolean,
required: false
},
selected: {
type: Boolean,
required: false
},
data: {
type: Object,
required: true
},
dimensions: {
type: Object,
required: true
}
});
const chartRef = ref();
let chart = null;
const colorList = ['#9E87FF', '#73DDFF', '#fe9a8b', '#F56948', '#9E87FF'];
const sortData = (data) => {
let obj = [];
Object.keys(data).forEach((key) => {
if (key.includes('y') && !isNaN(key.split('y')?.[1])) {
obj.push([parseFloat(key.split('y')?.[1]), data[key]]);
}
});
obj.sort((a, b) => a[0] - b[0]);
return obj.map(e => e[1]) || [];
};
const getOption = () => {
let xData = [props.inputData?.x1 || []];
let yData = sortData(props.inputData);
let length = Math.min(...xData.map(e => e.length), ...yData.map(e => e.length));
let source = [['product', ...props.data.options?.yNames || []]];
const chartOption = JSON.parse(props.data.customData.option || '{}');
Array(length).fill(0).forEach((_, i) => {
let item = [];
xData.forEach(e => {
item.push(e[i]);
});
yData.forEach(e => {
item.push(e[i]);
});
source.push(item);
});
return {
...chartOption,
dataset: {
source
}
};
};
onMounted(() => {
chart = echarts.init(chartRef.value, 'macarons', {
renderer: 'svg'
});
chart.setOption(getOption(), true);
});
watch(() => [JSON.parse(JSON.stringify(props.inputData)), JSON.parse(JSON.stringify(props.data.options))], (obj1, obj2) => {
if (JSON.stringify(obj1) !== JSON.stringify(obj2)) {
console.log(getOption());
chart && chart.setOption(getOption(), true);
}
}, { deep: true, immediate: true });
const emit = defineEmits(['resize']);
const resize = (e) => {
chart.resize();
emit('resize', e, props.id);
};
</script>
<style scoped>
.custom-node {
width: 100%;
height: 100%;
}
</style>

@ -0,0 +1,231 @@
<template>
<div
:style="{width:props.dimensions.width+'px',height:props.dimensions.height+'px'}">
<NodeResizer color="#fff" v-if="!props.isView && !props.isHideHandle && props.selected" @resize="resize" />
<div class="custom-node"
:style="{width:props.dimensions.width+'px',height:props.dimensions.height+'px',pointerEvents:props.isView?'auto': 'none'}">
<div style="width: 100%;height: 100%" ref="chartRef" />
</div>
<Handle v-if="!props.isView" :id="`${props.id}.-t`" type="target" :position="Position.Left" />
</div>
</template>
<script setup>
import { defineEmits, defineProps, ref } from 'vue';
import { NodeResizer } from '@vue-flow/node-resizer';
import { Handle, Position } from '@vue-flow/core';
import * as echarts from 'echarts';
const props = defineProps({
isView: {
type: Boolean,
required: false
},
inputData: {
type: Object,
required: false
},
id: {
type: String,
required: true
},
isHideHandle: {
type: Boolean,
required: false
},
selected: {
type: Boolean,
required: false
},
data: {
type: Object,
required: true
},
dimensions: {
type: Object,
required: true
}
});
const chartRef = ref();
let chart = null;
const colorList = ['#9E87FF', '#73DDFF', '#fe9a8b', '#F56948', '#9E87FF'];
const getOption = () => {
const chartOption = {
title: {
text: props.data.options.title || '设备运行数量',
textStyle: {
fontSize: 12,
fontWeight: 400,
color: '#fff'
},
left: '0',
top: '5%'
},
legend: {
icon: 'circle',
top: '5%',
right: '5%',
itemWidth: 6,
itemGap: 20,
textStyle: {
color: '#fff'
}
},
tooltip: {
trigger: 'axis',
axisPointer: {
label: {
show: true,
backgroundColor: '#fff',
color: '#000',
borderColor: 'rgba(0,0,0,0)',
shadowColor: 'rgba(0,0,0,0)',
shadowOffsetY: 0
},
lineStyle: {
width: 0
}
},
backgroundColor: '#fff',
textStyle: {
color: '#000'
},
padding: [10, 10],
extraCssText: 'box-shadow: 1px 0 2px 0 rgba(163,163,163,0.5)'
},
grid: {
top: '30%',
bottom: '10%'
},
xAxis: [
{
type: 'category',
axisLine: {
lineStyle: {
color: '#DCE2E8'
}
},
axisTick: {
show: true
},
axisLabel: {
interval: 0,
textStyle: {
color: '#fff'
},
// x
fontSize: 12,
// margin:x
margin: 3
},
axisPointer: {
label: {
padding: [0, 0, 0, 0],
margin: 0,
//
fontSize: 12
}
},
boundaryGap: true
}
],
yAxis: [
{
type: 'value',
axisTick: {
show: false
},
axisLine: {
show: true,
lineStyle: {
color: '#fff'
}
},
axisLabel: {
textStyle: {
color: '#fff'
}
},
splitLine: {
show: false
}
}
],
series: [
{
type: 'line',
symbolSize: 1,
symbol: 'circle',
smooth: false,
yAxisIndex: 0,
showSymbol: false,
lineStyle: {
width: 1,
color: new echarts.graphic.LinearGradient(0, 1, 0, 0, [
{
offset: 0,
color: '#9effff'
},
{
offset: 1,
color: '#9E87FF'
}
]),
shadowColor: 'rgba(158,135,255, 0.3)',
shadowBlur: 10,
shadowOffsetY: 20
},
itemStyle: {
normal: {
color: colorList[0],
borderColor: colorList[0]
}
}
}
]
};
let xData = [props.inputData?.x1 || []];
let yData = [props.inputData?.y1 || []];
let length = Math.min(...xData.map(e => e.length), ...yData.map(e => e.length));
let source = [['product', ...[props.data.options?.yNames?.[0] || '数量']]];
Array(length).fill(0).forEach((_, i) => {
let item = [];
xData.forEach(e => {
item.push(e[i]);
});
yData.forEach(e => {
item.push(e[i]);
});
source.push(item);
});
return {
...chartOption,
dataset: {
source
}
};
};
onMounted(() => {
chart = echarts.init(chartRef.value, 'macarons', {
renderer: 'svg'
});
chart.setOption(getOption(), true);
});
watch(() => [JSON.parse(JSON.stringify(props.inputData)), JSON.parse(JSON.stringify(props.data.options))], (obj1, obj2) => {
if (JSON.stringify(obj1) !== JSON.stringify(obj2)) {
chart && chart.setOption(getOption(), true);
}
}, { deep: true, immediate: true });
const emit = defineEmits(['resize']);
const resize = (e) => {
chart.resize();
emit('resize', e, props.id);
};
</script>
<style scoped>
.custom-node {
width: 100%;
height: 100%;
}
</style>

@ -0,0 +1,234 @@
<template>
<div
:style="{width:props.dimensions.width+'px',height:props.dimensions.height+'px'}">
<NodeResizer color="#fff" v-if="!props.isView && !props.isHideHandle && props.selected" @resize="resize" />
<div class="custom-node"
:style="{width:props.dimensions.width+'px',height:props.dimensions.height+'px',pointerEvents:props.isView?'auto': 'none'}">
<div style="width: 100%;height: 100%" ref="chartRef" />
</div>
<Handle v-if="!props.isView" :id="`${props.id}.-t`" type="target" :position="Position.Left" />
</div>
</template>
<script setup>
import { defineEmits, defineProps, ref } from 'vue';
import { NodeResizer } from '@vue-flow/node-resizer';
import { Handle, Position } from '@vue-flow/core';
import * as echarts from 'echarts';
const props = defineProps({
isView: {
type: Boolean,
required: false
},
inputData: {
type: Object,
required: false
},
id: {
type: String,
required: true
},
isHideHandle: {
type: Boolean,
required: false
},
selected: {
type: Boolean,
required: false
},
data: {
type: Object,
required: true
},
dimensions: {
type: Object,
required: true
}
});
const chartRef = ref();
let chart = null;
const colorList = ['#9E87FF', '#73DDFF', '#fe9a8b', '#F56948', '#9E87FF'];
const sortData = (data) => {
let obj = [];
Object.keys(data).forEach((key) => {
if (key.includes('y') && !isNaN(key.split('y')?.[1])) {
obj.push([parseFloat(key.split('y')?.[1]), data[key]]);
}
});
obj.sort((a, b) => a[0] - b[0]);
return obj.map(e => e[1]) || [];
};
const getOption = () => {
let xData = [props.inputData?.x1 || []];
let yData = sortData(props.inputData);
let length = Math.min(...xData.map(e => e.length), ...yData.map(e => e.length));
let source = [['product', ...props.data.options?.yNames || []]];
const chartOption = {
title: {
text: props.data.options.title || '设备运行数量',
textStyle: {
fontSize: 12,
fontWeight: 400,
color: '#fff'
},
left: '0',
top: '5%'
},
legend: {
icon: 'circle',
top: '5%',
right: '5%',
itemWidth: 6,
itemGap: 20,
textStyle: {
color: '#fff'
}
},
tooltip: {
trigger: 'axis',
axisPointer: {
label: {
show: true,
backgroundColor: '#fff',
color: '#000',
borderColor: 'rgba(0,0,0,0)',
shadowColor: 'rgba(0,0,0,0)',
shadowOffsetY: 0
},
lineStyle: {
width: 0
}
},
backgroundColor: '#fff',
textStyle: {
color: '#000'
},
padding: [10, 10],
extraCssText: 'box-shadow: 1px 0 2px 0 rgba(163,163,163,0.5)'
},
grid: {
top: '30%',
bottom: '10%'
},
xAxis: [
{
type: 'category',
axisLine: {
lineStyle: {
color: '#DCE2E8'
}
},
axisTick: {
show: true
},
axisLabel: {
interval: 0,
textStyle: {
color: '#fff'
},
// x
fontSize: 12,
// margin:x
margin: 3
},
axisPointer: {
label: {
padding: [0, 0, 0, 0],
margin: 0,
//
fontSize: 12
}
},
boundaryGap: true
}
],
yAxis: {
type: 'value',
axisTick: {
show: false
},
axisLine: {
show: true,
lineStyle: {
color: '#fff'
}
},
axisLabel: {
textStyle: {
color: '#fff'
}
},
splitLine: {
show: false
}
},
series: (props.data.options?.yNames || []).map(() => {
return {
type: 'bar',
itemStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 1, 0, 0, [{
offset: 0,
color: '#0372FF'
},
{
offset: 1,
color: '#75ECFF'
}
])
}
},
barMaxWidth: 50,
label: {
show: true,
position: 'top',
color: '#fff',
fontSize: 16
}
};
})
};
Array(length).fill(0).forEach((_, i) => {
let item = [];
xData.forEach(e => {
item.push(e[i]);
});
yData.forEach(e => {
item.push(e[i]);
});
source.push(item);
});
return {
...chartOption,
dataset: {
source
}
};
};
onMounted(() => {
chart = echarts.init(chartRef.value, 'macarons', {
renderer: 'svg'
});
chart && chart.setOption(getOption(), true);
});
watch(() => [JSON.parse(JSON.stringify(props.inputData)), JSON.parse(JSON.stringify(props.data.options))], (obj1, obj2) => {
if (JSON.stringify(obj1) !== JSON.stringify(obj2)) {
chart && chart.setOption(getOption(), true);
}
}, { deep: true, immediate: true });
const emit = defineEmits(['resize']);
const resize = (e) => {
chart.resize();
emit('resize', e, props.id);
};
</script>
<style scoped>
.custom-node {
width: 100%;
height: 100%;
}
</style>

@ -0,0 +1,241 @@
<template>
<div
:style="{width:props.dimensions.width+'px',height:props.dimensions.height+'px'}">
<NodeResizer color="#fff" v-if="!props.isView && !props.isHideHandle && props.selected" @resize="resize" />
<div class="custom-node"
:style="{width:props.dimensions.width+'px',height:props.dimensions.height+'px',pointerEvents:props.isView?'auto': 'none'}">
<div style="width: 100%;height: 100%" ref="chartRef" />
</div>
<Handle v-if="!props.isView" :id="`${props.id}.-t`" type="target" :position="Position.Left" />
</div>
</template>
<script setup>
import { defineEmits, defineProps, ref } from 'vue';
import { NodeResizer } from '@vue-flow/node-resizer';
import { Handle, Position } from '@vue-flow/core';
import * as echarts from 'echarts';
const props = defineProps({
isView: {
type: Boolean,
required: false
},
inputData: {
type: Object,
required: false
},
id: {
type: String,
required: true
},
isHideHandle: {
type: Boolean,
required: false
},
selected: {
type: Boolean,
required: false
},
data: {
type: Object,
required: true
},
dimensions: {
type: Object,
required: true
}
});
const chartRef = ref();
let chart = null;
const colorList = ['#9E87FF', '#73DDFF', '#fe9a8b', '#F56948', '#9E87FF'];
const sortData = (data) => {
let obj = [];
Object.keys(data).forEach((key) => {
if (key.includes('y') && !isNaN(key.split('y')?.[1])) {
obj.push([parseFloat(key.split('y')?.[1]), data[key]]);
}
});
obj.sort((a, b) => a[0] - b[0]);
return obj.map(e => e[1]) || [];
};
const getOption = () => {
let xData = [props.inputData?.x1 || []];
let yData = sortData(props.inputData);
let length = Math.min(...xData.map(e => e.length), ...yData.map(e => e.length));
let source = [['product', ...props.data.options?.yNames]];
const chartOption = {
title: {
text: props.data.options.title || '设备运行数量',
textStyle: {
fontSize: 12,
fontWeight: 400,
color: '#fff'
},
left: '0',
top: '5%'
},
legend: {
icon: 'circle',
top: '5%',
right: '5%',
itemWidth: 6,
itemGap: 20,
textStyle: {
color: '#fff'
}
},
tooltip: {
trigger: 'axis',
axisPointer: {
label: {
show: true,
backgroundColor: '#fff',
color: '#000',
borderColor: 'rgba(0,0,0,0)',
shadowColor: 'rgba(0,0,0,0)',
shadowOffsetY: 0
},
lineStyle: {
width: 0
}
},
backgroundColor: '#fff',
textStyle: {
color: '#000'
},
padding: [10, 10],
extraCssText: 'box-shadow: 1px 0 2px 0 rgba(163,163,163,0.5)'
},
grid: {
top: '30%',
bottom: '10%'
},
xAxis: [
{
type: 'category',
axisLine: {
lineStyle: {
color: '#DCE2E8'
}
},
axisTick: {
show: true
},
axisLabel: {
interval: 0,
textStyle: {
color: '#fff'
},
// x
fontSize: 12,
// margin:x
margin: 3
},
axisPointer: {
label: {
padding: [0, 0, 0, 0],
margin: 0,
//
fontSize: 12
}
},
boundaryGap: true
}
],
yAxis: {
type: 'value',
axisTick: {
show: false
},
axisLine: {
show: true,
lineStyle: {
color: '#fff'
}
},
axisLabel: {
textStyle: {
color: '#fff'
}
},
splitLine: {
show: false
}
},
series: props.data.options?.yNames.map(() => {
return {
type: 'line',
symbolSize: 1,
symbol: 'circle',
smooth: true,
yAxisIndex: 0,
showSymbol: false,
lineStyle: {
width: 1,
color: new echarts.graphic.LinearGradient(0, 1, 0, 0, [
{
offset: 0,
color: '#9effff'
},
{
offset: 1,
color: '#9E87FF'
}
]),
shadowColor: 'rgba(158,135,255, 0.3)',
shadowBlur: 10,
shadowOffsetY: 20
},
itemStyle: {
normal: {
color: colorList[0],
borderColor: colorList[0]
}
}
};
})
};
Array(length).fill(0).forEach((_, i) => {
let item = [];
xData.forEach(e => {
item.push(e[i]);
});
yData.forEach(e => {
item.push(e[i]);
});
source.push(item);
});
return {
...chartOption,
dataset: {
source
}
};
};
onMounted(() => {
chart = echarts.init(chartRef.value, 'macarons', {
renderer: 'svg'
});
chart && chart.setOption(getOption(), true);
});
watch(() => [JSON.parse(JSON.stringify(props.inputData)), JSON.parse(JSON.stringify(props.data.options))], (obj1, obj2) => {
if (JSON.stringify(obj1) !== JSON.stringify(obj2)) {
chart && chart.setOption(getOption(), true);
}
}, { deep: true, immediate: true });
const emit = defineEmits(['resize']);
const resize = (e) => {
chart.resize();
emit('resize', e, props.id);
};
</script>
<style scoped>
.custom-node {
width: 100%;
height: 100%;
}
</style>

@ -0,0 +1,241 @@
<template>
<div
:style="{width:props.dimensions.width+'px',height:props.dimensions.height+'px'}">
<NodeResizer color="#fff" v-if="!props.isView && !props.isHideHandle && props.selected" @resize="resize" />
<div class="custom-node"
:style="{width:props.dimensions.width+'px',height:props.dimensions.height+'px',pointerEvents:props.isView?'auto': 'none'}">
<div style="width: 100%;height: 100%" ref="chartRef" />
</div>
<Handle v-if="!props.isView" :id="`${props.id}.-t`" type="target" :position="Position.Left" />
</div>
</template>
<script setup>
import { defineEmits, defineProps, ref } from 'vue';
import { NodeResizer } from '@vue-flow/node-resizer';
import { Handle, Position } from '@vue-flow/core';
import * as echarts from 'echarts';
const props = defineProps({
isView: {
type: Boolean,
required: false
},
inputData: {
type: Object,
required: false
},
id: {
type: String,
required: true
},
isHideHandle: {
type: Boolean,
required: false
},
selected: {
type: Boolean,
required: false
},
data: {
type: Object,
required: true
},
dimensions: {
type: Object,
required: true
}
});
const chartRef = ref();
let chart = null;
const colorList = ['#9E87FF', '#73DDFF', '#fe9a8b', '#F56948', '#9E87FF'];
const sortData = (data) => {
let obj = [];
Object.keys(data).forEach((key) => {
if (key.includes('y') && !isNaN(key.split('y')?.[1])) {
obj.push([parseFloat(key.split('y')?.[1]), data[key]]);
}
});
obj.sort((a, b) => a[0] - b[0]);
return obj.map(e => e[1]) || [];
};
const getOption = () => {
let xData = [props.inputData?.x1 || []];
let yData = sortData(props.inputData);
let length = Math.min(...xData.map(e => e.length), ...yData.map(e => e.length));
let source = [['product', ...props.data.options?.yNames]];
const chartOption = {
title: {
text: props.data.options.title || '设备运行数量',
textStyle: {
fontSize: 12,
fontWeight: 400,
color: '#fff'
},
left: '0',
top: '5%'
},
legend: {
icon: 'circle',
top: '5%',
right: '5%',
itemWidth: 6,
itemGap: 20,
textStyle: {
color: '#fff'
}
},
tooltip: {
trigger: 'axis',
axisPointer: {
label: {
show: true,
backgroundColor: '#fff',
color: '#000',
borderColor: 'rgba(0,0,0,0)',
shadowColor: 'rgba(0,0,0,0)',
shadowOffsetY: 0
},
lineStyle: {
width: 0
}
},
backgroundColor: '#fff',
textStyle: {
color: '#000'
},
padding: [10, 10],
extraCssText: 'box-shadow: 1px 0 2px 0 rgba(163,163,163,0.5)'
},
grid: {
top: '30%',
bottom: '10%'
},
xAxis: [
{
type: 'category',
axisLine: {
lineStyle: {
color: '#DCE2E8'
}
},
axisTick: {
show: true
},
axisLabel: {
interval: 0,
textStyle: {
color: '#fff'
},
// x
fontSize: 12,
// margin:x
margin: 3
},
axisPointer: {
label: {
padding: [0, 0, 0, 0],
margin: 0,
//
fontSize: 12
}
},
boundaryGap: true
}
],
yAxis: {
type: 'value',
axisTick: {
show: false
},
axisLine: {
show: true,
lineStyle: {
color: '#fff'
}
},
axisLabel: {
textStyle: {
color: '#fff'
}
},
splitLine: {
show: false
}
},
series: props.data.options?.yNames.map(() => {
return {
type: 'line',
symbolSize: 1,
symbol: 'circle',
smooth: false,
yAxisIndex: 0,
showSymbol: false,
lineStyle: {
width: 1,
color: new echarts.graphic.LinearGradient(0, 1, 0, 0, [
{
offset: 0,
color: '#9effff'
},
{
offset: 1,
color: '#9E87FF'
}
]),
shadowColor: 'rgba(158,135,255, 0.3)',
shadowBlur: 10,
shadowOffsetY: 20
},
itemStyle: {
normal: {
color: colorList[0],
borderColor: colorList[0]
}
}
};
})
};
Array(length).fill(0).forEach((_, i) => {
let item = [];
xData.forEach(e => {
item.push(e[i]);
});
yData.forEach(e => {
item.push(e[i]);
});
source.push(item);
});
return {
...chartOption,
dataset: {
source
}
};
};
onMounted(() => {
chart = echarts.init(chartRef.value, 'macarons', {
renderer: 'svg'
});
chart && chart.setOption(getOption(), true);
});
watch(() => [JSON.parse(JSON.stringify(props.inputData)), JSON.parse(JSON.stringify(props.data.options))], (obj1, obj2) => {
if (JSON.stringify(obj1) !== JSON.stringify(obj2)) {
chart && chart.setOption(getOption(), true);
}
}, { deep: true, immediate: true });
const emit = defineEmits(['resize']);
const resize = (e) => {
chart.resize();
emit('resize', e, props.id);
};
</script>
<style scoped>
.custom-node {
width: 100%;
height: 100%;
}
</style>

@ -0,0 +1,154 @@
<template>
<div
:style="{width:props.dimensions.width+'px',height:props.dimensions.height+'px'}">
<NodeResizer color="#fff" v-if="!props.isView && !props.isHideHandle && props.selected" @resize="resize" />
<div class="custom-node"
:style="{width:props.dimensions.width+'px',height:props.dimensions.height+'px',pointerEvents:props.isView?'auto': 'none'}">
<div style="width: 100%;height: 100%" ref="chartRef" />
</div>
<Handle v-if="!props.isView" :id="`${props.id}.-t`" type="target" :position="Position.Left" />
</div>
</template>
<script setup>
import { defineEmits, defineProps, ref } from 'vue';
import { NodeResizer } from '@vue-flow/node-resizer';
import { Handle, Position } from '@vue-flow/core';
import * as echarts from 'echarts';
const props = defineProps({
isView: {
type: Boolean,
required: false
},
inputData: {
type: Object,
required: false
},
id: {
type: String,
required: true
},
isHideHandle: {
type: Boolean,
required: false
},
selected: {
type: Boolean,
required: false
},
data: {
type: Object,
required: true
},
dimensions: {
type: Object,
required: true
}
});
const chartRef = ref();
let chart = null;
const colorList = ['#9E87FF', '#73DDFF', '#fe9a8b', '#F56948', '#9E87FF'];
const getOption = () => {
const chartOption = {
title: {
text: props.data.options.title || '设备运行数量',
textStyle: {
fontSize: 12,
fontWeight: 400,
color: '#fff'
},
left: '0',
top: '5%'
},
legend: {
icon: 'circle',
top: '5%',
right: '5%',
itemWidth: 6,
itemGap: 20,
textStyle: {
color: '#fff'
}
},
tooltip: {
trigger: 'axis',
axisPointer: {
label: {
show: true,
backgroundColor: '#fff',
color: '#000',
borderColor: 'rgba(0,0,0,0)',
shadowColor: 'rgba(0,0,0,0)',
shadowOffsetY: 0
},
lineStyle: {
width: 0
}
},
backgroundColor: '#fff',
textStyle: {
color: '#000'
},
padding: [10, 10],
extraCssText: 'box-shadow: 1px 0 2px 0 rgba(163,163,163,0.5)'
},
grid: {
top: '30%',
bottom: '10%'
},
series: [
{
type: 'pie',
radius: '50%',
label: {
formatter: '{b}: {d}%'
}
}
]
};
let xData = [props.inputData?.x1 || []];
let yData = [props.inputData?.y1 || []];
let length = Math.min(...xData.map(e => e.length), ...yData.map(e => e.length));
let source = [['product', ...[props.data.options?.yNames?.[0] || '数量']]];
Array(length).fill(0).forEach((_, i) => {
let item = [];
xData.forEach(e => {
item.push(e[i]);
});
yData.forEach(e => {
item.push(e[i]);
});
source.push(item);
});
return {
...chartOption,
dataset: {
source
}
};
};
onMounted(() => {
chart = echarts.init(chartRef.value, 'macarons', {
renderer: 'svg'
});
chart.setOption(getOption(), true);
});
watch(() => [JSON.parse(JSON.stringify(props.inputData)), JSON.parse(JSON.stringify(props.data.options))], (obj1, obj2) => {
if (JSON.stringify(obj1) !== JSON.stringify(obj2)) {
chart && chart.setOption(getOption(), true);
}
}, { deep: true, immediate: true });
const emit = defineEmits(['resize']);
const resize = (e) => {
chart.resize();
emit('resize', e, props.id);
};
</script>
<style scoped>
.custom-node {
width: 100%;
height: 100%;
}
</style>

@ -0,0 +1,124 @@
<template>
<div
:style="{width:props.dimensions.width+'px',height:props.dimensions.height+'px'}">
<NodeResizer color="#fff" v-if="!props.isView && !props.isHideHandle && props.selected" @resize="resize" />
<div class="custom-node"
:style="{width:props.dimensions.width+'px',height:props.dimensions.height+'px',pointerEvents:props.isView?'auto': 'none'}">
<el-button type="primary" :style="{width:props.dimensions.width +'px',height:props.dimensions.height+'px'}"
:icon="Connection">{{ props.data.customData.name }}
</el-button>
<span style="color:#fff">
</span>
</div>
<Handle v-if="!props.isView" :id="`${props.id}.-t`" type="target" :position="Position.Left" />
<Handle v-if="!props.isView" :id="`${props.id}.-s`" type="source" :position="Position.Right" />
</div>
</template>
<script setup>
import { defineEmits, defineProps, ref } from 'vue';
import { NodeResizer } from '@vue-flow/node-resizer';
import { Connection } from '@element-plus/icons-vue';
import { Handle, Position } from '@vue-flow/core';
import request from '@/utils/request';
import axios from 'axios';
import { options } from '../../tool.js';
const props = defineProps({
isView: {
type: Boolean,
required: false
},
inputData: {
type: Object,
required: false
},
id: {
type: String,
required: true
},
isHideHandle: {
type: Boolean,
required: false
},
selected: {
type: Boolean,
required: false
},
data: {
type: Object,
required: true
},
dimensions: {
type: Object,
required: true
}
});
const anyFun = (data, fun, key) => {
switch (fun) {
case 'map':
return data.map(e => e[key]);
default:
return null;
}
};
const parseData = (data, rule) => {
let resData = data;
let step = rule.split(',');
step.forEach((item) => {
if (resData) {
let fun = item.split('%');
if (fun.length === 1) {
resData = resData[fun[0]];
}
if (fun.length === 2) {
resData = anyFun(resData, fun[0], fun[1]);
}
}
});
return resData;
};
const getOutputData = () => {
let params = {};
(props.data.customData.inputData || []).forEach((item) => {
params[item.name] = props.inputData[item.name];
});
(options.isD ? request : axios.request)({
method: props.data.customData.method,
url: props.data.customData.url,
params: props.data.customData.method === 'get' ? params : '',
data: props.data.customData.method === 'post' ? params : ''
}).then(res => {
let output = {};
props.data.customData.outputData.forEach(item => {
output[item.name] = parseData(res.data, item.tier);
});
props.data.outputData = output;
});
};
onMounted(() => {
getOutputData();
});
let interval = setInterval(() => {
getOutputData();
}, 1000);
onBeforeUnmount(() => {
clearInterval(interval);
interval = null;
});
const emit = defineEmits(['resize']);
const resize = (e) => {
emit('resize', e, props.id);
};
</script>
<style scoped>
.custom-node {
width: 100%;
height: 100%;
}
</style>

@ -0,0 +1,97 @@
<template>
<div
:style="{width:props.dimensions.width+'px',height:props.dimensions.height+'px'}">
<NodeResizer color="#fff" v-if="!props.isView && !props.isHideHandle && props.selected" @resize="resize" />
<div class="custom-node"
:style="{width:props.dimensions.width+'px',height:props.dimensions.height+'px',pointerEvents:props.isView?'auto': 'none'}">
<el-button type="primary" :style="{width:props.dimensions.width +'px',height:props.dimensions.height+'px'}"
:icon="Connection">设备运行数量
</el-button>
<span style="color:#fff">
</span>
</div>
<Handle v-if="!props.isView" :id="`${props.id}.-t`" type="target" :position="Position.Left" />
<Handle v-if="!props.isView" :id="`${props.id}.-s`" type="source" :position="Position.Right" />
</div>
</template>
<script setup>
import { defineEmits, defineProps, ref } from 'vue';
import { NodeResizer } from '@vue-flow/node-resizer';
import { Connection } from '@element-plus/icons-vue';
import { Handle, Position } from '@vue-flow/core';
import axios from 'axios';
import request from '@/utils/request';
import { options } from '../../tool.js';
const props = defineProps({
isView: {
type: Boolean,
required: false
},
inputData: {
type: Object,
required: false
},
id: {
type: String,
required: true
},
isHideHandle: {
type: Boolean,
required: false
},
selected: {
type: Boolean,
required: false
},
data: {
type: Object,
required: true
},
dimensions: {
type: Object,
required: true
}
});
const service = axios.create({
baseURL: 'http://localhost:3000',
timeout: 10000
});
const getOutputData = () => {
(options.isD ? request : service)({
method: 'post',
url: '/test/getDevice',
data: props.inputData
}).then(res => {
props.data.outputData = {
time: res.data?.data?.map(e => e.time),
value: res.data?.data?.map(e => e.value)
};
});
};
onMounted(() => {
getOutputData();
});
let interval = setInterval(() => {
getOutputData();
}, 1000);
onBeforeUnmount(() => {
clearInterval(interval);
interval = null;
});
const emit = defineEmits(['resize']);
const resize = (e) => {
emit('resize', e, props.id);
};
</script>
<style scoped>
.custom-node {
width: 100%;
height: 100%;
}
</style>

@ -0,0 +1,62 @@
<template>
<div
:style="{width:props.dimensions.width+'px',height:props.dimensions.height+'px'}">
<NodeResizer color="#fff" v-if="!props.isView && !props.isHideHandle && props.selected"
@resize="resize" />
<div class="custom-node"
:style="{width:props.dimensions.width+'px',height:props.dimensions.height+'px',pointerEvents:props.isView?'auto': 'none'}">
<el-image style="width: 100%; height: 100%"
:src="props.inputData?.imgSrc ||props.data.options.imgSrc || 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg'"
fit="contain" />
</div>
<Handle v-if="!props.isView" :id="`${props.id}.-t`" type="target" :position="Position.Left" />
</div>
</template>
<script setup>
import { defineEmits, defineProps, ref } from 'vue';
import { NodeResizer } from '@vue-flow/node-resizer';
import { Handle, Position } from '@vue-flow/core';
const props = defineProps({
isView: {
type: Boolean,
required: false
},
inputData: {
type: Object,
required: false
},
id: {
type: String,
required: true
},
isHideHandle: {
type: Boolean,
required: false
},
selected: {
type: Boolean,
required: false
},
data: {
type: Object,
required: true
},
dimensions: {
type: Object,
required: true
}
});
const emit = defineEmits(['resize']);
const resize = (e) => {
emit('resize', e, props.id);
};
</script>
<style scoped>
.custom-node {
position: absolute;
}
</style>

@ -0,0 +1,76 @@
<template>
<div
:style="{width:props.dimensions.width+'px',height:props.dimensions.height+'px'}">
<NodeResizer color="#fff" v-if="!props.isView && !props.isHideHandle && props.selected" @resize="resize" />
<div class="custom-node"
:style="{width:props.dimensions.width+'px',height:props.dimensions.height+'px',pointerEvents:props.isView?'auto': 'none'}">
<el-input v-model="input" placeholder="输入值" style="width: 100%;height: 100%" />
</div>
<Handle v-if="!props.isView" :id="`${props.id}.-t`" type="target" :position="Position.Left" />
<Handle v-if="!props.isView" :id="`${props.id}.-s`" type="source" :position="Position.Right" />
</div>
</template>
<script setup>
import { defineEmits, defineProps, ref } from 'vue';
import { NodeResizer } from '@vue-flow/node-resizer';
import { Handle, Position } from '@vue-flow/core';
const input = ref('');
const props = defineProps({
isView: {
type: Boolean,
required: false
},
inputData: {
type: Object,
required: false
},
id: {
type: String,
required: true
},
isHideHandle: {
type: Boolean,
required: false
},
selected: {
type: Boolean,
required: false
},
data: {
type: Object,
required: true
},
dimensions: {
type: Object,
required: true
}
});
watch(() => [JSON.parse(JSON.stringify(input.value || '')), JSON.parse(JSON.stringify(props.data.options.field))], (obj1, obj2) => {
if (JSON.stringify(obj1) !== JSON.stringify(obj2)) {
if (props.data?.options?.field) {
props.data.outputData[props.data.options.field] = input.value;
}
}
}, { deep: true, immediate: true });
watch(() => JSON.parse(JSON.stringify(props.data.options?.defaultInput || '')), (obj1, obj2) => {
if (JSON.stringify(obj1) !== JSON.stringify(obj2)) {
input.value = props.data.options.defaultInput;
if (props.data?.options?.field) {
props.data.outputData[props.data.options.field] = input.value;
}
}
}, { deep: true, immediate: true });
const emit = defineEmits(['resize']);
const resize = (e) => {
emit('resize', e, props.id);
};
</script>
<style scoped>
.custom-node {
position: absolute;
}
</style>

@ -0,0 +1,89 @@
<template>
<div
:style="{width:props.dimensions.width+'px',height:props.dimensions.height+'px'}">
<NodeResizer color="#fff" v-if="!props.isView && !props.isHideHandle && props.selected" @resize="resize" />
<div class="custom-node"
:style="{width:props.dimensions.width+'px',height:props.dimensions.height+'px',pointerEvents:props.isView?'auto': 'none'}">
<el-button type="primary" :style="{width:props.dimensions.width +'px',height:props.dimensions.height+'px'}"
:icon="Refresh">数据映射
</el-button>
</div>
<Handle v-if="!props.isView" :id="`${props.id}.-t`" type="target" :position="Position.Left" />
<Handle v-if="!props.isView" :id="`${props.id}.-s`" type="source" :position="Position.Right" />
</div>
</template>
<script setup>
import { defineEmits, defineProps, ref } from 'vue';
import { NodeResizer } from '@vue-flow/node-resizer';
import { Refresh } from '@element-plus/icons-vue';
import { Handle, Position } from '@vue-flow/core';
const props = defineProps({
isView: {
type: Boolean,
required: false
},
inputData: {
type: Object,
required: false
},
id: {
type: String,
required: true
},
isHideHandle: {
type: Boolean,
required: false
},
selected: {
type: Boolean,
required: false
},
data: {
type: Object,
required: true
},
dimensions: {
type: Object,
required: true
}
});
const convertData = () => {
const rule = {};
props.data.options.dataMap.forEach(item => {
rule[item.source] = item.target;
});
let res = {};
Object.keys(props.inputData).forEach(item => {
if (Object.keys(rule).includes(item)) {
res[rule[item]] = props.inputData[item];
} else {
res[item] = props.inputData[item];
}
});
props.data.outputData = res;
};
watch(() => JSON.parse(JSON.stringify(props.inputData)), (obj1, obj2) => {
if (JSON.stringify(obj1) !== JSON.stringify(obj2)) {
convertData();
}
}, { deep: true, immediate: true });
watch(() => JSON.parse(JSON.stringify(props.data.options.dataMap)), (obj1, obj2) => {
if (JSON.stringify(obj1) !== JSON.stringify(obj2)) {
convertData();
}
}, { deep: true, immediate: true });
const emit = defineEmits(['resize']);
const resize = (e) => {
emit('resize', e, props.id);
};
</script>
<style scoped>
.custom-node {
width: 100%;
height: 100%;
}
</style>

@ -0,0 +1,62 @@
<template>
<div
:style="{width:props.dimensions.width+'px',height:props.dimensions.height+'px'}">
<NodeResizer color="#fff" v-if="!props.isView && !props.isHideHandle && props.selected"
@resize="resize" />
<div class="custom-node"
:style="{textAlign:props.data.options.align,width:props.dimensions.width+'px',lineHeight:props.dimensions.height+'px',height:props.dimensions.height+'px',pointerEvents:props.isView?'auto': 'none'}">
<span
:style="{color:props.data.options.color,fontSize:props.dimensions.height+'px'}">{{ props.inputData?.text || props.data.options.text
}}</span>
</div>
<Handle v-if="!props.isView" :id="`${props.id}.-t`" type="target" :position="Position.Left" />
</div>
</template>
<script setup>
import { defineEmits, defineProps, ref } from 'vue';
import { NodeResizer } from '@vue-flow/node-resizer';
import { Handle, Position } from '@vue-flow/core';
const props = defineProps({
isView: {
type: Boolean,
required: false
},
inputData: {
type: Object,
required: false
},
id: {
type: String,
required: true
},
isHideHandle: {
type: Boolean,
required: false
},
selected: {
type: Boolean,
required: false
},
data: {
type: Object,
required: true
},
dimensions: {
type: Object,
required: true
}
});
const emit = defineEmits(['resize']);
const resize = (e) => {
emit('resize', e, props.id);
};
</script>
<style scoped>
.custom-node {
position: absolute;
}
</style>

@ -0,0 +1,91 @@
<template>
<div
:style="{width:props.dimensions.width+'px',height:props.dimensions.height+'px'}">
<NodeResizer color="#fff" v-if="!props.isView && !props.isHideHandle && props.selected"
@resize="resize" />
<div class="custom-node"
:style="{width:props.dimensions.width+'px',height:props.dimensions.height+'px',pointerEvents:props.isView?'auto': 'none'}">
<el-date-picker
v-model="value"
type="datetimerange"
range-separator="到"
start-placeholder="开始时间"
end-placeholder="结束时间"
style="width: 100%;height: 100%"
:format="props.data.options.format||'YYYY/MM/DD HH:mm:ss'"
/>
</div>
<Handle v-if="!props.isView" :id="`${props.id}.-t`" type="target" :position="Position.Left" />
<Handle v-if="!props.isView" :id="`${props.id}.-s`" type="source" :position="Position.Right" />
</div>
</template>
<script setup>
import { defineEmits, defineProps, ref } from 'vue';
import { NodeResizer } from '@vue-flow/node-resizer';
import { Handle, Position } from '@vue-flow/core';
const value = ref('');
const props = defineProps({
isView: {
type: Boolean,
required: false
},
inputData: {
type: Object,
required: false
},
id: {
type: String,
required: true
},
isHideHandle: {
type: Boolean,
required: false
},
selected: {
type: Boolean,
required: false
},
data: {
type: Object,
required: true
},
dimensions: {
type: Object,
required: true
}
});
watch(() => [JSON.parse(JSON.stringify(value.value || '[]')), JSON.parse(JSON.stringify(props.data?.options?.startTimeId)), JSON.parse(JSON.stringify(props.data?.options?.endTimeId))], (obj1, obj2) => {
if (JSON.stringify(obj1) !== JSON.stringify(obj2)) {
if (props.data?.options?.startTimeId) {
props.data.outputData[props.data.options.startTimeId || 'startTime'] = value.value?.[0];
}
if (props.data?.options?.endTimeId) {
props.data.outputData[props.data.options.endTimeId || 'endTime'] = value.value?.[1];
}
}
}, { deep: true, immediate: true });
watch(() => JSON.parse(JSON.stringify(props.data.options?.defaultTime || '')), (obj1, obj2) => {
if (JSON.stringify(obj1) !== JSON.stringify(obj2)) {
value.value = props.data.options.defaultTime;
if (props.data?.options?.startTimeId) {
props.data.outputData[props.data.options.startTimeId || 'startTime'] = value.value?.[0];
}
if (props.data?.options?.endTimeId) {
props.data.outputData[props.data.options.endTimeId || 'endTime'] = value.value?.[1];
}
}
}, { deep: true, immediate: true });
const emit = defineEmits(['resize']);
const resize = (e) => {
emit('resize', e, props.id);
};
</script>
<style scoped>
.custom-node {
position: absolute;
}
</style>

@ -0,0 +1,25 @@
<template>
<div style="pointer-events: none">
<div class="custom-node">
<div class="area"
:style='`width:${props.pageData.width || "1920px"};height:${props.pageData.height || "1080px"};border: 1px solid #fff;background-image:url(${props.pageData.bg})`'></div>
</div>
</div>
</template>
<script setup>
import { defineProps } from 'vue';
const props = defineProps({
pageData: {
type: Object,
required: false
}
});
</script>
<style scoped>
.area {
background-repeat: no-repeat;
background-size: 100% 100%;
}
</style>

File diff suppressed because one or more lines are too long

@ -0,0 +1,122 @@
import { useVueFlow } from '@vue-flow/core';
import { ref, watch } from 'vue';
import { v4 as uuidv4 } from 'uuid';
const getId = (type) => {
return `${type}_${uuidv4().replaceAll('-', '_')}`;
};
const getOption = (e) => {
if (e === 'line' || e === 'multiLines') {
return { title: '', yNames: [] };
} else if (e === 'bar' || e === 'multiBars') {
return { title: '', yNames: [] };
} else if (e === 'curve' || e === 'multiCurves') {
return { title: '', yNames: [] };
} else if (e === 'pie' ) {
return { title: '', yNames: [] };
} else if (e === 'customBoard') {
return { title: '', yNames: [] };
} else if (e === 'data') {
return {};
} else if (e === 'map') {
return { dataMap: [] };
} else if (e === 'inputNode') {
return { field: '', defaultInput: '' };
} else if (e === 'time') {
return { startTimeId: 'startTime', endTimeId: 'endTime', defaultTime: [], format: '' };
} else if (e === 'text') {
return { text: '文字', align: '', color: '#fff' };
} else if (e === 'img') {
return { imgSrc: '' };
} else {
return {};
}
};
const getNodeSize = (e) => {
if (e === 'line' || e === 'multiLines' || e === 'bar' || e === 'multiBars' || e === 'curve' || e === 'multiCurves' || e === 'customBoard'|| e === 'pie') {
return { width: 300, height: 150 };
} else if (e === 'data' || e === 'customData') {
return { width: 150, height: 50 };
} else if (e === 'inputNode') {
return { width: 100, height: 30 };
} else if (e === 'text') {
return { width: 100, height: 30 };
} else if (e === 'time') {
return { width: 200, height: 30 };
} else if (e === 'img') {
return { width: 300, height: 300 };
} else if (e === 'map') {
return { width: 100, height: 30 };
} else {
return { width: 100, height: 100 };
}
};
const tool = () => {
const nodeType = ref('');
const customData = ref('');
const { addNodes, screenToFlowCoordinate, onNodesInitialized, updateNode } = useVueFlow();
const onDragStart = (event, type, data) => {
if (event.dataTransfer) {
event.dataTransfer.setData('application/vueflow', type);
event.dataTransfer.effectAllowed = 'move';
}
nodeType.value = type;
customData.value = data;
document.addEventListener('drop', onDragEnd);
};
const onDragEnd = () => {
nodeType.value = null;
document.removeEventListener('drop', onDragEnd);
};
const onDrop = (event) => {
const dimensions = getNodeSize(nodeType.value);
const position = screenToFlowCoordinate({
x: event.clientX, y: event.clientY
});
const nodeId = getId(nodeType.value);
const newNode = {
id: nodeId,
name: nodeType.value,
type: nodeType.value,
dimensions,
position,
data: { options: getOption(nodeType.value), outputData: {}, customData: customData.value }
};
const { off } = onNodesInitialized(() => {
updateNode(nodeId, (node) => ({
position: { x: node.position.x - node.dimensions.width / 2, y: node.position.y - node.dimensions.height / 2 }
}));
off();
});
addNodes(newNode);
};
const onDragOver = (event) => {
event.preventDefault();
if (nodeType.value) {
if (event.dataTransfer) {
event.dataTransfer.dropEffect = 'move';
}
}
};
return {
onDragStart, onDragEnd, onDrop, onDragOver
};
};
export default tool;
export const options = {
isD: false
};

@ -0,0 +1,113 @@
<template>
<div class="content"
:style='`width:${area.width || "1920px"};height:${area.height || "1080px"};background-image:url(${area.bg})`'>
<div v-for="i in nodes" class="node" :style="{left:i.position?.x+'px',top: i.position?.y+'px'}">
<template v-if="i.type === 'customBoard'">
<CustomBoardNode :isView="true" :inputData=getInputData(i.id) v-bind="i"></CustomBoardNode>
</template>
<template v-if="i.type === 'customData'">
<CustomDataNode :isView="true" :inputData=getInputData(i.id) v-bind="i"></CustomDataNode>
</template>
<template v-if="i.type === 'line'">
<LineNode :isView="true" :inputData=getInputData(i.id) v-bind="i"></LineNode>
</template>
<template v-if="i.type === 'multiLines'">
<MultiLinesNode :isView="true" :inputData=getInputData(i.id) v-bind="i"></MultiLinesNode>
</template>
<template v-if="i.type === 'curve'">
<CurveNode :isView="true" :inputData=getInputData(i.id) v-bind="i"></CurveNode>
</template>
<template v-if="i.type === 'multiCurves'">
<MultiCurvesNode :isView="true" :inputData=getInputData(i.id) v-bind="i"></MultiCurvesNode>
</template>
<template v-if="i.type === 'bar'">
<BarNode :isView="true" :inputData=getInputData(i.id) v-bind="i"></BarNode>
</template>
<template v-if="i.type === 'multiBars'">
<MultiBarsNode :isView="true" :inputData=getInputData(i.id) v-bind="i"></MultiBarsNode>
</template>
<template v-if="i.type === 'data'">
<DataNode :isView="true" :inputData=getInputData(i.id) v-bind="i"></DataNode>
</template>
<template v-if="i.type === 'inputNode'">
<InputNode :isView="true" :inputData=getInputData(i.id) v-bind="i"></InputNode>
</template>
<template v-if="i.type === 'time'">
<TimeNode :isView="true" :inputData=getInputData(i.id) v-bind="i"></TimeNode>
</template>
<template v-if="i.type === 'map'">
<MapNode :isView="true" :inputData=getInputData(i.id) v-bind="i"></MapNode>
</template>
<template v-if="i.type === 'text'">
<TextNode :isView="true" :inputData=getInputData(i.id) v-bind="i"></TextNode>
</template>
<template v-if="i.type === 'img'">
<ImgNode :isView="true" :inputData=getInputData(i.id) v-bind="i"></ImgNode>
</template>
<template v-if="i.type === 'pie'">
<PieNode :isView="true" :inputData=getInputData(i.id) v-bind="i"></PieNode>
</template>
</div>
</div>
</template>
<script setup lang="ts">
import TimeNode from './nodes/form/timeNode.vue';
import DataNode from './nodes/data/dataNode.vue';
import LineNode from './nodes/board/lineNode.vue';
import MultiLinesNode from './nodes/board/multiLinesNode.vue';
import CurveNode from './nodes/board/curveNode.vue';
import MultiCurvesNode from './nodes/board/multiCurvesNode.vue';
import BarNode from './nodes/board/barNode.vue';
import MultiBarsNode from './nodes/board/multiBarsNode.vue';
import InputNode from './nodes/form/inputNode.vue';
import MapNode from './nodes/form/mapNode.vue';
import AreaNode from './nodes/other/areaNode.vue';
import CustomBoardNode from './nodes/board/customBoardNode.vue';
import CustomDataNode from './nodes/data/customDataNode.vue';
import TextNode from './nodes/form/textNode.vue';
import ImgNode from './nodes/form/imgNode.vue';
import PieNode from '@/views/boardGenerate/nodes/board/pieNode.vue';
const nodes = ref([]);
const edges = ref([]);
const area = ref({
width: 1910,
height: 970
});
const getInputData = (e) => {
let outputData = {};
let nodeIds = edges.value.map(v => {
if (v.target === e) {
return v.source;
}
});
nodes.value.forEach(v => {
if (nodeIds.includes(v.id)) {
outputData = {
...outputData,
...v.data.outputData
};
}
});
return outputData;
};
onMounted(() => {
nodes.value = JSON.parse(localStorage.getItem('NODES') || '[]');
edges.value = JSON.parse(localStorage.getItem('EDGES') || '[]');
area.value = JSON.parse(localStorage.getItem('PAGEDATA'));
});
</script>
<style lang="less" scoped>
.content {
position: absolute;
overflow: hidden;
background-color: #000000;
background-repeat: no-repeat;
background-size: 100% 100%;
}
.node {
position: absolute;
}
</style>
Loading…
Cancel
Save