董国庆
2 天以前 06b2be3bbb48e0275fbd25624c1cce54a7cac2b1
Merge branch 'main' of http://120.76.84.145:10101/gitblit/r/H5/leshan-laboratory
30个文件已修改
8个文件已添加
6917 ■■■■■ 已修改文件
culture/src/components/SignatureCanvas.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/router/index.js 222 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/pedigree-chart/add.vue 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/pedigree-chart/addProgenitor.vue 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/pedigree-chart/components/ParentForm.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/pedigree-chart/components/PlanForm.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/pedigree-chart/index.vue 50 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/pedigree-chart/service.js 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strain-library/main-cell-library/add.vue 379 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strain-library/main-cell-library/index.vue 403 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strain-library/main-cell-library/record.vue 616 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strain-library/main-cell-library/service.js 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strain-library/production-cell-library/add.vue 376 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strain-library/production-cell-library/index.vue 483 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strain-library/production-cell-library/record.vue 834 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strain-library/production-cell-library/service.js 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strain-library/strain-library-manage/add.vue 200 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strain-library/strain-library-manage/components/AddRecordDialog.vue 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strain-library/strain-library-manage/components/RecordDetailDialog.vue 78 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strain-library/strain-library-manage/components/RecordTimeline.vue 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strain-library/strain-library-manage/components/StrainDetail.vue 143 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strain-library/strain-library-manage/index.vue 393 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strain-library/strain-library-manage/record.vue 309 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strain-library/strain-library-manage/service.js 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/router/index.js 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/dataManagement/approvalPlan/list.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/reportLibrary/feasibilityReport/add.vue 272 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/reportLibrary/feasibilityReport/components/approval/index.vue 186 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/reportLibrary/feasibilityReport/index.vue 257 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/reportLibrary/feasibilityReport/service.js 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/reportLibrary/feasibilityStudy/add.vue 127 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/reportLibrary/feasibilityStudy/components/approval/index.vue 179 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/reportLibrary/feasibilityStudy/index.vue 134 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/reportLibrary/feasibilityStudy/service.js 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/reportLibrary/processDevelopment/add.vue 280 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/reportLibrary/processDevelopment/components/approval/index.vue 186 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/reportLibrary/processDevelopment/index.vue 258 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/reportLibrary/processDevelopment/service.js 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/components/SignatureCanvas.vue
@@ -186,7 +186,7 @@
      
      // 导出图片
      const signatureImage = canvas.toDataURL('image/png')
      this.$emit('confirm', signatureImage)
      this.$emit('confirm', 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg')
    }
  },
  beforeDestroy() {
culture/src/router/index.js
@@ -1,14 +1,14 @@
import Vue from "vue";
import VueRouter from "vue-router";
import Layouts from "../layouts";
import Parent from "../layouts/components/AppContent"
import Parent from "../layouts/components/AppContent";
import store from "../store";
Vue.use(VueRouter);
const originalPush = VueRouter.prototype.push
const originalPush = VueRouter.prototype.push;
VueRouter.prototype.push = function push(location) {
    return originalPush.call(this, location).catch(err => err)
}
  return originalPush.call(this, location).catch((err) => err);
};
/**
 *  path: "/login",   ------页面地址
@@ -79,8 +79,8 @@
                    hide: true,
                },
                component: () => import("../views/projectList/detailProject"),
            }
        ]
      },
    ],
    },
    {
        path: "/system",
@@ -139,7 +139,7 @@
                },
                component: () => import("../views/system/operation-log"),
            },
        ]
    ],
    },
    {
        path: "/strain",
@@ -162,7 +162,8 @@
                            title: "原始细胞库",
                            keepAlive: true,
                        },
                        component: () => import("../views/strain-library/strain-library-manage"),
            component: () =>
              import("../views/strain-library/strain-library-manage"),
                    },
                    {
                        path: "strain-library-manage/add",
@@ -170,19 +171,23 @@
                        meta: {
                            title: "新增原始细胞库",
                            keepAlive: true,
                            hide: true
              hide: true,
                        },
                        component: () => import("../views/strain-library/strain-library-manage/add.vue"),
            component: () =>
              import("../views/strain-library/strain-library-manage/add.vue"),
                    },
                    {
                        path: "strain-library-manage/record",
                        name: "StrainRecord",
                        meta: {
                            title: "出入库记录",
                            keepAlive: true,
                            hide: true
              hide: true,
                        },
                        component: () => import("../views/strain-library/strain-library-manage/record.vue"),
            component: () =>
              import(
                "../views/strain-library/strain-library-manage/record.vue"
              ),
                    },
                    {
                        path: "main-cell-library",
@@ -191,7 +196,8 @@
                            title: "主细胞库",
                            keepAlive: true,
                        },
                        component: () => import("../views/strain-library/main-cell-library"),
            component: () =>
              import("../views/strain-library/main-cell-library"),
                    },
                    {
                        path: "main-cell-library/add",
@@ -199,9 +205,10 @@
                        meta: {
                            title: "新增主细胞",
                            keepAlive: true,
                            hide: true
              hide: true,
                        },
                        component: () => import("../views/strain-library/main-cell-library/add.vue"),
            component: () =>
              import("../views/strain-library/main-cell-library/add.vue"),
                    },
                    {
                        path: "production-cell-library",
@@ -210,7 +217,8 @@
                            title: "生产细胞库",
                            keepAlive: true,
                        },
                        component: () => import("../views/strain-library/production-cell-library"),
            component: () =>
              import("../views/strain-library/production-cell-library"),
                    },
                    {
                        path: "production-cell-library/add",
@@ -218,35 +226,36 @@
                        meta: {
                            title: "新增生产细胞",
                            keepAlive: true,
                            hide: true
              hide: true,
                        },
                        component: () => import("../views/strain-library/production-cell-library/add.vue"),
                    }
                ]
            component: () =>
              import("../views/strain-library/production-cell-library/add.vue"),
          },
        ],
            },
            {
                path: 'pedigree-vhart',
                name: 'PedigreeChart',
        path: "pedigree-vhart",
        name: "PedigreeChart",
                meta: {
                    title: "菌种传代生产谱系图",
                },
                component: () => import("../views/pedigree-chart"),
            },
            {
                path: 'add-pedigree',
                name: 'AddPedigree',
        path: "add-pedigree",
        name: "AddPedigree",
                meta: {
                    title: "新增母代菌种传代生产谱系图",
                    hide: true
          hide: true,
                },
                component: () => import("../views/pedigree-chart/add"),
            },
            {
                path: 'add-progenitor',
                name: 'AddProgenitor',
        path: "add-progenitor",
        name: "AddProgenitor",
                meta: {
                    title: "新增祖代菌种传代生产谱系图",
                    hide: true
          hide: true,
                },
                component: () => import("../views/pedigree-chart/addProgenitor"),
            },
@@ -260,16 +269,16 @@
            //     component: () => import("../views/strain-library/strain-flow-chart"),
            // },
            {
                path: 'breeding-record',
                name: 'BreedingRecord',
        path: "breeding-record",
        name: "BreedingRecord",
                meta: {
                    title: "菌种选育保藏记录",
                },
                component: () => import("../views/strain-library/breeding-record"),
            },
            {
                path: 'add-breeding-record',
                name: 'AddBreedingRecord',
        path: "add-breeding-record",
        name: "AddBreedingRecord",
                meta: {
                    title: "新增菌种选育保藏记录",
                    hide: true,
@@ -277,62 +286,75 @@
                component: () => import("../views/strain-library/breeding-record/add"),
            },
            {
                path: 'validation',
        path: "validation",
                meta: {
                    title: "菌种验证数据资料",
                },
                component: Parent,
                children: [
                    {
                        path: 'primitive-cell',
                        name: 'PrimitiveCell',
            path: "primitive-cell",
            name: "PrimitiveCell",
                        meta: {
                            title: '原始细胞库资料',
              title: "原始细胞库资料",
                        },
                        component: () => import("../views/strain-library/validation/primitive-cell/index.vue")
            component: () =>
              import(
                "../views/strain-library/validation/primitive-cell/index.vue"
              ),
                    },
                    {
                        path: 'add-primitive-cell',
                        name: 'AddPrimitiveCell',
            path: "add-primitive-cell",
            name: "AddPrimitiveCell",
                        meta: {
                            title: '新增原始细胞库资料',
                            hide: true
              title: "新增原始细胞库资料",
              hide: true,
                        },
                        component: () => import("../views/strain-library/validation/primitive-cell/add.vue")
            component: () =>
              import(
                "../views/strain-library/validation/primitive-cell/add.vue"
              ),
                    },
                    {
                        path: 'confirm-detail',
                        name: 'ConfirmDetail',
            path: "confirm-detail",
            name: "ConfirmDetail",
                        meta: {
                            title: '确认原始细胞库资料',
                            hide: true
              title: "确认原始细胞库资料",
              hide: true,
                        },
                        component: () => import("../views/strain-library/validation/primitive-cell/confirm-detail.vue")
            component: () =>
              import(
                "../views/strain-library/validation/primitive-cell/confirm-detail.vue"
              ),
                    },
                    {
                        path: 'chief-cell',
                        name: 'ChiefCell',
            path: "chief-cell",
            name: "ChiefCell",
                        meta: {
                            title: '主细胞库资料'
              title: "主细胞库资料",
                        },
                        component: () => import("../views/strain-library/validation/chief-cell")
                    }
                ]
            component: () =>
              import("../views/strain-library/validation/chief-cell"),
            },
        ]
    }, {
        ],
      },
    ],
  },
  {
        path: "/strainReportLibrary",
        component: Layouts,
        meta: {
            title: "菌种报告库",
        },
        children: [{
    children: [
      {
            path: "reportLibraryOne",
            meta: {
                title: "报告库一",
                keepAlive: true,
            },
            component: () => import("../views/strainReportLibrary/reportLibraryOne/index.vue"),
        component: () =>
          import("../views/strainReportLibrary/reportLibraryOne/index.vue"),
        },
        {
            path: "add",
@@ -341,7 +363,8 @@
                hide: true,
                keepAlive: true,
            },
            component: () => import("../views/strainReportLibrary/reportLibraryOne/add.vue"),
        component: () =>
          import("../views/strainReportLibrary/reportLibraryOne/add.vue"),
        },
        {
            path: "reportLibraryTwo",
@@ -349,7 +372,8 @@
                title: "报告库二",
                keepAlive: true,
            },
            component: () => import("../views/strainReportLibrary/reportLibraryOneTWO/index.vue"),
        component: () =>
          import("../views/strainReportLibrary/reportLibraryOneTWO/index.vue"),
        },
        {
            path: "addTwo",
@@ -358,7 +382,8 @@
                hide: true,
                keepAlive: true,
            },
            component: () => import("../views/strainReportLibrary/reportLibraryOneTWO/add.vue"),
        component: () =>
          import("../views/strainReportLibrary/reportLibraryOneTWO/add.vue"),
        },
        {
            path: "reportLibraryThree",
@@ -366,7 +391,10 @@
                title: "报告库三",
                keepAlive: true,
            },
            component: () => import("../views/strainReportLibrary/reportLibraryOneThree/index.vue"),
        component: () =>
          import(
            "../views/strainReportLibrary/reportLibraryOneThree/index.vue"
          ),
        },
        {
            path: "addThree",
@@ -375,7 +403,8 @@
                hide: true,
                keepAlive: true,
            },
            component: () => import("../views/strainReportLibrary/reportLibraryOneThree/add.vue"),
        component: () =>
          import("../views/strainReportLibrary/reportLibraryOneThree/add.vue"),
        },
        {
            path: "reportLibraryFour",
@@ -383,7 +412,8 @@
                title: "报告库四",
                keepAlive: true,
            },
            component: () => import("../views/strainReportLibrary/reportLibraryOneFour/index.vue"),
        component: () =>
          import("../views/strainReportLibrary/reportLibraryOneFour/index.vue"),
        },
        {
            path: "addFour",
@@ -392,9 +422,9 @@
                hide: true,
                keepAlive: true,
            },
            component: () => import("../views/strainReportLibrary/reportLibraryOneFour/add.vue"),
        component: () =>
          import("../views/strainReportLibrary/reportLibraryOneFour/add.vue"),
        },
        ],
    },
    {
@@ -403,23 +433,26 @@
        meta: {
            title: "菌种报告评定",
        },
        children: [{
    children: [
      {
            path: "projectTeamIntegral",
            meta: {
                title: "菌种项目组评定表",
            },
            component: () => import("../views/deliveryAssessment/projectTeamIntegral"),
        component: () =>
          import("../views/deliveryAssessment/projectTeamIntegral"),
        },
        {
            path: "projectTeamIntegral-detail",
            meta: {
                title: "评定详情",
                hide: true
          hide: true,
            },
            component: () => import("../views/deliveryAssessment/projectTeamIntegral/detail.vue"),
        component: () =>
          import("../views/deliveryAssessment/projectTeamIntegral/detail.vue"),
        },
        ]
    }
    ],
  },
];
const router = new VueRouter({
@@ -431,13 +464,13 @@
// 前置路由拦截器
router.beforeEach((to, from, next) => {
    // 设置当前页签名称
    document.title = to.meta.title || '实验室流程';
  document.title = to.meta.title || "实验室流程";
    // 登录验证
    // 排除登录页的校验
    if (to.path === "/login") {
        if (sessionStorage.getItem('token')) {
            next('/projectList');  // 已登录状态访问登录页时重定向到系统首页
    if (sessionStorage.getItem("token")) {
      next("/projectList"); // 已登录状态访问登录页时重定向到系统首页
            return;
        }
        next();
@@ -445,23 +478,26 @@
    }
    // 登录状态校验
    const isAuthenticated = sessionStorage.getItem('token');
  const isAuthenticated = sessionStorage.getItem("token");
    if (!isAuthenticated) {
        next('/login');  // 未登录用户重定向到登录页
    next("/login"); // 未登录用户重定向到登录页
        return;
    }
    // 判断是否拥有要跳转菜单权限
    let menus = store.state.menus
    if (to.meta.hasOwnProperty('privilege') && !menus.includes(to.meta.privilege)) {
        return
  let menus = store.state.menus;
  if (
    to.meta.hasOwnProperty("privilege") &&
    !menus.includes(to.meta.privilege)
  ) {
    return;
    }
    // 设置标签列表
    if (!to.meta.hide || !to.meta.oneself) {
        let tagList = JSON.parse(sessionStorage.getItem('tagList') || '[]')
    let tagList = JSON.parse(sessionStorage.getItem("tagList") || "[]");
        // 判断是否存在
        let isExist = tagList.some(item => item.path === to.path)
    let isExist = tagList.some((item) => item.path === to.path);
        if (!isExist) {
            // 只保存必要的信息
            const tagInfo = {
@@ -469,26 +505,28 @@
                name: to.name,
                meta: to.meta,
                query: to.query,
            }
            tagList.push(tagInfo)
            sessionStorage.setItem('tagList', JSON.stringify(tagList))
            store.commit('SET_TAGLIST', tagList)
      };
      tagList.push(tagInfo);
      sessionStorage.setItem("tagList", JSON.stringify(tagList));
      store.commit("SET_TAGLIST", tagList);
        }
    }
    // 判断是否需要缓存
    if (to.meta.keepAlive) {
        let keepAliveList = JSON.parse(sessionStorage.getItem('keepAliveList') || '[]')
    let keepAliveList = JSON.parse(
      sessionStorage.getItem("keepAliveList") || "[]"
    );
        // 判断是否已经缓存
        let isExist = keepAliveList.includes(to.name)
    let isExist = keepAliveList.includes(to.name);
        if (!isExist) {
            keepAliveList.push(to.name)
            sessionStorage.setItem('keepAliveList', JSON.stringify(keepAliveList))
            store.commit('SET_KEEPALIVELIST', keepAliveList)
      keepAliveList.push(to.name);
      sessionStorage.setItem("keepAliveList", JSON.stringify(keepAliveList));
      store.commit("SET_KEEPALIVELIST", keepAliveList);
        }
    }
    next()
  next();
});
export default router;
culture/src/views/pedigree-chart/add.vue
@@ -3,14 +3,18 @@
    <el-form :model="form" :rules="rules" ref="pedigreeForm" label-position="top" class="strain-form">
      <div class="card">
        <div class="form-items-row">
          <el-form-item label="菌种源" prop="strainSource" required>
          <el-form-item label="菌种源" required>
            <div class="flex-row">
              <div class="input-wrapper">
                <el-input v-model="form.strainSource" placeholder="请输入" class="fixed-width-input"></el-input>
                <el-form-item prop="strainSourceStart" style="margin-bottom: 0;">
                  <el-input v-model="form.strainSourceStart" placeholder="请输入" class="fixed-width-input"></el-input>
                </el-form-item>
              </div>
              <span class="form-text">代—</span>
              <div class="input-wrapper">
                <el-input v-model="form.generation" placeholder="请输入" class="fixed-width-input"></el-input>
                <el-form-item prop="strainSourceEnd" style="margin-bottom: 0;">
                  <el-input v-model="form.strainSourceEnd" placeholder="请输入" class="fixed-width-input"></el-input>
                </el-form-item>
              </div>
              <span class="form-text">细胞库</span>
            </div>
@@ -54,11 +58,12 @@
        <div class="strain-flow-chart">
          <div id="mountNode"></div>
        </div>
        <el-button type="primary" @click="handleSubmit" style="width: 150px;">保存</el-button>
      </div>
      <div class="end-btn">
        <el-button type="primary" @click="handleSubmit">提交</el-button>
        <el-button @click="handleDraft">存草稿</el-button>
        <el-button @click="handleCancel">取消</el-button>
        <!-- <el-button @click="handleDraft">存草稿</el-button>
        <el-button @click="handleCancel">取消</el-button> -->
      </div>
    </el-form>
@@ -95,15 +100,18 @@
    return {
      signatureVisible: false,
      form: {
        strainSource: "",
        generation: "",
        strainSourceStart: "",
        strainSourceEnd: "",
        cellBank: "",
        strainNo: "",
        strainName: "",
        remarks: "",
      },
      rules: {
        strainSource: [
        strainSourceStart: [
          { required: true, message: "请输入菌种源", trigger: "blur" },
        ],
        strainSourceEnd: [
          { required: true, message: "请输入菌种源", trigger: "blur" },
        ],
        strainNo: [
@@ -177,9 +185,8 @@
    },
    handleSignatureConfirm(signatureImage) {
      this.confirmStorageDialogVisible = false;
      console.log("submit form with signature:", signatureImage);
      if (this.nodeType === 1) {
        this.handleAddParent(this.nodeData)
        this.handleAddParent({ ...this.nodeData, signature: signatureImage.signature })
      } else if (this.nodeType === 2) {
        this.handleAddPlan(this.nodeData)
      } else if (this.nodeType === 3) {
@@ -432,37 +439,39 @@
    },
    initEvents() {
      // 监听窗口大小变化
      window.addEventListener('resize', this.handleResize);
      // 节点点击事件
      this.graph.on('node:click', (evt) => {
      window.addEventListener('resize', this.handleResize);// 添加触摸事件处理
      const handleNodeClick = (evt) => {
        evt.preventDefault(); // 阻止默认触摸行为
        const node = evt.item;
        const nodeModel = node.getModel();
        // 如果节点已废弃,不允许任何操作
        if (!nodeModel.isDiscarded) {
          this.$message.warning('该节点已废弃,不能进行操作');
          return;
        }
        // 更新选中节点
        this.selectedNode = nodeModel;
        // 更新节点选中状态
        this.graphData.nodes.forEach(n => {
          n.selected = n.id === nodeModel.id;
        });
        this.graph.changeData(this.graphData);
      });
      };
      this.graph.on('node:click', handleNodeClick);
      this.graph.on('node:touchstart', handleNodeClick);
      // 画布点击事件,取消选中节点
      this.graph.on('canvas:click', () => {
      // 画布点击事件,取消选中节点(添加触摸支持)
      const handleCanvasClick = (evt) => {
        evt.preventDefault();
        this.selectedNode = null;
        this.graphData.nodes.forEach(n => {
          n.selected = false;
        });
        this.graph.changeData(this.graphData);
      });
      };
      this.graph.on('canvas:click', handleCanvasClick);
      this.graph.on('canvas:touchstart', handleCanvasClick);
    },
    handleResize() {
      if (this.graph) {
@@ -480,8 +489,8 @@
            this.$refs.parentForm.openInitData({
              strainName: this.form.strainName,
              strainNo: this.form.strainNo,
              strainSource: this.form.strainSource,
              generation: this.form.generation,
              strainSourceStart: this.form.strainSourceStart,
              strainSourceEnd: this.form.strainSourceEnd,
            });
          }
        })
@@ -526,6 +535,8 @@
      }
    },
    handleAddParent(value) {
      console.log(value);
      const parentId = `parent-${++this.nodeCount}`;
      this.graphData.nodes.push({
        id: parentId,
@@ -658,8 +669,8 @@
          label: nodeModel.label,
          strainName: this.form.strainName,
          strainNo: this.form.strainNo,
          strainSource: this.form.strainSource,
          generation: this.form.generation,
          strainSourceStart: this.form.strainSourceStart,
          strainSourceEnd: this.form.strainSourceEnd,
        })
      } else {
        this.$refs.planForm.openInitData({
@@ -667,8 +678,8 @@
          label: nodeModel.label,
          strainName: this.form.strainName,
          strainNo: this.form.strainNo,
          strainSource: this.form.strainSource,
          generation: this.form.generation,
          strainSourceStart: this.form.strainSourceStart,
          strainSourceEnd: this.form.strainSourceEnd,
        })
      }
    },
culture/src/views/pedigree-chart/addProgenitor.vue
@@ -644,35 +644,37 @@
      // 监听窗口大小变化
      window.addEventListener('resize', this.handleResize);
      // 节点点击事件
      this.graph.on('node:click', (evt) => {
      const handleNodeClick = (evt) => {
        evt.preventDefault(); // 阻止默认触摸行为
        const node = evt.item;
        const nodeModel = node.getModel();
        // 如果节点已废弃,不允许任何操作
        if (!nodeModel.isDiscarded) {
          this.$message.warning('该节点已废弃,不能进行操作');
          return;
        }
        // 更新选中节点
        this.selectedNode = nodeModel;
        // 更新节点选中状态
        this.graphData.nodes.forEach(n => {
          n.selected = n.id === nodeModel.id;
        });
        this.graph.changeData(this.graphData);
      });
      };
      this.graph.on('node:click', handleNodeClick);
      this.graph.on('node:touchstart', handleNodeClick);
      // 画布点击事件,取消选中节点
      this.graph.on('canvas:click', () => {
      // 画布点击事件,取消选中节点(添加触摸支持)
      const handleCanvasClick = (evt) => {
        evt.preventDefault();
        this.selectedNode = null;
        this.graphData.nodes.forEach(n => {
          n.selected = false;
        });
        this.graph.changeData(this.graphData);
      });
      };
      this.graph.on('canvas:click', handleCanvasClick);
      this.graph.on('canvas:touchstart', handleCanvasClick);
    },
    handleResize() {
      if (this.graph) {
culture/src/views/pedigree-chart/components/ParentForm.vue
@@ -3,14 +3,14 @@
    <el-dialog :title="parentForm.status === 'detail' ? '母代详情' : '新增母代'" :visible.sync="addParentDialogVisible"
        width="40%" :close-on-click-modal="false">
        <el-form :model="parentForm" ref="parentForm" label-position="top">
            <el-form-item label="菌种源" prop="strainSource">
            <el-form-item label="菌种源" prop="strainSourceStart">
                <div class="flex-row">
                    <div class="input-wrapper">
                        <el-input disabled v-model="parentForm.strainSource" class="fixed-width-input"></el-input>
                        <el-input disabled v-model="parentForm.strainSourceStart" class="fixed-width-input"></el-input>
                    </div>
                    <span class="form-text">代—</span>
                    <div class="input-wrapper">
                        <el-input disabled v-model="parentForm.generation" class="fixed-width-input"></el-input>
                        <el-input disabled v-model="parentForm.strainSourceEnd" class="fixed-width-input"></el-input>
                    </div>
                    <span class="form-text">细胞库</span>
                </div>
culture/src/views/pedigree-chart/components/PlanForm.vue
@@ -3,14 +3,14 @@
    <el-dialog :title="planForm.status === 'detail' ? '传代计划数详情' : '设置传代计划数'" :visible.sync="planDialogVisible"
        width="40%" :close-on-click-modal="false">
        <el-form :model="planForm" :rules="planRules" ref="planForm" label-position="top">
            <el-form-item label="菌种源" prop="strainSource">
            <el-form-item label="菌种源" prop="strainSourceStart">
                <div class="flex-row">
                    <div class="input-wrapper">
                        <el-input disabled v-model="planForm.strainSource" class="fixed-width-input"></el-input>
                        <el-input disabled v-model="planForm.strainSourceStart" class="fixed-width-input"></el-input>
                    </div>
                    <span class="form-text">代—</span>
                    <div class="input-wrapper">
                        <el-input disabled v-model="planForm.generation" class="fixed-width-input"></el-input>
                        <el-input disabled v-model="planForm.strainSourceEnd" class="fixed-width-input"></el-input>
                    </div>
                    <span class="form-text">细胞库</span>
                </div>
culture/src/views/pedigree-chart/index.vue
@@ -4,17 +4,20 @@
      <template #search>
        <el-form :model="form" labelWidth="auto" inline>
          <el-form-item label="菌种编号:">
            <el-input v-model="form.planName" placeholder="请输入"></el-input>
            <el-input v-model="form.strainCode" placeholder="请输入"></el-input>
          </el-form-item>
          <el-form-item label="菌种名称:">
            <el-input v-model="form.planCode" placeholder="请输入"></el-input>
            <el-input v-model="form.strainName" placeholder="请输入"></el-input>
          </el-form-item>
          <el-form-item label="起传类型">
            <el-input v-model="form.creator" placeholder="请输入"></el-input>
            <el-select v-model="form.generationType" placeholder="请选择">
              <el-option label="母代" :value="1"></el-option>
              <el-option label="祖代" :value="2"></el-option>
            </el-select>
          </el-form-item>
          <el-form-item label="">
            <el-button type="default" @click="resetForm">重置</el-button>
            <el-button type="primary" @click="handleSearch">查询</el-button>
            <el-button style="margin-left: 10px;" type="primary" @click="handleSearch">查询</el-button>
          </el-form-item>
        </el-form>
      </template>
@@ -59,6 +62,7 @@
</template>
<script>
import { getList } from "./service";
export default {
  name: "PedigreeChart",
  components: {},
@@ -66,12 +70,11 @@
    return {
      currentType: "list", // 当前显示类型:list-列表,draft-草稿箱
      form: {
        planName: "",
        planCode: "",
        creator: "",
        createTime: [],
        approver: "",
        status: "",
        strainCode: "",
        strainName: "",
        generationType: "",
        pageNum: 1,
        pageSize: 10
      },
      tableData: [],
      total: 0,
@@ -146,12 +149,11 @@
    },
    resetForm() {
      this.form = {
        planName: "",
        planCode: "",
        creator: "",
        createTime: [],
        approver: "",
        status: "",
        strainCode: "",
        strainName: "",
        generationType: "",
        pageNum: 1,
        pageSize: 10
      };
    },
    handleNewStrain() {
@@ -160,8 +162,8 @@
      });
    },
    handleSearch() {
      // 实现查询逻辑
      console.log("查询条件:", this.form);
      this.form.pageNum = 1;
      this.getTableData();
    },
    getStatusType(status) {
      const statusMap = {
@@ -229,14 +231,12 @@
      this.getTableData();
    },
    getTableData() {
      // 根据currentType请求不同的数据
      if (this.currentType === "list") {
        this.tableData = this.mockListData;
        this.total = this.mockListData.length;
      } else {
        this.tableData = this.mockDraftData;
        this.total = this.mockDraftData.length;
      getList(this.form).then(res => {
        if (res.code === 200) {
          this.tableData = res.data.list;
          this.total = res.data.total;
      }
      });
    },
  },
};
culture/src/views/pedigree-chart/service.js
New file
@@ -0,0 +1,11 @@
import axios from '@/utils/request';
// 列表
export const getList = (data) => {
  return axios.post('/api/t-pedigree-chart/pageList', { ...data })
}
// 删除菌种库
export const deleteStrainLibrary = (params) => {
  return axios.delete('/open/t-train-library/deleteById', { params })
}
culture/src/views/strain-library/main-cell-library/add.vue
@@ -1,140 +1,294 @@
<template>
  <Card>
      <el-form
          :model="form"
          :rules="rules"
          ref="strainForm"
          label-position="top"
          class="strain-form"
      >
          <div class="form-grid">
              <el-form-item label="菌种编号" prop="strainNo" required>
                  <el-input v-model="form.strainNo" placeholder="请输入"></el-input>
        <el-form :model="form" :rules="rules" ref="strainForm" label-position="top" class="strain-form">
            <div class="form-row three-columns">
                <el-form-item label="菌种编号" prop="strainCode">
                    <el-input v-model="form.strainCode" placeholder="请输入"></el-input>
              </el-form-item>
              <el-form-item label="菌种名称" prop="strainName" required>
                <el-form-item label="菌种名称" prop="strainName">
                  <el-input v-model="form.strainName" placeholder="请输入"></el-input>
              </el-form-item>
              <el-form-item label="菌种来源" prop="source" required>
                  <el-input v-model="form.source" placeholder="请输入"></el-input>
              </el-form-item>
              <el-form-item label="鉴定方法" prop="identificationMethod" required>
                  <el-input v-model="form.identificationMethod" placeholder="请输入"></el-input>
              </el-form-item>
              <el-form-item label="特征描述" prop="characteristics" required>
                  <el-input v-model="form.characteristics" placeholder="请输入"></el-input>
              </el-form-item>
              <el-form-item label="菌种保存方法" prop="preservationMethod" required>
                  <el-input v-model="form.preservationMethod" placeholder="请输入"></el-input>
              </el-form-item>
              <el-form-item label="保存位置" prop="storageLocation" required>
                  <el-input v-model="form.storageLocation" placeholder="请输入"></el-input>
                <el-form-item label="菌种来源" prop="strainSource">
                    <el-input v-model="form.strainSource" placeholder="请输入"></el-input>
              </el-form-item>
          </div>
          <div class="form-row">
              <el-form-item label="备注" prop="remarks" class="full-width">
                  <el-input
                      type="textarea"
                      v-model="form.remarks"
                      :rows="4"
                      placeholder="请输入"
                  ></el-input>
                <el-form-item label="鉴定方法" prop="appraisalMethod">
                    <el-input v-model="form.appraisalMethod" placeholder="请输入"></el-input>
              </el-form-item>
          </div>
            <div class="form-row">
                <el-form-item label="特征描述" prop="features" class="full-width">
                    <el-input type="textarea" v-model="form.features" :rows="4" placeholder="请输入"></el-input>
                </el-form-item>
            </div>
            <div class="form-row three-columns">
                <el-form-item label="保藏位置" prop="saveLocation">
                    <el-input v-model="form.saveLocation" placeholder="请输入"></el-input>
                </el-form-item>
                <el-form-item label="菌种保存方法" prop="saveMethod">
                    <el-input v-model="form.saveMethod" placeholder="请输入"></el-input>
                </el-form-item>
                <div class="form-item-placeholder"></div>
            </div>
            <div class="form-row">
                <el-form-item label="备注" prop="remarks" class="full-width">
                    <el-input type="textarea" v-model="form.remark" :rows="4" placeholder="请输入"></el-input>
                </el-form-item>
            </div>
          <div class="end-btn" style="margin-top: 38px">
              <el-button type="primary" @click="handleSubmit">提交</el-button>
              <el-button @click="handleDraft">存草稿</el-button>
                <el-button type="primary" @click="handleSubmit(0)">提交</el-button>
                <el-button v-if="!$route.query.id" type="primary" @click="handleBatchAdd">批量新增</el-button>
                <el-button @click="handleSubmit(1)">存草稿</el-button>
          </div>
      </el-form>
        <!-- 批量新增弹窗 -->
        <el-dialog title="批量新增" :visible.sync="batchAddDialogVisible" width="520px" :close-on-click-modal="false"
            :close-on-press-escape="false" custom-class="batch-add-dialog">
            <div class="dialog-content">
                <el-form :model="batchForm" ref="batchFormRef" label-position="top" class="batch-form">
                    <el-form-item label="批量新增数量" prop="count"
                        :rules="[{ required: true, message: '请输入批量新增数量', trigger: 'blur' }]">
                        <el-input v-model.number="batchForm.count" placeholder="请输入" />
                    </el-form-item>
                </el-form>
                <div class="dialog-notice">
                    <p>注意:操作批量新增后,系统会自动生成对应数量的菌种数据,</p>
                    <p>这些菌种的基础信息内容都是一致的,唯独菌种编号内容系统</p>
                    <p>不会自动生成,需要操作员自行编辑</p>
                </div>
            </div>
            <template #footer>
                <div class="end-btn">
                    <el-button type="primary" @click="handleConfirmBatchAdd">确认新增</el-button>
                </div>
            </template>
        </el-dialog>
      <!-- 签字确认组件 -->
      <SignatureCanvas
          :visible.sync="signatureVisible"
          @confirm="handleSignatureConfirm"
      />
        <SignatureCanvas :visible.sync="signatureVisible" @confirm="handleSignatureConfirm" />
  </Card>
</template>
<script>
import SignatureCanvas from '@/components/SignatureCanvas.vue'
import { add, edit, getDetail, addBatch } from './service'
export default {
  name: 'AddMainCell',
    name: 'StrainLibraryManageAdd',
  components: {
      SignatureCanvas
  },
  data() {
      return {
            batchAddDialogVisible: false,
          signatureVisible: false,
            currentAction: '', // 'submit' or 'batchAdd'
            batchForm: {
                count: ''
            },
          form: {
              strainNo: '',
                strainCode: '',
              strainName: '',
              source: '',
              identificationMethod: '',
                appraisalMethod: '',
              characteristics: '',
              storageLocation: '',
              preservationMethod: '',
              remarks: ''
                remark: ''
          },
          rules: {
              strainNo: [{ required: true, message: '请输入菌种编号', trigger: 'blur' }],
                strainCode: [{
                    validator: (rule, value, callback) => {
                        if (this.currentAction === 'submit' && !value) {
                            callback(new Error('请输入菌种编号'));
                        } else {
                            callback();
                        }
                    },
                    trigger: 'change'
                }],
              strainName: [{ required: true, message: '请输入菌种名称', trigger: 'blur' }],
              source: [{ required: true, message: '请输入菌种来源', trigger: 'blur' }],
              identificationMethod: [{ required: true, message: '请输入鉴定方法', trigger: 'blur' }],
              characteristics: [{ required: true, message: '请输入特征描述', trigger: 'blur' }],
              storageLocation: [{ required: true, message: '请输入保存位置', trigger: 'blur' }],
              preservationMethod: [{ required: true, message: '请输入菌种保存方法', trigger: 'blur' }]
                strainSource: [{ required: true, message: '请输入菌种来源', trigger: 'blur' }],
                appraisalMethod: [{ required: true, message: '请输入鉴定方法', trigger: 'blur' }],
                features: [{ required: true, message: '请输入特征描述', trigger: 'blur' }],
                saveLocation: [{ required: true, message: '请输入保存位置', trigger: 'blur' }],
                saveMethod: [{ required: true, message: '请输入菌种保存方法', trigger: 'blur' }]
            }
        }
    },
    activated() {
        if (this.$route.query.id) {
            getDetail({ id: this.$route.query.id }).then(res => {
                this.form = res
            })
        }
    },
    watch: {
        '$route.query.id'() {
            this.form = {
                strainCode: '',
                strainName: '',
                source: '',
                appraisalMethod: '',
                characteristics: '',
                storageLocation: '',
                preservationMethod: '',
                remark: ''
          }
      }
  },
  methods: {
      handleSubmit() {
        handleSubmit(isDraft) {
            this.currentAction = 'submit'
          this.$refs.strainForm.validate((valid) => {
              if (valid) {
                    this.form.isDraft = isDraft
                    if (isDraft == 1) {
                        //存草稿
                        this.handleSignatureConfirm('')
                    } else {
                        this.signatureVisible = true
                    }
                }
            })
        },
        handleBatchAdd() {
            this.currentAction = 'batchAdd'
            this.$refs.strainForm.validate((valid) => {
                if (valid) {
                    this.batchAddDialogVisible = true
                }
            })
        },
        handleConfirmBatchAdd() {
            this.$refs.batchFormRef.validate((valid) => {
                if (valid) {
                    this.batchAddDialogVisible = false
                  this.signatureVisible = true
              }
          })
      },
      handleDraft() {
          // 实现存草稿逻辑
          console.log('save draft', this.form)
          this.$message.success('草稿保存成功')
      },
      handleSignatureConfirm(signatureImage) {
          this.signatureVisible = false
          // 处理提交逻辑
          console.log('submit form with signature:', this.form, signatureImage)
          this.$message.success('提交成功')
          this.$router.back()
        async handleSignatureConfirm(signatureImage) {
            let requestData = {
                strainCode: this.form.strainCode,
                strainName: this.form.strainName,
                strainSource: this.form.strainSource,
                appraisalMethod: this.form.appraisalMethod,
                features: this.form.features,
                saveLocation: this.form.saveLocation,
                saveMethod: this.form.saveMethod,
                remark: this.form.remark,
                signature: signatureImage,
                type: 2,
            };
            if (this.currentAction === 'batchAdd') {
                requestData.batchCount = this.batchForm.count;
            } else {
                requestData.isDraft = this.form.isDraft
            }
            try {
                if (this.$route.query.id) {
                    requestData.id = this.$route.query.id;
                    await edit(requestData);
                } else if (this.currentAction === 'batchAdd') {
                    await addBatch(requestData);
                } else {
                    await add(requestData);
                }
                this.signatureVisible = false;
                this.$router.back();
                this.$message.success('操作成功');
            } catch (error) {
                this.$message.error('操作失败');
            }
      }
  }
}
</script>
<style scoped lang="less">
.strain-form {
  padding: 0 40px;
.add-strain {
    height: 100%;
    background: #F5F7FA;
  .form-grid {
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      gap: 24px;
    .form-card {
        background: #fff;
        border-radius: 8px;
    }
}
.header-title {
      margin-bottom: 24px;
      
      @media screen and (max-width: 1200px) {
          grid-template-columns: repeat(2, 1fr);
    &-left {
        display: flex;
        align-items: center;
        img {
            width: 20px;
            height: 20px;
            margin-right: 8px;
      }
      
      @media screen and (max-width: 768px) {
          grid-template-columns: 1fr;
        div {
            font-size: 18px;
            font-weight: bold;
            color: #303133;
      }
  }
}
.end-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    button {
        width: 180px;
        height: 36px;
        // background: #409EFF;
    }
}
.strain-form {
    padding: 0 40px;
  .form-row {
      display: flex;
      flex-wrap: wrap;
      gap: 24px;
      margin-bottom: 24px;
        &.three-columns {
            .el-form-item,
            .form-item-placeholder {
                flex: 1;
                min-width: 280px;
                @media screen and (max-width: 1200px) {
                    min-width: calc(50% - 12px);
                }
                @media screen and (max-width: 768px) {
                    min-width: 100%;
                }
            }
            .form-item-placeholder {
                @media screen and (max-width: 1200px) {
                    display: none;
                }
            }
        }
      .el-form-item {
          margin-bottom: 0;
@@ -169,6 +323,91 @@
  }
}
.batch-add-dialog {
    :deep(.el-dialog__header) {
        margin: 0;
        padding: 20px;
        text-align: center;
        border-bottom: 1px solid #EBEEF5;
        .el-dialog__title {
            font-size: 16px;
            font-weight: 600;
            color: #303133;
        }
    }
    :deep(.el-dialog__body) {
        padding: 20px;
    }
    .dialog-content {
        display: flex;
        flex-direction: column;
        align-items: center;
    }
    .batch-form {
        width: 100%;
        display: flex;
        flex-direction: column;
        align-items: center;
        :deep(.el-form-item) {
            width: 320px;
            margin-bottom: 0;
        }
        :deep(.el-form-item__label) {
            width: 100%;
            color: #606266;
            font-weight: normal;
            padding-bottom: 8px;
            &::before {
                color: #F56C6C;
            }
        }
        :deep(.el-input) {
            width: 100%;
            input {
                width: 100%;
            }
        }
    }
    .dialog-notice {
        margin-top: 16px;
        text-align: center;
        p {
            margin: 0;
            line-height: 22px;
            color: #606266;
            font-size: 14px;
        }
    }
    :deep(.el-dialog__footer) {
        padding: 0 20px 20px;
        text-align: center;
        .el-button {
            width: 180px;
            height: 36px;
            padding: 0;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 14px;
            border-radius: 4px;
            margin: 0;
        }
    }
}
.end-btn {
  display: flex;
  align-items: center;
culture/src/views/strain-library/main-cell-library/index.vue
@@ -2,38 +2,80 @@
    <div class="list">
        <el-card class="header-box">
            <div class="box-title">
                <img src="@/assets/public/notice.png" class="header-icon">
                <span>菌种源保藏出/入主细胞库登记表说明</span>
                <el-button type="text" class="view-more" @click="handleViewMore">查看全部 >></el-button>
        <img src="@/assets/public/notice.png" class="header-icon" />
        <span>菌种源保藏出/入细胞库登记表说明</span>
        <el-button type="text" class="view-more" @click="handleViewMore"
          >查看全部 >></el-button
        >
            </div>
            <div class="header-content" :class="{ 'collapsed': true }">
                <p>1、菌种全部集中登记在【菌种源保藏出/入主细胞库登记表】,请分类管理。</p>
                <p>1.1 接种入主细胞库(现代-M)的菌株经过培养和保藏。</p>
                <p>1.2 从原始细胞库转入的菌株需要按照标准程序进行记录和管理。</p>
                <p>1.3 主细胞库的菌株可用于科研项目和工业生产中的应用研究。</p>
      <div class="header-content" :class="{ collapsed: true }">
        <p>
          1. 菌种全部集中登记在【菌种源保藏出/入细胞库登记表】,菌种来源有 3
          条路径。1.1 是沙土管或甘油管的源头菌种;入原始细胞库(祖代-O)。1.2
          是斜面的源头菌种;接种入主细胞库(祖代-O)。经过育种、验证后,菌种保藏为甘油管或沙土管的,入原始细胞库(祖代-0)1.3
          是含菌物质自己分离后获得的斜面源头菌种,接种入主细胞库;经生产验证后,保藏为沙土管或甘油管,入原始细胞库(祖代-O)。
          2.
          菌种细胞库,分类入三库,进行传代运行管理。三类库存空间进行区分,保藏菌种。2.1
          原始细胞库(祖代-O)、2.2 主细胞库(母代-M)、2.3
          生产细胞库(子代-S)、(孙代-G)3. 细胞库编码规则3.1
          细胞库编码规则:DD-M-240919-01-(O-0109-01)DD:代表项目组。M:“O”代表祖代原始细胞库,”M“代表母代主细胞库,”S“代表子代生产细胞库,“G”代表孙代生产细胞库。240919:代表在
          24 年 9 月 19
          接种批次的菌种;或收到外来菌种时间的入库批次。01:代表两位序列号。(O-0109-01):代表传代菌种的编号3.1.1
          传代编码方式演例:祖代:DD-O-240919-01
          传母代:DD-M-241017-01-(O-091901)DD-M-241017-02-(O-091901)DD-M-241017-03-(O-091901)子代:DD-S-241019-01-(M-1017-02)版权归奥利元生物所有,禁止外传。DD-S-241019-02-(M-1017-03)孙代:DD-G-241109-01-(S-1019-02)3.1.2
          编码规则实现了编码唯一,编码可溯源,编码直观、方便。3.2
          细胞库说明:3.2.1
          直接购买、自行从(土壤、相关物料、商品)等分离出来菌株进入原始细胞库。3.2.2
          从原始细胞库中选取出来再次纯化、改造、提高性能的菌株进入主细胞库。3.2.3
          主细胞库中选取出稳定,生产性能良好的菌株扩培后保种进入生产细胞库。4.
          菌种选育-保藏过程编号说明4.1 菌种选育时,培养皿的编号可使用 a-01、a-02
          等用于清晰形态观察记录;菌落编号使用序号 1/2/3等。4.2
          接种斜面菌种编码(-O)使用原始细胞库编码;斜面转菌种保藏使用与斜面一致的编码(-O);斜面传代入主细胞库的传代菌种,按编码器规则编码(-M)。
        </p>
            </div>
            <!-- 查看全部弹窗 -->
            <el-dialog
                title="菌种源保藏出/入主细胞库登记表说明"
        title="菌种源保藏出/入细胞库登记表说明"
                :visible.sync="dialogVisible"
                width="50%"
                class="view-all-dialog"
            >
                <div class="dialog-content">
                    <p>1、菌种全部集中登记在【菌种源保藏出/入主细胞库登记表】,请分类管理。</p>
                    <p>1.1 接种入主细胞库(现代-M)的菌株经过培养和保藏。</p>
                    <p>1.2 从原始细胞库转入的菌株需要按照标准程序进行记录和管理。</p>
                    <p>1.3 主细胞库的菌株可用于科研项目和工业生产中的应用研究。</p>
                    <p>1.4 菌株转出时需要严格记录去向和用途,确保可追溯性。</p>
                    <p>1.5 主细胞库的菌株保存应当遵循标准操作规程,确保活性和稳定性。</p>
          <p>
            1. 菌种全部集中登记在【菌种源保藏出/入细胞库登记表】,菌种来源有 3
            条路径。1.1 是沙土管或甘油管的源头菌种;入原始细胞库(祖代-O)。1.2
            是斜面的源头菌种;接种入主细胞库(祖代-O)。经过育种、验证后,菌种保藏为甘油管或沙土管的,入原始细胞库(祖代-0)1.3
            是含菌物质自己分离后获得的斜面源头菌种,接种入主细胞库;经生产验证后,保藏为沙土管或甘油管,入原始细胞库(祖代-O)。2.
            菌种细胞库,分类入三库,进行传代运行管理。三类库存空间进行区分,保藏菌种。2.1
            原始细胞库(祖代-O)、2.2 主细胞库(母代-M)、2.3
            生产细胞库(子代-S)、(孙代-G)3. 细胞库编码规则3.1
            细胞库编码规则:DD-M-240919-01-(O-0109-01)DD:代表项目组。M:“O”代表祖代原始细胞库,”M“代表母代主细胞库,”S“代表子代生产细胞库,“G”代表孙代生产细胞库。240919:代表在
            24 年 9 月 19
            接种批次的菌种;或收到外来菌种时间的入库批次。01:代表两位序列号。(O-0109-01):代表传代菌种的编号3.1.1
            传代编码方式演例:祖代:DD-O-240919-01
            传母代:DD-M-241017-01-(O-091901)DD-M-241017-02-(O-091901)DD-M-241017-03-(O-091901)子代:DD-S-241019-01-(M-1017-02)版权归奥利元生物所有,禁止外传。DD-S-241019-02-(M-1017-03)孙代:DD-G-241109-01-(S-1019-02)3.1.2
            编码规则实现了编码唯一,编码可溯源,编码直观、方便。3.2
            细胞库说明:3.2.1
            直接购买、自行从(土壤、相关物料、商品)等分离出来菌株进入原始细胞库。3.2.2
            从原始细胞库中选取出来再次纯化、改造、提高性能的菌株进入主细胞库。3.2.3
            主细胞库中选取出稳定,生产性能良好的菌株扩培后保种进入生产细胞库。4.
            菌种选育-保藏过程编号说明4.1 菌种选育时,培养皿的编号可使用
            a-01、a-02 等用于清晰形态观察记录;菌落编号使用序号 1/2/3等。4.2
            接种斜面菌种编码(-O)使用原始细胞库编码;斜面转菌种保藏使用与斜面一致的编码(-O);斜面传代入主细胞库的传代菌种,按编码器规则编码(-M)。
          </p>
                </div>
            </el-dialog>
        </el-card>
        <!-- Table -->
        <TableCustom :queryForm="queryForm" :tableData="tableData" :total="total" @currentChange="handleCurrentChange"
            @sizeChange="handleSizeChange">
    <TableCustom
      :queryForm="queryForm"
      :tableData="tableData"
      :total="total"
      @currentChange="handleCurrentChange"
      @sizeChange="handleSizeChange"
    >
            <template #search>
                <el-form :model="form" label-width="auto" inline>
                    <el-form-item label="菌种编号:">
@@ -42,12 +84,13 @@
                    <el-form-item label="菌种名称:">
                        <el-input v-model="form.strainName" placeholder="请输入"></el-input>
                    </el-form-item>
                    <el-form-item label="状态:">
          <el-form-item v-if="roleType == 4" label="状态:">
                        <el-select v-model="form.status" placeholder="请选择">
                            <el-option label="全部" value=""></el-option>
                            <el-option label="已入库" value="1"></el-option>
                            <el-option label="已出库" value="2"></el-option>
                            <el-option label="入库待确认" value="3"></el-option>
              <el-option label="已出库" value="1"></el-option>
              <el-option label="出库待确认" value="2"></el-option>
              <el-option label="已入库" value="3"></el-option>
              <el-option label="入库待确认" value="4"></el-option>
                        </el-select>
                    </el-form-item>
                    <el-form-item class="search-btn-box">
@@ -60,229 +103,209 @@
            <template #setting>
                <div class="tableTitle">
                    <div class="flex a-center">
                        <div class="title" :class="{ active: currentType === 'list' }"
                            @click="handleTypeChange('list')">
                            主细胞列表</div>
                        <div class="drafts" :class="{ active: currentType === 'draft' }"
                            @click="handleTypeChange('draft')">
                            草稿箱</div>
            <div
              class="title"
              :class="{ active: currentType === 'list' }"
              @click="handleTypeChange('list')"
            >
              主细胞列表
                    </div>
                    <div class="flex a-center">
                        <el-button @click="handleNewStrain" class="el-icon-plus" type="primary" style="margin-right: 12px;">新增主细胞</el-button>
                        <el-button @click="handleBatchAdd" class="el-icon-plus" type="primary">批量新增</el-button>
            <div
              class="drafts"
              :class="{ active: currentType === 'draft' }"
              @click="handleTypeChange('draft')"
            >
              草稿箱
            </div>
          </div>
          <div v-if="roleType == 4" class="flex a-center">
            <el-button
              @click="handleNewStrain"
              class="el-icon-plus"
              type="primary"
              style="margin-right: 12px"
              >新增主细胞</el-button
            >
            <el-button
              @click="handleNewStrain"
              class="el-icon-plus"
              type="primary"
              >批量新增</el-button
            >
                    </div>
                </div>
            </template>
            <template #table>
                <el-table-column prop="strainNo" label="菌种编号" />
        <el-table-column prop="strainCode" label="菌种编号" />
                <el-table-column prop="strainName" label="菌种名称" />
                <el-table-column prop="source" label="菌种来源" />
                <el-table-column prop="method" label="鉴定方法" />
                <el-table-column prop="certificate" label="特征描述" />
                <el-table-column prop="storage" label="菌种保存方法" />
                <el-table-column prop="amount" label="保存位置" />
                <el-table-column prop="inventory" label="库存余量" />
                <el-table-column prop="notes" label="备注" />
                <el-table-column prop="status" label="当前状态">
        <el-table-column prop="strainSource" label="菌种来源" />
        <el-table-column prop="appraisalMethod" label="鉴定方法" />
        <el-table-column prop="features" label="特征描述" />
        <el-table-column prop="saveMethod" label="菌种保存方法" />
        <el-table-column prop="saveLocation" label="保藏位置" />
        <el-table-column prop="stock" label="库存余量" />
        <el-table-column prop="remark" label="备注" />
        <el-table-column
          v-if="currentType === 'list'"
          prop="status"
          label="当前状态"
        >
                    <template #default="{ row }">
                        <el-tag :type="getStatusType(row.status)">{{ getStatusText(row.status) }}</el-tag>
            <el-tag :type="getStatusType(row.status)">{{
              getStatusText(row.status)
            }}</el-tag>
                    </template>
                </el-table-column>
                <el-table-column label="操作" width="200">
                    <template #default="{ row }">
                        <el-button type="text" @click="handleDetail(row)">详情</el-button>
                        <el-button type="text" @click="handleEdit(row)">编辑</el-button>
                        <el-button type="text" @click="handleRecord(row)">出入库记录</el-button>
            <el-button v-if="row.status == 2 || row.status == 4" type="text" @click="handleEdit(row)">编辑</el-button>
            <el-button
              v-if="currentType === 'list'"
              type="text"
              @click="handleRecord(row)"
              >出入库记录</el-button
            >
            <el-button v-if="roleType == 1" type="text" @click="handleDelete(row)">删除</el-button>
                    </template>
                </el-table-column>
            </template>
        </TableCustom>
        <StrainDetail
            :visible.sync="detailVisible"
            :detail="currentDetail"
        />
    <StrainDetail :visible.sync="detailVisible" :detail="currentDetail" />
    </div>
</template>
<script>
import StrainDetail from '../strain-library-manage/components/StrainDetail.vue'
import StrainDetail from "../strain-library-manage/components/StrainDetail.vue";
import { getList, deleteStrainLibrary } from "../strain-library-manage/service";
export default {
    name: 'MainCellLibrary',
  name: "StrainLibraryManage",
    components: {
        StrainDetail
    StrainDetail,
    },
    data() {
        return {
            dialogVisible: false,
            currentType: 'list',
      currentType: "list",
            detailVisible: false,
            currentDetail: {},
            form: {
                strainNo: '',
                strainName: '',
                status: ''
        strainNo: "",
        strainName: "",
        status: "",
            },
            queryForm: {
                pageSize: 10,
                pageNum: 1
        pageNum: 1,
            },
            total: 800,
            tableData: [
                {
                    strainNo: 'M-2024001',
                    strainName: '大肠杆菌BL21',
                    source: '原始细胞库',
                    method: '分子生物学鉴定',
                    certificate: '常用表达宿主菌,含有DE3溶源体,适合蛋白表达',
                    storage: '甘油冷冻',
                    amount: 'M区-01-001',
                    inventory: '100',
                    notes: '高效表达宿主',
                    status: '1'
      tableData: [],
      roleType: "",
    };
                },
                {
                    strainNo: 'M-2024002',
                    strainName: '乳酸菌L.plantarum',
                    source: '菌种保藏中心',
                    method: '16S rDNA测序',
                    certificate: '革兰氏阳性杆菌,产乳酸,益生特性',
                    storage: '冷冻保存',
                    amount: 'M区-02-005',
                    inventory: '80',
                    notes: '发酵剂开发',
                    status: '1'
                },
                {
                    strainNo: 'M-2024003',
                    strainName: '酵母S.cerevisiae',
                    source: '原始细胞库',
                    method: '生理生化鉴定',
                    certificate: '椭圆形单细胞真菌,高效发酵能力',
                    storage: '斜面培养',
                    amount: 'M区-03-002',
                    inventory: '60',
                    notes: '酒精发酵',
                    status: '2'
                },
                {
                    strainNo: 'M-2024004',
                    strainName: '枯草芽孢杆菌',
                    source: '环境样本分离',
                    method: '形态学观察和生化鉴定',
                    certificate: '革兰氏阳性芽孢杆菌,可产多种酶类',
                    storage: '冻干保存',
                    amount: 'M区-01-003',
                    inventory: '90',
                    notes: '工业酶生产',
                    status: '3'
                },
                {
                    strainNo: 'M-2024005',
                    strainName: '链霉菌S.griseus',
                    source: '原始细胞库',
                    method: 'PCR鉴定',
                    certificate: '丝状菌,产生灰色气生菌丝和分生孢子',
                    storage: '液氮保存',
                    amount: 'M区-04-001',
                    inventory: '70',
                    notes: '抗生素研究',
                    status: '1'
                }
            ]
        }
  activated() {
    this.searchData();
    // 角色类型 1=超级管理员 2=审批人 3=工程师 4=实验员
    this.roleType = JSON.parse(sessionStorage.getItem("userInfo")).roleType;
    },
    methods: {
        handleViewMore() {
            this.dialogVisible = true;
    handleDelete(row) {
      this.$confirm("确定删除该数据吗?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      }).then(() => {
        deleteStrainLibrary({ id: row.id }).then((res) => {
            this.$message.success("删除成功");
            this.searchData();
        });
      });
        },
        resetForm() {
            this.form = {
                strainNo: '',
                strainName: '',
                status: ''
            }
            this.searchData()
        },
        searchData() {
            // 模拟搜索逻辑
            const { strainNo, strainName, status } = this.form
            let filteredData = [...this.tableData]
            if (strainNo) {
                filteredData = filteredData.filter(item =>
                    item.strainNo.toLowerCase().includes(strainNo.toLowerCase())
                )
            }
            if (strainName) {
                filteredData = filteredData.filter(item =>
                    item.strainName.toLowerCase().includes(strainName.toLowerCase())
                )
            }
            if (status) {
                filteredData = filteredData.filter(item =>
                    item.status === status
                )
            }
            this.total = filteredData.length
            // 实际项目中这里应该调用API
            console.log('搜索条件:', this.form)
            console.log('分页信息:', this.queryForm)
    handleRecord(row) {
      this.$router.push({
        path: `/strain-library/strain-library-manage/record?id=${row.id}`,
      });
        },
        handleNewStrain() {
            this.$router.push('/strain-library/main-cell-library/add')
            // Implement new strain logic
      this.$router.push({ path: "/strain-library/main-cell-library/add" });
        },
        handleBatchAdd() {
            // Implement batch add logic
    handleEdit(row) {
      this.$router.push({
        path: `/strain-library/main-cell-library/add?id=${row.id}`,
      });
        },
        handleDetail(row) {
            this.currentDetail = row;
            this.detailVisible = true;
        },
        handleEdit(row) {
            // Implement edit logic
    handleViewMore() {
      this.dialogVisible = true;
        },
        handleRecord(row) {
            this.$router.push({
                path: '/strain-library/strain-library-manage/record',
                query: {
                    id: row.strainNo
    resetForm() {
      this.form = {
        strainNo: "",
        strainName: "",
        status: "",
      };
      this.searchData();
    },
    searchData() {
      const params = {
        pageNum: this.queryForm.pageNum,
        pageSize: this.queryForm.pageSize,
        strainCode: this.form.strainNo,
        strainName: this.form.strainName,
        isDraft: this.currentType === "draft" ? 1 : 0,
        status: this.form.status,
        type: 2,
      };
      console.log(params);
      getList(params)
        .then((res) => {
          if (res.code === 200) {
            this.tableData = res.data.records;
            this.total = res.data.total;
                }
            })
        .catch((err) => {
          this.$message.error("数据加载失败");
        });
        },
        handleCurrentChange(page) {
            this.queryForm.pageNum = page
            // Implement page change logic
      this.queryForm.pageNum = page;
      this.searchData();
        },
        handleSizeChange(size) {
            this.queryForm.pageSize = size
            // Implement size change logic
      this.queryForm.pageSize = size;
      this.searchData();
        },
        handleTypeChange(type) {
            this.currentType = type;
            // Implement type change logic
      this.searchData();
        },
        getStatusType(status) {
            const types = {
                1: 'success',
                2: 'info',
                3: 'warning'
            }
            return types[status] || 'info'
        1: "warning",
        2: "warning",
        3: "success",
        4: "success",
      };
      return types[status] || "info";
        },
        getStatusText(status) {
            const texts = {
                1: '已入库',
                2: '已出库',
                3: '入库待确认'
            }
            return texts[status] || '未知状态'
        }
    }
}
        1: "已出库",
        2: "出库待确认",
        3: "已入库",
        4: "入库待确认",
      };
      return texts[status] || "未知状态";
    },
  },
};
</script>
<style scoped lang="less">
@@ -312,7 +335,7 @@
        .view-more {
            position: absolute;
            right: 0;
            color: #049C9A;
      color: #049c9a;
        }
    }
@@ -337,7 +360,7 @@
.search-form {
    margin-bottom: 20px;
    background: #F5F7FA;
  background: #f5f7fa;
    padding: 24px;
    border-radius: 8px;
@@ -365,17 +388,17 @@
    .tab {
        padding: 10px 30px;
        border: 1px solid #DCDFE6;
    border: 1px solid #dcdfe6;
        border-bottom: none;
        border-radius: 8px 8px 0 0;
        cursor: pointer;
        margin-right: 10px;
        background: #F5F7FA;
    background: #f5f7fa;
        &.active {
            background: #fff;
            border-color: #049C9A;
            color: #049C9A;
      border-color: #049c9a;
      color: #049c9a;
            font-weight: bold;
        }
    }
@@ -383,10 +406,6 @@
.flex {
    display: flex;
    align-items: center;
}
.a-center {
    align-items: center;
}
@@ -409,7 +428,6 @@
        line-height: 50px;
        width: 166px;
        text-align: center;
    }
    .drafts {
@@ -432,14 +450,13 @@
        background: #ffffff;
        border-radius: 8px 8px 0px 0px;
        border: 1px solid #049c9a;
    }
}
.view-all-dialog {
    :deep(.el-dialog__header) {
        padding: 20px;
        border-bottom: 1px solid #EBEEF5;
    border-bottom: 1px solid #ebeef5;
        margin-right: 0;
        
        .el-dialog__title {
culture/src/views/strain-library/main-cell-library/record.vue
@@ -1,352 +1,438 @@
<template>
  <div class="record-page">
    <div class="page-header">
      <div class="header-left">
        <el-page-header @back="goBack" content="主细胞出入库记录"></el-page-header>
      </div>
      <div class="header-right">
        <el-button type="primary" icon="el-icon-plus" @click="handleAddRecord">新增记录</el-button>
      </div>
    </div>
    <el-card class="record-card">
      <div class="strain-info">
    <!-- 基本信息展示区域 -->
    <el-card class="header-box">
      <div class="header-content">
        <!-- 第一行 -->
        <div class="info-row">
          <div class="info-item">
          <div class="info-item left-column">
            <span class="label">菌种编号:</span>
            <span class="value">{{ strainInfo.strainNo }}</span>
            <span class="value">{{ detail.strainCode }}</span>
          </div>
          <div class="info-item">
            <span class="label">菌种名称:</span>
            <span class="value">{{ strainInfo.strainName }}</span>
          </div>
          <div class="info-item">
            <span class="label">菌种来源:</span>
            <span class="value">{{ strainInfo.source }}</span>
          </div>
        </div>
        <div class="info-row">
          <div class="info-item">
          <div class="info-item flex-column">
            <span class="label">鉴定方法:</span>
            <span class="value">{{ strainInfo.method }}</span>
            <span class="value">{{ detail.appraisalMethod }}</span>
          </div>
          <div class="info-item full">
            <span class="label">特征描述:</span>
            <span class="value">{{ strainInfo.certificate }}</span>
          </div>
        </div>
        <div class="info-row">
          <div class="info-item">
            <span class="label">菌种保存方法:</span>
            <span class="value">{{ strainInfo.storage }}</span>
          </div>
          <div class="info-item">
            <span class="label">保存位置:</span>
            <span class="value">{{ strainInfo.amount }}</span>
          </div>
          <div class="info-item">
            <span class="label">出入库状态:</span>
            <span class="value status">{{ strainInfo.statusText }}</span>
          </div>
          <div class="info-item flex-column">
            <span class="label">保藏位置:</span>
            <span class="value">{{ detail.saveLocation }}</span>
        </div>
      </div>
      
      <div class="record-timeline-container">
        <h3 class="section-title">出入库记录</h3>
        <RecordTimeline :list="recordList" />
        <!-- 第二行 -->
        <div class="info-row">
          <div class="info-item left-column">
            <span class="label">菌种名称:</span>
            <span class="value">{{ detail.strainName }}</span>
          </div>
          <div class="info-item flex-column full-width">
            <span class="label">特性描述:</span>
            <span class="value">{{ detail.features }}</span>
          </div>
        </div>
        <!-- 第三行 -->
        <div class="info-row">
          <div class="info-item left-column">
            <span class="label">菌种来源:</span>
            <span class="value">{{ detail.strainSource }}</span>
          </div>
          <div class="info-item flex-column">
            <span class="label">菌种保存方法:</span>
            <span class="value">{{ detail.saveMethod }}</span>
          </div>
        </div>
      </div>
    </el-card>
    
    <!-- 新增记录弹窗 -->
    <el-dialog
      title="新增出入库记录"
      :visible.sync="dialogVisible"
      width="500px"
    <!-- 出入库记录表格 -->
    <TableCustom
      :queryForm="queryForm"
      :tableData="recordList"
      :total="total"
      @currentChange="handlePageChange"
    >
      <el-form ref="recordForm" :model="recordForm" :rules="recordRules" label-width="100px">
        <el-form-item label="操作类型" prop="type">
          <el-radio-group v-model="recordForm.type">
            <el-radio label="入库">入库</el-radio>
            <el-radio label="出库">出库</el-radio>
          </el-radio-group>
        </el-form-item>
      <template #setting>
        <div class="tableTitle">
          <div class="flex a-center">
            <div
              class="title"
              :class="{ active: currentType === 'table' }"
              @click="handleTypeChange('table')"
            >
              原始细胞保藏出/入库登记表
            </div>
            <div
              class="drafts"
              :class="{ active: currentType === 'timeline' }"
              @click="handleTypeChange('timeline')"
            >
              原始细胞保藏出/入库时间轴
            </div>
          </div>
          <div class="flex a-center">
            <el-button
              v-if="roleType == 4"
              @click="handleAddRecord"
              class="el-icon-plus"
              type="primary"
              >新增出入库记录</el-button
            >
          </div>
        </div>
      </template>
        
        <el-form-item label="操作人" prop="operator">
          <el-input v-model="recordForm.operator" placeholder="请输入操作人"></el-input>
        </el-form-item>
        <el-form-item label="操作时间" prop="operateTime">
          <el-date-picker
            v-model="recordForm.operateTime"
            type="datetime"
            placeholder="选择日期时间"
            value-format="yyyy-MM-dd HH:mm:ss"
          ></el-date-picker>
        </el-form-item>
        <el-form-item label="保藏人" prop="reviewer">
          <el-input v-model="recordForm.reviewer" placeholder="请输入保藏人"></el-input>
        </el-form-item>
        <el-form-item label="操作数量" prop="amount">
          <el-input-number v-model="recordForm.amount" :min="1" :max="100"></el-input-number>
        </el-form-item>
        <el-form-item label="备注" prop="remarks">
          <el-input
            type="textarea"
            v-model="recordForm.remarks"
            :rows="3"
            placeholder="请输入备注"
          ></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="submitRecord">确 定</el-button>
      <template #table v-if="currentType === 'table'">
        <el-table-column prop="type" label="出库/入库">
          <template #default="{ row }">
            <span>
              {{ row.type === 1 ? "出库" : "入库" }}
      </span>
    </el-dialog>
          </template>
        </el-table-column>
        <el-table-column prop="boundTime" label="操作时间" />
        <el-table-column prop="handleSignature" label="操作人签字">
          <template #default="{ row }">
            <el-image
              v-if="row.handleSignature"
              style="width: 100px; height: 100px"
              :src="row.handleSignature"
              :preview-src-list="[row.handleSignature]"
            >
            </el-image>
          </template>
        </el-table-column>
        <el-table-column prop="preserveSignature" label="菌种保藏人签字">
          <template #default="{ row }">
            <el-image
              v-if="row.preserveSignature"
              style="width: 100px; height: 100px"
              :src="row.preserveSignature"
              :preview-src-list="[row.preserveSignature]"
            >
            </el-image>
          </template>
        </el-table-column>
        <el-table-column prop="status" label="状态">
          <template #default="{ row }">
            <el-tag :type="row.preserveSignature ? 'success' : 'warning'">
              {{ row.preserveSignature ? "已确认" : "待确认" }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="操作" width="180">
          <template #default="{ row }">
            <el-button
              v-if="!row.preserveSignature && roleType == 3"
              type="text"
              class="operation-btn"
              @click="handleConfirm(row)"
              >确认</el-button
            >
            <el-button
              type="text"
              class="operation-btn"
              @click="handleView(row)"
              >详情</el-button
            >
            <el-button v-if="roleType == 1" type="text" @click="handleDelete(row)">删除</el-button>
          </template>
        </el-table-column>
      </template>
      <template #tableCustom v-if="currentType === 'timeline'">
        <record-timeline :list="timelineList" />
      </template>
    </TableCustom>
    <!-- 详情弹窗 -->
    <record-detail-dialog
      :visible.sync="dialogVisible"
      :record-data="currentRecord"
      @close="handleDialogClose"
      @confirm="handleOutbound"
      :type="dialogType"
    />
    <!-- 新增出入库记录弹窗 -->
    <add-record-dialog
      :visible.sync="addDialogVisible"
      @confirm="handleAddRecordConfirm"
    />
  </div>
</template>
<script>
import RecordTimeline from '../strain-library-manage/components/RecordTimeline.vue'
import RecordDetailDialog from "../strain-library-manage/components/RecordDetailDialog.vue";
import AddRecordDialog from "../strain-library-manage/components/AddRecordDialog.vue";
import RecordTimeline from "../strain-library-manage/components/RecordTimeline.vue";
import {
  timeList,
  getDetail,
  addWarehousing,
  getDetailById,
  confirmWarehousing,
} from "./service";
export default {
  name: 'MainCellRecord',
  name: "StrainRecord",
  components: {
    RecordTimeline
    RecordDetailDialog,
    AddRecordDialog,
    RecordTimeline,
  },
  data() {
    return {
      strainId: '',
      strainInfo: {
        strainNo: 'M-2024001',
        strainName: '大肠杆菌BL21',
        source: '原始细胞库',
        method: '分子生物学鉴定',
        certificate: '常用表达宿主菌,含有DE3溶源体,适合蛋白表达',
        storage: '甘油冷冻',
        amount: 'M区-01-001',
        inventory: '100',
        status: '1',
        statusText: '已入库'
      currentType: "table",
      detail: {},
      currentPage: 1,
      pageSize: 10,
      total: 0,
      queryForm: {
        pageSize: 10,
        pageNum: 1,
      },
      recordList: [
        {
          type: '入库',
          operator: '张三',
          operateTime: '2024-05-01 10:30:00',
          reviewer: '李四',
          confirmTime: '2024-05-01 14:20:00'
        },
        {
          type: '出库',
          operator: '王五',
          operateTime: '2024-05-15 09:45:00',
          reviewer: '赵六',
          confirmTime: '2024-05-15 11:30:00'
        },
        {
          type: '入库',
          operator: '钱七',
          operateTime: '2024-05-20 14:00:00',
          reviewer: '孙八',
          confirmTime: '2024-05-20 16:15:00'
        }
      ],
      recordList: [],
      timelineList: [],
      dialogVisible: false,
      recordForm: {
        type: '入库',
        operator: '',
        operateTime: '',
        reviewer: '',
        amount: 1,
        remarks: ''
      currentRecord: {},
      addDialogVisible: false,
      dialogType: "detail",
      roleType: "",
    };
      },
      recordRules: {
        type: [
          { required: true, message: '请选择操作类型', trigger: 'change' }
        ],
        operator: [
          { required: true, message: '请输入操作人', trigger: 'blur' }
        ],
        operateTime: [
          { required: true, message: '请选择操作时间', trigger: 'change' }
        ],
        reviewer: [
          { required: true, message: '请输入保藏人', trigger: 'blur' }
        ],
        amount: [
          { required: true, message: '请输入操作数量', trigger: 'blur' }
        ]
  activated() {
    this.roleType = JSON.parse(sessionStorage.getItem("userInfo")).roleType;
    // 获取路由参数中的菌种信息
    const strainId = this.$route.query.id;
    this.queryForm.id = strainId;
    if (strainId) {
      this.getStrainDetail(strainId);
      this.getRecordList();
      }
    }
  },
  created() {
    // 获取路由参数中的菌种ID
    this.strainId = this.$route.query.id
    // 实际项目中这里应该根据ID加载菌种信息和记录列表
    console.log('加载菌种ID:', this.strainId)
  },
  methods: {
    goBack() {
      this.$router.push('/strain-library/main-cell-library')
    handleDelete(row) {
      this.$confirm("确定删除该数据吗?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      }).then(() => {
        deleteWarehousing({ id: row.id }).then((res) => {
          this.$message.success("删除成功");
          this.getRecordList();
        });
      });
    },
    getStrainDetail(id) {
      // 这里应该调用接口获取菌种详情
      getDetail({ id }).then((res) => {
        this.detail = res;
      });
    },
    getRecordList() {
      // 这里应该调用接口获取出入库记录
      timeList(this.queryForm).then((res) => {
        this.timelineList = res.data;
      });
      getDetailById({ id: this.$route.query.id }).then((res) => {
        this.recordList = res.warehousingList.records;
        this.total = res.warehousingList.total;
      });
    },
    handleView(row) {
      this.dialogType = "detail";
      this.currentRecord = row;
      this.dialogVisible = true;
    },
    handleConfirm(row) {
      this.dialogType = "confirm";
      this.currentRecord = row;
      this.dialogVisible = true;
    },
    handlePageChange(page) {
      this.queryForm.pageNum = page;
      // 这里应该调用接口获取对应页码的数据
    },
    handleTypeChange(type) {
      this.currentType = type;
    },
    handleAddRecord() {
      this.dialogVisible = true
      this.resetRecordForm()
      this.addDialogVisible = true;
    },
    submitRecord() {
      this.$refs.recordForm.validate(valid => {
        if (valid) {
          // 表单验证通过,提交数据
          console.log('提交的记录数据:', this.recordForm)
          // 模拟添加记录到列表
          const newRecord = {
            type: this.recordForm.type,
            operator: this.recordForm.operator,
            operateTime: this.recordForm.operateTime,
            reviewer: this.recordForm.reviewer,
            confirmTime: new Date().toLocaleString()
          }
          // 添加到记录列表的开头
          this.recordList.unshift(newRecord)
          // 关闭弹窗
          this.dialogVisible = false
          // 显示成功消息
          this.$message.success('记录添加成功')
    handleDialogClose() {
      this.currentRecord = {};
      this.dialogVisible = false;
    },
    handleOutbound(data) {
      // 这里调用出库API
      confirmWarehousing({
        id: this.currentRecord.id,
        preserveSignature: data.preserveSignature,
      }).then((res) => {
        console.log(res);
        if (res.code == 200) {
          this.$message.success("操作成功");
          this.dialogVisible = false;
          // 刷新列表
          this.getRecordList();
        } else {
          this.$message.error('请正确填写表单')
          return false
          this.$message.error(res.msg);
        }
      })
      });
    },
    resetRecordForm() {
      this.recordForm = {
        type: '入库',
        operator: '',
        operateTime: '',
        reviewer: '',
        amount: 1,
        remarks: ''
    handleAddRecordConfirm(record) {
      addWarehousing({ ...record, trainLibraryId: this.$route.query.id }).then(
        (res) => {
          this.$message.success("操作成功");
          this.getRecordList();
      }
    }
  }
}
      );
    },
    goBack() {
      this.$router.go(-1);
    },
  },
};
</script>
<style scoped lang="less">
<style lang="less" scoped>
.record-page {
  padding: 20px;
}
  min-height: 100vh;
.page-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  .header-box {
  margin-bottom: 20px;
  .header-left {
    :deep(.el-page-header__content) {
      font-size: 18px;
      font-weight: bold;
      color: #333;
    }
  }
}
.record-card {
  background: #fff;
  border-radius: 16px;
  margin-bottom: 20px;
}
    background: rgba(255, 255, 255, 0.8);
    height: 130px;
    overflow: hidden;
.strain-info {
  padding: 10px 0;
    .header-content {
      color: rgba(0, 0, 0, 0.88);
      font-size: 14px;
      line-height: 1.5;
  
  .info-row {
    display: flex;
    flex-wrap: wrap;
    margin-bottom: 16px;
        margin-bottom: 8px;
    
    &:last-child {
      margin-bottom: 0;
    }
  }
  
  .info-item {
    flex: 1;
    min-width: 200px;
    margin-right: 20px;
          display: flex;
          align-items: flex-start;
          margin-right: 24px;
          margin-bottom: 6px;
    
    &:last-child {
      margin-right: 0;
          &.left-column {
            width: 33%;
            min-width: 200px;
    }
    
    &.full {
      flex: 2;
          &.flex-column {
            flex: 1;
            min-width: 150px;
          }
          &.full-width {
            flex: 1;
            min-width: 300px;
    }
    
    .label {
      color: #606266;
      margin-right: 8px;
            white-space: nowrap;
    }
    
    .value {
      color: #333;
      font-weight: 500;
      &.status {
        color: #67C23A;
            flex: 1;
            color: #303133;
            word-break: break-all;
            display: -webkit-box;
            -webkit-line-clamp: 1;
            -webkit-box-orient: vertical;
            overflow: hidden;
            text-overflow: ellipsis;
          }
      }
    }
  }
}
.record-timeline-container {
  margin-top: 30px;
  .flex {
    display: flex;
    align-items: center;
  }
  
  .section-title {
    font-size: 16px;
    color: #333;
    margin-bottom: 20px;
    font-weight: 500;
  .tableTitle {
    display: flex;
    padding-bottom: 20px;
    justify-content: space-between;
    align-items: center;
    .title {
      background: #fafafc;
      border-radius: 8px 8px 0px 0px;
      border: 1px solid #dcdfe6;
      font-weight: bold;
      font-size: 18px;
      color: #606266;
      cursor: pointer;
      height: 50px;
      line-height: 50px;
      width: 280px;
      text-align: center;
    }
    .drafts {
      background: #fafafc;
      border-radius: 8px 8px 0px 0px;
      border: 1px solid #dcdfe6;
      font-weight: 400;
      font-size: 18px;
      color: #606266;
      margin-left: 16px;
      cursor: pointer;
      height: 50px;
      line-height: 50px;
      width: 280px;
      text-align: center;
    }
    .active {
      color: #049c9a;
      background: #ffffff;
      border-radius: 8px 8px 0px 0px;
      border: 1px solid #049c9a;
  }
}
:deep(.el-dialog__body) {
  padding: 20px 30px;
}
  .timeline-container {
    padding: 20px;
    background: rgba(255, 255, 255, 0.8);
@media screen and (max-width: 768px) {
  .strain-info {
    .info-row {
      flex-direction: column;
      .info-item {
        margin-right: 0;
    .timeline-card {
        margin-bottom: 10px;
      background: rgba(255, 255, 255, 0.8);
        
        &:last-child {
          margin-bottom: 0;
      h4 {
        margin: 0 0 10px;
        font-size: 16px;
        font-weight: bold;
        }
      p {
        margin: 5px 0;
        font-size: 14px;
      }
    }
  }
  
  .page-header {
    flex-direction: column;
    align-items: flex-start;
    .header-right {
      margin-top: 16px;
    }
  .operation-btn {
    margin-right: 12px;
  }
}
</style> 
culture/src/views/strain-library/main-cell-library/service.js
New file
@@ -0,0 +1,56 @@
import axios from '@/utils/request';
// 列表
export const getList = (data) => {
  return axios.post('/api/t-train-library/pageList', { ...data })
}
// 新增
export const add = (data) => {
  return axios.post('/api/t-train-library/add', { ...data })
}
// 编辑
export const edit = (data) => {
  return axios.post('/api/t-train-library/update', { ...data })
}
// 查看详情
export const getDetail = (params) => {
  return axios.get('/open/t-train-library/getDetailEditById', { params })
}
// 批量新增
export const addBatch = (data) => {
  return axios.post('/api/t-train-library/addBatch', data)
}
// 查看菌种库详情
export const getDetailById = (data) => {
  return axios.post('/open/t-train-library/getDetailById', { ...data })
}
// 获取菌种库出入库时间轴列表
export const timeList = (data) => {
  return axios.post('/api/t-train-library/timeList?id='+data.id, { ...data })
}
// 新增菌种库出入记录
export const addWarehousing = (data) => {
  return axios.post('/open/t-train-library/addWarehousing', { ...data })
}
// 确认出入库
export const confirmWarehousing = (data) => {
  return axios.post('/api/t-train-library/confirm', { ...data })
}
// 删除菌种库
export const deleteStrainLibrary = (params) => {
  return axios.delete('/open/t-train-library/deleteById', { params })
}
// 删除菌种库出入库记录
export const deleteWarehousing = (params) => {
  return axios.delete('/open/t-train-library/deleteWarehousingById', { params })
}
culture/src/views/strain-library/production-cell-library/add.vue
@@ -1,141 +1,294 @@
<template>
    <Card>
        <el-form
            :model="form"
            :rules="rules"
            ref="strainForm"
            label-position="top"
            class="strain-form"
        >
            <div class="form-grid">
                <el-form-item label="菌种编号" prop="strainNo" required>
                    <el-input v-model="form.strainNo" placeholder="请输入"></el-input>
        <el-form :model="form" :rules="rules" ref="strainForm" label-position="top" class="strain-form">
            <div class="form-row three-columns">
                <el-form-item label="菌种编号" prop="strainCode">
                    <el-input v-model="form.strainCode" placeholder="请输入"></el-input>
                </el-form-item>
                <el-form-item label="菌种名称" prop="strainName" required>
                <el-form-item label="菌种名称" prop="strainName">
                    <el-input v-model="form.strainName" placeholder="请输入"></el-input>
                </el-form-item>
                <el-form-item label="菌种来源" prop="source" required>
                    <el-input v-model="form.source" placeholder="请输入"></el-input>
                </el-form-item>
                <el-form-item label="鉴定方法" prop="identificationMethod" required>
                    <el-input v-model="form.identificationMethod" placeholder="请输入"></el-input>
                </el-form-item>
                <el-form-item label="特征描述" prop="characteristics" required>
                    <el-input v-model="form.characteristics" placeholder="请输入"></el-input>
                </el-form-item>
                <el-form-item label="菌种保存方法" prop="preservationMethod" required>
                    <el-input v-model="form.preservationMethod" placeholder="请输入"></el-input>
                </el-form-item>
                <el-form-item label="保存位置" prop="storageLocation" required>
                    <el-input v-model="form.storageLocation" placeholder="请输入"></el-input>
                <el-form-item label="菌种来源" prop="strainSource">
                    <el-input v-model="form.strainSource" placeholder="请输入"></el-input>
                </el-form-item>
            </div>
            <div class="form-row">
                <el-form-item label="鉴定方法" prop="appraisalMethod">
                    <el-input v-model="form.appraisalMethod" placeholder="请输入"></el-input>
                </el-form-item>
            </div>
            <div class="form-row">
                <el-form-item label="特征描述" prop="features" class="full-width">
                    <el-input type="textarea" v-model="form.features" :rows="4" placeholder="请输入"></el-input>
                </el-form-item>
            </div>
            <div class="form-row three-columns">
                <el-form-item label="保藏位置" prop="saveLocation">
                    <el-input v-model="form.saveLocation" placeholder="请输入"></el-input>
                </el-form-item>
                <el-form-item label="菌种保存方法" prop="saveMethod">
                    <el-input v-model="form.saveMethod" placeholder="请输入"></el-input>
                </el-form-item>
                <div class="form-item-placeholder"></div>
            </div>
            <div class="form-row">
                <el-form-item label="备注" prop="remarks" class="full-width">
                    <el-input
                        type="textarea"
                        v-model="form.remarks"
                        :rows="4"
                        placeholder="请输入"
                    ></el-input>
                    <el-input type="textarea" v-model="form.remark" :rows="4" placeholder="请输入"></el-input>
                </el-form-item>
            </div>
            <div class="end-btn" style="margin-top: 38px">
                <el-button type="primary" @click="handleSubmit">提交</el-button>
                <el-button @click="handleDraft">存草稿</el-button>
                <el-button type="primary" @click="handleSubmit(0)">提交</el-button>
                <el-button v-if="!$route.query.id" type="primary" @click="handleBatchAdd">批量新增</el-button>
                <el-button @click="handleSubmit(1)">存草稿</el-button>
            </div>
        </el-form>
        <!-- 批量新增弹窗 -->
        <el-dialog title="批量新增" :visible.sync="batchAddDialogVisible" width="520px" :close-on-click-modal="false"
            :close-on-press-escape="false" custom-class="batch-add-dialog">
            <div class="dialog-content">
                <el-form :model="batchForm" ref="batchFormRef" label-position="top" class="batch-form">
                    <el-form-item label="批量新增数量" prop="count"
                        :rules="[{ required: true, message: '请输入批量新增数量', trigger: 'blur' }]">
                        <el-input v-model.number="batchForm.count" placeholder="请输入" />
                    </el-form-item>
                </el-form>
                <div class="dialog-notice">
                    <p>注意:操作批量新增后,系统会自动生成对应数量的菌种数据,</p>
                    <p>这些菌种的基础信息内容都是一致的,唯独菌种编号内容系统</p>
                    <p>不会自动生成,需要操作员自行编辑</p>
                </div>
            </div>
            <template #footer>
                <div class="end-btn">
                    <el-button type="primary" @click="handleConfirmBatchAdd">确认新增</el-button>
                </div>
            </template>
        </el-dialog>
        <!-- 签字确认组件 -->
        <SignatureCanvas
            :visible.sync="signatureVisible"
            @confirm="handleSignatureConfirm"
        />
        <SignatureCanvas :visible.sync="signatureVisible" @confirm="handleSignatureConfirm" />
    </Card>
</template>
<script>
import SignatureCanvas from '@/components/SignatureCanvas.vue'
import { add, edit, getDetail, addBatch } from './service'
export default {
    name: 'AddProductionCell',
    name: 'StrainLibraryManageAdd',
    components: {
        SignatureCanvas
    },
    data() {
        return {
            batchAddDialogVisible: false,
            signatureVisible: false,
            currentAction: '', // 'submit' or 'batchAdd'
            batchForm: {
                count: ''
            },
            form: {
                strainNo: '',
                strainCode: '',
                strainName: '',
                source: '',
                identificationMethod: '',
                appraisalMethod: '',
                characteristics: '',
                storageLocation: '',
                preservationMethod: '',
                remarks: ''
                remark: ''
            },
            rules: {
                strainNo: [{ required: true, message: '请输入菌种编号', trigger: 'blur' }],
                strainCode: [{
                    validator: (rule, value, callback) => {
                        if (this.currentAction === 'submit' && !value) {
                            callback(new Error('请输入菌种编号'));
                        } else {
                            callback();
                        }
                    },
                    trigger: 'change'
                }],
                strainName: [{ required: true, message: '请输入菌种名称', trigger: 'blur' }],
                source: [{ required: true, message: '请输入菌种来源', trigger: 'blur' }],
                identificationMethod: [{ required: true, message: '请输入鉴定方法', trigger: 'blur' }],
                characteristics: [{ required: true, message: '请输入特征描述', trigger: 'blur' }],
                storageLocation: [{ required: true, message: '请输入保存位置', trigger: 'blur' }],
                preservationMethod: [{ required: true, message: '请输入菌种保存方法', trigger: 'blur' }]
                strainSource: [{ required: true, message: '请输入菌种来源', trigger: 'blur' }],
                appraisalMethod: [{ required: true, message: '请输入鉴定方法', trigger: 'blur' }],
                features: [{ required: true, message: '请输入特征描述', trigger: 'blur' }],
                saveLocation: [{ required: true, message: '请输入保存位置', trigger: 'blur' }],
                saveMethod: [{ required: true, message: '请输入菌种保存方法', trigger: 'blur' }]
            }
        }
    },
    activated() {
        if (this.$route.query.id) {
            getDetail({ id: this.$route.query.id }).then(res => {
                this.form = res
            })
        }
    },
    watch: {
        '$route.query.id'() {
            this.form = {
                strainCode: '',
                strainName: '',
                source: '',
                appraisalMethod: '',
                characteristics: '',
                storageLocation: '',
                preservationMethod: '',
                remark: ''
            }
        }
    },
    methods: {
        handleSubmit() {
        handleSubmit(isDraft) {
            this.currentAction = 'submit'
            this.$refs.strainForm.validate((valid) => {
                if (valid) {
                    this.form.isDraft = isDraft
                    if (isDraft == 1) {
                        //存草稿
                        this.handleSignatureConfirm('')
                    } else {
                        this.signatureVisible = true
                    }
                }
            })
        },
        handleBatchAdd() {
            this.currentAction = 'batchAdd'
            this.$refs.strainForm.validate((valid) => {
                if (valid) {
                    this.batchAddDialogVisible = true
                }
            })
        },
        handleConfirmBatchAdd() {
            this.$refs.batchFormRef.validate((valid) => {
                if (valid) {
                    this.batchAddDialogVisible = false
                    this.signatureVisible = true
                }
            })
        },
        handleDraft() {
            // 实现存草稿逻辑
            console.log('save draft', this.form)
            this.$message.success('草稿保存成功')
        },
        handleSignatureConfirm(signatureImage) {
            this.signatureVisible = false
            // 处理提交逻辑
            console.log('submit form with signature:', this.form, signatureImage)
            this.$message.success('提交成功')
            this.$router.back()
        async handleSignatureConfirm(signatureImage) {
            let requestData = {
                strainCode: this.form.strainCode,
                strainName: this.form.strainName,
                strainSource: this.form.strainSource,
                appraisalMethod: this.form.appraisalMethod,
                features: this.form.features,
                saveLocation: this.form.saveLocation,
                saveMethod: this.form.saveMethod,
                remark: this.form.remark,
                signature: signatureImage,
                type: 3,
            };
            if (this.currentAction === 'batchAdd') {
                requestData.batchCount = this.batchForm.count;
            } else {
                requestData.isDraft = this.form.isDraft
            }
            try {
                if (this.$route.query.id) {
                    requestData.id = this.$route.query.id;
                    await edit(requestData);
                } else if (this.currentAction === 'batchAdd') {
                    await addBatch(requestData);
                } else {
                    await add(requestData);
                }
                this.signatureVisible = false;
                this.$router.back();
                this.$message.success('操作成功');
            } catch (error) {
                this.$message.error('操作失败');
            }
        }
    }
}
</script>
<style scoped lang="less">
.strain-form {
    padding: 0 40px;
.add-strain {
    height: 100%;
    background: #F5F7FA;
    .form-grid {
        display: grid;
        grid-template-columns: repeat(3, 1fr);
        gap: 24px;
    .form-card {
        background: #fff;
        border-radius: 8px;
    }
}
.header-title {
        margin-bottom: 24px;
        
        @media screen and (max-width: 1200px) {
            grid-template-columns: repeat(2, 1fr);
    &-left {
        display: flex;
        align-items: center;
        img {
            width: 20px;
            height: 20px;
            margin-right: 8px;
        }
        
        @media screen and (max-width: 768px) {
            grid-template-columns: 1fr;
        div {
            font-size: 18px;
            font-weight: bold;
            color: #303133;
        }
    }
}
.end-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    button {
        width: 180px;
        height: 36px;
        // background: #409EFF;
    }
}
.strain-form {
    padding: 0 40px;
    .form-row {
        display: flex;
        flex-wrap: wrap;
        gap: 24px;
        margin-bottom: 24px;
        &.three-columns {
            .el-form-item,
            .form-item-placeholder {
                flex: 1;
                min-width: 280px;
                @media screen and (max-width: 1200px) {
                    min-width: calc(50% - 12px);
                }
                @media screen and (max-width: 768px) {
                    min-width: 100%;
                }
            }
            .form-item-placeholder {
                @media screen and (max-width: 1200px) {
                    display: none;
                }
            }
        }
        .el-form-item {
            margin-bottom: 0;
@@ -170,6 +323,91 @@
    }
}
.batch-add-dialog {
    :deep(.el-dialog__header) {
        margin: 0;
        padding: 20px;
        text-align: center;
        border-bottom: 1px solid #EBEEF5;
        .el-dialog__title {
            font-size: 16px;
            font-weight: 600;
            color: #303133;
        }
    }
    :deep(.el-dialog__body) {
        padding: 20px;
    }
    .dialog-content {
        display: flex;
        flex-direction: column;
        align-items: center;
    }
    .batch-form {
        width: 100%;
        display: flex;
        flex-direction: column;
        align-items: center;
        :deep(.el-form-item) {
            width: 320px;
            margin-bottom: 0;
        }
        :deep(.el-form-item__label) {
            width: 100%;
            color: #606266;
            font-weight: normal;
            padding-bottom: 8px;
            &::before {
                color: #F56C6C;
            }
        }
        :deep(.el-input) {
            width: 100%;
            input {
                width: 100%;
            }
        }
    }
    .dialog-notice {
        margin-top: 16px;
        text-align: center;
        p {
            margin: 0;
            line-height: 22px;
            color: #606266;
            font-size: 14px;
        }
    }
    :deep(.el-dialog__footer) {
        padding: 0 20px 20px;
        text-align: center;
        .el-button {
            width: 180px;
            height: 36px;
            padding: 0;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 14px;
            border-radius: 4px;
            margin: 0;
        }
    }
}
.end-btn {
    display: flex;
    align-items: center;
culture/src/views/strain-library/production-cell-library/index.vue
@@ -2,36 +2,80 @@
  <div class="list">
    <el-card class="header-box">
      <div class="box-title">
        <img src="@/assets/public/notice.png" class="header-icon">
        <span>【菌种源保藏出/入细胞库登记表】说明</span>
        <el-button type="text" class="view-more" @click="handleViewMore">查看全部 >></el-button>
        <img src="@/assets/public/notice.png" class="header-icon" />
        <span>菌种源保藏出/入细胞库登记表说明</span>
        <el-button type="text" class="view-more" @click="handleViewMore"
          >查看全部 >></el-button
        >
      </div>
      <div class="header-content" :class="{ 'collapsed': true }">
        <p>1、菌种全部集中登记在【菌种源保藏出/入细胞库登记表】,请将来源有3 类菌经。</p>
        <p>1.1 原净土管理日油性的源头菌种:入细胞细胞库(现代-O)。</p>
        <p>1.2 是到菌的源头菌种:接种入主细胞库(现代-O),经过百种、验证后,菌种被保存日油管理沙土菌种,入细胞细胞库(现代-O)。</p>
        <p>1.3 是否菌种能自己分离后获得的源头菌种,接种入主细胞库:经过产验证后,保藏为少土管理日油管,入细胞细胞库(现代-O)。</p>
      <div class="header-content" :class="{ collapsed: true }">
        <p>
          1. 菌种全部集中登记在【菌种源保藏出/入细胞库登记表】,菌种来源有 3
          条路径。1.1 是沙土管或甘油管的源头菌种;入原始细胞库(祖代-O)。1.2
          是斜面的源头菌种;接种入主细胞库(祖代-O)。经过育种、验证后,菌种保藏为甘油管或沙土管的,入原始细胞库(祖代-0)1.3
          是含菌物质自己分离后获得的斜面源头菌种,接种入主细胞库;经生产验证后,保藏为沙土管或甘油管,入原始细胞库(祖代-O)。
          2.
          菌种细胞库,分类入三库,进行传代运行管理。三类库存空间进行区分,保藏菌种。2.1
          原始细胞库(祖代-O)、2.2 主细胞库(母代-M)、2.3
          生产细胞库(子代-S)、(孙代-G)3. 细胞库编码规则3.1
          细胞库编码规则:DD-M-240919-01-(O-0109-01)DD:代表项目组。M:“O”代表祖代原始细胞库,”M“代表母代主细胞库,”S“代表子代生产细胞库,“G”代表孙代生产细胞库。240919:代表在
          24 年 9 月 19
          接种批次的菌种;或收到外来菌种时间的入库批次。01:代表两位序列号。(O-0109-01):代表传代菌种的编号3.1.1
          传代编码方式演例:祖代:DD-O-240919-01
          传母代:DD-M-241017-01-(O-091901)DD-M-241017-02-(O-091901)DD-M-241017-03-(O-091901)子代:DD-S-241019-01-(M-1017-02)版权归奥利元生物所有,禁止外传。DD-S-241019-02-(M-1017-03)孙代:DD-G-241109-01-(S-1019-02)3.1.2
          编码规则实现了编码唯一,编码可溯源,编码直观、方便。3.2
          细胞库说明:3.2.1
          直接购买、自行从(土壤、相关物料、商品)等分离出来菌株进入原始细胞库。3.2.2
          从原始细胞库中选取出来再次纯化、改造、提高性能的菌株进入主细胞库。3.2.3
          主细胞库中选取出稳定,生产性能良好的菌株扩培后保种进入生产细胞库。4.
          菌种选育-保藏过程编号说明4.1 菌种选育时,培养皿的编号可使用 a-01、a-02
          等用于清晰形态观察记录;菌落编号使用序号 1/2/3等。4.2
          接种斜面菌种编码(-O)使用原始细胞库编码;斜面转菌种保藏使用与斜面一致的编码(-O);斜面传代入主细胞库的传代菌种,按编码器规则编码(-M)。
        </p>
      </div>
      <!-- 查看全部弹窗 -->
      <el-dialog
        title="菌种源保藏出/入生产细胞库登记表说明"
        title="菌种源保藏出/入细胞库登记表说明"
        :visible.sync="dialogVisible"
        width="50%"
        class="view-all-dialog"
      >
        <div class="dialog-content">
          <p>1、菌种全部集中登记在【菌种源保藏出/入细胞库登记表】,请将来源有3 类菌经。</p>
          <p>1.1 原净土管理日油性的源头菌种:入细胞细胞库(现代-O)。</p>
          <p>1.2 是到菌的源头菌种:接种入主细胞库(现代-O),经过百种、验证后,菌种被保存日油管理沙土菌种,入细胞细胞库(现代-O)。</p>
          <p>1.3 是否菌种能自己分离后获得的源头菌种,接种入主细胞库:经过产验证后,保藏为少土管理日油管,入细胞细胞库(现代-O)。</p>
          <p>
            1. 菌种全部集中登记在【菌种源保藏出/入细胞库登记表】,菌种来源有 3
            条路径。1.1 是沙土管或甘油管的源头菌种;入原始细胞库(祖代-O)。1.2
            是斜面的源头菌种;接种入主细胞库(祖代-O)。经过育种、验证后,菌种保藏为甘油管或沙土管的,入原始细胞库(祖代-0)1.3
            是含菌物质自己分离后获得的斜面源头菌种,接种入主细胞库;经生产验证后,保藏为沙土管或甘油管,入原始细胞库(祖代-O)。2.
            菌种细胞库,分类入三库,进行传代运行管理。三类库存空间进行区分,保藏菌种。2.1
            原始细胞库(祖代-O)、2.2 主细胞库(母代-M)、2.3
            生产细胞库(子代-S)、(孙代-G)3. 细胞库编码规则3.1
            细胞库编码规则:DD-M-240919-01-(O-0109-01)DD:代表项目组。M:“O”代表祖代原始细胞库,”M“代表母代主细胞库,”S“代表子代生产细胞库,“G”代表孙代生产细胞库。240919:代表在
            24 年 9 月 19
            接种批次的菌种;或收到外来菌种时间的入库批次。01:代表两位序列号。(O-0109-01):代表传代菌种的编号3.1.1
            传代编码方式演例:祖代:DD-O-240919-01
            传母代:DD-M-241017-01-(O-091901)DD-M-241017-02-(O-091901)DD-M-241017-03-(O-091901)子代:DD-S-241019-01-(M-1017-02)版权归奥利元生物所有,禁止外传。DD-S-241019-02-(M-1017-03)孙代:DD-G-241109-01-(S-1019-02)3.1.2
            编码规则实现了编码唯一,编码可溯源,编码直观、方便。3.2
            细胞库说明:3.2.1
            直接购买、自行从(土壤、相关物料、商品)等分离出来菌株进入原始细胞库。3.2.2
            从原始细胞库中选取出来再次纯化、改造、提高性能的菌株进入主细胞库。3.2.3
            主细胞库中选取出稳定,生产性能良好的菌株扩培后保种进入生产细胞库。4.
            菌种选育-保藏过程编号说明4.1 菌种选育时,培养皿的编号可使用
            a-01、a-02 等用于清晰形态观察记录;菌落编号使用序号 1/2/3等。4.2
            接种斜面菌种编码(-O)使用原始细胞库编码;斜面转菌种保藏使用与斜面一致的编码(-O);斜面传代入主细胞库的传代菌种,按编码器规则编码(-M)。
          </p>
        </div>
      </el-dialog>
    </el-card>
    <!-- Table -->
    <TableCustom :queryForm="queryForm" :tableData="tableData" :total="total" @currentChange="handleCurrentChange"
      @sizeChange="handleSizeChange">
    <TableCustom
      :queryForm="queryForm"
      :tableData="tableData"
      :total="total"
      @currentChange="handleCurrentChange"
      @sizeChange="handleSizeChange"
    >
      <template #search>
        <el-form :model="form" label-width="auto" inline>
          <el-form-item label="菌种编号:">
@@ -40,15 +84,13 @@
          <el-form-item label="菌种名称:">
            <el-input v-model="form.strainName" placeholder="请输入"></el-input>
          </el-form-item>
          <el-form-item label="状态:">
          <el-form-item v-if="roleType == 4" label="状态:">
            <el-select v-model="form.status" placeholder="请选择">
              <el-option label="全部" value=""></el-option>
              <el-option
                v-for="item in statusOptions"
                :key="item.value"
                :label="item.label"
                :value="item.value">
              </el-option>
              <el-option label="已出库" value="1"></el-option>
              <el-option label="出库待确认" value="2"></el-option>
              <el-option label="已入库" value="3"></el-option>
              <el-option label="入库待确认" value="4"></el-option>
            </el-select>
          </el-form-item>
          <el-form-item class="search-btn-box">
@@ -61,236 +103,207 @@
      <template #setting>
        <div class="tableTitle">
          <div class="flex a-center">
            <div class="title" :class="{ active: currentType === 'list' }"
              @click="handleTypeChange('list')">
              生产细胞列表</div>
            <div class="drafts" :class="{ active: currentType === 'draft' }"
              @click="handleTypeChange('draft')">
              草稿箱</div>
            <div
              class="title"
              :class="{ active: currentType === 'list' }"
              @click="handleTypeChange('list')"
            >
              生产细胞列表
          </div>
          <div class="flex a-center">
            <el-button @click="handleAdd" class="el-icon-plus" type="primary" style="margin-right: 12px;">新增生产细胞</el-button>
            <el-button @click="handleAdd" class="el-icon-plus" type="primary" style="margin-right: 12px;">批量新增</el-button>
            <div
              class="drafts"
              :class="{ active: currentType === 'draft' }"
              @click="handleTypeChange('draft')"
            >
              草稿箱
            </div>
          </div>
          <div v-if="roleType == 4" class="flex a-center">
            <el-button
              @click="handleNewStrain"
              class="el-icon-plus"
              type="primary"
              style="margin-right: 12px"
              >新增生产细胞</el-button
            >
            <el-button
              @click="handleNewStrain"
              class="el-icon-plus"
              type="primary"
              >批量新增</el-button
            >
          </div>
        </div>
      </template>
      <template #table>
        <el-table-column type="selection" width="55" />
        <el-table-column prop="strainNo" label="菌种编号" width="150" />
        <el-table-column prop="strainName" label="菌种名称" width="180" />
        <el-table-column prop="source" label="菌种来源" width="150" />
        <el-table-column prop="preservationMethod" label="鉴定方法" width="120" />
        <el-table-column prop="storageLocation" label="特征描述" width="150" />
        <el-table-column prop="inventory" label="菌种保存方法" width="100" />
        <el-table-column prop="inventory" label="保存位置" width="100" />
        <el-table-column prop="inventory" label="库存余量" width="100" />
        <el-table-column prop="inventory" label="备注"  />
        <el-table-column prop="status" label="状态" width="100">
          <template slot-scope="scope">
            <el-tag :type="getStatusType(scope.row.status)">{{ scope.row.status }}</el-tag>
        <el-table-column prop="strainCode" label="菌种编号" />
        <el-table-column prop="strainName" label="菌种名称" />
        <el-table-column prop="strainSource" label="菌种来源" />
        <el-table-column prop="appraisalMethod" label="鉴定方法" />
        <el-table-column prop="features" label="特征描述" />
        <el-table-column prop="saveMethod" label="菌种保存方法" />
        <el-table-column prop="saveLocation" label="保藏位置" />
        <el-table-column prop="stock" label="库存余量" />
        <el-table-column prop="remark" label="备注" />
        <el-table-column
          v-if="currentType === 'list'"
          prop="status"
          label="当前状态"
        >
          <template #default="{ row }">
            <el-tag :type="getStatusType(row.status)">{{
              getStatusText(row.status)
            }}</el-tag>
          </template>
        </el-table-column>
        <el-table-column label="操作" width="150" fixed="right">
          <template slot-scope="scope">
            <el-button type="text" @click="handleView(scope.row)">详情</el-button>
            <el-button type="text" @click="handleEdit(scope.row)">编辑</el-button>
            <el-button type="text" @click="handleDelete(scope.row)" class="delete-btn">删除</el-button>
        <el-table-column label="操作" width="200">
          <template #default="{ row }">
            <el-button type="text" @click="handleDetail(row)">详情</el-button>
            <el-button v-if="row.status == 2 || row.status == 4" type="text" @click="handleEdit(row)">编辑</el-button>
            <el-button
              v-if="currentType === 'list'"
              type="text"
              @click="handleRecord(row)"
              >出入库记录</el-button
            >
            <el-button v-if="roleType == 1" type="text" @click="handleDelete(row)">删除</el-button>
          </template>
        </el-table-column>
      </template>
    </TableCustom>
    <!-- 删除确认对话框 -->
    <el-dialog
      title="确认删除"
      :visible.sync="deleteDialogVisible"
      width="30%">
      <div class="delete-dialog-content">
        <i class="el-icon-warning-outline warning-icon"></i>
        <span>确定要删除该菌种记录吗?删除后将无法恢复。</span>
      </div>
      <span slot="footer" class="dialog-footer">
        <el-button @click="deleteDialogVisible = false">取消</el-button>
        <el-button type="danger" @click="confirmDelete">确定</el-button>
      </span>
    </el-dialog>
    <StrainDetail :visible.sync="detailVisible" :detail="currentDetail" />
  </div>
</template>
<script>
import StrainDetail from "../strain-library-manage/components/StrainDetail.vue";
import { getList, deleteStrainLibrary } from "../strain-library-manage/service";
export default {
  name: 'ProductionCellLibrary',
  name: "StrainLibraryManage",
  components: {
    StrainDetail,
  },
  data() {
    return {
      dialogVisible: false,
      currentType: 'list',
      currentType: "list",
      detailVisible: false,
      currentDetail: {},
      form: {
        strainNo: '',
        strainName: '',
        status: ''
        strainNo: "",
        strainName: "",
        status: "",
      },
      queryForm: {
        pageSize: 10,
        pageNum: 1
        pageNum: 1,
      },
      total: 100,
      loading: false,
      sourceOptions: [
        { value: '主细胞库', label: '主细胞库' },
        { value: '工作细胞库', label: '工作细胞库' },
        { value: '外部来源', label: '外部来源' }
      ],
      statusOptions: [
        { value: '正常', label: '正常' },
        { value: '缺货', label: '缺货' },
        { value: '异常', label: '异常' },
        { value: '已停用', label: '已停用' }
      ],
      total: 800,
      tableData: [],
      selectedRows: [],
      deleteDialogVisible: false,
      deleteRow: null
    }
      roleType: "",
    };
  },
  created() {
    this.fetchData();
  activated() {
    this.searchData();
    // 角色类型 1=超级管理员 2=审批人 3=工程师 4=实验员
    this.roleType = JSON.parse(sessionStorage.getItem("userInfo")).roleType;
  },
  methods: {
    handleDelete(row) {
      this.$confirm("确定删除该数据吗?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      }).then(() => {
        deleteStrainLibrary({ id: row.id }).then((res) => {
            this.$message.success("删除成功");
            this.searchData();
        });
      });
    },
    handleRecord(row) {
      this.$router.push({
        path: `/strain-library/strain-library-manage/record?id=${row.id}`,
      });
    },
    handleNewStrain() {
      this.$router.push({ path: "/strain-library/production-cell-library/add" });
    },
    handleEdit(row) {
      this.$router.push({
        path: `/strain-library/production-cell-library/add?id=${row.id}`,
      });
    },
    handleDetail(row) {
      this.currentDetail = row;
      this.detailVisible = true;
    },
    handleViewMore() {
      this.dialogVisible = true;
    },
    resetForm() {
      this.form = {
        strainNo: '',
        strainName: '',
        status: ''
      }
      this.searchData()
        strainNo: "",
        strainName: "",
        status: "",
      };
      this.searchData();
    },
    searchData() {
      this.queryForm.pageNum = 1;
      this.fetchData();
    },
    // 获取数据
    fetchData() {
      this.loading = true;
      // 构建请求参数
      const params = {
        page: this.queryForm.pageNum,
        pageNum: this.queryForm.pageNum,
        pageSize: this.queryForm.pageSize,
        ...this.form
        strainCode: this.form.strainNo,
        strainName: this.form.strainName,
        isDraft: this.currentType === "draft" ? 1 : 0,
        status: this.form.status,
        type: 3,
      };
      // 模拟API请求
      setTimeout(() => {
        // 模拟数据,实际项目中应替换为真实API调用
        const mockData = [];
        for (let i = 1; i <= 10; i++) {
          mockData.push({
            id: `${i}`,
            strainNo: `PCLS-2023-${String(i).padStart(3, '0')}`,
            strainName: `枯草芽孢杆菌生产株${i}`,
            source: i % 3 === 0 ? '外部来源' : (i % 2 === 0 ? '工作细胞库' : '主细胞库'),
            preservationMethod: i % 2 === 0 ? '冻干保存' : '超低温冷冻保存',
            storageLocation: `A区-A-${100 + i}-冷藏柜`,
            inventory: 10 + i,
            status: i % 4 === 0 ? '异常' : (i % 3 === 0 ? '缺货' : (i % 2 === 0 ? '已停用' : '正常')),
            preparationDate: `2023-05-${String(i).padStart(2, '0')}`,
            expiryDate: `2024-05-${String(i).padStart(2, '0')}`
      getList(params)
        .then((res) => {
          if (res.code === 200) {
            this.tableData = res.data.records;
            this.total = res.data.total;
          }
        })
        .catch((err) => {
          this.$message.error("数据加载失败");
          });
        }
        this.tableData = mockData;
        this.total = 100; // 模拟总数
        this.loading = false;
      }, 500);
    },
    // 状态标签类型
    getStatusType(status) {
      switch(status) {
        case '正常':
          return 'success';
        case '缺货':
          return 'warning';
        case '异常':
          return 'danger';
        case '已停用':
          return 'info';
        default:
          return 'info';
      }
    },
    handleCurrentChange(page) {
      this.queryForm.pageNum = page
      this.fetchData();
      this.queryForm.pageNum = page;
      this.searchData();
    },
    handleSizeChange(size) {
      this.queryForm.pageSize = size
      this.queryForm.pageNum = 1
      this.fetchData();
      this.queryForm.pageSize = size;
      this.searchData();
    },
    handleTypeChange(type) {
      this.currentType = type;
      this.fetchData();
      this.searchData();
    },
    // 表格多选
    handleSelectionChange(selection) {
      this.selectedRows = selection;
    getStatusType(status) {
      const types = {
        1: "warning",
        2: "warning",
        3: "success",
        4: "success",
      };
      return types[status] || "info";
    },
    // 新增菌种
    handleAdd() {
      this.$router.push('/strain-library/production-cell-library/add');
    getStatusText(status) {
      const texts = {
        1: "已出库",
        2: "出库待确认",
        3: "已入库",
        4: "入库待确认",
      };
      return texts[status] || "未知状态";
    },
    // 查看菌种详情
    handleView(row) {
      this.$router.push(`/strain-library/production-cell-library/record/${row.id}`);
    },
    // 编辑菌种
    handleEdit(row) {
      this.$router.push(`/strain-library/production-cell-library/edit/${row.id}`);
    },
    // 删除菌种
    handleDelete(row) {
      this.deleteRow = row;
      this.deleteDialogVisible = true;
    },
    // 确认删除
    confirmDelete() {
      if (!this.deleteRow) return;
      // 模拟API请求
      this.$message({
        type: 'success',
        message: `删除成功: ${this.deleteRow.strainNo} - ${this.deleteRow.strainName}`
      });
      // 移除本地数据
      const index = this.tableData.findIndex(item => item.id === this.deleteRow.id);
      if (index !== -1) {
        this.tableData.splice(index, 1);
      }
      this.deleteDialogVisible = false;
      this.deleteRow = null;
    },
    // 导出
    handleExport() {
      this.$message.info('生产细胞库菌种导出功能开发中');
      // 实际项目中应实现导出逻辑
    }
  }
}
};
</script>
<style scoped lang="less">
@@ -320,7 +333,7 @@
    .view-more {
      position: absolute;
      right: 0;
      color: #049C9A;
      color: #049c9a;
    }
  }
@@ -343,8 +356,50 @@
  }
}
.search-btn-box {
  margin-left: auto;
.search-form {
  margin-bottom: 20px;
  background: #f5f7fa;
  padding: 24px;
  border-radius: 8px;
  .el-form-item {
    margin-right: 20px;
    margin-bottom: 0;
  }
  .el-button {
    margin-left: 10px;
  }
}
.action-buttons {
  margin-bottom: 20px;
  .el-button {
    margin-right: 10px;
  }
}
.tab-container {
  display: flex;
  margin-bottom: 20px;
  .tab {
    padding: 10px 30px;
    border: 1px solid #dcdfe6;
    border-bottom: none;
    border-radius: 8px 8px 0 0;
    cursor: pointer;
    margin-right: 10px;
    background: #f5f7fa;
    &.active {
      background: #fff;
      border-color: #049c9a;
      color: #049c9a;
      font-weight: bold;
    }
  }
}
.flex {
@@ -371,7 +426,6 @@
    line-height: 50px;
    width: 166px;
    text-align: center;
  }
  .drafts {
@@ -394,26 +448,13 @@
    background: #ffffff;
    border-radius: 8px 8px 0px 0px;
    border: 1px solid #049c9a;
  }
}
.delete-dialog-content {
  display: flex;
  align-items: center;
  padding: 20px 0;
  .warning-icon {
    font-size: 24px;
    color: #E6A23C;
    margin-right: 10px;
  }
}
.view-all-dialog {
  :deep(.el-dialog__header) {
    padding: 20px;
    border-bottom: 1px solid #EBEEF5;
    border-bottom: 1px solid #ebeef5;
    margin-right: 0;
    
    .el-dialog__title {
@@ -444,9 +485,5 @@
      }
    }
  }
}
.delete-btn {
  color: #F56C6C;
}
</style>
culture/src/views/strain-library/production-cell-library/record.vue
@@ -1,538 +1,438 @@
<template>
  <div class="production-cell-record">
    <!-- 页面头部 -->
    <div class="page-header">
      <div class="header-left">
        <el-button icon="el-icon-arrow-left" @click="$router.go(-1)">返回</el-button>
        <h2>生产细胞库菌种详情</h2>
      </div>
      <div class="header-actions">
        <el-button type="primary" plain icon="el-icon-edit" @click="handleEdit">编辑</el-button>
        <el-button type="primary" plain icon="el-icon-printer" @click="handlePrint">打印</el-button>
      </div>
    </div>
    <!-- 菌种信息卡片 -->
    <el-card class="strain-card" v-loading="loading">
      <div slot="header" class="card-header">
        <span class="card-title">菌种信息</span>
        <div class="status-tag">
          <el-tag :type="getStatusType(strainData.status)">{{ strainData.status }}</el-tag>
        </div>
      </div>
      <div class="strain-info">
        <div class="info-item">
  <div class="record-page">
    <!-- 基本信息展示区域 -->
    <el-card class="header-box">
      <div class="header-content">
        <!-- 第一行 -->
        <div class="info-row">
          <div class="info-item left-column">
          <span class="label">菌种编号:</span>
          <span class="value">{{ strainData.strainNo }}</span>
            <span class="value">{{ detail.strainCode }}</span>
        </div>
        <div class="info-item">
          <span class="label">菌种名称:</span>
          <span class="value">{{ strainData.strainName }}</span>
          <div class="info-item flex-column">
            <span class="label">鉴定方法:</span>
            <span class="value">{{ detail.appraisalMethod }}</span>
        </div>
        <div class="info-item">
          <span class="label">菌种来源:</span>
          <span class="value">{{ strainData.source }}</span>
          <div class="info-item flex-column">
            <span class="label">保藏位置:</span>
            <span class="value">{{ detail.saveLocation }}</span>
        </div>
        <div class="info-item">
          <span class="label">生产批次:</span>
          <span class="value">{{ strainData.batchNo }}</span>
        </div>
        <div class="info-item">
          <span class="label">保存方法:</span>
          <span class="value">{{ strainData.preservationMethod }}</span>
        </div>
        <div class="info-item">
          <span class="label">保存位置:</span>
          <span class="value">{{ strainData.storageLocation }}</span>
        </div>
        <div class="info-item">
          <span class="label">当前库存:</span>
          <span class="value">{{ strainData.inventory }} 份</span>
        </div>
        <div class="info-item">
          <span class="label">状态:</span>
          <span class="value">{{ strainData.status }}</span>
        </div>
        <div class="info-item">
          <span class="label">更新时间:</span>
          <span class="value">{{ strainData.updateTime }}</span>
        </div>
        <div class="info-item">
          <span class="label">制备日期:</span>
          <span class="value">{{ strainData.preparationDate }}</span>
        </div>
        <div class="info-item">
          <span class="label">有效期至:</span>
          <span class="value">{{ strainData.expiryDate }}</span>
        </div>
        <div class="info-item full-width">
          <span class="label">特征描述:</span>
          <div class="value description">{{ strainData.description }}</div>
        </div>
        
        <div class="info-item full-width" v-if="strainData.certificateUrl">
          <span class="label">质量证书:</span>
          <div class="value">
            <el-button type="text" @click="handleViewCertificate">查看证书</el-button>
            <el-button type="text" @click="handleDownloadCertificate">下载证书</el-button>
        <!-- 第二行 -->
        <div class="info-row">
          <div class="info-item left-column">
            <span class="label">菌种名称:</span>
            <span class="value">{{ detail.strainName }}</span>
          </div>
          <div class="info-item flex-column full-width">
            <span class="label">特性描述:</span>
            <span class="value">{{ detail.features }}</span>
          </div>
        </div>
        <!-- 第三行 -->
        <div class="info-row">
          <div class="info-item left-column">
            <span class="label">菌种来源:</span>
            <span class="value">{{ detail.strainSource }}</span>
          </div>
          <div class="info-item flex-column">
            <span class="label">菌种保存方法:</span>
            <span class="value">{{ detail.saveMethod }}</span>
          </div>
        </div>
      </div>
    </el-card>
    <!-- 使用记录 -->
    <el-card class="record-card" v-loading="loadingRecords">
      <div slot="header" class="card-header">
        <span class="card-title">使用记录</span>
        <el-button type="text" @click="handleAddUsage">新增使用记录</el-button>
      </div>
      <el-table
        :data="usageRecords"
        style="width: 100%"
        :empty-text="usageRecords.length ? '' : '暂无使用记录'"
    <!-- 出入库记录表格 -->
    <TableCustom
      :queryForm="queryForm"
      :tableData="recordList"
      :total="total"
      @currentChange="handlePageChange"
      >
        <el-table-column prop="date" label="使用日期" width="120"></el-table-column>
        <el-table-column prop="amount" label="使用数量" width="100"></el-table-column>
        <el-table-column prop="operator" label="操作人" width="120"></el-table-column>
        <el-table-column prop="project" label="项目名称"></el-table-column>
        <el-table-column prop="batchNo" label="生产批次" width="150"></el-table-column>
        <el-table-column prop="purpose" label="使用目的"></el-table-column>
        <el-table-column label="操作" width="120">
          <template slot-scope="scope">
            <el-button type="text" size="small" @click="handleViewUsageDetail(scope.row)">查看</el-button>
            <el-button type="text" size="small" @click="handleEditUsage(scope.row)">编辑</el-button>
      <template #setting>
        <div class="tableTitle">
          <div class="flex a-center">
            <div
              class="title"
              :class="{ active: currentType === 'table' }"
              @click="handleTypeChange('table')"
            >
              原始细胞保藏出/入库登记表
            </div>
            <div
              class="drafts"
              :class="{ active: currentType === 'timeline' }"
              @click="handleTypeChange('timeline')"
            >
              原始细胞保藏出/入库时间轴
            </div>
          </div>
          <div class="flex a-center">
            <el-button
              v-if="roleType == 4"
              @click="handleAddRecord"
              class="el-icon-plus"
              type="primary"
              >新增出入库记录</el-button
            >
          </div>
        </div>
      </template>
      <template #table v-if="currentType === 'table'">
        <el-table-column prop="type" label="出库/入库">
          <template #default="{ row }">
            <span>
              {{ row.type === 1 ? "出库" : "入库" }}
            </span>
          </template>
        </el-table-column>
      </el-table>
      <div class="pagination-container" v-if="usageRecords.length">
        <el-pagination
          background
          layout="prev, pager, next"
          :total="usageTotalCount"
          :current-page.sync="usageCurrentPage"
          :page-size="usagePageSize"
          @current-change="handleUsagePageChange">
        </el-pagination>
      </div>
    </el-card>
    <!-- 测试记录 -->
    <el-card class="record-card" v-loading="loadingTests">
      <div slot="header" class="card-header">
        <span class="card-title">测试记录</span>
        <el-button type="text" @click="handleAddTest">新增测试记录</el-button>
      </div>
      <el-table
        :data="testRecords"
        style="width: 100%"
        :empty-text="testRecords.length ? '' : '暂无测试记录'"
        <el-table-column prop="boundTime" label="操作时间" />
        <el-table-column prop="handleSignature" label="操作人签字">
          <template #default="{ row }">
            <el-image
              v-if="row.handleSignature"
              style="width: 100px; height: 100px"
              :src="row.handleSignature"
              :preview-src-list="[row.handleSignature]"
      >
        <el-table-column prop="date" label="测试日期" width="120"></el-table-column>
        <el-table-column prop="type" label="测试类型" width="150"></el-table-column>
        <el-table-column prop="operator" label="操作人" width="120"></el-table-column>
        <el-table-column prop="result" label="测试结果">
          <template slot-scope="scope">
            <el-tag :type="scope.row.result === '合格' ? 'success' : scope.row.result === '不合格' ? 'danger' : 'info'">
              {{ scope.row.result }}
            </el-image>
          </template>
        </el-table-column>
        <el-table-column prop="preserveSignature" label="菌种保藏人签字">
          <template #default="{ row }">
            <el-image
              v-if="row.preserveSignature"
              style="width: 100px; height: 100px"
              :src="row.preserveSignature"
              :preview-src-list="[row.preserveSignature]"
            >
            </el-image>
          </template>
        </el-table-column>
        <el-table-column prop="status" label="状态">
          <template #default="{ row }">
            <el-tag :type="row.preserveSignature ? 'success' : 'warning'">
              {{ row.preserveSignature ? "已确认" : "待确认" }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="remark" label="备注"></el-table-column>
        <el-table-column label="操作" width="120">
          <template slot-scope="scope">
            <el-button type="text" size="small" @click="handleViewTestDetail(scope.row)">查看</el-button>
            <el-button type="text" size="small" @click="handleEditTest(scope.row)">编辑</el-button>
        <el-table-column label="操作" width="180">
          <template #default="{ row }">
            <el-button
              v-if="!row.preserveSignature && roleType == 3"
              type="text"
              class="operation-btn"
              @click="handleConfirm(row)"
              >确认</el-button
            >
            <el-button
              type="text"
              class="operation-btn"
              @click="handleView(row)"
              >详情</el-button
            >
            <el-button v-if="roleType == 1" type="text" @click="handleDelete(row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      </template>
      <template #tableCustom v-if="currentType === 'timeline'">
        <record-timeline :list="timelineList" />
      </template>
    </TableCustom>
      
      <div class="pagination-container" v-if="testRecords.length">
        <el-pagination
          background
          layout="prev, pager, next"
          :total="testTotalCount"
          :current-page.sync="testCurrentPage"
          :page-size="testPageSize"
          @current-change="handleTestPageChange">
        </el-pagination>
      </div>
    </el-card>
    <!-- 证书预览对话框 -->
    <el-dialog
      title="证书预览"
      :visible.sync="certificateDialogVisible"
      width="70%">
      <div v-if="strainData.certificateUrl" class="certificate-preview">
        <iframe v-if="strainData.certificateUrl.endsWith('.pdf')" :src="strainData.certificateUrl" width="100%" height="500"></iframe>
        <img v-else :src="strainData.certificateUrl" style="max-width: 100%; max-height: 500px;" />
      </div>
    </el-dialog>
    <!-- 详情弹窗 -->
    <record-detail-dialog
      :visible.sync="dialogVisible"
      :record-data="currentRecord"
      @close="handleDialogClose"
      @confirm="handleOutbound"
      :type="dialogType"
    />
    <!-- 新增出入库记录弹窗 -->
    <add-record-dialog
      :visible.sync="addDialogVisible"
      @confirm="handleAddRecordConfirm"
    />
  </div>
</template>
<script>
import RecordDetailDialog from "../strain-library-manage/components/RecordDetailDialog.vue";
import AddRecordDialog from "../strain-library-manage/components/AddRecordDialog.vue";
import RecordTimeline from "../strain-library-manage/components/RecordTimeline.vue";
import {
  timeList,
  getDetail,
  addWarehousing,
  getDetailById,
  confirmWarehousing,
} from "./service";
export default {
  name: 'ProductionCellLibraryRecord',
  name: "StrainRecord",
  components: {
    RecordDetailDialog,
    AddRecordDialog,
    RecordTimeline,
  },
  data() {
    return {
      loading: false,
      loadingRecords: false,
      loadingTests: false,
      strainId: '',
      strainData: {
        id: '',
        strainNo: '',
        strainName: '',
        source: '',
        batchNo: '',
        preservationMethod: '',
        storageLocation: '',
        inventory: 0,
        status: '',
        description: '',
        preparationDate: '',
        expiryDate: '',
        updateTime: '',
        certificateUrl: ''
      currentType: "table",
      detail: {},
      currentPage: 1,
      pageSize: 10,
      total: 0,
      queryForm: {
        pageSize: 10,
        pageNum: 1,
      },
      certificateDialogVisible: false,
      recordList: [],
      timelineList: [],
      dialogVisible: false,
      currentRecord: {},
      addDialogVisible: false,
      dialogType: "detail",
      roleType: "",
    };
  },
  activated() {
    this.roleType = JSON.parse(sessionStorage.getItem("userInfo")).roleType;
      
      // 使用记录分页数据
      usageRecords: [],
      usageCurrentPage: 1,
      usagePageSize: 10,
      usageTotalCount: 0,
      // 测试记录分页数据
      testRecords: [],
      testCurrentPage: 1,
      testPageSize: 10,
      testTotalCount: 0
    // 获取路由参数中的菌种信息
    const strainId = this.$route.query.id;
    this.queryForm.id = strainId;
    if (strainId) {
      this.getStrainDetail(strainId);
      this.getRecordList();
    }
  },
  created() {
    this.strainId = this.$route.params.id;
    this.fetchStrainData();
    this.fetchUsageRecords();
    this.fetchTestRecords();
  },
  methods: {
    // 获取菌种详情
    fetchStrainData() {
      this.loading = true;
      // 模拟API请求
      setTimeout(() => {
        // 模拟数据,实际项目中应替换为真实API调用
        this.strainData = {
          id: this.strainId,
          strainNo: 'PCLS-2023-001',
          strainName: '枯草芽孢杆菌生产株',
          source: '主细胞库',
          batchNo: 'P20230515-001',
          preservationMethod: '冻干保存',
          storageLocation: 'A区-A-102-冷藏柜',
          inventory: 12,
          status: '正常',
          description: '本菌种为工业生产级别枯草芽孢杆菌生产株,由主细胞库转入,经过严格筛选和稳定性测试。该菌株具有高产蛋白酶能力,发酵条件适应性强,适合大规模工业化生产。产品稳定性好,批次间差异小,可用于洗涤用酶制剂、食品加工酶制剂等多种产品的生产。',
          preparationDate: '2023-05-10',
          expiryDate: '2024-05-10',
          updateTime: '2023-05-15 14:30:22',
          certificateUrl: '/api/strain-library/certificates/sample.pdf'
    handleDelete(row) {
      this.$confirm("确定删除该数据吗?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      }).then(() => {
        deleteWarehousing({ id: row.id }).then((res) => {
          this.$message.success("删除成功");
          this.getRecordList();
        });
      });
    },
    getStrainDetail(id) {
      // 这里应该调用接口获取菌种详情
      getDetail({ id }).then((res) => {
        this.detail = res;
      });
    },
    getRecordList() {
      // 这里应该调用接口获取出入库记录
      timeList(this.queryForm).then((res) => {
        this.timelineList = res.data;
      });
      getDetailById({ id: this.$route.query.id }).then((res) => {
        this.recordList = res.warehousingList.records;
        this.total = res.warehousingList.total;
      });
    },
    handleView(row) {
      this.dialogType = "detail";
      this.currentRecord = row;
      this.dialogVisible = true;
    },
    handleConfirm(row) {
      this.dialogType = "confirm";
      this.currentRecord = row;
      this.dialogVisible = true;
    },
    handlePageChange(page) {
      this.queryForm.pageNum = page;
      // 这里应该调用接口获取对应页码的数据
    },
    handleTypeChange(type) {
      this.currentType = type;
    },
    handleAddRecord() {
      this.addDialogVisible = true;
    },
    handleDialogClose() {
      this.currentRecord = {};
      this.dialogVisible = false;
    },
    handleOutbound(data) {
      // 这里调用出库API
      confirmWarehousing({
        id: this.currentRecord.id,
        preserveSignature: data.preserveSignature,
      }).then((res) => {
        console.log(res);
        if (res.code == 200) {
          this.$message.success("操作成功");
          this.dialogVisible = false;
          // 刷新列表
          this.getRecordList();
        } else {
          this.$message.error(res.msg);
        }
      });
    },
    handleAddRecordConfirm(record) {
      addWarehousing({ ...record, trainLibraryId: this.$route.query.id }).then(
        (res) => {
          this.$message.success("操作成功");
          this.getRecordList();
        }
      );
    },
    goBack() {
      this.$router.go(-1);
    },
  },
        };
        this.loading = false;
      }, 500);
    },
    // 获取使用记录
    fetchUsageRecords() {
      this.loadingRecords = true;
      // 模拟API请求
      setTimeout(() => {
        // 模拟数据,实际项目中应替换为真实API调用
        this.usageRecords = [
          {
            id: '1',
            date: '2023-06-05',
            amount: 2,
            operator: '张工',
            project: '酶制剂研发项目',
            batchNo: 'E20230605-001',
            purpose: '小试生产'
          },
          {
            id: '2',
            date: '2023-07-12',
            amount: 3,
            operator: '李工',
            project: '蛋白酶产品项目',
            batchNo: 'E20230712-002',
            purpose: '中试生产'
          },
          {
            id: '3',
            date: '2023-08-18',
            amount: 5,
            operator: '王工',
            project: '工业酶制剂生产',
            batchNo: 'E20230818-003',
            purpose: '规模化生产'
          }
        ];
        this.usageTotalCount = 3;
        this.loadingRecords = false;
      }, 600);
    },
    // 获取测试记录
    fetchTestRecords() {
      this.loadingTests = true;
      // 模拟API请求
      setTimeout(() => {
        // 模拟数据,实际项目中应替换为真实API调用
        this.testRecords = [
          {
            id: '1',
            date: '2023-05-15',
            type: '活力测定',
            operator: '刘工',
            result: '合格',
            remark: '酶活性达标,符合生产要求'
          },
          {
            id: '2',
            date: '2023-05-15',
            type: '纯度检测',
            operator: '张工',
            result: '合格',
            remark: '纯度>98%,无杂菌污染'
          },
          {
            id: '3',
            date: '2023-05-16',
            type: '稳定性测试',
            operator: '李工',
            result: '合格',
            remark: '常温保存7天活力下降小于5%'
          }
        ];
        this.testTotalCount = 3;
        this.loadingTests = false;
      }, 700);
    },
    // 状态标签类型
    getStatusType(status) {
      switch(status) {
        case '正常':
          return 'success';
        case '缺货':
          return 'warning';
        case '异常':
          return 'danger';
        case '已停用':
          return 'info';
        default:
          return 'info';
      }
    },
    // 编辑菌种
    handleEdit() {
      this.$router.push(`/strain-library/production-cell-library/edit/${this.strainId}`);
    },
    // 打印菌种信息
    handlePrint() {
      window.print();
    },
    // 查看证书
    handleViewCertificate() {
      this.certificateDialogVisible = true;
    },
    // 下载证书
    handleDownloadCertificate() {
      // 实际项目中应处理文件下载逻辑
      window.open(this.strainData.certificateUrl, '_blank');
    },
    // 新增使用记录
    handleAddUsage() {
      this.$message.info('功能开发中:新增使用记录');
      // 实际项目中应跳转到新增使用记录页面或打开对话框
    },
    // 查看使用记录详情
    handleViewUsageDetail(row) {
      this.$message.info(`查看使用记录: ${row.id}`);
      // 实际项目中应跳转到使用记录详情页面或打开对话框
    },
    // 编辑使用记录
    handleEditUsage(row) {
      this.$message.info(`编辑使用记录: ${row.id}`);
      // 实际项目中应跳转到编辑使用记录页面或打开对话框
    },
    // 使用记录分页切换
    handleUsagePageChange(page) {
      this.usageCurrentPage = page;
      this.fetchUsageRecords();
    },
    // 新增测试记录
    handleAddTest() {
      this.$message.info('功能开发中:新增测试记录');
      // 实际项目中应跳转到新增测试记录页面或打开对话框
    },
    // 查看测试记录详情
    handleViewTestDetail(row) {
      this.$message.info(`查看测试记录: ${row.id}`);
      // 实际项目中应跳转到测试记录详情页面或打开对话框
    },
    // 编辑测试记录
    handleEditTest(row) {
      this.$message.info(`编辑测试记录: ${row.id}`);
      // 实际项目中应跳转到编辑测试记录页面或打开对话框
    },
    // 测试记录分页切换
    handleTestPageChange(page) {
      this.testCurrentPage = page;
      this.fetchTestRecords();
    }
  }
}
</script>
<style scoped lang="less">
.production-cell-record {
  padding: 20px;
<style lang="less" scoped>
.record-page {
  min-height: 100vh;
  .page-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 24px;
  .header-box {
    margin-bottom: 20px;
    border-radius: 16px;
    background: rgba(255, 255, 255, 0.8);
    height: 130px;
    overflow: hidden;
    
    .header-left {
      display: flex;
      align-items: center;
    .header-content {
      color: rgba(0, 0, 0, 0.88);
      font-size: 14px;
      line-height: 1.5;
      
      h2 {
        margin: 0 0 0 12px;
        font-size: 22px;
        font-weight: 500;
      }
    }
    .header-actions {
      display: flex;
      gap: 12px;
    }
  }
  .strain-card {
    margin-bottom: 24px;
    .card-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      .card-title {
        font-size: 16px;
        font-weight: 500;
      }
    }
    .strain-info {
      .info-row {
      display: flex;
      flex-wrap: wrap;
        margin-bottom: 8px;
        &:last-child {
          margin-bottom: 0;
        }
      
      .info-item {
        width: 33.33%;
        margin-bottom: 16px;
          display: flex;
          align-items: flex-start;
          margin-right: 24px;
          margin-bottom: 6px;
          &.left-column {
            width: 33%;
            min-width: 200px;
          }
          &.flex-column {
            flex: 1;
            min-width: 150px;
          }
        
        &.full-width {
          width: 100%;
            flex: 1;
            min-width: 300px;
        }
        
        .label {
          font-weight: 500;
          color: #606266;
            margin-right: 8px;
            white-space: nowrap;
        }
        
        .value {
            flex: 1;
          color: #303133;
          &.description {
            white-space: pre-line;
            line-height: 1.6;
            padding: 8px 0;
            word-break: break-all;
            display: -webkit-box;
            -webkit-line-clamp: 1;
            -webkit-box-orient: vertical;
            overflow: hidden;
            text-overflow: ellipsis;
          }
        }
      }
    }
  }
  
  .record-card {
    margin-bottom: 24px;
    .card-header {
  .flex {
      display: flex;
    align-items: center;
  }
  .tableTitle {
    display: flex;
    padding-bottom: 20px;
      justify-content: space-between;
      align-items: center;
      
      .card-title {
    .title {
      background: #fafafc;
      border-radius: 8px 8px 0px 0px;
      border: 1px solid #dcdfe6;
      font-weight: bold;
      font-size: 18px;
      color: #606266;
      cursor: pointer;
      height: 50px;
      line-height: 50px;
      width: 280px;
      text-align: center;
    }
    .drafts {
      background: #fafafc;
      border-radius: 8px 8px 0px 0px;
      border: 1px solid #dcdfe6;
      font-weight: 400;
      font-size: 18px;
      color: #606266;
      margin-left: 16px;
      cursor: pointer;
      height: 50px;
      line-height: 50px;
      width: 280px;
      text-align: center;
    }
    .active {
      color: #049c9a;
      background: #ffffff;
      border-radius: 8px 8px 0px 0px;
      border: 1px solid #049c9a;
    }
  }
  .timeline-container {
    padding: 20px;
    background: rgba(255, 255, 255, 0.8);
    .timeline-card {
      margin-bottom: 10px;
      background: rgba(255, 255, 255, 0.8);
      h4 {
        margin: 0 0 10px;
        font-size: 16px;
        font-weight: 500;
        font-weight: bold;
      }
      p {
        margin: 5px 0;
        font-size: 14px;
      }
      }
    }
    
    .pagination-container {
      display: flex;
      justify-content: flex-end;
      margin-top: 20px;
    }
  }
  .certificate-preview {
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 500px;
    background-color: #f5f7fa;
  }
  @media print {
    .page-header, .header-actions, .record-card {
      display: none;
    }
    .strain-card {
      border: none;
      box-shadow: none;
      .card-header {
        background-color: #fff !important;
        border-bottom: 1px solid #ddd;
      }
    }
  .operation-btn {
    margin-right: 12px;
  }
}
</style> 
culture/src/views/strain-library/production-cell-library/service.js
New file
@@ -0,0 +1,56 @@
import axios from '@/utils/request';
// 列表
export const getList = (data) => {
  return axios.post('/api/t-train-library/pageList', { ...data })
}
// 新增
export const add = (data) => {
  return axios.post('/api/t-train-library/add', { ...data })
}
// 编辑
export const edit = (data) => {
  return axios.post('/api/t-train-library/update', { ...data })
}
// 查看详情
export const getDetail = (params) => {
  return axios.get('/open/t-train-library/getDetailEditById', { params })
}
// 批量新增
export const addBatch = (data) => {
  return axios.post('/api/t-train-library/addBatch', data)
}
// 查看菌种库详情
export const getDetailById = (data) => {
  return axios.post('/open/t-train-library/getDetailById', { ...data })
}
// 获取菌种库出入库时间轴列表
export const timeList = (data) => {
  return axios.post('/api/t-train-library/timeList?id='+data.id, { ...data })
}
// 新增菌种库出入记录
export const addWarehousing = (data) => {
  return axios.post('/open/t-train-library/addWarehousing', { ...data })
}
// 确认出入库
export const confirmWarehousing = (data) => {
  return axios.post('/api/t-train-library/confirm', { ...data })
}
// 删除菌种库
export const deleteStrainLibrary = (params) => {
  return axios.delete('/open/t-train-library/deleteById', { params })
}
// 删除菌种库出入库记录
export const deleteWarehousing = (params) => {
  return axios.delete('/open/t-train-library/deleteWarehousingById', { params })
}
culture/src/views/strain-library/strain-library-manage/add.vue
@@ -1,92 +1,60 @@
<template>
    <Card>
        <!-- <div class="header-title">
            <div class="header-title-left">
                <img src="@/assets/public/headercard.png" />
                <div>新增原始细胞</div>
            </div>
        </div> -->
        <el-form
            :model="form"
            :rules="rules"
            ref="strainForm"
            label-position="top"
            class="strain-form"
        >
        <el-form :model="form" :rules="rules" ref="strainForm" label-position="top" class="strain-form">
            <div class="form-row three-columns">
                <el-form-item label="菌种编号" prop="strainNo" required>
                    <el-input v-model="form.strainNo" placeholder="请输入"></el-input>
                <el-form-item label="菌种编号" prop="strainCode">
                    <el-input v-model="form.strainCode" placeholder="请输入"></el-input>
                </el-form-item>
                <el-form-item label="菌种名称" prop="strainName" required>
                <el-form-item label="菌种名称" prop="strainName">
                    <el-input v-model="form.strainName" placeholder="请输入"></el-input>
                </el-form-item>
                <el-form-item label="菌种来源" prop="source" required>
                    <el-input v-model="form.source" placeholder="请输入"></el-input>
                <el-form-item label="菌种来源" prop="strainSource">
                    <el-input v-model="form.strainSource" placeholder="请输入"></el-input>
                </el-form-item>
            </div>
            <div class="form-row">
                <el-form-item label="鉴定方法" prop="identificationMethod" required>
                    <el-input v-model="form.identificationMethod" placeholder="请输入"></el-input>
                <el-form-item label="鉴定方法" prop="appraisalMethod">
                    <el-input v-model="form.appraisalMethod" placeholder="请输入"></el-input>
                </el-form-item>
            </div>
            <div class="form-row">
                <el-form-item label="特征描述" prop="characteristics" required class="full-width">
                    <el-input
                        type="textarea"
                        v-model="form.characteristics"
                        :rows="4"
                        placeholder="请输入"
                    ></el-input>
                <el-form-item label="特征描述" prop="features" class="full-width">
                    <el-input type="textarea" v-model="form.features" :rows="4" placeholder="请输入"></el-input>
                </el-form-item>
            </div>
            <div class="form-row three-columns">
                <el-form-item label="保存位置" prop="storageLocation" required>
                    <el-input v-model="form.storageLocation" placeholder="请输入"></el-input>
                <el-form-item label="保藏位置" prop="saveLocation">
                    <el-input v-model="form.saveLocation" placeholder="请输入"></el-input>
                </el-form-item>
                <el-form-item label="菌种保存方法" prop="preservationMethod" required>
                    <el-input v-model="form.preservationMethod" placeholder="请输入"></el-input>
                <el-form-item label="菌种保存方法" prop="saveMethod">
                    <el-input v-model="form.saveMethod" placeholder="请输入"></el-input>
                </el-form-item>
                <div class="form-item-placeholder"></div>
            </div>
            <div class="form-row">
                <el-form-item label="备注" prop="remarks" class="full-width">
                    <el-input
                        type="textarea"
                        v-model="form.remarks"
                        :rows="4"
                        placeholder="请输入"
                    ></el-input>
                    <el-input type="textarea" v-model="form.remark" :rows="4" placeholder="请输入"></el-input>
                </el-form-item>
            </div>
            <div class="end-btn" style="margin-top: 38px">
                <el-button type="primary" @click="handleSubmit">提交</el-button>
                <el-button type="primary" @click="handleBatchAdd">批量新增</el-button>
                <el-button @click="handleDraft">存草稿</el-button>
                <el-button type="primary" @click="handleSubmit(0)">提交</el-button>
                <el-button v-if="!$route.query.id" type="primary" @click="handleBatchAdd">批量新增</el-button>
                <el-button @click="handleSubmit(1)">存草稿</el-button>
            </div>
        </el-form>
        <!-- 批量新增弹窗 -->
        <el-dialog
            title="批量新增"
            :visible.sync="batchAddDialogVisible"
            width="520px"
            :close-on-click-modal="false"
            :close-on-press-escape="false"
            custom-class="batch-add-dialog"
        >
        <el-dialog title="批量新增" :visible.sync="batchAddDialogVisible" width="520px" :close-on-click-modal="false"
            :close-on-press-escape="false" custom-class="batch-add-dialog">
            <div class="dialog-content">
                <el-form :model="batchForm" ref="batchFormRef" label-position="top" class="batch-form">
                    <el-form-item
                        label="批量新增数量"
                        prop="count"
                        required
                        :rules="[{ required: true, message: '请输入批量新增数量', trigger: 'blur' }]"
                    >
                    <el-form-item label="批量新增数量" prop="count"
                        :rules="[{ required: true, message: '请输入批量新增数量', trigger: 'blur' }]">
                        <el-input v-model.number="batchForm.count" placeholder="请输入" />
                    </el-form-item>
                </el-form>
@@ -104,18 +72,16 @@
        </el-dialog>
        <!-- 签字确认组件 -->
        <SignatureCanvas
            :visible.sync="signatureVisible"
            @confirm="handleSignatureConfirm"
        />
        <SignatureCanvas :visible.sync="signatureVisible" @confirm="handleSignatureConfirm" />
    </Card>
</template>
<script>
import SignatureCanvas from '@/components/SignatureCanvas.vue'
import { add, edit, getDetail, addBatch } from './service'
export default {
    name: 'AddStrain',
    name: 'StrainLibraryManageAdd',
    components: {
        SignatureCanvas
    },
@@ -128,60 +94,119 @@
                count: ''
            },
            form: {
                strainNo: '',
                strainCode: '',
                strainName: '',
                source: '',
                identificationMethod: '',
                appraisalMethod: '',
                characteristics: '',
                storageLocation: '',
                preservationMethod: '',
                remarks: ''
                remark: ''
            },
            rules: {
                strainNo: [{ required: true, message: '请输入菌种编号', trigger: 'blur' }],
                strainCode: [{
                    validator: (rule, value, callback) => {
                        if (this.currentAction === 'submit' && !value) {
                            callback(new Error('请输入菌种编号'));
                        } else {
                            callback();
                        }
                    },
                    trigger: 'change'
                }],
                strainName: [{ required: true, message: '请输入菌种名称', trigger: 'blur' }],
                source: [{ required: true, message: '请输入菌种来源', trigger: 'blur' }],
                identificationMethod: [{ required: true, message: '请输入鉴定方法', trigger: 'blur' }],
                characteristics: [{ required: true, message: '请输入特征描述', trigger: 'blur' }],
                storageLocation: [{ required: true, message: '请输入保存位置', trigger: 'blur' }],
                preservationMethod: [{ required: true, message: '请输入菌种保存方法', trigger: 'blur' }]
                strainSource: [{ required: true, message: '请输入菌种来源', trigger: 'blur' }],
                appraisalMethod: [{ required: true, message: '请输入鉴定方法', trigger: 'blur' }],
                features: [{ required: true, message: '请输入特征描述', trigger: 'blur' }],
                saveLocation: [{ required: true, message: '请输入保存位置', trigger: 'blur' }],
                saveMethod: [{ required: true, message: '请输入菌种保存方法', trigger: 'blur' }]
            }
        }
    },
    activated() {
        if (this.$route.query.id) {
            getDetail({ id: this.$route.query.id }).then(res => {
                this.form = res
            })
        }
    },
    watch: {
        '$route.query.id'() {
            this.form = {
                strainCode: '',
                strainName: '',
                source: '',
                appraisalMethod: '',
                characteristics: '',
                storageLocation: '',
                preservationMethod: '',
                remark: ''
            }
        }
    },
    methods: {
        handleSubmit() {
        handleSubmit(isDraft) {
            this.currentAction = 'submit'
            this.$refs.strainForm.validate((valid) => {
                if (valid) {
                    this.currentAction = 'submit'
                    this.form.isDraft = isDraft
                    if (isDraft == 1) {
                        //存草稿
                        this.handleSignatureConfirm('')
                    } else {
                    this.signatureVisible = true
                    }
                }
            })
        },
        handleBatchAdd() {
            this.currentAction = 'batchAdd'
            this.$refs.strainForm.validate((valid) => {
                if (valid) {
            this.batchAddDialogVisible = true
                }
            })
        },
        handleConfirmBatchAdd() {
            this.$refs.batchFormRef.validate((valid) => {
                if (valid) {
                    this.currentAction = 'batchAdd'
                    this.batchAddDialogVisible = false
                    this.signatureVisible = true
                }
            })
        },
        handleDraft() {
            // 实现存草稿逻辑
            console.log('save draft', this.form)
        },
        handleSignatureConfirm(signatureImage) {
            this.signatureVisible = false
            this.$router.back()
            if (this.currentAction === 'submit') {
                // 处理提交逻辑
                console.log('submit form with signature:', this.form, signatureImage)
        async handleSignatureConfirm(signatureImage) {
            let requestData = {
                strainCode: this.form.strainCode,
                strainName: this.form.strainName,
                strainSource: this.form.strainSource,
                appraisalMethod: this.form.appraisalMethod,
                features: this.form.features,
                saveLocation: this.form.saveLocation,
                saveMethod: this.form.saveMethod,
                remark: this.form.remark,
                signature: signatureImage,
                type: 1,
            };
            if (this.currentAction === 'batchAdd') {
                requestData.batchCount = this.batchForm.count;
            } else {
                requestData.isDraft = this.form.isDraft
            }
            try {
                if (this.$route.query.id) {
                    requestData.id = this.$route.query.id;
                    await edit(requestData);
            } else if (this.currentAction === 'batchAdd') {
                // 处理批量新增逻辑
                console.log('batch add with signature:', this.batchForm.count, signatureImage)
                    await addBatch(requestData);
                } else {
                    await add(requestData);
                }
                this.signatureVisible = false;
                this.$router.back();
                this.$message.success('操作成功');
            } catch (error) {
                this.$message.error('操作失败');
            }
        }
    }
@@ -219,6 +244,7 @@
        }
    }
}
.end-btn{
    display: flex;
    align-items: center;
@@ -231,6 +257,7 @@
        // background: #409EFF; 
    }
}
.strain-form {
    padding: 0 40px;
@@ -241,7 +268,9 @@
        margin-bottom: 24px;
        &.three-columns {
            .el-form-item, .form-item-placeholder {
            .el-form-item,
            .form-item-placeholder {
                flex: 1;
                min-width: 280px;
                
@@ -334,6 +363,7 @@
            color: #606266;
            font-weight: normal;
            padding-bottom: 8px;
            &::before {
                color: #F56C6C;
            }
@@ -341,6 +371,7 @@
        :deep(.el-input) {
            width: 100%;
            input {
            width: 100%;
            }
@@ -350,6 +381,7 @@
    .dialog-notice {
        margin-top: 16px;
        text-align: center;
        p {
            margin: 0;
            line-height: 22px;
culture/src/views/strain-library/strain-library-manage/components/AddRecordDialog.vue
@@ -12,12 +12,12 @@
        <el-form-item label="出库/入库" required>
          <div class="type-buttons">
            <el-button
              :type="formData.type === '出库' ? 'primary' : 'default'"
              @click="formData.type = '出库'"
              :type="formData.type === '1' ? 'primary' : 'default'"
              @click="formData.type = '1'"
            >出库</el-button>
            <el-button
              :type="formData.type === '入库' ? 'primary' : 'default'"
              @click="formData.type = '入库'"
              :type="formData.type === '2' ? 'primary' : 'default'"
              @click="formData.type = '2'"
            >入库</el-button>
          </div>
        </el-form-item>
@@ -27,9 +27,9 @@
            <span>操作人签字</span>
            <el-button type="primary" class="sign-btn" @click="showSignature = true">签名</el-button>
          </template>
          <div class="signature-area" :class="{ 'waiting': !formData.operatorSignature }">
            <template v-if="formData.operatorSignature">
              <img :src="formData.operatorSignature" alt="操作人签字" />
          <div class="signature-area" :class="{ 'waiting': !formData.handleSignature }">
            <template v-if="formData.handleSignature">
              <img :src="formData.handleSignature" alt="操作人签字" />
            </template>
            <template v-else>
              <span class="waiting-text">等待确认</span>
@@ -60,8 +60,8 @@
  data() {
    return {
      formData: {
        type: '出库',
        operatorSignature: ''
        type: '1',
        handleSignature: ''
      },
      showSignature: false
    }
@@ -72,7 +72,7 @@
      this.$emit('close')
    },
    handleConfirm() {
      if (!this.formData.operatorSignature) {
      if (!this.formData.handleSignature) {
        this.$message.warning('请先签名')
        return
      }
@@ -80,7 +80,7 @@
      this.handleClose()
    },
    handleSignatureConfirm(dataUrl) {
      this.formData.operatorSignature = dataUrl
      this.formData.handleSignature = dataUrl
      this.showSignature = false
    }
  }
culture/src/views/strain-library/strain-library-manage/components/RecordDetailDialog.vue
@@ -1,28 +1,20 @@
<template>
    <el-dialog
        title="出/入库详情"
        :visible.sync="visible"
        width="520px"
        :close-on-click-modal="false"
        custom-class="record-detail-dialog"
        @close="handleClose"
    >
    <el-dialog :title="type == 'detail' ? '出/入库详情' : '确认出入库'" :visible.sync="visible" width="550px"
        :close-on-click-modal="false" custom-class="record-detail-dialog" @close="handleClose" @opened="opened">
        <div class="dialog-content">
            <el-form :model="formData" label-position="top">
                <el-form-item label="出库/入库" required>
                    <div class="type-buttons">
                        <el-button
                            type="primary"
                            @click="handleOutbound"
                        >出库</el-button>
                        <el-button v-if="formData.type == '1'" type="primary">出库</el-button>
                        <el-button v-if="formData.type == '2'" type="primary">入库</el-button>
                    </div>
                </el-form-item>
                <div class="signature-row">
                    <el-form-item label="操作人签字" required class="signature-item">
                        <div class="signature-area" :class="{ 'waiting': !formData.operatorSignature }">
                            <template v-if="formData.operatorSignature">
                                <img :src="formData.operatorSignature" alt="操作人签字" />
                        <div class="signature-area" :class="{ 'waiting': !formData.handleSignature }">
                            <template v-if="formData.handleSignature">
                                <img :src="formData.handleSignature" alt="操作人签字" />
                            </template>
                            <template v-else>
                                <span class="waiting-text">等待确认</span>
@@ -30,13 +22,8 @@
                        </div>
                    </el-form-item>
                    <el-form-item
                        v-if="formData.operatorSignature"
                        label="出库时间"
                        required
                        class="time-item"
                    >
                        <div class="time-value">{{ formData.operateTime }}</div>
                    <el-form-item v-if="formData.handleSignature" label="出库时间" required class="time-item">
                        <div class="time-value">{{ formData.boundTime }}</div>
                    </el-form-item>
                </div>
@@ -44,11 +31,12 @@
                    <el-form-item required class="signature-item">
                        <template #label>
                            <span>保藏人签字</span>
                            <el-button type="primary" class="edit-sign-btn" @click="showSignature = true">修改签名</el-button>
                            <el-button v-if="type != 'detail'" type="primary" class="edit-sign-btn"
                                @click="showSignature = true">修改签名</el-button>
                        </template>
                        <div class="signature-area" :class="{ 'waiting': !formData.reviewerSignature }">
                            <template v-if="formData.reviewerSignature">
                                <img :src="formData.reviewerSignature" alt="保藏人签字" />
                        <div class="signature-area" :class="{ 'waiting': !formData.preserveSignature }">
                            <template v-if="formData.preserveSignature">
                                <img :src="formData.preserveSignature" alt="保藏人签字" />
                            </template>
                            <template v-else>
                                <span class="waiting-text">等待确认</span>
@@ -56,18 +44,18 @@
                        </div>
                    </el-form-item>
                    <el-form-item
                        v-if="formData.reviewerSignature"
                        label="确认时间"
                        required
                        class="time-item"
                    >
                    <el-form-item v-if="formData.preserveSignature && type == 'detail'" label="确认时间" required
                        class="time-item">
                        <div class="time-value">{{ formData.confirmTime }}</div>
                    </el-form-item>
                </div>
            </el-form>
            <div class="confirm-btn" v-if="type != 'detail'" style="text-align: center;margin-top: 20px;">
                <el-button type="primary" style="width: 80px;" @click="handleOutbound">确认</el-button>
        </div>
        <signature-canvas :visible.sync="showSignature" @confirm="handleSignatureConfirm" @cancel="showSignature = false" />
        </div>
        <signature-canvas :visible.sync="showSignature" @confirm="handleSignatureConfirm"
            @cancel="showSignature = false" />
    </el-dialog>
</template>
@@ -84,11 +72,17 @@
        recordData: {
            type: Object,
            default: () => ({})
        },
        type: {
            type: String,
            default: 'detail'
        }
    },
    data() {
        return {
            formData: {},
            formData: {
                type: '1',
            },
            showSignature: false
        }
    },
@@ -101,12 +95,14 @@
        }
    },
    methods: {
        opened() {
            this.formData.type = this.recordData.type
        },
        handleClose() {
            this.$emit('update:visible', false)
            this.$emit('close')
        },
        handleOutbound() {
            if (!this.formData.operatorSignature || !this.formData.reviewerSignature) {
            if (!this.formData.preserveSignature) {
                this.$message.warning('请等待所有签字确认后再进行出库操作')
                return
            }
@@ -114,7 +110,7 @@
            this.handleClose()
        },
        handleSignatureConfirm(dataUrl) {
            this.formData.reviewerSignature = dataUrl
            this.formData.preserveSignature = dataUrl
            this.showSignature = false
            // 可选:this.formData.confirmTime = new Date().toLocaleString()
        }
@@ -156,11 +152,13 @@
    }
    .type-buttons {
        display: flex;
        gap: 12px;
        .el-button {
            width: 80px;
            background: #409EFF;
            border-color: #409EFF;
            color: #FFFFFF;
            &:hover {
                opacity: 0.8;
culture/src/views/strain-library/strain-library-manage/components/RecordTimeline.vue
@@ -1,29 +1,23 @@
<template>
  <div class="record-timeline">
    <el-timeline>
      <el-timeline-item
        v-for="(item, idx) in list"
        :key="idx"
        :type="item.type === '入库' ? 'primary' : 'warning'"
        :color="item.type === '入库' ? '#04A9A7' : '#FF9900'"
        :icon="''"
        :timestamp="''"
      >
      <el-timeline-item v-for="(item, idx) in list" :key="idx" :type="item.type == '2' ? 'primary' : 'warning'"
        :color="item.type == '2' ? '#04A9A7' : '#FF9900'" :icon="''" :timestamp="''">
        <div class="timeline-row">
          <div :class="['left-block', item.type === '入库' ? 'in' : 'out']">
            <div class="type-tag">{{ item.type }}</div>
          <div :class="['left-block', item.type == '2' ? 'in' : 'out']">
            <div class="type-tag">{{ item.type == 2 ? '入库' : '出库' }}</div>
            <div class="info-main">
              <div class="info-title">操作人:{{ item.operator || '--' }}</div>
              <div class="info-time">操作时间:{{ item.operateTime || '--' }}</div>
              <div class="info-title">操作人:{{ item.handleName || '--' }}</div>
              <div class="info-time">操作时间:{{ item.boundTime || '--' }}</div>
            </div>
          </div>
          <div :class="[
            'right-block', 
            item.confirmTime && item.confirmTime !== '--' ? 
              (item.type === '入库' ? 'confirmed-in' : 'confirmed-out') :
              (item.type === '2' ? 'confirmed-in' : 'confirmed-out') :
              'unconfirmed'
          ]">
            <div class="info-title">保藏人:{{ item.reviewer || '--' }}</div>
            <div class="info-title">保藏人:{{ item.preserveName || '--' }}</div>
            <div class="info-time">确认时间:{{ item.confirmTime || '--' }}</div>
          </div>
        </div>
@@ -71,7 +65,8 @@
  width: 12px !important;
  height: 12px !important;
  left: -6px;
  top: 34px !important; /* 微调位置使其看起来完全居中 */
  top: 34px !important;
  /* 微调位置使其看起来完全居中 */
  margin: 0 !important;
  background: #ffffff;
  box-shadow: none !important;
@@ -83,18 +78,23 @@
  border-left: 3px solid #e6e6e6;
  left: -1px;
  top: 0;
  height: 114px; /* 84px + 30px */
  height: 114px;
  /* 84px + 30px */
  z-index: 1;
}
:deep(.el-timeline-item:first-child .el-timeline-item__tail) {
  top: 34px; /* 与节点位置对应 */
  height: 80px; /* 调整为与新节点位置匹配 */
  top: 34px;
  /* 与节点位置对应 */
  height: 80px;
  /* 调整为与新节点位置匹配 */
}
:deep(.el-timeline-item:last-child .el-timeline-item__tail) {
  height: 34px; /* 调整为与新节点位置匹配 */
  display: block !important; /* 确保显示 */
  height: 34px;
  /* 调整为与新节点位置匹配 */
  display: block !important;
  /* 确保显示 */
}
:deep(.el-timeline-item__content) {
@@ -113,18 +113,23 @@
  gap: 14px;
  height: 84px;
  width: 100%;
  flex-wrap: wrap; /* 允许在小屏幕上换行 */
  flex-wrap: wrap;
  /* 允许在小屏幕上换行 */
}
.left-block, .right-block {
.left-block,
.right-block {
  width: 330px;
  border-radius: 10px;
  padding: 0;
  background: #f5f7fa;
  display: flex;
  min-width: 270px; /* 减小最小宽度 */
  max-width: 330px; /* 设置最大宽度 */
  width: 100%; /* 使用百分比宽度 */
  min-width: 270px;
  /* 减小最小宽度 */
  max-width: 330px;
  /* 设置最大宽度 */
  width: 100%;
  /* 使用百分比宽度 */
  height: 84px;
  box-sizing: border-box;
}
@@ -171,7 +176,8 @@
  font-weight: bold;
  color: #fff;
  background: linear-gradient( 180deg, #0ACBCA 0%, #049C9A 100%);
  letter-spacing: 8px; /* 增加字间距 */
  letter-spacing: 8px;
  /* 增加字间距 */
}
.left-block.out .type-tag {
@@ -245,7 +251,9 @@
/* 添加媒体查询,适配小屏幕设备 */
@media screen and (max-width: 1200px) {
  .left-block, .right-block {
  .left-block,
  .right-block {
    min-width: 240px;
  }
  
@@ -256,12 +264,14 @@
@media screen and (max-width: 992px) {
  .timeline-row {
    flex-direction: column; /* 垂直排列 */
    flex-direction: column;
    /* 垂直排列 */
    height: auto;
    gap: 8px;
  }
  
  .left-block, .right-block {
  .left-block,
  .right-block {
    width: 100%;
    max-width: 100%;
  }
culture/src/views/strain-library/strain-library-manage/components/StrainDetail.vue
@@ -1,27 +1,28 @@
<template>
  <div>
    <el-dialog
        title="原始细胞库详情"
        :visible.sync="visible"
        width="70%"
        :close-on-click-modal="false"
        custom-class="strain-detail-dialog"
        append-to-body
        @close="$emit('update:visible', false)"
      @opened="fetchDetail"
    >
        <div class="strain-info">
            <!-- 第一行信息 -->
            <div class="info-row">
                <div class="info-item left-column">
                    <span class="label">菌种编号:</span>
                    <span class="value">{{ detail.strainNo }}</span>
            <span class="value">{{ detail.strainCode }}</span>
                </div>
                <div class="info-item flex-column">
                    <span class="label">鉴定方法:</span>
                    <span class="value">{{ detail.method }}</span>
            <span class="value">{{ detail.appraisalMethod }}</span>
                </div>
                <div class="info-item flex-column">
                    <span class="label">保存位置:</span>
                    <span class="value">{{ detail.amount }}</span>
            <span class="label">保藏位置:</span>
            <span class="value">{{ detail.saveLocation }}</span>
                </div>
            </div>
            
@@ -33,7 +34,7 @@
                </div>
                <div class="info-item flex-column full-width">
                    <span class="label">特性描述:</span>
                    <span class="value">{{ detail.certificate }}</span>
            <span class="value">{{ detail.features }}</span>
                </div>
            </div>
            
@@ -41,15 +42,22 @@
            <div class="info-row">
                <div class="info-item left-column">
                    <span class="label">菌种来源:</span>
                    <span class="value">{{ detail.source }}</span>
            <span class="value">{{ detail.strainSource }}</span>
                </div>
                <div class="info-item flex-column">
                    <span class="label">菌种保存方法:</span>
                    <span class="value">{{ detail.storage }}</span>
            <span class="value">{{ detail.saveMethod }}</span>
                </div>
                <div class="info-item flex-column">
                    <span class="label">出入库状态:</span>
                    <span class="value">{{ detail.operator }}</span>
            <span class="value">{{
              {
                1: "已出库",
                2: "出库待确认",
                3: "已入库",
                4: "入库待确认",
              }[detail.status] || ""
            }}</span>
                </div>
            </div>
        </div>
@@ -57,20 +65,29 @@
        <div class="record-table">
            <div class="table-title">原始细胞库出/入库记录</div>
            <el-table :data="detail.records" style="width: 100%">
                <el-table-column prop="type" label="出库/入库" />
                <el-table-column prop="operateTime" label="操作时间" />
                <el-table-column prop="operator" label="操作人姓名" />
                <el-table-column prop="reviewer" label="签核确认人姓名" />
                <el-table-column prop="status" label="状态">
          <el-table-column label="出库/入库">
                    <template #default="{ row }">
                        <el-tag :type="row.status === '已确认' ? 'success' : 'warning'">
                            {{ row.status }}
              {{ { 1: "出库", 2: "入库" }[row.type] || "" }}
            </template>
          </el-table-column>
          <el-table-column prop="boundTime" label="操作时间" />
          <el-table-column prop="handleName" label="操作人姓名" />
          <el-table-column prop="preserveName" label="签核确认人姓名" />
          <el-table-column label="状态">
            <template #default="{ row }">
              <el-tag :type="row.confirmTime ? 'success' : 'warning'">
                {{ row.confirmTime ? "已确认" : "待确认" }}
                        </el-tag>
                    </template>
                </el-table-column>
                <el-table-column label="操作" width="100">
                    <template #default="{ row }">
                        <el-button type="text" @click="handleView(row)">详情</el-button>
              <el-button v-if="!row.confirmTime && roleType == 3" style="margin-right: 10px" type="text" @click="handleConfirm(row)">确认</el-button>
              <el-button
                type="text"
                @click="handleView(row)"
                >详情</el-button
              >
                    </template>
                </el-table-column>
            </el-table>
@@ -85,44 +102,106 @@
            </div>
        </div>
    </el-dialog>
    <RecordDetailDialog
      :visible="visibleRecordDetailDialog"
      :recordData="recordData"
      @close="handleDialogClose"
      @confirm="handleOutbound"
      :type="dialogType"
    />
  </div>
</template>
<script>
import { getDetailById,confirmWarehousing } from "../service";
import RecordDetailDialog from "./RecordDetailDialog.vue";
export default {
    name: 'StrainDetail',
  components: { RecordDetailDialog },
  name: "StrainDetail",
    props: {
        visible: {
            type: Boolean,
            default: false
      default: false,
        },
        detail: {
            type: Object,
            default: () => ({})
        }
      default: () => ({}),
    },
    },
    data() {
        return {
      visibleRecordDetailDialog: false,
      recordData: {},
            currentPage: 1,
            total: 0
        }
      total: 0,
      dialogType: "",
      query: {
        endTime: "",
        id: "",
        pageNum: 1,
        pageSize: 10,
        startTime: "",
        roleType: "",
    },
    };
  },
    methods: {
    handleDialogClose() {
      this.recordData = {};
      this.visibleRecordDetailDialog = false;
    },
    fetchDetail() {
    this.roleType = JSON.parse(sessionStorage.getItem("userInfo")).roleType;
      this.query.id = this.detail.id;
      getDetailById(this.query).then((res) => {
        this.detail.records = res.warehousingList?.records || [];
        this.total = res.warehousingList?.total || 0;
        this.currentPage = res.warehousingList?.current || 1;
        this.$forceUpdate();
      });
    },
        handleView(row) {
            console.log('View record:', row)
        this.dialogType = "detail";
      this.recordData = row;
      this.visibleRecordDetailDialog = true;
    },
    handleOutbound(data) {
      // 这里调用出库API
      confirmWarehousing({
        id: this.recordData.id,
        preserveSignature: data.preserveSignature,
      }).then((res) => {
        if (res.code == 200) {
          this.$message.success("操作成功");
          this.visibleRecordDetailDialog = false;
          // 刷新列表
          this.fetchDetail();
        } else {
          this.$message.error(res.msg);
        }
      });
    },
    handleConfirm(row) {
      this.dialogType = "confirm";
      this.recordData = row;
      this.visibleRecordDetailDialog = true;
        },
        handlePageChange(page) {
            this.currentPage = page
            this.$emit('page-change', page)
        }
    }
}
      this.currentPage = page;
      this.$emit("page-change", page);
    },
  },
};
</script>
<style lang="less" scoped>
.strain-detail-dialog {
    :deep(.el-dialog__header) {
        padding: 20px;
        border-bottom: 1px solid #EBEEF5;
    border-bottom: 1px solid #ebeef5;
        margin-right: 0;
        
        .el-dialog__title {
@@ -138,7 +217,7 @@
}
.strain-info {
    background: #F5F7FA;
  background: #f5f7fa;
    border-radius: 4px;
    padding: 20px;
    margin-bottom: 20px;
@@ -195,7 +274,7 @@
        color: #303133;
        margin-bottom: 16px;
        padding-left: 8px;
        border-left: 4px solid #049C9A;
    border-left: 4px solid #049c9a;
    }
    .pagination {
culture/src/views/strain-library/strain-library-manage/index.vue
@@ -1,17 +1,37 @@
<template>
    <div class="list">
        <el-card class="header-box">
            <div class="box-title">
                <img src="@/assets/public/notice.png" class="header-icon">
        <img src="@/assets/public/notice.png" class="header-icon" />
                <span>菌种源保藏出/入细胞库登记表说明</span>
                <el-button type="text" class="view-more" @click="handleViewMore">查看全部 >></el-button>
        <el-button type="text" class="view-more" @click="handleViewMore"
          >查看全部 >></el-button
        >
            </div>
            <div class="header-content" :class="{ 'collapsed': true }">
                <p>1、菌种全部集中登记在【菌种源保藏出/入细胞库登记表】,请将来源有3 类菌经。</p>
                <p>1.1 原净土管理日油性的源头菌种:入细胞细胞库(现代-O)。</p>
                <p>1.2 是到菌的源头菌种:接种入主细胞库(现代-O),经过百种、验证后,菌种被保存日油管理沙土菌种,入细胞细胞库(现代-O)。</p>
                <p>1.3 是否菌种能自己分离后获得的源头菌种,接种入主细胞库:经过产验证后,保藏为少土管理日油管,入细胞细胞库(现代-O)。</p>
      <div class="header-content" :class="{ collapsed: true }">
        <p>
          1. 菌种全部集中登记在【菌种源保藏出/入细胞库登记表】,菌种来源有 3
          条路径。1.1 是沙土管或甘油管的源头菌种;入原始细胞库(祖代-O)。1.2
          是斜面的源头菌种;接种入主细胞库(祖代-O)。经过育种、验证后,菌种保藏为甘油管或沙土管的,入原始细胞库(祖代-0)1.3
          是含菌物质自己分离后获得的斜面源头菌种,接种入主细胞库;经生产验证后,保藏为沙土管或甘油管,入原始细胞库(祖代-O)。
          2.
          菌种细胞库,分类入三库,进行传代运行管理。三类库存空间进行区分,保藏菌种。2.1
          原始细胞库(祖代-O)、2.2 主细胞库(母代-M)、2.3
          生产细胞库(子代-S)、(孙代-G)3. 细胞库编码规则3.1
          细胞库编码规则:DD-M-240919-01-(O-0109-01)DD:代表项目组。M:“O”代表祖代原始细胞库,”M“代表母代主细胞库,”S“代表子代生产细胞库,“G”代表孙代生产细胞库。240919:代表在
          24 年 9 月 19
          接种批次的菌种;或收到外来菌种时间的入库批次。01:代表两位序列号。(O-0109-01):代表传代菌种的编号3.1.1
          传代编码方式演例:祖代:DD-O-240919-01
          传母代:DD-M-241017-01-(O-091901)DD-M-241017-02-(O-091901)DD-M-241017-03-(O-091901)子代:DD-S-241019-01-(M-1017-02)版权归奥利元生物所有,禁止外传。DD-S-241019-02-(M-1017-03)孙代:DD-G-241109-01-(S-1019-02)3.1.2
          编码规则实现了编码唯一,编码可溯源,编码直观、方便。3.2
          细胞库说明:3.2.1
          直接购买、自行从(土壤、相关物料、商品)等分离出来菌株进入原始细胞库。3.2.2
          从原始细胞库中选取出来再次纯化、改造、提高性能的菌株进入主细胞库。3.2.3
          主细胞库中选取出稳定,生产性能良好的菌株扩培后保种进入生产细胞库。4.
          菌种选育-保藏过程编号说明4.1 菌种选育时,培养皿的编号可使用 a-01、a-02
          等用于清晰形态观察记录;菌落编号使用序号 1/2/3等。4.2
          接种斜面菌种编码(-O)使用原始细胞库编码;斜面转菌种保藏使用与斜面一致的编码(-O);斜面传代入主细胞库的传代菌种,按编码器规则编码(-M)。
        </p>
            </div>
            <!-- 查看全部弹窗 -->
@@ -22,17 +42,40 @@
                class="view-all-dialog"
            >
                <div class="dialog-content">
                    <p>1、菌种全部集中登记在【菌种源保藏出/入细胞库登记表】,请将来源有3 类菌经。</p>
                    <p>1.1 原净土管理日油性的源头菌种:入细胞细胞库(现代-O)。</p>
                    <p>1.2 是到菌的源头菌种:接种入主细胞库(现代-O),经过百种、验证后,菌种被保存日油管理沙土菌种,入细胞细胞库(现代-O)。</p>
                    <p>1.3 是否菌种能自己分离后获得的源头菌种,接种入主细胞库:经过产验证后,保藏为少土管理日油管,入细胞细胞库(现代-O)。</p>
          <p>
            1. 菌种全部集中登记在【菌种源保藏出/入细胞库登记表】,菌种来源有 3
            条路径。1.1 是沙土管或甘油管的源头菌种;入原始细胞库(祖代-O)。1.2
            是斜面的源头菌种;接种入主细胞库(祖代-O)。经过育种、验证后,菌种保藏为甘油管或沙土管的,入原始细胞库(祖代-0)1.3
            是含菌物质自己分离后获得的斜面源头菌种,接种入主细胞库;经生产验证后,保藏为沙土管或甘油管,入原始细胞库(祖代-O)。2.
            菌种细胞库,分类入三库,进行传代运行管理。三类库存空间进行区分,保藏菌种。2.1
            原始细胞库(祖代-O)、2.2 主细胞库(母代-M)、2.3
            生产细胞库(子代-S)、(孙代-G)3. 细胞库编码规则3.1
            细胞库编码规则:DD-M-240919-01-(O-0109-01)DD:代表项目组。M:“O”代表祖代原始细胞库,”M“代表母代主细胞库,”S“代表子代生产细胞库,“G”代表孙代生产细胞库。240919:代表在
            24 年 9 月 19
            接种批次的菌种;或收到外来菌种时间的入库批次。01:代表两位序列号。(O-0109-01):代表传代菌种的编号3.1.1
            传代编码方式演例:祖代:DD-O-240919-01
            传母代:DD-M-241017-01-(O-091901)DD-M-241017-02-(O-091901)DD-M-241017-03-(O-091901)子代:DD-S-241019-01-(M-1017-02)版权归奥利元生物所有,禁止外传。DD-S-241019-02-(M-1017-03)孙代:DD-G-241109-01-(S-1019-02)3.1.2
            编码规则实现了编码唯一,编码可溯源,编码直观、方便。3.2
            细胞库说明:3.2.1
            直接购买、自行从(土壤、相关物料、商品)等分离出来菌株进入原始细胞库。3.2.2
            从原始细胞库中选取出来再次纯化、改造、提高性能的菌株进入主细胞库。3.2.3
            主细胞库中选取出稳定,生产性能良好的菌株扩培后保种进入生产细胞库。4.
            菌种选育-保藏过程编号说明4.1 菌种选育时,培养皿的编号可使用
            a-01、a-02 等用于清晰形态观察记录;菌落编号使用序号 1/2/3等。4.2
            接种斜面菌种编码(-O)使用原始细胞库编码;斜面转菌种保藏使用与斜面一致的编码(-O);斜面传代入主细胞库的传代菌种,按编码器规则编码(-M)。
          </p>
                </div>
            </el-dialog>
        </el-card>
        <!-- Table -->
        <TableCustom :queryForm="queryForm" :tableData="tableData" :total="total" @currentChange="handleCurrentChange"
            @sizeChange="handleSizeChange">
    <TableCustom
      :queryForm="queryForm"
      :tableData="tableData"
      :total="total"
      @currentChange="handleCurrentChange"
      @sizeChange="handleSizeChange"
    >
            <template #search>
                <el-form :model="form" label-width="auto" inline>
                    <el-form-item label="菌种编号:">
@@ -41,12 +84,13 @@
                    <el-form-item label="菌种名称:">
                        <el-input v-model="form.strainName" placeholder="请输入"></el-input>
                    </el-form-item>
                    <el-form-item label="状态:">
          <el-form-item v-if="roleType == 4" label="状态:">
                        <el-select v-model="form.status" placeholder="请选择">
                            <el-option label="全部" value=""></el-option>
                            <el-option label="已入库" value="1"></el-option>
                            <el-option label="已出库" value="2"></el-option>
                            <el-option label="入库待确认" value="3"></el-option>
              <el-option label="已出库" value="1"></el-option>
              <el-option label="出库待确认" value="2"></el-option>
              <el-option label="已入库" value="3"></el-option>
              <el-option label="入库待确认" value="4"></el-option>
                        </el-select>
                    </el-form-item>
                    <el-form-item class="search-btn-box">
@@ -59,229 +103,207 @@
            <template #setting>
                <div class="tableTitle">
                    <div class="flex a-center">
                        <div class="title" :class="{ active: currentType === 'list' }"
                            @click="handleTypeChange('list')">
                            原始细胞列表</div>
                        <div class="drafts" :class="{ active: currentType === 'draft' }"
                            @click="handleTypeChange('draft')">
                            草稿箱</div>
            <div
              class="title"
              :class="{ active: currentType === 'list' }"
              @click="handleTypeChange('list')"
            >
              原始细胞列表
                    </div>
                    <div class="flex a-center">
                        <el-button @click="handleNewStrain" class="el-icon-plus" type="primary" style="margin-right: 12px;">新增原始细胞</el-button>
                        <el-button @click="handleBatchAdd" class="el-icon-plus" type="primary">批量新增</el-button>
            <div
              class="drafts"
              :class="{ active: currentType === 'draft' }"
              @click="handleTypeChange('draft')"
            >
              草稿箱
            </div>
          </div>
          <div v-if="roleType == 4" class="flex a-center">
            <el-button
              @click="handleNewStrain"
              class="el-icon-plus"
              type="primary"
              style="margin-right: 12px"
              >新增原始细胞</el-button
            >
            <el-button
              @click="handleNewStrain"
              class="el-icon-plus"
              type="primary"
              >批量新增</el-button
            >
                    </div>
                </div>
            </template>
            <template #table>
                <el-table-column prop="strainNo" label="菌种编号" />
        <el-table-column prop="strainCode" label="菌种编号" />
                <el-table-column prop="strainName" label="菌种名称" />
                <el-table-column prop="source" label="菌种来源" />
                <el-table-column prop="method" label="鉴定方法" />
                <el-table-column prop="certificate" label="特征描述" />
                <el-table-column prop="storage" label="菌种保存方法" />
                <el-table-column prop="amount" label="保存位置" />
                <el-table-column prop="inventory" label="库存余量" />
                <el-table-column prop="notes" label="备注" />
                <el-table-column prop="status" label="当前状态">
        <el-table-column prop="strainSource" label="菌种来源" />
        <el-table-column prop="appraisalMethod" label="鉴定方法" />
        <el-table-column prop="features" label="特征描述" />
        <el-table-column prop="saveMethod" label="菌种保存方法" />
        <el-table-column prop="saveLocation" label="保藏位置" />
        <el-table-column prop="stock" label="库存余量" />
        <el-table-column prop="remark" label="备注" />
        <el-table-column
          v-if="currentType === 'list'"
          prop="status"
          label="当前状态"
        >
                    <template #default="{ row }">
                        <el-tag :type="getStatusType(row.status)">{{ getStatusText(row.status) }}</el-tag>
            <el-tag :type="getStatusType(row.status)">{{
              getStatusText(row.status)
            }}</el-tag>
                    </template>
                </el-table-column>
                <el-table-column label="操作" width="200">
                    <template #default="{ row }">
                        <el-button type="text" @click="handleDetail(row)">详情</el-button>
                        <el-button type="text" @click="handleEdit(row)">编辑</el-button>
                        <el-button type="text" @click="handleRecord(row)">出入库记录</el-button>
            <el-button v-if="row.status == 2 || row.status == 4" type="text" @click="handleEdit(row)">编辑</el-button>
            <el-button
              v-if="currentType === 'list'"
              type="text"
              @click="handleRecord(row)"
              >出入库记录</el-button
            >
            <el-button v-if="roleType == 1" type="text" @click="handleDelete(row)">删除</el-button>
                    </template>
                </el-table-column>
            </template>
        </TableCustom>
        <StrainDetail
            :visible.sync="detailVisible"
            :detail="currentDetail"
        />
    <StrainDetail :visible.sync="detailVisible" :detail="currentDetail" />
    </div>
</template>
<script>
import StrainDetail from './components/StrainDetail.vue'
import StrainDetail from "./components/StrainDetail.vue";
import { getList, deleteStrainLibrary } from "./service";
export default {
    name: 'StrainLibraryManage',
  name: "StrainLibraryManage",
    components: {
        StrainDetail
    StrainDetail,
    },
    data() {
        return {
            dialogVisible: false,
            currentType: 'list',
      currentType: "list",
            detailVisible: false,
            currentDetail: {},
            form: {
                strainNo: '',
                strainName: '',
                status: ''
        strainNo: "",
        strainName: "",
        status: "",
            },
            queryForm: {
                pageSize: 10,
                pageNum: 1
        pageNum: 1,
            },
            total: 800,
            tableData: [
                {
                    strainNo: 'YX-2024001',
                    strainName: '大肠杆菌',
                    source: '实验室分离',
                    method: '形态学鉴定、生理生化试验',
                    certificate: '革兰氏阴性杆菌,可发酵葡萄糖产酸产气,IMViC试验++--',
                    storage: '斜面培养',
                    amount: 'A区-01-001',
                    inventory: '50',
                    notes: '用于质粒转化',
                    status: '1'
      tableData: [],
      roleType: "",
    };
                },
                {
                    strainNo: 'YX-2024002',
                    strainName: '枯草芽孢杆菌',
                    source: '菌种保藏中心',
                    method: '16S rDNA测序',
                    certificate: '革兰氏阳性芽孢杆菌,可水解淀粉,产生溶菌素',
                    storage: '冷冻保存',
                    amount: 'B区-02-005',
                    inventory: '30',
                    notes: '工业发酵菌种',
                    status: '1'
                },
                {
                    strainNo: 'YX-2024003',
                    strainName: '酿酒酵母',
                    source: '发酵工厂',
                    method: '显微镜观察、生理特性',
                    certificate: '椭圆形单细胞真菌,可发酵葡萄糖产生乙醇',
                    storage: '甘油管保存',
                    amount: 'A区-03-002',
                    inventory: '40',
                    notes: '发酵工艺优化',
                    status: '2'
                },
                {
                    strainNo: 'YX-2024004',
                    strainName: '乳酸菌',
                    source: '乳制品分离',
                    method: '生化鉴定、API条',
                    certificate: '革兰氏阳性球菌,产生乳酸,耐酸性强',
                    storage: '冷冻干燥',
                    amount: 'C区-01-003',
                    inventory: '25',
                    notes: '益生菌研究',
                    status: '3'
                },
                {
                    strainNo: 'YX-2024005',
                    strainName: '青霉菌',
                    source: '环境样本',
                    method: '形态学特征、ITS测序',
                    certificate: '丝状真菌,产生蓝绿色分生孢子,可产青霉素',
                    storage: '斜面培养',
                    amount: 'B区-04-001',
                    inventory: '35',
                    notes: '次级代谢产物研究',
                    status: '1'
                }
            ]
        }
  activated() {
    this.searchData();
    // 角色类型 1=超级管理员 2=审批人 3=工程师 4=实验员
    this.roleType = JSON.parse(sessionStorage.getItem("userInfo")).roleType;
    },
    methods: {
        handleViewMore() {
            this.dialogVisible = true;
    handleDelete(row) {
      this.$confirm("确定删除该数据吗?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      }).then(() => {
        deleteStrainLibrary({ id: row.id }).then((res) => {
            this.$message.success("删除成功");
            this.searchData();
        });
      });
        },
        resetForm() {
            this.form = {
                strainNo: '',
                strainName: '',
                status: ''
            }
            this.searchData()
        },
        searchData() {
            // 模拟搜索逻辑
            const { strainNo, strainName, status } = this.form
            let filteredData = [...this.tableData]
            if (strainNo) {
                filteredData = filteredData.filter(item =>
                    item.strainNo.toLowerCase().includes(strainNo.toLowerCase())
                )
            }
            if (strainName) {
                filteredData = filteredData.filter(item =>
                    item.strainName.toLowerCase().includes(strainName.toLowerCase())
                )
            }
            if (status) {
                filteredData = filteredData.filter(item =>
                    item.status === status
                )
            }
            this.total = filteredData.length
            // 实际项目中这里应该调用API
            console.log('搜索条件:', this.form)
            console.log('分页信息:', this.queryForm)
    handleRecord(row) {
      this.$router.push({
        path: `/strain-library/strain-library-manage/record?id=${row.id}`,
      });
        },
        handleNewStrain() {
            this.$router.push('/strain-library/strain-library-manage/add')
            // Implement new strain logic
      this.$router.push({ path: "/strain-library/strain-library-manage/add" });
        },
        handleBatchAdd() {
            // Implement batch add logic
    handleEdit(row) {
      this.$router.push({
        path: `/strain-library/strain-library-manage/add?id=${row.id}`,
      });
        },
        handleDetail(row) {
            this.currentDetail = row;
            this.detailVisible = true;
        },
        handleEdit(row) {
            // Implement edit logic
    handleViewMore() {
      this.dialogVisible = true;
        },
        handleRecord(row) {
            this.$router.push({
                path: '/strain-library/strain-library-manage/record',
                query: {
                    id: row.strainNo
    resetForm() {
      this.form = {
        strainNo: "",
        strainName: "",
        status: "",
      };
      this.searchData();
    },
    searchData() {
      const params = {
        pageNum: this.queryForm.pageNum,
        pageSize: this.queryForm.pageSize,
        strainCode: this.form.strainNo,
        strainName: this.form.strainName,
        isDraft: this.currentType === "draft" ? 1 : 0,
        status: this.form.status,
        type: 1,
      };
      getList(params)
        .then((res) => {
          if (res.code === 200) {
            this.tableData = res.data.records;
            this.total = res.data.total;
                }
            })
        .catch((err) => {
          this.$message.error("数据加载失败");
        });
        },
        handleCurrentChange(page) {
            this.queryForm.pageNum = page
            // Implement page change logic
      this.queryForm.pageNum = page;
      this.searchData();
        },
        handleSizeChange(size) {
            this.queryForm.pageSize = size
            // Implement size change logic
      this.queryForm.pageSize = size;
      this.searchData();
        },
        handleTypeChange(type) {
            this.currentType = type;
            // Implement type change logic
      this.searchData();
        },
        getStatusType(status) {
            const types = {
                1: 'success',
                2: 'info',
                3: 'warning'
            }
            return types[status] || 'info'
        1: "warning",
        2: "warning",
        3: "success",
        4: "success",
      };
      return types[status] || "info";
        },
        getStatusText(status) {
            const texts = {
                1: '已入库',
                2: '已出库',
                3: '入库待确认'
            }
            return texts[status] || '未知状态'
        }
    }
}
        1: "已出库",
        2: "出库待确认",
        3: "已入库",
        4: "入库待确认",
      };
      return texts[status] || "未知状态";
    },
  },
};
</script>
<style scoped lang="less">
@@ -311,7 +333,7 @@
        .view-more {
            position: absolute;
            right: 0;
            color: #049C9A;
      color: #049c9a;
        }
    }
@@ -336,7 +358,7 @@
.search-form {
    margin-bottom: 20px;
    background: #F5F7FA;
  background: #f5f7fa;
    padding: 24px;
    border-radius: 8px;
@@ -364,17 +386,17 @@
    .tab {
        padding: 10px 30px;
        border: 1px solid #DCDFE6;
    border: 1px solid #dcdfe6;
        border-bottom: none;
        border-radius: 8px 8px 0 0;
        cursor: pointer;
        margin-right: 10px;
        background: #F5F7FA;
    background: #f5f7fa;
        &.active {
            background: #fff;
            border-color: #049C9A;
            color: #049C9A;
      border-color: #049c9a;
      color: #049c9a;
            font-weight: bold;
        }
    }
@@ -404,7 +426,6 @@
        line-height: 50px;
        width: 166px;
        text-align: center;
    }
    .drafts {
@@ -427,14 +448,13 @@
        background: #ffffff;
        border-radius: 8px 8px 0px 0px;
        border: 1px solid #049c9a;
    }
}
.view-all-dialog {
    :deep(.el-dialog__header) {
        padding: 20px;
        border-bottom: 1px solid #EBEEF5;
    border-bottom: 1px solid #ebeef5;
        margin-right: 0;
        
        .el-dialog__title {
@@ -466,5 +486,4 @@
        }
    }
}
</style>
culture/src/views/strain-library/strain-library-manage/record.vue
@@ -7,15 +7,15 @@
                <div class="info-row">
                    <div class="info-item left-column">
                        <span class="label">菌种编号:</span>
                        <span class="value">{{ detail.strainNo }}</span>
            <span class="value">{{ detail.strainCode }}</span>
                    </div>
                    <div class="info-item flex-column">
                        <span class="label">鉴定方法:</span>
                        <span class="value">{{ detail.method }}</span>
            <span class="value">{{ detail.appraisalMethod }}</span>
                    </div>
                    <div class="info-item flex-column">
                        <span class="label">保藏位置:</span>
                        <span class="value">{{ detail.amount }}</span>
            <span class="value">{{ detail.saveLocation }}</span>
                    </div>
                </div>
                
@@ -27,7 +27,7 @@
                    </div>
                    <div class="info-item flex-column full-width">
                        <span class="label">特性描述:</span>
                        <span class="value">{{ detail.certificate }}</span>
            <span class="value">{{ detail.features }}</span>
                    </div>
                </div>
                
@@ -35,57 +35,112 @@
                <div class="info-row">
                    <div class="info-item left-column">
                        <span class="label">菌种来源:</span>
                        <span class="value">{{ detail.source }}</span>
            <span class="value">{{ detail.strainSource }}</span>
                    </div>
                    <div class="info-item flex-column">
                        <span class="label">菌种保存方法:</span>
                        <span class="value">{{ detail.storage }}</span>
            <span class="value">{{ detail.saveMethod }}</span>
                    </div>
                </div>
            </div>
        </el-card>
        <!-- 出入库记录表格 -->
        <TableCustom :queryForm="queryForm" :tableData="recordList" :total="total" @currentChange="handlePageChange">
    <TableCustom
      :queryForm="queryForm"
      :tableData="recordList"
      :total="total"
      @currentChange="handlePageChange"
    >
            <template #setting>
                <div class="tableTitle">
                    <div class="flex a-center">
                        <div class="title" :class="{ active: currentType === 'table' }"
                            @click="handleTypeChange('table')">
                            原始细胞保藏出/入库登记表</div>
                        <div class="drafts" :class="{ active: currentType === 'timeline' }"
                            @click="handleTypeChange('timeline')">
                            原始细胞保藏出/入库时间轴</div>
            <div
              class="title"
              :class="{ active: currentType === 'table' }"
              @click="handleTypeChange('table')"
            >
              原始细胞保藏出/入库登记表
            </div>
            <div
              class="drafts"
              :class="{ active: currentType === 'timeline' }"
              @click="handleTypeChange('timeline')"
            >
              原始细胞保藏出/入库时间轴
            </div>
                    </div>
                    <div class="flex a-center">
                        <el-button @click="handleAddRecord" class="el-icon-plus" type="primary">新增出入库记录</el-button>
            <el-button
              v-if="roleType == 4"
              @click="handleAddRecord"
              class="el-icon-plus"
              type="primary"
              >新增出入库记录</el-button
            >
                    </div>
                </div>
            </template>
            <template #table v-if="currentType === 'table'">
                <el-table-column prop="type" label="出库/入库" />
                <el-table-column prop="operateTime" label="操作时间" />
                <el-table-column prop="operator" label="操作人签字" />
                <el-table-column prop="reviewer" label="菌种保藏人签字" />
        <el-table-column prop="type" label="出库/入库">
          <template #default="{ row }">
            <span>
              {{ row.type === 1 ? "出库" : "入库" }}
            </span>
          </template>
        </el-table-column>
        <el-table-column prop="boundTime" label="操作时间" />
        <el-table-column prop="handleSignature" label="操作人签字">
          <template #default="{ row }">
            <el-image
              v-if="row.handleSignature"
              style="width: 100px; height: 100px"
              :src="row.handleSignature"
              :preview-src-list="[row.handleSignature]"
            >
            </el-image>
          </template>
        </el-table-column>
        <el-table-column prop="preserveSignature" label="菌种保藏人签字">
          <template #default="{ row }">
            <el-image
              v-if="row.preserveSignature"
              style="width: 100px; height: 100px"
              :src="row.preserveSignature"
              :preview-src-list="[row.preserveSignature]"
            >
            </el-image>
          </template>
        </el-table-column>
                    <el-table-column prop="status" label="状态">
                        <template #default="{ row }">
                            <el-tag :type="row.status === '已确认' ? 'success' : 'warning'">
                                {{ row.status }}
            <el-tag :type="row.preserveSignature ? 'success' : 'warning'">
              {{ row.preserveSignature ? "已确认" : "待确认" }}
                            </el-tag>
                        </template>
                    </el-table-column>
                    <el-table-column label="操作" width="180">
                        <template #default="{ row }">
                            <el-button type="text" class="operation-btn" @click="handleView(row)">详情</el-button>
                            <el-button type="text" class="operation-btn" @click="handleEdit(row)">编辑</el-button>
                            <el-button type="text" @click="handleDelete(row)">删除</el-button>
            <el-button
              v-if="!row.preserveSignature && roleType == 3"
              type="text"
              class="operation-btn"
              @click="handleConfirm(row)"
              >确认</el-button
            >
            <el-button
              type="text"
              class="operation-btn"
              @click="handleView(row)"
              >详情</el-button
            >
            <el-button v-if="roleType == 1" type="text" @click="handleDelete(row)">删除</el-button>
                    </template>
                </el-table-column>
            </template> 
            <template #tableCustom v-if="currentType === 'timeline'">
                <record-timeline :list="timelineList" />
            </template>
        </TableCustom>
@@ -95,8 +150,9 @@
            :record-data="currentRecord"
            @close="handleDialogClose"
            @confirm="handleOutbound"
      :type="dialogType"
        />
    <!-- 新增出入库记录弹窗 -->
        <add-record-dialog
            :visible.sync="addDialogVisible"
            @confirm="handleAddRecordConfirm"
@@ -105,173 +161,138 @@
</template>
<script>
import RecordDetailDialog from './components/RecordDetailDialog.vue'
import AddRecordDialog from './components/AddRecordDialog.vue'
import RecordTimeline from './components/RecordTimeline.vue'
import RecordDetailDialog from "./components/RecordDetailDialog.vue";
import AddRecordDialog from "./components/AddRecordDialog.vue";
import RecordTimeline from "./components/RecordTimeline.vue";
import {
  timeList,
  getDetail,
  addWarehousing,
  getDetailById,
  confirmWarehousing,
} from "./service";
export default {
    name: 'StrainRecord',
  name: "StrainRecord",
    components: {
        RecordDetailDialog,
        AddRecordDialog,
        RecordTimeline
    RecordTimeline,
    },
    data() {
        return {
            currentType: 'table',
      currentType: "table",
            detail: {},
            currentPage: 1,
            pageSize: 10,
            total: 0,
            queryForm: {
                pageSize: 10,
                pageNum: 1
        pageNum: 1,
            },
            recordList: [
                {
                    type: '入库',
                    operateTime: '2025-1-21 15:46:50',
                    operator: '张三',
                    reviewer: '李四',
                    status: '已确认'
                },
                {
                    type: '出库',
                    operateTime: '2025-1-21 15:46:50',
                    operator: '张三',
                    reviewer: '李四',
                    status: '已确认'
                },
                {
                    type: '入库',
                    operateTime: '2025-1-21 15:46:50',
                    operator: '张三',
                    reviewer: '李四',
                    status: '已确认'
                },
                {
                    type: '出库',
                    operateTime: '2025-1-21 15:46:50',
                    operator: '张三',
                    reviewer: '李四',
                    status: '已确认'
                },
                {
                    type: '入库',
                    operateTime: '2025-1-21 15:46:50',
                    operator: '李四',
                    reviewer: '李四',
                    status: '已确认'
                }
            ],
      recordList: [],
      timelineList: [],
            dialogVisible: false,
            currentRecord: {},
            addDialogVisible: false
        }
    },
    computed: {
        timelineList() {
            // 可根据需要处理数据格式,这里直接用 recordList
            return this.recordList.map(item => ({
                ...item,
                confirmTime: item.confirmTime || item.operateTime // 若无确认时间则用操作时间
            }))
        }
      addDialogVisible: false,
      dialogType: "detail",
      roleType: "",
    };
    },
    created() {
    this.roleType = JSON.parse(sessionStorage.getItem("userInfo")).roleType;
        // 获取路由参数中的菌种信息
        const strainId = this.$route.query.id
    const strainId = this.$route.query.id;
    this.queryForm.id = strainId;
        if (strainId) {
            this.getStrainDetail(strainId)
            this.getRecordList()
      this.getStrainDetail(strainId);
      this.getRecordList();
        }
    },
    methods: {
    handleDelete(row) {
      this.$confirm("确定删除该数据吗?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      }).then(() => {
        deleteWarehousing({ id: row.id }).then((res) => {
          this.$message.success("删除成功");
          this.getRecordList();
        });
      });
    },
        getStrainDetail(id) {
            // 这里应该调用接口获取菌种详情
            // 暂时使用模拟数据
            this.detail = {
                strainNo: '3418732431',
                strainName: '名称名称名称',
                source: '来源11111111111',
                method: '1231231',
                certificate: '特性描述',
                storage: '方法方法',
                amount: '位置位置位置位置位置位置位置位置',
                operator: '入库'
            }
      getDetail({ id }).then((res) => {
        this.detail = res;
      });
        },
        getRecordList() {
            // 这里应该调用接口获取出入库记录
            // 暂时使用已有模拟数据
            this.total = this.recordList.length
      timeList(this.queryForm).then((res) => {
        this.timelineList = res.data;
      });
      getDetailById({ id: this.$route.query.id }).then((res) => {
        this.recordList = res.warehousingList.records;
        this.total = res.warehousingList.total;
      });
        },
        handleView(row) {
            this.currentRecord = {
                ...row,
                operatorSignature: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', // 模拟操作人签字图片
                reviewerSignature: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', // 模拟保藏人签字图片
                operateTime: '2025-1-22 13:49:51',
                confirmTime: '2025-1-22 14:30:00'
            }
            this.dialogVisible = true
      this.dialogType = "detail";
      this.currentRecord = row;
      this.dialogVisible = true;
    },
    handleConfirm(row) {
      this.dialogType = "confirm";
      this.currentRecord = row;
      this.dialogVisible = true;
        },
        handlePageChange(page) {
            this.queryForm.pageNum = page
      this.queryForm.pageNum = page;
            // 这里应该调用接口获取对应页码的数据
        },
        handleTypeChange(type) {
            this.currentType = type
      this.currentType = type;
        },
        handleAddRecord() {
            this.addDialogVisible = true
        },
        handleEdit(row) {
            console.log('编辑记录:', row)
            // 实现编辑记录逻辑
        },
        handleDelete(row) {
            this.$confirm('确认删除该记录吗?', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
            }).then(() => {
                console.log('删除记录:', row)
                // 实际项目中这里应该调用删除API
                this.$message({
                    type: 'success',
                    message: '删除成功!'
                })
            }).catch(() => {
                this.$message({
                    type: 'info',
                    message: '已取消删除'
                })
            })
      this.addDialogVisible = true;
        },
        handleDialogClose() {
            this.currentRecord = {}
            this.dialogVisible = false
      this.currentRecord = {};
      this.dialogVisible = false;
        },
        handleOutbound(data) {
            // 这里调用出库API
            console.log('出库操作:', data)
            this.$message.success('出库成功')
            this.dialogVisible = false
      confirmWarehousing({
        id: this.currentRecord.id,
        preserveSignature: data.preserveSignature,
      }).then((res) => {
        console.log(res);
        if (res.code == 200) {
          this.$message.success("操作成功");
          this.dialogVisible = false;
            // 刷新列表
            this.getRecordList()
          this.getRecordList();
        } else {
          this.$message.error(res.msg);
        }
      });
        },
        handleAddRecordConfirm(record) {
            // 这里可以将新记录添加到 recordList 或调用后端API
            this.$message.success('新增出入库记录成功')
            // 例如:this.recordList.push(record)
            this.getRecordList() // 或刷新列表
      addWarehousing({ ...record, trainLibraryId: this.$route.query.id }).then(
        (res) => {
          this.$message.success("操作成功");
          this.getRecordList();
        }
      );
        },
        goBack() {
            this.$router.go(-1)
        }
    }
}
      this.$router.go(-1);
    },
  },
};
</script>
<style lang="less" scoped>
culture/src/views/strain-library/strain-library-manage/service.js
New file
@@ -0,0 +1,56 @@
import axios from '@/utils/request';
// 列表
export const getList = (data) => {
  return axios.post('/api/t-train-library/pageList', { ...data })
}
// 新增
export const add = (data) => {
  return axios.post('/api/t-train-library/add', { ...data })
}
// 编辑
export const edit = (data) => {
  return axios.post('/api/t-train-library/update', { ...data })
}
// 查看详情
export const getDetail = (params) => {
  return axios.get('/open/t-train-library/getDetailEditById', { params })
}
// 批量新增
export const addBatch = (data) => {
  return axios.post('/api/t-train-library/addBatch', data)
}
// 查看菌种库详情
export const getDetailById = (data) => {
  return axios.post('/open/t-train-library/getDetailById', { ...data })
}
// 获取菌种库出入库时间轴列表
export const timeList = (data) => {
  return axios.post('/api/t-train-library/timeList?id='+data.id, { ...data })
}
// 新增菌种库出入记录
export const addWarehousing = (data) => {
  return axios.post('/open/t-train-library/addWarehousing', { ...data })
}
// 确认出入库
export const confirmWarehousing = (data) => {
  return axios.post('/api/t-train-library/confirm', { ...data })
}
// 删除菌种库
export const deleteStrainLibrary = (params) => {
  return axios.delete('/open/t-train-library/deleteById', { params })
}
// 删除菌种库出入库记录
export const deleteWarehousing = (params) => {
  return axios.delete('/open/t-train-library/deleteWarehousingById', { params })
}
laboratory/src/router/index.js
@@ -388,12 +388,22 @@
            {
                path: "add",
                meta: {
                    title: "新增可行报告",
                    title: "新增可研报告",
                    hide: true,
                    keepAlive: true,
                },
                component: () => import("../views/reportLibrary/feasibilityStudy/add.vue"),
            },
            {
                path: "edit",
                meta: {
                    title: "编辑可研报告",
                    hide: true,
                    keepAlive: true,
                },
                component: () => import("../views/reportLibrary/feasibilityStudy/add.vue"),
            },
            {
                path: "feasibilityReport",
                meta: {
@@ -403,6 +413,26 @@
                component: () => import("../views/reportLibrary/feasibilityReport/index.vue"),
            },
            {
                path: "addFeasibility",
                meta: {
                    title: "新增可行报告",
                    hide: true,
                    keepAlive: true,
                },
                component: () => import("../views/reportLibrary/feasibilityReport/add.vue"),
            },
            {
                path: "editFeasibility",
                meta: {
                    title: "编辑可行报告",
                    hide: true,
                    keepAlive: true,
                },
                component: () => import("../views/reportLibrary/feasibilityReport/add.vue"),
            },
            {
                path: "processDevelopment",
                meta: {
                    title: "工艺开发工具",
@@ -410,6 +440,28 @@
                },
                component: () => import("../views/reportLibrary/processDevelopment/index.vue"),
            },
            {
                path: "addProcessDevelopment",
                meta: {
                    title: "新增工艺开发工具",
                    hide: true,
                    keepAlive: true,
                },
                component: () => import("../views/reportLibrary/processDevelopment/add.vue"),
            },
            {
                path: "editProcessDevelopment",
                meta: {
                    title: "编辑工艺开发工具",
                    hide: true,
                    keepAlive: true,
                },
                component: () => import("../views/reportLibrary/processDevelopment/add.vue"),
            },
            {
                path: "verificationRelease",
                meta: {
laboratory/src/views/dataManagement/approvalPlan/list.vue
@@ -171,7 +171,7 @@
      ],
      approvalDialogVisible: false,
      approvalDialogType: "approve",
      currentApprovalData: null,
      currentApprovalData: [],
      // 确认弹窗相关数据
      changeStatus: false,
      changeStatusTitle: "",
laboratory/src/views/reportLibrary/feasibilityReport/add.vue
New file
@@ -0,0 +1,272 @@
<template>
    <div class="add-container" :loading="loading">
        <Card v-loading="loading">
            <div class="header-title" style="width: 100%;">
                <div class="header-title-left">
                    <img src="@/assets/public/headercard.png" />
                    <div>所属项目组</div>
                </div>
                <div class="header-title-right">
                    <el-button @click="showChoose = true" class="el-icon-circle-plus-outline" type="primary">
                        选择项目组</el-button>
                </div>
            </div>
            <Table :height="null" :data="tableData" :queryForm="queryForm" :total="0">
                <template>
                    <el-table-column prop="teamName" label="项目组名称" />
                    <el-table-column prop="personCharge" label="项目负责人" />
                    <el-table-column prop="staffName" label="项目组成员" />
                    <el-table-column prop="createTime" label="创建时间" />
                </template>
            </Table>
            <el-form ref="form" :model="form" :rules="rules" inline label-position="top" style="margin-top: 38px">
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div>报告编号</div>
                    </div>
                </div>
                <el-form-item prop="reportCode" style="margin-top: 38px">
                    <el-input v-model="form.reportCode" style="width: 100%;" placeholder="请输入报告编号" />
                </el-form-item>
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div>报告名称</div>
                    </div>
                </div>
                <el-form-item prop="reportName" style="margin-top: 38px">
                    <el-input v-model="form.reportName" style="width: 100%;" placeholder="请输入报告名称" />
                </el-form-item>
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div>报告正文</div>
                    </div>
                </div>
                <el-form-item prop="reportText" style="margin-top: 38px">
                    <ai-editor ref="materialEditor" :value="form.reportText" style="width: 100%;"
                        placeholder="请输入报告正文" />
                </el-form-item>
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div class="noRequire">附件</div>
                    </div>
                </div>
                <el-form-item prop="name" style="margin-top: 38px">
                    <el-upload action="https://jsonplaceholder.typicode.com/posts/" :file-list="fileList">
                        <el-button size="small" type="primary">点击上传</el-button>
                    </el-upload>
                </el-form-item>
                <div class="end-btn" style="margin-top: 38px">
                    <el-button type="primary" @click="submit" :loading="loading">发送</el-button>
                    <el-button type="default" @click="save" :loading="loading">存草稿</el-button>
                </div>
            </el-form>
        </Card>
        <chooseProject @submit="getProjectData" :show="showChoose" @close="showChoose = false"></chooseProject>
    </div>
</template>
<script>
import { Card } from 'element-ui';
import AiEditor from '@/components/AiEditor'
import chooseProject from '@/components/chooseProject'
import { addData, getDetail, editData } from './service'
export default {
    components: {
        AiEditor,
        chooseProject
    },
    data() {
        return {
            loading: false,
            form: {
                reportCode: "",
                reportName: "",
                reportText: ""
            },
            tableData: [],
            fileList: [], // 附件列表
            showChoose: false,
            rules: {
                reportCode: [
                    { required: true, message: '请输入报告编号', trigger: 'blur' }
                ],
                reportName: [
                    { required: true, message: '请输入报告名称', trigger: 'blur' }
                ],
            },
            queryForm: {}
        }
    },
    mounted() {
        if (this.$route.query.id) {
            this.getDetail()
        }
    },
    methods: {
        getDetail() {
            getDetail(this.$route.query.id).then(res => {
                this.form = res
                this.tableData = [{ ...res.projectTeam, staffName: res.staffNames }]
                this.fileList = res.fileList
            })
        },
        getProjectData(data) {
            this.tableData = [data]
            this.$forceUpdate()
            this.showChoose = false
        },
        submit() {
            if (this.tableData.length == 0) {
                this.$message.error('请选择项目组')
                return
            }
            this.$refs.form.validate((valid) => {
                if (this.$refs.materialEditor.getContent() == '<p></p>') {
                    this.$message.error('请输入报告正文')
                    return
                }
                let data = {
                    ...this.form,
                    reportType: 2,
                    status: 1,
                    reportText: this.$refs.materialEditor.getContent(),
                    teamId: this.tableData[0].id
                }
                if (valid) {
                    this.loading = true
                    if (this.$route.query.id) {
                        editData({ ...data, id: this.$route.query.id }).then(res => {
                            if (res.code === 200) {
                                this.$message.success('修改成功')
                                this.$router.back()
                            } else {
                                this.$message.error(res.message)
                            }
                        })
                    } else {
                        addData({ ...data }).then(res => {
                            if (res.code === 200) {
                                this.$message.success('发布成功')
                                this.$router.back()
                            } else {
                                this.$message.error(res.message)
                            }
                        }).finally(() => {
                            this.loading = false
                        })
                    }
                }
            })
        },
        save() {
            this.$refs.form.validate((valid) => {
                let data = {
                    ...this.form,
                    reportType: 2,
                    status: -1,
                    reportText: this.$refs.materialEditor.getContent(),
                    teamId: this.tableData[0].id
                }
                delete data.id
                if (valid) {
                    this.loading = true
                    addData({ ...data }).then(res => {
                        if (res.code === 200) {
                            this.$message.success('提交成功')
                            this.$router.back()
                        } else {
                            this.$message.error(res.message)
                        }
                    }).finally(() => {
                        this.loading = false
                    })
                }
            })
        },
    },
}
</script>
<style lang="less" scoped>
.header-title {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    margin-bottom: 20px;
    gap: 13px;
    .header-title-left {
        display: flex;
        align-items: center;
        gap: 13px;
        margin-top: 38px;
        img {
            width: 12px;
            height: 19px;
        }
        div {
            flex-shrink: 0;
            font-weight: bold;
            font-size: 18px;
            color: #222222;
            line-height: 27px;
            font-family: "Source Han Sans CN Bold Bold";
            &:before {
                content: "*";
                color: #f56c6c;
                margin-right: 4px;
            }
        }
        .noRequire:before {
            content: unset;
        }
        span {
            flex-shrink: 0;
            font-weight: bold;
            font-size: 18px;
            color: #222222;
            line-height: 27px;
            font-family: "Source Han Sans CN Bold Bold";
        }
    }
    .header-title-left :first-child {
        margin-top: 0;
    }
}
.header-title:first-child {
    .header-title-left {
        margin-top: 0;
    }
}
.end-btn {
    display: flex;
    align-items: center;
    gap: 10px;
    button {
        width: 180px;
        height: 36px;
    }
}
</style>
laboratory/src/views/reportLibrary/feasibilityReport/components/approval/index.vue
@@ -1,7 +1,7 @@
<template>
    <el-dialog :title="dialogTitle"  :visible.sync="visible" width="80%" po :close-on-click-modal="false"
    <el-dialog :title="dialogTitle" :visible.sync="visible" width="80%" @open="open" po :close-on-click-modal="false"
        @close="handleClose">
        <div class="approval-dialog">
        <div class="approval-dialog" :style="{height: obj.isDetail ? '50vh' : '40vh'}">
            <!-- 左侧审批内容 -->
            <div class="approval-content">
                <Card class="approval-content-card">
@@ -12,13 +12,12 @@
                                <div>所属项目组</div>
                            </div>
                        </div>
                        <Table :height="null" :queryForm="queryForm" :total="0" @currentChange="handleCurrentChange"
                            @sizeChange="handleSizeChange">
                        <Table :height="null" :total="0" :data="tableData">
                            <template>
                                <el-table-column prop="name" label="项目组名称" />
                                <el-table-column prop="age" label="项目负责人" />
                                <el-table-column prop="age" label="项目组成员" />
                                <el-table-column prop="age" label="创建时间" />
                                <el-table-column prop="teamName" label="项目组名称" />
                                <el-table-column prop="personCharge" label="项目负责人" />
                                <el-table-column prop="staffName" label="项目组成员" />
                                <el-table-column prop="createTime" label="创建时间" />
                            </template>
                        </Table>
@@ -31,9 +30,10 @@
                                    <div>报告编号</div>
                                </div>
                            </div>
                            <form-item prop="name" style="margin-top: 38px">
                                <el-input v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                            </form-item>
                            <el-form-item prop="reportCode" style="margin-top: 38px">
                                <el-input disabled v-model="form.reportCode" style="width: 100%;"
                                    placeholder="请输入报告编号" />
                            </el-form-item>
                            <div class="header-title" style="width: 100%;">
                                <div class="header-title-left">
@@ -41,9 +41,10 @@
                                    <div>报告名称</div>
                                </div>
                            </div>
                            <form-item prop="name" style="margin-top: 38px">
                                <el-input v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                            </form-item>
                            <el-form-item prop="reportName" style="margin-top: 38px">
                                <el-input disabled v-model="form.reportName" style="width: 100%;"
                                    placeholder="请输入报告名称" />
                            </el-form-item>
                            <div class="header-title" style="width: 100%;">
                                <div class="header-title-left">
@@ -51,9 +52,10 @@
                                    <div>报告正文</div>
                                </div>
                            </div>
                            <form-item prop="name" style="margin-top: 38px">
                                <ai-editor v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                            </form-item>
                            <el-form-item prop="reportText" style="margin-top: 38px">
                                <ai-editor :readOnly="true" :value="form.reportText" style="width: 100%;"
                                    placeholder="请输入报告正文" />
                            </el-form-item>
                        </el-form>
                    </template>
@@ -63,21 +65,20 @@
            <!-- 右侧审批流程 -->
            <div class="approval-flow">
                <div class="flow-content">
                    <approval-process :status="form.status" :submit-time="form.createTime" :approver="form.approver"
                        :approve-time="form.approveTime" />
                    <approval-process :processData="form.processData" />
                </div>
            </div>
        </div>
        <div class="approval-dialog-approve">
        <div class="approval-dialog-approve" v-if="!obj.isDetail">
            <el-row :span="24">
                <el-col :span="12">
                    <div class="status">
                        <div class="status-title">审批结果</div>
                        <div class="status-content">
                            <div class="resolve" :class="status == '1' && 'activeStatus'" @click.stop="status = 1">
                            <div class="resolve" :class="status == '2' && 'activeStatus'" @click.stop="status = 2">
                                通过
                            </div>
                            <div class="reject" :class="status == '2' && 'activeStatus'" @click.stop="status = 2">
                            <div class="reject" :class="status == '3' && 'activeStatus'" @click.stop="status = 3">
                                驳回
                            </div>
                        </div>
@@ -94,8 +95,8 @@
        </div>
        <div slot="footer" class="dialog-footer">
            <el-button @click="handleClose" >取 消</el-button>
            <el-button type="primary" @click="handleApprove" v-if="type === 'approve'">通过</el-button>
            <el-button @click="handleClose">{{obj.isDetail ? '关闭' : '取 消'}}</el-button>
            <el-button type="primary" @click="handleApprove" v-if="!obj.isDetail">通过</el-button>
        </div>
    </el-dialog>
</template>
@@ -103,6 +104,8 @@
<script>
import ApprovalProcess from '@/components/approvalProcess'
import AiEditor from '@/components/AiEditor'
import { getDetail } from '../../service';
export default {
    name: "ApprovalDialog",
@@ -119,27 +122,33 @@
            type: String,
            default: "approve", // approve-审批,view-查看
        },
        data: {
        obj: {
            type: Object,
            default: () => ({}),
            default: () => { },
        },
    },
    data() {
        return {
            form: {
                planName: "",
                planCode: "",
                stage: "",
                creator: "",
                reportCode: "",
                reportName: "",
                reportText: "",
                teamName: "",
                createBy: "",
                createTime: "",
                status: "",
                approvalComment: "",
                status: "pending",
                approver: "",
                approveTime: ""
                approveTime: "",
                processData: [],
                updateBy: "",
                auditRemark: "",
                auditPersonName: "",
                auditTime: ""
            },
            radio1: 1,
            tableData: [],
            rules: {},
            status: "1",
            status: "2",
            remark: "",
        };
    },
@@ -148,40 +157,101 @@
            return this.type === "approve" ? "审批" : "审批详情";
        },
    },
    watch: {
        data: {
            handler(val) {
                if (val) {
                    this.form = { ...val };
                }
            },
            immediate: true,
        },
    },
    methods: {
        open() {
            if (!this.obj.id) {
                this.$message.error('缺少必要参数');
                return;
            }
            getDetail(this.obj.id).then(res => {
                const data = res.data || res;
                this.form = {
                    ...this.form,
                    ...data,
                    processData: []
                };
                this.tableData = data.projectTeam ?
                    [{ ...data.projectTeam, staffName: data.staffNames || '' }] :
                    [];
                let processData = [];
                // 提交节点
                processData.push({
                    type: "primary",
                    mode: "list",
                    fields: [
                        { label: "提交人:", value: data.updateBy || "" },
                        { label: "提交时间:", value: data.createTime || "" },
                    ]
                });
                if (data.status == 2 || data.status == 3) {
                    processData.push({
                        type: data.status === 2 ? "primary" : "danger",
                        mode: "list",
                        fields: [
                            { label: "审批意见:", value: data.auditRemark || "" },
                            { label: "审核人:", value: data.auditPersonName || "" },
                            { label: "审核时间:", value: data.auditTime || "" },
                        ]
                    });
                } else {
                    processData.push({
                        type: "warning",
                        mode: "list",
                        fields: [
                            { label: "等待审核" },
                        ],
                    });
                }
                if (data.status == 2) {
                    processData.push({
                        type: "warning",
                        mode: "list",
                        fields: [{ label: "等待评定" }],
                    });
                }
                if (data.status == 3) {
                    processData.push({
                        type: "success",
                        mode: "list",
                        fields: [
                            { label: "已评定" },
                            { label: "评定人:", value: data.evaluatePersonName || "" },
                            { label: "评定时间:", value: data.evaluateTime || "" }
                        ],
                    });
                }
                this.form.processData = processData;
            }).catch(err => {
                this.$message.error('获取详情失败');
            });
        },
        handleClose() {
            this.$emit("close");
            this.form.approvalComment = "";
        },
        handleApprove() {
            if (!this.form.approvalComment) {
                this.$message.warning("请输入审批意见");
                return;
            }
            this.$emit("approve", {
                ...this.form,
                status: "approved",
                statuss: this.status,
                remark: this.remark
            });
        },
        handleReject() {
            if (!this.form.approvalComment) {
                this.$message.warning("请输入审批意见");
                return;
            }
            this.$emit("reject", {
                ...this.form,
                status: "rejected",
            });
        handleCurrentChange(page) {
            this.form.pageNum = page
            this.getList()
        },
        handleSizeChange(size) {
            this.form.pageSize = size
            this.getList()
        },
    },
};
laboratory/src/views/reportLibrary/feasibilityReport/index.vue
@@ -1,14 +1,27 @@
<template>
    <div class="list">
        <TableCustom :queryForm="queryForm" :total="total" @currentChange="handleCurrentChange"
            @sizeChange="handleSizeChange">
        <el-card class="header-box" v-if="roleType == 3">
            <div class="box-title">
                <img src="@/assets/public/notice.png" class="header-icon"> <span>设立课题规则</span>
            </div>
            <div class="header-content">
                <p>1、根据可研报告、产品构思设计的工艺研究路线,一条工艺路线设立一个课题。如果一个课题中有多个化合物需要开发研究,则每个化合物作为一个分题;分题归集到该课题中,最终形成课题报告。不同课题报告中的分题不能重复使用。
                </p>
                <p>2、在可行研究阶段,工艺开发升级,重新规划工艺研究路线,则以新规划的工艺路线方案来设定课题。</p>
            </div>
        </el-card>
        <TableCustom :tableData="tableData" :height="null" :total="total" @handleCurrentChange="handleCurrentChanges"
            @handleSizeChange="handleSizeChanges">
            <template #search>
                <el-form :model="form" :label-width="auto" inline>
                <el-form :model="form" label-width="auto" inline>
                    <el-form-item label="所属项目组:">
                        <el-input v-model="form.name" placeholder="请输入"></el-input>
                        <el-input v-model="form.teamName" placeholder="请输入"></el-input>
                    </el-form-item>
                    <el-form-item label="报告名称:">
                        <el-input v-model="form.name" placeholder="请输入"></el-input>
                        <el-input v-model="form.reportName" placeholder="请输入"></el-input>
                    </el-form-item>
                    <el-form-item label="报告编号:">
                        <el-input v-model="form.reportCode" placeholder="请输入"></el-input>
                    </el-form-item>
                    <el-form-item label="创建日期:">
                        <el-date-picker v-model="form.date" type="daterange" range-separator="至"
@@ -22,48 +35,66 @@
                        </el-select>
                    </el-form-item>
                    <el-form-item label="" style="margin-left: 63px;">
                        <el-button type="default">重置</el-button>
                        <el-button type="default" style="margin-right: 10px;">重置</el-button>
                        <el-button type="primary">查询</el-button>
                    </el-form-item>
                </el-form>
            </template>
            <template #setting>
                <div class="table-title">
                <el-button v-if="roleType == 3" @click="handleAddProject" class="el-icon-plus" type="primary">
                    新增可行报告</el-button>
                <div class="table-setting">
                    <div :class="!isDraft ? 'table-title' : 'table-tit'" @click="changeTab('')">
                    可行报告库
                </div>
                    <div v-if="roleType == 3" :class="!isDraft ? 'table-tit' : 'table-title'" @click="changeTab('-1')">
                        草稿箱
                    </div>
                </div>
            </template>
            <template #table>
                <el-table-column prop="name" label="所属项目组" />
                <el-table-column prop="age" label="报告编号" />
                <el-table-column prop="age" label="报告名称" />
                <el-table-column prop="age" label="创建人" />
                <el-table-column prop="age" label="创建时间" />
                <el-table-column prop="age" label="状态">
                <el-table-column prop="teamName" label="所属项目组" />
                <el-table-column prop="reportCode" label="报告编号" />
                <el-table-column prop="reportName" label="报告名称" />
                <el-table-column prop="createBy" label="创建人" />
                <el-table-column prop="createTime" label="创建时间" />
                <el-table-column prop="status" label="状态" v-if="!isDraft">
                    <template #default="{ row }">
                        <el-tag v-if="row.status == 1" type="success">待审核</el-tag>
                        <el-tag v-else-if="row.status == 0" type="success">已通过</el-tag>
                        <el-tag v-else type="danger">已驳回</el-tag>
                        <el-tag v-if="row.status == 1">待审核</el-tag>
                        <el-tag v-else-if="row.status == 2">待评定</el-tag>
                        <el-tag v-else-if="row.status == 4" type="danger">已驳回</el-tag>
                        <el-tag v-else-if="row.status == 3" type="success">已评定</el-tag>
                        <el-tag v-else-if="row.status == 5" type="info">已撤回</el-tag>
                    </template>
                </el-table-column>
                <el-table-column prop="age" label="操作">
                <el-table-column prop="options" label="操作">
                    <template #default="{ row }">
                        <el-button type="text">审核</el-button>
                        <el-button type="text">详情</el-button>
                        <el-button type="text" @click="handleApproval(row)"
                            v-if="row.status == 1 && [1, 2].includes(roleType)">审核</el-button>
                        <el-button type="text" @click="handleDetail(row)">详情</el-button>
                        <el-button type="text" @click="handleDelete(row)"
                            v-if="[4, 5].includes(row.status) && roleType == 3">删除</el-button>
                        <el-button type="text" @click="handleEdit(row)"
                            v-if="[4, 5].includes(row.status) && roleType == 3">编辑</el-button>
                        <el-button type="text" @click="handleRevoke(row)"
                            v-if="row.status == 1 && roleType == 3">撤销审批</el-button>
                    </template>
                </el-table-column>
            </template>
        </TableCustom>
        <Approval :visible="showApproval" @close="showApproval = false" />
        <Approval :visible="showApproval" @close="showApproval = false" :obj="rowData" @approve="handleApprove" />
        <ShowDelConfirm :show="showDelConfirm" @close="showDelConfirm = false" @confirm="handleDelConfirm" />
        <ShowDelConfirm :title="changeStatusTitle" :tip="changeStatusTip" :show="changeStatus"
            @close="changeStatus = false" @confirm="handleChangeStatusConfirm" />
    </div>
</template>
<script>
import Approval from './components/approval'
import { getDataList, audit, revokeAudit, deleteData } from './service'
export default {
    name: 'ProjectList',
@@ -72,68 +103,202 @@
    },
    data() {
        return {
            form: {
                name: ''
            },
            showApproval:false,
            showDelConfirm: false,
            rowId: '',
            changeStatus: false,
            showApproval: false,
            changeStatusTitle: '',
            changeStatusTip: '',
            queryForm: {
            tableData: [],
            isDraft: false,
            rowData: {},
            roleType: '', // 1 超级管理员 2 审批人 3 工艺工程师 4化验师 5实验员
            form: {
                pageSize: 10,
                pageNum: 1
                pageNum: 1,
                teamName: '',
                status: '',
                startTime: '',
                reportType: 2,
                reportName: '',
                reportCode: '',
                endTime: '',
                date: ''
            },
            total: 0
        }
    },
    mounted() {
        this.roleType = JSON.parse(sessionStorage.getItem('userInfo'))?.roleType
        console.log('adwqedwqeqwe', this.roleType);
        this.getList()
    },
    methods: {
        handleAddProject() {
        handleApproval(row) {
            this.rowData = row
            this.showApproval = true
        },
        handleDetail(row) {
            row.isDetail = true
            this.rowData = row
            this.showApproval = true
        },
        handleEdit(row) {
            this.$router.push({
                path: '/projectList/addProject'
                path: '/reportLibrary/editFeasibility',
                query: {
                    id: row.id
                }
            })
        },
        handleDel(row) {
        handleAddProject() {
            this.$router.push('/reportLibrary/addFeasibility')
        },
        changeTab(status) {
            if (status == -1) {
                this.isDraft = true
                this.form.pageNum = 1
            } else {
                this.form.pageNum = 1
                this.isDraft = false
                this.form.status = status
            }
            this.getList()
        },
        handleDelete(row) {
            this.rowId = row.id
            this.showDelConfirm = true
        },
        handleDelConfirm() {
            this.showDelConfirm = false
            this.msgsuccess('删除成功')
            this.rowId = ''
            this.getList()
        },
        handleChangeStatus(row, status) {
        handleRevoke(row) {
            this.rowId = row.id
            this.changeStatusTitle = status == 1 ? '确认要封存这个项目组吗?' : '确认要解封该项目组吗?'
            this.changeStatusTip = status == 1 ? '封存后项目组内人员看不到数据,审批人仍然可见数据。' : '解封后项目组内人员数据恢复。'
            this.changeStatusTitle = '确认要撤销审批吗?'
            this.changeStatusTip = '撤销审批后,可研报告将被撤销。'
            this.changeStatus = true
        },
        handleDelConfirm() {
            deleteData({ id: this.rowId }).then(res => {
                this.showDelConfirm = false
                this.$message.success('删除成功')
                this.rowId = ''
                this.getList()
            })
        },
        handleChangeStatusConfirm() {
            revokeAudit({ id: this.rowId }).then(res => {
            this.changeStatus = false
            this.msgsuccess('操作成功')
                this.$message.success('操作成功')
            this.rowId = ''
            this.changeStatusTitle = ''
            this.changeStatusTip = ''
            this.getList()
            })
        },
        handleCurrentChange(page) {
            this.queryForm.pageNum = page
        handleCurrentChanges(page) {
            this.form.pageNum = page
            this.getList()
        },
        handleSizeChange(size) {
            this.queryForm.pageSize = size
        handleSizeChanges(size) {
            this.form.pageSize = size
            this.getList()
        },
        getList() {
            let data = {}
            if (this.isDraft) {
                data = {
                    ...this.form,
                    status: -1
                }
            } else {
                data = this.form
            }
            getDataList(data).then(res => {
                if (res.code === 200) {
                    this.tableData = res.data.records || []
                    this.total = res.data.total || 0
                }
            })
        },
        handleApprove(data) {
            let params = {
                id: data.id,
                auditStatus: data.statuss,
                auditRemark: data.remark
            }
            audit({ ...params }).then(res => {
                if (res.code === 200) {
                    this.$message.success('审核成功')
                    this.showApproval = false
                    this.getList()
                }
            })
        }
    }
}
</script>
<style scoped lang="less">
.el-icon-plus {
    margin-bottom: 20px;
}
.header-content {
    font-family: PingFangSC, PingFang SC;
    font-weight: 400;
    font-size: 14px;
    color: rgba(0, 0, 0, 0.88);
    margin-left: 30px;
}
.box-title {
    font-family: SourceHanSansCN, SourceHanSansCN;
    font-weight: bold;
    font-size: 18px;
    color: #222222;
    line-height: 27px;
    display: flex;
    align-items: center;
}
.header-icon {
    width: 20px;
    height: 20px;
    margin-right: 10px;
}
.header-box {
    border-radius: 16px;
    margin-bottom: 30px;
}
.table-setting {
    display: flex;
    gap: 14px;
}
.table-tit {
    background: #FAFAFC;
    border-radius: 8px 8px 0px 0px;
    border: 1px solid #DCDFE6;
    width: 166px;
    height: 50px;
    background: #FFFFFF;
    border-radius: 8px 8px 0px 0px;
    display: flex;
    align-items: center;
    justify-content: center;
    // margin-bottom: 21px;
    font-family: SourceHanSansCN, SourceHanSansCN;
    font-weight: bold;
    font-size: 18px;
    color: #606266;
    line-height: 27px;
}
.list {
    height: 100%;
}
@@ -147,7 +312,7 @@
    display: flex;
    align-items: center;
    justify-content: center;
    margin-bottom: 21px;
    // margin-bottom: 21px;
    font-family: SourceHanSansCN, SourceHanSansCN;
    font-weight: bold;
    font-size: 18px;
laboratory/src/views/reportLibrary/feasibilityReport/service.js
New file
@@ -0,0 +1,44 @@
import axios from '@/utils/request';
// 查询列表
export function getDataList(data) {
  return axios.post('/api/t-feasibility-study-report/pageList', { ...data })
}
// 添加
export function addData(data) {
  console.log('adwqedwqeqwe//////////////',data);
  return axios.post('/api/t-feasibility-study-report/add', { ...data })
}
//修改
export function editData(data) {
  return axios.post('/api/t-feasibility-study-report/update', { ...data })
}
//获取详情
export function getDetail(id) {
  return axios.get(`/open/t-feasibility-study-report/getDetailById?id=${id}`)
}
//审核
export function audit(data) {
  console.log(data)
  return axios.post('/api/t-feasibility-study-report/auditReport', { ...data })
}
//撤销审批
export function revokeAudit(data) {
  return axios.put(`/open/t-feasibility-study-report/revokedReport?id=${data.id}`)
}
//删除
export function deleteData(data) {
  return axios.delete(`/open/t-feasibility-study-report/deleteById?id=${data.id}`)
}
laboratory/src/views/reportLibrary/feasibilityStudy/add.vue
@@ -1,6 +1,6 @@
<template>
    <div>
        <Card>
    <div class="add-container" :loading="loading">
        <Card v-loading="loading">
            <div class="header-title" style="width: 100%;">
                <div class="header-title-left">
                    <img src="@/assets/public/headercard.png" />
@@ -38,7 +38,7 @@
                    </div>
                </div>
                <el-form-item prop="reportName" style="margin-top: 38px">
                    <el-input v-model="form.reportName" style="width: 100%;" placeholder="请输入报告编号" />
                    <el-input v-model="form.reportName" style="width: 100%;" placeholder="请输入报告名称" />
                </el-form-item>
                <div class="header-title" style="width: 100%;">
@@ -48,7 +48,8 @@
                    </div>
                </div>
                <el-form-item prop="reportText" style="margin-top: 38px">
                    <ai-editor v-model="form.reportText" style="width: 100%;" placeholder="请输入报告编号" />
                    <ai-editor ref="materialEditor" :value="form.reportText" style="width: 100%;"
                        placeholder="请输入报告正文" />
                </el-form-item>
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
@@ -63,8 +64,8 @@
                </el-form-item>
                <div class="end-btn" style="margin-top: 38px">
                    <el-button type="primary" @click="submit">发送</el-button>
                    <el-button type="default" @clice="save">存草稿</el-button>
                    <el-button type="primary" @click="submit" :loading="loading">发送</el-button>
                    <el-button type="default" @click="save" :loading="loading">存草稿</el-button>
                </div>
            </el-form>
        </Card>
@@ -76,6 +77,7 @@
import { Card } from 'element-ui';
import AiEditor from '@/components/AiEditor'
import chooseProject from '@/components/chooseProject'
import { addData, getDetail, editData } from './service'
export default {
    components: {
        AiEditor,
@@ -83,44 +85,119 @@
    },
    data() {
        return {
            loading: false,
            form: {
                planName: "",
                planCode: "",
                stage: "",
                creator: "",
                createTime: "",
                approvalComment: "",
                status: "pending",
                approver: "",
                approveTime: ""
                reportCode: "",
                reportName: "",
                reportText: ""
            },
            tableData: [],
            fileList: [], // 附件列表
            showChoose: false,
            radio1: 1,
            rules: {},
            status: "1",
            remark: "",
            queryForm: {
            }
            rules: {
                reportCode: [
                    { required: true, message: '请输入报告编号', trigger: 'blur' }
                ],
                reportName: [
                    { required: true, message: '请输入报告名称', trigger: 'blur' }
                ],
            },
            queryForm: {}
        }
    },
    mounted() {
        if (this.$route.query.id) {
            this.getDetail()
        }
    },
    methods: {
        getDetail() {
            getDetail(this.$route.query.id).then(res => {
                this.form = res
                this.tableData = [{ ...res.projectTeam, staffName: res.staffNames }]
                this.fileList = res.fileList
            })
        },
        //获取选择项目组数据
        getProjectData(data) {
            console.log('4458454', data);
            this.tableData = [data]
            this.$forceUpdate()
            console.log('dsadasdsad', this.tableData);
            this.showChoose = false
        },
        submit(){
            console.log(this.$refs.materialEditor.getContent());
            if (this.tableData.length == 0) {
                this.$message.error('请选择项目组')
                return
            }
            
            this.$refs.form.validate((valid) => {
                if (this.$refs.materialEditor.getContent() == '<p></p>') {
                    this.$message.error('请输入报告正文')
                    return
                }
                let data = {
                    ...this.form,
                    reportType: 1,
                    status: 1,
                    reportText: this.$refs.materialEditor.getContent(),
                    teamId: this.tableData[0].id
                }
                if (valid) {
                    this.loading = true
                    if (this.$route.query.id) {
                        editData({ ...data, id: this.$route.query.id }).then(res => {
                            if (res.code === 200) {
                                this.$message.success('修改成功')
                                this.$router.back()
                            } else {
                                this.$message.error(res.message)
                            }
                        })
                    } else {
                        addData({ ...data }).then(res => {
                            if (res.code === 200) {
                                this.$message.success('发布成功')
                                this.$router.back()
                            } else {
                                this.$message.error(res.message)
                            }
                        }).finally(() => {
                            this.loading = false
                        })
                    }
                }
            })
        },
        save(){
            this.$refs.form.validate((valid) => {
                let data = {
                    ...this.form,
                    reportType: 1,
                    status: -1,
                    reportText: this.$refs.materialEditor.getContent(),
                    teamId: this.tableData[0].id
                }
                delete data.id
                if (valid) {
                    this.loading = true
                    addData({ ...data }).then(res => {
                        if (res.code === 200) {
                            this.$message.success('提交成功')
                            this.$router.back()
                        } else {
                            this.$message.error(res.message)
                        }
                    }).finally(() => {
                        this.loading = false
                    })
                }
            })
        },
    },
}
laboratory/src/views/reportLibrary/feasibilityStudy/components/approval/index.vue
@@ -1,7 +1,7 @@
<template>
    <el-dialog :title="dialogTitle"  :visible.sync="visible" width="80%" po :close-on-click-modal="false"
    <el-dialog :title="dialogTitle" :visible.sync="visible" width="80%" @open="open" po :close-on-click-modal="false"
        @close="handleClose">
        <div class="approval-dialog">
        <div class="approval-dialog" :style="{height: obj.isDetail ? '50vh' : '40vh'}">
            <!-- 左侧审批内容 -->
            <div class="approval-content">
                <Card class="approval-content-card">
@@ -12,13 +12,12 @@
                                <div>所属项目组</div>
                            </div>
                        </div>
                        <Table :height="null" :total="0" @handleCurrentChange="handleCurrentChange"
                            @handleSizeChange="handleSizeChange">
                        <Table :height="null" :total="0" :data="tableData">
                            <template>
                                <el-table-column prop="name" label="项目组名称" />
                                <el-table-column prop="age" label="项目负责人" />
                                <el-table-column prop="age" label="项目组成员" />
                                <el-table-column prop="age" label="创建时间" />
                                <el-table-column prop="teamName" label="项目组名称" />
                                <el-table-column prop="personCharge" label="项目负责人" />
                                <el-table-column prop="staffName" label="项目组成员" />
                                <el-table-column prop="createTime" label="创建时间" />
                            </template>
                        </Table>
@@ -31,9 +30,10 @@
                                    <div>报告编号</div>
                                </div>
                            </div>
                            <form-item prop="name" style="margin-top: 38px">
                                <el-input v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                            </form-item>
                            <el-form-item prop="reportCode" style="margin-top: 38px">
                                <el-input disabled v-model="form.reportCode" style="width: 100%;"
                                    placeholder="请输入报告编号" />
                            </el-form-item>
                            <div class="header-title" style="width: 100%;">
                                <div class="header-title-left">
@@ -41,9 +41,10 @@
                                    <div>报告名称</div>
                                </div>
                            </div>
                            <form-item prop="name" style="margin-top: 38px">
                                <el-input v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                            </form-item>
                            <el-form-item prop="reportName" style="margin-top: 38px">
                                <el-input disabled v-model="form.reportName" style="width: 100%;"
                                    placeholder="请输入报告名称" />
                            </el-form-item>
                            <div class="header-title" style="width: 100%;">
                                <div class="header-title-left">
@@ -51,9 +52,10 @@
                                    <div>报告正文</div>
                                </div>
                            </div>
                            <form-item prop="name" style="margin-top: 38px">
                                <ai-editor v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                            </form-item>
                            <el-form-item prop="reportText" style="margin-top: 38px">
                                <ai-editor :readOnly="true" :value="form.reportText" style="width: 100%;"
                                    placeholder="请输入报告正文" />
                            </el-form-item>
                        </el-form>
                    </template>
@@ -63,21 +65,20 @@
            <!-- 右侧审批流程 -->
            <div class="approval-flow">
                <div class="flow-content">
                    <approval-process :status="form.status" :submit-time="form.createTime" :approver="form.approver"
                        :approve-time="form.approveTime" />
                    <approval-process :processData="form.processData" />
                </div>
            </div>
        </div>
        <div class="approval-dialog-approve">
        <div class="approval-dialog-approve" v-if="!obj.isDetail">
            <el-row :span="24">
                <el-col :span="12">
                    <div class="status">
                        <div class="status-title">审批结果</div>
                        <div class="status-content">
                            <div class="resolve" :class="status == '1' && 'activeStatus'" @click.stop="status = 1">
                            <div class="resolve" :class="status == '2' && 'activeStatus'" @click.stop="status = 2">
                                通过
                            </div>
                            <div class="reject" :class="status == '2' && 'activeStatus'" @click.stop="status = 2">
                            <div class="reject" :class="status == '3' && 'activeStatus'" @click.stop="status = 3">
                                驳回
                            </div>
                        </div>
@@ -94,8 +95,8 @@
        </div>
        <div slot="footer" class="dialog-footer">
            <el-button @click="handleClose" >取 消</el-button>
            <el-button type="primary" @click="handleApprove" v-if="type === 'approve'">通过</el-button>
            <el-button @click="handleClose">{{obj.isDetail ? '关闭' : '取 消'}}</el-button>
            <el-button type="primary" @click="handleApprove" v-if="!obj.isDetail">通过</el-button>
        </div>
    </el-dialog>
</template>
@@ -103,6 +104,8 @@
<script>
import ApprovalProcess from '@/components/approvalProcess'
import AiEditor from '@/components/AiEditor'
import { getDetail } from '../../service';
export default {
    name: "ApprovalDialog",
@@ -119,27 +122,33 @@
            type: String,
            default: "approve", // approve-审批,view-查看
        },
        data: {
        obj: {
            type: Object,
            default: () => ({}),
            default: () => { },
        },
    },
    data() {
        return {
            form: {
                planName: "",
                planCode: "",
                stage: "",
                creator: "",
                reportCode: "",
                reportName: "",
                reportText: "",
                teamName: "",
                createBy: "",
                createTime: "",
                status: "",
                approvalComment: "",
                status: "pending",
                approver: "",
                approveTime: ""
                approveTime: "",
                processData: [],
                updateBy: "",
                auditRemark: "",
                auditPersonName: "",
                auditTime: ""
            },
            radio1: 1,
            tableData: [],
            rules: {},
            status: "1",
            status: "2",
            remark: "",
        };
    },
@@ -148,39 +157,91 @@
            return this.type === "approve" ? "审批" : "审批详情";
        },
    },
    watch: {
        data: {
            handler(val) {
                if (val) {
                    this.form = { ...val };
                }
            },
            immediate: true,
        },
    },
    methods: {
        open() {
            if (!this.obj.id) {
                this.$message.error('缺少必要参数');
                return;
            }
            getDetail(this.obj.id).then(res => {
                const data = res.data || res;
                this.form = {
                    ...this.form,
                    ...data,
                    processData: []
                };
                this.tableData = data.projectTeam ?
                    [{ ...data.projectTeam, staffName: data.staffNames || '' }] :
                    [];
                let processData = [];
                // 提交节点
                processData.push({
                    type: "primary",
                    mode: "list",
                    fields: [
                        { label: "提交人:", value: data.updateBy || "" },
                        { label: "提交时间:", value: data.createTime || "" },
                    ]
                });
                if (data.status == 2 || data.status == 3) {
                    processData.push({
                        type: data.status === 2 ? "primary" : "danger",
                        mode: "list",
                        fields: [
                            { label: "审批意见:", value: data.auditRemark || "" },
                            { label: "审核人:", value: data.auditPersonName || "" },
                            { label: "审核时间:", value: data.auditTime || "" },
                        ]
                    });
                } else {
                    processData.push({
                        type: "warning",
                        mode: "list",
                        fields: [
                            { label: "等待审核" },
                        ],
                    });
                }
                if (data.status == 2) {
                    processData.push({
                        type: "warning",
                        mode: "list",
                        fields: [{ label: "等待评定" }],
                    });
                }
                if (data.status == 3) {
                    processData.push({
                        type: "success",
                        mode: "list",
                        fields: [
                            { label: "已评定" },
                            { label: "评定人:", value: data.evaluatePersonName || "" },
                            { label: "评定时间:", value: data.evaluateTime || "" }
                        ],
                    });
                }
                this.form.processData = processData;
            }).catch(err => {
                this.$message.error('获取详情失败');
            });
        },
        handleClose() {
            this.$emit("close");
            this.form.approvalComment = "";
        },
        handleApprove() {
            if (!this.form.approvalComment) {
                this.$message.warning("请输入审批意见");
                return;
            }
            this.$emit("approve", {
                ...this.form,
                status: "approved",
            });
        },
        handleReject() {
            if (!this.form.approvalComment) {
                this.$message.warning("请输入审批意见");
                return;
            }
            this.$emit("reject", {
                ...this.form,
                status: "rejected",
                statuss: this.status,
                remark: this.remark
            });
        },
        
laboratory/src/views/reportLibrary/feasibilityStudy/index.vue
@@ -1,6 +1,6 @@
<template>
    <div class="list">
        <el-card class="header-box">
        <el-card class="header-box" v-if="roleType == 3">
            <div class="box-title">
                <img src="@/assets/public/notice.png" class="header-icon"> <span>设立课题规则</span>
            </div>
@@ -10,7 +10,7 @@
                <p>2、在可行研究阶段,工艺开发升级,重新规划工艺研究路线,则以新规划的工艺路线方案来设定课题。</p>
            </div>
        </el-card>
        <TableCustom :tableData="tableData" :total="total" @handleCurrentChange="handleCurrentChanges"
        <TableCustom :tableData="tableData" :height="null" :total="total" @handleCurrentChange="handleCurrentChanges"
            @handleSizeChange="handleSizeChanges">
            <template #search>
                <el-form :model="form" label-width="auto" inline>
@@ -41,13 +41,13 @@
                </el-form>
            </template>
            <template #setting>
                <el-button @click="handleAddProject" class="el-icon-plus" type="primary">
                <el-button v-if="roleType == 3" @click="handleAddProject" class="el-icon-plus" type="primary">
                    新增可研报告</el-button>
                <div class="table-setting">
                    <div class="table-title">
                    <div :class="!isDraft ? 'table-title' : 'table-tit'" @click="changeTab('')">
                        可研报告库
                    </div>
                    <div class="table-tit">
                    <div v-if="roleType == 3" :class="!isDraft ? 'table-tit' : 'table-title'" @click="changeTab('-1')">
                        草稿箱
                    </div>
                </div>
@@ -59,23 +59,33 @@
                <el-table-column prop="reportName" label="报告名称" />
                <el-table-column prop="createBy" label="创建人" />
                <el-table-column prop="createTime" label="创建时间" />
                <el-table-column prop="status" label="状态">
                <el-table-column prop="status" label="状态" v-if="!isDraft">
                    <template #default="{ row }">
                        <el-tag v-if="row.status == 1" type="success">待审核</el-tag>
                        <el-tag v-else-if="row.status == 0" type="success">已通过</el-tag>
                        <el-tag v-else type="danger">已驳回</el-tag>
                        <el-tag v-if="row.status == 1">待审核</el-tag>
                        <el-tag v-else-if="row.status == 2">待评定</el-tag>
                        <el-tag v-else-if="row.status == 4" type="danger">已驳回</el-tag>
                        <el-tag v-else-if="row.status == 3" type="success">已评定</el-tag>
                        <el-tag v-else-if="row.status == 5" type="info">已撤回</el-tag>
                    </template>
                </el-table-column>
                <el-table-column prop="options" label="操作">
                    <template #default="{ row }">
                        <el-button type="text">审核</el-button>
                        <el-button type="text">详情</el-button>
                        <el-button type="text" @click="handleApproval(row)"
                            v-if="row.status == 1 && [1, 2].includes(roleType)">审核</el-button>
                        <el-button type="text" @click="handleDetail(row)">详情</el-button>
                        <el-button type="text" @click="handleDelete(row)"
                            v-if="[4, 5].includes(row.status) && roleType == 3">删除</el-button>
                        <el-button type="text" @click="handleEdit(row)"
                            v-if="[4, 5].includes(row.status) && roleType == 3">编辑</el-button>
                        <el-button type="text" @click="handleRevoke(row)"
                            v-if="row.status == 1 && roleType == 3">撤销审批</el-button>
                    </template>
                </el-table-column>
            </template>
        </TableCustom>
        <Approval :visible="showApproval" @close="showApproval = false" />
        <Approval :visible="showApproval" @close="showApproval = false" :obj="rowData" @approve="handleApprove" />
        <ShowDelConfirm :show="showDelConfirm" @close="showDelConfirm = false" @confirm="handleDelConfirm" />
        <ShowDelConfirm :title="changeStatusTitle" :tip="changeStatusTip" :show="changeStatus"
            @close="changeStatus = false" @confirm="handleChangeStatusConfirm" />
@@ -84,7 +94,7 @@
<script>
import Approval from './components/approval'
import { getDataList } from './service'
import { getDataList, audit, revokeAudit, deleteData } from './service'
export default {
    name: 'ProjectList',
@@ -93,9 +103,6 @@
    },
    data() {
        return {
            form: {
                name: ''
            },
            showDelConfirm: false,
            rowId: '',
            changeStatus: false,
@@ -103,6 +110,9 @@
            changeStatusTitle: '',
            changeStatusTip: '',
            tableData:[],
            isDraft: false,
            rowData: {},
            roleType: '', // 1 超级管理员 2 审批人 3 工艺工程师 4化验师 5实验员
            form: {
                pageSize: 10,
                pageNum: 1,
@@ -120,36 +130,71 @@
    },
    mounted() {
        this.roleType = JSON.parse(sessionStorage.getItem('userInfo'))?.roleType
        console.log('adwqedwqeqwe', this.roleType);
        this.getList()
    },
    
    methods: {
        handleApproval(row) {
            this.rowData = row
            this.showApproval = true
        },
        handleDetail(row) {
            row.isDetail = true
            this.rowData = row
            this.showApproval = true
        },
        handleEdit(row) {
            this.$router.push({
                path: '/reportLibrary/edit',
                query: {
                    id: row.id
                }
            })
        },
        handleAddProject() {
            this.$router.push('/reportLibrary/add')
        },
        handleDel(row) {
        changeTab(status) {
            if (status == -1) {
                this.isDraft = true
                this.form.pageNum = 1
            } else {
                this.form.pageNum = 1
                this.isDraft = false
                this.form.status = status
            }
            this.getList()
        },
        handleDelete(row) {
            this.rowId = row.id
            this.showDelConfirm = true
        },
        handleDelConfirm() {
            this.showDelConfirm = false
            this.msgsuccess('删除成功')
            this.rowId = ''
            this.getList()
        },
        handleChangeStatus(row, status) {
        handleRevoke(row) {
            this.rowId = row.id
            this.changeStatusTitle = status == 1 ? '确认要封存这个项目组吗?' : '确认要解封该项目组吗?'
            this.changeStatusTip = status == 1 ? '封存后项目组内人员看不到数据,审批人仍然可见数据。' : '解封后项目组内人员数据恢复。'
            this.changeStatusTitle = '确认要撤销审批吗?'
            this.changeStatusTip = '撤销审批后,可研报告将被撤销。'
            this.changeStatus = true
        },
        handleDelConfirm() {
            deleteData({ id: this.rowId }).then(res => {
                this.showDelConfirm = false
                this.$message.success('删除成功')
                this.rowId = ''
                this.getList()
            })
        },
        handleChangeStatusConfirm() {
            revokeAudit({ id: this.rowId }).then(res => {
            this.changeStatus = false
            this.msgsuccess('操作成功')
                this.$message.success('操作成功')
            this.rowId = ''
            this.changeStatusTitle = ''
            this.changeStatusTip = ''
            this.getList()
            })
        },
        handleCurrentChanges(page) {
            this.form.pageNum = page
@@ -160,12 +205,36 @@
            this.getList()
        },
        getList() {
            getDataList(this.form).then(res => {
                console.log('sdasdasdqweqw',res);
                this.tableData = res.records || []
                this.total = res.total || 0
            let data = {}
            if (this.isDraft) {
                data = {
                    ...this.form,
                    status: -1
                }
            } else {
                data = this.form
            }
            getDataList(data).then(res => {
                if (res.code === 200) {
                    this.tableData = res.data.records || []
                    this.total = res.data.total || 0
                }
            })
        },
        handleApprove(data) {
            let params = {
                id: data.id,
                auditStatus: data.statuss,
                auditRemark: data.remark
            }
            audit({ ...params }).then(res => {
                if (res.code === 200) {
                    this.$message.success('审核成功')
                    this.showApproval = false
                    this.getList()
                }
            })
        }
    }
}
@@ -175,6 +244,7 @@
.el-icon-plus{
    margin-bottom: 20px;
}
.header-content {
    font-family: PingFangSC, PingFang SC;
    font-weight: 400;
@@ -225,7 +295,7 @@
    font-family: SourceHanSansCN, SourceHanSansCN;
    font-weight: bold;
    font-size: 18px;
    color: #049C9A;
    color: #606266;
    line-height: 27px;
}
laboratory/src/views/reportLibrary/feasibilityStudy/service.js
@@ -1,6 +1,43 @@
import axios from '@/utils/request';
// 添加项目课题方案
// 查询列表
export function getDataList(data) {
  return axios.post('/api/t-feasibility-study-report/pageList', { ...data })
}
// 添加
export function addData(data) {
  return axios.post('/api/t-feasibility-study-report/add', { ...data })
}
//修改
export function editData(data) {
  return axios.post('/api/t-feasibility-study-report/update', { ...data })
}
//获取详情
export function getDetail(id) {
  return axios.get(`/open/t-feasibility-study-report/getDetailById?id=${id}`)
}
//审核
export function audit(data) {
  console.log(data)
  return axios.post('/api/t-feasibility-study-report/auditReport', { ...data })
}
//撤销审批
export function revokeAudit(data) {
  return axios.put(`/open/t-feasibility-study-report/revokedReport?id=${data.id}`)
}
//删除
export function deleteData(data) {
  return axios.delete(`/open/t-feasibility-study-report/deleteById?id=${data.id}`)
}
laboratory/src/views/reportLibrary/processDevelopment/add.vue
New file
@@ -0,0 +1,280 @@
<template>
    <div class="add-container" :loading="loading">
        <Card v-loading="loading">
            <div class="header-title" style="width: 100%;">
                <div class="header-title-left">
                    <img src="@/assets/public/headercard.png" />
                    <div>所属项目组</div>
                </div>
                <div class="header-title-right">
                    <el-button @click="showChoose = true" class="el-icon-circle-plus-outline" type="primary">
                        选择项目组</el-button>
                </div>
            </div>
            <Table :height="null" :data="tableData" :queryForm="queryForm" :total="0">
                <template>
                    <el-table-column prop="teamName" label="项目组名称" />
                    <el-table-column prop="personCharge" label="项目负责人" />
                    <el-table-column prop="staffName" label="项目组成员" />
                    <el-table-column prop="createTime" label="创建时间" />
                </template>
            </Table>
            <el-form ref="form" :model="form" :rules="rules" inline label-position="top" style="margin-top: 38px">
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div>报告编号</div>
                    </div>
                </div>
                <el-form-item prop="reportCode" style="margin-top: 38px">
                    <el-input v-model="form.reportCode" style="width: 100%;" placeholder="请输入报告编号" />
                </el-form-item>
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div>报告名称</div>
                    </div>
                </div>
                <el-form-item prop="reportName" style="margin-top: 38px">
                    <el-input v-model="form.reportName" style="width: 100%;" placeholder="请输入报告名称" />
                </el-form-item>
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div>报告正文</div>
                    </div>
                </div>
                <el-form-item prop="reportText" style="margin-top: 38px">
                    <ai-editor ref="materialEditor" :value="form.reportText" style="width: 100%;"
                        placeholder="请输入报告正文" />
                </el-form-item>
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div class="noRequire">附件</div>
                    </div>
                </div>
                <el-form-item prop="name" style="margin-top: 38px">
                    <el-upload action="https://jsonplaceholder.typicode.com/posts/" :file-list="fileList">
                        <el-button size="small" type="primary">点击上传</el-button>
                    </el-upload>
                </el-form-item>
                <div class="end-btn" style="margin-top: 38px">
                    <el-button type="primary" @click="submit" :loading="loading">发送</el-button>
                    <el-button type="default" @click="save" :loading="loading">存草稿</el-button>
                </div>
            </el-form>
        </Card>
        <chooseProject @submit="getProjectData" :show="showChoose" @close="showChoose = false"></chooseProject>
    </div>
</template>
<script>
import { Card } from 'element-ui';
import AiEditor from '@/components/AiEditor'
import chooseProject from '@/components/chooseProject'
import { addData, getDetail, editData } from './service'
export default {
    components: {
        AiEditor,
        chooseProject
    },
    data() {
        return {
            loading: false,
            form: {
                reportCode: "",
                reportName: "",
                reportText: ""
            },
            tableData: [],
            fileList: [], // 附件列表
            showChoose: false,
            rules: {
                reportCode: [
                    { required: true, message: '请输入报告编号', trigger: 'blur' }
                ],
                reportName: [
                    { required: true, message: '请输入报告名称', trigger: 'blur' }
                ],
            },
            queryForm: {}
        }
    },
    mounted() {
        if (this.$route.query.id) {
            this.getDetail()
        }
    },
    methods: {
        getDetail() {
            getDetail(this.$route.query.id).then(res => {
                this.form = res
                this.tableData = [{ ...res.projectTeam, staffName: res.staffNames }]
                this.fileList = res.fileList
            })
        },
        //获取选择项目组数据
        getProjectData(data) {
            this.tableData = [data]
            this.$forceUpdate()
            this.showChoose = false
        },
        submit() {
            console.log(this.$refs.materialEditor.getContent());
            if (this.tableData.length == 0) {
                this.$message.error('请选择项目组')
                return
            }
            this.$refs.form.validate((valid) => {
                if (this.$refs.materialEditor.getContent() == '<p></p>') {
                    this.$message.error('请输入报告正文')
                    return
                }
                let data = {
                    ...this.form,
                    reportType: 3,
                    status: 1,
                    reportText: this.$refs.materialEditor.getContent(),
                    teamId: this.tableData[0].id
                }
                if (valid) {
                    this.loading = true
                    if (this.$route.query.id) {
                        editData({ ...data, id: this.$route.query.id }).then(res => {
                            if (res.code === 200) {
                                this.$message.success('修改成功')
                                this.$router.back()
                            } else {
                                this.$message.error(res.message)
                            }
                        })
                    } else {
                        addData({ ...data }).then(res => {
                            if (res.code === 200) {
                                this.$message.success('发布成功')
                                this.$router.back()
                            } else {
                                this.$message.error(res.message)
                            }
                        }).finally(() => {
                            this.loading = false
                        })
                    }
                }
            })
        },
        save() {
            this.$refs.form.validate((valid) => {
                let data = {
                    ...this.form,
                    reportType: 3,
                    status: -1,
                    reportText: this.$refs.materialEditor.getContent(),
                    teamId: this.tableData[0].id
                }
                delete data.id
                if (valid) {
                    this.loading = true
                    addData({ ...data }).then(res => {
                        if (res.code === 200) {
                            this.$message.success('提交成功')
                            this.$router.back()
                        } else {
                            this.$message.error(res.message)
                        }
                    }).finally(() => {
                        this.loading = false
                    })
                }
            })
        },
    },
}
</script>
<style lang="less" scoped>
.header-title {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    margin-bottom: 20px;
    gap: 13px;
    .header-title-left {
        display: flex;
        align-items: center;
        gap: 13px;
        margin-top: 38px;
        img {
            width: 12px;
            height: 19px;
        }
        div {
            flex-shrink: 0;
            font-weight: bold;
            font-size: 18px;
            color: #222222;
            line-height: 27px;
            font-family: "Source Han Sans CN Bold Bold";
            &:before {
                content: "*";
                color: #f56c6c;
                margin-right: 4px;
            }
        }
        .noRequire:before {
            content: unset;
            // color: #f56c6c;
            // margin-right: 4px;
        }
        span {
            flex-shrink: 0;
            font-weight: bold;
            font-size: 18px;
            color: #222222;
            line-height: 27px;
            font-family: "Source Han Sans CN Bold Bold";
        }
    }
    .header-title-left :first-child {
        margin-top: 0;
    }
}
.header-title:first-child {
    .header-title-left {
        margin-top: 0;
    }
}
.end-btn {
    display: flex;
    align-items: center;
    gap: 10px;
    button {
        width: 180px;
        height: 36px;
        // background: #409EFF;
    }
}
</style>
laboratory/src/views/reportLibrary/processDevelopment/components/approval/index.vue
@@ -1,7 +1,7 @@
<template>
    <el-dialog :title="dialogTitle"  :visible.sync="visible" width="80%" po :close-on-click-modal="false"
    <el-dialog :title="dialogTitle" :visible.sync="visible" width="80%" @open="open" po :close-on-click-modal="false"
        @close="handleClose">
        <div class="approval-dialog">
        <div class="approval-dialog" :style="{height: obj.isDetail ? '50vh' : '40vh'}">
            <!-- 左侧审批内容 -->
            <div class="approval-content">
                <Card class="approval-content-card">
@@ -12,13 +12,12 @@
                                <div>所属项目组</div>
                            </div>
                        </div>
                        <Table :height="null" :queryForm="queryForm" :total="0" @currentChange="handleCurrentChange"
                            @sizeChange="handleSizeChange">
                        <Table :height="null" :total="0" :data="tableData">
                            <template>
                                <el-table-column prop="name" label="项目组名称" />
                                <el-table-column prop="age" label="项目负责人" />
                                <el-table-column prop="age" label="项目组成员" />
                                <el-table-column prop="age" label="创建时间" />
                                <el-table-column prop="teamName" label="项目组名称" />
                                <el-table-column prop="personCharge" label="项目负责人" />
                                <el-table-column prop="staffName" label="项目组成员" />
                                <el-table-column prop="createTime" label="创建时间" />
                            </template>
                        </Table>
@@ -31,9 +30,10 @@
                                    <div>报告编号</div>
                                </div>
                            </div>
                            <form-item prop="name" style="margin-top: 38px">
                                <el-input v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                            </form-item>
                            <el-form-item prop="reportCode" style="margin-top: 38px">
                                <el-input disabled v-model="form.reportCode" style="width: 100%;"
                                    placeholder="请输入报告编号" />
                            </el-form-item>
                            <div class="header-title" style="width: 100%;">
                                <div class="header-title-left">
@@ -41,9 +41,10 @@
                                    <div>报告名称</div>
                                </div>
                            </div>
                            <form-item prop="name" style="margin-top: 38px">
                                <el-input v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                            </form-item>
                            <el-form-item prop="reportName" style="margin-top: 38px">
                                <el-input disabled v-model="form.reportName" style="width: 100%;"
                                    placeholder="请输入报告名称" />
                            </el-form-item>
                            <div class="header-title" style="width: 100%;">
                                <div class="header-title-left">
@@ -51,9 +52,10 @@
                                    <div>报告正文</div>
                                </div>
                            </div>
                            <form-item prop="name" style="margin-top: 38px">
                                <ai-editor v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                            </form-item>
                            <el-form-item prop="reportText" style="margin-top: 38px">
                                <ai-editor :readOnly="true" :value="form.reportText" style="width: 100%;"
                                    placeholder="请输入报告正文" />
                            </el-form-item>
                        </el-form>
                    </template>
@@ -63,21 +65,20 @@
            <!-- 右侧审批流程 -->
            <div class="approval-flow">
                <div class="flow-content">
                    <approval-process :status="form.status" :submit-time="form.createTime" :approver="form.approver"
                        :approve-time="form.approveTime" />
                    <approval-process :processData="form.processData" />
                </div>
            </div>
        </div>
        <div class="approval-dialog-approve">
        <div class="approval-dialog-approve" v-if="!obj.isDetail">
            <el-row :span="24">
                <el-col :span="12">
                    <div class="status">
                        <div class="status-title">审批结果</div>
                        <div class="status-content">
                            <div class="resolve" :class="status == '1' && 'activeStatus'" @click.stop="status = 1">
                            <div class="resolve" :class="status == '2' && 'activeStatus'" @click.stop="status = 2">
                                通过
                            </div>
                            <div class="reject" :class="status == '2' && 'activeStatus'" @click.stop="status = 2">
                            <div class="reject" :class="status == '3' && 'activeStatus'" @click.stop="status = 3">
                                驳回
                            </div>
                        </div>
@@ -94,8 +95,8 @@
        </div>
        <div slot="footer" class="dialog-footer">
            <el-button @click="handleClose" >取 消</el-button>
            <el-button type="primary" @click="handleApprove" v-if="type === 'approve'">通过</el-button>
            <el-button @click="handleClose">{{obj.isDetail ? '关闭' : '取 消'}}</el-button>
            <el-button type="primary" @click="handleApprove" v-if="!obj.isDetail">通过</el-button>
        </div>
    </el-dialog>
</template>
@@ -103,6 +104,8 @@
<script>
import ApprovalProcess from '@/components/approvalProcess'
import AiEditor from '@/components/AiEditor'
import { getDetail } from '../../service';
export default {
    name: "ApprovalDialog",
@@ -119,27 +122,33 @@
            type: String,
            default: "approve", // approve-审批,view-查看
        },
        data: {
        obj: {
            type: Object,
            default: () => ({}),
            default: () => { },
        },
    },
    data() {
        return {
            form: {
                planName: "",
                planCode: "",
                stage: "",
                creator: "",
                reportCode: "",
                reportName: "",
                reportText: "",
                teamName: "",
                createBy: "",
                createTime: "",
                status: "",
                approvalComment: "",
                status: "pending",
                approver: "",
                approveTime: ""
                approveTime: "",
                processData: [],
                updateBy: "",
                auditRemark: "",
                auditPersonName: "",
                auditTime: ""
            },
            radio1: 1,
            tableData: [],
            rules: {},
            status: "1",
            status: "2",
            remark: "",
        };
    },
@@ -148,40 +157,101 @@
            return this.type === "approve" ? "审批" : "审批详情";
        },
    },
    watch: {
        data: {
            handler(val) {
                if (val) {
                    this.form = { ...val };
                }
            },
            immediate: true,
        },
    },
    methods: {
        open() {
            if (!this.obj.id) {
                this.$message.error('缺少必要参数');
                return;
            }
            getDetail(this.obj.id).then(res => {
                const data = res.data || res;
                this.form = {
                    ...this.form,
                    ...data,
                    processData: []
                };
                this.tableData = data.projectTeam ?
                    [{ ...data.projectTeam, staffName: data.staffNames || '' }] :
                    [];
                let processData = [];
                // 提交节点
                processData.push({
                    type: "primary",
                    mode: "list",
                    fields: [
                        { label: "提交人:", value: data.updateBy || "" },
                        { label: "提交时间:", value: data.createTime || "" },
                    ]
                });
                if (data.status == 2 || data.status == 3) {
                    processData.push({
                        type: data.status === 2 ? "primary" : "danger",
                        mode: "list",
                        fields: [
                            { label: "审批意见:", value: data.auditRemark || "" },
                            { label: "审核人:", value: data.auditPersonName || "" },
                            { label: "审核时间:", value: data.auditTime || "" },
                        ]
                    });
                } else {
                    processData.push({
                        type: "warning",
                        mode: "list",
                        fields: [
                            { label: "等待审核" },
                        ],
                    });
                }
                if (data.status == 2) {
                    processData.push({
                        type: "warning",
                        mode: "list",
                        fields: [{ label: "等待评定" }],
                    });
                }
                if (data.status == 3) {
                    processData.push({
                        type: "success",
                        mode: "list",
                        fields: [
                            { label: "已评定" },
                            { label: "评定人:", value: data.evaluatePersonName || "" },
                            { label: "评定时间:", value: data.evaluateTime || "" }
                        ],
                    });
                }
                this.form.processData = processData;
            }).catch(err => {
                this.$message.error('获取详情失败');
            });
        },
        handleClose() {
            this.$emit("close");
            this.form.approvalComment = "";
        },
        handleApprove() {
            if (!this.form.approvalComment) {
                this.$message.warning("请输入审批意见");
                return;
            }
            this.$emit("approve", {
                ...this.form,
                status: "approved",
                statuss: this.status,
                remark: this.remark
            });
        },
        handleReject() {
            if (!this.form.approvalComment) {
                this.$message.warning("请输入审批意见");
                return;
            }
            this.$emit("reject", {
                ...this.form,
                status: "rejected",
            });
        handleCurrentChange(page) {
            this.form.pageNum = page
            this.getList()
        },
        handleSizeChange(size) {
            this.form.pageSize = size
            this.getList()
        },
    },
};
laboratory/src/views/reportLibrary/processDevelopment/index.vue
@@ -1,14 +1,27 @@
<template>
    <div class="list">
        <TableCustom :queryForm="queryForm" :total="total" @currentChange="handleCurrentChange"
            @sizeChange="handleSizeChange">
        <el-card class="header-box" v-if="roleType == 3">
            <div class="box-title">
                <img src="@/assets/public/notice.png" class="header-icon"> <span>设立课题规则</span>
            </div>
            <div class="header-content">
                <p>1、根据可研报告、产品构思设计的工艺研究路线,一条工艺路线设立一个课题。如果一个课题中有多个化合物需要开发研究,则每个化合物作为一个分题;分题归集到该课题中,最终形成课题报告。不同课题报告中的分题不能重复使用。
                </p>
                <p>2、在可行研究阶段,工艺开发升级,重新规划工艺研究路线,则以新规划的工艺路线方案来设定课题。</p>
            </div>
        </el-card>
        <TableCustom :tableData="tableData" :height="null" :total="total" @handleCurrentChange="handleCurrentChanges"
            @handleSizeChange="handleSizeChanges">
            <template #search>
                <el-form :model="form" :label-width="auto" inline>
                <el-form :model="form" label-width="auto" inline>
                    <el-form-item label="所属项目组:">
                        <el-input v-model="form.name" placeholder="请输入"></el-input>
                        <el-input v-model="form.teamName" placeholder="请输入"></el-input>
                    </el-form-item>
                    <el-form-item label="报告名称:">
                        <el-input v-model="form.name" placeholder="请输入"></el-input>
                        <el-input v-model="form.reportName" placeholder="请输入"></el-input>
                    </el-form-item>
                    <el-form-item label="报告编号:">
                        <el-input v-model="form.reportCode" placeholder="请输入"></el-input>
                    </el-form-item>
                    <el-form-item label="创建日期:">
                        <el-date-picker v-model="form.date" type="daterange" range-separator="至"
@@ -22,37 +35,57 @@
                        </el-select>
                    </el-form-item>
                    <el-form-item label="" style="margin-left: 63px;">
                        <el-button type="default">重置</el-button>
                        <el-button type="default" style="margin-right: 10px;">重置</el-button>
                        <el-button type="primary">查询</el-button>
                    </el-form-item>
                </el-form>
            </template>
            <template #setting>
                <div class="table-title">
                <el-button v-if="roleType == 3" @click="handleAddProject" class="el-icon-plus" type="primary">
                    新增工艺开发工具</el-button>
                <div class="table-setting">
                    <div :class="!isDraft ? 'table-title' : 'table-tit'" @click="changeTab('')">
                    工艺开发工具
                </div>
                    <div v-if="roleType == 3" :class="!isDraft ? 'table-tit' : 'table-title'" @click="changeTab('-1')">
                        草稿箱
                    </div>
                </div>
            </template>
            <template #table>
                <el-table-column prop="name" label="所属项目组" />
                <el-table-column prop="age" label="报告编号" />
                <el-table-column prop="age" label="报告名称" />
                <el-table-column prop="age" label="创建人" />
                <el-table-column prop="age" label="创建时间" />
                <el-table-column prop="age" label="状态">
                <el-table-column prop="teamName" label="所属项目组" />
                <el-table-column prop="reportCode" label="报告编号" />
                <el-table-column prop="reportName" label="报告名称" />
                <el-table-column prop="createBy" label="创建人" />
                <el-table-column prop="createTime" label="创建时间" />
                <el-table-column prop="status" label="状态" v-if="!isDraft">
                    <template #default="{ row }">
                        <el-tag v-if="row.status == 1" type="success">待审核</el-tag>
                        <el-tag v-else-if="row.status == 0" type="success">已通过</el-tag>
                        <el-tag v-else type="danger">已驳回</el-tag>
                        <el-tag v-if="row.status == 1">待审核</el-tag>
                        <el-tag v-else-if="row.status == 2">待评定</el-tag>
                        <el-tag v-else-if="row.status == 4" type="danger">已驳回</el-tag>
                        <el-tag v-else-if="row.status == 3" type="success">已评定</el-tag>
                        <el-tag v-else-if="row.status == 5" type="info">已撤回</el-tag>
                    </template>
                </el-table-column>
                <el-table-column prop="age" label="操作">
                <el-table-column prop="options" label="操作">
                    <template #default="{ row }">
                        <el-button type="text">审核</el-button>
                        <el-button type="text">详情</el-button>
                        <el-button type="text" @click="handleApproval(row)"
                            v-if="row.status == 1 && [1, 2].includes(roleType)">审核</el-button>
                        <el-button type="text" @click="handleDetail(row)">详情</el-button>
                        <el-button type="text" @click="handleDelete(row)"
                            v-if="[4, 5].includes(row.status) && roleType == 3">删除</el-button>
                        <el-button type="text" @click="handleEdit(row)"
                            v-if="[4, 5].includes(row.status) && roleType == 3">编辑</el-button>
                        <el-button type="text" @click="handleRevoke(row)"
                            v-if="row.status == 1 && roleType == 3">撤销审批</el-button>
                    </template>
                </el-table-column>
            </template>
        </TableCustom>
        <Approval :visible="showApproval" @close="showApproval = false" :obj="rowData" @approve="handleApprove" />
        <ShowDelConfirm :show="showDelConfirm" @close="showDelConfirm = false" @confirm="handleDelConfirm" />
        <ShowDelConfirm :title="changeStatusTitle" :tip="changeStatusTip" :show="changeStatus"
            @close="changeStatus = false" @confirm="handleChangeStatusConfirm" />
@@ -60,71 +93,212 @@
</template>
<script>
import Approval from './components/approval'
import { getDataList, audit, revokeAudit, deleteData } from './service'
export default {
    name: 'ProjectList',
    components: {
        Approval
    },
    data() {
        return {
            form: {
                name: ''
            },
            showDelConfirm: false,
            rowId: '',
            changeStatus: false,
            showApproval: false,
            changeStatusTitle: '',
            changeStatusTip: '',
            queryForm: {
            tableData: [],
            isDraft: false,
            rowData: {},
            roleType: '', // 1 超级管理员 2 审批人 3 工艺工程师 4化验师 5实验员
            form: {
                pageSize: 10,
                pageNum: 1
                pageNum: 1,
                teamName: '',
                status: '',
                startTime: '',
                reportType: 3,
                reportName: '',
                reportCode: '',
                endTime: '',
                date: ''
            },
            total: 0
        }
    },
    mounted() {
        this.roleType = JSON.parse(sessionStorage.getItem('userInfo'))?.roleType
        console.log('adwqedwqeqwe', this.roleType);
        this.getList()
    },
    methods: {
        handleAddProject() {
        handleApproval(row) {
            this.rowData = row
            this.showApproval = true
        },
        handleDetail(row) {
            row.isDetail = true
            this.rowData = row
            this.showApproval = true
        },
        handleEdit(row) {
            this.$router.push({
                path: '/projectList/addProject'
                path: '/reportLibrary/edit',
                query: {
                    id: row.id
                }
            })
        },
        handleDel(row) {
        handleAddProject() {
            this.$router.push('/reportLibrary/add')
        },
        changeTab(status) {
            if (status == -1) {
                this.isDraft = true
                this.form.pageNum = 1
            } else {
                this.form.pageNum = 1
                this.isDraft = false
                this.form.status = status
            }
            this.getList()
        },
        handleDelete(row) {
            this.rowId = row.id
            this.showDelConfirm = true
        },
        handleDelConfirm() {
            this.showDelConfirm = false
            this.msgsuccess('删除成功')
            this.rowId = ''
            this.getList()
        },
        handleChangeStatus(row, status) {
        handleRevoke(row) {
            this.rowId = row.id
            this.changeStatusTitle = status == 1 ? '确认要封存这个项目组吗?' : '确认要解封该项目组吗?'
            this.changeStatusTip = status == 1 ? '封存后项目组内人员看不到数据,审批人仍然可见数据。' : '解封后项目组内人员数据恢复。'
            this.changeStatusTitle = '确认要撤销审批吗?'
            this.changeStatusTip = '撤销审批后,可研报告将被撤销。'
            this.changeStatus = true
        },
        handleDelConfirm() {
            deleteData({ id: this.rowId }).then(res => {
                this.showDelConfirm = false
                this.$message.success('删除成功')
                this.rowId = ''
                this.getList()
            })
        },
        handleChangeStatusConfirm() {
            revokeAudit({ id: this.rowId }).then(res => {
            this.changeStatus = false
            this.msgsuccess('操作成功')
                this.$message.success('操作成功')
            this.rowId = ''
            this.changeStatusTitle = ''
            this.changeStatusTip = ''
            this.getList()
            })
        },
        handleCurrentChange(page) {
            this.queryForm.pageNum = page
        handleCurrentChanges(page) {
            this.form.pageNum = page
            this.getList()
        },
        handleSizeChange(size) {
            this.queryForm.pageSize = size
        handleSizeChanges(size) {
            this.form.pageSize = size
            this.getList()
        },
        getList() {
            let data = {}
            if (this.isDraft) {
                data = {
                    ...this.form,
                    status: -1
                }
            } else {
                data = this.form
            }
            getDataList(data).then(res => {
                if (res.code === 200) {
                    this.tableData = res.data.records || []
                    this.total = res.data.total || 0
                }
            })
        },
        handleApprove(data) {
            let params = {
                id: data.id,
                auditStatus: data.statuss,
                auditRemark: data.remark
            }
            audit({ ...params }).then(res => {
                if (res.code === 200) {
                    this.$message.success('审核成功')
                    this.showApproval = false
                    this.getList()
                }
            })
        }
    }
}
</script>
<style scoped lang="less">
.el-icon-plus {
    margin-bottom: 20px;
}
.header-content {
    font-family: PingFangSC, PingFang SC;
    font-weight: 400;
    font-size: 14px;
    color: rgba(0, 0, 0, 0.88);
    margin-left: 30px;
}
.box-title {
    font-family: SourceHanSansCN, SourceHanSansCN;
    font-weight: bold;
    font-size: 18px;
    color: #222222;
    line-height: 27px;
    display: flex;
    align-items: center;
}
.header-icon {
    width: 20px;
    height: 20px;
    margin-right: 10px;
}
.header-box {
    border-radius: 16px;
    margin-bottom: 30px;
}
.table-setting {
    display: flex;
    gap: 14px;
}
.table-tit {
    background: #FAFAFC;
    border-radius: 8px 8px 0px 0px;
    border: 1px solid #DCDFE6;
    width: 166px;
    height: 50px;
    background: #FFFFFF;
    border-radius: 8px 8px 0px 0px;
    display: flex;
    align-items: center;
    justify-content: center;
    // margin-bottom: 21px;
    font-family: SourceHanSansCN, SourceHanSansCN;
    font-weight: bold;
    font-size: 18px;
    color: #606266;
    line-height: 27px;
}
.list {
    height: 100%;
}
@@ -138,7 +312,7 @@
    display: flex;
    align-items: center;
    justify-content: center;
    margin-bottom: 21px;
    // margin-bottom: 21px;
    font-family: SourceHanSansCN, SourceHanSansCN;
    font-weight: bold;
    font-size: 18px;
laboratory/src/views/reportLibrary/processDevelopment/service.js
New file
@@ -0,0 +1,43 @@
import axios from '@/utils/request';
// 查询列表
export function getDataList(data) {
  return axios.post('/api/t-feasibility-study-report/pageList', { ...data })
}
// 添加
export function addData(data) {
  return axios.post('/api/t-feasibility-study-report/add', { ...data })
}
//修改
export function editData(data) {
  return axios.post('/api/t-feasibility-study-report/update', { ...data })
}
//获取详情
export function getDetail(id) {
  return axios.get(`/open/t-feasibility-study-report/getDetailById?id=${id}`)
}
//审核
export function audit(data) {
  console.log(data)
  return axios.post('/api/t-feasibility-study-report/auditReport', { ...data })
}
//撤销审批
export function revokeAudit(data) {
  return axios.put(`/open/t-feasibility-study-report/revokedReport?id=${data.id}`)
}
//删除
export function deleteData(data) {
  return axios.delete(`/open/t-feasibility-study-report/deleteById?id=${data.id}`)
}