修改组态化
parent
0f4bfcc4a1
commit
8ac8b45fd5
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…
Reference in New Issue