init : 初始化
This commit is contained in:
341
src/views/custom-query/topo/index.vue
Normal file
341
src/views/custom-query/topo/index.vue
Normal file
@@ -0,0 +1,341 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-form :model="queryParams" inline class="query-form" ref="queryForm">
|
||||
<el-form-item label="名称" prop="uqName">
|
||||
<el-input v-model="queryParams.uqName" placeholder="请输入名称" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否发布" prop="publish">
|
||||
<el-select v-model="queryParams.publish" placeholder="请选择发布类型" clearable filterable>
|
||||
<el-option label="已发布" value="2"/>
|
||||
<el-option label="未发布" value="0"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="getList" :icon="Search">搜索</el-button>
|
||||
<el-button @click="handleReset" :icon="Refresh">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="query-btn">
|
||||
<el-button type="primary" @click="handleAdd" :icon="Plus" plain>新增</el-button>
|
||||
<el-button type="danger" @click="handleMoreDelete" :icon="Delete" plain>删除</el-button>
|
||||
</div>
|
||||
<div class="table">
|
||||
<el-table
|
||||
:data="list"
|
||||
row-key="id"
|
||||
:lazy="true"
|
||||
ref="singleTable"
|
||||
v-loading="loading"
|
||||
@selection-change="handleSelect"
|
||||
>
|
||||
<el-table-column type="selection" width="55"/>
|
||||
<el-table-column label="序号" type="index" width="60" align="center"/>
|
||||
<el-table-column prop="uqName" label="名称" align="center"/>
|
||||
<el-table-column prop="dataSourceId" label="数据源" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag >{{getDataSourceOptionItem(scope.row.dataSourceId)}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="publish" label="发布类型" align="center">
|
||||
<template #default="scope">
|
||||
<tag dict-type="publish_type" :value="scope.row.publish"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="uqDescribe" label="描述" align="center"/>
|
||||
<el-table-column prop="createTime" label="创建时间" align="center"/>
|
||||
<el-table-column prop="updateTime" label="更新时间" align="center"/>
|
||||
<el-table-column label="操作">
|
||||
<template #default="scope">
|
||||
<div v-if="scope.row.publish == false">
|
||||
<el-button type="text" size="mini"
|
||||
@click="handleEdit(scope.row.id)" :icon="Edit">编辑
|
||||
</el-button>
|
||||
<el-button type="text" size="mini"
|
||||
@click="handleDesign(scope.row)" :icon="Edit">设计
|
||||
</el-button>
|
||||
<el-button type="text" size="mini" @click="handleDelete(scope.row)"
|
||||
:icon="Delete" style="color: red">删除
|
||||
</el-button>
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-button type="text" size="mini" @click="handleDownLine(scope.row)"
|
||||
:icon="Bottom" style="color: red">下线
|
||||
</el-button>
|
||||
<el-button type="text" size="mini"
|
||||
@click="handleView(scope.row)" :icon="View">查看
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<paging :current-page="pageInfo.pageNum" :page-size="pageInfo.pageSize" :page-sizes="[10, 20, 30, 40,50]"
|
||||
:total="total" @changeSize="handleSizeChange" @goPage="handleCurrentChange"/>
|
||||
<el-dialog v-model="isVisited" :title="title" width="600px">
|
||||
<el-form :model="form" ref="formInstance" :rules="formRules" class="dialog-form">
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="数据源" prop="dataSourceId" v-if="title=='新增拓扑'">
|
||||
<el-select v-model="form.dataSourceId" placeholder="数据源" clearable filterable>
|
||||
<el-option
|
||||
v-for="item in dataSourceOption"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="数据源" prop="dataSourceId" v-else>
|
||||
<el-text>{{ getDataSourceOptionItem(form.dataSourceId) }}</el-text>
|
||||
</el-form-item>
|
||||
<el-form-item label="名称" prop="uqName">
|
||||
<el-input v-model="form.uqName" placeholder="请输入topo名称"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述" prop="uqDescribe">
|
||||
<el-input v-model="form.uqDescribe" placeholder="请输入描述"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- <el-col :span="11" >-->
|
||||
<!-- <el-form-item label="发布类型" prop="publish">-->
|
||||
<!-- <el-radio-group v-model="form.publish">-->
|
||||
<!-- <el-radio border :label="1">已发布</el-radio>-->
|
||||
<!-- <el-radio border :label="0">未发布</el-radio>-->
|
||||
<!-- </el-radio-group>-->
|
||||
<!-- </el-form-item>-->
|
||||
<!-- </el-col>-->
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span>
|
||||
<el-button @click="handleCancel">取消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit(formInstance)">确定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {Search, Refresh, Plus, Delete, Edit,Bottom,View} from '@element-plus/icons-vue'
|
||||
import {ElMessage, ElMessageBox} from "element-plus";
|
||||
import {useRouter} from "vue-router";
|
||||
import {getTopoList, delTopo, addTopo, editTopo, getTopoDetails} from "@/api/custom-query/topo";
|
||||
import {getDataSourceOption} from "@/api/custom-query/datamodel";
|
||||
import {topoDownLine} from "@/api/custom-query/topo-search";
|
||||
import Paging from "@/components/pagination/index.vue";
|
||||
|
||||
const router = useRouter()
|
||||
const queryParams = reactive({
|
||||
preview: '',
|
||||
publish: '',
|
||||
type: '',
|
||||
uqName: '',
|
||||
})
|
||||
const dataSourceOption = ref()
|
||||
const queryForm = ref([])
|
||||
const list = ref([])
|
||||
const total = ref()
|
||||
const loading = ref(true)
|
||||
const title = ref('')
|
||||
const isVisited = ref(false)
|
||||
const pageInfo = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
})
|
||||
const form = ref()
|
||||
const formInstance = ref()
|
||||
const formRules = ref({
|
||||
dataSourceId: [{required: true, message: '请选择数据源', trigger: "blur"}],
|
||||
uqName: [{required: true, message: '请输入topo名称', trigger: 'blur'}],
|
||||
uqDescribe: [{required: true, message: '请输入描述', trigger: 'blur'}],
|
||||
})
|
||||
//获取数据源select的option
|
||||
const getOption = () => {
|
||||
console.log('getOpeion')
|
||||
getDataSourceOption().then(res => {
|
||||
if (res.code === 1000) {
|
||||
dataSourceOption.value = res.data
|
||||
// getList();
|
||||
}
|
||||
})
|
||||
}
|
||||
const getDataSourceOptionItem = (dataSourceId) => {
|
||||
for (let item of dataSourceOption.value) {
|
||||
if (item.value === dataSourceId) {
|
||||
return item.label;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
//重置搜索
|
||||
const handleReset = () => {
|
||||
queryForm.value.resetFields()
|
||||
getList()
|
||||
}
|
||||
//获取数据
|
||||
const getList = async () => {
|
||||
console.log('getList')
|
||||
let params = {
|
||||
...queryParams,
|
||||
...pageInfo
|
||||
}
|
||||
getTopoList(params).then(res => {
|
||||
if (res.code === 1000) {
|
||||
list.value = res.data.rows
|
||||
total.value = res.data.total
|
||||
loading.value = false
|
||||
} else {
|
||||
ElMessage.error(res.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
//重置from表单
|
||||
const restFrom = () => {
|
||||
form.value = {
|
||||
dataSourceId: null,
|
||||
uqName: null,
|
||||
uqDescribe: null
|
||||
}
|
||||
|
||||
}
|
||||
//添加
|
||||
const handleAdd = async () => {
|
||||
formRules.value.dataSourceId[0].required = true
|
||||
restFrom()
|
||||
title.value = "新增拓扑"
|
||||
isVisited.value = true
|
||||
nextTick(() => {
|
||||
// 清空校验
|
||||
formInstance.value.clearValidate()
|
||||
})
|
||||
}
|
||||
//修改
|
||||
const handleEdit = async (id) => {
|
||||
restFrom()
|
||||
getTopoDetails(id).then(res => {
|
||||
console.log('详情', res.data)
|
||||
if (res.code === 1000) {
|
||||
formRules.value.dataSourceId[0].required = false
|
||||
form.value = res.data
|
||||
title.value = "编辑拓扑"
|
||||
isVisited.value = true
|
||||
nextTick(() => {
|
||||
// 清空校验
|
||||
formInstance.value.clearValidate()
|
||||
})
|
||||
} else {
|
||||
ElMessage.error(res.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
//设计
|
||||
const handleDesign = (row) => {
|
||||
router.push({path: `/topo/design/${row.id}`})
|
||||
}
|
||||
//下线
|
||||
const handleDownLine = (row) => {
|
||||
ElMessageBox.confirm(`确认撤销名称为${row.uqName}的数据吗? 撤销后分配的查询权限将全部失效`, '系统提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
customClass: 'red-warning'
|
||||
}).then(() => {
|
||||
topoDownLine(row.id).then(res => {
|
||||
if (res.code === 1000) {
|
||||
ElMessage.success(res.msg)
|
||||
getList()
|
||||
} else {
|
||||
ElMessage.error(res.msg)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
//单个删除
|
||||
const handleDelete = async (row) => {
|
||||
let list = []
|
||||
list.push(row.id)
|
||||
ElMessageBox.confirm(`确认删除名称为${row.uqName}的拓扑图吗?`, '系统提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
delTopo(list).then(res => {
|
||||
if (res.code === 1000) {
|
||||
ElMessage.success(res.msg)
|
||||
getList()
|
||||
} else {
|
||||
ElMessage.error(res.msg)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
//取消
|
||||
const handleCancel = () => {
|
||||
restFrom()
|
||||
isVisited.value = false
|
||||
}
|
||||
|
||||
//提交
|
||||
const handleSubmit = async (instance) => {
|
||||
if (!instance) return
|
||||
instance.validate(async (valid) => {
|
||||
if (!valid) return
|
||||
if (title.value === '新增拓扑') {
|
||||
addTopo(form.value).then(res => {
|
||||
if (res.code === 1000) {
|
||||
ElMessage.success(res.msg)
|
||||
getList()
|
||||
isVisited.value = false
|
||||
} else {
|
||||
ElMessage.error(res.msg)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
editTopo(form.value).then(res => {
|
||||
if (res.code === 1000) {
|
||||
ElMessage.success(res.msg)
|
||||
getList()
|
||||
isVisited.value = false
|
||||
} else {
|
||||
ElMessage.error(res.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
//可删除多行
|
||||
const handleMoreDelete = () => {
|
||||
|
||||
}
|
||||
//勾选table数据行事件
|
||||
const handleSelect = (selection) => {
|
||||
console.log('selection', selection)
|
||||
// tableNameArray.value=selection.map(item => item.tableName)
|
||||
}
|
||||
//切换每页显示条数
|
||||
const handleSizeChange = async (val) => {
|
||||
pageInfo.pageSize = val
|
||||
await getList()
|
||||
}
|
||||
|
||||
//点击页码进行分页功能
|
||||
const handleCurrentChange = async (val) => {
|
||||
pageInfo.pageNum = val
|
||||
await getList()
|
||||
}
|
||||
getOption()
|
||||
getList()
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.red-warning{
|
||||
.el-message-box__content{
|
||||
.el-message-box__container{
|
||||
.el-icon{
|
||||
color: red !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
75
src/views/custom-query/topo/top/behavior/click-er-edge.js
Normal file
75
src/views/custom-query/topo/top/behavior/click-er-edge.js
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* @author: clay
|
||||
* @data: 2021/5/14 23:20
|
||||
* @email: clay@hchyun.com
|
||||
* @description: node er图点击事件
|
||||
*/
|
||||
let vm = null;
|
||||
|
||||
const sendThis = (_this) => {
|
||||
vm = _this;
|
||||
};
|
||||
|
||||
export default {
|
||||
sendThis,
|
||||
name: "click-er-edge",
|
||||
options: {
|
||||
getEvents() {
|
||||
return {
|
||||
"edge:click": "onEdgeClick",
|
||||
"edge:contextmenu": "onEdgeRightClick"
|
||||
};
|
||||
},
|
||||
/**
|
||||
* 点击连线函数
|
||||
* @param event
|
||||
*/
|
||||
onEdgeClick(event) {
|
||||
let clickEdge = event.item;
|
||||
clickEdge.setState("selected", !clickEdge.hasState("selected"));
|
||||
vm.editCurrentFocus("edge")
|
||||
this.updateVmData(event);
|
||||
},
|
||||
/**
|
||||
* 右键点击线
|
||||
* @param event
|
||||
*/
|
||||
onEdgeRightClick(event) {
|
||||
let graph = vm.getGraph();
|
||||
let clickEdge = event.item;
|
||||
let clickEdgeModel = toRaw(clickEdge.getModel());
|
||||
let selectedEdges = graph.findAllByState("edge", "selected");
|
||||
// 如果当前点击节点不是之前选中的单个节点,才进行下面的处理
|
||||
if (!(selectedEdges.length === 1 && clickEdgeModel.id === selectedEdges[0].getModel().id)) {
|
||||
// 先取消所有节点的选中状态
|
||||
graph.findAllByState("edge", "selected").forEach(edge => {
|
||||
edge.setState("selected", false);
|
||||
});
|
||||
// 再添加该节点的选中状态
|
||||
clickEdge.setState("selected", true);
|
||||
vm.editCurrentFocus("edge")
|
||||
this.updateVmData(event);
|
||||
}
|
||||
let point = { x: event.x, y: event.y };
|
||||
},
|
||||
updateVmData(event) {
|
||||
// 更新vm的data: selectedEdge 和 selectedEdgeParams
|
||||
let clickEdge = event.item;
|
||||
if (clickEdge.hasState("selected")) {
|
||||
let clickEdgeModel = toRaw(clickEdge.getModel());
|
||||
vm.editSelectedEdge(clickEdge)
|
||||
let edgeAppConfig = { ...vm.edgeAppConfig };
|
||||
Object.keys(edgeAppConfig).forEach(function(key) {
|
||||
edgeAppConfig[key] = "";
|
||||
});
|
||||
vm.editSelectedEdgeParams({
|
||||
label: clickEdgeModel.label || "",
|
||||
relationalItem: clickEdgeModel.relationalItem,
|
||||
sourceColumn: clickEdgeModel.sourceColumn,
|
||||
targetColumn: clickEdgeModel.targetColumn,
|
||||
appConfig: { ...edgeAppConfig, ...clickEdgeModel.appConfig }
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
154
src/views/custom-query/topo/top/behavior/click-er-node.js
Normal file
154
src/views/custom-query/topo/top/behavior/click-er-node.js
Normal file
@@ -0,0 +1,154 @@
|
||||
/**
|
||||
* @author: clay
|
||||
* @data: 2021/5/14 23:20
|
||||
* @email: clay@hchyun.com
|
||||
* @description: node er图点击事件
|
||||
*/
|
||||
const isInBBox = (point, bbox) => {
|
||||
const {
|
||||
x,
|
||||
y
|
||||
} = point;
|
||||
const {
|
||||
minX,
|
||||
minY,
|
||||
maxX,
|
||||
maxY
|
||||
} = bbox;
|
||||
|
||||
return x < maxX && x > minX && y > minY && y < maxY;
|
||||
};
|
||||
|
||||
let vm = null;
|
||||
|
||||
const sendThis = (_this) => {
|
||||
vm = _this;
|
||||
};
|
||||
|
||||
export default {
|
||||
sendThis,
|
||||
name: "click-er-node",
|
||||
options: {
|
||||
getEvents() {
|
||||
return {
|
||||
wheel: "scroll",
|
||||
"node:click": "onNodeClick",
|
||||
"node:contextmenu": "onNodeRightClick",
|
||||
"canvas:click": "onCanvasClick"
|
||||
};
|
||||
},
|
||||
//滚动事件监听
|
||||
scroll(event) {
|
||||
//禁止滚动的默认事件
|
||||
event.preventDefault();
|
||||
let graph = vm.getGraph()
|
||||
if (vm.clickCtrl || vm.getClickCtrlValue() == true) {
|
||||
if (event.deltaY > 0) {
|
||||
if (graph && !graph.destroyed) {
|
||||
graph.zoom(0.8, {x: event.x, y: event.y})
|
||||
}
|
||||
} else {
|
||||
if (graph && !graph.destroyed) {
|
||||
graph.zoom(1.2, {x: event.x, y: event.y})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const nodes = graph.getNodes().filter((n) => {
|
||||
const bbox = n.getBBox();
|
||||
return isInBBox(graph.getPointByClient(event.clientX, event.clientY), bbox);
|
||||
});
|
||||
if (nodes) {
|
||||
nodes.forEach((node) => {
|
||||
const model = node.getModel();
|
||||
if (model.columns.length < 9) {
|
||||
return;
|
||||
}
|
||||
const idx = model.startIndex || 0;
|
||||
this.start = idx;
|
||||
let startX = model.startX || 0.5;
|
||||
let startIndex = idx + event.deltaY * 0.018;
|
||||
if ((model.columns.length - idx) < 10 && startIndex > idx) {
|
||||
return;
|
||||
}
|
||||
startX -= event.deltaX;
|
||||
if (startIndex < 0) {
|
||||
startIndex = 0;
|
||||
}
|
||||
if (startX > 0) {
|
||||
startX = 0;
|
||||
}
|
||||
if (startIndex > model.columns.length - 1) {
|
||||
startIndex = model.columns.length - 1;
|
||||
}
|
||||
|
||||
graph.update(node, {
|
||||
startIndex,
|
||||
startX
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
//节点左键点击监听
|
||||
onNodeClick(event) {
|
||||
if (event) {
|
||||
vm.editCurrentFocus("node")
|
||||
vm.editRightMenuShow(false)
|
||||
this.shrinkage(event);
|
||||
this.updateVmData(event);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* todo 右键打开设置面板
|
||||
* @param event
|
||||
*/
|
||||
onNodeRightClick(event) {
|
||||
this.updateVmData(event);
|
||||
vm.editTableColumnEditOpen(true)
|
||||
},
|
||||
onCanvasClick() {
|
||||
vm.editCurrentFocus("canvas")
|
||||
vm.editRightMenuShow(false)
|
||||
},
|
||||
updateVmData(event) {
|
||||
let clickNode = event.item;
|
||||
clickNode.setState("selected", true);
|
||||
let clickNodeModel = toRaw(clickNode.getModel());
|
||||
vm.editSelectedNode(clickNode);
|
||||
let nodeAppConfig = {...vm.nodeAppConfig};
|
||||
Object.keys(nodeAppConfig).forEach(function (key) {
|
||||
nodeAppConfig[key] = "";
|
||||
});
|
||||
vm.editSelectedNodeParams({
|
||||
label: clickNodeModel.label || "",
|
||||
columns: clickNodeModel.columns,
|
||||
appConfig: {...nodeAppConfig, ...clickNodeModel.appConfig}
|
||||
})
|
||||
},
|
||||
shrinkage(e) {
|
||||
const {
|
||||
graph
|
||||
} = vm;
|
||||
if (!e.item) {
|
||||
return;
|
||||
}
|
||||
if (e.shape.get("name") === "collapse") {
|
||||
graph.updateItem(e.item, {
|
||||
collapsed: true,
|
||||
size: [300, 50],
|
||||
height: 44
|
||||
});
|
||||
setTimeout(() => graph.layout(), 100);
|
||||
} else if (e.shape.get("name") === "expand") {
|
||||
graph.updateItem(e.item, {
|
||||
collapsed: false,
|
||||
size: [300, 500],
|
||||
height: 316
|
||||
});
|
||||
setTimeout(() => graph.layout(), 100);
|
||||
} else {
|
||||
const model = e.item.getModel();
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
240
src/views/custom-query/topo/top/behavior/drag-add-edge.js
Normal file
240
src/views/custom-query/topo/top/behavior/drag-add-edge.js
Normal file
@@ -0,0 +1,240 @@
|
||||
/**
|
||||
* @author: clay
|
||||
* @data: 2019/07/16
|
||||
* @description: edit mode: 通过拖拽节点上的锚点添加连线
|
||||
*/
|
||||
import utils from "../../utils";
|
||||
|
||||
// 用来获取调用此js的vue组件实例(this)
|
||||
let vm = null;
|
||||
|
||||
const sendThis = (_this) => {
|
||||
vm = _this;
|
||||
};
|
||||
|
||||
import theme from "../theme";
|
||||
import {ElMessage} from "element-plus";
|
||||
|
||||
export default {
|
||||
sendThis, // 暴露函数
|
||||
name: "drag-add-edge",
|
||||
options: {
|
||||
getEvents() {
|
||||
return {
|
||||
"node:mousedown": "onNodeMousedown",
|
||||
"node:mouseup": "onNodeMouseup",
|
||||
"edge:mouseup": "onEdgeMouseup",
|
||||
"mousemove": "onMousemove"
|
||||
};
|
||||
},
|
||||
onNodeMousedown(event) {
|
||||
let self = this;
|
||||
// 交互过程中的信息
|
||||
self.evtInfo = {
|
||||
action: null,
|
||||
node: event.item,
|
||||
target: event.target,
|
||||
};
|
||||
if (self.evtInfo.target && self.evtInfo.target.attrs.name) {
|
||||
// todo...未来可能针对锚点增加其它功能(例如拖拽调整大小)
|
||||
switch (self.evtInfo.target.attrs.name) {
|
||||
case "anchor":
|
||||
self.evtInfo.action = "drawEdge";
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (self.evtInfo && self.evtInfo.action) {
|
||||
self[self.evtInfo.action].start.call(self, event);
|
||||
}
|
||||
},
|
||||
onNodeMouseup(event) {
|
||||
let self = this;
|
||||
if (self.evtInfo && self.evtInfo.action) {
|
||||
self[self.evtInfo.action].stop.call(self, event);
|
||||
}
|
||||
},
|
||||
onEdgeMouseup(event) {
|
||||
let self = this;
|
||||
if (self.evtInfo && self.evtInfo.action === "drawEdge") {
|
||||
self[self.evtInfo.action].stop.call(self, event);
|
||||
}
|
||||
},
|
||||
onMousemove(event) {
|
||||
let self = this;
|
||||
if (self.evtInfo && self.evtInfo.action) {
|
||||
self[self.evtInfo.action].move.call(self, event);
|
||||
}
|
||||
},
|
||||
drawEdge: {
|
||||
isMoving: false,
|
||||
currentLine: null,
|
||||
start: function (event) {
|
||||
let self = this;
|
||||
let themeStyle = theme.defaultStyle; // todo...先使用默认主题,后期可能增加其它风格的主体
|
||||
|
||||
// ************** 暂存【连线】前的数据状态 start **************
|
||||
let graph = vm.getGraph();
|
||||
self.historyData = JSON.stringify(graph.save());
|
||||
// ************** 暂存【连线】前的数据状态 end **************
|
||||
|
||||
let sourceAnchor = self.evtInfo.node.getAnchorPoints();
|
||||
let sourceNodeModel = toRaw(self.evtInfo.node.getModel());
|
||||
// 锚点数据
|
||||
let anchorPoints = self.evtInfo.node.getAnchorPoints();
|
||||
// 处理线条目标点
|
||||
if (anchorPoints && anchorPoints.length) {
|
||||
// 获取距离指定坐标最近的一个锚点
|
||||
sourceAnchor = self.evtInfo.node.getLinkPoint({
|
||||
x: event.x,
|
||||
y: event.y
|
||||
})
|
||||
}
|
||||
// let relational = vm.getRelationalMap().get(sourceNodeModel.tableId)
|
||||
let relational = vm.getRelationalMap().map(item => {
|
||||
if (item.mainId === sourceNodeModel.tableId) {
|
||||
return item
|
||||
}
|
||||
})
|
||||
//item.columnName+':'+item.columnComment
|
||||
let columns = []
|
||||
for (let i = 0; i < sourceNodeModel.columns.length; i++) {
|
||||
let column = {
|
||||
columnName: sourceNodeModel.columns[i].columnName,
|
||||
columnComment: sourceNodeModel.columns[i].columnComment
|
||||
}
|
||||
columns.push(column)
|
||||
}
|
||||
self.drawEdge.currentLine = self.graph.addItem("edge", {
|
||||
// id: G6.Util.uniqueId(), // 这种生成id的方式有bug,会重复
|
||||
id: utils.generateUUID(),
|
||||
// 起始节点
|
||||
source: sourceNodeModel.id,
|
||||
sourceColumn: columns,
|
||||
sourceAnchor: sourceAnchor ? sourceAnchor.anchorIndex : "",
|
||||
// 终止节点/位置
|
||||
relational: relational,
|
||||
target: {
|
||||
x: event.x,
|
||||
y: event.y
|
||||
},
|
||||
type: self.graph.$C.edge.type || "top-cubic",
|
||||
style: themeStyle.edgeStyle.default || self.graph.$C.edge.style
|
||||
});
|
||||
self.drawEdge.isMoving = true;
|
||||
},
|
||||
move(event) {
|
||||
let self = this;
|
||||
if (self.drawEdge.isMoving && self.drawEdge.currentLine) {
|
||||
self.graph.updateItem(self.drawEdge.currentLine, {
|
||||
target: {
|
||||
x: event.x,
|
||||
y: event.y
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
stop(event) {
|
||||
let self = this;
|
||||
if (self.drawEdge.isMoving) {
|
||||
if (self.drawEdge.currentLine === event.item) {
|
||||
// 画线过程中点击则移除当前画线
|
||||
self.graph.removeItem(event.item);
|
||||
} else {
|
||||
let targetNode = event.item;
|
||||
let targetNodeModel = toRaw(targetNode.getModel());
|
||||
let targetAnchor = null;
|
||||
// 锚点数据
|
||||
let anchorPoints = targetNode.getAnchorPoints();
|
||||
// 处理线条目标点
|
||||
let relational = self.drawEdge.currentLine.getModel().relational
|
||||
let starts = false;
|
||||
let relationalItem = null;
|
||||
if (relational) {
|
||||
// let flag = false
|
||||
// relational.forEach(item=>{
|
||||
// flag = item.childId=== targetNodeModel.tableId
|
||||
// })
|
||||
// if(flag) {
|
||||
// starts = true
|
||||
// relationalItem = relational
|
||||
// }
|
||||
relational.map(item => {
|
||||
if (item) {
|
||||
if (item.childId === targetNodeModel.tableId) {
|
||||
starts = true
|
||||
relationalItem = item
|
||||
}else {
|
||||
starts = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
// 去掉关系验证
|
||||
// starts = true
|
||||
if (starts) {
|
||||
if (anchorPoints && anchorPoints.length) {
|
||||
// 获取距离指定坐标最近的一个锚点
|
||||
targetAnchor = targetNode.getLinkPoint({
|
||||
x: event.x,
|
||||
y: event.y
|
||||
});
|
||||
}
|
||||
let columns = []
|
||||
for (let i = 0; i < targetNodeModel.columns.length; i++) {
|
||||
let column = {
|
||||
columnName: targetNodeModel.columns[i].columnName,
|
||||
columnComment: targetNodeModel.columns[i].columnComment
|
||||
}
|
||||
columns.push(column)
|
||||
}
|
||||
self.graph.updateItem(self.drawEdge.currentLine, {
|
||||
target: targetNodeModel.id,
|
||||
relationalItem: relationalItem,
|
||||
targetAnchor: targetAnchor ? targetAnchor.anchorIndex : "",
|
||||
targetColumn: columns,
|
||||
});
|
||||
|
||||
// ************** 记录historyData的逻辑 start **************
|
||||
if (self.historyData) {
|
||||
let graph = self.graph;
|
||||
// 如果当前点过【撤销】了,拖拽节点后没有【重做】功能
|
||||
// 重置undoCount,拖拽后的数据给(当前所在historyIndex + 1),且清空这个时间点之后的记录
|
||||
if (vm.undoCount > 0) {
|
||||
vm.historyIndex = vm.historyIndex - vm.undoCount;// 此时的historyIndex应当更新为【撤销】后所在的索引位置
|
||||
|
||||
for (let i = 1; i <= vm.undoCount; i++) {
|
||||
let key = `graph_history_${vm.historyIndex + i}`;
|
||||
vm.removeHistoryData(key);
|
||||
}
|
||||
vm.editUndoCount(0)
|
||||
// vm.undoCount=0
|
||||
} else {
|
||||
// 正常顺序执行的情况,记录拖拽前的数据状态
|
||||
let key = `graph_history_${vm.historyIndex}`;
|
||||
vm.addHistoryData(key, self.historyData);
|
||||
}
|
||||
// 记录拖拽后的数据状态
|
||||
vm.historyIndex += 1
|
||||
vm.getHistoryIndex(vm.historyIndex);
|
||||
let key = `graph_history_${vm.historyIndex}`;
|
||||
let currentData = JSON.stringify(graph.save());
|
||||
vm.addHistoryData(key, currentData);
|
||||
}
|
||||
} else {
|
||||
if(self.evtInfo.node.getModel().tableId===targetNodeModel.tableId){
|
||||
ElMessage.warning("不可连接自身")
|
||||
}else {
|
||||
ElMessage.warning("两表之间无关联关系")
|
||||
}
|
||||
self.graph.removeItem(self.drawEdge.currentLine);
|
||||
}
|
||||
// ************** 记录historyData的逻辑 end **************
|
||||
}
|
||||
}
|
||||
self.drawEdge.currentLine = null;
|
||||
self.drawEdge.isMoving = false;
|
||||
self.evtInfo = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
57
src/views/custom-query/topo/top/behavior/drag-event-edit.js
Normal file
57
src/views/custom-query/topo/top/behavior/drag-event-edit.js
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* @author: clay
|
||||
* @data: 2019/07/16
|
||||
* @description: edit mode: 鼠标拖动节点的交互(记录拖拽前后的数据,用于【撤销】和【重做】)
|
||||
*/
|
||||
|
||||
// 用来获取调用此js的vue组件实例(this)
|
||||
let vm = null
|
||||
let historyData = null
|
||||
|
||||
const sendThis = (_this) => {
|
||||
vm = _this
|
||||
}
|
||||
|
||||
export default {
|
||||
sendThis, // 暴露函数
|
||||
name: 'drag-event-edit',
|
||||
options: {
|
||||
getEvents() {
|
||||
return {
|
||||
'node:dragstart': 'onNodeDragstart',
|
||||
'node:dragend': 'onNodeDragend'
|
||||
}
|
||||
},
|
||||
onNodeDragstart() {
|
||||
let graph = vm.getGraph()
|
||||
if(graph.cfg){
|
||||
historyData = JSON.stringify(graph.save())
|
||||
}
|
||||
},
|
||||
onNodeDragend() {
|
||||
if (historyData) {
|
||||
let graph = vm.getGraph()
|
||||
// 如果当前点过【撤销】了,拖拽节点后没有【重做】功能
|
||||
// 重置undoCount,拖拽后的数据给(当前所在historyIndex + 1),且清空这个时间点之后的记录
|
||||
if (vm.undoCount > 0) {
|
||||
vm.historyIndex = vm.historyIndex - vm.undoCount // 此时的historyIndex应当更新为【撤销】后所在的索引位置
|
||||
for (let i = 1; i <= vm.undoCount; i++) {
|
||||
let key = `graph_history_${vm.historyIndex + i}`
|
||||
vm.removeHistoryData(key)
|
||||
}
|
||||
vm.undoCount = 0
|
||||
} else {
|
||||
// 正常顺序执行的情况,记录拖拽前的数据状态
|
||||
let key = `graph_history_${vm.historyIndex}`
|
||||
vm.addHistoryData(key, historyData)
|
||||
}
|
||||
// 记录拖拽后的数据状态
|
||||
vm.historyIndex += 1
|
||||
vm.getHistoryIndex(vm.historyIndex);
|
||||
let key = `graph_history_${vm.historyIndex}`
|
||||
let currentData = JSON.stringify(graph.save())
|
||||
vm.addHistoryData(key, currentData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
63
src/views/custom-query/topo/top/behavior/hover-event-edit.js
Normal file
63
src/views/custom-query/topo/top/behavior/hover-event-edit.js
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* @author: clay
|
||||
* @data: 2019/07/16
|
||||
* @description: edit mode: 悬浮交互
|
||||
*/
|
||||
// 用来获取调用此js的vue组件实例(this)
|
||||
let vm = null;
|
||||
let hourItem = null;
|
||||
const sendThis = (topo) => {
|
||||
vm = topo;
|
||||
};
|
||||
export default {
|
||||
sendThis, // 暴露函数
|
||||
name: "hover-event-edit",
|
||||
options: {
|
||||
getEvents() {
|
||||
return {
|
||||
"node:mouseover": "onNodeHover",
|
||||
"node:mouseout": "onNodeOut",
|
||||
"node:mouseleave":"onNodeLeave",
|
||||
};
|
||||
},
|
||||
onNodeHover(event) {
|
||||
let graph = vm.getGraph();
|
||||
let hoverNode = event.item;
|
||||
const name = event.shape.get("name");
|
||||
const item = event.item;
|
||||
if (name && name.startsWith("item")) {
|
||||
graph.updateItem(item, {
|
||||
selectedIndex: Number(name.split("-")[1])
|
||||
});
|
||||
} else {
|
||||
graph.updateItem(item, {
|
||||
selectedIndex: NaN
|
||||
});
|
||||
}
|
||||
if (name && name.startsWith("marker")) {
|
||||
hoverNode.setState("hover", true, graph);
|
||||
hourItem=hoverNode;
|
||||
}else {
|
||||
// if (hourItem!=null){
|
||||
// hourItem.setState("hover", false)
|
||||
// }
|
||||
}
|
||||
},
|
||||
onNodeOut(event) {
|
||||
const name = event.shape.get("name");
|
||||
let hoverNode = event.item;
|
||||
if (name && name.startsWith("marker")) {
|
||||
hoverNode.setState("hover", false);
|
||||
}
|
||||
// if (hourItem!=null){
|
||||
// hourItem.setState("hover", false)
|
||||
// }
|
||||
hoverNode.setState("hover", false);
|
||||
},
|
||||
onNodeLeave(event) {
|
||||
// if (hourItem!=null){
|
||||
// hourItem.setState("hover", false)
|
||||
// }
|
||||
}
|
||||
}
|
||||
};
|
||||
30
src/views/custom-query/topo/top/behavior/index.js
Normal file
30
src/views/custom-query/topo/top/behavior/index.js
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* @author: clay
|
||||
* @data: 2019/07/16
|
||||
* @description: register behaviors
|
||||
*/
|
||||
|
||||
import dragAddEdge from './drag-add-edge'
|
||||
import hoverEventEdit from './hover-event-edit'
|
||||
import dragEventEdit from './drag-event-edit'
|
||||
import keyupEventEdit from './keyup-event-edit'
|
||||
import clickErNode from './click-er-node'
|
||||
import clickErEdge from './click-er-edge'
|
||||
|
||||
const obj = {
|
||||
dragAddEdge,
|
||||
hoverEventEdit,
|
||||
dragEventEdit,
|
||||
keyupEventEdit,
|
||||
clickErNode,
|
||||
clickErEdge
|
||||
}
|
||||
|
||||
export default {
|
||||
obj,
|
||||
register(G6) {
|
||||
Object.values(obj).map(item => {
|
||||
G6.registerBehavior(item.name, item.options)
|
||||
})
|
||||
}
|
||||
}
|
||||
73
src/views/custom-query/topo/top/behavior/keyup-event-edit.js
Normal file
73
src/views/custom-query/topo/top/behavior/keyup-event-edit.js
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* @author: clay
|
||||
* @data: 2019/07/16
|
||||
* @description: edit mode: 键盘事件的交互,主要是删除节点和连线(记录删除前后的数据,用于【撤销】和【重做】)
|
||||
*/
|
||||
|
||||
// 用来获取调用此js的vue组件实例(this)
|
||||
let vm = null;
|
||||
|
||||
const sendThis = (_this) => {
|
||||
vm = _this;
|
||||
};
|
||||
|
||||
export default {
|
||||
sendThis, // 暴露函数
|
||||
name: "keyup-event-edit",
|
||||
options: {
|
||||
getEvents() {
|
||||
return {
|
||||
"keyup": "onKeyup",
|
||||
"keydown": "onKeydown",
|
||||
};
|
||||
},
|
||||
onKeyup(event) {
|
||||
let graph = vm.getGraph();
|
||||
let selectedNodes = graph.findAllByState("node", "selected");
|
||||
let selectedEdges = graph.findAllByState("edge", "selected");
|
||||
if (event.keyCode === 46 && (selectedNodes.length > 0 || selectedEdges.length > 0)) {
|
||||
// ************** 记录【删除】前的数据状态 start **************
|
||||
let historyData = JSON.stringify(graph.save());
|
||||
let key = `graph_history_${vm.historyIndex}`;
|
||||
vm.addHistoryData(key, historyData);
|
||||
// ************** 记录【删除】前的数据状态 end **************
|
||||
|
||||
// 开始删除
|
||||
for (let i = 0; i < selectedNodes.length; i++) {
|
||||
graph.removeItem(selectedNodes[i]);
|
||||
}
|
||||
for (let i = 0; i < selectedEdges.length; i++) {
|
||||
graph.removeItem(selectedEdges[i]);
|
||||
}
|
||||
|
||||
// ************** 记录【删除】后的数据状态 start **************
|
||||
// 如果当前点过【撤销】了,拖拽节点后将取消【重做】功能
|
||||
// 重置undoCount,【删除】后的数据状态给(当前所在historyIndex + 1),且清空这个时间点之后的记录
|
||||
if (vm.undoCount > 0) {
|
||||
vm.historyIndex = vm.historyIndex - vm.undoCount; // 此时的historyIndex应当更新为【撤销】后所在的索引位置
|
||||
for (let i = 1; i <= vm.undoCount; i++) {
|
||||
let key = `graph_history_${vm.historyIndex + i}`;
|
||||
vm.removeHistoryData(key);
|
||||
}
|
||||
vm.undoCount = 0;
|
||||
}
|
||||
// 记录【删除】后的数据状态
|
||||
vm.historyIndex += 1;
|
||||
vm.getHistoryIndex(vm.historyIndex);
|
||||
key = `graph_history_${vm.historyIndex}`;
|
||||
let currentData = JSON.stringify(graph.save());
|
||||
vm.addHistoryData(key, currentData);
|
||||
// ************** 记录【删除】后的数据状态 end **************
|
||||
} else if (event.keyCode == 17) {
|
||||
vm.editClickCtrl(false)
|
||||
}
|
||||
},
|
||||
onKeydown(event) {
|
||||
if (event.keyCode == 17) {
|
||||
if (!vm.clickCtrl) {
|
||||
vm.editClickCtrl(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
91
src/views/custom-query/topo/top/components/FooterBar.vue
Normal file
91
src/views/custom-query/topo/top/components/FooterBar.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<div class="footer-bar">
|
||||
<div class="links">
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://github.com/clay" target="_blank">
|
||||
<i class="iconfont icon-github"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="javascript:void(0);">
|
||||
<i class="fab fa-weixin"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="javascript:void(0);">更新日志</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/clay/style-guide" target="_blank">代码规范</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="copyright">
|
||||
<ul>
|
||||
<li class="nav-item">
|
||||
<span>ClayTop 0.1.0</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>|</span>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<span>ChainCloud FE</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const home = () => {
|
||||
this.$router.push({
|
||||
path: '/'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.footer-bar {
|
||||
.links {
|
||||
float: left;
|
||||
|
||||
ul {
|
||||
padding-left: 60px;
|
||||
}
|
||||
|
||||
li {
|
||||
display: inline;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgba(255, 255, 255, 0.45);
|
||||
}
|
||||
}
|
||||
|
||||
.copyright {
|
||||
float: right;
|
||||
|
||||
ul {
|
||||
padding-right: 60px;
|
||||
}
|
||||
|
||||
li {
|
||||
display: inline;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgba(255, 255, 255, 0.45);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
91
src/views/custom-query/topo/top/components/HeaderBar.vue
Normal file
91
src/views/custom-query/topo/top/components/HeaderBar.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<div class="header-bar">
|
||||
<el-row>
|
||||
<el-col :span="8" style="text-align: left">
|
||||
<router-link to="/">
|
||||
<div class="navbar-brand">
|
||||
<img id="logo" src="@/assets/logo.svg" width="38px" height="38px" alt="ClayTop"/>
|
||||
<span> ClayTop </span>
|
||||
</div>
|
||||
</router-link>
|
||||
</el-col>
|
||||
<el-col :span="8" style="text-align: center">
|
||||
<div class="navbar-title"> </div>
|
||||
</el-col>
|
||||
<el-col :span="8" style="text-align: right">
|
||||
<div class="navbar-btns">
|
||||
<el-button type="text" @click="home">首页</el-button>
|
||||
<el-button type="text" @click="about">关于ClayTop</el-button>
|
||||
<el-button type="text" @click="tutorial">使用教程</el-button>
|
||||
<el-button type="text" @click="$message('敬请期待...')">其它</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const home = () => {
|
||||
this.$router.push({
|
||||
path: '/'
|
||||
})
|
||||
}
|
||||
const about = () => {
|
||||
this.$router.push({
|
||||
path: '/about'
|
||||
})
|
||||
}
|
||||
const tutorial = () => {
|
||||
this.$message({
|
||||
dangerouslyUseHTMLString: true,
|
||||
message: '<div style="text-align: left">时间原因,暂未编写。<br/><br/>目前【开发方式】和【使用方式】可先参考 README.md。 <br/><br/><div style="text-align: right">—— by clay</div></div>',
|
||||
type: 'info',
|
||||
center: true,
|
||||
duration: 5000,
|
||||
showClose: false,
|
||||
offset: 100
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.header-bar {
|
||||
.navbar-brand {
|
||||
display: inline-block;
|
||||
margin: 0 18px 0 35px;
|
||||
/*padding: 0 0 0 8px;*/
|
||||
line-height: 22px;
|
||||
font-size: 20px;
|
||||
|
||||
img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
span {
|
||||
vertical-align: middle;
|
||||
/*font-family: "Microsoft YaHei";*/
|
||||
color: #06080A;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-title {
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.navbar-btns {
|
||||
vertical-align: middle;
|
||||
text-align: right;
|
||||
|
||||
.el-button:last-child {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.el-button--text {
|
||||
color: #06080A;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
13
src/views/custom-query/topo/top/config/edge.js
Normal file
13
src/views/custom-query/topo/top/config/edge.js
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* @author: clay
|
||||
* @data: 2019/08/16
|
||||
* @description: 线条的后期设置
|
||||
*/
|
||||
|
||||
export default {
|
||||
type: 'top-cubic',
|
||||
style: {
|
||||
startArrow: false,
|
||||
endArrow: true
|
||||
}
|
||||
}
|
||||
11
src/views/custom-query/topo/top/config/index.js
Normal file
11
src/views/custom-query/topo/top/config/index.js
Normal file
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* @author: clay
|
||||
* @data: 2019/08/16
|
||||
* @description: 配置
|
||||
*/
|
||||
|
||||
import edge from './edge'
|
||||
|
||||
export default {
|
||||
edge
|
||||
}
|
||||
30
src/views/custom-query/topo/top/edge/base.js
Normal file
30
src/views/custom-query/topo/top/edge/base.js
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* @author: Clay
|
||||
* @data: 2019/07/18
|
||||
* @description: 线公共方法
|
||||
*/
|
||||
|
||||
import utils from '../../utils'
|
||||
|
||||
export default {
|
||||
draw(cfg, group) {
|
||||
const { startPoint, endPoint } = cfg
|
||||
const keyShape = group.addShape('path', {
|
||||
className: 'edge-shape',
|
||||
attrs: {
|
||||
...cfg.style,
|
||||
path: [
|
||||
['M', startPoint.x, startPoint.y],
|
||||
['L', endPoint.x, endPoint.y]
|
||||
]
|
||||
},
|
||||
name: 'edge-shape'
|
||||
})
|
||||
keyShape.attrs.endArrow = true
|
||||
return keyShape
|
||||
},
|
||||
setState(name, value, item) {
|
||||
// 设置边状态
|
||||
utils.edge.setState(name, value, item)
|
||||
}
|
||||
}
|
||||
18
src/views/custom-query/topo/top/edge/index.js
Normal file
18
src/views/custom-query/topo/top/edge/index.js
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* @author: clay
|
||||
* @data: 2019/07/18
|
||||
* @description: register edges
|
||||
*/
|
||||
|
||||
|
||||
import topCubic from './top-cubic'
|
||||
|
||||
const obj = {
|
||||
topCubic
|
||||
}
|
||||
|
||||
export default function(G6) {
|
||||
Object.values(obj).map(item => {
|
||||
G6.registerEdge(item.name, item.options, item.extendName)
|
||||
})
|
||||
}
|
||||
14
src/views/custom-query/topo/top/edge/top-cubic.js
Normal file
14
src/views/custom-query/topo/top/edge/top-cubic.js
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* @author: Clay
|
||||
* @data: 2019/07/18
|
||||
* @description: 曲线
|
||||
*/
|
||||
|
||||
import base from './base'
|
||||
export default {
|
||||
name: 'top-cubic',
|
||||
extendName: 'cubic',
|
||||
options: {
|
||||
...base
|
||||
}
|
||||
}
|
||||
92
src/views/custom-query/topo/top/elements/button.vue
Normal file
92
src/views/custom-query/topo/top/elements/button.vue
Normal file
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<button
|
||||
class="cc-button"
|
||||
@click="handleClick"
|
||||
:disabled="buttonDisabled"
|
||||
:class="[
|
||||
type ? 'cc-button--' + type : '',
|
||||
buttonSize ? 'cc-button--' + buttonSize : '',
|
||||
{
|
||||
'is-disabled': buttonDisabled,
|
||||
'is-plain': plain
|
||||
}
|
||||
]"
|
||||
>
|
||||
<span><slot></slot></span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {defineProps, computed} from "vue";
|
||||
|
||||
const emit = defineEmits(['click'])
|
||||
const props = defineProps({
|
||||
type: {
|
||||
type: String,
|
||||
default: 'default'
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
plain: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const buttonSize = computed(() => {
|
||||
return props.size
|
||||
})
|
||||
const buttonDisabled = computed(() => {
|
||||
return props.disabled
|
||||
})
|
||||
|
||||
const handleClick = (evt) => {
|
||||
emit('click', evt)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cc-button {
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
background: #fff;
|
||||
border: 1px solid #dcdfe6;
|
||||
color: #606266;
|
||||
-webkit-appearance: none;
|
||||
text-align: center;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
outline: 0;
|
||||
margin: 0;
|
||||
-webkit-transition: .1s;
|
||||
transition: .1s;
|
||||
font-weight: 500;
|
||||
padding: 12px 20px;
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.cc-button:focus, .cc-button:hover {
|
||||
color: #409eff;
|
||||
border-color: #c6e2ff;
|
||||
background-color: #ecf5ff;
|
||||
}
|
||||
|
||||
.cc-button--mini, .cc-button--mini.is-round {
|
||||
padding: 7px 15px;
|
||||
}
|
||||
|
||||
.cc-button--mini, .cc-button--small {
|
||||
font-size: 12px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
</style>
|
||||
75
src/views/custom-query/topo/top/elements/checkbox.vue
Normal file
75
src/views/custom-query/topo/top/elements/checkbox.vue
Normal file
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<div class="cc-checkbox" :class="{ 'checkbox-checked': checkVal }">
|
||||
<input
|
||||
id="checkbox"
|
||||
type="checkbox"
|
||||
v-model="checkVal"
|
||||
@change="handleChange"
|
||||
/>
|
||||
<label for="checkbox"></label>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref} from "vue";
|
||||
|
||||
const emit = defineEmits(['change'])
|
||||
const checkVal = ref(false);
|
||||
|
||||
const handleChange = () => {
|
||||
emit('change', checkVal.value)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cc-checkbox {
|
||||
display: inline-block;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
|
||||
input[type='checkbox'] + label {
|
||||
margin: 0 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input[type='checkbox'] + label::before {
|
||||
content: '\a0';
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 2px;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
line-height: 0.9;
|
||||
background-color: #fff;
|
||||
z-index: 1;
|
||||
-webkit-transition: border-color .25s cubic-bezier(.71, -.46, .29, 1.46), background-color .25s cubic-bezier(.71, -.46, .29, 1.46);
|
||||
transition: border-color .25s cubic-bezier(.71, -.46, .29, 1.46), background-color .25s cubic-bezier(.71, -.46, .29, 1.46);
|
||||
}
|
||||
|
||||
input[type='checkbox']:checked + label::before {
|
||||
content: '\2714';
|
||||
color: #fff;
|
||||
background-color: #409eff;
|
||||
border-color: #409eff;
|
||||
}
|
||||
|
||||
input[type='checkbox'] {
|
||||
opacity: 0;
|
||||
outline: 0;
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox-checked {
|
||||
color: #409eff;
|
||||
}
|
||||
</style>
|
||||
75
src/views/custom-query/topo/top/elements/dropdown.vue
Normal file
75
src/views/custom-query/topo/top/elements/dropdown.vue
Normal file
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<div class="graph-op">
|
||||
<span v-for="edgeItem in dropdownItems" @click="onItemClick(edgeItem.value, $event)">{{ edgeItem.label }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import $ from 'jquery'
|
||||
import {defineProps, ref, watch} from "vue";
|
||||
|
||||
const emit = defineEmits(['change'])
|
||||
const props = defineProps({
|
||||
dropdownItems: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
defaultIndex: {
|
||||
type: Number,
|
||||
default() {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
})
|
||||
const activeIndex = ref(props.defaultIndex)
|
||||
const timeout = ref(null)
|
||||
//todo 可能有问题
|
||||
watch(() => activeIndex.value, (value) => {
|
||||
emit('change', activeIndex.value)
|
||||
})
|
||||
|
||||
const onItemClick = (index, e) => {
|
||||
let lineList = $('.iconfonts')
|
||||
for (let i = 0; i < lineList.length; i++) {
|
||||
lineList[i].style.backgroundColor = '#ffffff'
|
||||
}
|
||||
// if (e.path[0].style.backgroundColor == 'rgb(255, 255, 255)') {
|
||||
// e.path[0].style.backgroundColor = '#ebeef2'
|
||||
// }
|
||||
if (index !== activeIndex.value) {
|
||||
activeIndex.value = index
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.iconfonts {
|
||||
color: #666666;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
height: 20px;
|
||||
padding-bottom: 2px;
|
||||
|
||||
}
|
||||
|
||||
.graph-op {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
span {
|
||||
width: 120px;
|
||||
margin: 0 6px;
|
||||
color: #a8a8a8;
|
||||
text-align: center;
|
||||
border-radius: 2px;
|
||||
display: inline-block;
|
||||
border: 1px solid rgba(2, 2, 2, 0);
|
||||
}
|
||||
|
||||
span:hover {
|
||||
cursor: pointer;
|
||||
border: 1px solid #E9E9E9;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
17
src/views/custom-query/topo/top/elements/index.js
Normal file
17
src/views/custom-query/topo/top/elements/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* @author: clay
|
||||
* @data: 2019/11/14
|
||||
* @description: ClayTop内部的通用组件
|
||||
*/
|
||||
|
||||
import Checkbox from './checkbox.vue'
|
||||
import Button from './button.vue'
|
||||
import Dropdown from './dropdown.vue'
|
||||
import Loading from './loading.vue'
|
||||
|
||||
export {
|
||||
Checkbox,
|
||||
Button,
|
||||
Dropdown,
|
||||
Loading
|
||||
}
|
||||
91
src/views/custom-query/topo/top/elements/loading.vue
Normal file
91
src/views/custom-query/topo/top/elements/loading.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<div class="model-overlay">
|
||||
<div class="cc-loading">
|
||||
<svg class="circular" viewBox="0 0 50 50">
|
||||
<circle cx="25" cy="25" r="20" fill="none" stroke="#409EFF" stroke-width="5%" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<span class="loading-text">{{ loadingText }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {defineProps} from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
loadingText: {
|
||||
type: String,
|
||||
default: '加载中...'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.model-overlay {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
z-index: 1000;
|
||||
overflow: auto;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.cc-loading {
|
||||
position: absolute;
|
||||
width: 50px;
|
||||
top: 45%;
|
||||
left: 50%;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
display: block;
|
||||
padding-top: 100%;
|
||||
}
|
||||
|
||||
.circular {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
animation: rotate 2s linear infinite;
|
||||
}
|
||||
|
||||
circle {
|
||||
animation: circle-dash 1.5s ease-in-out infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes circle-dash {
|
||||
0% {
|
||||
stroke-dasharray: 1, 125;
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
50% {
|
||||
stroke-dasharray: 100, 125;
|
||||
stroke-dashoffset: -25px;
|
||||
}
|
||||
100% {
|
||||
stroke-dasharray: 100, 125;
|
||||
stroke-dashoffset: -125px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
position: absolute;
|
||||
top: calc(45% + 60px);
|
||||
left: calc(50% - 10px);
|
||||
color: #409EFF;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
161
src/views/custom-query/topo/top/graph/index.js
Normal file
161
src/views/custom-query/topo/top/graph/index.js
Normal file
@@ -0,0 +1,161 @@
|
||||
/**
|
||||
* @author: clay
|
||||
* @data: 2019/07/05
|
||||
* @description: 图的布局方式/图的初始化
|
||||
*/
|
||||
|
||||
// import d3 from '../plugins/d3-installer'
|
||||
import * as d3 from 'd3'
|
||||
import theme from '../theme'
|
||||
|
||||
/**
|
||||
* 图的布局方式/图的初始化
|
||||
* @type {{commonGraph: (function(*, *): G6.Graph)}}
|
||||
*/
|
||||
const initGraph = {
|
||||
/**
|
||||
* 一般布局
|
||||
* @param G6
|
||||
* @param options
|
||||
* @returns {G6.Graph}
|
||||
*/
|
||||
commonGraph: function(G6, options) {
|
||||
let graphData = options.graphData
|
||||
let themeStyle = theme.defaultStyle // todo...先使用默认主题,后期可能增加其它风格的主体
|
||||
// 生成G6实例
|
||||
let graph = new G6.Graph({
|
||||
plugins: options.plugins,
|
||||
container: options.container,
|
||||
width: options.width,
|
||||
height: options.height,
|
||||
// layout: {
|
||||
// type: 'random',
|
||||
// width: options.width,
|
||||
// height: options.height
|
||||
// },
|
||||
defaultNode: {
|
||||
type: 'dice-er-box',
|
||||
labelCfg: {
|
||||
position: 'bottom'
|
||||
}
|
||||
},
|
||||
defaultEdge: {
|
||||
type: 'top-cubic',
|
||||
labelCfg: {
|
||||
position: 'center',
|
||||
autoRotate: false
|
||||
}
|
||||
},
|
||||
nodeStateStyles: themeStyle.nodeStyle,
|
||||
// nodeStyle: {
|
||||
// selected: {
|
||||
// shadowColor: '#626262',
|
||||
// shadowBlur: 8,
|
||||
// shadowOffsetX: -1,
|
||||
// shadowOffsetY: 3
|
||||
// }
|
||||
// },
|
||||
edgeStateStyles:{
|
||||
edgeStyle: {
|
||||
default: {
|
||||
stroke: '#e2e2e2',
|
||||
lineWidth: 3,
|
||||
lineAppendWidth: 10
|
||||
},
|
||||
selected: {
|
||||
shadowColor: '#626262',
|
||||
shadowBlur: 3
|
||||
}
|
||||
},
|
||||
},
|
||||
modes: options.modes
|
||||
})
|
||||
// 将 read 方法分解成 data() 和 render 方法,便于整个生命周期的管理
|
||||
graph.read(graphData)
|
||||
graph.render()
|
||||
// 返回G6实例
|
||||
return graph
|
||||
},
|
||||
/**
|
||||
* 力导布局
|
||||
* @param G6
|
||||
* @param options
|
||||
* @returns {*}
|
||||
*/
|
||||
forceLayoutGraph: function(resolve, G6, options) {
|
||||
let graphData = options.graphData
|
||||
let themeStyle = theme.defaultStyle // todo...先使用默认主题,后期可能增加其它风格的主体
|
||||
// 生成G6实例
|
||||
let graph = new G6.Graph({
|
||||
container: options.container,
|
||||
width: options.width,
|
||||
height: options.height,
|
||||
nodeStateStyles: themeStyle.nodeStyle,
|
||||
edgeStateStyles: themeStyle.edgeStyle
|
||||
})
|
||||
// 初始化力导布局
|
||||
let simulation = d3
|
||||
.forceSimulation()
|
||||
.force(
|
||||
'link',
|
||||
d3
|
||||
.forceLink()
|
||||
.id(function(d) {
|
||||
return d.id
|
||||
})
|
||||
.distance(linkDistance)
|
||||
.strength(0.5)
|
||||
)
|
||||
.force('charge', d3.forceManyBody().strength(-500).distanceMax(500).distanceMin(100))
|
||||
.force('center', d3.forceCenter(options.width / 2, options.height / 2))
|
||||
// 定义节点数据
|
||||
simulation.nodes(graphData.nodes).on('tick', ticked)
|
||||
// 定义连线数据
|
||||
let edges = []
|
||||
for (let i = 0; i < graphData.edges.length; i++) {
|
||||
edges.push({
|
||||
id: graphData.edges[i].id,
|
||||
source: graphData.edges[i].source,
|
||||
target: graphData.edges[i].target
|
||||
})
|
||||
}
|
||||
simulation.force('link').links(edges)
|
||||
graph.data(graphData)
|
||||
graph.render()
|
||||
|
||||
function linkDistance(d) {
|
||||
return 150
|
||||
}
|
||||
|
||||
function ticked() {
|
||||
// protect: planA: 移除事件监听器 planB: 手动停止力模拟
|
||||
if (graph.destroyed) {
|
||||
// simulation.nodes(graphData.nodes).on('tick', null)
|
||||
simulation.stop()
|
||||
return
|
||||
}
|
||||
if (!graph.get('data')) {
|
||||
// 若是第一次渲染,定义数据,渲染
|
||||
graph.data(graphData)
|
||||
graph.render()
|
||||
} else {
|
||||
// 后续渲染,直接刷新所有点和边的位置
|
||||
graph.refreshPositions()
|
||||
}
|
||||
}
|
||||
|
||||
// 控制时间: 只布局10秒
|
||||
let t = setTimeout(function() {
|
||||
simulation.stop()
|
||||
resolve(graph)
|
||||
}, 10000)
|
||||
|
||||
// 判断force-layout结束
|
||||
simulation.on('end', () => {
|
||||
clearTimeout(t)
|
||||
resolve(graph)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default initGraph
|
||||
12
src/views/custom-query/topo/top/index.vue
Normal file
12
src/views/custom-query/topo/top/index.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<el-button @click="handleClick">设计</el-button>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {useRouter} from "vue-router";
|
||||
|
||||
const router = useRouter()
|
||||
const handleClick=()=>{
|
||||
router.push('/top/edit/1')
|
||||
}
|
||||
</script>
|
||||
295
src/views/custom-query/topo/top/node/dice-er-box.js
Normal file
295
src/views/custom-query/topo/top/node/dice-er-box.js
Normal file
@@ -0,0 +1,295 @@
|
||||
/**
|
||||
* @author: clay
|
||||
* @data: 2021/5/15 0:16
|
||||
* @email: clay@hchyun.com
|
||||
* @description: draw anchor 绘制锚点
|
||||
*/
|
||||
import utils from '../../utils/index'
|
||||
|
||||
const itemHeight = 30;
|
||||
export default {
|
||||
name: 'dice-er-box',
|
||||
options: {
|
||||
setState(name, value, item) {
|
||||
// 设置节点状态
|
||||
utils.node.setState(name, value, item);
|
||||
// 设置锚点状态
|
||||
utils.anchor.setState(name, value, item);
|
||||
},
|
||||
draw(cfg, group) {
|
||||
const width = 250;
|
||||
const height = 316;
|
||||
const itemCount = 10;
|
||||
const boxStyle = {
|
||||
stroke: "#096DD9",
|
||||
radius: 4,
|
||||
};
|
||||
|
||||
const {
|
||||
columns = [],
|
||||
startIndex = 0,
|
||||
selectedIndex,
|
||||
collapsed,
|
||||
icon,
|
||||
} = cfg;
|
||||
const list = columns;
|
||||
const afterList = list.slice(
|
||||
Math.floor(startIndex),
|
||||
Math.floor(startIndex + itemCount - 1)
|
||||
);
|
||||
const offsetY = (0.5 - (startIndex % 1)) * itemHeight + 30;
|
||||
|
||||
//设置表名的容器
|
||||
group.addShape("rect", {
|
||||
attrs: {
|
||||
fill: boxStyle.stroke,
|
||||
height: 30,
|
||||
width,
|
||||
radius: [boxStyle.radius, boxStyle.radius, 0, 0],
|
||||
},
|
||||
draggable: true,
|
||||
});
|
||||
|
||||
//设置左侧字体的边距
|
||||
let fontLeft = 12;
|
||||
|
||||
// 设置图标
|
||||
if (icon && icon.show !== false) {
|
||||
group.addShape("image", {
|
||||
attrs: {
|
||||
x: 8,
|
||||
y: 8,
|
||||
height: 16,
|
||||
width: 16,
|
||||
...icon,
|
||||
},
|
||||
});
|
||||
fontLeft += 18;
|
||||
}
|
||||
|
||||
//设置表名
|
||||
group.addShape("text", {
|
||||
attrs: {
|
||||
y: 22,
|
||||
x: fontLeft,
|
||||
fill: "#fff",
|
||||
text: cfg.label,
|
||||
fontSize: 12,
|
||||
fontWeight: 500,
|
||||
},
|
||||
});
|
||||
|
||||
//设置收缩部分的容器
|
||||
group.addShape("rect", {
|
||||
attrs: {
|
||||
x: 0,
|
||||
y: collapsed ? 30 : 300,
|
||||
height: 15,
|
||||
width,
|
||||
fill: "#eee",
|
||||
radius: [0, 0, boxStyle.radius, boxStyle.radius],
|
||||
cursor: "pointer",
|
||||
},
|
||||
name: collapsed ? "expand" : "collapse",
|
||||
});
|
||||
|
||||
//设置收缩显示字符
|
||||
group.addShape("text", {
|
||||
attrs: {
|
||||
x: width / 2 - 6,
|
||||
y: (collapsed ? 30 : 300) + 12,
|
||||
text: collapsed ? "+" : "-",
|
||||
width,
|
||||
fill: "#000",
|
||||
radius: [0, 0, boxStyle.radius, boxStyle.radius],
|
||||
cursor: "pointer",
|
||||
},
|
||||
name: collapsed ? "expand" : "collapse",
|
||||
});
|
||||
|
||||
//设置外边框
|
||||
const keyshape = group.addShape("rect", {
|
||||
attrs: {
|
||||
name: 'border',
|
||||
x: 0,
|
||||
y: 0,
|
||||
width,
|
||||
height: collapsed ? 45 : height,
|
||||
...boxStyle,
|
||||
},
|
||||
//是否被允许拖拽
|
||||
draggable: true
|
||||
});
|
||||
|
||||
//如果收缩状态,则返回当前图形
|
||||
if (collapsed) {
|
||||
return keyshape;
|
||||
}
|
||||
//添加空白组
|
||||
const listContainer = group.addGroup({});
|
||||
//todo 设置裁剪对象,字体加粗?
|
||||
listContainer.setClip({
|
||||
type: "rect",
|
||||
attrs: {
|
||||
x: -8,
|
||||
y: 30,
|
||||
width: width + 16,
|
||||
height: 300 - 30,
|
||||
},
|
||||
});
|
||||
listContainer.addShape({
|
||||
type: "rect",
|
||||
attrs: {
|
||||
x: 1,
|
||||
y: 30,
|
||||
width: width - 2,
|
||||
height: 300 - 30,
|
||||
fill: "#fff",
|
||||
},
|
||||
draggable: true,
|
||||
});
|
||||
|
||||
//如果list中的column字段超过10个
|
||||
if (list.length > itemCount) {
|
||||
const barStyle = {
|
||||
width: 4,
|
||||
padding: 0,
|
||||
boxStyle: {
|
||||
stroke: "#00000022",
|
||||
},
|
||||
innerStyle: {
|
||||
fill: "#00000022",
|
||||
},
|
||||
};
|
||||
|
||||
listContainer.addShape("rect", {
|
||||
attrs: {
|
||||
y: 30,
|
||||
x: width - barStyle.padding - barStyle.width,
|
||||
width: barStyle.width,
|
||||
height: height - 30,
|
||||
...barStyle.boxStyle,
|
||||
},
|
||||
});
|
||||
|
||||
//设置矩形高度
|
||||
const indexHeight =
|
||||
afterList.length > itemCount ?
|
||||
(afterList.length / list.length) * height :
|
||||
10;
|
||||
|
||||
listContainer.addShape("rect", {
|
||||
attrs: {
|
||||
y: 30 +
|
||||
barStyle.padding +
|
||||
(startIndex / (list.length-8)) * (height - 30),
|
||||
x: width - barStyle.padding - barStyle.width,
|
||||
width: barStyle.width,
|
||||
height: Math.min(height, indexHeight),
|
||||
...barStyle.innerStyle,
|
||||
},
|
||||
});
|
||||
}
|
||||
//渲染显示区域
|
||||
if (afterList) {
|
||||
afterList.forEach((e, i) => {
|
||||
//设置选中的列
|
||||
const isSelected = Math.floor(startIndex) + i === Number(selectedIndex);
|
||||
let {
|
||||
columnName = "", columnType,columnComment
|
||||
} = e;
|
||||
if (columnComment){
|
||||
columnName+= " : " + columnComment
|
||||
}
|
||||
if (columnType) {
|
||||
columnName += " - " + columnType;
|
||||
}
|
||||
const label = columnName.length > 26 ? columnName.slice(0, 24) + "..." : columnName;
|
||||
|
||||
listContainer.addShape("rect", {
|
||||
attrs: {
|
||||
x: 1,
|
||||
y: i * itemHeight - itemHeight / 2 + offsetY,
|
||||
width: width - 4,
|
||||
height: itemHeight,
|
||||
radius: 2,
|
||||
fill:isSelected ? "#ddd" : "#fff",
|
||||
lineWidth: 1,
|
||||
cursor: "pointer",
|
||||
},
|
||||
name: `item-${Math.floor(startIndex) + i}-content`,
|
||||
draggable: true,
|
||||
});
|
||||
|
||||
listContainer.addShape("text", {
|
||||
attrs: {
|
||||
x: 12,
|
||||
y: i * itemHeight + offsetY + 6,
|
||||
text: label,
|
||||
fontSize: 12,
|
||||
fill: "#000",
|
||||
fontFamily: "Avenir,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol",
|
||||
full: e,
|
||||
fontWeight: isSelected ? 500 : 100,
|
||||
cursor: "pointer",
|
||||
},
|
||||
name: `item-${Math.floor(startIndex) + i}`,
|
||||
});
|
||||
|
||||
//未来设置字段之间有锚点
|
||||
// if (!cfg.hideDot) {
|
||||
// utils.anchor.erDrawLeft(group, label, 0, i * itemHeight + offsetY)
|
||||
// utils.anchor.erDrawLeft(group,label,width,i * itemHeight + offsetY)
|
||||
// listContainer.addShape("marker", {
|
||||
// attrs: {
|
||||
// x: 0,
|
||||
// y: i * itemHeight + offsetY,
|
||||
// r: 3,
|
||||
// stroke: boxStyle.stroke,
|
||||
// fill: "white",
|
||||
// radius: 2,
|
||||
// lineWidth: 1,
|
||||
// cursor: "crosshair",
|
||||
// },
|
||||
//
|
||||
// name: 'marker-shape'
|
||||
// });
|
||||
// listContainer.addShape("marker", {
|
||||
// attrs: {
|
||||
// x: width,
|
||||
// y: i * itemHeight + offsetY,
|
||||
// r: 3,
|
||||
// stroke: boxStyle.stroke,
|
||||
// fill: "white",
|
||||
// radius: 2,
|
||||
// lineWidth: 1,
|
||||
// cursor: "crosshair",
|
||||
//
|
||||
//
|
||||
// },
|
||||
// name: 'marker-shape'
|
||||
// });
|
||||
// }
|
||||
});
|
||||
}
|
||||
return keyshape;
|
||||
},
|
||||
// getAnchorPoints() {
|
||||
// return [
|
||||
// [0, 0],
|
||||
// [1, 0],
|
||||
// ];
|
||||
// },
|
||||
// 绘制后附加锚点
|
||||
afterDraw(cfg, group) {
|
||||
// 绘制锚点
|
||||
utils.anchor.drawMark(cfg, group)
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
20
src/views/custom-query/topo/top/node/index.js
Normal file
20
src/views/custom-query/topo/top/node/index.js
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* @author: clay
|
||||
* @data: 2019/07/05
|
||||
* @description: register nodes
|
||||
*/
|
||||
|
||||
|
||||
import diceErBox from './dice-er-box'
|
||||
const obj = {
|
||||
diceErBox
|
||||
}
|
||||
|
||||
export default {
|
||||
obj,
|
||||
register(G6) {
|
||||
Object.values(obj).map(item => {
|
||||
G6.registerNode(item.name, item.options, item.extendName)
|
||||
})
|
||||
}
|
||||
}
|
||||
177
src/views/custom-query/topo/top/theme/dark-style.js
Normal file
177
src/views/custom-query/topo/top/theme/dark-style.js
Normal file
@@ -0,0 +1,177 @@
|
||||
/**
|
||||
* @author: clay
|
||||
* @data: 2019/11/20
|
||||
* @description: dark style
|
||||
*/
|
||||
|
||||
export default {
|
||||
// 节点样式
|
||||
nodeStyle: {
|
||||
default: {
|
||||
stroke: '#CED4D9',
|
||||
fill: 'transparent',
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 4,
|
||||
shadowBlur: 10,
|
||||
shadowColor: 'rgba(13, 26, 38, 0.08)',
|
||||
lineWidth: 1,
|
||||
radius: 4,
|
||||
strokeOpacity: 0.7
|
||||
},
|
||||
selected: {
|
||||
shadowColor: '#ff240b',
|
||||
shadowBlur: 4,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 0
|
||||
// shadowColor: '#626262',
|
||||
// shadowBlur: 8,
|
||||
// shadowOffsetX: -1,
|
||||
// shadowOffsetY: 3
|
||||
},
|
||||
unselected: {
|
||||
shadowColor: ''
|
||||
}
|
||||
},
|
||||
//todo 节点标签样式??修改不起
|
||||
nodeLabelCfg: {
|
||||
positions: 'center',
|
||||
style: {
|
||||
fill: '#fff'
|
||||
}
|
||||
},
|
||||
// 连线样式
|
||||
edgeStyle: {
|
||||
default: {
|
||||
stroke: '#000000',
|
||||
lineWidth: 2,
|
||||
strokeOpacity: 0.92,
|
||||
lineAppendWidth: 10
|
||||
// endArrow: true
|
||||
},
|
||||
active: {
|
||||
shadowColor: 'red',
|
||||
shadowBlur: 4,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 0
|
||||
},
|
||||
inactive: {
|
||||
shadowColor: ''
|
||||
},
|
||||
selected: {
|
||||
shadowColor: '#ff240b',
|
||||
shadowBlur: 4,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 0
|
||||
},
|
||||
unselected: {
|
||||
shadowColor: ''
|
||||
}
|
||||
},
|
||||
// 锚点样式
|
||||
anchorStyle: {
|
||||
default: {
|
||||
radius: 3,
|
||||
symbol: 'circle',
|
||||
fill: '#FFFFFF',
|
||||
fillOpacity: 0,
|
||||
stroke: '#1890FF',
|
||||
strokeOpacity: 0,
|
||||
cursor: 'crosshair'
|
||||
},
|
||||
hover: {
|
||||
fillOpacity: 1,
|
||||
strokeOpacity: 1
|
||||
},
|
||||
unhover: {
|
||||
fillOpacity: 0,
|
||||
strokeOpacity: 0
|
||||
},
|
||||
active: {
|
||||
fillOpacity: 1,
|
||||
strokeOpacity: 1
|
||||
},
|
||||
inactive: {
|
||||
fillOpacity: 0,
|
||||
strokeOpacity: 0
|
||||
}
|
||||
},
|
||||
// 锚点背景样式
|
||||
anchorBgStyle: {
|
||||
default: {
|
||||
radius: 10,
|
||||
symbol: 'circle',
|
||||
fill: '#1890FF',
|
||||
fillOpacity: 0,
|
||||
stroke: '#1890FF',
|
||||
strokeOpacity: 0,
|
||||
cursor: 'crosshair'
|
||||
},
|
||||
hover: {
|
||||
fillOpacity: 1,
|
||||
strokeOpacity: 1
|
||||
},
|
||||
unhover: {
|
||||
fillOpacity: 0,
|
||||
strokeOpacity: 0
|
||||
},
|
||||
active: {
|
||||
fillOpacity: 0.3,
|
||||
strokeOpacity: 0.5
|
||||
},
|
||||
inactive: {
|
||||
fillOpacity: 0,
|
||||
strokeOpacity: 0
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
nodeActivedOutterStyle: { lineWidth: 0 },
|
||||
groupSelectedOutterStyle: { stroke: '#E0F0FF', lineWidth: 2 },
|
||||
nodeSelectedOutterStyle: { stroke: '#E0F0FF', lineWidth: 2 },
|
||||
edgeActivedStyle: { stroke: '#1890FF', strokeOpacity: .92 },
|
||||
nodeActivedStyle: { fill: '#F3F9FF', stroke: '#1890FF' },
|
||||
groupActivedStyle: { stroke: '#1890FF' },
|
||||
edgeSelectedStyle: { lineWidth: 2, strokeOpacity: .92, stroke: '#A3B1BF' },
|
||||
nodeSelectedStyle: { fill: '#F3F9FF', stroke: '#1890FF', fillOpacity: .4 },
|
||||
groupSelectedStyle: { stroke: '#1890FF', fillOpacity: .92 },
|
||||
|
||||
groupBackgroundPadding: [40, 10, 10, 10],
|
||||
groupLabelOffsetX: 10,
|
||||
groupLabelOffsetY: 10,
|
||||
edgeLabelStyle: { fill: '#666', textAlign: 'center', textBaseline: 'middle' },
|
||||
edgeLabelRectPadding: 4,
|
||||
edgeLabelRectStyle: { fill: 'white' },
|
||||
nodeLabelStyle: { fill: '#666', textAlign: 'center', textBaseline: 'middle' },
|
||||
groupStyle: { stroke: '#CED4D9', radius: 4 },
|
||||
groupLabelStyle: { fill: '#666', textAlign: 'left', textBaseline: 'top' },
|
||||
multiSelectRectStyle: { fill: '#1890FF', fillOpacity: .08, stroke: '#1890FF', opacity: .1 },
|
||||
dragNodeHoverToGroupStyle: { stroke: '#1890FF', lineWidth: 2 },
|
||||
dragNodeLeaveFromGroupStyle: { stroke: '#BAE7FF', lineWidth: 2 },
|
||||
anchorPointStyle: { radius: 3.5, fill: '#fff', stroke: '#1890FF', lineAppendWidth: 12 },
|
||||
anchorHotsoptStyle: { radius: 12, fill: '#1890FF', fillOpacity: .25 },
|
||||
anchorHotsoptActivedStyle: { radius: 14 },
|
||||
anchorPointHoverStyle: { radius: 4, fill: '#1890FF', fillOpacity: 1, stroke: '#1890FF' },
|
||||
nodeControlPointStyle: { radius: 4, fill: '#fff', shadowBlur: 4, shadowColor: '#666' },
|
||||
edgeControlPointStyle: { radius: 6, symbol: 'square', lineAppendWidth: 6, fillOpacity: 0, strokeOpacity: 0 },
|
||||
nodeSelectedBoxStyle: { stroke: '#C2C2C2' },
|
||||
cursor: {
|
||||
panningCanvas: '-webkit-grabbing',
|
||||
beforePanCanvas: '-webkit-grab',
|
||||
hoverNode: 'move',
|
||||
hoverEffectiveAnchor: 'crosshair',
|
||||
hoverEdge: 'default',
|
||||
hoverGroup: 'move',
|
||||
hoverUnEffectiveAnchor: 'default',
|
||||
hoverEdgeControllPoint: 'crosshair',
|
||||
multiSelect: 'crosshair'
|
||||
},
|
||||
nodeDelegationStyle: {
|
||||
stroke: '#1890FF',
|
||||
fill: '#1890FF',
|
||||
fillOpacity: .08,
|
||||
lineDash: [4, 4],
|
||||
radius: 4,
|
||||
lineWidth: 1
|
||||
},
|
||||
edgeDelegationStyle: { stroke: '#1890FF', lineDash: [4, 4], lineWidth: 1 }
|
||||
}
|
||||
179
src/views/custom-query/topo/top/theme/default-style.js
Normal file
179
src/views/custom-query/topo/top/theme/default-style.js
Normal file
@@ -0,0 +1,179 @@
|
||||
/**
|
||||
* @author: clay
|
||||
* @data: 2019/08/15
|
||||
* @description: default style
|
||||
*/
|
||||
|
||||
export default {
|
||||
// 节点样式
|
||||
nodeStyle: {
|
||||
default: {
|
||||
stroke: '#CED4D9',
|
||||
fill: 'transparent',
|
||||
// shadowOffsetX: 0,
|
||||
// shadowOffsetY: 4,
|
||||
shadowBlur: 10,
|
||||
shadowColor: 'rgba(13, 26, 38, 0.08)',
|
||||
lineWidth: 1,
|
||||
radius: 4,
|
||||
strokeOpacity: 0.7
|
||||
},
|
||||
selected: {
|
||||
shadowColor: '#ff240b',
|
||||
shadowBlur: 2,
|
||||
// shadowOffsetX: 0,
|
||||
// shadowOffsetY: 0,
|
||||
// fontSize:'50'
|
||||
// shadowColor: '#626262',
|
||||
// shadowBlur: 8,
|
||||
// shadowOffsetX: -1,
|
||||
// shadowOffsetY: 3
|
||||
},
|
||||
unselected: {
|
||||
shadowColor: ''
|
||||
}
|
||||
},
|
||||
// 节点标签样式
|
||||
nodeLabelCfg: {
|
||||
positions: 'center',
|
||||
style: {
|
||||
fill: '#000'
|
||||
}
|
||||
},
|
||||
// 连线样式
|
||||
edgeStyle: {
|
||||
default: {
|
||||
stroke: '#A3B1BF',
|
||||
lineWidth: 2,
|
||||
strokeOpacity: 0.92,
|
||||
lineAppendWidth: 10
|
||||
// endArrow: true
|
||||
},
|
||||
active: {
|
||||
shadowColor: 'red',
|
||||
shadowBlur: 4,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 0
|
||||
},
|
||||
inactive: {
|
||||
shadowColor: ''
|
||||
},
|
||||
selected: {
|
||||
shadowColor: '#ff240b',
|
||||
shadowBlur: 4,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 0
|
||||
},
|
||||
unselected: {
|
||||
shadowColor: ''
|
||||
}
|
||||
},
|
||||
// 锚点样式
|
||||
anchorStyle: {
|
||||
default: {
|
||||
r: 3,
|
||||
symbol: 'circle',
|
||||
lineWidth: 1,
|
||||
fill: '#FFFFFF',
|
||||
fillOpacity: 1,
|
||||
stroke: '#096DD9',
|
||||
strokeOpacity: 1,
|
||||
cursor: 'crosshair'
|
||||
},
|
||||
hover: {
|
||||
fillOpacity: 0.3,
|
||||
strokeOpacity: 0.5
|
||||
},
|
||||
unhover: {
|
||||
fillOpacity: 0,
|
||||
strokeOpacity: 0
|
||||
},
|
||||
active: {
|
||||
fillOpacity: 1,
|
||||
strokeOpacity: 1
|
||||
},
|
||||
inactive: {
|
||||
fillOpacity: 0,
|
||||
strokeOpacity: 0
|
||||
}
|
||||
},
|
||||
// 锚点背景样式
|
||||
anchorBgStyle: {
|
||||
default: {
|
||||
r: 10,
|
||||
symbol: 'circle',
|
||||
fill: '#1890FF',
|
||||
fillOpacity: 0,
|
||||
stroke: '#1890FF',
|
||||
strokeOpacity: 0,
|
||||
cursor: 'crosshair'
|
||||
},
|
||||
hover: {
|
||||
fillOpacity: 1,
|
||||
strokeOpacity: 1
|
||||
},
|
||||
unhover: {
|
||||
fillOpacity: 0,
|
||||
strokeOpacity: 0
|
||||
},
|
||||
active: {
|
||||
fillOpacity: 0.3,
|
||||
strokeOpacity: 0.5
|
||||
},
|
||||
inactive: {
|
||||
fillOpacity: 0,
|
||||
strokeOpacity: 0
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
nodeActivedOutterStyle: { lineWidth: 0 },
|
||||
groupSelectedOutterStyle: { stroke: '#E0F0FF', lineWidth: 2 },
|
||||
nodeSelectedOutterStyle: { stroke: '#E0F0FF', lineWidth: 2 },
|
||||
edgeActivedStyle: { stroke: '#1890FF', strokeOpacity: .92 },
|
||||
nodeActivedStyle: { fill: '#F3F9FF', stroke: '#1890FF' },
|
||||
groupActivedStyle: { stroke: '#1890FF' },
|
||||
edgeSelectedStyle: { lineWidth: 2, strokeOpacity: .92, stroke: '#A3B1BF' },
|
||||
nodeSelectedStyle: { fill: '#F3F9FF', stroke: '#1890FF', fillOpacity: .4 },
|
||||
groupSelectedStyle: { stroke: '#1890FF', fillOpacity: .92 },
|
||||
|
||||
groupBackgroundPadding: [40, 10, 10, 10],
|
||||
groupLabelOffsetX: 10,
|
||||
groupLabelOffsetY: 10,
|
||||
edgeLabelStyle: { fill: '#666', textAlign: 'center', textBaseline: 'middle' },
|
||||
edgeLabelRectPadding: 4,
|
||||
edgeLabelRectStyle: { fill: 'white' },
|
||||
nodeLabelStyle: { fill: '#666', textAlign: 'center', textBaseline: 'middle' },
|
||||
groupStyle: { stroke: '#CED4D9', radius: 4 },
|
||||
groupLabelStyle: { fill: '#666', textAlign: 'left', textBaseline: 'top' },
|
||||
multiSelectRectStyle: { fill: '#1890FF', fillOpacity: .08, stroke: '#1890FF', opacity: .1 },
|
||||
dragNodeHoverToGroupStyle: { stroke: '#1890FF', lineWidth: 2 },
|
||||
dragNodeLeaveFromGroupStyle: { stroke: '#BAE7FF', lineWidth: 2 },
|
||||
anchorPointStyle: { radius: 3.5, fill: '#fff', stroke: '#1890FF', lineAppendWidth: 12 },
|
||||
anchorHotsoptStyle: { radius: 12, fill: '#1890FF', fillOpacity: .25 },
|
||||
anchorHotsoptActivedStyle: { radius: 14 },
|
||||
anchorPointHoverStyle: { radius: 4, fill: '#1890FF', fillOpacity: 1, stroke: '#1890FF' },
|
||||
nodeControlPointStyle: { radius: 4, fill: '#fff', shadowBlur: 4, shadowColor: '#666' },
|
||||
edgeControlPointStyle: { radius: 6, symbol: 'square', lineAppendWidth: 6, fillOpacity: 0, strokeOpacity: 0 },
|
||||
nodeSelectedBoxStyle: { stroke: '#C2C2C2' },
|
||||
cursor: {
|
||||
panningCanvas: '-webkit-grabbing',
|
||||
beforePanCanvas: '-webkit-grab',
|
||||
hoverNode: 'move',
|
||||
hoverEffectiveAnchor: 'crosshair',
|
||||
hoverEdge: 'default',
|
||||
hoverGroup: 'move',
|
||||
hoverUnEffectiveAnchor: 'default',
|
||||
hoverEdgeControllPoint: 'crosshair',
|
||||
multiSelect: 'crosshair'
|
||||
},
|
||||
nodeDelegationStyle: {
|
||||
stroke: '#1890FF',
|
||||
fill: '#1890FF',
|
||||
fillOpacity: .08,
|
||||
lineDash: [4, 4],
|
||||
radius: 4,
|
||||
lineWidth: 1
|
||||
},
|
||||
edgeDelegationStyle: { stroke: '#1890FF', lineDash: [4, 4], lineWidth: 1 }
|
||||
}
|
||||
15
src/views/custom-query/topo/top/theme/index.js
Normal file
15
src/views/custom-query/topo/top/theme/index.js
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* @author: clay
|
||||
* @data: 2019/08/15
|
||||
* @description: 编辑器主题样式 - 节点、连线的预设样式
|
||||
*/
|
||||
|
||||
import defaultStyle from './default-style'
|
||||
import darkStyle from './dark-style'
|
||||
import officeStyle from './office-style'
|
||||
|
||||
export default {
|
||||
defaultStyle,
|
||||
darkStyle,
|
||||
officeStyle
|
||||
}
|
||||
177
src/views/custom-query/topo/top/theme/office-style.js
Normal file
177
src/views/custom-query/topo/top/theme/office-style.js
Normal file
@@ -0,0 +1,177 @@
|
||||
/**
|
||||
* @author: clay
|
||||
* @data: 2019/11/21
|
||||
* @description: office style
|
||||
*/
|
||||
|
||||
export default {
|
||||
// 节点样式
|
||||
nodeStyle: {
|
||||
default: {
|
||||
stroke: '#CED4D9',
|
||||
fill: '#FFFFFF',
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 4,
|
||||
shadowBlur: 10,
|
||||
shadowColor: 'rgba(13, 26, 38, 0.08)',
|
||||
lineWidth: 1,
|
||||
radius: 4,
|
||||
strokeOpacity: 0.7
|
||||
},
|
||||
selected: {
|
||||
shadowColor: '#ff240b',
|
||||
shadowBlur: 4,
|
||||
shadowOffsetX: 1,
|
||||
shadowOffsetY: 1
|
||||
// shadowColor: '#626262',
|
||||
// shadowBlur: 8,
|
||||
// shadowOffsetX: -1,
|
||||
// shadowOffsetY: 3
|
||||
},
|
||||
unselected: {
|
||||
shadowColor: ''
|
||||
}
|
||||
},
|
||||
// 节点标签样式
|
||||
nodeLabelCfg: {
|
||||
positions: 'center',
|
||||
style: {
|
||||
fill: '#000'
|
||||
}
|
||||
},
|
||||
// 连线样式
|
||||
edgeStyle: {
|
||||
default: {
|
||||
stroke: '#41c23a',
|
||||
lineWidth: 2,
|
||||
strokeOpacity: 0.92,
|
||||
lineAppendWidth: 10
|
||||
// endArrow: true
|
||||
},
|
||||
active: {
|
||||
shadowColor: 'red',
|
||||
shadowBlur: 4,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 0
|
||||
},
|
||||
inactive: {
|
||||
shadowColor: ''
|
||||
},
|
||||
selected: {
|
||||
shadowColor: '#ff240b',
|
||||
shadowBlur: 4,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 0
|
||||
},
|
||||
unselected: {
|
||||
shadowColor: ''
|
||||
}
|
||||
},
|
||||
// 锚点样式
|
||||
anchorStyle: {
|
||||
default: {
|
||||
radius: 3,
|
||||
symbol: 'circle',
|
||||
fill: '#FFFFFF',
|
||||
fillOpacity: 0,
|
||||
stroke: '#1890FF',
|
||||
strokeOpacity: 0,
|
||||
cursor: 'crosshair'
|
||||
},
|
||||
hover: {
|
||||
fillOpacity: 1,
|
||||
strokeOpacity: 1
|
||||
},
|
||||
unhover: {
|
||||
fillOpacity: 0,
|
||||
strokeOpacity: 0
|
||||
},
|
||||
active: {
|
||||
fillOpacity: 1,
|
||||
strokeOpacity: 1
|
||||
},
|
||||
inactive: {
|
||||
fillOpacity: 0,
|
||||
strokeOpacity: 0
|
||||
}
|
||||
},
|
||||
// 锚点背景样式
|
||||
anchorBgStyle: {
|
||||
default: {
|
||||
radius: 10,
|
||||
symbol: 'circle',
|
||||
fill: '#1890FF',
|
||||
fillOpacity: 0,
|
||||
stroke: '#1890FF',
|
||||
strokeOpacity: 0,
|
||||
cursor: 'crosshair'
|
||||
},
|
||||
hover: {
|
||||
fillOpacity: 1,
|
||||
strokeOpacity: 1
|
||||
},
|
||||
unhover: {
|
||||
fillOpacity: 0,
|
||||
strokeOpacity: 0
|
||||
},
|
||||
active: {
|
||||
fillOpacity: 0.3,
|
||||
strokeOpacity: 0.5
|
||||
},
|
||||
inactive: {
|
||||
fillOpacity: 0,
|
||||
strokeOpacity: 0
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
nodeActivedOutterStyle: { lineWidth: 0 },
|
||||
groupSelectedOutterStyle: { stroke: '#E0F0FF', lineWidth: 2 },
|
||||
nodeSelectedOutterStyle: { stroke: '#E0F0FF', lineWidth: 2 },
|
||||
edgeActivedStyle: { stroke: '#1890FF', strokeOpacity: .92 },
|
||||
nodeActivedStyle: { fill: '#F3F9FF', stroke: '#1890FF' },
|
||||
groupActivedStyle: { stroke: '#1890FF' },
|
||||
edgeSelectedStyle: { lineWidth: 2, strokeOpacity: .92, stroke: '#A3B1BF' },
|
||||
nodeSelectedStyle: { fill: '#F3F9FF', stroke: '#1890FF', fillOpacity: .4 },
|
||||
groupSelectedStyle: { stroke: '#1890FF', fillOpacity: .92 },
|
||||
|
||||
groupBackgroundPadding: [40, 10, 10, 10],
|
||||
groupLabelOffsetX: 10,
|
||||
groupLabelOffsetY: 10,
|
||||
edgeLabelStyle: { fill: '#666', textAlign: 'center', textBaseline: 'middle' },
|
||||
edgeLabelRectPadding: 4,
|
||||
edgeLabelRectStyle: { fill: 'white' },
|
||||
nodeLabelStyle: { fill: '#666', textAlign: 'center', textBaseline: 'middle' },
|
||||
groupStyle: { stroke: '#CED4D9', radius: 4 },
|
||||
groupLabelStyle: { fill: '#666', textAlign: 'left', textBaseline: 'top' },
|
||||
multiSelectRectStyle: { fill: '#1890FF', fillOpacity: .08, stroke: '#1890FF', opacity: .1 },
|
||||
dragNodeHoverToGroupStyle: { stroke: '#1890FF', lineWidth: 2 },
|
||||
dragNodeLeaveFromGroupStyle: { stroke: '#BAE7FF', lineWidth: 2 },
|
||||
anchorPointStyle: { radius: 3.5, fill: '#fff', stroke: '#1890FF', lineAppendWidth: 12 },
|
||||
anchorHotsoptStyle: { radius: 12, fill: '#1890FF', fillOpacity: .25 },
|
||||
anchorHotsoptActivedStyle: { radius: 14 },
|
||||
anchorPointHoverStyle: { radius: 4, fill: '#1890FF', fillOpacity: 1, stroke: '#1890FF' },
|
||||
nodeControlPointStyle: { radius: 4, fill: '#fff', shadowBlur: 4, shadowColor: '#666' },
|
||||
edgeControlPointStyle: { radius: 6, symbol: 'square', lineAppendWidth: 6, fillOpacity: 0, strokeOpacity: 0 },
|
||||
nodeSelectedBoxStyle: { stroke: '#C2C2C2' },
|
||||
cursor: {
|
||||
panningCanvas: '-webkit-grabbing',
|
||||
beforePanCanvas: '-webkit-grab',
|
||||
hoverNode: 'move',
|
||||
hoverEffectiveAnchor: 'crosshair',
|
||||
hoverEdge: 'default',
|
||||
hoverGroup: 'move',
|
||||
hoverUnEffectiveAnchor: 'default',
|
||||
hoverEdgeControllPoint: 'crosshair',
|
||||
multiSelect: 'crosshair'
|
||||
},
|
||||
nodeDelegationStyle: {
|
||||
stroke: '#1890FF',
|
||||
fill: '#1890FF',
|
||||
fillOpacity: .08,
|
||||
lineDash: [4, 4],
|
||||
radius: 4,
|
||||
lineWidth: 1
|
||||
},
|
||||
edgeDelegationStyle: { stroke: '#1890FF', lineDash: [4, 4], lineWidth: 1 }
|
||||
}
|
||||
218
src/views/custom-query/topo/top/toolbar-edit.vue
Normal file
218
src/views/custom-query/topo/top/toolbar-edit.vue
Normal file
@@ -0,0 +1,218 @@
|
||||
<template>
|
||||
<div class="toolbar">
|
||||
<div class="left">
|
||||
<!-- <el-checkbox class="edge-enabled" title="连线模式" @change="$parent.enableEdgeHandler"></el-checkbox>-->
|
||||
<!-- <dropdown-->
|
||||
<!-- class="edge-shape"-->
|
||||
<!-- :dropdown-items="edgeShapeList"-->
|
||||
<!-- @change="changeEdgeShapeHandler"-->
|
||||
<!-- >-->
|
||||
<!-- </dropdown>-->
|
||||
<el-button size="mini" @click="router.push('/custom/query/topo')">返回</el-button>
|
||||
</div>
|
||||
<div class="center">
|
||||
<div class="graph-ops">
|
||||
<svg-icon name="undo" title="撤回" :class="disableUndo ? 'disabled':''" @handle="undoHandler"/>
|
||||
<svg-icon name="redo" title="重做" :class="disableRedo ? 'disabled':''" @handle="redoHandler"/>
|
||||
<span class="separator"></span>
|
||||
<svg-icon name="copy" title="复制" :class="disableCopy ? 'disabled':''" @handle="copyHandler"/>
|
||||
<svg-icon name="paste" title="粘贴" :class="disablePaste ? 'disabled':''" @handle="pasteHandler"/>
|
||||
<svg-icon name="clear" title="删除" :class="disableDelete ? 'disabled':''" @handle="deleteHandler"/>
|
||||
<span class="separator"></span>
|
||||
<svg-icon name="zoom-in" title="放大" id="zoom-in" @handle="zoomInHandler"/>
|
||||
<svg-icon name="zoom-out" title="缩小" @handle="zoomOutHandler"/>
|
||||
<svg-icon name="fit" title="适应画布" @handle="autoZoomHandler"/>
|
||||
<svg-icon name="actual_size" title="实际尺寸" @handle="resetZoomHandler"/>
|
||||
<span class="separator"></span>
|
||||
<svg-icon name="selector" title="框选" id="multi-select" @handle="multiSelectHandler"/>
|
||||
|
||||
<!-- <div v-if="graphMode === 'edit'" class="iconfont zx">-->
|
||||
<!-- <dropdown-->
|
||||
<!-- class="edge-shape"-->
|
||||
<!-- :dropdown-items="edgeShapeList"-->
|
||||
<!-- @change="changeEdgeShapeHandler"-->
|
||||
<!-- >-->
|
||||
<!-- </dropdown>-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<el-button size="mini" @click="autoLayout">自动布局</el-button>
|
||||
<!--<el-button size="mini" @click="circularLayoutHandler">环形布局</el-button>-->
|
||||
<!--<el-button size="mini" @click="radialLayoutHandler">辐射</el-button>-->
|
||||
<!--<el-button size="mini" @click="mdsLayoutHandler">MDS</el-button>-->
|
||||
<!--<el-button size="mini" @click="dagreLayoutHandler">层次</el-button>-->
|
||||
<!--<el-button size="mini" @click="autoLayoutHandler">自动(old)</el-button>-->
|
||||
<!-- todo 返回到预览模式 -->
|
||||
<el-button size="mini" @click="handlePreview('preview')">预览</el-button>
|
||||
<el-button size="mini" @click="saveDataHandler">保存</el-button>
|
||||
<el-button size="mini" @click="handleTopLine">上线</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import SvgIcon from '@/components/svgIcon/index.vue'
|
||||
import Dropdown from './elements/dropdown.vue'
|
||||
import {defineProps} from "vue";
|
||||
import {useRouter} from "vue-router";
|
||||
const router = useRouter()
|
||||
|
||||
const emit = defineEmits(["autoLayout", "handlePreview", "saveDataHandler",
|
||||
"undoHandler", "redoHandler", "copyHandler", "pasteHandler", "deleteHandler", "zoomInHandler",
|
||||
"zoomOutHandler", "autoZoomHandler", "resetZoomHandler", "multiSelectHandler"]);
|
||||
const props = defineProps({
|
||||
disableUndo: {
|
||||
type: String, //参数类型
|
||||
default: String, //默认值
|
||||
},
|
||||
disableRedo: {
|
||||
type: String, //参数类型
|
||||
default: String, //默认值
|
||||
},
|
||||
disableCopy: {
|
||||
type: String, //参数类型
|
||||
default: String, //默认值
|
||||
},
|
||||
disablePaste: {
|
||||
type: String, //参数类型
|
||||
default: String, //默认值
|
||||
},
|
||||
disableDelete: {
|
||||
type: String, //参数类型
|
||||
default: String, //默认值
|
||||
},
|
||||
graphMode: {
|
||||
type: String, //参数类型
|
||||
default: String, //默认值
|
||||
},
|
||||
edgeShapeList: {
|
||||
type: Array, //参数类型
|
||||
default: [], //默认值
|
||||
}
|
||||
})
|
||||
const autoLayout = () => {
|
||||
emit('autoLayout');
|
||||
}
|
||||
const handlePreview = (graphMode) => {
|
||||
emit('handlePreview', graphMode);
|
||||
}
|
||||
const saveDataHandler = () => {
|
||||
emit('saveDataHandler');
|
||||
}
|
||||
const handleTopLine = () => {
|
||||
emit('handleTopLine');
|
||||
}
|
||||
const undoHandler = () => {
|
||||
emit('undoHandler');
|
||||
}
|
||||
const redoHandler = () => {
|
||||
emit('redoHandler');
|
||||
}
|
||||
const copyHandler = () => {
|
||||
emit('copyHandler');
|
||||
}
|
||||
const pasteHandler = () => {
|
||||
emit('pasteHandler');
|
||||
}
|
||||
const deleteHandler = () => {
|
||||
emit('deleteHandler');
|
||||
}
|
||||
const zoomInHandler = () => {
|
||||
emit('zoomInHandler');
|
||||
}
|
||||
const zoomOutHandler = () => {
|
||||
emit('zoomOutHandler');
|
||||
}
|
||||
const autoZoomHandler = () => {
|
||||
emit('autoZoomHandler');
|
||||
}
|
||||
const resetZoomHandler = () => {
|
||||
emit('resetZoomHandler');
|
||||
}
|
||||
const multiSelectHandler = () => {
|
||||
emit('multiSelectHandler');
|
||||
}
|
||||
const changeEdgeShapeHandler = () => {
|
||||
emit('changeEdgeShapeHandler');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.zx {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
padding-right: 10px;
|
||||
color: #333;
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
line-height: 42px;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #E9E9E9;
|
||||
box-shadow: 0 8px 12px 0 rgba(0, 52, 107, 0.04);
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
|
||||
.left {
|
||||
display: inline-block;
|
||||
width: 12%;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.center {
|
||||
display: inline-block;
|
||||
width: 58%;
|
||||
}
|
||||
|
||||
.right {
|
||||
display: inline-block;
|
||||
width: 30%;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.edge-enabled {
|
||||
width: 40%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.edge-shape {
|
||||
width: 100%;
|
||||
/*margin-right: 20px;*/
|
||||
line-height: 25px;
|
||||
text-align: center;
|
||||
/*border-right: 1px solid #E6E9ED;*/
|
||||
}
|
||||
|
||||
.graph-ops {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-left: 20px;
|
||||
|
||||
.disabled {
|
||||
color: rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.disabled:hover {
|
||||
cursor: not-allowed;
|
||||
border: 1px solid rgba(2, 2, 2, 0);
|
||||
}
|
||||
|
||||
.icon-select {
|
||||
background-color: #EEEEEE;
|
||||
}
|
||||
|
||||
.separator {
|
||||
margin-right: 5px;
|
||||
border-left: 1px solid #E9E9E9;
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
margin: 0 5px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
183
src/views/custom-query/topo/top/toolbar-preview.vue
Normal file
183
src/views/custom-query/topo/top/toolbar-preview.vue
Normal file
@@ -0,0 +1,183 @@
|
||||
<template>
|
||||
<div class="toolbar">
|
||||
<div class="left">
|
||||
<dropdown
|
||||
class="theme-style"
|
||||
:dropdown-items="themeOptions"
|
||||
@change="toggleThemeChange"
|
||||
>
|
||||
</dropdown>
|
||||
</div>
|
||||
<div class="center">
|
||||
<div class="graph-ops">
|
||||
<svg-icon name="zoom-in" title="放大" id="zoom-in" @handle="zoomInHandler"/>
|
||||
<svg-icon name="zoom-out" title="缩小" @handle="zoomOutHandler"/>
|
||||
<svg-icon name="fit" title="适应画布" @handle="autoZoomHandler"/>
|
||||
<svg-icon name="actual_size" title="实际尺寸" @handle="resetZoomHandler"/>
|
||||
<span class="separator"></span>
|
||||
<el-checkbox @change="enableMinimapHandler">导航器</el-checkbox>
|
||||
<dropdown :dropdown-items="refreshOptions" @change="toggleAutoRefresh"></dropdown>
|
||||
</div>
|
||||
<div class="right">
|
||||
<el-button size="mini" @click="manualRefreshHandler">刷新</el-button>
|
||||
<el-button size="mini" @click="handleEdit('edit')">编辑</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import SvgIcon from '@/components/svgIcon/index.vue'
|
||||
import Dropdown from './elements/dropdown.vue'
|
||||
import {ref} from "vue";
|
||||
const emit = defineEmits([ "handleEdit","zoomInHandler",
|
||||
"zoomOutHandler","autoZoomHandler","resetZoomHandler","enableMinimapHandler","autoRefreshHandler","manualRefreshHandler"]);
|
||||
|
||||
const themeOptions = ref([
|
||||
{value: 'defaultStyle', label: '默认主题'},
|
||||
{value: 'darkStyle', label: '暗黑风格'},
|
||||
{value: 'officeStyle', label: '办公风格'}
|
||||
])
|
||||
const refreshOptions = ref([
|
||||
{value: -1, label: '不自动刷新'},
|
||||
{value: 10000, label: '10秒自动刷新'},
|
||||
{value: 30000, label: '30秒自动刷新'},
|
||||
{value: 60000, label: '60秒自动刷新'}
|
||||
])
|
||||
|
||||
const toggleThemeChange = (clickItem) => {
|
||||
emit('changeGraphTheme',clickItem);
|
||||
}
|
||||
|
||||
const changeThemeActiveIndex = (index) => {
|
||||
// 给父组件调用(代码触发主题修改)
|
||||
themeOptions.value.activeIndex = index
|
||||
}
|
||||
const zoomInHandler = () => {
|
||||
emit('zoomInHandler');
|
||||
}
|
||||
const zoomOutHandler = () => {
|
||||
emit('zoomOutHandler');
|
||||
}
|
||||
const autoZoomHandler = () => {
|
||||
emit('autoZoomHandler');
|
||||
}
|
||||
const resetZoomHandler = () => {
|
||||
emit('resetZoomHandler');
|
||||
}
|
||||
const enableMinimapHandler = (e) => {
|
||||
emit('enableMinimapHandler',e);
|
||||
}
|
||||
const toggleAutoRefresh = (activeIndex) => {
|
||||
emit('autoRefreshHandler',activeIndex);
|
||||
}
|
||||
const manualRefreshHandler = () => {
|
||||
emit('manualRefreshHandler');
|
||||
}
|
||||
const handleEdit = (graphMode) => {
|
||||
emit('handleEdit', graphMode);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.toolbar {
|
||||
padding-right: 10px;
|
||||
color: #333;
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
line-height: 42px;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #E9E9E9;
|
||||
box-shadow: 0 8px 12px 0 rgba(0, 52, 107, 0.04);
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
|
||||
.left {
|
||||
display: inline-block;
|
||||
width: 12%;
|
||||
}
|
||||
|
||||
.center {
|
||||
display: inline-block;
|
||||
width: 58%;
|
||||
}
|
||||
|
||||
.right {
|
||||
display: inline-block;
|
||||
width: 30%;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.edge-enabled {
|
||||
width: 40%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.edge-type {
|
||||
width: 60%;
|
||||
line-height: 25px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.theme-style {
|
||||
width: 100%;
|
||||
line-height: 25px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.graph-ops {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-left: 20px;
|
||||
|
||||
i {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin: 0 6px;
|
||||
line-height: 20px;
|
||||
color: #a8a8a8;
|
||||
text-align: center;
|
||||
border-radius: 2px;
|
||||
display: inline-block;
|
||||
border: 1px solid rgba(2, 2, 2, 0);
|
||||
}
|
||||
|
||||
i:hover {
|
||||
cursor: pointer;
|
||||
border: 1px solid #E9E9E9;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
color: rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.disabled:hover {
|
||||
cursor: not-allowed;
|
||||
border: 1px solid rgba(2, 2, 2, 0);
|
||||
}
|
||||
|
||||
.icon-select {
|
||||
background-color: #EEEEEE;
|
||||
}
|
||||
|
||||
.separator {
|
||||
margin: 4px;
|
||||
border-left: 1px solid #E9E9E9;
|
||||
}
|
||||
|
||||
.auto-refresh {
|
||||
margin: 0 8px 0 12px;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
margin: 0 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
margin: 0 5px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
1636
src/views/custom-query/topo/top/topology.vue
Normal file
1636
src/views/custom-query/topo/top/topology.vue
Normal file
File diff suppressed because one or more lines are too long
279
src/views/custom-query/topo/topologyDesign.vue
Normal file
279
src/views/custom-query/topo/topologyDesign.vue
Normal file
@@ -0,0 +1,279 @@
|
||||
<template>
|
||||
<topology
|
||||
ref="topologyRef"
|
||||
@handleSave="handleSave"
|
||||
@handlePreview="handlePreview"
|
||||
@handleTopoLine="handleTopoLine"
|
||||
:node-type-list="nodeTypeList"
|
||||
:node-app-config="nodeAppConfig"
|
||||
:edge-app-config="edgeAppConfig"
|
||||
:relational-map="relationalMap"
|
||||
>
|
||||
</topology>
|
||||
<el-dialog title="预览" width="1500px" v-model="previewShow" @close="closePreview">
|
||||
<el-form inline class="query-form" ref="queryForm">
|
||||
<el-form-item v-for="column in uniCons" :key="column.ucId"
|
||||
:label="column.ucName">
|
||||
<el-input :placeholder="column.ucDescribe" :type="column.ucType" v-model="column.query" clearable
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handlePreviewData" :icon="Search">搜索</el-button>
|
||||
<el-button @click="handleReset" :icon="Refresh">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="table">
|
||||
<el-table :data="previewTable" v-loading="previewLoading">
|
||||
<el-table-column v-for="column in uniColumns" :key="column.prop" :prop="column.prop"
|
||||
:label="column.label" align="center"/>
|
||||
</el-table>
|
||||
</div>
|
||||
<paging :current-page="pageInfo.pageNum" :page-size="pageInfo.pageSize" :page-sizes="[10, 20, 30, 40,50]"
|
||||
:total="total" @changeSize="handleSizeChange" @goPage="handleCurrentChange"/>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="isTopLineVisited" title="上线" width="700px">
|
||||
<el-form :model="form" :rules="formRules" class="query-form" ref="formInstance">
|
||||
<el-form-item label="上级菜单">
|
||||
<el-tree-select v-model="form.parentId" :data="menuOpt" style="width: 100%;"
|
||||
:filter-node-method="filterNodeMethod" filterable :check-strictly="true"/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span>
|
||||
<el-button @click="handleCancel">取消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit(formInstance)">
|
||||
确定
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Topology from "@/views/custom-query/topo/top/topology.vue"
|
||||
import {getTopoDragInfo, saveTopo, previewTopo, previewTopologyData, topoToLine} from "@/api/custom-query/topo-search"
|
||||
import {reactive} from "vue";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {Search, Refresh} from '@element-plus/icons-vue'
|
||||
import {getMenuOpt} from '@/api/system/menuman.js'
|
||||
import Paging from "@/components/pagination/index.vue";
|
||||
|
||||
const router = useRouter()
|
||||
const params = reactive(router.currentRoute.value.params)
|
||||
const isTopLineVisited = ref(false)
|
||||
const form = ref({
|
||||
parentId: ''
|
||||
})
|
||||
const formRules = ref({
|
||||
parentId: [{required: true, message: '请选择上级菜单', trigger: 'blur'}]
|
||||
})
|
||||
const formInstance = ref()
|
||||
const menuOpt = ref([])
|
||||
const topologyRef = ref()
|
||||
const pageInfo = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
})
|
||||
const jsonData = ref()
|
||||
const queryForm = ref([])
|
||||
const queryValue = ref(null)
|
||||
const total = ref(0)
|
||||
const previewLoading = ref(true)
|
||||
const nodeTypeList = ref([])
|
||||
const relationalMap = ref(new Map())
|
||||
const graphDefaultData = ref([])
|
||||
const previewShow = ref(false)
|
||||
const previewTable = ref({})
|
||||
const uniColumns = ref({})
|
||||
const uniCons = ref({})
|
||||
const ucId = ref(null)
|
||||
|
||||
const nodeAppConfig = reactive({
|
||||
// ip: '节点IP',
|
||||
// port: '节点端口',
|
||||
// sysName: '设备名称'
|
||||
})
|
||||
const edgeAppConfig = reactive({
|
||||
associated: "查询方式",
|
||||
sourceColumn: "主表字段",
|
||||
targetColumn: "关联字段"
|
||||
})
|
||||
|
||||
const closePreview = () => {
|
||||
previewShow.value = false
|
||||
}
|
||||
const handleSave = (json) => {
|
||||
// if(JSON.parse(json).edges.length!==JSON.parse(json).nodes.length-1){
|
||||
// ElMessage.warning('图与图之间尚未构成联系, 请检查后保存')
|
||||
// }else {
|
||||
saveTopo({
|
||||
queryId: params.queryId,
|
||||
topJson: json
|
||||
}).then(res => {
|
||||
if (res.code === 1000) {
|
||||
ElMessage.success(res.msg)
|
||||
} else {
|
||||
ElMessage.error(res.msg)
|
||||
}
|
||||
})
|
||||
// }
|
||||
}
|
||||
//点击上线按钮弹框
|
||||
const handleTopoLine = (json) => {
|
||||
getMenuOpt().then(res => {
|
||||
menuOpt.value = [{
|
||||
value: 0,
|
||||
label: "一级目录",
|
||||
children: res.data
|
||||
}]
|
||||
})
|
||||
jsonData.value = json
|
||||
isTopLineVisited.value = true
|
||||
}
|
||||
const filterNodeMethod = (value, data) => data.label.includes(value)
|
||||
|
||||
const restForm = () => {
|
||||
form.value = {
|
||||
parentId: ''
|
||||
}
|
||||
}
|
||||
//取消
|
||||
const handleCancel = () => {
|
||||
restForm();
|
||||
isTopLineVisited.value = false;
|
||||
};
|
||||
const handleSubmit = async (instance) => {
|
||||
if (!instance) return
|
||||
instance.validate(async (valid, fields) => {
|
||||
if (!valid) return
|
||||
topoToLine({
|
||||
menuId: form.value.parentId,
|
||||
queryId: params.queryId,
|
||||
topJson: jsonData.value
|
||||
}).then(res => {
|
||||
if (res.code === 1000) {
|
||||
ElMessage.success(res.msg)
|
||||
router.push('/custom/query/topo')
|
||||
} else {
|
||||
ElMessage.error(res.msg)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const handlePreview = (json) => {
|
||||
previewTopo({
|
||||
queryId: params.queryId,
|
||||
topJson: json,
|
||||
}).then(res => {
|
||||
console.log('预览', res)
|
||||
if (res.code === 1000) {
|
||||
previewShow.value = true
|
||||
previewTable.value = res.data.table.rows
|
||||
uniColumns.value = res.data.uniColumns
|
||||
uniCons.value = res.data.uniCons
|
||||
total.value = res.data.table.total
|
||||
previewLoading.value = false
|
||||
uniCons.value.forEach(item => {
|
||||
ucId.value = item.ucId
|
||||
})
|
||||
} else {
|
||||
ElMessage.error(res.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//重置搜索
|
||||
const handleReset = () => {
|
||||
uniCons.value.forEach(con=>{
|
||||
con.query=''
|
||||
})
|
||||
handlePreviewData()
|
||||
}
|
||||
|
||||
const handlePreviewData = () => {
|
||||
previewLoading.value = true
|
||||
let queryData = []
|
||||
uniCons.value.forEach(con=>{
|
||||
if (con.query){
|
||||
let queryItem = {
|
||||
query:con.query,
|
||||
ucId: con.ucId
|
||||
}
|
||||
queryData.push(queryItem)
|
||||
}
|
||||
})
|
||||
previewTopologyData({
|
||||
list:queryData,
|
||||
queryId: params.queryId
|
||||
}, pageInfo).then(res => {
|
||||
previewTable.value = res.data.rows
|
||||
total.value = res.data.total
|
||||
previewLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const getDragInfo = async () => {
|
||||
let queryId = params.queryId
|
||||
getTopoDragInfo(queryId).then(res => {
|
||||
if (res.code === 1000) {
|
||||
console.log('拖拽数据', res.data)
|
||||
nodeTypeList.value = res.data.tableList
|
||||
// for (let relationalItem of res.data.relational) {
|
||||
// let item = relationalMap.value.get(relationalItem.mainId)
|
||||
// if (null == item) {
|
||||
// item = [];
|
||||
// }
|
||||
// item.push(relationalItem)
|
||||
// relationalMap.value.set(relationalItem.mainId,item)
|
||||
// }
|
||||
console.log('res.data.relational', res.data.relational)
|
||||
if (res.data.relational.length === 0) {
|
||||
relationalMap.value = []
|
||||
} else {
|
||||
relationalMap.value = res.data.relational
|
||||
}
|
||||
if (res.data.topJson === null) {
|
||||
let data = {
|
||||
nodes: [],
|
||||
edges: [],
|
||||
combos: [],
|
||||
groups: []
|
||||
}
|
||||
topologyRef.value.initTopo(data);
|
||||
} else {
|
||||
graphDefaultData.value = JSON.parse(res.data.topJson)
|
||||
topologyRef.value.initTopo(graphDefaultData.value);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
//切换每页显示条数
|
||||
const handleSizeChange = (val) => {
|
||||
pageInfo.pageSize = val
|
||||
handlePreviewData()
|
||||
}
|
||||
|
||||
//点击页码进行分页功能
|
||||
const handleCurrentChange = (val) => {
|
||||
pageInfo.pageNum = val
|
||||
handlePreviewData()
|
||||
}
|
||||
getDragInfo()
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.demo-topology {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
html {
|
||||
overflow-x: hidden;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
56
src/views/custom-query/topo/utils/anchor/draw_mark.js
Normal file
56
src/views/custom-query/topo/utils/anchor/draw_mark.js
Normal file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* @author: clay
|
||||
* @data: 2019/08/15
|
||||
* @description: 画锚点
|
||||
*/
|
||||
|
||||
import theme from '@/views/custom-query/topo/top/theme'
|
||||
export default function(cfg, group) {
|
||||
const themeStyle = theme.defaultStyle // todo...先使用默认主题,后期可能增加其它风格的主体
|
||||
let { anchorPoints, width, height, id } = cfg
|
||||
if (anchorPoints && anchorPoints.length) {
|
||||
for (let i = 0, len = anchorPoints.length; i < len; i++) {
|
||||
let [x, y] = anchorPoints[i]
|
||||
// 计算Marker中心点坐标
|
||||
let anchorX = x * width
|
||||
let anchorY = y * height
|
||||
// 添加锚点背景
|
||||
let anchorBgShape = group.addShape('marker', {
|
||||
id: id + '_anchor_bg_' + i,
|
||||
attrs: {
|
||||
name: 'anchorBg',
|
||||
x: anchorX,
|
||||
y: anchorY,
|
||||
// 锚点默认样式
|
||||
...themeStyle.anchorBgStyle.default
|
||||
},
|
||||
draggable: false,
|
||||
name: 'markerBg-shape'
|
||||
})
|
||||
// 添加锚点Marker形状
|
||||
let anchorShape = group.addShape('marker', {
|
||||
id: id + '_anchor_' + i,
|
||||
attrs: {
|
||||
name: 'anchor',
|
||||
x: anchorX,
|
||||
y: anchorY,
|
||||
// 锚点默认样式
|
||||
...themeStyle.anchorStyle.default
|
||||
},
|
||||
draggable: false,
|
||||
name: 'marker-shape'
|
||||
})
|
||||
|
||||
anchorShape.on('mouseenter', function() {
|
||||
anchorBgShape.attr({
|
||||
...themeStyle.anchorBgStyle.active
|
||||
})
|
||||
})
|
||||
anchorShape.on('mouseleave', function() {
|
||||
anchorBgShape.attr({
|
||||
...themeStyle.anchorBgStyle.inactive
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
13
src/views/custom-query/topo/utils/anchor/index.js
Normal file
13
src/views/custom-query/topo/utils/anchor/index.js
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* @author: clay
|
||||
* @data: 2019/08/15
|
||||
* @description: anchor
|
||||
*/
|
||||
|
||||
import setState from './set-state'
|
||||
import drawMark from './draw_mark'
|
||||
|
||||
export default {
|
||||
setState,
|
||||
drawMark,
|
||||
}
|
||||
26
src/views/custom-query/topo/utils/anchor/set-state.js
Normal file
26
src/views/custom-query/topo/utils/anchor/set-state.js
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* @author: clay
|
||||
* @data: 2019/08/15
|
||||
* @description: set anchor state
|
||||
*/
|
||||
import theme from '@/views/custom-query/topo/top/theme'
|
||||
|
||||
export default function(name, value, item) {
|
||||
|
||||
const themeStyle = theme.defaultStyle // todo...先使用默认主题,后期可能增加其它风格的主体
|
||||
if (name === 'hover') {
|
||||
let group = item.getContainer()
|
||||
let children = group.get('children')
|
||||
for (let i = 0, len = children.length; i < len; i++) {
|
||||
let child = children[i]
|
||||
// 处理锚点状态
|
||||
if (child.attrs.name === 'anchorBg') {
|
||||
if (value) {
|
||||
child.attr(themeStyle.anchorStyle.hover)
|
||||
} else {
|
||||
child.attr(themeStyle.anchorStyle.unhover)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
31
src/views/custom-query/topo/utils/anchor/update.js
Normal file
31
src/views/custom-query/topo/utils/anchor/update.js
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* @author: clay
|
||||
* @data: 2019/08/15
|
||||
* @description: update anchor
|
||||
*/
|
||||
|
||||
export default function(cfg, group) {
|
||||
let { anchorPoints, width, height, id } = cfg
|
||||
if (anchorPoints && anchorPoints.length) {
|
||||
for (let i = 0, len = anchorPoints.length; i < len; i++) {
|
||||
let [x, y] = anchorPoints[i]
|
||||
// 计算Marker中心点坐标
|
||||
let originX = -width / 2
|
||||
let originY = -height / 2
|
||||
let anchorX = x * width + originX
|
||||
let anchorY = y * height + originY
|
||||
// 锚点背景
|
||||
let anchorBgShape = group.findById(id + '_anchor_bg_' + i)
|
||||
// 锚点
|
||||
let anchorShape = group.findById(id + '_anchor_' + i)
|
||||
anchorBgShape.attr({
|
||||
x: anchorX,
|
||||
y: anchorY
|
||||
})
|
||||
anchorShape.attr({
|
||||
x: anchorX,
|
||||
y: anchorY
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
11
src/views/custom-query/topo/utils/collapse/index.js
Normal file
11
src/views/custom-query/topo/utils/collapse/index.js
Normal file
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* @author: clay
|
||||
* @data: 2021/5/11 17:28
|
||||
* @email: clay@hchyun.com
|
||||
* @description: node
|
||||
*/
|
||||
import setState from './set-state'
|
||||
|
||||
export default {
|
||||
setState
|
||||
}
|
||||
32
src/views/custom-query/topo/utils/collapse/set-state.js
Normal file
32
src/views/custom-query/topo/utils/collapse/set-state.js
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* @author: clay
|
||||
* @data: 2021/5/11 17:28
|
||||
* @email: clay@hchyun.com
|
||||
* @description: node 节点收缩和放大
|
||||
*/
|
||||
export default function(e){
|
||||
const {
|
||||
graph
|
||||
} = this;
|
||||
const item = e.item;
|
||||
const shape = e.shape;
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
//收缩
|
||||
if (shape.get("name") === "collapse") {
|
||||
graph.updateItem(item, {
|
||||
collapsed: true,
|
||||
size: [300, 50],
|
||||
});
|
||||
setTimeout(() => graph.layout(), 100);
|
||||
//展开
|
||||
} else if (shape.get("name") === "expand") {
|
||||
graph.updateItem(item, {
|
||||
collapsed: false,
|
||||
size: [300, 500],
|
||||
});
|
||||
setTimeout(() => graph.layout(), 100);
|
||||
}
|
||||
}
|
||||
11
src/views/custom-query/topo/utils/edge/index.js
Normal file
11
src/views/custom-query/topo/utils/edge/index.js
Normal file
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* @author: clay
|
||||
* @data: 2019/08/15
|
||||
* @description: edge
|
||||
*/
|
||||
|
||||
import setState from './set-state'
|
||||
|
||||
export default {
|
||||
setState
|
||||
}
|
||||
20
src/views/custom-query/topo/utils/edge/set-state.js
Normal file
20
src/views/custom-query/topo/utils/edge/set-state.js
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* @author: clay
|
||||
* @data: 2019/08/15
|
||||
* @description: set edge state
|
||||
*/
|
||||
|
||||
import theme from '@/views/custom-query/topo/top/theme'
|
||||
|
||||
export default function(name, value, item) {
|
||||
const group = item.getContainer()
|
||||
const shape = group.get('children')[0] // 顺序根据 draw 时确定
|
||||
const themeStyle = theme.defaultStyle // todo...先使用默认主题,后期可能增加其它风格的主体
|
||||
if (name === 'selected') {
|
||||
if (value) {
|
||||
shape.attr(themeStyle.edgeStyle.selected)
|
||||
} else {
|
||||
shape.attr(themeStyle.edgeStyle.unselected)
|
||||
}
|
||||
}
|
||||
}
|
||||
117
src/views/custom-query/topo/utils/index.js
Normal file
117
src/views/custom-query/topo/utils/index.js
Normal file
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* Created by clay on 2019/10/14
|
||||
* Description: common utils
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is just a simple version of deep copy
|
||||
* Has a lot of edge cases bug
|
||||
* If you want to use a perfect deep copy, use lodash's _.cloneDeep
|
||||
* @param {Object} source
|
||||
* @returns {Object} targetObj
|
||||
*/
|
||||
const deepClone = function(source) {
|
||||
if (!source && typeof source !== 'object') {
|
||||
throw new Error('error arguments: deepClone')
|
||||
}
|
||||
let targetObj = source.constructor === Array ? [] : {}
|
||||
Object.keys(source).forEach(key => {
|
||||
if (source[key] && typeof source[key] === 'object') {
|
||||
targetObj[key] = deepClone(source[key])
|
||||
} else {
|
||||
targetObj[key] = source[key]
|
||||
}
|
||||
})
|
||||
return targetObj
|
||||
}
|
||||
|
||||
/**
|
||||
* Randomly extract one or more elements from an array
|
||||
* If you want to use a perfect solution, use lodash's _.sample or _.sampleSize
|
||||
* @param {Array} arr
|
||||
* @param {number} count
|
||||
* @returns {Array} arr
|
||||
*/
|
||||
const getRandomArrayElements = function(arr, count = 1) {
|
||||
if (count > arr.length) {
|
||||
throw new Error('error arguments: count is greater than length of array')
|
||||
}
|
||||
let shuffled = arr.slice(0), i = arr.length, min = i - count, temp, index
|
||||
while (i-- > min) {
|
||||
index = Math.floor((i + 1) * Math.random())
|
||||
temp = shuffled[index]
|
||||
shuffled[index] = shuffled[i]
|
||||
shuffled[i] = temp
|
||||
}
|
||||
return shuffled.slice(min)
|
||||
}
|
||||
|
||||
/**
|
||||
* @author: clay
|
||||
* @data: 2019/08/15
|
||||
* @description: graph utils
|
||||
*/
|
||||
|
||||
import node from './node'
|
||||
import anchor from './anchor'
|
||||
import edge from './edge'
|
||||
import collapse from './collapse'
|
||||
|
||||
/**
|
||||
* 比较两个对象的内容是否相同(两个对象的键值都相同)
|
||||
* @param obj1
|
||||
* @param obj2
|
||||
* @returns {*}
|
||||
*/
|
||||
const isObjectValueEqual = function(obj1, obj2) {
|
||||
let o1 = obj1 instanceof Object
|
||||
let o2 = obj2 instanceof Object
|
||||
// 不是对象的情况
|
||||
if (!o1 || !o2) {
|
||||
return obj1 === obj2
|
||||
}
|
||||
// 对象的属性(key值)个数不相等
|
||||
if (Object.keys(obj1).length !== Object.keys(obj2).length) {
|
||||
return false
|
||||
}
|
||||
// 判断每个属性(如果属性值也是对象则需要递归)
|
||||
for (let attr in obj1) {
|
||||
let t1 = obj1[attr] instanceof Object
|
||||
let t2 = obj2[attr] instanceof Object
|
||||
if (t1 && t2) {
|
||||
return isObjectValueEqual(obj1[attr], obj2[attr])
|
||||
} else if (obj1[attr] !== obj2[attr]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 生成uuid算法,碰撞率低于1/2^^122
|
||||
* @returns {string}
|
||||
*/
|
||||
const generateUUID = function() {
|
||||
let d = new Date().getTime()
|
||||
// x 是 0-9 或 a-f 范围内的一个32位十六进制数
|
||||
let uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
let r = (d + Math.random() * 16) % 16 | 0
|
||||
d = Math.floor(d / 16)
|
||||
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16)
|
||||
})
|
||||
return uuid
|
||||
}
|
||||
|
||||
export default {
|
||||
node,
|
||||
anchor,
|
||||
edge,
|
||||
collapse,
|
||||
// 通用工具类函数
|
||||
isObjectValueEqual,
|
||||
generateUUID,
|
||||
deepClone,
|
||||
getRandomArrayElements
|
||||
}
|
||||
|
||||
11
src/views/custom-query/topo/utils/node/index.js
Normal file
11
src/views/custom-query/topo/utils/node/index.js
Normal file
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* @author: clay
|
||||
* @data: 2019/08/15
|
||||
* @description: node
|
||||
*/
|
||||
|
||||
import setState from './set-state'
|
||||
|
||||
export default {
|
||||
setState
|
||||
}
|
||||
26
src/views/custom-query/topo/utils/node/set-state.js
Normal file
26
src/views/custom-query/topo/utils/node/set-state.js
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* @author: clay
|
||||
* @data: 2019/08/15
|
||||
* @description: set node state
|
||||
*/
|
||||
|
||||
import theme from '@/views/custom-query/topo/top/theme'
|
||||
|
||||
export default function(name, value, item) {
|
||||
const group = item.getContainer()
|
||||
const themeStyle = theme.defaultStyle // todo...先使用默认主题,后期可能增加其它风格的主体
|
||||
let children = group.get('children')
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
let child = children[i]
|
||||
//判断是否为er图的外围,是则改变样式
|
||||
if (child.attrs.name === 'border') {
|
||||
if (name === 'selected') {
|
||||
if (value) {
|
||||
child.attr(themeStyle.nodeStyle.selected)
|
||||
} else {
|
||||
child.attr(themeStyle.nodeStyle.default)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user