13404089107
2025-05-28 a60dc30be50598fe2d1acb42f8171ec69e37b436
Merge branch 'main' of http://120.76.84.145:10101/gitblit/r/H5/leshan-laboratory
13个文件已修改
12个文件已添加
2059 ■■■■ 已修改文件
culture/src/components/TableSlot/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/pedigree-chart/add.vue 142 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/pedigree-chart/addProgenitor.vue 929 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/pedigree-chart/components/AddSublevelForm.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/pedigree-chart/components/AddSublevelPlan.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/pedigree-chart/components/PlanForm.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/pedigree-chart/index.vue 51 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/pedigree-chart/progenitorComponents/AddAncestor.vue 220 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/pedigree-chart/progenitorComponents/AddSublevelForm.vue 149 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/pedigree-chart/progenitorComponents/PlanForm.vue 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/pedigree-chart/service.js 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/assets/login/cardBg.png 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/assets/login/img1.png 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/assets/login/img2.png 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/assets/login/img3.png 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/assets/login/img4.png 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/assets/login/mi.png 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/assets/login/midBg.png 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/assets/login/notice.png 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/assets/login/rili.png 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/assets/login/time.png 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/layouts/components/HeaderNav.vue 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/router/index.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/middleground/index.vue 466 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/middleground/service.js 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/components/TableSlot/index.vue
@@ -13,7 +13,7 @@
            </template>
            <template v-if="$slots.table">
                <Table :data="tableData" :total="total" :height="height" :queryForm="queryForm"
                    @currentChange="handleCurrentChange" @sizeChange="handleSizeChange">
                    @handleCurrentChange="handleCurrentChange" @handleSizeChange="handleSizeChange">
                    <slot name="table"></slot>
                </Table>
            </template>
culture/src/views/pedigree-chart/add.vue
@@ -120,7 +120,7 @@
import AddSublevelForm from "./components/AddSublevelForm.vue";
import AddSublevelPlan from "./components/AddSublevelPlan.vue";
import ConfirmStorageDialog from "@/components/confirm-storage-dialog";
import { add } from './service'
import { add } from "./service";
export default {
  name: "AddPedigree",
  components: {
@@ -207,123 +207,49 @@
    handleSubmit() {
      this.$refs.pedigreeForm.validate((valid) => {
        if (valid) {
          if (this.treeData.length == 0) {
          if (this.treeData.length === 0) {
            this.$message.warning("请新增节点");
            return;
          }
          // 处理节点数据,将data字段中的数据提升到根级别
          const processNodeData = (node) => {
          const processNodeData = (node, level = 0) => {
            const processedNode = {
              ...node,
              ...node.data,
              children: [],
              level,
              type: [1, 3, 5].includes(level + 1) ? 1 : 2,
            };
            delete processedNode.data;
            return processedNode;
          };
          // 将扁平结构转换为嵌套结构
          const buildNestedStructure = (nodes) => {
            const levelMap = {
              0: 'one',
              1: 'two',
              2: 'three',
              3: 'four',
              4: 'five'
            };
          // 构建 children 树结构
          const buildTree = (nodes, edges) => {
            // 创建节点映射,提升性能
            const nodeMap = new Map(
              nodes.map((node) => [node.nodeId, processNodeData(node)])
            );
            // 处理每个节点
            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;
            });
            // 创建子节点ID集合,用于快速查找根节点
            const childNodeIds = new Set(edges.map((edge) => edge.target));
            // 构建嵌套结构
            const result = {};
            const nodeMap = new Map();
            // 首先创建所有节点的映射
            processedNodes.forEach(node => {
              nodeMap.set(node.nodeId, { ...node });
            });
            // 处理边关系,构建嵌套结构
            this.graphData.edges.forEach(edge => {
            // 构建父子关系
            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);
                          }
                        }
                      });
                    }
                  });
                }
                child.level = parent.level + 1;
                child.type = [1, 3, 5].includes(child.level + 1) ? 1 : 2;
                parent.children.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;
            // 返回根节点数组
            return nodes
              .filter((node) => !childNodeIds.has(node.nodeId))
              .map((node) => nodeMap.get(node.nodeId));
          };
          // 获取基础表单数据
@@ -335,11 +261,10 @@
              strainCode: this.form.strainCode,
              strainName: this.form.strainName,
            },
            ...(this.treeData.length > 0 ? buildNestedStructure(this.treeData) : {})
            one: buildTree(this.graphData.nodes, this.graphData.edges),
          };
          console.log("提交的数据:", baseData);
          return
          console.log("提交的数据:", baseData, this.graphData);
          add(baseData).then((res) => {
            if (res.code == 200) {
              this.$message.success("保存成功");
@@ -669,7 +594,7 @@
          nodeMap.set(node.nodeId, {
            ...node,
            id: node.nodeId,
            level: 0 // 添加层级标记
            level: 0, // 添加层级标记
          });
        });
@@ -691,11 +616,16 @@
          }
        });
        // 将所有节点按层级排序
        // 将所有节点按层级排序并设置type字段
        const allNodes = Array.from(nodeMap.values());
        allNodes.sort((a, b) => a.level - b.level);
        return allNodes;
        // 设置type字段:第1,3,5层为1,其他层级为2
        allNodes.forEach((node) => {
          node.type = [1, 3, 5].includes(node.level + 1) ? 1 : 2;
        });
        return allNodes; // 返回所有节点,包含type字段
      };
      const treeData = buildTree(this.graphData.nodes, this.graphData.edges);
@@ -817,8 +747,6 @@
      }
    },
    handleAddParent(value) {
      console.log(value);
      const parentId = `parent-${++this.nodeCount}`;
      this.graphData.nodes.push({
        nodeId: parentId,
culture/src/views/pedigree-chart/addProgenitor.vue
@@ -1,20 +1,34 @@
<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="传代菌种编号" 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" />
@@ -32,8 +46,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>
@@ -41,31 +59,46 @@
        <div class="strain-flow-chart">
          <div id="mountNode"></div>
        </div>
        <el-button
          v-if="!$route.query.id"
          type="primary"
          @click="handleSubmit"
          style="width: 150px"
          >保存</el-button
        >
      </div>
      <div class="end-btn">
        <el-button type="primary" @click="handleSubmit">提交</el-button>
        <!-- <el-button type="primary" @click="handleSubmit">提交</el-button>
        <el-button @click="handleDraft">存草稿</el-button>
        <el-button @click="handleCancel">取消</el-button>
        <el-button @click="handleCancel">取消</el-button> -->
      </div>
    </el-form>
    <AddAncestor ref="addAncestor" @addNodeSign="addNodeSign" />
    <PlanForm ref="planForm" @addNodeSign="addNodeSign" />
    <AddSublevelForm ref="addSublevelForm" @addNodeSign="addNodeSign" />
    <ConfirmStorageDialog name="接种操作人签字" :visible.sync="confirmStorageDialogVisible"
      @confirm="handleSignatureConfirm" />
    <ConfirmStorageDialog
      name="接种操作人签字"
      :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 PlanForm from "./progenitorComponents/PlanForm.vue";
import AddAncestor from "./progenitorComponents/AddAncestor.vue";
import AddSublevelForm from "./progenitorComponents/AddSublevelForm.vue";
import ConfirmStorageDialog from "@/components/confirm-storage-dialog";
import { add, getDetail, addProgenitorChild, addProgenitor } from "./service";
export default {
  name: "AddPedigree",
@@ -73,24 +106,18 @@
    PlanForm,
    AddAncestor,
    AddSublevelForm,
    ConfirmStorageDialog
    ConfirmStorageDialog,
  },
  data() {
    return {
      signatureVisible: false,
      form: {
        strainSource: "",
        generation: "",
        cellBank: "",
        strainNo: "",
        strainCode: "",
        strainName: "",
        remarks: "",
        generationType: "2",
      },
      rules: {
        strainSource: [
          { required: true, message: "请输入菌种源", trigger: "blur" },
        ],
        strainNo: [
        strainCode: [
          { required: true, message: "请输入传代菌种编号", trigger: "blur" },
        ],
        strainName: [
@@ -102,20 +129,20 @@
      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,
    };
  },
  computed: {
@@ -125,10 +152,15 @@
        return true;
      }
      // 如果选中了传代计划数节点,可以新增下一代
      if (this.selectedNode && this.selectedNode.label === '传代计划数') {
      if (this.selectedNode && this.selectedNode.label === "传代计划数") {
        return true;
      }
      return false;
    },
  },
  created() {
    if (this.$route.query.id) {
      this.reloadData();
    }
  },
  mounted() {
@@ -137,18 +169,143 @@
  },
  beforeDestroy() {
    this.graph?.destroy();
    window.removeEventListener('resize', this.handleResize);
    window.removeEventListener("resize", this.handleResize);
  },
  methods: {
    async reloadData() {
      getDetail({ id: this.$route.query.id }).then((res) => {
        const detailData = res.pedigreeChartParentAllDetailDTO;
        // 设置基础表单数据
        this.form = {
          strainCode: res.strainCode,
          strainName: res.strainName,
          generationType: "2",
        };
        // 递归处理节点数据,构建树形结构
        function processNode(node, parentType = null) {
          // label 逻辑与新增时保持一致
          let label = "";
          if (node.type === 2) {
            label = "传代计划数";
          } else if (parentType === 2) {
            label = "母代";
          } else {
            label = "祖代";
          }
          const processedNode = {
            id: node.id,
            label,
            number: node.strainCode,
            data: node,
            isDiscarded: node.status === 1,
            style: {
              fill: node.status === 1 ? "#00B5AA" : "#999",
              opacity: node.status === 1 ? 0.6 : 0.3,
            },
            children: [],
          };
          if (node.type === 2) {
            processedNode.planCount = node.generationCount;
            processedNode.currentCount = node.children
              ? node.children.length
              : 0;
          }
          if (node.children && node.children.length > 0) {
            processedNode.children = node.children.map((child) =>
              processNode(child, node.type)
            );
          }
          return processedNode;
        }
        // 构建完整的树形结构
        this.graphData.nodes = detailData.one.map((node) => processNode(node));
        // 在数据加载完成后重新渲染图表
        this.$nextTick(() => {
          if (this.graph) {
            // 转换树形数据为G6可用的格式
            const convertToG6Data = (nodes) => {
              const g6Nodes = [];
              const g6Edges = [];
              const processNode = (node, parentId = null) => {
                g6Nodes.push({
                  id: node.id,
                  label: node.label,
                  number: node.number,
                  data: node.data,
                  isDiscarded: node.isDiscarded,
                  style: node.style,
                  planCount: node.planCount,
                  currentCount: node.currentCount,
                });
                if (parentId) {
                  g6Edges.push({
                    source: parentId,
                    target: node.id,
                    style: {
                      stroke: "rgba(4, 156, 154, 1)",
                      lineWidth: 1,
                    },
                  });
                }
                if (node.children && node.children.length > 0) {
                  node.children.forEach((child) => processNode(child, node.id));
                }
              };
              nodes.forEach((node) => processNode(node));
              return { nodes: g6Nodes, edges: g6Edges };
            };
            const g6Data = convertToG6Data(this.graphData.nodes);
            this.graph.changeData(g6Data);
            this.graph.render();
            this.graph.fitView();
          }
        });
      });
    },
    addNodeSign(value, type) {
      this.nodeData = value
      this.nodeType = type
      this.nodeData = value;
      this.nodeType = type;
      this.confirmStorageDialogVisible = true;
    },
    handleSubmit() {
      this.$refs.pedigreeForm.validate((valid) => {
        if (valid) {
          this.confirmStorageDialogVisible = true;
          // 递归处理节点数据,将data对象的内容提取到上一级并删除data属性
          const processNodeData = (nodes) => {
            return nodes.map((node) => {
              const { data, children, ...rest } = node;
              const processedNode = {
                ...rest,
                ...data, // 将data对象的内容展开到当前节点
                children: children ? processNodeData(children) : [],
              };
              return processedNode;
            });
          };
          // 获取基础表单数据
          const baseData = {
            parent: { ...this.form },
            one: processNodeData(this.graphData.nodes),
          };
          console.log("提交数据:", baseData);
          add(baseData).then((res) => {
            if (res.code == 200) {
              this.$message.success("保存成功");
              this.$router.back();
            }
          });
        }
      });
    },
@@ -161,249 +318,460 @@
    },
    handleSignatureConfirm(signatureImage) {
      this.confirmStorageDialogVisible = false;
      console.log("submit form with signature:", signatureImage);
      this.nodeData.vaccinateSignature = signatureImage.signature;
      if (this.nodeType === 1) {
        this.handleAddParent(this.nodeData)
        this.handleAddParent(this.nodeData);
      } else if (this.nodeType === 2) {
        this.handleAddPlan(this.nodeData)
        this.handleAddPlan(this.nodeData);
      } else if (this.nodeType === 3) {
        this.handleAddSublevel(this.nodeData)
        this.handleAddSublevel(this.nodeData);
      }
      // 处理提交逻辑
    },
    addNode() {
      // 如果没有节点,新增祖代
      if (this.graphData.nodes.length === 0) {
        this.$refs.pedigreeForm.validate((valid) => {
          if (valid) {
        this.$refs.addAncestor.openInitData({
          strainName: this.form.strainName,
          strainNo: this.form.strainNo,
          status: 'add',
          activeType: 1,
          isDiscarded: true,
              source: "", // 来源获得/菌落编号
              strainCode: "", // 菌种编号
              strainName: "", // 菌种名称
              colonyNumber: "", // 菌落编号
              strainType: "1", // 1原始祖代菌株 2分离菌落 3祖代菌株
              status: 1, // 1保存 2废弃
              type: 1, // 固定为1
              isDiscarded: true, // 仅用于前端切换保存/废弃
              activeType: "1", // 仅用于前端切换类型
              confirmTime: "",
              formStatus: "add",
        });
        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 findParentNode = (nodes) => {
          for (let node of nodes) {
            if (node.children && node.children.length > 0) {
              for (let child of node.children) {
                if (child.id === nodeModel.id) {
                  return node;
                }
              }
              const found = findParentNode(node.children);
              if (found) return found;
            }
          }
          return null;
        };
        const parentNode = findParentNode(this.graphData.nodes);
        // 如果父节点是母代,不允许添加
        if (parentNode.label === '母代') {
          this.$message.warning('母代节点不能再生成下一代');
        if (parentNode && parentNode.label === "母代") {
          this.$message.warning("母代节点不能再生成下一代");
          return;
        }
        this.showDiscarded = true;
        this.isAddingNode = true;
        console.log(parentNode);
        this.$refs.addSublevelForm.openInitData({
          title: '新增菌种传代项',
          title: "新增菌种传代项",
          form: {
            isDiscarded: true,
            ...nodeModel.data
          }
        })
            strainName1: this.form.strainName,
            strainCode1: this.form.strainCode,
            status: 1,
            parentId: this.selectedNode.id,
          },
        });
      } else {
        this.$message.warning('请选择传代计划数节点');
        this.$message.warning("请选择传代计划数节点");
      }
    },
    handleAddParent(value) {
      const parentId = `parent-${++this.nodeCount}`;
      this.graphData.nodes.push({
      const parentNode = {
        id: parentId,
        label: '祖代',
        number: value.inoculateNo.trim(),
        label: "祖代",
        number: value.strainCode.trim(),
        data: value,
        isDiscarded: true,
        isDiscarded: value.status == "1" ? true : false,
        x: 200,
        y: 200,
        style: {
          fill: '#00B5AA',
          fill: "#00B5AA",
        },
      });
        children: [],
      };
      this.graphData.nodes = [parentNode];
      this.graph.changeData(this.graphData);
      this.$message.success('祖代节点添加成功');
      this.$message.success("祖代节点添加成功");
      this.$refs.addAncestor.closeDialog();
    },
    handleDialogClose() {
      this.$refs.form.resetFields();
    },
    handleAddSublevel(value) {
      if (this.$route.query.id) {
        addProgenitor(value).then((res) => {
          if (res.code === 200) {
            this.$message.success("操作成功");
            this.$refs.addSublevelForm.closeDialog();
            this.reloadData();
          }
        });
        return;
      }
      if (this.isAddingNode) {
        // 新增节点的处理逻辑
        const nodeModel = this.selectedNode;
        const childId = `child-${++this.nodeCount}`;
        this.graphData.nodes.push({
        const childNode = {
          id: childId,
          label: '母代',
          number: value.inoculateNo.trim(),
          isDiscarded: value.isDiscarded,
          label: "母代",
          number: value.strainCode.trim(),
          isDiscarded: value.status == "1" ? true : false,
          data: value,
          style: {
            fill: value.isDiscarded ? '#999' : '#00B5AA',
            opacity: value.isDiscarded ? 0.3 : 0.6,
            fill: value.status == "1" ? "#999" : "#00B5AA",
            opacity: value.status == "1" ? 0.3 : 0.6,
          },
          children: [],
        };
        // 找到父节点并添加子节点
        const findAndAddChild = (nodes) => {
          for (let node of nodes) {
            if (node.id === nodeModel.id) {
              if (!node.children) {
                node.children = [];
              }
              node.children.push(childNode);
              node.currentCount = (node.currentCount || 0) + 1;
              return true;
            }
            if (node.children && node.children.length > 0) {
              if (findAndAddChild(node.children)) {
                return true;
              }
            }
          }
          return false;
        };
        findAndAddChild(this.graphData.nodes);
        // 转换树形数据为G6可用的格式
        const convertToG6Data = (nodes) => {
          const g6Nodes = [];
          const g6Edges = [];
          const processNode = (node, parentId = null) => {
            g6Nodes.push({
              id: node.id,
              label: node.label,
              number: node.number,
              data: node.data,
              isDiscarded: node.isDiscarded,
              style: node.style,
              planCount: node.planCount,
              currentCount: node.currentCount,
        });
        this.graphData.edges.push({
          source: nodeModel.id,
          target: childId,
            if (parentId) {
              g6Edges.push({
                source: parentId,
                target: node.id,
          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);
        this.graphData.nodes[nodeIndex].currentCount++;
            if (node.children && node.children.length > 0) {
              node.children.forEach((child) => processNode(child, node.id));
            }
          };
        this.graph.changeData(this.graphData);
        this.$message.success('母代添加成功');
          nodes.forEach((node) => processNode(node));
          return { nodes: g6Nodes, edges: g6Edges };
        };
        const g6Data = convertToG6Data(this.graphData.nodes);
        this.graph.changeData(g6Data);
        this.$message.success("母代添加成功");
        this.$refs.addSublevelForm.closeDialog();
        this.isAddingNode = false;
      } else {
        // 编辑节点的处理逻辑
        const nodeModel = this.selectedNode;
        const nodeIndex = this.graphData.nodes.findIndex(n => n.id === nodeModel.id);
        if (nodeIndex > -1) {
          if (nodeModel.label === '传代计划数') {
            this.graphData.nodes[nodeIndex].planCount = parseInt(value.value);
        const updateNode = (nodes) => {
          for (let node of nodes) {
            if (node.id === nodeModel.id) {
              if (nodeModel.label === "传代计划数") {
                node.planCount = parseInt(value.value);
          } else {
            this.graphData.nodes[nodeIndex].number = value.value.trim();
                node.number = value.value.trim();
            if (this.showDiscarded) {
              this.graphData.nodes[nodeIndex].isDiscarded = value.isDiscarded;
                  node.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);
                      }
                  if (value.isDiscarded && node.children) {
                    const discardChildren = (children) => {
                      children.forEach((child) => {
                        child.isDiscarded = true;
                        child.style.fill = "#999";
                        child.style.opacity = 0.3;
                        if (child.children) {
                          discardChildren(child.children);
                    }
                  });
                };
                discardChildren(nodeModel.id);
                    discardChildren(node.children);
              }
            }
          }
          this.graph.changeData(this.graphData);
          this.$message.success('修改成功');
              return true;
            }
            if (node.children && node.children.length > 0) {
              if (updateNode(node.children)) {
                return true;
              }
            }
          }
          return false;
        };
        updateNode(this.graphData.nodes);
        // 转换树形数据为G6可用的格式
        const convertToG6Data = (nodes) => {
          const g6Nodes = [];
          const g6Edges = [];
          const processNode = (node, parentId = null) => {
            g6Nodes.push({
              id: node.id,
              label: node.label,
              number: node.number,
              data: node.data,
              isDiscarded: node.isDiscarded,
              style: node.style,
              planCount: node.planCount,
              currentCount: node.currentCount,
            });
            if (parentId) {
              g6Edges.push({
                source: parentId,
                target: node.id,
                style: {
                  stroke: "rgba(4, 156, 154, 1)",
                  lineWidth: 1,
                },
              });
            }
            if (node.children && node.children.length > 0) {
              node.children.forEach((child) => processNode(child, node.id));
            }
          };
          nodes.forEach((node) => processNode(node));
          return { nodes: g6Nodes, edges: g6Edges };
        };
        const g6Data = convertToG6Data(this.graphData.nodes);
        this.graph.changeData(g6Data);
        this.$message.success("修改成功");
          this.dialogVisible = false;
        }
      }
    },
    setGenerationPlan() {
      if (!this.selectedNode) {
        this.$message.warning('请先选择节点');
        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 =>
      const hasGenerationNode = this.graphData.edges.some(
        (e) =>
        e.source === nodeModel.id &&
        this.graphData.nodes.some(n => n.id === e.target && n.label === '传代计划数')
          this.graphData.nodes.some(
            (n) => n.id === e.target && n.label === "传代计划数"
          )
      );
      if (hasGenerationNode) {
        this.$message.warning('该节点已经存在传代计划数节点');
        this.$message.warning("该节点已经存在传代计划数节点");
        return;
      }
      console.log({
        ...nodeModel.data,
        label: nodeModel.label,
      });
      this.$refs.planForm.openInitData({
        ...nodeModel.data,
        label: nodeModel.label,
        strainName: this.form.strainName,
        strainNo: this.form.strainNo,
      })
      });
    },
    handleAddPlan(value) {
      if (this.$route.query.id) {
        let params = {
          parentId: value.id,
          generationCount: value.generationCount,
          vaccinateSignature: value.vaccinateSignature,
        };
        addProgenitorChild(params).then((res) => {
          if (res.code === 200) {
            this.$message.success("操作成功");
            this.$refs.planForm.closeDialog();
            this.reloadData();
          }
        });
        return;
      }
      const nodeModel = this.selectedNode;
      const generationId = `generation-${++this.nodeCount}`;
      this.graphData.nodes.push({
      const generationNode = {
        id: generationId,
        label: '传代计划数',
        planCount: value.count,
        label: "传代计划数",
        planCount: value.generationCount,
        data: value,
        isDiscarded: true,
        currentCount: 0,
        style: {
          fill: '#00B5AA',
          fill: "#00B5AA",
        },
        children: [],
      };
      // 找到父节点并添加子节点
      const findAndAddChild = (nodes) => {
        for (let node of nodes) {
          if (node.id === nodeModel.id) {
            if (!node.children) {
              node.children = [];
            }
            node.children.push(generationNode);
            return true;
          }
          if (node.children && node.children.length > 0) {
            if (findAndAddChild(node.children)) {
              return true;
            }
          }
        }
        return false;
      };
      findAndAddChild(this.graphData.nodes);
      // 转换树形数据为G6可用的格式
      const convertToG6Data = (nodes) => {
        const g6Nodes = [];
        const g6Edges = [];
        const processNode = (node, parentId = null) => {
          g6Nodes.push({
            id: node.id,
            label: node.label,
            number: node.number,
            data: node.data,
            isDiscarded: node.isDiscarded,
            style: node.style,
            planCount: node.planCount,
            currentCount: node.currentCount,
      });
      this.graphData.edges.push({
        source: nodeModel.id,
        target: generationId,
          if (parentId) {
            g6Edges.push({
              source: parentId,
              target: node.id,
        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()
          if (node.children && node.children.length > 0) {
            node.children.forEach((child) => processNode(child, node.id));
          }
        };
        nodes.forEach((node) => processNode(node));
        return { nodes: g6Nodes, edges: g6Edges };
      };
      const g6Data = convertToG6Data(this.graphData.nodes);
      this.graph.changeData(g6Data);
      this.$message.success("传代计划数设置成功");
      this.$refs.planForm.closeDialog();
    },
    showDetail() {
      if (!this.selectedNode) {
        this.$message.warning('请先选择节点');
        this.$message.warning("请先选择节点");
        return;
      }
      const nodeModel = this.selectedNode;
      if (nodeModel.label === '祖代') {
        this.$refs.addAncestor.openInitData({ ...nodeModel.data, status: 'detail' });
      } else if (nodeModel.label === '母代') {
        this.dialogTitle = '母代详情';
      if (nodeModel.label === "祖代") {
        this.$refs.addAncestor.openInitData({
          ...nodeModel.data,
          formStatus: "detail",
        });
      } else if (nodeModel.label === "母代") {
        nodeModel.data.strainCode1 = this.form.strainCode;
        nodeModel.data.strainName1 = this.form.strainName;
        this.dialogTitle = "母代详情";
        this.$refs.addSublevelForm.openInitData({
          title: '母代详情',
          form: { ...nodeModel.data }
        })
      } else if (nodeModel.label === '传代计划数') {
        this.$refs.planForm.openInitData({ ...nodeModel.data, status: 'detail' });
          title: "母代详情",
          form: { ...nodeModel.data },
        });
      } else if (nodeModel.label === "传代计划数") {
        // this.$refs.planForm.openInitData({
        //   ...nodeModel.data,
        //   formStatus: "detail",
        // });
      }
    },
    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;
@@ -413,13 +781,25 @@
          // 根据节点状态设置颜色
          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 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,
@@ -427,15 +807,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,
@@ -443,52 +823,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",
            });
          }
@@ -508,71 +891,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: 50,
          ranksep: 70,
          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)",
            },
          },
        },
@@ -580,61 +963,63 @@
        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("id") === model.id || target.get("id") === model.id
            );
          });
          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("id") === model.id || target.get("id") === model.id;
        });
        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);
      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);
@@ -642,7 +1027,7 @@
    },
    initEvents() {
      // 监听窗口大小变化
      window.addEventListener('resize', this.handleResize);
      window.addEventListener("resize", this.handleResize);
      const handleNodeClick = (evt) => {
        evt.preventDefault(); // 阻止默认触摸行为
@@ -650,35 +1035,127 @@
        const nodeModel = node.getModel();
        // 如果节点已废弃,不允许任何操作
        if (!nodeModel.isDiscarded) {
          this.$message.warning('该节点已废弃,不能进行操作');
          this.$message.warning("该节点已废弃,不能进行操作");
          return;
        }
        // 更新选中节点
        this.selectedNode = nodeModel;
        // 更新节点选中状态
        this.graphData.nodes.forEach(n => {
          n.selected = n.id === nodeModel.id;
        });
        this.graph.changeData(this.graphData);
        const updateNodeSelected = (nodes) => {
          for (let node of nodes) {
            node.selected = node.id === nodeModel.id;
            if (node.children && node.children.length > 0) {
              updateNodeSelected(node.children);
            }
          }
      };
      this.graph.on('node:click', handleNodeClick);
      this.graph.on('node:touchstart', handleNodeClick);
        updateNodeSelected(this.graphData.nodes);
        // 转换树形数据为G6可用的格式
        const convertToG6Data = (nodes) => {
          const g6Nodes = [];
          const g6Edges = [];
          const processNode = (node, parentId = null) => {
            g6Nodes.push({
              id: node.id,
              label: node.label,
              number: node.number,
              data: node.data,
              isDiscarded: node.isDiscarded,
              style: node.style,
              planCount: node.planCount,
              currentCount: node.currentCount,
              selected: node.selected,
            });
            if (parentId) {
              g6Edges.push({
                source: parentId,
                target: node.id,
                style: {
                  stroke: "rgba(4, 156, 154, 1)",
                  lineWidth: 1,
                },
              });
            }
            if (node.children && node.children.length > 0) {
              node.children.forEach((child) => processNode(child, node.id));
            }
          };
          nodes.forEach((node) => processNode(node));
          return { nodes: g6Nodes, edges: g6Edges };
        };
        const g6Data = convertToG6Data(this.graphData.nodes);
        this.graph.changeData(g6Data);
      };
      this.graph.on("node:click", handleNodeClick);
      this.graph.on("node:touchstart", handleNodeClick);
      // 画布点击事件,取消选中节点(添加触摸支持)
      const handleCanvasClick = (evt) => {
        evt.preventDefault();
        this.selectedNode = null;
        this.graphData.nodes.forEach(n => {
          n.selected = false;
        });
        this.graph.changeData(this.graphData);
        const updateNodeSelected = (nodes) => {
          for (let node of nodes) {
            node.selected = false;
            if (node.children && node.children.length > 0) {
              updateNodeSelected(node.children);
            }
          }
      };
      this.graph.on('canvas:click', handleCanvasClick);
      this.graph.on('canvas:touchstart', handleCanvasClick);
        updateNodeSelected(this.graphData.nodes);
        // 转换树形数据为G6可用的格式
        const convertToG6Data = (nodes) => {
          const g6Nodes = [];
          const g6Edges = [];
          const processNode = (node, parentId = null) => {
            g6Nodes.push({
              id: node.id,
              label: node.label,
              number: node.number,
              data: node.data,
              isDiscarded: node.isDiscarded,
              style: node.style,
              planCount: node.planCount,
              currentCount: node.currentCount,
              selected: node.selected,
            });
            if (parentId) {
              g6Edges.push({
                source: parentId,
                target: node.id,
                style: {
                  stroke: "rgba(4, 156, 154, 1)",
                  lineWidth: 1,
                },
              });
            }
            if (node.children && node.children.length > 0) {
              node.children.forEach((child) => processNode(child, node.id));
            }
          };
          nodes.forEach((node) => processNode(node));
          return { nodes: g6Nodes, edges: g6Edges };
        };
        const g6Data = convertToG6Data(this.graphData.nodes);
        this.graph.changeData(g6Data);
      };
      this.graph.on("canvas:click", handleCanvasClick);
      this.graph.on("canvas:touchstart", handleCanvasClick);
    },
    handleResize() {
      if (this.graph) {
        const container = document.getElementById('mountNode');
        const container = document.getElementById("mountNode");
        const width = container.scrollWidth;
        const height = container.scrollHeight || 600;
        this.graph.changeSize(width, height);
@@ -705,7 +1182,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 {
culture/src/views/pedigree-chart/components/AddSublevelForm.vue
@@ -57,7 +57,7 @@
                    </el-form-item>
                </el-col>
            </el-row>
            <el-row v-if="!dialogTitle.includes('新增')" :gutter="20">
            <!-- <el-row v-if="!dialogTitle.includes('新增')" :gutter="20">
                <el-col :span="10">
                    <el-form-item label="接种操作人签字">
                        <el-image />
@@ -68,7 +68,7 @@
                        <el-image />
                    </el-form-item>
                </el-col>
            </el-row>
            </el-row> -->
        </el-form>
        <div v-if="dialogTitle.includes('新增')" class="dialog-footer">
            <el-button type="primary" @click="handleSubmit">提交签字</el-button>
culture/src/views/pedigree-chart/components/AddSublevelPlan.vue
@@ -63,7 +63,7 @@
        return {
            planDialogVisible: false,
            planForm: {
                status: 'add',
                status: '1',
                strainCode: '',
                strainName: '',
                inoculateNo: '',
@@ -106,7 +106,7 @@
            this.planDialogVisible = false
            // 重置表单数据
            this.planForm = {
                status: 'add',
                status: '1',
                strainCode: '',
                strainName: '',
                inoculateNo: '',
culture/src/views/pedigree-chart/components/PlanForm.vue
@@ -46,7 +46,7 @@
        return {
            planDialogVisible: false,
            planForm: {
                status: 'add',
                status: '1',
                strainCode: '',
                strainName: '',
                strainSourceStart: '',
@@ -82,7 +82,7 @@
            this.planDialogVisible = false
            // 重置表单数据
            this.planForm = {
                status: 'add',
                status: '1',
                strainCode: '',
                strainName: '',
                strainSourceStart: '',
culture/src/views/pedigree-chart/index.vue
@@ -1,6 +1,6 @@
<template>
  <div class="list">
    <TableCustom :queryForm="form" :tableData="tableData" :total="total">
    <TableCustom :queryForm="form" :tableData="tableData" :total="total" @handleSizeChange="handleSizeChange" @handleCurrentChange="handleCurrentChange">
      <template #search>
        <el-form :model="form" labelWidth="auto" inline>
          <el-form-item label="菌种编号:">
@@ -27,9 +27,9 @@
            <div class="title" :class="{ active: currentType === 'list' }" @click="handleTypeChange('list')">
              菌种选育保藏记录列表
            </div>
            <div class="drafts" :class="{ active: currentType === 'draft' }" @click="handleTypeChange('draft')">
            <!-- <div class="drafts" :class="{ active: currentType === 'draft' }" @click="handleTypeChange('draft')">
              草稿箱
            </div>
            </div> -->
          </div>
          <div class="flex a-center">
            <el-button @click="handleNewStrain" class="el-icon-plus" type="primary"
@@ -39,14 +39,20 @@
        </div>
      </template>
      <template #table>
        <el-table-column prop="planCode" label="项目课题方案编号"></el-table-column>
        <el-table-column prop="planName" label="项目课题方案名称"></el-table-column>
        <el-table-column prop="stage" label="起传类型"></el-table-column>
        <el-table-column prop="creator" label="菌种源"></el-table-column>
        <el-table-column prop="createTime" label="菌种编号"></el-table-column>
        <el-table-column prop="approver" label="菌种名称"></el-table-column>
        <el-table-column prop="approveTime" label="创建时间"></el-table-column>
        <el-table-column prop="approver" label="创建人"></el-table-column>
        <el-table-column prop="strainCode" label="菌种编号"></el-table-column>
        <el-table-column prop="strainName" label="菌种名称"></el-table-column>
        <el-table-column prop="generationType" label="起传类型">
          <template slot-scope="scope">
            {{ scope.row.generationType === 1 ? '母代' : '祖代' }}
          </template>
        </el-table-column>
        <el-table-column label="菌种源">
          <template slot-scope="scope">
            {{ scope.row.strainSourceStart }}代—{{ scope.row.strainSourceEnd }}细胞库
          </template>
        </el-table-column>
        <el-table-column prop="createTime" label="创建时间"></el-table-column>
        <el-table-column prop="createBy" label="创建人"></el-table-column>
        <el-table-column label="操作" width="250">
          <template slot-scope="scope">
            <el-button type="text" @click="handleDetail(scope.row)">详情</el-button>
@@ -142,6 +148,14 @@
    this.getTableData();
  },
  methods: {
    handleSizeChange(size) {
      this.form.pageSize = size;
      this.getTableData();
    },
    handleCurrentChange(page) {
      this.form.pageNum = page;
      this.getTableData();
    },
    handleBatchAdd() {
      this.$router.push({
        path: "/strain/add-pedigree",
@@ -155,6 +169,8 @@
        pageNum: 1,
        pageSize: 10
      };
    this.getTableData();
    },
    handleNewStrain() {
      this.$router.push({
@@ -222,9 +238,14 @@
      console.log("删除数据:", row);
    },
    handleDetail(row) {
      this.currentApprovalData = row;
      this.approvalDialogType = "view";
      this.approvalDialogVisible = true;
      if (row.generationType == 1) {
        //母代详情
        this.$router.push('/strain/add-pedigree?id='+row.id)
      } else {
        //祖代详情
        this.$router.push('/strain/add-progenitor?id='+row.id)
      }
    },
    handleTypeChange(type) {
      this.currentType = type;
@@ -233,7 +254,7 @@
    getTableData() {
      getList(this.form).then(res => {
        if (res.code === 200) {
          this.tableData = res.data.list;
          this.tableData = res.data.records;
          this.total = res.data.total;
        }
      });
culture/src/views/pedigree-chart/progenitorComponents/AddAncestor.vue
@@ -1,94 +1,143 @@
<template>
    <!-- 设置传代计划数弹窗 -->
    <el-dialog :title="planForm.status == 'add' ? '新增菌种传代项' : '菌种传代项详情'" :visible.sync="planDialogVisible" width="40%"
        :close-on-click-modal="false">
        <el-form :model="planForm" :rules="planRules" ref="planForm" label-position="top">
  <el-dialog
    :title="planForm.formStatus == 'add' ? '新增菌种传代项' : '菌种传代项详情'"
    :visible.sync="planDialogVisible"
    width="40%"
    :close-on-click-modal="false"
  >
    <el-form
      :model="planForm"
      :rules="planRules"
      ref="planForm"
      label-position="top"
    >
            <el-row :gutter="20">
                <el-col :span="16">
                    <el-form-item label="菌株类型" required>
                        <div class="type-box" v-if="planForm.status == 'add'">
                            <div @click="handleType(index)" v-for="(item, index) in ['原始祖代菌株SO', '分离菌落 CO', '祖代菌株 O']"
                                :key="item" class="type-box-item"
                                :class="index + 1 == planForm.activeType && 'activeType'">
            <div class="type-box" v-if="planForm.formStatus == 'add'">
              <div
                @click="handleType(index)"
                v-for="(item, index) in [
                  '原始祖代菌株SO',
                  '分离菌落 CO',
                  '祖代菌株 O',
                ]"
                :key="item"
                class="type-box-item"
                :class="index + 1 == planForm.strainType && 'activeType'"
              >
                                <div class="type-box-item-text">{{ item }}</div>
                                <img v-if="index + 1 == planForm.activeType" class="type-box-item-select"
                                    src="../../../assets/public/selectType.png" />
                <img
                  v-if="index + 1 == planForm.strainType"
                  class="type-box-item-select"
                  src="../../../assets/public/selectType.png"
                />
                            </div>
                        </div>
                        <div v-else class="type-box">
                            <div class="type-box-item activeType">
                                {{ ['原始祖代菌株SO', '分离菌落 CO', '祖代菌株 O'][planForm.activeType - 1] }}
                {{
                  ["原始祖代菌株SO", "分离菌落 CO", "祖代菌株 O"][
                    planForm.strainType - 1
                  ]
                }}
                            </div>
                        </div>
                    </el-form-item>
                </el-col>
                <el-col :span="20" v-if="planForm.activeType == 1">
        <el-col :span="20" v-if="planForm.strainType == 1">
                    <el-form-item label="来源获得" prop="source">
                        <el-input :disabled="planForm.status != 'add'" v-model="planForm.source"
                            placeholder="请输入"></el-input>
            <el-input
              :disabled="planForm.formStatus != 'add'"
              v-model="planForm.source"
              placeholder="请输入"
            ></el-input>
                    </el-form-item>
                </el-col>
                <el-col :span="10" v-else>
                    <el-form-item label="菌落编号" prop="source">
                        <el-input :disabled="planForm.status != 'add'" v-model="planForm.source"
                            placeholder="请输入"></el-input>
          <el-form-item label="菌落编号" prop="colonyNumber">
            <el-input
              :disabled="planForm.formStatus != 'add'"
              v-model="planForm.colonyNumber"
              placeholder="请输入"
            ></el-input>
                    </el-form-item>
                </el-col>
                <el-col :span="10">
                    <el-form-item label="菌种名称" prop="inoculateName">
                        <el-input :disabled="planForm.status != 'add'" v-model="planForm.inoculateName"
                            placeholder="请输入"></el-input>
          <el-form-item label="菌种名称" prop="strainName">
            <el-input
              :disabled="planForm.formStatus != 'add'"
              v-model="planForm.strainName"
              placeholder="请输入"
            ></el-input>
                    </el-form-item>
                </el-col>
                <el-col :span="10">
                    <el-form-item label="菌种编号" prop="inoculateNo">
                        <el-input :disabled="planForm.status != 'add'" v-model="planForm.inoculateNo"
                            placeholder="请输入"></el-input>
          <el-form-item label="菌种编号" prop="strainCode">
            <el-input
              :disabled="planForm.formStatus != 'add'"
              v-model="planForm.strainCode"
              placeholder="请输入"
            ></el-input>
                    </el-form-item>
                </el-col>
            </el-row>
            <el-row :gutter="20">
                <el-col :span="10">
                    <el-form-item label="保存/废弃" required>
                        <div class="flex-row" v-if="planForm.status == 'add'">
                            <div @click="handleStatus('save')" :class="planForm.isDiscarded && 'active'">保存</div>
                            <div @click="handleStatus('discard')" :class="!planForm.isDiscarded && 'active'">废弃</div>
            <div class="flex-row" v-if="planForm.formStatus == 'add'">
              <div
                @click="handleStatus('save')"
                :class="planForm.status === 1 && 'active'"
              >
                保存
              </div>
              <div
                @click="handleStatus('discard')"
                :class="planForm.status === 2 && 'active'"
              >
                废弃
              </div>
                        </div>
                        <div v-else class="activeStatus">
                            保存
              {{ planForm.status === 1 ? "保存" : "废弃" }}
                        </div>
                    </el-form-item>
                </el-col>
            </el-row>
            <el-row :gutter="20" v-if="!planForm.isDiscarded">
      <el-row :gutter="20" v-if="planForm.status === 2">
                <el-col :span="10">
                    <el-form-item label="废弃原因说明" required>
                        <el-input :disabled="planForm.status != 'add'" v-model="planForm.discardReason"
                            placeholder="请输入"></el-input>
            <el-input
              :disabled="planForm.formStatus != 'add'"
              v-model="planForm.remark"
              placeholder="请输入"
            ></el-input>
                    </el-form-item>
                </el-col>
            </el-row>
            <el-row :gutter="20">
                <el-col :span="10">
                    <el-form-item label="菌种入库时间">
                        <el-input disabled v-model="planForm.inTime"></el-input>
            <el-input disabled v-model="planForm.confirmTime"></el-input>
                    </el-form-item>
                </el-col>
            </el-row>
            <el-row v-if="planForm.status != 'add'" :gutter="20">
                <el-col :span="10">
      <el-row v-if="planForm.formStatus != 'add'" :gutter="20">
        <el-col v-if="planForm.vaccinateSignature" :span="10">
                    <el-form-item label="接种操作人签字">
                        <el-image />
            <el-image :src="planForm.vaccinateSignature" />
                    </el-form-item>
                </el-col>
                <el-col :span="10">
        <el-col v-if="planForm.preserveSignature" :span="10">
                    <el-form-item label="菌种保藏人签字">
                        <el-image />
            <el-image :src="planForm.preserveSignature" />
                    </el-form-item>
                </el-col>
            </el-row>
        </el-form>
        <div v-if="planForm.status == 'add'" class="dialog-footer">
    <div v-if="planForm.formStatus == 'add'" class="dialog-footer">
            <el-button>保存草稿</el-button>
            <el-button type="primary" @click="handleAddPlan">提交</el-button>
        </div>
@@ -99,54 +148,78 @@
    data() {
        return {
            planDialogVisible: false,
            planForm: {},
      planForm: {
        source: "", // 来源获得
        strainCode: "", // 菌种编号
        strainName: "", // 菌种名称
        colonyNumber: "", // 菌落编号
        strainType: "1", // 1原始祖代菌株 2分离菌落 3祖代菌株
        status: 1, // 1保存 2废弃
        type: 1, // 固定为1
        confirmTime: "",
        remark: "",
        formStatus: "add",
      },
            planRules: {
                inoculateNo: [
                    { required: true, message: '请输入菌种编号', trigger: 'blur' }
        strainCode: [
          { required: true, message: "请输入菌种编号", trigger: "blur" },
                ],
                inoculateName: [
                    { required: true, message: '请输入菌种名称', trigger: 'blur' }
        colonyNumber: [
          { required: true, message: "请输入菌落编号", trigger: "blur" },
        ],
        strainName: [
          { required: true, message: "请输入菌种名称", trigger: "blur" },
                ],
                source: [
                    { required: true, message: '请输入来源获得', trigger: 'blur' }
          { required: true, message: "请输入来源获得", trigger: "blur" },
                ],
            },
            dialogTitle: ''
        }
      dialogTitle: "",
    };
    },
    methods: {
        openInitData(value) {
            this.planForm = value
            this.openDialog()
      // 赋值当前时间
      // const now = new Date();
      // const pad = (n) => n.toString().padStart(2, "0");
      // this.planForm.storageTime = `${now.getFullYear()}-${pad(
      //   now.getMonth() + 1
      // )}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(
      //   now.getMinutes()
      // )}:${pad(now.getSeconds())}`;
      this.planForm = value;
      this.openDialog();
        },
        openDialog() {
            this.planDialogVisible = true
      this.planDialogVisible = true;
        },
        closeDialog() {
            this.planDialogVisible = false
      this.planDialogVisible = false;
        },
        handleAddPlan() {
            this.$refs.planForm.validate((valid) => {
                if (valid) {
                    if (!this.planForm.activeType) {
                        this.$message.warning('请选择菌株类型');
                        return
          if (!this.planForm.strainType) {
            this.$message.warning("请选择菌株类型");
            return;
                    }
                    this.$emit('addNodeSign', this.planForm, 1)
          this.$emit("addNodeSign", { ...this.planForm }, 1);
                }
            })
      });
        },
        handleStatus(status) {
            if (this.planForm.status != 'add') return
            this.planForm.isDiscarded = status === 'save'
            this.$forceUpdate()
      if (this.planForm.formStatus !== "add") return;
      this.planForm.status = status === "save" ? 1 : 2;
      this.$forceUpdate();
        },
        handleType(index) {
            if (this.planForm.status != 'add') return
            this.planForm.activeType = index + 1
        }
    }
}
      if (this.planForm.formStatus !== "add") return;
      this.planForm.strainType = index + 1;
      // 如果是分离菌落或祖代菌株,colonyNumber=source,否则清空
      this.planForm.colonyNumber = "";
    },
  },
};
</script>
<style scoped lang="less">
.dialog-footer {
@@ -158,12 +231,11 @@
    .el-button--primary {
        width: 150px;
        height: 40px;
        background: #049C9A;
    background: #049c9a;
        border-radius: 4px;
    }
    .el-button--default {
        width: 150px;
        height: 40px;
        border-radius: 4px;
@@ -187,10 +259,10 @@
    padding: 4px;
    border-radius: 10px;
    border: 2px solid rgba(4, 156, 154, 0.5);
    font-family: 'PingFangSCRegular';
  font-family: "PingFangSCRegular";
    .flex-row-save {
        background: #049C9A;
    background: #049c9a;
        color: #fff;
    }
@@ -203,18 +275,18 @@
    }
    .active {
        font-family: 'SourceHanSansCN-Medium';
        color: #049C9A;
        background: #EBFEFD;
    font-family: "sourceHanSansCN-Medium";
    color: #049c9a;
    background: #ebfefd;
        box-shadow: 0px 0px 6px 0px rgba(10, 109, 108, 0.25);
        border-radius: 10px;
    }
}
.activeStatus {
    font-family: 'SourceHanSansCN-Medium';
    color: #049C9A;
    background: #EBFEFD;
  font-family: "sourceHanSansCN-Medium";
  color: #049c9a;
  background: #ebfefd;
    border-radius: 10px;
    width: 183px;
    line-height: 40px;
@@ -230,9 +302,9 @@
    gap: 11px;
    .activeType {
        background: #EBFEFD;
    background: #ebfefd;
        font-weight: 500;
        color: #049C9A;
    color: #049c9a;
    }
    &-item {
@@ -240,7 +312,7 @@
        position: relative;
        width: 150px;
        text-align: center;
        background: #F5F5F5;
    background: #f5f5f5;
        border-radius: 4px;
        font-weight: 400;
        font-size: 16px;
culture/src/views/pedigree-chart/progenitorComponents/AddSublevelForm.vue
@@ -1,6 +1,11 @@
<template>
    <el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="40%" @close="closeDialog"
        :close-on-click-modal="false">
  <el-dialog
    :title="dialogTitle"
    :visible.sync="dialogVisible"
    width="40%"
    @close="closeDialog"
    :close-on-click-modal="false"
  >
        <el-form :model="form" :rules="rules" ref="form" label-position="top">
            <el-row :gutter="20">
                <el-col :span="10">
@@ -14,58 +19,75 @@
                    </el-form-item>
                </el-col>
                <el-col :span="10">
                    <el-form-item label="传代菌种编号" prop="strainNo">
                        <el-input disabled v-model="form.inoculateNo"></el-input>
          <el-form-item label="传代菌种编号" prop="strainCode1">
            <el-input disabled v-model="form.strainCode1"></el-input>
                    </el-form-item>
                </el-col>
                <el-col :span="10">
                    <el-form-item label="传代菌种名称" prop="strainName">
                        <el-input disabled v-model="form.inoculateName"></el-input>
          <el-form-item label="传代菌种名称" prop="strainName1">
            <el-input disabled v-model="form.strainName1"></el-input>
                    </el-form-item>
                </el-col>
                <el-col :span="10">
                    <el-form-item label="接种菌种编号" prop="inoculateNo">
                        <el-input :disabled="!dialogTitle.includes('新增')" v-model="form.num"
                            placeholder="请输入"></el-input>
          <el-form-item label="接种菌种编号" prop="strainCode">
            <el-input
              :disabled="!dialogTitle.includes('新增')"
              v-model="form.strainCode"
              placeholder="请输入"
            ></el-input>
                    </el-form-item>
                </el-col>
                <el-col :span="10">
                    <el-form-item label="接种菌种名称" prop="inoculateName">
                        <el-input :disabled="!dialogTitle.includes('新增')" v-model="form.name"
                            placeholder="请输入"></el-input>
          <el-form-item label="接种菌种名称" prop="strainName">
            <el-input
              :disabled="!dialogTitle.includes('新增')"
              v-model="form.strainName"
              placeholder="请输入"
            ></el-input>
                    </el-form-item>
                </el-col>
            </el-row>
            <el-form-item label="保存/废弃" required>
                <div class="flex-row">
                    <div @click="handleStatus('save')" :class="form.isDiscarded && 'active'">保存</div>
                    <div @click="handleStatus('discard')" :class="!form.isDiscarded && 'active'">废弃</div>
          <div @click="handleStatus(1)" :class="form.status === 1 && 'active'">
            保存
          </div>
          <div @click="handleStatus(2)" :class="form.status === 2 && 'active'">
            废弃
          </div>
                </div>
            </el-form-item>
            <el-row :gutter="20">
                <el-col :span="10">
                    <el-form-item v-if="!form.isDiscarded" label="废弃原因说明" required>
                        <el-input :disabled="!dialogTitle.includes('新增')" v-model="form.discardReason"
                            placeholder="请输入"></el-input>
          <el-form-item
            v-if="form.status === 2"
            label="废弃原因说明"
            prop="remark"
          >
            <el-input
              :disabled="!dialogTitle.includes('新增')"
              v-model="form.remark"
              placeholder="请输入"
            ></el-input>
                    </el-form-item>
                </el-col>
            </el-row>
            <el-row :gutter="20">
                <el-col :span="10">
                    <el-form-item label="菌种入库时间" prop="inTime">
                        <el-input disabled v-model="form.inTime"></el-input>
          <el-form-item label="菌种入库时间" prop="confirmTime">
            <el-input disabled v-model="form.confirmTime"></el-input>
                    </el-form-item>
                </el-col>
            </el-row>
            <el-row v-if="!dialogTitle.includes('新增')" :gutter="20">
                <el-col :span="10">
        <el-col v-if="form.vaccinateSignature" :span="10">
                    <el-form-item label="接种操作人签字">
                        <el-image />
            <el-image :src="form.vaccinateSignature" />
                    </el-form-item>
                </el-col>
                <el-col :span="10">
        <el-col v-if="form.preserveSignature" :span="10">
                    <el-form-item label="菌种保藏人签字">
                        <el-image />
            <el-image :src="form.preserveSignature" />
                    </el-form-item>
                </el-col>
            </el-row>
@@ -80,42 +102,71 @@
    data() {
        return {
            dialogVisible: false,
            dialogTitle: '',
      dialogTitle: "",
            form: {
                isDiscarded: true
        status: 1,
            },
            rules: {
                isDiscarded: [
                    { required: true, message: '请选择废弃状态', trigger: 'blur' }
        status: [
          { required: true, message: "请选择废弃状态", trigger: "blur" },
                ],
                inoculateNo: [
                    { required: true, message: '请输入接种菌种编号', trigger: 'blur' }
        strainCode: [
          { required: true, message: "请输入接种菌种编号", trigger: "blur" },
                ],
                inoculateName: [
                    { required: true, message: '请输入接种菌种名称', trigger: 'blur' }
        strainName: [
          { required: true, message: "请输入接种菌种名称", trigger: "blur" },
        ],
        remark: [
          { required: true, message: "请输入废弃原因", trigger: "blur" },
                ],
            },
        }
    };
    },
    methods: {
        openInitData(value) {
            this.dialogTitle = value.title
            this.form = value.form
            this.dialogVisible = true
      console.log(value);
      this.dialogTitle = value.title;
      // 获取用户信息
      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: value.form.vaccinateTime
          ? value.form.vaccinateTime
          : formatTime,
        type: 1,
      };
      this.dialogVisible = true;
        },
        closeDialog() {
            this.dialogVisible = false
      this.dialogVisible = false;
        },
        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.$forceUpdate()
        }
    }
}
      if (!this.dialogTitle.includes("新增")) return;
      this.form.status = status;
      this.$forceUpdate();
    },
  },
};
</script>
<style scoped lang="less">
@@ -127,7 +178,7 @@
    .el-button--primary {
        width: 150px;
        height: 40px;
        background: #049C9A;
    background: #049c9a;
        border-radius: 4px;
    }
}
@@ -141,10 +192,10 @@
    padding: 4px;
    border-radius: 10px;
    border: 2px solid rgba(4, 156, 154, 0.5);
    font-family: 'PingFangSCRegular';
  font-family: "PingFangSCRegular";
    .flex-row-save {
        background: #049C9A;
    background: #049c9a;
        color: #fff;
    }
@@ -157,9 +208,9 @@
    }
    .active {
        font-family: 'SourceHanSansCN-Medium';
        color: #049C9A;
        background: #EBFEFD;
    font-family: "SourceHanSansCN-Medium";
    color: #049c9a;
    background: #ebfefd;
        box-shadow: 0px 0px 6px 0px rgba(10, 109, 108, 0.25);
        border-radius: 10px;
    }
culture/src/views/pedigree-chart/progenitorComponents/PlanForm.vue
@@ -1,27 +1,32 @@
<template>
    <!-- 设置传代计划数弹窗 -->
    <el-dialog :title="planForm.status === 'detail' ? '祖代传代计划数详情' : '设置祖代传代计划数'" :visible.sync="planDialogVisible"
    <el-dialog :title="planForm.formStatus === 'detail' ? '祖代传代计划数详情' : '设置祖代传代计划数'" :visible.sync="planDialogVisible"
        width="40%" :close-on-click-modal="false">
        <el-form :model="planForm" :rules="planRules" ref="planForm" label-position="top">
            <el-row :gutter="20">
                <el-col :span="16">
                    <el-form-item label="菌株类型">
                        <div class="activeType">{{ ['原始祖代菌株SO', '分离菌落 CO', '祖代菌株 O'][planForm.activeType - 1] }}</div>
                        <div class="activeType">{{ ['原始祖代菌株SO', '分离菌落 CO', '祖代菌株 O'][planForm.strainType - 1] }}</div>
                    </el-form-item>
                </el-col>
                <el-col :span="20">
                <el-col :span="20" v-if="planForm.strainType == '1'">
                    <el-form-item label="来源获得" prop="source">
                        <el-input disabled v-model="planForm.source" placeholder="请输入"></el-input>
                    </el-form-item>
                </el-col>
                <el-col :span="10">
                    <el-form-item label="菌种名称" prop="inoculateName">
                        <el-input disabled v-model="planForm.inoculateName" placeholder="请输入"></el-input>
                <el-col :span="10" v-else>
                    <el-form-item label="菌落编号" prop="colonyNumber">
                        <el-input disabled v-model="planForm.colonyNumber" placeholder="请输入"></el-input>
                    </el-form-item>
                </el-col>
                <el-col :span="10">
                    <el-form-item label="菌种编号" prop="inoculateNo">
                        <el-input disabled v-model="planForm.inoculateNo" placeholder="请输入"></el-input>
                    <el-form-item label="菌种名称" prop="strainName">
                        <el-input disabled v-model="planForm.strainName" placeholder="请输入"></el-input>
                    </el-form-item>
                </el-col>
                <el-col :span="10">
                    <el-form-item label="菌种编号" prop="strainCode">
                        <el-input disabled v-model="planForm.strainCode" placeholder="请输入"></el-input>
                    </el-form-item>
                </el-col>
            </el-row>
@@ -35,20 +40,20 @@
            <el-row :gutter="20">
                <el-col :span="10">
                    <el-form-item label="菌种入库时间">
                        <el-input disabled v-model="planForm.inTime"></el-input>
                        <el-input disabled v-model="planForm.confirmTime"></el-input>
                    </el-form-item>
                </el-col>
            </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.formStatus === '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">
        <div v-if="planForm.formStatus !== 'detail'" class="dialog-footer">
            <el-button type="primary" @click="handleAddPlan">提交签字</el-button>
        </div>
    </el-dialog>
@@ -60,7 +65,7 @@
            planDialogVisible: false,
            planForm: {},
            planRules: {
                count: [
                generationCount: [
                    { required: true, message: '请输入传代计划数', trigger: 'blur' }
                ]
            }
@@ -80,7 +85,7 @@
        handleAddPlan() {
            this.$refs.planForm.validate((valid) => {
                if (valid) {
                    this.$emit('addNodeSign', this.planForm, 2)
                    this.$emit('addNodeSign', {...this.planForm,type: 2}, 2)
                }
            })
        }
culture/src/views/pedigree-chart/service.js
@@ -10,7 +10,22 @@
  return axios.post('/api/t-pedigree-chart/addProgenitor', { ...data })
}
// 谱系图详情
export const getDetail = (params) => {
  return axios.get('/open/t-pedigree-chart/getDetailInfoById', { params })
}
// 删除菌种库
export const deleteStrainLibrary = (params) => {
  return axios.delete('/open/t-train-library/deleteById', { params })
}
// 新增计划数
export const addProgenitorChild = (params) => {
  return axios.post('/api/t-pedigree-chart/updateCount', { ...params })
}
// 祖代-母代新增菌种传代项
export const addProgenitor = (params) => {
  return axios.post('/api/t-pedigree-chart/addProgenitorChild', { ...params })
}
laboratory/src/assets/login/cardBg.png
laboratory/src/assets/login/img1.png
laboratory/src/assets/login/img2.png
laboratory/src/assets/login/img3.png
laboratory/src/assets/login/img4.png
laboratory/src/assets/login/mi.png
laboratory/src/assets/login/midBg.png
laboratory/src/assets/login/notice.png
laboratory/src/assets/login/rili.png
laboratory/src/assets/login/time.png
laboratory/src/layouts/components/HeaderNav.vue
@@ -2,7 +2,12 @@
  <div>
    <!-- 右侧用户登录图标 -->
    <div class="user-logininfo">
      <div class="user-logininfo-icon">
      <div class="logoIcon" v-if="logo">
        <div class="image">
          <img src="../../assets/logo.jpg" alt="" srcset="" />
        </div>
      </div>
      <div class="user-logininfo-icon" v-else>
        <!-- 折叠 -->
        <i @click="clickFold" class="el-icon-s-fold"></i>
        <!-- 标签列表 -->
@@ -37,6 +42,12 @@
      scrollTimer: null,
      scrollAmount: 0
    }
  },
  props: {
    logo: {
      type: String,
      default: ''
    },
  },
  computed: {
    ...mapState(['tagList', 'isFold'])
@@ -110,6 +121,18 @@
  justify-content: space-between;
  overflow: hidden;
  .image {
    // margin-top: 40px;
    width: 70px;
    height: 70px;
    img {
      width: 100%;
      height: 100%;
      border-radius: 50%;
    }
  }
  .user-logininfo-icon {
    margin-right: 30px;
    flex: 1;
laboratory/src/router/index.js
@@ -37,6 +37,15 @@
        component: () => import("../views/login"),
    },
    {
        path: "/middleground",
        meta: {
            title: "中台",
            middleground: true,
            // hide: true,
        },
        component: () => import("../views/middleground"),
    },
    {
        path: "/system",
        meta: {
            title: "系统管理",
laboratory/src/views/middleground/index.vue
New file
@@ -0,0 +1,466 @@
<template>
  <div class="login-page">
    <div class="top-nav">
      <HeaderNav class="header-main" :logo="true" />
    </div>
    <div class="middleground">
      <!-- 左侧模块区域 -->
      <div class="left-modules">
        <!-- 这里将放置四个模块 -->
        <div class="module-item">
          <!-- 模块内容,例如图标和文字 -->
          <div class="module-icon"></div>
          <div class="module-text">实验室运行模块</div>
        </div>
        <div class="module-item">
          <div class="module-icon"></div>
          <div class="module-text">专业报告库</div>
        </div>
        <div class="module-item">
          <div class="module-icon"></div>
          <div class="module-text">化验师QA专题报告库</div>
        </div>
        <div class="module-item">
          <div class="module-icon"></div>
          <div class="module-text">评定模块</div>
        </div>
      </div>
      <!-- 右侧日历和待办事项区域 -->
      <div class="right-content">
        <!-- 日历 -->
        <div class="calendar-section">
          <!-- <h3>日历</h3> -->
          <el-calendar v-model="date" />
        </div>
        <!-- 待办事项 -->
        <div class="todo-list-section">
          <div class="title">待办事项</div>
          <!-- 待办事项列表将放置在这里 -->
          <div class="todo-list">
            <div class="todo-item" v-for="i in 6" :key="i">
              <div class="todo-details">
                <div class="notice-card">
                  <div class="todo-icon">
                  </div>
                  <div class="red-notice"></div>
                </div>
                <span class="todo-title">您有 [1] 条 【项目课题方案】 等待审批</span>
              </div>
              <div class="todo-meta">
                <div class="me"></div>
                <span class="todo-submitter">提交人: 王晓晓</span>
                <div class="time"></div>
                <span class="todo-submitter">2023.12.10 08:00</span>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import { loginReq } from './service'
import HeaderNav from '../../layouts/components/HeaderNav.vue'
// 引入 Element UI 的日历组件
// import { ElCalendar } from 'element-ui';
export default {
  name: 'Login',
  components: {
    HeaderNav,
    // ElCalendar // 注册 ElCalendar 组件
  },
  data() {
    return {
      windowWidth: window.innerWidth,
      loginForm: {
        username: '',
        password: ''
      },
      date: new Date(),
      viewWidth: '',
      scale: 1
    }
  },
  computed: {},
  created() { },
  mounted() { },
  methods: {
    // ...mapActions(['setIsFold']),
    handleKeyDown(event) {
      if (event.key === 'Enter') {
        this.login()
      }
    },
    // 添加处理窗口大小变化的方法
    handleResize() {
      this.viewWidth = window.innerWidth
    },
    login() {
      if (this.loginForm.username == '') {
        this.$message.warning('请输入账号')
        return
      }
      if (this.loginForm.password == '') {
        this.$message.warning('请输入密码')
        return
      }
      loginReq(this.loginForm).then(res => {
        sessionStorage.setItem('token', res.token)
        sessionStorage.setItem('userInfo', JSON.stringify(res.userInfo.user))
        this.$router.push('/system')
      })
    }
  }
}
</script>
<style scoped lang="less">
.flex {
  display: flex;
}
.j-between {
  justify-content: space-between;
}
.mt-40 {
  margin-top: 40px;
}
.login-page {
  width: 100%;
  height: 100vh;
  background: url('../../assets/login/midBg.png') no-repeat center center;
  background-size: cover;
  display: flex;
  flex-direction: column;
  // background-attachment: fixed;
  // display: flex;
  // justify-content: center;
  // align-items: center;
  .top-nav {
    display: flex;
    // justify-content: space-between;
    width: 100%;
    display: flex;
    flex-direction: column;
    transition: width 0.3s ease-in-out;
    height: 70px;
    .header-main {
      height: 70px;
      min-width: 200px;
    }
  }
  .middleground {
    flex: 1;
    height: calc(100vh - 70px);
    display: flex;
    justify-content: space-between;
    padding: 20px 20px 20px 108px;
    /* 调整左右内边距 */
    .left-modules {
      width: 38%;
      align-items: center;
      // padding: 140px 0;
      display: flex;
      flex-wrap: wrap;
      box-sizing: content-box;
      justify-content: space-between;
      /* 水平居中 */
      align-items: center;
      /* 垂直居中 */
      padding: 100px 0;
    }
    .module-item {
      width: 37%;
      height: 30%;
      // width: 307px;
      // height: 307px;
      background-color: red;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: space-between;
      background: url('../../assets/login/cardBg.png');
      /* 设置模块背景图 */
      background-size: cover;
      /* 背景图覆盖整个元素 */
      background-position: center top;
      /* 调整背景图位置:顶部居中 */
      .module-icon {
        width: 80px;
        height: 80px;
        // background-color: #eee; /* Placeholder for icon */
        z-index: 1;
        /* 确保图标在背景之上 */
        background-size: contain;
        /* 图标背景图包含在元素内 */
        background-repeat: no-repeat;
        /* 不重复 */
        background-position: 100% 100%;
        /* 图标背景图居中 */
      }
      /* 为每个模块图标设置特定的背景图片 */
      &:nth-child(1) .module-icon {
        background-image: url('../../assets/login/img1.png');
      }
      &:nth-child(2) .module-icon {
        background-image: url('../../assets/login/img2.png');
      }
      &:nth-child(3) .module-icon {
        background-image: url('../../assets/login/img3.png');
      }
      &:nth-child(4) .module-icon {
        background-image: url('../../assets/login/img4.png');
      }
      .module-text {
        font-size: 16px;
        font-weight: bold;
        position: relative;
        /* 为下划线定位 */
        z-index: 1;
        /* 确保文字在背景之上 */
        margin-bottom: 50px;
        &::after {
          content: '';
          position: absolute;
          left: 50%;
          /* 从文字中间开始 */
          bottom: -5px;
          /* 调整下划线位置 */
          transform: translateX(-50%);
          /* 使下划线居中 */
          width: 50%;
          /* 调整下划线宽度 */
          height: 2px;
          /* 调整下划线粗细 */
          background-color: #007bff;
          /* 示例下划线颜色 */
        }
      }
    }
    .right-content {
      width: 45%;
      height: calc(100vh - 150px);
      display: flex;
      /* 使日历和待办事项垂直排列 */
      flex-direction: column;
      .calendar-section {
        margin-bottom: 20px;
        /* 日历下方间距 */
        height: 59%;
        padding: 20px;
        border-radius: 14px;
        background: url(../../assets/login/rili.png) no-repeat center;
        h3 {
          margin-top: 0;
          margin-bottom: 10px;
          font-size: 18px;
          color: #333;
        }
        .calendar {
          height: 100% !important;
          width: 100% !important;
        }
        /* 尝试调整 Element UI 日历的样式 */
        ::v-deep .el-calendar {
          height: 100%;
          width: 100%;
          background: none !important;
          .el-calendar__header {
            padding: 12px 0;
          }
          .el-calendar__body {
            padding: 0;
          }
          .el-calendar-table {
            thead th {
              padding: 5px 0;
              font-weight: normal;
            }
            tbody td {
              border: none;
              .el-calendar-day {
                /* 调整日期单元格高度 */
                display: flex;
                justify-content: center;
                align-items: center;
                border-radius: 4px;
                /* 圆角 */
                margin: 2px;
                /* 单元格间距 */
                cursor: pointer;
                width: 90px;
                height: 58px;
                background: #FFFFFF;
                border-radius: 8px;
                border: 1px solid #EDEDED;
                &:hover {
                  background-color: #f0f0f0;
                }
              }
              /* 当前日期样式 */
              &.is-selected .el-calendar-day {
                background-color: #007bff;
                /* 示例选中颜色 */
                color: #fff;
              }
            }
          }
        }
      }
      .todo-list-section {
        height: 32%;
        border-radius: 14px;
        background-color: rgba(255, 255, 255, 1);
        // overflow: auto;
        padding: 24px 34px;
        display: flex;
        flex-direction: column;
        .title {
          height: 22px;
          font-family: SourceHanSansCN, SourceHanSansCN;
          font-weight: bold;
          font-size: 18px;
          color: #222222;
          line-height: 22px;
          margin-bottom: 20px;
        }
        .todo-list {
          flex: 1;
          height: 100%;
          overflow: auto;
        }
        /* 待办事项列表样式 */
        .todo-item {
          display: flex;
          align-items: center;
          justify-content: space-between;
          margin-bottom: 20PX;
          /* 分隔线 */
          &:first-child {
            margin-top: 3px;
          }
          .todo-details {
            display: flex;
            align-content: center;
          }
          .notice-card {
            position: relative;
            margin-right: 12px;
          }
          .todo-icon {
            position: relative;
            width: 24px;
            height: 24px;
            flex-shrink: 0;
            background: url('../../assets//login/notice.png') no-repeat center;
            background-position: center;
            background-size: 100% 100%;
          }
          .red-notice {
            position: absolute;
            top: -3px;
            right: -3px;
            width: 6px;
            height: 6px;
            border-radius: 50%;
            background: rgba(235, 65, 65, 1);
          }
          .todo-title {
            font-family: SourceHanSansCN, SourceHanSansCN;
            font-weight: 500;
            font-size: 14px;
            color: #303133;
            line-height: 24px;
          }
          .todo-meta {
            display: flex;
            align-items: center;
          }
          .time {
            width: 14px;
            height: 14px;
            margin-right: 6px;
            margin-left: 20px;
            flex-shrink: 0;
            background: url('../../assets//login/time.png') no-repeat center;
            background-position: center;
            background-size: 100% 100%;
          }
          .me {
            width: 14px;
            height: 14px;
            margin-right: 6px;
            flex-shrink: 0;
            background: url('../../assets//login/mi.png') no-repeat center;
            background-position: center;
            background-size: 100% 100%;
          }
          .todo-submitter {
            font-family: SourceHanSansCN, SourceHanSansCN;
            font-weight: 400;
            font-size: 12px;
            color: #909399;
            // line-height: 18px;
          }
        }
      }
    }
  }
}
</style>
laboratory/src/views/middleground/service.js
New file
@@ -0,0 +1,6 @@
import axios from '@/utils/request';
// 登录
export const loginReq = (data) => {
    return axios.post('/login', { ...data })
}