<template>
|
<div>
|
<el-form
|
:model="form"
|
:rules="rules"
|
ref="pedigreeForm"
|
label-position="top"
|
class="strain-form"
|
>
|
<div class="card">
|
<div class="form-items-row">
|
<el-form-item label="传代菌种编号" prop="strainCode" required>
|
<el-input
|
v-model="form.strainCode"
|
placeholder="请输入"
|
class="fixed-width-input"
|
></el-input>
|
</el-form-item>
|
<el-form-item label="传代菌种名称" prop="strainName" required>
|
<el-input
|
v-model="form.strainName"
|
placeholder="请输入"
|
class="fixed-width-input"
|
></el-input>
|
</el-form-item>
|
</div>
|
</div>
|
<div class="card" style="margin-top: 30px">
|
<Table :height="null" :total="0" :tableData="tableData">
|
<el-table-column label="菌株类型" prop="strainSource" />
|
<el-table-column label="来源获得/菌落编号" prop="strainCode" />
|
<el-table-column label="菌种编号" prop="strainName" />
|
<el-table-column label="菌种名称" prop="strainName" />
|
<el-table-column label="入库总数" prop="strainName" />
|
<el-table-column label="保存/废弃" prop="strainName" />
|
<el-table-column label="菌种入库时间" prop="strainName" />
|
<el-table-column label="接种操作人" prop="strainName" />
|
<el-table-column label="操作">
|
<template #default="{ row }">
|
<el-button type="text">确认入库</el-button>
|
</template>
|
</el-table-column>
|
</Table>
|
</div>
|
<div class="chart">
|
<div class="header">
|
<div class="title">菌种传代生产谱系图</div>
|
<div class="option-btn">
|
<el-button type="primary" class="el-icon-plus" @click="addNode">
|
新增</el-button
|
>
|
<el-button type="primary" @click="setGenerationPlan"
|
>设置传代计划数</el-button
|
>
|
<el-button type="primary" @click="showDetail">详情</el-button>
|
</div>
|
</div>
|
|
<div class="strain-flow-chart">
|
<div id="mountNode"></div>
|
</div>
|
<el-button
|
v-if="!$route.query.id"
|
type="primary"
|
@click="handleSubmit"
|
style="width: 150px"
|
>保存</el-button
|
>
|
</div>
|
<div class="end-btn">
|
<!-- <el-button type="primary" @click="handleSubmit">提交</el-button>
|
<el-button @click="handleDraft">存草稿</el-button>
|
<el-button @click="handleCancel">取消</el-button> -->
|
</div>
|
</el-form>
|
|
<AddAncestor ref="addAncestor" @addNodeSign="addNodeSign" />
|
<PlanForm ref="planForm" @addNodeSign="addNodeSign" />
|
<AddSublevelForm ref="addSublevelForm" @addNodeSign="addNodeSign" />
|
<ConfirmStorageDialog
|
name="接种操作人签字"
|
:visible.sync="confirmStorageDialogVisible"
|
@confirm="handleSignatureConfirm"
|
/>
|
<!-- 菌种工程师 -->
|
<ConfirmStorageDialog
|
name="菌种保藏人签字"
|
text="是否确认该项菌种信息入库"
|
:visible.sync="storageVisible"
|
@confirm="handleSignatureConfirm"
|
/>
|
</div>
|
</template>
|
|
<script>
|
import G6 from "@antv/g6";
|
import PlanForm from "./progenitorComponents/PlanForm.vue";
|
import AddAncestor from "./progenitorComponents/AddAncestor.vue";
|
import AddSublevelForm from "./progenitorComponents/AddSublevelForm.vue";
|
import ConfirmStorageDialog from "@/components/confirm-storage-dialog";
|
import { add, getDetail, addProgenitorChild, addProgenitor } from "./service";
|
|
export default {
|
name: "AddPedigree",
|
components: {
|
PlanForm,
|
AddAncestor,
|
AddSublevelForm,
|
ConfirmStorageDialog,
|
},
|
data() {
|
return {
|
signatureVisible: false,
|
form: {
|
strainCode: "",
|
strainName: "",
|
generationType: "2",
|
},
|
rules: {
|
strainCode: [
|
{ required: true, message: "请输入传代菌种编号", trigger: "blur" },
|
],
|
strainName: [
|
{ required: true, message: "请输入传代菌种名称", trigger: "blur" },
|
],
|
},
|
graph: null,
|
nodeCount: 0,
|
selectedNode: null,
|
graphData: {
|
nodes: [],
|
edges: [],
|
},
|
// 弹窗相关数据
|
dialogVisible: false,
|
dialogTitle: "",
|
formLabel: "",
|
inputType: "text",
|
showDiscarded: false,
|
isAddingNode: false,
|
nodeData: {},
|
nodeType: "", //1祖代 2计划数 3母代
|
tableData: [],
|
confirmStorageDialogVisible: false,
|
storageVisible: false,
|
};
|
},
|
computed: {
|
canAddNode() {
|
// 如果没有节点,可以新增母代
|
if (this.graphData.nodes.length === 0) {
|
return true;
|
}
|
// 如果选中了传代计划数节点,可以新增下一代
|
if (this.selectedNode && this.selectedNode.label === "传代计划数") {
|
return true;
|
}
|
return false;
|
},
|
},
|
created() {
|
if (this.$route.query.id) {
|
this.reloadData();
|
}
|
},
|
mounted() {
|
this.initGraph();
|
this.initEvents();
|
},
|
beforeDestroy() {
|
this.graph?.destroy();
|
window.removeEventListener("resize", this.handleResize);
|
},
|
methods: {
|
async reloadData() {
|
getDetail({ id: this.$route.query.id }).then((res) => {
|
const detailData = res.pedigreeChartParentAllDetailDTO;
|
// 设置基础表单数据
|
this.form = {
|
strainCode: res.strainCode,
|
strainName: res.strainName,
|
generationType: "2",
|
};
|
// 递归处理节点数据,构建树形结构
|
function processNode(node, parentType = null) {
|
// label 逻辑与新增时保持一致
|
let label = "";
|
if (node.type === 2) {
|
label = "传代计划数";
|
} else if (parentType === 2) {
|
label = "母代";
|
} else {
|
label = "祖代";
|
}
|
const processedNode = {
|
id: node.id,
|
label,
|
number: node.strainCode,
|
data: node,
|
isDiscarded: node.status === 1,
|
style: {
|
fill: node.status === 1 ? "#00B5AA" : "#999",
|
opacity: node.status === 1 ? 0.6 : 0.3,
|
},
|
children: [],
|
};
|
|
if (node.type === 2) {
|
processedNode.planCount = node.generationCount;
|
processedNode.currentCount = node.children
|
? node.children.length
|
: 0;
|
}
|
|
if (node.children && node.children.length > 0) {
|
processedNode.children = node.children.map((child) =>
|
processNode(child, node.type)
|
);
|
}
|
|
return processedNode;
|
}
|
|
// 构建完整的树形结构
|
this.graphData.nodes = detailData.one.map((node) => processNode(node));
|
|
// 在数据加载完成后重新渲染图表
|
this.$nextTick(() => {
|
if (this.graph) {
|
// 转换树形数据为G6可用的格式
|
const convertToG6Data = (nodes) => {
|
const g6Nodes = [];
|
const g6Edges = [];
|
|
const processNode = (node, parentId = null) => {
|
g6Nodes.push({
|
id: node.id,
|
label: node.label,
|
number: node.number,
|
data: node.data,
|
isDiscarded: node.isDiscarded,
|
style: node.style,
|
planCount: node.planCount,
|
currentCount: node.currentCount,
|
});
|
|
if (parentId) {
|
g6Edges.push({
|
source: parentId,
|
target: node.id,
|
style: {
|
stroke: "rgba(4, 156, 154, 1)",
|
lineWidth: 1,
|
},
|
});
|
}
|
|
if (node.children && node.children.length > 0) {
|
node.children.forEach((child) => processNode(child, node.id));
|
}
|
};
|
|
nodes.forEach((node) => processNode(node));
|
return { nodes: g6Nodes, edges: g6Edges };
|
};
|
|
const g6Data = convertToG6Data(this.graphData.nodes);
|
this.graph.changeData(g6Data);
|
this.graph.render();
|
this.graph.fitView();
|
}
|
});
|
});
|
},
|
addNodeSign(value, type) {
|
this.nodeData = value;
|
this.nodeType = type;
|
this.confirmStorageDialogVisible = true;
|
},
|
handleSubmit() {
|
this.$refs.pedigreeForm.validate((valid) => {
|
if (valid) {
|
// 递归处理节点数据,将data对象的内容提取到上一级并删除data属性
|
const processNodeData = (nodes) => {
|
return nodes.map((node) => {
|
const { data, children, ...rest } = node;
|
const processedNode = {
|
...rest,
|
...data, // 将data对象的内容展开到当前节点
|
children: children ? processNodeData(children) : [],
|
};
|
return processedNode;
|
});
|
};
|
|
// 获取基础表单数据
|
const baseData = {
|
parent: { ...this.form },
|
one: processNodeData(this.graphData.nodes),
|
};
|
|
console.log("提交数据:", baseData);
|
add(baseData).then((res) => {
|
if (res.code == 200) {
|
this.$message.success("保存成功");
|
this.$router.back();
|
}
|
});
|
}
|
});
|
},
|
handleDraft() {
|
// 实现存草稿逻辑
|
console.log("save draft", this.form);
|
},
|
handleCancel() {
|
this.$router.back();
|
},
|
handleSignatureConfirm(signatureImage) {
|
this.confirmStorageDialogVisible = false;
|
this.nodeData.vaccinateSignature = signatureImage.signature;
|
if (this.nodeType === 1) {
|
this.handleAddParent(this.nodeData);
|
} else if (this.nodeType === 2) {
|
this.handleAddPlan(this.nodeData);
|
} else if (this.nodeType === 3) {
|
this.handleAddSublevel(this.nodeData);
|
}
|
// 处理提交逻辑
|
},
|
addNode() {
|
// 如果没有节点,新增祖代
|
if (this.graphData.nodes.length === 0) {
|
this.$refs.pedigreeForm.validate((valid) => {
|
if (valid) {
|
this.$refs.addAncestor.openInitData({
|
source: "", // 来源获得/菌落编号
|
strainCode: "", // 菌种编号
|
strainName: "", // 菌种名称
|
colonyNumber: "", // 菌落编号
|
strainType: "1", // 1原始祖代菌株 2分离菌落 3祖代菌株
|
status: 1, // 1保存 2废弃
|
type: 1, // 固定为1
|
isDiscarded: true, // 仅用于前端切换保存/废弃
|
activeType: "1", // 仅用于前端切换类型
|
confirmTime: "",
|
formStatus: "add",
|
});
|
}
|
});
|
return;
|
}
|
|
// 如果选中了传代计划数节点,新增母代
|
|
if (this.selectedNode && this.selectedNode.label === "传代计划数") {
|
const nodeModel = this.selectedNode;
|
|
// 检查是否已达到计划数
|
if (nodeModel.currentCount >= nodeModel.planCount) {
|
this.$message.warning("已达到计划数,不能再添加");
|
return;
|
}
|
|
// 获取父节点
|
const findParentNode = (nodes) => {
|
for (let node of nodes) {
|
if (node.children && node.children.length > 0) {
|
for (let child of node.children) {
|
if (child.id === nodeModel.id) {
|
return node;
|
}
|
}
|
const found = findParentNode(node.children);
|
if (found) return found;
|
}
|
}
|
return null;
|
};
|
|
const parentNode = findParentNode(this.graphData.nodes);
|
|
// 如果父节点是母代,不允许添加
|
if (parentNode && parentNode.label === "母代") {
|
this.$message.warning("母代节点不能再生成下一代");
|
return;
|
}
|
|
this.showDiscarded = true;
|
this.isAddingNode = true;
|
console.log(parentNode);
|
|
this.$refs.addSublevelForm.openInitData({
|
title: "新增菌种传代项",
|
form: {
|
strainName1: this.form.strainName,
|
strainCode1: this.form.strainCode,
|
status: 1,
|
parentId: this.selectedNode.id,
|
},
|
});
|
} else {
|
this.$message.warning("请选择传代计划数节点");
|
}
|
},
|
handleAddParent(value) {
|
const parentId = `parent-${++this.nodeCount}`;
|
const parentNode = {
|
id: parentId,
|
label: "祖代",
|
number: value.strainCode.trim(),
|
data: value,
|
isDiscarded: value.status == "1" ? true : false,
|
x: 200,
|
y: 200,
|
style: {
|
fill: "#00B5AA",
|
},
|
children: [],
|
};
|
this.graphData.nodes = [parentNode];
|
this.graph.changeData(this.graphData);
|
this.$message.success("祖代节点添加成功");
|
this.$refs.addAncestor.closeDialog();
|
},
|
handleDialogClose() {
|
this.$refs.form.resetFields();
|
},
|
handleAddSublevel(value) {
|
if (this.$route.query.id) {
|
addProgenitor(value).then((res) => {
|
if (res.code === 200) {
|
this.$message.success("操作成功");
|
this.$refs.addSublevelForm.closeDialog();
|
this.reloadData();
|
}
|
});
|
return;
|
}
|
if (this.isAddingNode) {
|
// 新增节点的处理逻辑
|
const nodeModel = this.selectedNode;
|
const childId = `child-${++this.nodeCount}`;
|
const childNode = {
|
id: childId,
|
label: "母代",
|
number: value.strainCode.trim(),
|
isDiscarded: value.status == "1" ? true : false,
|
data: value,
|
style: {
|
fill: value.status == "1" ? "#999" : "#00B5AA",
|
opacity: value.status == "1" ? 0.3 : 0.6,
|
},
|
children: [],
|
};
|
|
// 找到父节点并添加子节点
|
const findAndAddChild = (nodes) => {
|
for (let node of nodes) {
|
if (node.id === nodeModel.id) {
|
if (!node.children) {
|
node.children = [];
|
}
|
node.children.push(childNode);
|
node.currentCount = (node.currentCount || 0) + 1;
|
return true;
|
}
|
if (node.children && node.children.length > 0) {
|
if (findAndAddChild(node.children)) {
|
return true;
|
}
|
}
|
}
|
return false;
|
};
|
|
findAndAddChild(this.graphData.nodes);
|
|
// 转换树形数据为G6可用的格式
|
const convertToG6Data = (nodes) => {
|
const g6Nodes = [];
|
const g6Edges = [];
|
|
const processNode = (node, parentId = null) => {
|
g6Nodes.push({
|
id: node.id,
|
label: node.label,
|
number: node.number,
|
data: node.data,
|
isDiscarded: node.isDiscarded,
|
style: node.style,
|
planCount: node.planCount,
|
currentCount: node.currentCount,
|
});
|
|
if (parentId) {
|
g6Edges.push({
|
source: parentId,
|
target: node.id,
|
style: {
|
stroke: "rgba(4, 156, 154, 1)",
|
lineWidth: 1,
|
},
|
});
|
}
|
|
if (node.children && node.children.length > 0) {
|
node.children.forEach((child) => processNode(child, node.id));
|
}
|
};
|
|
nodes.forEach((node) => processNode(node));
|
return { nodes: g6Nodes, edges: g6Edges };
|
};
|
|
const g6Data = convertToG6Data(this.graphData.nodes);
|
this.graph.changeData(g6Data);
|
this.$message.success("母代添加成功");
|
this.$refs.addSublevelForm.closeDialog();
|
this.isAddingNode = false;
|
} else {
|
// 编辑节点的处理逻辑
|
const nodeModel = this.selectedNode;
|
const updateNode = (nodes) => {
|
for (let node of nodes) {
|
if (node.id === nodeModel.id) {
|
if (nodeModel.label === "传代计划数") {
|
node.planCount = parseInt(value.value);
|
} else {
|
node.number = value.value.trim();
|
if (this.showDiscarded) {
|
node.isDiscarded = value.isDiscarded;
|
// 如果设置为废弃状态,同时废弃所有子节点
|
if (value.isDiscarded && node.children) {
|
const discardChildren = (children) => {
|
children.forEach((child) => {
|
child.isDiscarded = true;
|
child.style.fill = "#999";
|
child.style.opacity = 0.3;
|
if (child.children) {
|
discardChildren(child.children);
|
}
|
});
|
};
|
discardChildren(node.children);
|
}
|
}
|
}
|
return true;
|
}
|
if (node.children && node.children.length > 0) {
|
if (updateNode(node.children)) {
|
return true;
|
}
|
}
|
}
|
return false;
|
};
|
|
updateNode(this.graphData.nodes);
|
|
// 转换树形数据为G6可用的格式
|
const convertToG6Data = (nodes) => {
|
const g6Nodes = [];
|
const g6Edges = [];
|
|
const processNode = (node, parentId = null) => {
|
g6Nodes.push({
|
id: node.id,
|
label: node.label,
|
number: node.number,
|
data: node.data,
|
isDiscarded: node.isDiscarded,
|
style: node.style,
|
planCount: node.planCount,
|
currentCount: node.currentCount,
|
});
|
|
if (parentId) {
|
g6Edges.push({
|
source: parentId,
|
target: node.id,
|
style: {
|
stroke: "rgba(4, 156, 154, 1)",
|
lineWidth: 1,
|
},
|
});
|
}
|
|
if (node.children && node.children.length > 0) {
|
node.children.forEach((child) => processNode(child, node.id));
|
}
|
};
|
|
nodes.forEach((node) => processNode(node));
|
return { nodes: g6Nodes, edges: g6Edges };
|
};
|
|
const g6Data = convertToG6Data(this.graphData.nodes);
|
this.graph.changeData(g6Data);
|
this.$message.success("修改成功");
|
this.dialogVisible = false;
|
}
|
},
|
setGenerationPlan() {
|
if (!this.selectedNode) {
|
this.$message.warning("请先选择节点");
|
return;
|
}
|
|
const nodeModel = this.selectedNode;
|
|
if (nodeModel.label === "母代") {
|
this.$message.warning("母代节点不能再生成传代计划数");
|
return;
|
}
|
|
if (nodeModel.label === "传代计划数") {
|
this.$message.warning("传代计划数节点不能再设置计划数");
|
return;
|
}
|
|
const hasGenerationNode = this.graphData.edges.some(
|
(e) =>
|
e.source === nodeModel.id &&
|
this.graphData.nodes.some(
|
(n) => n.id === e.target && n.label === "传代计划数"
|
)
|
);
|
|
if (hasGenerationNode) {
|
this.$message.warning("该节点已经存在传代计划数节点");
|
return;
|
}
|
console.log({
|
...nodeModel.data,
|
label: nodeModel.label,
|
});
|
|
this.$refs.planForm.openInitData({
|
...nodeModel.data,
|
label: nodeModel.label,
|
});
|
},
|
handleAddPlan(value) {
|
if (this.$route.query.id) {
|
let params = {
|
parentId: value.id,
|
generationCount: value.generationCount,
|
vaccinateSignature: value.vaccinateSignature,
|
};
|
addProgenitorChild(params).then((res) => {
|
if (res.code === 200) {
|
this.$message.success("操作成功");
|
this.$refs.planForm.closeDialog();
|
this.reloadData();
|
}
|
});
|
return;
|
}
|
|
const nodeModel = this.selectedNode;
|
const generationId = `generation-${++this.nodeCount}`;
|
const generationNode = {
|
id: generationId,
|
label: "传代计划数",
|
planCount: value.generationCount,
|
data: value,
|
isDiscarded: true,
|
currentCount: 0,
|
style: {
|
fill: "#00B5AA",
|
},
|
children: [],
|
};
|
|
// 找到父节点并添加子节点
|
const findAndAddChild = (nodes) => {
|
for (let node of nodes) {
|
if (node.id === nodeModel.id) {
|
if (!node.children) {
|
node.children = [];
|
}
|
node.children.push(generationNode);
|
return true;
|
}
|
if (node.children && node.children.length > 0) {
|
if (findAndAddChild(node.children)) {
|
return true;
|
}
|
}
|
}
|
return false;
|
};
|
|
findAndAddChild(this.graphData.nodes);
|
|
// 转换树形数据为G6可用的格式
|
const convertToG6Data = (nodes) => {
|
const g6Nodes = [];
|
const g6Edges = [];
|
|
const processNode = (node, parentId = null) => {
|
g6Nodes.push({
|
id: node.id,
|
label: node.label,
|
number: node.number,
|
data: node.data,
|
isDiscarded: node.isDiscarded,
|
style: node.style,
|
planCount: node.planCount,
|
currentCount: node.currentCount,
|
});
|
|
if (parentId) {
|
g6Edges.push({
|
source: parentId,
|
target: node.id,
|
style: {
|
stroke: "rgba(4, 156, 154, 1)",
|
lineWidth: 1,
|
},
|
});
|
}
|
|
if (node.children && node.children.length > 0) {
|
node.children.forEach((child) => processNode(child, node.id));
|
}
|
};
|
|
nodes.forEach((node) => processNode(node));
|
return { nodes: g6Nodes, edges: g6Edges };
|
};
|
|
const g6Data = convertToG6Data(this.graphData.nodes);
|
this.graph.changeData(g6Data);
|
this.$message.success("传代计划数设置成功");
|
this.$refs.planForm.closeDialog();
|
},
|
showDetail() {
|
if (!this.selectedNode) {
|
this.$message.warning("请先选择节点");
|
return;
|
}
|
|
const nodeModel = this.selectedNode;
|
|
if (nodeModel.label === "祖代") {
|
this.$refs.addAncestor.openInitData({
|
...nodeModel.data,
|
formStatus: "detail",
|
});
|
} else if (nodeModel.label === "母代") {
|
nodeModel.data.strainCode1 = this.form.strainCode;
|
nodeModel.data.strainName1 = this.form.strainName;
|
this.dialogTitle = "母代详情";
|
this.$refs.addSublevelForm.openInitData({
|
title: "母代详情",
|
form: { ...nodeModel.data },
|
});
|
} else if (nodeModel.label === "传代计划数") {
|
// this.$refs.planForm.openInitData({
|
// ...nodeModel.data,
|
// formStatus: "detail",
|
// });
|
}
|
},
|
initGraph() {
|
const container = document.getElementById("mountNode");
|
const width = container.scrollWidth;
|
const height = container.scrollHeight || 600;
|
|
// 自定义节点
|
G6.registerNode("custom-node", {
|
draw(cfg, group) {
|
const width = 120;
|
const titleHeight = 30;
|
const contentHeight = 40;
|
const gap = 4;
|
const totalHeight = titleHeight + gap + contentHeight;
|
|
// 根据节点状态设置颜色
|
const isDiscarded = !cfg.isDiscarded;
|
const titleFill = isDiscarded
|
? "rgba(245, 248, 250, 1)"
|
: cfg.selected
|
? "l(0) 0:#0ACBCA 1:#049C9A"
|
: "l(0) 0:#0ACBCA 1:#049C9A";
|
const contentFill = isDiscarded
|
? "rgba(245, 248, 250, 1)"
|
: cfg.selected
|
? "rgba(4,156,154,0.2)"
|
: "rgba(4,156,154,0.1)";
|
const textFill = isDiscarded ? "rgba(144, 147, 153, 1)" : "#049C9A";
|
const stroke = isDiscarded
|
? "#DCDFE6"
|
: cfg.selected
|
? "#049C9A"
|
: "transparent";
|
|
// 创建渐变
|
const gradient = group.addShape("rect", {
|
attrs: {
|
x: -width / 2,
|
y: -totalHeight / 2,
|
width: width,
|
height: titleHeight,
|
radius: 20,
|
fill: titleFill,
|
cursor: "move",
|
stroke: stroke,
|
lineWidth: isDiscarded ? 1 : cfg.selected ? 2 : 0,
|
},
|
name: "title-box",
|
});
|
|
// 下部分 - 内容背景
|
const contentBox = group.addShape("rect", {
|
attrs: {
|
x: -width / 2,
|
y: -totalHeight / 2 + titleHeight + gap,
|
width: width,
|
height: contentHeight,
|
fill: contentFill,
|
radius: 15,
|
cursor: "move",
|
stroke: stroke,
|
lineWidth: isDiscarded ? 1 : cfg.selected ? 2 : 0,
|
},
|
name: "content-box",
|
});
|
|
// 标题文本
|
if (cfg.label) {
|
group.addShape("text", {
|
attrs: {
|
text: cfg.label,
|
x: 0,
|
y: -totalHeight / 2 + titleHeight / 2,
|
fill: isDiscarded ? "rgba(144, 147, 153, 1)" : "#fff",
|
fontSize: 12,
|
textAlign: "center",
|
textBaseline: "middle",
|
fontWeight: "bold",
|
cursor: "move",
|
},
|
name: "title-text",
|
});
|
}
|
|
// 内容文本
|
let content = "";
|
if (cfg.label === "传代计划数") {
|
content = `${cfg.planCount || 0}`;
|
} else if (cfg.number) {
|
content =
|
cfg.label === "母代"
|
? `代传菌种编号:${cfg.number}`
|
: `接种菌种编号:${cfg.number}`;
|
}
|
|
if (content) {
|
group.addShape("text", {
|
attrs: {
|
text: content,
|
x: 0,
|
y: -totalHeight / 2 + titleHeight + gap + contentHeight / 2,
|
fill: textFill,
|
fontSize: 10,
|
textAlign: "center",
|
textBaseline: "middle",
|
cursor: "move",
|
},
|
name: "content-text",
|
});
|
}
|
|
return gradient;
|
},
|
getAnchorPoints() {
|
return [
|
[0.5, 0], // 上
|
[1, 0.5], // 右
|
[0.5, 1], // 下
|
[0, 0.5], // 左
|
];
|
},
|
setState(name, value, item) {
|
// 移除悬浮效果,保持节点样式始终一致
|
},
|
});
|
|
this.graph = new G6.Graph({
|
container: "mountNode",
|
width,
|
height,
|
fitView: true,
|
fitViewPadding: 30,
|
animate: false,
|
enabledStack: false,
|
renderer: "canvas",
|
minZoom: 0.3,
|
maxZoom: 2,
|
defaultZoom: 1,
|
layout: {
|
type: "dagre",
|
rankdir: "LR",
|
align: "UL",
|
nodesep: 50,
|
ranksep: 70,
|
controlPoints: true,
|
},
|
modes: {
|
default: [
|
{
|
type: "drag-canvas",
|
enableOptimize: true,
|
direction: "both",
|
scalableRange: 0.1,
|
dragTimesOfScale: 0.1,
|
onlyChangeComputeZoom: true,
|
},
|
{
|
type: "zoom-canvas",
|
sensitivity: 1.5,
|
enableOptimize: true,
|
},
|
{
|
type: "drag-node",
|
enableDelegate: true,
|
delegateStyle: {
|
fill: "#f3f3f3",
|
stroke: "#ccc",
|
opacity: 0.5,
|
},
|
updateEdge: false,
|
enableOptimize: true,
|
optimizeZoom: 0.7,
|
damping: 0.1,
|
},
|
],
|
},
|
defaultNode: {
|
type: "custom-node",
|
style: {
|
fill: "l(0) 0:#0ACBCA 1:#049C9A",
|
},
|
},
|
defaultEdge: {
|
type: "cubic-horizontal",
|
style: {
|
stroke: "rgba(4, 156, 154, 1)",
|
lineWidth: 1,
|
opacity: 0.5,
|
endArrow: {
|
path: G6.Arrow.triangle(6, 6),
|
fill: "rgba(4, 156, 154, 1)",
|
stroke: "rgba(4, 156, 154, 1)",
|
},
|
},
|
},
|
optimizeEdge: true,
|
optimizeLayoutAnimation: true,
|
});
|
|
const canvas = this.graph.get("canvas");
|
canvas.set("localRefresh", false);
|
canvas.set("autoDraw", true);
|
canvas.set("animating", false);
|
|
let throttleTimer = null;
|
const throttleInterval = 16;
|
|
this.graph.on("node:dragstart", () => {
|
canvas.set("localRefresh", false);
|
this.graph.get("canvas").draw();
|
});
|
|
this.graph.on("node:drag", (e) => {
|
if (throttleTimer) return;
|
throttleTimer = setTimeout(() => {
|
const model = e.item.get("model");
|
const edges = this.graph.getEdges().filter((edge) => {
|
const source = edge.getSource();
|
const target = edge.getTarget();
|
return (
|
source.get("id") === model.id || target.get("id") === model.id
|
);
|
});
|
edges.forEach((edge) => {
|
this.graph.refreshItem(edge);
|
});
|
throttleTimer = null;
|
}, throttleInterval);
|
});
|
|
this.graph.on("node:dragend", (e) => {
|
if (throttleTimer) {
|
clearTimeout(throttleTimer);
|
throttleTimer = null;
|
}
|
const model = e.item.get("model");
|
const edges = this.graph.getEdges().filter((edge) => {
|
const source = edge.getSource();
|
const target = edge.getTarget();
|
return source.get("id") === model.id || target.get("id") === model.id;
|
});
|
edges.forEach((edge) => {
|
this.graph.refreshItem(edge);
|
});
|
canvas.set("localRefresh", true);
|
this.graph.get("canvas").draw();
|
});
|
|
this.graph.data(this.graphData);
|
this.graph.render();
|
|
let debounceTimer = null;
|
this.graph.on("afterchange", () => {
|
if (debounceTimer) clearTimeout(debounceTimer);
|
debounceTimer = setTimeout(() => {
|
if (!canvas.get("destroyed")) {
|
canvas.draw();
|
}
|
}, 16);
|
});
|
},
|
initEvents() {
|
// 监听窗口大小变化
|
window.addEventListener("resize", this.handleResize);
|
|
const handleNodeClick = (evt) => {
|
evt.preventDefault(); // 阻止默认触摸行为
|
const node = evt.item;
|
const nodeModel = node.getModel();
|
// 如果节点已废弃,不允许任何操作
|
if (!nodeModel.isDiscarded) {
|
this.$message.warning("该节点已废弃,不能进行操作");
|
return;
|
}
|
// 更新选中节点
|
this.selectedNode = nodeModel;
|
// 更新节点选中状态
|
const updateNodeSelected = (nodes) => {
|
for (let node of nodes) {
|
node.selected = node.id === nodeModel.id;
|
if (node.children && node.children.length > 0) {
|
updateNodeSelected(node.children);
|
}
|
}
|
};
|
updateNodeSelected(this.graphData.nodes);
|
|
// 转换树形数据为G6可用的格式
|
const convertToG6Data = (nodes) => {
|
const g6Nodes = [];
|
const g6Edges = [];
|
|
const processNode = (node, parentId = null) => {
|
g6Nodes.push({
|
id: node.id,
|
label: node.label,
|
number: node.number,
|
data: node.data,
|
isDiscarded: node.isDiscarded,
|
style: node.style,
|
planCount: node.planCount,
|
currentCount: node.currentCount,
|
selected: node.selected,
|
});
|
|
if (parentId) {
|
g6Edges.push({
|
source: parentId,
|
target: node.id,
|
style: {
|
stroke: "rgba(4, 156, 154, 1)",
|
lineWidth: 1,
|
},
|
});
|
}
|
|
if (node.children && node.children.length > 0) {
|
node.children.forEach((child) => processNode(child, node.id));
|
}
|
};
|
|
nodes.forEach((node) => processNode(node));
|
return { nodes: g6Nodes, edges: g6Edges };
|
};
|
|
const g6Data = convertToG6Data(this.graphData.nodes);
|
this.graph.changeData(g6Data);
|
};
|
this.graph.on("node:click", handleNodeClick);
|
this.graph.on("node:touchstart", handleNodeClick);
|
|
// 画布点击事件,取消选中节点(添加触摸支持)
|
const handleCanvasClick = (evt) => {
|
evt.preventDefault();
|
this.selectedNode = null;
|
const updateNodeSelected = (nodes) => {
|
for (let node of nodes) {
|
node.selected = false;
|
if (node.children && node.children.length > 0) {
|
updateNodeSelected(node.children);
|
}
|
}
|
};
|
updateNodeSelected(this.graphData.nodes);
|
|
// 转换树形数据为G6可用的格式
|
const convertToG6Data = (nodes) => {
|
const g6Nodes = [];
|
const g6Edges = [];
|
|
const processNode = (node, parentId = null) => {
|
g6Nodes.push({
|
id: node.id,
|
label: node.label,
|
number: node.number,
|
data: node.data,
|
isDiscarded: node.isDiscarded,
|
style: node.style,
|
planCount: node.planCount,
|
currentCount: node.currentCount,
|
selected: node.selected,
|
});
|
|
if (parentId) {
|
g6Edges.push({
|
source: parentId,
|
target: node.id,
|
style: {
|
stroke: "rgba(4, 156, 154, 1)",
|
lineWidth: 1,
|
},
|
});
|
}
|
|
if (node.children && node.children.length > 0) {
|
node.children.forEach((child) => processNode(child, node.id));
|
}
|
};
|
|
nodes.forEach((node) => processNode(node));
|
return { nodes: g6Nodes, edges: g6Edges };
|
};
|
|
const g6Data = convertToG6Data(this.graphData.nodes);
|
this.graph.changeData(g6Data);
|
};
|
this.graph.on("canvas:click", handleCanvasClick);
|
this.graph.on("canvas:touchstart", handleCanvasClick);
|
},
|
handleResize() {
|
if (this.graph) {
|
const container = document.getElementById("mountNode");
|
const width = container.scrollWidth;
|
const height = container.scrollHeight || 600;
|
this.graph.changeSize(width, height);
|
}
|
},
|
},
|
};
|
</script>
|
|
<style scoped lang="less">
|
.card {
|
min-height: 145px;
|
background: rgba(255, 255, 255, 0.8);
|
box-shadow: 0px 10px 19px 0px rgba(0, 0, 0, 0.06);
|
border-radius: 16px;
|
border: 4px solid #ffffff;
|
padding: 0 20px;
|
display: flex;
|
align-items: center;
|
}
|
|
.chart {
|
padding: 20px 38px;
|
background: rgba(255, 255, 255, 0.8);
|
box-shadow: 0px 10px 19px 0px rgba(0, 0, 0, 0.06);
|
border-radius: 16px;
|
border: 4px solid #ffffff;
|
margin-top: 30px;
|
|
.header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
|
.title {
|
font-size: 18px;
|
}
|
|
.option-btn {
|
display: flex;
|
gap: 10px;
|
|
.el-button {
|
margin-left: 0;
|
}
|
}
|
}
|
}
|
|
.form-items-row {
|
display: flex;
|
flex-wrap: wrap;
|
width: 100%;
|
align-items: center;
|
|
@media (min-width: 1200px) {
|
flex-direction: row;
|
}
|
|
@media (max-width: 1199px) {
|
flex-direction: column;
|
align-items: flex-start;
|
}
|
}
|
|
.strain-form {
|
width: 100%;
|
|
:deep(.el-form-item) {
|
margin-bottom: 15px;
|
display: flex;
|
flex-direction: column;
|
align-items: flex-start;
|
|
@media (min-width: 1200px) {
|
margin-right: 10px;
|
}
|
|
@media (max-width: 1199px) {
|
width: 100%;
|
margin-right: 0;
|
}
|
|
.el-form-item__label {
|
padding: 0 0 8px;
|
line-height: 1.2;
|
text-align: left;
|
}
|
|
.el-form-item__content {
|
width: 100%;
|
}
|
}
|
}
|
|
.end-btn {
|
margin-top: 20px;
|
display: flex;
|
flex-wrap: wrap;
|
gap: 10px;
|
}
|
|
.strain-flow-chart {
|
width: 100%;
|
height: 100%;
|
display: flex;
|
flex-direction: column;
|
|
.toolbar {
|
margin-bottom: 16px;
|
display: flex;
|
gap: 12px;
|
|
.el-button {
|
margin-right: 0;
|
}
|
}
|
|
#mountNode {
|
flex: 1;
|
width: 100%;
|
min-height: 500px;
|
border-radius: 4px;
|
}
|
}
|
</style>
|