董国庆
2025-05-26 fd68873f292c7e42bf24039d99b96fa67238618c
Merge branch 'main' of http://120.76.84.145:10101/gitblit/r/H5/leshan-laboratory
8个文件已修改
1164 ■■■■ 已修改文件
culture/src/components/confirm-storage-dialog/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/pedigree-chart/add.vue 735 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/pedigree-chart/components/AddSublevelForm.vue 80 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/pedigree-chart/components/AddSublevelPlan.vue 103 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/pedigree-chart/components/ParentForm.vue 146 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/pedigree-chart/components/PlanForm.vue 91 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/pedigree-chart/service.js 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/layouts/components/HeaderNav.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/components/confirm-storage-dialog/index.vue
@@ -38,7 +38,7 @@
      <el-button @click="handleClose" style="margin-right: 16px"
        >取消</el-button
      >
      <el-button type="primary" @click="handleConfirm">确认</el-button>
      <el-button type="primary" @click="handleConfirm" :disabled="!form.signature">确认</el-button>
    </div>
    <signature-canvas
      :visible.sync="showSignature"
culture/src/views/pedigree-chart/add.vue
@@ -1,36 +1,58 @@
<template>
  <div>
    <el-form :model="form" :rules="rules" ref="pedigreeForm" label-position="top" class="strain-form">
    <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 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 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>
          </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 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-input
              v-model="form.strainName"
              placeholder="请输入"
              class="fixed-width-input"
            ></el-input>
          </el-form-item>
        </div>
      </div>
      <div class="card" style="margin-top: 30px;">
      <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="strainCode" />
          <el-table-column label="传代菌种编号" prop="strainName" />
          <el-table-column label="传代菌种名称" prop="strainName" />
          <el-table-column label="接种菌种编号" prop="strainName" />
@@ -49,8 +71,12 @@
        <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" 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>
@@ -58,8 +84,9 @@
        <div class="strain-flow-chart">
          <div id="mountNode"></div>
        </div>
        <el-button type="primary" @click="handleSubmit" style="width: 150px;">保存</el-button>
        <el-button type="primary" @click="handleSubmit" style="width: 150px"
          >保存</el-button
        >
      </div>
      <div class="end-btn">
        <!-- <el-button @click="handleDraft">存草稿</el-button>
@@ -71,22 +98,29 @@
    <PlanForm ref="planForm" @addNodeSign="addNodeSign" />
    <AddSublevelForm ref="addSublevelForm" @addNodeSign="addNodeSign" />
    <AddSublevelPlan ref="addSublevelPlan" @addNodeSign="addNodeSign" />
    <ConfirmStorageDialog name="接种操作人签字" :visible.sync="confirmStorageDialogVisible"
      @confirm="handleSignatureConfirm" />
    <ConfirmStorageDialog
      name="接种操作人签字"
      :visible.sync="confirmStorageDialogVisible"
      @confirm="handleSignatureConfirm"
    />
    <!-- 菌种工程师 -->
    <ConfirmStorageDialog name="菌种保藏人签字" text="是否确认该项菌种信息入库" :visible.sync="storageVisible"
      @confirm="handleSignatureConfirm" />
    <ConfirmStorageDialog
      name="菌种保藏人签字"
      text="是否确认该项菌种信息入库"
      :visible.sync="storageVisible"
      @confirm="handleSignatureConfirm"
    />
  </div>
</template>
<script>
import G6 from '@antv/g6';
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";
import { add } from './service'
export default {
  name: "AddPedigree",
  components: {
@@ -94,7 +128,7 @@
    PlanForm,
    AddSublevelForm,
    AddSublevelPlan,
    ConfirmStorageDialog
    ConfirmStorageDialog,
  },
  data() {
    return {
@@ -103,7 +137,7 @@
        strainSourceStart: "",
        strainSourceEnd: "",
        cellBank: "",
        strainNo: "",
        strainCode: "",
        strainName: "",
        remarks: "",
      },
@@ -114,7 +148,7 @@
        strainSourceEnd: [
          { required: true, message: "请输入菌种源", trigger: "blur" },
        ],
        strainNo: [
        strainCode: [
          { required: true, message: "请输入传代菌种编号", trigger: "blur" },
        ],
        strainName: [
@@ -126,20 +160,21 @@
      selectedNode: null,
      graphData: {
        nodes: [],
        edges: []
        edges: [],
      },
      // 弹窗相关数据
      dialogVisible: false,
      dialogTitle: '',
      formLabel: '',
      inputType: 'text',
      dialogTitle: "",
      formLabel: "",
      inputType: "text",
      showDiscarded: false,
      isAddingNode: false,
      nodeData: {},
      nodeType: '',//1母代 2计划数 3子孙代
      nodeType: "", //1母代 2计划数 3子孙代
      tableData: [],
      confirmStorageDialogVisible: false,
      storageVisible: false
      storageVisible: false,
      treeData: [],
    };
  },
  computed: {
@@ -149,11 +184,11 @@
        return true;
      }
      // 如果选中了传代计划数节点,可以新增下一代
      if (this.selectedNode && this.selectedNode.label === '传代计划数') {
      if (this.selectedNode && this.selectedNode.label === "传代计划数") {
        return true;
      }
      return false;
    }
    },
  },
  mounted() {
    this.initGraph();
@@ -161,18 +196,156 @@
  },
  beforeDestroy() {
    this.graph?.destroy();
    window.removeEventListener('resize', this.handleResize);
    window.removeEventListener("resize", this.handleResize);
  },
  methods: {
    addNodeSign(value, type) {
      this.nodeData = value
      this.nodeType = type
      this.nodeData = value;
      this.nodeType = type;
      this.confirmStorageDialogVisible = true;
    },
    handleSubmit() {
      this.$refs.pedigreeForm.validate((valid) => {
        if (valid) {
          this.confirmStorageDialogVisible = true;
          if (this.treeData.length == 0) {
            this.$message.warning("请新增节点");
          }
          // 处理节点数据,将data字段中的数据提升到根级别
          const processNodeData = (node) => {
            const processedNode = {
              ...node,
              ...node.data,
            };
            delete processedNode.data;
            return processedNode;
          };
          // 将扁平结构转换为嵌套结构
          const buildNestedStructure = (nodes) => {
            const levelMap = {
              0: 'one',
              1: 'two',
              2: 'three',
              3: 'four',
              4: 'five'
            };
            // 处理每个节点
            const processedNodes = nodes.map(node => {
              const processedNode = processNodeData(node);
              // 设置 type 字段
              if (node.level % 2 === 0) { // 0, 2, 4 对应 one, three, five
                processedNode.type = 1;
              } else { // 1, 3 对应 two, four
                processedNode.type = 2;
              }
              return processedNode;
            });
            // 构建嵌套结构
            const result = {};
            const nodeMap = new Map();
            // 首先创建所有节点的映射
            processedNodes.forEach(node => {
              nodeMap.set(node.nodeId, { ...node });
            });
            // 处理边关系,构建嵌套结构
            this.graphData.edges.forEach(edge => {
              const parent = nodeMap.get(edge.source);
              const child = nodeMap.get(edge.target);
              if (parent && child) {
                const parentLevel = parent.level;
                const childLevel = child.level;
                const parentKey = levelMap[parentLevel];
                const childKey = levelMap[childLevel];
                // 确保父节点存在
                if (!result[parentKey]) {
                  result[parentKey] = { ...parent };
                }
                // 根据层级关系设置子节点
                if (parentLevel === 0) { // one 包含 two
                  if (!result[parentKey].two) {
                    result[parentKey].two = [];
                  }
                  result[parentKey].two.push(child);
                } else if (parentLevel === 1) { // two 包含 three
                  if (!result.one.two) {
                    result.one.two = [];
                  }
                  const twoNode = result.one.two.find(n => n.nodeId === parent.nodeId);
                  if (twoNode) {
                    if (!twoNode.three) {
                      twoNode.three = [];
                    }
                    twoNode.three.push(child);
                  }
                } else if (parentLevel === 2) { // three 包含 four
                  result.one.two.forEach(twoNode => {
                    if (twoNode.three) {
                      const threeNode = twoNode.three.find(n => n.nodeId === parent.nodeId);
                      if (threeNode) {
                        if (!threeNode.four) {
                          threeNode.four = [];
                        }
                        threeNode.four.push(child);
                      }
                    }
                  });
                } else if (parentLevel === 3) { // four 包含 five
                  result.one.two.forEach(twoNode => {
                    if (twoNode.three) {
                      twoNode.three.forEach(threeNode => {
                        if (threeNode.four) {
                          const fourNode = threeNode.four.find(n => n.nodeId === parent.nodeId);
                          if (fourNode) {
                            if (!fourNode.five) {
                              fourNode.five = [];
                            }
                            fourNode.five.push(child);
                          }
                        }
                      });
                    }
                  });
                }
              }
            });
            // 如果没有边关系,至少保留第一层节点
            if (Object.keys(result).length === 0 && processedNodes.length > 0) {
              const rootNode = processedNodes.find(node => node.level === 0);
              if (rootNode) {
                result.one = { ...rootNode };
              }
            }
            return result;
          };
          // 获取基础表单数据
          const baseData = {
            parent: {
              generationType: 1,
              strainSourceStart: this.form.strainSourceStart,
              strainSourceEnd: this.form.strainSourceEnd,
              strainCode: this.form.strainCode,
              strainName: this.form.strainName,
            },
            ...(this.treeData.length > 0 ? buildNestedStructure(this.treeData) : {})
          };
          console.log("提交的数据:", baseData);
          return
          add(baseData).then((res) => {
            if (res.code == 200) {
              this.$message.success("保存成功");
              this.$router.back();
            }
          });
        }
      });
    },
@@ -185,22 +358,34 @@
    },
    handleSignatureConfirm(signatureImage) {
      this.confirmStorageDialogVisible = false;
      console.log(this.nodeType);
      if (this.nodeType === 1) {
        this.handleAddParent({ ...this.nodeData, signature: signatureImage.signature })
        this.handleAddParent({
          ...this.nodeData,
          preserveSignature: signatureImage.signature,
        });
      } else if (this.nodeType === 2) {
        this.handleAddPlan(this.nodeData)
        this.handleAddPlan({
          ...this.nodeData,
          preserveSignature: signatureImage.signature,
        });
      } else if (this.nodeType === 3) {
        this.handleAddSublevel(this.nodeData)
        this.handleAddSublevel({
          ...this.nodeData,
          preserveSignature: signatureImage.signature,
        });
        this.$refs.addSublevelPlan.closeDialog();
      }
      // 处理提交逻辑
    },
    initGraph() {
      const container = document.getElementById('mountNode');
      const container = document.getElementById("mountNode");
      const width = container.scrollWidth;
      const height = container.scrollHeight || 600;
      // 自定义节点
      G6.registerNode('custom-node', {
      G6.registerNode("custom-node", {
        draw(cfg, group) {
          const width = 120;
          const titleHeight = 30;
@@ -209,14 +394,26 @@
          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 isDiscarded = cfg.status === 2;
          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', {
          const gradient = group.addShape("rect", {
            attrs: {
              x: -width / 2,
              y: -totalHeight / 2,
@@ -224,15 +421,15 @@
              height: titleHeight,
              radius: 20,
              fill: titleFill,
              cursor: 'move',
              cursor: "move",
              stroke: stroke,
              lineWidth: isDiscarded ? 1 : (cfg.selected ? 2 : 0),
              lineWidth: isDiscarded ? 1 : cfg.selected ? 2 : 0,
            },
            name: 'title-box',
            name: "title-box",
          });
          // 下部分 - 内容背景
          const contentBox = group.addShape('rect', {
          const contentBox = group.addShape("rect", {
            attrs: {
              x: -width / 2,
              y: -totalHeight / 2 + titleHeight + gap,
@@ -240,52 +437,55 @@
              height: contentHeight,
              fill: contentFill,
              radius: 15,
              cursor: 'move',
              cursor: "move",
              stroke: stroke,
              lineWidth: isDiscarded ? 1 : (cfg.selected ? 2 : 0),
              lineWidth: isDiscarded ? 1 : cfg.selected ? 2 : 0,
            },
            name: 'content-box',
            name: "content-box",
          });
          // 标题文本
          if (cfg.label) {
            group.addShape('text', {
            group.addShape("text", {
              attrs: {
                text: cfg.label,
                x: 0,
                y: -totalHeight / 2 + titleHeight / 2,
                fill: isDiscarded ? 'rgba(144, 147, 153, 1)' : '#fff',
                fill: isDiscarded ? "rgba(144, 147, 153, 1)" : "#fff",
                fontSize: 12,
                textAlign: 'center',
                textBaseline: 'middle',
                fontWeight: 'bold',
                cursor: 'move',
                textAlign: "center",
                textBaseline: "middle",
                fontWeight: "bold",
                cursor: "move",
              },
              name: 'title-text',
              name: "title-text",
            });
          }
          // 内容文本
          let content = '';
          if (cfg.label === '传代计划数') {
          let content = "";
          if (cfg.label === "传代计划数") {
            content = `${cfg.planCount || 0}`;
          } else if (cfg.number) {
            content = cfg.label === '母代' ? `代传菌种编号:${cfg.number}` : `接种菌种编号:${cfg.number}`;
            content =
              cfg.label === "母代"
                ? `代传菌种编号:${cfg.number}`
                : `接种菌种编号:${cfg.number}`;
          }
          if (content) {
            group.addShape('text', {
            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',
                textAlign: "center",
                textBaseline: "middle",
                cursor: "move",
              },
              name: 'content-text',
              name: "content-text",
            });
          }
@@ -305,71 +505,71 @@
      });
      this.graph = new G6.Graph({
        container: 'mountNode',
        container: "mountNode",
        width,
        height,
        fitView: true,
        fitViewPadding: 30,
        animate: false,
        enabledStack: false,
        renderer: 'canvas',
        renderer: "canvas",
        minZoom: 0.3,
        maxZoom: 2,
        defaultZoom: 1,
        layout: {
          type: 'dagre',
          rankdir: 'LR',
          align: 'UL',
          nodesep: 30,  // 减小节点间距
          ranksep: 50,  // 减小层级间距
          type: "dagre",
          rankdir: "LR",
          align: "UL",
          nodesep: 30, // 减小节点间距
          ranksep: 50, // 减小层级间距
          controlPoints: true,
        },
        modes: {
          default: [
            {
              type: 'drag-canvas',
              type: "drag-canvas",
              enableOptimize: true,
              direction: 'both',
              direction: "both",
              scalableRange: 0.1,
              dragTimesOfScale: 0.1,
              onlyChangeComputeZoom: true,
            },
            {
              type: 'zoom-canvas',
              type: "zoom-canvas",
              sensitivity: 1.5,
              enableOptimize: true,
            },
            {
              type: 'drag-node',
              type: "drag-node",
              enableDelegate: true,
              delegateStyle: {
                fill: '#f3f3f3',
                stroke: '#ccc',
                fill: "#f3f3f3",
                stroke: "#ccc",
                opacity: 0.5,
              },
              updateEdge: false,
              enableOptimize: true,
              optimizeZoom: 0.7,
              damping: 0.1,
            }
          ]
            },
          ],
        },
        defaultNode: {
          type: 'custom-node',
          type: "custom-node",
          style: {
            fill: 'l(0) 0:#0ACBCA 1:#049C9A',
            fill: "l(0) 0:#0ACBCA 1:#049C9A",
          },
        },
        defaultEdge: {
          type: 'cubic-horizontal',
          type: "cubic-horizontal",
          style: {
            stroke: 'rgba(4, 156, 154, 1)',
            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)',
              fill: "rgba(4, 156, 154, 1)",
              stroke: "rgba(4, 156, 154, 1)",
            },
          },
        },
@@ -377,105 +577,183 @@
        optimizeLayoutAnimation: true,
      });
      const canvas = this.graph.get('canvas');
      canvas.set('localRefresh', false);
      canvas.set('autoDraw', true);
      canvas.set('animating', false);
      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:dragstart", () => {
        canvas.set("localRefresh", false);
        this.graph.get("canvas").draw();
      });
      this.graph.on('node:drag', (e) => {
      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 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;
            return (
              source.get("model").nodeId === model.nodeId ||
              target.get("model").nodeId === model.nodeId
            );
          });
          edges.forEach(edge => {
          edges.forEach((edge) => {
            this.graph.refreshItem(edge);
          });
          throttleTimer = null;
        }, throttleInterval);
      });
      this.graph.on('node:dragend', (e) => {
      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 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;
          return (
            source.get("model").nodeId === model.nodeId ||
            target.get("model").nodeId === model.nodeId
          );
        });
        edges.forEach(edge => {
        edges.forEach((edge) => {
          this.graph.refreshItem(edge);
        });
        canvas.set('localRefresh', true);
        this.graph.get('canvas').draw();
        canvas.set("localRefresh", true);
        this.graph.get("canvas").draw();
      });
      this.graph.data(this.graphData);
      // 在设置数据之前,确保每个节点都有 id 字段
      const processedData = {
        nodes: this.graphData.nodes.map((node) => ({
          ...node,
          id: node.nodeId, // 添加 id 字段,值与 nodeId 相同
        })),
        edges: this.graphData.edges.map((edge) => ({
          ...edge,
          id: `${edge.source}-${edge.target}`, // 为边添加唯一 id
        })),
      };
      this.graph.data(processedData);
      this.graph.render();
      let debounceTimer = null;
      this.graph.on('afterchange', () => {
      this.graph.on("afterchange", () => {
        if (debounceTimer) clearTimeout(debounceTimer);
        debounceTimer = setTimeout(() => {
          if (!canvas.get('destroyed')) {
          if (!canvas.get("destroyed")) {
            canvas.draw();
          }
        }, 16);
      });
    },
    // 添加更新图表数据的方法
    updateGraphData() {
      if (!this.graph) return;
      // 将扁平结构转换为树形结构
      const buildTree = (nodes, edges) => {
        const nodeMap = new Map();
        const rootNodes = [];
        // 首先创建所有节点的映射
        nodes.forEach((node) => {
          nodeMap.set(node.nodeId, {
            ...node,
            id: node.nodeId,
            level: 0 // 添加层级标记
          });
        });
        // 处理边,构建父子关系
        edges.forEach((edge) => {
          const parent = nodeMap.get(edge.source);
          const child = nodeMap.get(edge.target);
          if (parent && child) {
            // 设置子节点的层级
            child.level = parent.level + 1;
          }
        });
        // 找出根节点(没有父节点的节点)
        nodes.forEach((node) => {
          const hasParent = edges.some((edge) => edge.target === node.nodeId);
          if (!hasParent) {
            rootNodes.push(nodeMap.get(node.nodeId));
          }
        });
        // 将所有节点按层级排序
        const allNodes = Array.from(nodeMap.values());
        allNodes.sort((a, b) => a.level - b.level);
        return allNodes;
      };
      const treeData = buildTree(this.graphData.nodes, this.graphData.edges);
      // 更新图表数据
      this.graph.changeData({
        nodes: this.graphData.nodes.map((node) => ({
          ...node,
          id: node.nodeId,
        })),
        edges: this.graphData.edges.map((edge) => ({
          ...edge,
          id: `${edge.source}-${edge.target}`,
        })),
      });
      // 保存树形结构数据
      this.treeData = treeData;
    },
    initEvents() {
      // 监听窗口大小变化
      window.addEventListener('resize', this.handleResize);// 添加触摸事件处理
      window.addEventListener("resize", this.handleResize); // 添加触摸事件处理
      const handleNodeClick = (evt) => {
        evt.preventDefault(); // 阻止默认触摸行为
        const node = evt.item;
        const nodeModel = node.getModel();
        const nodeModel = node.get("model");
        // 如果节点已废弃,不允许任何操作
        if (!nodeModel.isDiscarded) {
          this.$message.warning('该节点已废弃,不能进行操作');
        if (nodeModel.status === 2) {
          this.$message.warning("该节点已废弃,不能进行操作");
          return;
        }
        // 更新选中节点
        this.selectedNode = nodeModel;
        // 更新节点选中状态
        this.graphData.nodes.forEach(n => {
          n.selected = n.id === nodeModel.id;
        this.graphData.nodes.forEach((n) => {
          n.selected = n.nodeId === nodeModel.nodeId;
        });
        this.graph.changeData(this.graphData);
        this.updateGraphData();
      };
      this.graph.on('node:click', handleNodeClick);
      this.graph.on('node:touchstart', handleNodeClick);
      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 => {
        this.graphData.nodes.forEach((n) => {
          n.selected = false;
        });
        this.graph.changeData(this.graphData);
        this.updateGraphData();
      };
      this.graph.on('canvas:click', handleCanvasClick);
      this.graph.on('canvas:touchstart', handleCanvasClick);
      this.graph.on("canvas:click", handleCanvasClick);
      this.graph.on("canvas:touchstart", handleCanvasClick);
    },
    handleResize() {
      if (this.graph) {
        const container = document.getElementById('mountNode');
        const container = document.getElementById("mountNode");
        const width = container.scrollWidth;
        const height = container.scrollHeight || 600;
        this.graph.changeSize(width, height);
@@ -488,37 +766,41 @@
          if (valid) {
            this.$refs.parentForm.openInitData({
              strainName: this.form.strainName,
              strainNo: this.form.strainNo,
              strainCode: this.form.strainCode,
              strainSourceStart: this.form.strainSourceStart,
              strainSourceEnd: this.form.strainSourceEnd,
            });
          }
        })
        return
        });
        return;
      }
      // 如果选中了传代计划数节点,新增下一代
      if (this.selectedNode && this.selectedNode.label === '传代计划数') {
      if (this.selectedNode && this.selectedNode.label === "传代计划数") {
        const nodeModel = this.selectedNode;
        // 检查是否已达到计划数
        if (nodeModel.currentCount >= nodeModel.planCount) {
          this.$message.warning('已达到计划数,不能再添加');
          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);
        const parentEdge = this.graphData.edges.find(
          (e) => e.target === nodeModel.nodeId
        );
        const parentNode = this.graphData.nodes.find(
          (n) => n.nodeId === parentEdge.source
        );
        // 如果父节点是孙代,不允许添加
        if (parentNode.label === '孙代') {
          this.$message.warning('孙代节点不能再生成下一代');
        if (parentNode.label === "孙代") {
          this.$message.warning("孙代节点不能再生成下一代");
          return;
        }
        const isParent = parentNode.label === '母代';
        const nextLevel = isParent ? '子代' : '孙代';
        const isParent = parentNode.label === "母代";
        const nextLevel = isParent ? "子代" : "孙代";
        this.showDiscarded = true;
        this.isAddingNode = true;
@@ -526,12 +808,12 @@
          title: `新增${nextLevel}`,
          form: {
            strainName: this.form.strainName,
            strainNo: this.form.strainNo,
            isDiscarded: true
          }
        })
            strainCode: this.form.strainCode,
            isDiscarded: true,
          },
        });
      } else {
        this.$message.warning('请选择传代计划数节点');
        this.$message.warning("请选择传代计划数节点");
      }
    },
    handleAddParent(value) {
@@ -539,19 +821,19 @@
      const parentId = `parent-${++this.nodeCount}`;
      this.graphData.nodes.push({
        id: parentId,
        label: '母代',
        number: value.strainNo.trim(),
        nodeId: parentId,
        label: "母代",
        number: value.strainCode.trim(),
        data: value,
        isDiscarded: true,
        status: 1,
        x: 200,
        y: 200,
        style: {
          fill: '#00B5AA',
          fill: "#00B5AA",
        },
      });
      this.graph.changeData(this.graphData);
      this.$message.success('母代节点添加成功');
      this.updateGraphData();
      this.$message.success("母代节点添加成功");
      this.$refs.parentForm.closeDialog();
    },
    handleDialogClose() {
@@ -561,37 +843,43 @@
      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 parentEdge = this.graphData.edges.find(
          (e) => e.target === nodeModel.nodeId
        );
        const parentNode = this.graphData.nodes.find(
          (n) => n.nodeId === parentEdge.source
        );
        const isParent = parentNode.label === "母代";
        const nextLevel = isParent ? "子代" : "孙代";
        const childId = `child-${++this.nodeCount}`;
        this.graphData.nodes.push({
          id: childId,
          nodeId: childId,
          label: nextLevel,
          number: value.inoculateNo.trim(),
          isDiscarded: value.isDiscarded,
          status: value.status,
          data: value,
          style: {
            fill: value.isDiscarded ? '#999' : '#00B5AA',
            opacity: value.isDiscarded ? 0.3 : (isParent ? 0.6 : 0.4),
            fill: value.status === 1 ? "#00B5AA" : "#999",
            opacity: value.status === 1 ? (isParent ? 0.6 : 0.4) : 0.3,
          },
        });
        this.graphData.edges.push({
          source: nodeModel.id,
          source: nodeModel.nodeId,
          target: childId,
          style: {
            stroke: 'rgba(4, 156, 154, 1)',
            stroke: "rgba(4, 156, 154, 1)",
            lineWidth: 1,
          },
        });
        const nodeIndex = this.graphData.nodes.findIndex(n => n.id === nodeModel.id);
        const nodeIndex = this.graphData.nodes.findIndex(
          (n) => n.nodeId === nodeModel.nodeId
        );
        this.graphData.nodes[nodeIndex].currentCount++;
        this.graph.changeData(this.graphData);
        this.updateGraphData();
        this.$message.success(`${nextLevel}添加成功`);
        this.$refs.addSublevelForm.closeDialog();
@@ -599,88 +887,99 @@
      } else {
        // 编辑节点的处理逻辑
        const nodeModel = this.selectedNode;
        const nodeIndex = this.graphData.nodes.findIndex(n => n.id === nodeModel.id);
        const nodeIndex = this.graphData.nodes.findIndex(
          (n) => n.nodeId === nodeModel.nodeId
        );
        if (nodeIndex > -1) {
          if (nodeModel.label === '传代计划数') {
          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;
              this.graphData.nodes[nodeIndex].status = value.status;
              // 如果设置为废弃状态,同时废弃所有子节点
              if (value.isDiscarded) {
              if (value.status === 2) {
                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);
                  const childEdges = this.graphData.edges.filter(
                    (e) => e.source === parentId
                  );
                  childEdges.forEach((edge) => {
                    const childNode = this.graphData.nodes.find(
                      (n) => n.nodeId === edge.target
                    );
                    if (childNode) {
                      const childIndex = this.graphData.nodes.findIndex(n => n.id === childNode.id);
                      const childIndex = this.graphData.nodes.findIndex(
                        (n) => n.nodeId === childNode.nodeId
                      );
                      if (childIndex > -1) {
                        this.graphData.nodes[childIndex].isDiscarded = true;
                        this.graphData.nodes[childIndex].style.fill = '#999';
                        this.graphData.nodes[childIndex].status = 2;
                        this.graphData.nodes[childIndex].style.fill = "#999";
                        this.graphData.nodes[childIndex].style.opacity = 0.3;
                        // 递归处理子节点的子节点
                        discardChildren(childNode.id);
                        discardChildren(childNode.nodeId);
                      }
                    }
                  });
                };
                discardChildren(nodeModel.id);
                discardChildren(nodeModel.nodeId);
              }
            }
          }
          this.graph.changeData(this.graphData);
          this.$message.success('修改成功');
          this.updateGraphData();
          this.$message.success("修改成功");
          this.dialogVisible = false;
        }
      }
    },
    setGenerationPlan() {
      if (!this.selectedNode) {
        this.$message.warning('请先选择节点');
        this.$message.warning("请先选择节点");
        return;
      }
      const nodeModel = this.selectedNode;
      if (nodeModel.label === '孙代') {
        this.$message.warning('孙代节点不能再生成传代计划数');
      if (nodeModel.label === "孙代") {
        this.$message.warning("孙代节点不能再生成传代计划数");
        return;
      }
      if (nodeModel.label === '传代计划数') {
        this.$message.warning('传代计划数节点不能再设置计划数');
      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 === '传代计划数')
      const hasGenerationNode = this.graphData.edges.some(
        (e) =>
          e.source === nodeModel.nodeId &&
          this.graphData.nodes.some(
            (n) => n.nodeId === e.target && n.label === "传代计划数"
          )
      );
      if (hasGenerationNode) {
        this.$message.warning('该节点已经存在传代计划数节点');
        this.$message.warning("该节点已经存在传代计划数节点");
        return;
      }
      if (nodeModel.label === '子代') {
      if (nodeModel.label === "子代") {
        this.$refs.addSublevelPlan.openInitData({
          ...nodeModel.data,
          label: nodeModel.label,
          strainName: this.form.strainName,
          strainNo: this.form.strainNo,
          strainCode: this.form.strainCode,
          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,
          strainCode: this.form.strainCode,
          strainSourceStart: this.form.strainSourceStart,
          strainSourceEnd: this.form.strainSourceEnd,
        })
        });
      }
    },
    handleAddPlan(value) {
@@ -688,55 +987,67 @@
      const generationId = `generation-${++this.nodeCount}`;
      this.graphData.nodes.push({
        id: generationId,
        label: '传代计划数',
        planCount: value.count,
        nodeId: generationId,
        label: "传代计划数",
        planCount: value.generationCount,
        data: value,
        isDiscarded: true,
        status: 1,
        currentCount: 0,
        style: {
          fill: '#00B5AA',
          fill: "#00B5AA",
        },
      });
      this.graphData.edges.push({
        source: nodeModel.id,
        source: nodeModel.nodeId,
        target: generationId,
        style: {
          stroke: 'rgba(4, 156, 154, 1)',
          stroke: "rgba(4, 156, 154, 1)",
          lineWidth: 1,
        },
      });
      this.graph.changeData(this.graphData);
      this.$message.success('传代计划数设置成功');
      this.$refs.planForm.closeDialog()
      this.updateGraphData();
      this.$message.success("传代计划数设置成功");
      this.$refs.planForm.closeDialog();
      this.$refs.addSublevelPlan.closeDialog();
    },
    showDetail() {
      if (!this.selectedNode) {
        this.$message.warning('请先选择节点');
        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 === '孙代') {
      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' });
          form: { ...nodeModel.data },
        });
      } else if (nodeModel.label === "传代计划数") {
        const nodeIndex = this.graphData.nodes.findIndex(
          (n) => n.nodeId === nodeModel.nodeId
        );
        if (this.graphData.nodes[nodeIndex - 1].label === "子代") {
          this.$refs.addSublevelPlan.openInitData({
            ...nodeModel.data,
            status: "detail",
          });
        } else {
          this.$refs.planForm.openInitData({ ...nodeModel.data, status: 'detail' });
          this.$refs.planForm.openInitData({
            ...nodeModel.data,
            status: "detail",
          });
        }
      }
    }
    },
  },
};
</script>
@@ -758,7 +1069,7 @@
  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;
  border: 4px solid #ffffff;
  margin-top: 30px;
  .header {
@@ -899,4 +1210,4 @@
    border-radius: 4px;
  }
}
</style>
</style>
culture/src/views/pedigree-chart/components/AddSublevelForm.vue
@@ -14,8 +14,8 @@
                    </el-form-item>
                </el-col>
                <el-col :span="10">
                    <el-form-item label="传代菌种编号" prop="strainNo">
                        <el-input disabled v-model="form.strainNo"></el-input>
                    <el-form-item label="传代菌种编号" prop="strainCode">
                        <el-input disabled v-model="form.strainCode"></el-input>
                    </el-form-item>
                </el-col>
                <el-col :span="10">
@@ -36,15 +36,15 @@
                    </el-form-item>
                </el-col>
            </el-row>
            <el-form-item label="保存/废弃" required>
            <el-form-item label="保存/废弃">
                <div class="flex-row">
                    <div @click="handleStatus('save')" :class="form.isDiscarded && 'active'">保存</div>
                    <div @click="handleStatus('discard')" :class="!form.isDiscarded && 'active'">废弃</div>
                    <div :class="form.status === 1 && 'active'" @click="handleStatus(1)">保存</div>
                    <div :class="form.status === 2 && 'active'" @click="handleStatus(2)">废弃</div>
                </div>
            </el-form-item>
            <el-row :gutter="20">
                <el-col :span="10">
                    <el-form-item v-if="!form.isDiscarded" label="废弃原因说明" required>
                    <el-form-item v-if="form.status === 2" label="废弃原因说明" required>
                        <el-input :disabled="!dialogTitle.includes('新增')" v-model="form.discardReason"
                            placeholder="请输入"></el-input>
                    </el-form-item>
@@ -52,7 +52,7 @@
            </el-row>
            <el-row :gutter="20">
                <el-col :span="10">
                    <el-form-item label="菌种入库时间" prop="inTime">
                    <el-form-item label="菌种入库时间" prop="inTime" required>
                        <el-input disabled v-model="form.inTime"></el-input>
                    </el-form-item>
                </el-col>
@@ -71,10 +71,11 @@
            </el-row>
        </el-form>
        <div v-if="dialogTitle.includes('新增')" class="dialog-footer">
            <el-button type="primary" @click="handleSubmit">提交</el-button>
            <el-button type="primary" @click="handleSubmit">提交签字</el-button>
        </div>
    </el-dialog>
</template>
<script>
export default {
    data() {
@@ -82,36 +83,83 @@
            dialogVisible: false,
            dialogTitle: '',
            form: {
                isDiscarded: true
                thisName: '',
                thisTime: '',
                strainCode: '',
                strainName: '',
                inoculateNo: '',
                inoculateName: '',
                status: 1,
                inTime: '',
                discardReason: ''
            },
            rules: {
                isDiscarded: [
                    { required: true, message: '请选择废弃状态', trigger: 'blur' }
                thisName: [
                    { required: true, message: '请输入接种操作人', trigger: 'blur' }
                ],
                thisTime: [
                    { required: true, message: '请输入接种操作时间', trigger: 'blur' }
                ],
                strainCode: [
                    { required: true, message: '请输入传代菌种编号', trigger: 'blur' }
                ],
                strainName: [
                    { required: true, message: '请输入传代菌种名称', trigger: 'blur' }
                ],
                inoculateNo: [
                    { required: true, message: '请输入接种菌种编号', trigger: 'blur' }
                ],
                inoculateName: [
                    { required: true, message: '请输入接种菌种名称', trigger: 'blur' }
                ],
            },
                ]
            }
        }
    },
    methods: {
        openInitData(value) {
            this.dialogTitle = value.title
            this.form = value.form
            // 获取用户信息
            const userInfo = JSON.parse(sessionStorage.getItem('userInfo') || '{}')
            // 获取当前时间并格式化
            const now = new Date()
            const formatTime = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`
            this.form = {
                ...this.form,
                ...value.form,
                thisName: userInfo.nickName || '',
                thisTime: formatTime,
                inTime: formatTime
            }
            this.dialogVisible = true
        },
        closeDialog() {
            this.dialogVisible = false
            // 重置表单数据
            this.form = {
                thisName: '',
                thisTime: '',
                strainCode: '',
                strainName: '',
                inoculateNo: '',
                inoculateName: '',
                status: 1,
                inTime: '',
                discardReason: ''
            }
            // 重置表单验证
            this.$refs.form && this.$refs.form.resetFields()
        },
        handleSubmit() {
            this.$emit('addNodeSign', this.form, 3)
            this.$refs.form.validate((valid) => {
                if (valid) {
                    this.$emit('addNodeSign', this.form, 3)
                }
            })
        },
        handleStatus(status) {
            if (!this.dialogTitle.includes('新增')) return
            this.form.isDiscarded = status === 'save'
            this.form.status = status
            this.$forceUpdate()
        }
    }
culture/src/views/pedigree-chart/components/AddSublevelPlan.vue
@@ -5,8 +5,8 @@
        <el-form :model="planForm" :rules="planRules" ref="planForm" label-position="top">
            <el-row :gutter="20">
                <el-col :span="10">
                    <el-form-item label="传代菌种编号" prop="strainNo">
                        <el-input disabled v-model="planForm.strainNo"></el-input>
                    <el-form-item label="传代菌种编号" prop="strainCode">
                        <el-input disabled v-model="planForm.strainCode"></el-input>
                    </el-form-item>
                </el-col>
                <el-col :span="10">
@@ -29,8 +29,8 @@
                <el-col :span="10">
                    <el-form-item label="保存/废弃">
                        <div class="flex-row">
                            <div :class="planForm.isDiscarded && 'active'">保存</div>
                            <div :class="!planForm.isDiscarded && 'active'">废弃</div>
                            <div :class="planForm.isDiscarded && 'active'" @click="handleStatus('save')">保存</div>
                            <div :class="!planForm.isDiscarded && 'active'" >废弃</div>
                        </div>
                    </el-form-item>
                </el-col>
@@ -44,18 +44,19 @@
            </el-row>
            <el-row :gutter="20">
                <el-col :span="10">
                    <el-form-item label="传代计划数" prop="count">
                        <el-input-number :disabled="planForm.status === 'detail'" v-model="planForm.count"
                    <el-form-item label="传代计划数" prop="generationCount">
                        <el-input-number :disabled="planForm.status === 'detail'" v-model="planForm.generationCount"
                            :controls="false" :min="1" placeholder="请输入" />
                    </el-form-item>
                </el-col>
            </el-row>
        </el-form>
        <div v-if="planForm.status !== 'detail'" class="dialog-footer">
            <el-button type="primary" @click="handleAddPlan">提交签字</el-button>
            <el-button type="primary" @click="handleSubmit">提交签字</el-button>
        </div>
    </el-dialog>
</template>
<script>
export default {
    data() {
@@ -63,9 +64,28 @@
            planDialogVisible: false,
            planForm: {
                status: 'add',
                strainCode: '',
                strainName: '',
                inoculateNo: '',
                inoculateName: '',
                generationCount: 1,
                isDiscarded: true,
                inTime: ''
            },
            planRules: {
                count: [
                strainCode: [
                    { required: true, message: '请输入传代菌种编号', trigger: 'blur' }
                ],
                strainName: [
                    { required: true, message: '请输入传代菌种名称', trigger: 'blur' }
                ],
                inoculateNo: [
                    { required: true, message: '请输入接种菌种编号', trigger: 'blur' }
                ],
                inoculateName: [
                    { required: true, message: '请输入接种菌种名称', trigger: 'blur' }
                ],
                generationCount: [
                    { required: true, message: '请输入传代计划数', trigger: 'blur' }
                ]
            }
@@ -73,7 +93,10 @@
    },
    methods: {
        openInitData(value) {
            this.planForm = value
            this.planForm = {
                ...this.planForm,
                ...value
            }
            this.openDialog()
        },
        openDialog() {
@@ -81,59 +104,37 @@
        },
        closeDialog() {
            this.planDialogVisible = false
            // 重置表单数据
            this.planForm = {
                status: 'add',
                strainCode: '',
                strainName: '',
                inoculateNo: '',
                inoculateName: '',
                generationCount: 1,
                isDiscarded: true,
                inTime: ''
            }
            // 重置表单验证
            this.$refs.planForm && this.$refs.planForm.resetFields()
        },
        handleAddPlan() {
        handleSubmit() {
            this.$refs.planForm.validate((valid) => {
                if (valid) {
                    this.$emit('addNodeSign', this.planForm, 2)
                }
            })
        },
        handleStatus(status) {
            if (this.planForm.status === 'detail') return
            this.planForm.isDiscarded = status === 'save'
            this.$forceUpdate()
        }
    }
}
</script>
<style scoped lang="less">
.flex-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    @media (max-width: 768px) {
        flex-direction: column;
        align-items: flex-start;
        width: 100%;
    }
}
.input-wrapper {
    @media (min-width: 769px) {
        width: 290px;
        min-width: 290px;
    }
    @media (max-width: 768px) {
        width: 100%;
    }
}
.fixed-width-input {
    width: 100%;
    @media (min-width: 769px) {
        width: 290px !important;
        min-width: 290px !important;
    }
}
.form-text {
    margin: 0 8px;
    white-space: nowrap;
    @media (max-width: 768px) {
        margin: 8px 0;
    }
}
.dialog-footer {
    margin-top: 39px;
    display: flex;
culture/src/views/pedigree-chart/components/ParentForm.vue
@@ -1,47 +1,38 @@
<template>
    <!-- 新增母代弹窗 -->
    <el-dialog :title="parentForm.status === 'detail' ? '母代详情' : '新增母代'" :visible.sync="addParentDialogVisible"
    <el-dialog :title="planForm.status === 'detail' ? '母代详情' : '新增母代'" :visible.sync="planDialogVisible"
        width="40%" :close-on-click-modal="false">
        <el-form :model="parentForm" ref="parentForm" label-position="top">
            <el-form-item label="菌种源" prop="strainSourceStart">
                <div class="flex-row">
                    <div class="input-wrapper">
                        <el-input disabled v-model="parentForm.strainSourceStart" class="fixed-width-input"></el-input>
                    </div>
                    <span class="form-text">代—</span>
                    <div class="input-wrapper">
                        <el-input disabled v-model="parentForm.strainSourceEnd" class="fixed-width-input"></el-input>
                    </div>
                    <span class="form-text">细胞库</span>
                </div>
            </el-form-item>
        <el-form :model="planForm" :rules="planRules" ref="planForm" label-position="top">
            <el-row :gutter="20">
                <el-col :span="10">
                    <el-form-item label="代传菌种编号" prop="strainNo">
                        <el-input disabled v-model="parentForm.strainNo"></el-input>
                <el-col :span="24">
                    <el-form-item label="菌种源" prop="strainSourceStart">
                        <div class="input-group">
                            <div class="input-wrapper">
                                <el-input disabled v-model="planForm.strainSourceStart" class="fixed-width-input"></el-input>
                            </div>
                            <span class="form-text">代—</span>
                            <div class="input-wrapper">
                                <el-input disabled v-model="planForm.strainSourceEnd" class="fixed-width-input"></el-input>
                            </div>
                            <span class="form-text">细胞库</span>
                        </div>
                    </el-form-item>
                </el-col>
                <el-col :span="10">
                    <el-form-item label="代传菌种名称" prop="strainName">
                        <el-input disabled v-model="parentForm.strainName"></el-input>
                    </el-form-item>
                </el-col>
            </el-row>
            <el-row v-if="parentForm.status === 'detail'" :gutter="20">
                <el-col :span="10">
                    <el-form-item label="接种操作人签字">
                        <el-image />
                    <el-form-item label="传代菌种编号" prop="strainCode">
                        <el-input disabled v-model="planForm.strainCode"></el-input>
                    </el-form-item>
                </el-col>
                <el-col :span="10">
                    <el-form-item label="菌种保藏人签字">
                        <el-image />
                    <el-form-item label="传代菌种名称" prop="strainName">
                        <el-input disabled v-model="planForm.strainName"></el-input>
                    </el-form-item>
                </el-col>
            </el-row>
        </el-form>
        <div v-if="parentForm.status !== 'detail'" class="dialog-footer">
            <el-button type="primary" @click="handleAddParent">提交</el-button>
        <div v-if="planForm.status !== 'detail'" class="dialog-footer">
            <el-button type="primary" @click="handleSubmit">提交</el-button>
        </div>
    </el-dialog>
</template>
@@ -49,29 +40,73 @@
export default {
    data() {
        return {
            addParentDialogVisible: false,
            parentForm: {},
            planDialogVisible: false,
            planForm: {
                strainCode: '',
                strainName: '',
                strainSourceStart: '',
                strainSourceEnd: '',
                isDiscarded: true
            },
            planRules: {
                strainCode: [
                    { required: true, message: '请输入传代菌种编号', trigger: 'blur' }
                ],
                strainName: [
                    { required: true, message: '请输入传代菌种名称', trigger: 'blur' }
                ],
                strainSourceStart: [
                    { required: true, message: '请输入菌种源起始', trigger: 'blur' }
                ],
                strainSourceEnd: [
                    { required: true, message: '请输入菌种源结束', trigger: 'blur' }
                ]
            }
        }
    },
    methods: {
        openInitData(value) {
            this.parentForm = value
            this.planForm = {
                ...this.planForm,
                ...value
            }
            this.openDialog()
        },
        openDialog() {
            this.addParentDialogVisible = true
            this.planDialogVisible = true
        },
        closeDialog() {
            this.addParentDialogVisible = false
            this.planDialogVisible = false
            // 重置表单数据
            this.planForm = {
                strainCode: '',
                strainName: '',
                strainSourceStart: '',
                strainSourceEnd: '',
                isDiscarded: true
            }
            // 重置表单验证
            this.$refs.planForm && this.$refs.planForm.resetFields()
        },
        handleAddParent() {
            this.$emit('addNodeSign', this.parentForm, 1)
        handleSubmit() {
            this.$refs.planForm.validate((valid) => {
                if (valid) {
                    console.log(this.planForm,'22');
                    this.$emit('addNodeSign', this.planForm, 1)
                }
            })
        },
        handleStatus(status) {
            if (this.planForm.status === 'detail') return
            this.planForm.isDiscarded = status === 'save'
            this.$forceUpdate()
        }
    }
}
</script>
<style scoped lang="less">
.flex-row {
.input-group {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
@@ -113,7 +148,7 @@
}
.dialog-footer {
    margin-top: 115px;
    margin-top: 39px;
    display: flex;
    justify-content: center;
@@ -124,4 +159,37 @@
        border-radius: 4px;
    }
}
.flex-row {
    width: 370px;
    display: flex;
    align-items: center;
    font-size: 16px;
    color: #333333;
    padding: 4px;
    border-radius: 10px;
    border: 2px solid rgba(4, 156, 154, 0.5);
    font-family: 'PingFangSCRegular';
    .flex-row-save {
        background: #049C9A;
        color: #fff;
    }
    div {
        width: 183px;
        height: 32px;
        text-align: center;
        flex-shrink: 0;
        cursor: pointer;
    }
    .active {
        font-family: 'SourceHanSansCN-Medium';
        color: #049C9A;
        background: #EBFEFD;
        box-shadow: 0px 0px 6px 0px rgba(10, 109, 108, 0.25);
        border-radius: 10px;
    }
}
</style>
culture/src/views/pedigree-chart/components/PlanForm.vue
@@ -4,7 +4,7 @@
        width="40%" :close-on-click-modal="false">
        <el-form :model="planForm" :rules="planRules" ref="planForm" label-position="top">
            <el-form-item label="菌种源" prop="strainSourceStart">
                <div class="flex-row">
                <div class="input-group">
                    <div class="input-wrapper">
                        <el-input disabled v-model="planForm.strainSourceStart" class="fixed-width-input"></el-input>
                    </div>
@@ -17,8 +17,8 @@
            </el-form-item>
            <el-row :gutter="20">
                <el-col :span="10">
                    <el-form-item label="传代菌种编号" prop="strainNo">
                        <el-input disabled v-model="planForm.strainNo"></el-input>
                    <el-form-item label="传代菌种编号" prop="strainCode">
                        <el-input disabled v-model="planForm.strainCode"></el-input>
                    </el-form-item>
                </el-col>
                <el-col :span="10">
@@ -27,26 +27,41 @@
                    </el-form-item>
                </el-col>
                <el-col :span="10">
                    <el-form-item label="传代计划数" prop="count">
                        <el-input-number :disabled="planForm.status === 'detail'" v-model="planForm.count"
                    <el-form-item label="传代计划数" prop="generationCount">
                        <el-input-number :disabled="planForm.status === 'detail'" v-model="planForm.generationCount"
                            :controls="false" :min="1" placeholder="请输入" />
                    </el-form-item>
                </el-col>
            </el-row>
        </el-form>
        <div v-if="planForm.status !== 'detail'" class="dialog-footer">
            <el-button type="primary" @click="handleAddPlan">提交签字</el-button>
            <el-button type="primary" @click="handleSubmit">提交签字</el-button>
        </div>
    </el-dialog>
</template>
<script>
export default {
    data() {
        return {
            planDialogVisible: false,
            planForm: {},
            planForm: {
                status: 'add',
                strainCode: '',
                strainName: '',
                strainSourceStart: '',
                strainSourceEnd: '',
                generationCount: '',
                isDiscarded: true
            },
            planRules: {
                count: [
                strainCode: [
                    { required: true, message: '请输入传代菌种编号', trigger: 'blur' }
                ],
                strainName: [
                    { required: true, message: '请输入传代菌种名称', trigger: 'blur' }
                ],
                generationCount: [
                    { required: true, message: '请输入传代计划数', trigger: 'blur' }
                ]
            }
@@ -54,7 +69,10 @@
    },
    methods: {
        openInitData(value) {
            this.planForm = value
            this.planForm = {
                ...this.planForm,
                ...value
            }
            this.openDialog()
        },
        openDialog() {
@@ -62,19 +80,37 @@
        },
        closeDialog() {
            this.planDialogVisible = false
            // 重置表单数据
            this.planForm = {
                status: 'add',
                strainCode: '',
                strainName: '',
                strainSourceStart: '',
                strainSourceEnd: '',
                generationCount: '',
                isDiscarded: true
            }
            // 重置表单验证
            this.$refs.planForm && this.$refs.planForm.resetFields()
        },
        handleAddPlan() {
        handleSubmit() {
            this.$refs.planForm.validate((valid) => {
                if (valid) {
                    this.$emit('addNodeSign', this.planForm, 2)
                }
            })
        },
        handleStatus(status) {
            if (this.planForm.status === 'detail') return
            this.planForm.isDiscarded = status === 'save'
            this.$forceUpdate()
        }
    }
}
</script>
<style scoped lang="less">
.flex-row {
.input-group {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
@@ -135,4 +171,37 @@
.el-input-number--small {
    width: 100%;
}
.flex-row {
    width: 370px;
    display: flex;
    align-items: center;
    font-size: 16px;
    color: #333333;
    padding: 4px;
    border-radius: 10px;
    border: 2px solid rgba(4, 156, 154, 0.5);
    font-family: 'PingFangSCRegular';
    .flex-row-save {
        background: #049C9A;
        color: #fff;
    }
    div {
        width: 183px;
        height: 32px;
        text-align: center;
        flex-shrink: 0;
        cursor: pointer;
    }
    .active {
        font-family: 'SourceHanSansCN-Medium';
        color: #049C9A;
        background: #EBFEFD;
        box-shadow: 0px 0px 6px 0px rgba(10, 109, 108, 0.25);
        border-radius: 10px;
    }
}
</style>
culture/src/views/pedigree-chart/service.js
@@ -5,6 +5,11 @@
  return axios.post('/api/t-pedigree-chart/pageList', { ...data })
}
// 新增谱系图-新增谱系图
export const add = (data) => {
  return axios.post('/api/t-pedigree-chart/addProgenitor', { ...data })
}
// 删除菌种库
export const deleteStrainLibrary = (params) => {
  return axios.delete('/open/t-train-library/deleteById', { params })
laboratory/src/layouts/components/HeaderNav.vue
@@ -17,7 +17,7 @@
      </div>
      <div class="user-info">
        <img src="@/assets/public/photo.png" />
        <div class="user-info-text">欢迎您,admin</div>
        <div class="user-info-text">欢迎您,{{ userInfo.nickName }}</div>
        <div class="user-info-line"></div>
        <div @click="outLogin" class="user-info-out">
          <img src="@/assets/public/logOut.png" />