From 993e5fd593398926af72af660cb5ed6aba8e4e2b Mon Sep 17 00:00:00 2001
From: 13404089107 <puwei@sinata.cn>
Date: 星期二, 20 五月 2025 16:43:04 +0800
Subject: [PATCH] 对接接口

---
 culture/src/views/pedigree-chart/add.vue |  808 +++++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 files changed, 727 insertions(+), 81 deletions(-)

diff --git a/culture/src/views/pedigree-chart/add.vue b/culture/src/views/pedigree-chart/add.vue
index 6f3d77f..65a1944 100644
--- a/culture/src/views/pedigree-chart/add.vue
+++ b/culture/src/views/pedigree-chart/add.vue
@@ -1,93 +1,117 @@
 <template>
-  <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="strainSource" required>
-          <div class="flex-row">
-            <div class="input-wrapper">
-              <el-input
-                v-model="form.strainSource"
-                placeholder="请输入"
-                class="fixed-width-input"
-              ></el-input>
+  <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="菌种源" required>
+            <div class="flex-row">
+              <div class="input-wrapper">
+                <el-form-item prop="strainSourceStart" style="margin-bottom: 0;">
+                  <el-input v-model="form.strainSourceStart" placeholder="请输入" class="fixed-width-input"></el-input>
+                </el-form-item>
+              </div>
+              <span class="form-text">代—</span>
+              <div class="input-wrapper">
+                <el-form-item prop="strainSourceEnd" style="margin-bottom: 0;">
+                  <el-input v-model="form.strainSourceEnd" placeholder="请输入" class="fixed-width-input"></el-input>
+                </el-form-item>
+              </div>
+              <span class="form-text">细胞库</span>
             </div>
-            <span class="form-text">代—</span>
-            <div class="input-wrapper">
-              <el-input
-                v-model="form.generation"
-                placeholder="请输入"
-                class="fixed-width-input"
-              ></el-input>
-            </div>
-            <span class="form-text">细胞库</span>
-          </div>
-        </el-form-item>
-        <el-form-item label="传代菌种编号" prop="strainNo" required>
-          <el-input
-            v-model="form.strainNo"
-            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="chart">
-      <div class="header">
-        <div class="title">菌种传代生产谱系图</div>
-        <div class="option-btn">
-          <el-button type="primary" class="el-icon-plus"> 新增</el-button>
-          <el-button type="primary">设置传代计划数</el-button>
-          <el-button type="primary">详情</el-button>
+          </el-form-item>
+          <el-form-item label="传代菌种编号" prop="strainNo" required>
+            <el-input v-model="form.strainNo" 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>
-    <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>
-    <!-- 签字确认组件 -->
-    <SignatureCanvas
-      :visible.sync="signatureVisible"
-      @confirm="handleSignatureConfirm"
-    />
-  </el-form>
+      <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="strainNo" />
+          <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="入库时间" 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 type="primary" @click="handleSubmit" style="width: 150px;">保存</el-button>
+
+      </div>
+      <div class="end-btn">
+        <!-- <el-button @click="handleDraft">存草稿</el-button>
+        <el-button @click="handleCancel">取消</el-button> -->
+      </div>
+    </el-form>
+
+    <ParentForm ref="parentForm" @addNodeSign="addNodeSign" />
+    <PlanForm ref="planForm" @addNodeSign="addNodeSign" />
+    <AddSublevelForm ref="addSublevelForm" @addNodeSign="addNodeSign" />
+    <AddSublevelPlan ref="addSublevelPlan" @addNodeSign="addNodeSign" />
+    <ConfirmStorageDialog name="接种操作人签字" :visible.sync="confirmStorageDialogVisible"
+      @confirm="handleSignatureConfirm" />
+    <!-- 菌种工程师 -->
+    <ConfirmStorageDialog name="菌种保藏人签字" text="是否确认该项菌种信息入库" :visible.sync="storageVisible"
+      @confirm="handleSignatureConfirm" />
+  </div>
 </template>
 
 <script>
-import SignatureCanvas from "@/components/SignatureCanvas.vue";
+import G6 from '@antv/g6';
+import ParentForm from "./components/ParentForm.vue";
+import PlanForm from "./components/PlanForm.vue";
+import AddSublevelForm from "./components/AddSublevelForm.vue";
+import AddSublevelPlan from "./components/AddSublevelPlan.vue";
+import ConfirmStorageDialog from "@/components/confirm-storage-dialog";
 
 export default {
   name: "AddPedigree",
   components: {
-    SignatureCanvas,
+    ParentForm,
+    PlanForm,
+    AddSublevelForm,
+    AddSublevelPlan,
+    ConfirmStorageDialog
   },
   data() {
     return {
       signatureVisible: false,
       form: {
-        strainSource: "",
-        generation: "",
+        strainSourceStart: "",
+        strainSourceEnd: "",
         cellBank: "",
         strainNo: "",
         strainName: "",
         remarks: "",
       },
       rules: {
-        strainSource: [
+        strainSourceStart: [
+          { required: true, message: "请输入菌种源", trigger: "blur" },
+        ],
+        strainSourceEnd: [
           { required: true, message: "请输入菌种源", trigger: "blur" },
         ],
         strainNo: [
@@ -97,13 +121,58 @@
           { 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;
+    }
+  },
+  mounted() {
+    this.initGraph();
+    this.initEvents();
+  },
+  beforeDestroy() {
+    this.graph?.destroy();
+    window.removeEventListener('resize', this.handleResize);
+  },
   methods: {
+    addNodeSign(value, type) {
+      this.nodeData = value
+      this.nodeType = type
+      this.confirmStorageDialogVisible = true;
+    },
     handleSubmit() {
       this.$refs.pedigreeForm.validate((valid) => {
         if (valid) {
-          this.signatureVisible = true;
+          this.confirmStorageDialogVisible = true;
         }
       });
     },
@@ -115,11 +184,559 @@
       this.$router.back();
     },
     handleSignatureConfirm(signatureImage) {
-      this.signatureVisible = false;
+      this.confirmStorageDialogVisible = false;
+      if (this.nodeType === 1) {
+        this.handleAddParent({ ...this.nodeData, signature: signatureImage.signature })
+      } else if (this.nodeType === 2) {
+        this.handleAddPlan(this.nodeData)
+      } else if (this.nodeType === 3) {
+        this.handleAddSublevel(this.nodeData)
+      }
       // 处理提交逻辑
-      console.log("submit form with signature:", this.form, signatureImage);
-      this.$router.back();
     },
+    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: 30,  // 减小节点间距
+          ranksep: 50,  // 减小层级间距
+          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;
+        // 更新节点选中状态
+        this.graphData.nodes.forEach(n => {
+          n.selected = n.id === nodeModel.id;
+        });
+        this.graph.changeData(this.graphData);
+      };
+      this.graph.on('node:click', handleNodeClick);
+      this.graph.on('node:touchstart', handleNodeClick);
+
+      // 画布点击事件,取消选中节点(添加触摸支持)
+      const handleCanvasClick = (evt) => {
+        evt.preventDefault();
+        this.selectedNode = null;
+        this.graphData.nodes.forEach(n => {
+          n.selected = false;
+        });
+        this.graph.changeData(this.graphData);
+      };
+
+      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);
+      }
+    },
+    addNode() {
+      // 如果没有节点,新增母代
+      if (this.graphData.nodes.length === 0) {
+        this.$refs.pedigreeForm.validate((valid) => {
+          if (valid) {
+            this.$refs.parentForm.openInitData({
+              strainName: this.form.strainName,
+              strainNo: this.form.strainNo,
+              strainSourceStart: this.form.strainSourceStart,
+              strainSourceEnd: this.form.strainSourceEnd,
+            });
+          }
+        })
+        return
+      }
+
+      // 如果选中了传代计划数节点,新增下一代
+      if (this.selectedNode && this.selectedNode.label === '传代计划数') {
+        const nodeModel = this.selectedNode;
+
+        // 检查是否已达到计划数
+        if (nodeModel.currentCount >= nodeModel.planCount) {
+          this.$message.warning('已达到计划数,不能再添加');
+          return;
+        }
+
+        // 获取父节点
+        const parentEdge = this.graphData.edges.find(e => e.target === nodeModel.id);
+        const parentNode = this.graphData.nodes.find(n => n.id === parentEdge.source);
+
+        // 如果父节点是孙代,不允许添加
+        if (parentNode.label === '孙代') {
+          this.$message.warning('孙代节点不能再生成下一代');
+          return;
+        }
+
+        const isParent = parentNode.label === '母代';
+        const nextLevel = isParent ? '子代' : '孙代';
+
+        this.showDiscarded = true;
+        this.isAddingNode = true;
+        this.$refs.addSublevelForm.openInitData({
+          title: `新增${nextLevel}`,
+          form: {
+            strainName: this.form.strainName,
+            strainNo: this.form.strainNo,
+            isDiscarded: true
+          }
+        })
+      } else {
+        this.$message.warning('请选择传代计划数节点');
+      }
+    },
+    handleAddParent(value) {
+      console.log(value);
+
+      const parentId = `parent-${++this.nodeCount}`;
+      this.graphData.nodes.push({
+        id: parentId,
+        label: '母代',
+        number: value.strainNo.trim(),
+        data: value,
+        isDiscarded: true,
+        x: 200,
+        y: 200,
+        style: {
+          fill: '#00B5AA',
+        },
+      });
+      this.graph.changeData(this.graphData);
+      this.$message.success('母代节点添加成功');
+      this.$refs.parentForm.closeDialog();
+    },
+    handleDialogClose() {
+      this.$refs.form.resetFields();
+    },
+    handleAddSublevel(value) {
+      if (this.isAddingNode) {
+        // 新增节点的处理逻辑
+        const nodeModel = this.selectedNode;
+        const parentEdge = this.graphData.edges.find(e => e.target === nodeModel.id);
+        const parentNode = this.graphData.nodes.find(n => n.id === parentEdge.source);
+        const isParent = parentNode.label === '母代';
+        const nextLevel = isParent ? '子代' : '孙代';
+
+        const childId = `child-${++this.nodeCount}`;
+        this.graphData.nodes.push({
+          id: childId,
+          label: nextLevel,
+          number: value.inoculateNo.trim(),
+          isDiscarded: value.isDiscarded,
+          data: value,
+          style: {
+            fill: value.isDiscarded ? '#999' : '#00B5AA',
+            opacity: value.isDiscarded ? 0.3 : (isParent ? 0.6 : 0.4),
+          },
+        });
+
+        this.graphData.edges.push({
+          source: nodeModel.id,
+          target: childId,
+          style: {
+            stroke: 'rgba(4, 156, 154, 1)',
+            lineWidth: 1,
+          },
+        });
+
+        const nodeIndex = this.graphData.nodes.findIndex(n => n.id === nodeModel.id);
+        this.graphData.nodes[nodeIndex].currentCount++;
+
+        this.graph.changeData(this.graphData);
+        this.$message.success(`${nextLevel}添加成功`);
+        this.$refs.addSublevelForm.closeDialog();
+
+        this.isAddingNode = false;
+      } else {
+        // 编辑节点的处理逻辑
+        const nodeModel = this.selectedNode;
+        const nodeIndex = this.graphData.nodes.findIndex(n => n.id === nodeModel.id);
+
+        if (nodeIndex > -1) {
+          if (nodeModel.label === '传代计划数') {
+            this.graphData.nodes[nodeIndex].planCount = parseInt(value.value);
+          } else {
+            this.graphData.nodes[nodeIndex].number = value.value.trim();
+            if (this.showDiscarded) {
+              this.graphData.nodes[nodeIndex].isDiscarded = value.isDiscarded;
+              // 如果设置为废弃状态,同时废弃所有子节点
+              if (value.isDiscarded) {
+                const discardChildren = (parentId) => {
+                  const childEdges = this.graphData.edges.filter(e => e.source === parentId);
+                  childEdges.forEach(edge => {
+                    const childNode = this.graphData.nodes.find(n => n.id === edge.target);
+                    if (childNode) {
+                      const childIndex = this.graphData.nodes.findIndex(n => n.id === childNode.id);
+                      if (childIndex > -1) {
+                        this.graphData.nodes[childIndex].isDiscarded = true;
+                        this.graphData.nodes[childIndex].style.fill = '#999';
+                        this.graphData.nodes[childIndex].style.opacity = 0.3;
+                        // 递归处理子节点的子节点
+                        discardChildren(childNode.id);
+                      }
+                    }
+                  });
+                };
+                discardChildren(nodeModel.id);
+              }
+            }
+          }
+          this.graph.changeData(this.graphData);
+          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;
+      }
+      if (nodeModel.label === '子代') {
+        this.$refs.addSublevelPlan.openInitData({
+          ...nodeModel.data,
+          label: nodeModel.label,
+          strainName: this.form.strainName,
+          strainNo: this.form.strainNo,
+          strainSourceStart: this.form.strainSourceStart,
+          strainSourceEnd: this.form.strainSourceEnd,
+        })
+      } else {
+        this.$refs.planForm.openInitData({
+          ...nodeModel.data,
+          label: nodeModel.label,
+          strainName: this.form.strainName,
+          strainNo: this.form.strainNo,
+          strainSourceStart: this.form.strainSourceStart,
+          strainSourceEnd: this.form.strainSourceEnd,
+        })
+      }
+    },
+    handleAddPlan(value) {
+      const nodeModel = this.selectedNode;
+      const generationId = `generation-${++this.nodeCount}`;
+
+      this.graphData.nodes.push({
+        id: generationId,
+        label: '传代计划数',
+        planCount: value.count,
+        data: value,
+        isDiscarded: true,
+        currentCount: 0,
+        style: {
+          fill: '#00B5AA',
+        },
+      });
+
+      this.graphData.edges.push({
+        source: nodeModel.id,
+        target: generationId,
+        style: {
+          stroke: 'rgba(4, 156, 154, 1)',
+          lineWidth: 1,
+        },
+      });
+
+      this.graph.changeData(this.graphData);
+      this.$message.success('传代计划数设置成功');
+      this.$refs.planForm.closeDialog()
+    },
+    showDetail() {
+      if (!this.selectedNode) {
+        this.$message.warning('请先选择节点');
+        return;
+      }
+
+      const nodeModel = this.selectedNode;
+
+      if (nodeModel.label === '母代') {
+        this.$refs.parentForm.openInitData({ ...nodeModel.data, status: 'detail' });
+      } else if (nodeModel.label === '子代' || nodeModel.label === '孙代') {
+        this.dialogTitle = `${nodeModel.label}详情`;
+        this.$refs.addSublevelForm.openInitData({
+          title: `${nodeModel.label}详情`,
+          form: { ...nodeModel.data }
+        })
+      } else if (nodeModel.label === '传代计划数') {
+        const nodeIndex = this.graphData.nodes.findIndex(n => n.id === nodeModel.id);
+        if (this.graphData.nodes[nodeIndex - 1].label === '子代') {
+          this.$refs.addSublevelPlan.openInitData({ ...nodeModel.data, status: 'detail' });
+        } else {
+          this.$refs.planForm.openInitData({ ...nodeModel.data, status: 'detail' });
+        }
+      }
+    }
   },
 };
 </script>
@@ -135,26 +752,31 @@
   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);
+  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;
+      font-size: 18px;
     }
+
     .option-btn {
-        display: flex;
-        gap: 10px;
-        .el-button {
-          margin-left: 0;
-        }
+      display: flex;
+      gap: 10px;
+
+      .el-button {
+        margin-left: 0;
+      }
     }
   }
 }
@@ -253,4 +875,28 @@
   flex-wrap: wrap;
   gap: 10px;
 }
-</style> 
\ No newline at end of file
+
+.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>
\ No newline at end of file

--
Gitblit v1.7.1