From 06b2be3bbb48e0275fbd25624c1cce54a7cac2b1 Mon Sep 17 00:00:00 2001 From: 董国庆 <364620639@qq.com> Date: 星期二, 20 五月 2025 16:44:34 +0800 Subject: [PATCH] Merge branch 'main' of http://120.76.84.145:10101/gitblit/r/H5/leshan-laboratory --- culture/src/views/strain-library/main-cell-library/service.js | 56 culture/src/views/strain-library/production-cell-library/index.vue | 495 +- laboratory/src/views/reportLibrary/feasibilityStudy/add.vue | 135 laboratory/src/views/reportLibrary/feasibilityStudy/service.js | 39 culture/src/views/strain-library/main-cell-library/record.vue | 684 ++- culture/src/views/strain-library/main-cell-library/add.vue | 573 ++- laboratory/src/views/dataManagement/approvalPlan/list.vue | 2 culture/src/views/strain-library/strain-library-manage/record.vue | 795 ++-- culture/src/views/pedigree-chart/add.vue | 67 culture/src/views/pedigree-chart/addProgenitor.vue | 20 culture/src/views/pedigree-chart/index.vue | 52 laboratory/src/views/reportLibrary/processDevelopment/add.vue | 280 + laboratory/src/views/reportLibrary/processDevelopment/service.js | 43 laboratory/src/views/reportLibrary/feasibilityReport/add.vue | 272 + laboratory/src/views/reportLibrary/feasibilityReport/index.vue | 273 + laboratory/src/views/reportLibrary/processDevelopment/components/approval/index.vue | 188 laboratory/src/views/reportLibrary/feasibilityReport/service.js | 44 culture/src/views/strain-library/strain-library-manage/components/AddRecordDialog.vue | 22 laboratory/src/views/reportLibrary/feasibilityStudy/components/approval/index.vue | 183 culture/src/views/pedigree-chart/components/PlanForm.vue | 6 culture/src/views/strain-library/strain-library-manage/components/RecordDetailDialog.vue | 82 culture/src/components/SignatureCanvas.vue | 2 laboratory/src/views/reportLibrary/processDevelopment/index.vue | 272 + culture/src/router/index.js | 904 ++-- laboratory/src/views/reportLibrary/feasibilityStudy/index.vue | 166 culture/src/views/strain-library/production-cell-library/add.vue | 384 + culture/src/views/strain-library/production-cell-library/record.vue | 864 ++-- culture/src/views/strain-library/strain-library-manage/index.vue | 843 ++-- culture/src/views/pedigree-chart/service.js | 11 culture/src/views/strain-library/main-cell-library/index.vue | 851 ++-- culture/src/views/pedigree-chart/components/ParentForm.vue | 6 laboratory/src/router/index.js | 54 culture/src/views/strain-library/strain-library-manage/add.vue | 224 culture/src/views/strain-library/strain-library-manage/service.js | 56 culture/src/views/strain-library/strain-library-manage/components/StrainDetail.vue | 433 +- culture/src/views/strain-library/production-cell-library/service.js | 56 laboratory/src/views/reportLibrary/feasibilityReport/components/approval/index.vue | 188 culture/src/views/strain-library/strain-library-manage/components/RecordTimeline.vue | 98 38 files changed, 6,022 insertions(+), 3,701 deletions(-) diff --git a/culture/src/components/SignatureCanvas.vue b/culture/src/components/SignatureCanvas.vue index f856bc8..e996394 100644 --- a/culture/src/components/SignatureCanvas.vue +++ b/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() { diff --git a/culture/src/router/index.js b/culture/src/router/index.js index 2d85a1d..e433d4a 100644 --- a/culture/src/router/index.js +++ b/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", ------页面地址 @@ -24,471 +24,509 @@ */ const routes = [ - { - path: "/", - redirect: "/login", + { + path: "/", + redirect: "/login", + }, + { + path: "/login", + meta: { + title: "登录", + oneself: true, + hide: true, }, - { - path: "/login", + component: () => import("../views/login"), + }, + { + path: "/projectList", + meta: { + title: "项目组管理", + }, + component: Layouts, + children: [ + { + path: "list", + name: "ProjectList", meta: { - title: "登录", - oneself: true, - hide: true, + title: "菌种库项目组管理", }, - component: () => import("../views/login"), - }, - { - path: "/projectList", + component: () => import("../views/projectList"), + }, + { + path: "addProject", + name: "AddProject", meta: { - title: "项目组管理", + title: "新增菌种库项目组", + hide: true, + keepAlive: true, }, - component: Layouts, - children: [ - { - path: "list", - name: "ProjectList", - meta: { - title: "菌种库项目组管理", - }, - component: () => import("../views/projectList"), - }, - { - path: "addProject", - name: "AddProject", - meta: { - title: "新增菌种库项目组", - hide: true, - keepAlive: true, - }, - component: () => import("../views/projectList/addProject"), - }, - { - path: "editProject", - name: "EditProject", - meta: { - title: "编辑菌种库项目组", - hide: true, - }, - component: () => import("../views/projectList/editProject"), - }, - { - path: "detailProject", - name: "DetailProject", - meta: { - title: "菌种库项目组详情", - hide: true, - }, - component: () => import("../views/projectList/detailProject"), - } - ] - }, - { - path: "/system", + component: () => import("../views/projectList/addProject"), + }, + { + path: "editProject", + name: "EditProject", meta: { - title: "系统管理", + title: "编辑菌种库项目组", + hide: true, }, - component: Layouts, - children: [ - { - path: "user", - name: "User", - meta: { - title: "人员管理", - }, - component: () => import("../views/system/user"), - }, - { - path: "role", - name: "Role", - meta: { - title: "角色管理", - }, - component: () => import("../views/system/role"), - }, - { - path: "add-role", - name: "AddRole", - meta: { - title: "新增角色", - hide: true, - }, - component: () => import("../views/system/role/add"), - }, - { - path: "edit-role", - name: "EditRole", - meta: { - title: "编辑角色", - hide: true, - }, - component: () => import("../views/system/role/edit"), - }, - { - path: "detail-role", - name: "DetailRole", - meta: { - title: "角色详情", - hide: true, - }, - component: () => import("../views/system/role/detail"), - }, - { - path: "operation-log", - meta: { - title: "操作日志", - }, - component: () => import("../views/system/operation-log"), - }, - ] - }, - { - path: "/strain", - component: Layouts, + component: () => import("../views/projectList/editProject"), + }, + { + path: "detailProject", + name: "DetailProject", meta: { - title: "菌种库", + title: "菌种库项目组详情", + hide: true, + }, + component: () => import("../views/projectList/detailProject"), + }, + ], + }, + { + path: "/system", + meta: { + title: "系统管理", + }, + component: Layouts, + children: [ + { + path: "user", + name: "User", + meta: { + title: "人员管理", + }, + component: () => import("../views/system/user"), + }, + { + path: "role", + name: "Role", + meta: { + title: "角色管理", + }, + component: () => import("../views/system/role"), + }, + { + path: "add-role", + name: "AddRole", + meta: { + title: "新增角色", + hide: true, + }, + component: () => import("../views/system/role/add"), + }, + { + path: "edit-role", + name: "EditRole", + meta: { + title: "编辑角色", + hide: true, + }, + component: () => import("../views/system/role/edit"), + }, + { + path: "detail-role", + name: "DetailRole", + meta: { + title: "角色详情", + hide: true, + }, + component: () => import("../views/system/role/detail"), + }, + { + path: "operation-log", + meta: { + title: "操作日志", + }, + component: () => import("../views/system/operation-log"), + }, + ], + }, + { + path: "/strain", + component: Layouts, + meta: { + title: "菌种库", + }, + children: [ + { + path: "/strain-library", + component: Parent, + meta: { + title: "菌种库管理", }, children: [ - { - path: "/strain-library", - component: Parent, - meta: { - title: "菌种库管理", - }, - children: [ - { - path: "strain-library-manage", - name: "StrainLibraryManage", - meta: { - title: "原始细胞库", - keepAlive: true, - }, - component: () => import("../views/strain-library/strain-library-manage"), - }, - { - path: "strain-library-manage/add", - name: "StrainLibraryManageAdd", - meta: { - title: "新增原始细胞库", - keepAlive: true, - hide: true - }, - component: () => import("../views/strain-library/strain-library-manage/add.vue"), - }, - { - path: "strain-library-manage/record", - name: "StrainRecord", - meta: { - title: "出入库记录", - keepAlive: true, - hide: true - }, - component: () => import("../views/strain-library/strain-library-manage/record.vue"), - }, - { - path: "main-cell-library", - name: "MainCellLibrary", - meta: { - title: "主细胞库", - keepAlive: true, - }, - component: () => import("../views/strain-library/main-cell-library"), - }, - { - path: "main-cell-library/add", - name: "MainCellLibraryAdd", - meta: { - title: "新增主细胞", - keepAlive: true, - hide: true - }, - component: () => import("../views/strain-library/main-cell-library/add.vue"), - }, - { - path: "production-cell-library", - name: "ProductionCellLibrary", - meta: { - title: "生产细胞库", - keepAlive: true, - }, - component: () => import("../views/strain-library/production-cell-library"), - }, - { - path: "production-cell-library/add", - name: "ProductionCellLibraryAdd", - meta: { - title: "新增生产细胞", - keepAlive: true, - hide: true - }, - component: () => import("../views/strain-library/production-cell-library/add.vue"), - } - ] - }, - { - path: 'pedigree-vhart', - name: 'PedigreeChart', - meta: { - title: "菌种传代生产谱系图", - }, - component: () => import("../views/pedigree-chart"), - }, - { - path: 'add-pedigree', - name: 'AddPedigree', - meta: { - title: "新增母代菌种传代生产谱系图", - hide: true - }, - component: () => import("../views/pedigree-chart/add"), - }, - { - path: 'add-progenitor', - name: 'AddProgenitor', - meta: { - title: "新增祖代菌种传代生产谱系图", - hide: true - }, - component: () => import("../views/pedigree-chart/addProgenitor"), - }, - // { - // path: "strain-flow-chart", - // name: "StrainFlowChart", - // meta: { - // title: "菌种传代产生流程图", - // keepAlive: true, - // }, - // component: () => import("../views/strain-library/strain-flow-chart"), - // }, - { - path: 'breeding-record', - name: 'BreedingRecord', - meta: { - title: "菌种选育保藏记录", - }, - component: () => import("../views/strain-library/breeding-record"), - }, - { - path: 'add-breeding-record', - name: 'AddBreedingRecord', - meta: { - title: "新增菌种选育保藏记录", - hide: true, - }, - component: () => import("../views/strain-library/breeding-record/add"), - }, - { - path: 'validation', - meta: { - title: "菌种验证数据资料", - }, - component: Parent, - children: [ - { - path: 'primitive-cell', - name: 'PrimitiveCell', - meta: { - title: '原始细胞库资料', - }, - component: () => import("../views/strain-library/validation/primitive-cell/index.vue") - }, - { - path: 'add-primitive-cell', - name: 'AddPrimitiveCell', - meta: { - title: '新增原始细胞库资料', - hide: true - }, - component: () => import("../views/strain-library/validation/primitive-cell/add.vue") - }, - { - path: 'confirm-detail', - name: 'ConfirmDetail', - meta: { - title: '确认原始细胞库资料', - hide: true - }, - component: () => import("../views/strain-library/validation/primitive-cell/confirm-detail.vue") - }, - { - path: 'chief-cell', - name: 'ChiefCell', - meta: { - title: '主细胞库资料' - }, - component: () => import("../views/strain-library/validation/chief-cell") - } - ] - }, - ] - }, { - path: "/strainReportLibrary", - component: Layouts, - meta: { - title: "菌种报告库", - }, - children: [{ - path: "reportLibraryOne", + { + path: "strain-library-manage", + name: "StrainLibraryManage", meta: { - title: "报告库一", - keepAlive: true, + title: "原始细胞库", + keepAlive: true, }, - component: () => import("../views/strainReportLibrary/reportLibraryOne/index.vue"), - }, - { - path: "add", + component: () => + import("../views/strain-library/strain-library-manage"), + }, + { + path: "strain-library-manage/add", + name: "StrainLibraryManageAdd", meta: { - title: "新增报告", - hide: true, - keepAlive: true, - }, - component: () => import("../views/strainReportLibrary/reportLibraryOne/add.vue"), - }, - { - path: "reportLibraryTwo", - meta: { - title: "报告库二", - keepAlive: true, - }, - component: () => import("../views/strainReportLibrary/reportLibraryOneTWO/index.vue"), - }, - { - path: "addTwo", - meta: { - title: "新增报告", - hide: true, - keepAlive: true, - }, - component: () => import("../views/strainReportLibrary/reportLibraryOneTWO/add.vue"), - }, - { - path: "reportLibraryThree", - meta: { - title: "报告库三", - keepAlive: true, - }, - component: () => import("../views/strainReportLibrary/reportLibraryOneThree/index.vue"), - }, - { - path: "addThree", - meta: { - title: "新增报告", - hide: true, - keepAlive: true, - }, - component: () => import("../views/strainReportLibrary/reportLibraryOneThree/add.vue"), - }, - { - path: "reportLibraryFour", - meta: { - title: "报告库四", - keepAlive: true, - }, - component: () => import("../views/strainReportLibrary/reportLibraryOneFour/index.vue"), - }, - { - path: "addFour", - meta: { - title: "新增报告", - hide: true, - keepAlive: true, - }, - component: () => import("../views/strainReportLibrary/reportLibraryOneFour/add.vue"), - }, + title: "新增原始细胞库", + keepAlive: true, + hide: true, + }, + component: () => + import("../views/strain-library/strain-library-manage/add.vue"), + }, + { + path: "strain-library-manage/record", + name: "StrainRecord", + meta: { + title: "出入库记录", + hide: true, + }, + component: () => + import( + "../views/strain-library/strain-library-manage/record.vue" + ), + }, + { + path: "main-cell-library", + name: "MainCellLibrary", + meta: { + title: "主细胞库", + keepAlive: true, + }, + component: () => + import("../views/strain-library/main-cell-library"), + }, + { + path: "main-cell-library/add", + name: "MainCellLibraryAdd", + meta: { + title: "新增主细胞", + keepAlive: true, + hide: true, + }, + component: () => + import("../views/strain-library/main-cell-library/add.vue"), + }, + { + path: "production-cell-library", + name: "ProductionCellLibrary", + meta: { + title: "生产细胞库", + keepAlive: true, + }, + component: () => + import("../views/strain-library/production-cell-library"), + }, + { + path: "production-cell-library/add", + name: "ProductionCellLibraryAdd", + meta: { + title: "新增生产细胞", + keepAlive: true, + hide: true, + }, + component: () => + import("../views/strain-library/production-cell-library/add.vue"), + }, ], - }, - { - path: "/deliveryAssessment", - component: Layouts, + }, + { + path: "pedigree-vhart", + name: "PedigreeChart", meta: { - title: "菌种报告评定", + title: "菌种传代生产谱系图", }, - children: [{ - path: "projectTeamIntegral", + component: () => import("../views/pedigree-chart"), + }, + { + path: "add-pedigree", + name: "AddPedigree", + meta: { + title: "新增母代菌种传代生产谱系图", + hide: true, + }, + component: () => import("../views/pedigree-chart/add"), + }, + { + path: "add-progenitor", + name: "AddProgenitor", + meta: { + title: "新增祖代菌种传代生产谱系图", + hide: true, + }, + component: () => import("../views/pedigree-chart/addProgenitor"), + }, + // { + // path: "strain-flow-chart", + // name: "StrainFlowChart", + // meta: { + // title: "菌种传代产生流程图", + // keepAlive: true, + // }, + // component: () => import("../views/strain-library/strain-flow-chart"), + // }, + { + path: "breeding-record", + name: "BreedingRecord", + meta: { + title: "菌种选育保藏记录", + }, + component: () => import("../views/strain-library/breeding-record"), + }, + { + path: "add-breeding-record", + name: "AddBreedingRecord", + meta: { + title: "新增菌种选育保藏记录", + hide: true, + }, + component: () => import("../views/strain-library/breeding-record/add"), + }, + { + path: "validation", + meta: { + title: "菌种验证数据资料", + }, + component: Parent, + children: [ + { + path: "primitive-cell", + name: "PrimitiveCell", meta: { - title: "菌种项目组评定表", + title: "原始细胞库资料", }, - component: () => import("../views/deliveryAssessment/projectTeamIntegral"), - }, - { - path: "projectTeamIntegral-detail", + component: () => + import( + "../views/strain-library/validation/primitive-cell/index.vue" + ), + }, + { + path: "add-primitive-cell", + name: "AddPrimitiveCell", meta: { - title: "评定详情", - hide: true + title: "新增原始细胞库资料", + hide: true, }, - component: () => import("../views/deliveryAssessment/projectTeamIntegral/detail.vue"), + component: () => + import( + "../views/strain-library/validation/primitive-cell/add.vue" + ), + }, + { + path: "confirm-detail", + name: "ConfirmDetail", + meta: { + title: "确认原始细胞库资料", + hide: true, + }, + component: () => + import( + "../views/strain-library/validation/primitive-cell/confirm-detail.vue" + ), + }, + { + path: "chief-cell", + name: "ChiefCell", + meta: { + title: "主细胞库资料", + }, + component: () => + import("../views/strain-library/validation/chief-cell"), + }, + ], + }, + ], + }, + { + path: "/strainReportLibrary", + component: Layouts, + meta: { + title: "菌种报告库", + }, + children: [ + { + path: "reportLibraryOne", + meta: { + title: "报告库一", + keepAlive: true, }, - ] - } + component: () => + import("../views/strainReportLibrary/reportLibraryOne/index.vue"), + }, + { + path: "add", + meta: { + title: "新增报告", + hide: true, + keepAlive: true, + }, + component: () => + import("../views/strainReportLibrary/reportLibraryOne/add.vue"), + }, + { + path: "reportLibraryTwo", + meta: { + title: "报告库二", + keepAlive: true, + }, + component: () => + import("../views/strainReportLibrary/reportLibraryOneTWO/index.vue"), + }, + { + path: "addTwo", + meta: { + title: "新增报告", + hide: true, + keepAlive: true, + }, + component: () => + import("../views/strainReportLibrary/reportLibraryOneTWO/add.vue"), + }, + { + path: "reportLibraryThree", + meta: { + title: "报告库三", + keepAlive: true, + }, + component: () => + import( + "../views/strainReportLibrary/reportLibraryOneThree/index.vue" + ), + }, + { + path: "addThree", + meta: { + title: "新增报告", + hide: true, + keepAlive: true, + }, + component: () => + import("../views/strainReportLibrary/reportLibraryOneThree/add.vue"), + }, + { + path: "reportLibraryFour", + meta: { + title: "报告库四", + keepAlive: true, + }, + component: () => + import("../views/strainReportLibrary/reportLibraryOneFour/index.vue"), + }, + { + path: "addFour", + meta: { + title: "新增报告", + hide: true, + keepAlive: true, + }, + component: () => + import("../views/strainReportLibrary/reportLibraryOneFour/add.vue"), + }, + ], + }, + { + path: "/deliveryAssessment", + component: Layouts, + meta: { + title: "菌种报告评定", + }, + children: [ + { + path: "projectTeamIntegral", + meta: { + title: "菌种项目组评定表", + }, + component: () => + import("../views/deliveryAssessment/projectTeamIntegral"), + }, + { + path: "projectTeamIntegral-detail", + meta: { + title: "评定详情", + hide: true, + }, + component: () => + import("../views/deliveryAssessment/projectTeamIntegral/detail.vue"), + }, + ], + }, ]; const router = new VueRouter({ - mode: "hash", - base: process.env.BASE_URL, - routes, + mode: "hash", + base: process.env.BASE_URL, + routes, }); // 前置路由拦截器 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'); // 已登录状态访问登录页时重定向到系统首页 - return; - } - next(); - return; + // 登录验证 + // 排除登录页的校验 + if (to.path === "/login") { + if (sessionStorage.getItem("token")) { + next("/projectList"); // 已登录状态访问登录页时重定向到系统首页 + return; } + next(); + return; + } - // 登录状态校验 - const isAuthenticated = sessionStorage.getItem('token'); - if (!isAuthenticated) { - next('/login'); // 未登录用户重定向到登录页 - return; + // 登录状态校验 + const isAuthenticated = sessionStorage.getItem("token"); + if (!isAuthenticated) { + next("/login"); // 未登录用户重定向到登录页 + 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 isExist = tagList.some((item) => item.path === to.path); + if (!isExist) { + // 只保存必要的信息 + const tagInfo = { + path: to.path, + name: to.name, + meta: to.meta, + query: to.query, + }; + tagList.push(tagInfo); + sessionStorage.setItem("tagList", JSON.stringify(tagList)); + store.commit("SET_TAGLIST", tagList); } + } - // 判断是否拥有要跳转菜单权限 - let menus = store.state.menus - if (to.meta.hasOwnProperty('privilege') && !menus.includes(to.meta.privilege)) { - return + // 判断是否需要缓存 + if (to.meta.keepAlive) { + let keepAliveList = JSON.parse( + sessionStorage.getItem("keepAliveList") || "[]" + ); + // 判断是否已经缓存 + let isExist = keepAliveList.includes(to.name); + if (!isExist) { + keepAliveList.push(to.name); + sessionStorage.setItem("keepAliveList", JSON.stringify(keepAliveList)); + store.commit("SET_KEEPALIVELIST", keepAliveList); } + } - // 设置标签列表 - if (!to.meta.hide || !to.meta.oneself) { - let tagList = JSON.parse(sessionStorage.getItem('tagList') || '[]') - // 判断是否存在 - let isExist = tagList.some(item => item.path === to.path) - if (!isExist) { - // 只保存必要的信息 - const tagInfo = { - path: to.path, - name: to.name, - meta: to.meta, - query: to.query, - } - 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 isExist = keepAliveList.includes(to.name) - if (!isExist) { - keepAliveList.push(to.name) - sessionStorage.setItem('keepAliveList', JSON.stringify(keepAliveList)) - store.commit('SET_KEEPALIVELIST', keepAliveList) - } - } - - next() + next(); }); export default router; diff --git a/culture/src/views/pedigree-chart/add.vue b/culture/src/views/pedigree-chart/add.vue index 84daec1..65a1944 100644 --- a/culture/src/views/pedigree-chart/add.vue +++ b/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, }) } }, diff --git a/culture/src/views/pedigree-chart/addProgenitor.vue b/culture/src/views/pedigree-chart/addProgenitor.vue index 885a190..6b1f28f 100644 --- a/culture/src/views/pedigree-chart/addProgenitor.vue +++ b/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) { diff --git a/culture/src/views/pedigree-chart/components/ParentForm.vue b/culture/src/views/pedigree-chart/components/ParentForm.vue index d7bf108..f997918 100644 --- a/culture/src/views/pedigree-chart/components/ParentForm.vue +++ b/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> diff --git a/culture/src/views/pedigree-chart/components/PlanForm.vue b/culture/src/views/pedigree-chart/components/PlanForm.vue index 441f636..df58249 100644 --- a/culture/src/views/pedigree-chart/components/PlanForm.vue +++ b/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> diff --git a/culture/src/views/pedigree-chart/index.vue b/culture/src/views/pedigree-chart/index.vue index d2a854c..fe2022e 100644 --- a/culture/src/views/pedigree-chart/index.vue +++ b/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; + } + }); }, }, }; diff --git a/culture/src/views/pedigree-chart/service.js b/culture/src/views/pedigree-chart/service.js new file mode 100644 index 0000000..4a1a7db --- /dev/null +++ b/culture/src/views/pedigree-chart/service.js @@ -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 }) +} \ No newline at end of file diff --git a/culture/src/views/strain-library/main-cell-library/add.vue b/culture/src/views/strain-library/main-cell-library/add.vue index 39b15f6..3e1e100 100644 --- a/culture/src/views/strain-library/main-cell-library/add.vue +++ b/culture/src/views/strain-library/main-cell-library/add.vue @@ -1,190 +1,429 @@ <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-item> - <el-form-item label="菌种名称" prop="strainName" required> - <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> - </div> + <Card> + <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"> + <el-input v-model="form.strainName" placeholder="请输入"></el-input> + </el-form-item> + <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> - </div> - <div class="end-btn" style="margin-top: 38px"> - <el-button type="primary" @click="handleSubmit">提交</el-button> - <el-button @click="handleDraft">存草稿</el-button> - </div> - </el-form> + <div class="form-row"> + <el-form-item label="鉴定方法" prop="appraisalMethod"> + <el-input v-model="form.appraisalMethod" placeholder="请输入"></el-input> + </el-form-item> + </div> - <!-- 签字确认组件 --> - <SignatureCanvas - :visible.sync="signatureVisible" - @confirm="handleSignatureConfirm" - /> - </Card> + <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(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" /> + </Card> </template> <script> import SignatureCanvas from '@/components/SignatureCanvas.vue' +import { add, edit, getDetail, addBatch } from './service' export default { - name: 'AddMainCell', - components: { - SignatureCanvas - }, - data() { - return { - signatureVisible: false, - form: { - strainNo: '', - strainName: '', - source: '', - identificationMethod: '', - characteristics: '', - storageLocation: '', - preservationMethod: '', - remarks: '' - }, - rules: { - strainNo: [{ required: true, message: '请输入菌种编号', trigger: 'blur' }], - 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' }] - } - } - }, - methods: { - handleSubmit() { - this.$refs.strainForm.validate((valid) => { - if (valid) { - 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() - } - } + name: 'StrainLibraryManageAdd', + components: { + SignatureCanvas + }, + data() { + return { + batchAddDialogVisible: false, + signatureVisible: false, + currentAction: '', // 'submit' or 'batchAdd' + batchForm: { + count: '' + }, + form: { + strainCode: '', + strainName: '', + source: '', + appraisalMethod: '', + characteristics: '', + storageLocation: '', + preservationMethod: '', + remark: '' + }, + rules: { + strainCode: [{ + validator: (rule, value, callback) => { + if (this.currentAction === 'submit' && !value) { + callback(new Error('请输入菌种编号')); + } else { + callback(); + } + }, + trigger: 'change' + }], + strainName: [{ 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(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 + } + }) + }, + 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; - margin-bottom: 24px; - - @media screen and (max-width: 1200px) { - grid-template-columns: repeat(2, 1fr); - } - - @media screen and (max-width: 768px) { - grid-template-columns: 1fr; - } - } + .form-card { + background: #fff; + border-radius: 8px; + } +} - .form-row { - display: flex; - flex-wrap: wrap; - gap: 24px; - margin-bottom: 24px; +.header-title { + margin-bottom: 24px; - .el-form-item { - margin-bottom: 0; + &-left { + display: flex; + align-items: center; - &.full-width { - width: 100%; - } - } - } + img { + width: 20px; + height: 20px; + margin-right: 8px; + } - :deep(.el-form-item__label) { - font-weight: normal; - color: #606266; - padding-bottom: 8px; - line-height: 20px; - } - - :deep(.el-form-item__content) { - line-height: unset; - } - - :deep(.el-input__inner) { - border-radius: 4px; - height: 36px; - line-height: 36px; - } - - :deep(.el-textarea__inner) { - border-radius: 4px; - padding: 8px 12px; - min-height: 120px; - } + div { + font-size: 18px; + font-weight: bold; + color: #303133; + } + } } .end-btn { - display: flex; - align-items: center; - justify-content: center; - gap: 10px; + display: flex; + align-items: center; + justify-content: center; + gap: 10px; - :deep(.el-button) { - width: 180px; - height: 36px; - padding: 0; - display: flex; - align-items: center; - justify-content: center; - font-size: 14px; - border-radius: 4px; - margin: 0; - } + button { + width: 180px; + height: 36px; + // background: #409EFF; + } } -</style> + +.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; + + &.full-width { + width: 100%; + } + } + } + + :deep(.el-form-item__label) { + font-weight: normal; + color: #606266; + padding-bottom: 8px; + line-height: 20px; + } + + :deep(.el-form-item__content) { + line-height: unset; + } + + :deep(.el-input__inner) { + border-radius: 4px; + height: 36px; + line-height: 36px; + } + + :deep(.el-textarea__inner) { + border-radius: 4px; + padding: 8px 12px; + min-height: 120px; + } +} + +.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; + justify-content: center; + gap: 10px; + + :deep(.el-button) { + width: 180px; + height: 36px; + padding: 0; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + border-radius: 4px; + margin: 0; + } +} +</style> \ No newline at end of file diff --git a/culture/src/views/strain-library/main-cell-library/index.vue b/culture/src/views/strain-library/main-cell-library/index.vue index 991ed6b..4346e86 100644 --- a/culture/src/views/strain-library/main-cell-library/index.vue +++ b/culture/src/views/strain-library/main-cell-library/index.vue @@ -1,474 +1,491 @@ <template> - <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> - </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> + <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 + > + </div> + <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="菌种源保藏出/入主细胞库登记表说明" - :visible.sync="dialogVisible" - width="50%" - class="view-all-dialog" + <!-- 查看全部弹窗 --> + <el-dialog + title="菌种源保藏出/入细胞库登记表说明" + :visible.sync="dialogVisible" + width="50%" + class="view-all-dialog" + > + <div class="dialog-content"> + <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" + > + <template #search> + <el-form :model="form" label-width="auto" inline> + <el-form-item label="菌种编号:"> + <el-input v-model="form.strainNo" placeholder="请输入"></el-input> + </el-form-item> + <el-form-item label="菌种名称:"> + <el-input v-model="form.strainName" placeholder="请输入"></el-input> + </el-form-item> + <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="4"></el-option> + </el-select> + </el-form-item> + <el-form-item class="search-btn-box"> + <el-button type="default" @click="resetForm">重置</el-button> + <el-button type="primary" @click="searchData">查询</el-button> + </el-form-item> + </el-form> + </template> + + <template #setting> + <div class="tableTitle"> + <div class="flex a-center"> + <div + class="title" + :class="{ active: currentType === 'list' }" + @click="handleTypeChange('list')" > - <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> - </div> - </el-dialog> - </el-card> + 主细胞列表 + </div> + <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> - <!-- Table --> - <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="菌种编号:"> - <el-input v-model="form.strainNo" placeholder="请输入"></el-input> - </el-form-item> - <el-form-item label="菌种名称:"> - <el-input v-model="form.strainName" placeholder="请输入"></el-input> - </el-form-item> - <el-form-item 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-select> - </el-form-item> - <el-form-item class="search-btn-box"> - <el-button type="default" @click="resetForm">重置</el-button> - <el-button type="primary" @click="searchData">查询</el-button> - </el-form-item> - </el-form> - </template> - - <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> - <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> - </div> - </template> - - <template #table> - <el-table-column prop="strainNo" 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="当前状态"> - <template #default="{ row }"> - <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> - </template> - </el-table-column> - </template> - </TableCustom> - <StrainDetail - :visible.sync="detailVisible" - :detail="currentDetail" - /> - </div> + <template #table> + <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="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> + <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', - components: { - StrainDetail + name: "StrainLibraryManage", + components: { + StrainDetail, + }, + data() { + return { + dialogVisible: false, + currentType: "list", + detailVisible: false, + currentDetail: {}, + form: { + strainNo: "", + strainName: "", + status: "", + }, + queryForm: { + pageSize: 10, + pageNum: 1, + }, + total: 800, + tableData: [], + roleType: "", + }; + }, + 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(); + }); + }); }, - data() { - return { - dialogVisible: false, - currentType: 'list', - detailVisible: false, - currentDetail: {}, - form: { - strainNo: '', - strainName: '', - status: '' - }, - queryForm: { - pageSize: 10, - pageNum: 1 - }, - total: 800, - tableData: [ - { - strainNo: 'M-2024001', - strainName: '大肠杆菌BL21', - source: '原始细胞库', - method: '分子生物学鉴定', - certificate: '常用表达宿主菌,含有DE3溶源体,适合蛋白表达', - storage: '甘油冷冻', - amount: 'M区-01-001', - inventory: '100', - notes: '高效表达宿主', - status: '1' - }, - { - 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' - } - ] - } + handleRecord(row) { + this.$router.push({ + path: `/strain-library/strain-library-manage/record?id=${row.id}`, + }); }, - methods: { - handleViewMore() { - this.dialogVisible = true; - }, - 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) - }, - handleNewStrain() { - this.$router.push('/strain-library/main-cell-library/add') - // Implement new strain logic - }, - handleBatchAdd() { - // Implement batch add logic - }, - handleDetail(row) { - this.currentDetail = row; - this.detailVisible = true; - }, - handleEdit(row) { - // Implement edit logic - }, - handleRecord(row) { - this.$router.push({ - path: '/strain-library/strain-library-manage/record', - query: { - id: row.strainNo - } - }) - }, - handleCurrentChange(page) { - this.queryForm.pageNum = page - // Implement page change logic - }, - handleSizeChange(size) { - this.queryForm.pageSize = size - // Implement size change logic - }, - handleTypeChange(type) { - this.currentType = type; - // Implement type change logic - }, - getStatusType(status) { - const types = { - 1: 'success', - 2: 'info', - 3: 'warning' - } - return types[status] || 'info' - }, - getStatusText(status) { - const texts = { - 1: '已入库', - 2: '已出库', - 3: '入库待确认' - } - return texts[status] || '未知状态' - } - } -} + handleNewStrain() { + this.$router.push({ path: "/strain-library/main-cell-library/add" }); + }, + handleEdit(row) { + this.$router.push({ + path: `/strain-library/main-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(); + }, + 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; + this.searchData(); + }, + handleSizeChange(size) { + this.queryForm.pageSize = size; + this.searchData(); + }, + handleTypeChange(type) { + this.currentType = type; + this.searchData(); + }, + getStatusType(status) { + const types = { + 1: "warning", + 2: "warning", + 3: "success", + 4: "success", + }; + return types[status] || "info"; + }, + getStatusText(status) { + const texts = { + 1: "已出库", + 2: "出库待确认", + 3: "已入库", + 4: "入库待确认", + }; + return texts[status] || "未知状态"; + }, + }, +}; </script> <style scoped lang="less"> .list { - padding: 20px; + padding: 20px; } .header-box { - margin-bottom: 20px; - border-radius: 16px; - background: rgba(255, 255, 255, 0.8); + margin-bottom: 20px; + border-radius: 16px; + background: rgba(255, 255, 255, 0.8); - .box-title { - display: flex; - align-items: center; - font-size: 18px; - font-weight: bold; - margin-bottom: 15px; - position: relative; + .box-title { + display: flex; + align-items: center; + font-size: 18px; + font-weight: bold; + margin-bottom: 15px; + position: relative; - .header-icon { - width: 20px; - height: 20px; - margin-right: 10px; - } - - .view-more { - position: absolute; - right: 0; - color: #049C9A; - } + .header-icon { + width: 20px; + height: 20px; + margin-right: 10px; } - .header-content { - color: rgba(0, 0, 0, 0.88); - font-size: 14px; - line-height: 1.8; - margin-left: 30px; - transition: max-height 0.3s ease-in-out; - overflow: hidden; - - &.collapsed { - max-height: 48px; - overflow: hidden; - } - - p { - margin: 5px 0; - } + .view-more { + position: absolute; + right: 0; + color: #049c9a; } + } + + .header-content { + color: rgba(0, 0, 0, 0.88); + font-size: 14px; + line-height: 1.8; + margin-left: 30px; + transition: max-height 0.3s ease-in-out; + overflow: hidden; + + &.collapsed { + max-height: 48px; + overflow: hidden; + } + + p { + margin: 5px 0; + } + } } .search-form { - margin-bottom: 20px; - background: #F5F7FA; - padding: 24px; - border-radius: 8px; + margin-bottom: 20px; + background: #f5f7fa; + padding: 24px; + border-radius: 8px; - .el-form-item { - margin-right: 20px; - margin-bottom: 0; - } + .el-form-item { + margin-right: 20px; + margin-bottom: 0; + } - .el-button { - margin-left: 10px; - } + .el-button { + margin-left: 10px; + } } .action-buttons { - margin-bottom: 20px; + margin-bottom: 20px; - .el-button { - margin-right: 10px; - } + .el-button { + margin-right: 10px; + } } .tab-container { - display: flex; - margin-bottom: 20px; + 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; + .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; - } + &.active { + background: #fff; + border-color: #049c9a; + color: #049c9a; + font-weight: bold; } + } } .flex { - display: flex; - align-items: center; -} - -.a-center { - align-items: center; + display: flex; + align-items: center; } .tableTitle { - display: flex; - padding-bottom: 20px; - justify-content: space-between; - align-items: center; + 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; - width: unset; - cursor: pointer; - height: 50px; - line-height: 50px; - width: 166px; - text-align: center; + .title { + background: #fafafc; + border-radius: 8px 8px 0px 0px; + border: 1px solid #dcdfe6; + font-weight: bold; + font-size: 18px; + color: #606266; + width: unset; + cursor: pointer; + height: 50px; + line-height: 50px; + width: 166px; + 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: 166px; + 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: 166px; - text-align: center; - } - - .active { - color: #049c9a; - background: #ffffff; - border-radius: 8px 8px 0px 0px; - border: 1px solid #049c9a; - - } + .active { + color: #049c9a; + 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; - margin-right: 0; - - .el-dialog__title { - font-size: 18px; - font-weight: bold; - color: #303133; - } + :deep(.el-dialog__header) { + padding: 20px; + border-bottom: 1px solid #ebeef5; + margin-right: 0; + + .el-dialog__title { + font-size: 18px; + font-weight: bold; + color: #303133; } + } - :deep(.el-dialog__body) { - padding: 20px; + :deep(.el-dialog__body) { + padding: 20px; - .dialog-content { - font-size: 14px; - line-height: 1.8; - color: #606266; + .dialog-content { + font-size: 14px; + line-height: 1.8; + color: #606266; - p { - margin: 12px 0; - - &:first-child { - margin-top: 0; - } - - &:last-child { - margin-bottom: 0; - } - } + p { + margin: 12px 0; + + &:first-child { + margin-top: 0; } + + &:last-child { + margin-bottom: 0; + } + } } + } } -</style> \ No newline at end of file +</style> diff --git a/culture/src/views/strain-library/main-cell-library/record.vue b/culture/src/views/strain-library/main-cell-library/record.vue index e434d70..aef2643 100644 --- a/culture/src/views/strain-library/main-cell-library/record.vue +++ b/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 class="info-item flex-column"> + <span class="label">保藏位置:</span> + <span class="value">{{ detail.saveLocation }}</span> </div> </div> + + <!-- 第二行 --> <div class="info-row"> - <div class="info-item"> - <span class="label">菌种保存方法:</span> - <span class="value">{{ strainInfo.storage }}</span> + <div class="info-item left-column"> + <span class="label">菌种名称:</span> + <span class="value">{{ detail.strainName }}</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 class="info-item flex-column full-width"> + <span class="label">特性描述:</span> + <span class="value">{{ detail.features }}</span> </div> </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.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> - - <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> - </span> - </el-dialog> + <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-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: '' - }, - 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' } - ] - } - } + currentRecord: {}, + addDialogVisible: false, + dialogType: "detail", + roleType: "", + }; }, - created() { - // 获取路由参数中的菌种ID - this.strainId = this.$route.query.id - // 实际项目中这里应该根据ID加载菌种信息和记录列表 - console.log('加载菌种ID:', this.strainId) + 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(); + } }, 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; - 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; -} - -.strain-info { - padding: 10px 0; - - .info-row { - display: flex; - flex-wrap: wrap; - margin-bottom: 16px; - - &:last-child { - margin-bottom: 0; - } - } - - .info-item { - flex: 1; - min-width: 200px; - margin-right: 20px; - - &:last-child { - margin-right: 0; - } - - &.full { - flex: 2; - } - - .label { - color: #606266; - margin-right: 8px; - } - - .value { - color: #333; - font-weight: 500; - - &.status { - color: #67C23A; - } - } - } -} - -.record-timeline-container { - margin-top: 30px; - - .section-title { - font-size: 16px; - color: #333; + .header-box { margin-bottom: 20px; - font-weight: 500; - } -} + border-radius: 16px; + background: rgba(255, 255, 255, 0.8); + height: 130px; + overflow: hidden; -:deep(.el-dialog__body) { - padding: 20px 30px; -} + .header-content { + color: rgba(0, 0, 0, 0.88); + font-size: 14px; + line-height: 1.5; -@media screen and (max-width: 768px) { - .strain-info { - .info-row { - flex-direction: column; - - .info-item { - margin-right: 0; - margin-bottom: 10px; - + .info-row { + display: flex; + flex-wrap: wrap; + margin-bottom: 8px; + &:last-child { margin-bottom: 0; } + + .info-item { + 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 { + flex: 1; + min-width: 300px; + } + + .label { + color: #606266; + margin-right: 8px; + white-space: nowrap; + } + + .value { + flex: 1; + color: #303133; + word-break: break-all; + display: -webkit-box; + -webkit-line-clamp: 1; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; + } + } } } } - - .page-header { - flex-direction: column; - align-items: flex-start; - - .header-right { - margin-top: 16px; + + .flex { + display: flex; + align-items: center; + } + + .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; } } + + .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: bold; + } + + p { + margin: 5px 0; + font-size: 14px; + } + } + } + + .operation-btn { + margin-right: 12px; + } } -</style> \ No newline at end of file +</style> diff --git a/culture/src/views/strain-library/main-cell-library/service.js b/culture/src/views/strain-library/main-cell-library/service.js new file mode 100644 index 0000000..08f789d --- /dev/null +++ b/culture/src/views/strain-library/main-cell-library/service.js @@ -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 }) +} diff --git a/culture/src/views/strain-library/production-cell-library/add.vue b/culture/src/views/strain-library/production-cell-library/add.vue index 132d57e..058aa82 100644 --- a/culture/src/views/strain-library/production-cell-library/add.vue +++ b/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; - margin-bottom: 24px; - - @media screen and (max-width: 1200px) { - grid-template-columns: repeat(2, 1fr); + .form-card { + background: #fff; + border-radius: 8px; + } +} + +.header-title { + margin-bottom: 24px; + + &-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; @@ -188,4 +426,4 @@ margin: 0; } } -</style> +</style> \ No newline at end of file diff --git a/culture/src/views/strain-library/production-cell-library/index.vue b/culture/src/views/strain-library/production-cell-library/index.vue index 1364cbf..31cfb28 100644 --- a/culture/src/views/strain-library/production-cell-library/index.vue +++ b/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="drafts" + :class="{ active: currentType === 'draft' }" + @click="handleTypeChange('draft')" + > + 草稿箱 + </div> </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 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')}` - }); - } - - this.tableData = mockData; - this.total = 100; // 模拟总数 - this.loading = false; - }, 500); + getList(params) + .then((res) => { + if (res.code === 200) { + this.tableData = res.data.records; + this.total = res.data.total; + } + }) + .catch((err) => { + this.$message.error("数据加载失败"); + }); }, - - // 状态标签类型 - 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,28 +448,15 @@ 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 { font-size: 18px; font-weight: bold; @@ -433,11 +474,11 @@ p { margin: 12px 0; - + &:first-child { margin-top: 0; } - + &:last-child { margin-bottom: 0; } @@ -445,8 +486,4 @@ } } } - -.delete-btn { - color: #F56C6C; -} -</style> \ No newline at end of file +</style> diff --git a/culture/src/views/strain-library/production-cell-library/record.vue b/culture/src/views/strain-library/production-cell-library/record.vue index b23bd7a..aef2643 100644 --- a/culture/src/views/strain-library/production-cell-library/record.vue +++ b/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> + <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">{{ detail.strainCode }}</span> + </div> + <div class="info-item flex-column"> + <span class="label">鉴定方法:</span> + <span class="value">{{ detail.appraisalMethod }}</span> + </div> + <div class="info-item flex-column"> + <span class="label">保藏位置:</span> + <span class="value">{{ detail.saveLocation }}</span> + </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 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> - - <div class="strain-info"> - <div class="info-item"> - <span class="label">菌种编号:</span> - <span class="value">{{ strainData.strainNo }}</span> - </div> - <div class="info-item"> - <span class="label">菌种名称:</span> - <span class="value">{{ strainData.strainName }}</span> - </div> - <div class="info-item"> - <span class="label">菌种来源:</span> - <span class="value">{{ strainData.source }}</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.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 ? '' : '暂无使用记录'" - > - <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> + <!-- 出入库记录表格 --> + <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> + <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="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-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 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> - - <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> + </template> + <template #tableCustom v-if="currentType === 'timeline'"> + <record-timeline :list="timelineList" /> + </template> + </TableCustom> - <!-- 证书预览对话框 --> - <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, - - // 使用记录分页数据 - usageRecords: [], - usageCurrentPage: 1, - usagePageSize: 10, - usageTotalCount: 0, - - // 测试记录分页数据 - testRecords: [], - testCurrentPage: 1, - testPageSize: 10, - testTotalCount: 0 - } + recordList: [], + timelineList: [], + dialogVisible: false, + currentRecord: {}, + addDialogVisible: false, + dialogType: "detail", + roleType: "", + }; }, - created() { - this.strainId = this.$route.params.id; - this.fetchStrainData(); - this.fetchUsageRecords(); - this.fetchTestRecords(); + 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(); + } }, 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' - }; - - this.loading = false; - }, 500); + handleDelete(row) { + this.$confirm("确定删除该数据吗?", "提示", { + confirmButtonText: "确定", + cancelButtonText: "取消", + type: "warning", + }).then(() => { + deleteWarehousing({ id: row.id }).then((res) => { + this.$message.success("删除成功"); + this.getRecordList(); + }); + }); }, - - // 获取使用记录 - 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); + getStrainDetail(id) { + // 这里应该调用接口获取菌种详情 + getDetail({ id }).then((res) => { + this.detail = res; + }); }, - - // 获取测试记录 - 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); + 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; + }); }, - - // 状态标签类型 - getStatusType(status) { - switch(status) { - case '正常': - return 'success'; - case '缺货': - return 'warning'; - case '异常': - return 'danger'; - case '已停用': - return 'info'; - default: - return 'info'; - } + handleView(row) { + this.dialogType = "detail"; + this.currentRecord = row; + this.dialogVisible = true; }, - - // 编辑菌种 - handleEdit() { - this.$router.push(`/strain-library/production-cell-library/edit/${this.strainId}`); + handleConfirm(row) { + this.dialogType = "confirm"; + this.currentRecord = row; + this.dialogVisible = true; }, - - // 打印菌种信息 - handlePrint() { - window.print(); + handlePageChange(page) { + this.queryForm.pageNum = page; + // 这里应该调用接口获取对应页码的数据 }, - - // 查看证书 - handleViewCertificate() { - this.certificateDialogVisible = true; + handleTypeChange(type) { + this.currentType = type; }, - - // 下载证书 - handleDownloadCertificate() { - // 实际项目中应处理文件下载逻辑 - window.open(this.strainData.certificateUrl, '_blank'); + handleAddRecord() { + this.addDialogVisible = true; }, - - // 新增使用记录 - handleAddUsage() { - this.$message.info('功能开发中:新增使用记录'); - // 实际项目中应跳转到新增使用记录页面或打开对话框 + handleDialogClose() { + this.currentRecord = {}; + this.dialogVisible = false; }, - - // 查看使用记录详情 - handleViewUsageDetail(row) { - this.$message.info(`查看使用记录: ${row.id}`); - // 实际项目中应跳转到使用记录详情页面或打开对话框 + 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); + } + }); }, - - // 编辑使用记录 - handleEditUsage(row) { - this.$message.info(`编辑使用记录: ${row.id}`); - // 实际项目中应跳转到编辑使用记录页面或打开对话框 + handleAddRecordConfirm(record) { + addWarehousing({ ...record, trainLibraryId: this.$route.query.id }).then( + (res) => { + this.$message.success("操作成功"); + this.getRecordList(); + } + ); }, - - // 使用记录分页切换 - handleUsagePageChange(page) { - this.usageCurrentPage = page; - this.fetchUsageRecords(); + goBack() { + this.$router.go(-1); }, - - // 新增测试记录 - 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-left { - display: flex; - align-items: center; - - 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 { - display: flex; - flex-wrap: wrap; - - .info-item { - width: 33.33%; - margin-bottom: 16px; - - &.full-width { - width: 100%; + .header-box { + margin-bottom: 20px; + border-radius: 16px; + background: rgba(255, 255, 255, 0.8); + height: 130px; + overflow: hidden; + + .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: 8px; + + &:last-child { + margin-bottom: 0; } - - .label { - font-weight: 500; - color: #606266; - } - - .value { - color: #303133; - - &.description { - white-space: pre-line; - line-height: 1.6; - padding: 8px 0; + + .info-item { + 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 { + flex: 1; + min-width: 300px; + } + + .label { + color: #606266; + margin-right: 8px; + white-space: nowrap; + } + + .value { + 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-card { - margin-bottom: 24px; - - .card-header { - display: flex; - justify-content: space-between; - align-items: center; - - .card-title { - font-size: 16px; - font-weight: 500; - } - } - - .pagination-container { - display: flex; - justify-content: flex-end; - margin-top: 20px; - } - } - - .certificate-preview { + + .flex { display: flex; - justify-content: center; align-items: center; - min-height: 500px; - background-color: #f5f7fa; } - - @media print { - .page-header, .header-actions, .record-card { - display: none; + + .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; } - - .strain-card { - border: none; - box-shadow: none; - - .card-header { - background-color: #fff !important; - border-bottom: 1px solid #ddd; + + .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: bold; + } + + p { + margin: 5px 0; + font-size: 14px; } } + } + + .operation-btn { + margin-right: 12px; } } -</style> \ No newline at end of file +</style> diff --git a/culture/src/views/strain-library/production-cell-library/service.js b/culture/src/views/strain-library/production-cell-library/service.js new file mode 100644 index 0000000..08f789d --- /dev/null +++ b/culture/src/views/strain-library/production-cell-library/service.js @@ -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 }) +} diff --git a/culture/src/views/strain-library/strain-library-manage/add.vue b/culture/src/views/strain-library/strain-library-manage/add.vue index 1894619..0c4dbd1 100644 --- a/culture/src/views/strain-library/strain-library-manage/add.vue +++ b/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.signatureVisible = true + this.form.isDraft = isDraft + if (isDraft == 1) { + //存草稿 + this.handleSignatureConfirm('') + } else { + this.signatureVisible = true + } } }) }, handleBatchAdd() { - this.batchAddDialogVisible = true + 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) - } else if (this.currentAction === 'batchAdd') { - // 处理批量新增逻辑 - console.log('batch add with signature:', this.batchForm.count, 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') { + await addBatch(requestData); + } else { + await add(requestData); + } + this.signatureVisible = false; + this.$router.back(); + this.$message.success('操作成功'); + } catch (error) { + this.$message.error('操作失败'); } } } @@ -205,7 +230,7 @@ &-left { display: flex; align-items: center; - + img { width: 20px; height: 20px; @@ -219,18 +244,20 @@ } } } -.end-btn{ + +.end-btn { display: flex; align-items: center; justify-content: center; gap: 10px; - button{ + button { width: 180px; height: 36px; // background: #409EFF; } } + .strain-form { padding: 0 40px; @@ -241,14 +268,16 @@ margin-bottom: 24px; &.three-columns { - .el-form-item, .form-item-placeholder { + + .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%; } @@ -300,7 +329,7 @@ padding: 20px; text-align: center; border-bottom: 1px solid #EBEEF5; - + .el-dialog__title { font-size: 16px; font-weight: 600; @@ -334,6 +363,7 @@ color: #606266; font-weight: normal; padding-bottom: 8px; + &::before { color: #F56C6C; } @@ -341,8 +371,9 @@ :deep(.el-input) { width: 100%; + input { - width: 100%; + width: 100%; } } } @@ -350,6 +381,7 @@ .dialog-notice { margin-top: 16px; text-align: center; + p { margin: 0; line-height: 22px; @@ -361,7 +393,7 @@ :deep(.el-dialog__footer) { padding: 0 20px 20px; text-align: center; - + .el-button { width: 180px; height: 36px; @@ -394,4 +426,4 @@ margin: 0; } } -</style> +</style> \ No newline at end of file diff --git a/culture/src/views/strain-library/strain-library-manage/components/AddRecordDialog.vue b/culture/src/views/strain-library/strain-library-manage/components/AddRecordDialog.vue index cde2f2d..95b914d 100644 --- a/culture/src/views/strain-library/strain-library-manage/components/AddRecordDialog.vue +++ b/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 } } diff --git a/culture/src/views/strain-library/strain-library-manage/components/RecordDetailDialog.vue b/culture/src/views/strain-library/strain-library-manage/components/RecordDetailDialog.vue index dbff104..ffe34f8 100644 --- a/culture/src/views/strain-library/strain-library-manage/components/RecordDetailDialog.vue +++ b/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> </div> - <signature-canvas :visible.sync="showSignature" @confirm="handleSignatureConfirm" @cancel="showSignature = false" /> + <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() } @@ -128,7 +124,7 @@ padding: 20px 24px; margin: 0; border-bottom: 1px solid #DCDFE6; - + .el-dialog__title { font-size: 16px; font-weight: 600; @@ -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; @@ -232,4 +230,4 @@ .edit-sign-btn { margin-left: 12px; } -</style> \ No newline at end of file +</style> \ No newline at end of file diff --git a/culture/src/views/strain-library/strain-library-manage/components/RecordTimeline.vue b/culture/src/views/strain-library/strain-library-manage/components/RecordTimeline.vue index 4de0a9a..4691991 100644 --- a/culture/src/views/strain-library/strain-library-manage/components/RecordTimeline.vue +++ b/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') : + 'right-block', + item.confirmTime && item.confirmTime !== '--' ? + (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; } @@ -170,12 +175,13 @@ font-size: 16px; font-weight: bold; color: #fff; - background: linear-gradient( 180deg, #0ACBCA 0%, #049C9A 100%); - letter-spacing: 8px; /* 增加字间距 */ + background: linear-gradient(180deg, #0ACBCA 0%, #049C9A 100%); + letter-spacing: 8px; + /* 增加字间距 */ } .left-block.out .type-tag { - background: linear-gradient( 180deg, #FDBF2D 0%, #FA8B14 100%); + background: linear-gradient(180deg, #FDBF2D 0%, #FA8B14 100%); } .info-main { @@ -245,10 +251,12 @@ /* 添加媒体查询,适配小屏幕设备 */ @media screen and (max-width: 1200px) { - .left-block, .right-block { + + .left-block, + .right-block { min-width: 240px; } - + .timeline-row { gap: 10px; } @@ -256,56 +264,58 @@ @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%; } - + :deep(.el-timeline-item) { position: relative; height: auto; min-height: 176px; margin-bottom: 40px; } - + :deep(.el-timeline-item__wrapper) { height: auto !important; min-height: 176px; } - + :deep(.el-timeline-item__tail) { height: calc(100% + 40px); } - + :deep(.el-timeline-item:last-of-type) { margin-bottom: 0; } - + :deep(.el-timeline-item:last-of-type .el-timeline-item__tail) { height: 34px; } - + /* 第一个元素的轴线需要特殊处理 */ :deep(.el-timeline-item:first-of-type .el-timeline-item__tail) { top: 34px; height: calc(100% + 40px - 34px); } - + /* 确保所有轴线正确连接 */ :deep(.el-timeline-item__tail) { top: 0; height: calc(100% + 40px); } - + /* 修正内容区域的高度 */ :deep(.el-timeline-item__content) { height: auto; min-height: 176px; } } -</style> \ No newline at end of file +</style> \ No newline at end of file diff --git a/culture/src/views/strain-library/strain-library-manage/components/StrainDetail.vue b/culture/src/views/strain-library/strain-library-manage/components/StrainDetail.vue index 6b64853..342fec3 100644 --- a/culture/src/views/strain-library/strain-library-manage/components/StrainDetail.vue +++ b/culture/src/views/strain-library/strain-library-manage/components/StrainDetail.vue @@ -1,207 +1,286 @@ <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)" + title="原始细胞库详情" + :visible.sync="visible" + width="70%" + :close-on-click-modal="false" + custom-class="strain-detail-dialog" + @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> - </div> - <div class="info-item flex-column"> - <span class="label">鉴定方法:</span> - <span class="value">{{ detail.method }}</span> - </div> - <div class="info-item flex-column"> - <span class="label">保存位置:</span> - <span class="value">{{ detail.amount }}</span> - </div> - </div> - - <!-- 第二行信息 --> - <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.certificate }}</span> - </div> - </div> - - <!-- 第三行信息 --> - <div class="info-row"> - <div class="info-item left-column"> - <span class="label">菌种来源:</span> - <span class="value">{{ detail.source }}</span> - </div> - <div class="info-item flex-column"> - <span class="label">菌种保存方法:</span> - <span class="value">{{ detail.storage }}</span> - </div> - <div class="info-item flex-column"> - <span class="label">出入库状态:</span> - <span class="value">{{ detail.operator }}</span> - </div> - </div> + <div class="strain-info"> + <!-- 第一行信息 --> + <div class="info-row"> + <div class="info-item left-column"> + <span class="label">菌种编号:</span> + <span class="value">{{ detail.strainCode }}</span> + </div> + <div class="info-item flex-column"> + <span class="label">鉴定方法:</span> + <span class="value">{{ detail.appraisalMethod }}</span> + </div> + <div class="info-item flex-column"> + <span class="label">保藏位置:</span> + <span class="value">{{ detail.saveLocation }}</span> + </div> </div> - <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="状态"> - <template #default="{ row }"> - <el-tag :type="row.status === '已确认' ? 'success' : 'warning'"> - {{ row.status }} - </el-tag> - </template> - </el-table-column> - <el-table-column label="操作" width="100"> - <template #default="{ row }"> - <el-button type="text" @click="handleView(row)">详情</el-button> - </template> - </el-table-column> - </el-table> - <div class="pagination"> - <el-pagination - :current-page.sync="currentPage" - :page-size="10" - layout="total, prev, pager, next" - :total="total" - @current-change="handlePageChange" - /> - </div> + <!-- 第二行信息 --> + <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 class="info-item flex-column"> + <span class="label">出入库状态:</span> + <span class="value">{{ + { + 1: "已出库", + 2: "出库待确认", + 3: "已入库", + 4: "入库待确认", + }[detail.status] || "" + }}</span> + </div> + </div> + </div> + + <div class="record-table"> + <div class="table-title">原始细胞库出/入库记录</div> + <el-table :data="detail.records" style="width: 100%"> + <el-table-column label="出库/入库"> + <template #default="{ row }"> + {{ { 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 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> + <div class="pagination"> + <el-pagination + :current-page.sync="currentPage" + :page-size="10" + layout="total, prev, pager, next" + :total="total" + @current-change="handlePageChange" + /> + </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', - props: { - visible: { - type: Boolean, - default: false - }, - detail: { - type: Object, - default: () => ({}) - } + components: { RecordDetailDialog }, + name: "StrainDetail", + props: { + visible: { + type: Boolean, + default: false, }, - data() { - return { - currentPage: 1, - total: 0 - } + detail: { + type: Object, + default: () => ({}), }, - methods: { - handleView(row) { - console.log('View record:', row) - }, - handlePageChange(page) { - this.currentPage = page - this.$emit('page-change', page) + }, + data() { + return { + visibleRecordDetailDialog: false, + recordData: {}, + currentPage: 1, + 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) { + 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); + }, + }, +}; </script> <style lang="less" scoped> .strain-detail-dialog { - :deep(.el-dialog__header) { - padding: 20px; - border-bottom: 1px solid #EBEEF5; - margin-right: 0; - - .el-dialog__title { - font-size: 18px; - font-weight: bold; - color: #303133; - } - } + :deep(.el-dialog__header) { + padding: 20px; + border-bottom: 1px solid #ebeef5; + margin-right: 0; - :deep(.el-dialog__body) { - padding: 20px; + .el-dialog__title { + font-size: 18px; + font-weight: bold; + color: #303133; } + } + + :deep(.el-dialog__body) { + padding: 20px; + } } .strain-info { - background: #F5F7FA; - border-radius: 4px; - padding: 20px; - margin-bottom: 20px; + background: #f5f7fa; + border-radius: 4px; + padding: 20px; + margin-bottom: 20px; - .info-row { - display: flex; - flex-wrap: wrap; - margin-bottom: 16px; - - &:last-child { - margin-bottom: 0; - } - - .info-item { - display: flex; - align-items: flex-start; - margin-right: 24px; - margin-bottom: 8px; - - &.left-column { - width: 33%; - min-width: 200px; - } - - &.flex-column { - flex: 1; - min-width: 150px; - } - - &.full-width { - flex: 1; - min-width: 300px; - } - - .label { - color: #606266; - margin-right: 8px; - white-space: nowrap; - } - - .value { - flex: 1; - color: #303133; - word-break: break-all; - } - } + .info-row { + display: flex; + flex-wrap: wrap; + margin-bottom: 16px; + + &:last-child { + margin-bottom: 0; } + + .info-item { + display: flex; + align-items: flex-start; + margin-right: 24px; + margin-bottom: 8px; + + &.left-column { + width: 33%; + min-width: 200px; + } + + &.flex-column { + flex: 1; + min-width: 150px; + } + + &.full-width { + flex: 1; + min-width: 300px; + } + + .label { + color: #606266; + margin-right: 8px; + white-space: nowrap; + } + + .value { + flex: 1; + color: #303133; + word-break: break-all; + } + } + } } .record-table { - .table-title { - font-size: 16px; - font-weight: bold; - color: #303133; - margin-bottom: 16px; - padding-left: 8px; - border-left: 4px solid #049C9A; - } + .table-title { + font-size: 16px; + font-weight: bold; + color: #303133; + margin-bottom: 16px; + padding-left: 8px; + border-left: 4px solid #049c9a; + } - .pagination { - margin-top: 20px; - display: flex; - justify-content: center; - } + .pagination { + margin-top: 20px; + display: flex; + justify-content: center; + } } -</style> \ No newline at end of file +</style> diff --git a/culture/src/views/strain-library/strain-library-manage/index.vue b/culture/src/views/strain-library/strain-library-manage/index.vue index f5e0be6..9917b97 100644 --- a/culture/src/views/strain-library/strain-library-manage/index.vue +++ b/culture/src/views/strain-library/strain-library-manage/index.vue @@ -1,470 +1,489 @@ <template> - <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> - </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> + <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 + > + </div> + <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="菌种源保藏出/入细胞库登记表说明" - :visible.sync="dialogVisible" - width="50%" - class="view-all-dialog" + <!-- 查看全部弹窗 --> + <el-dialog + title="菌种源保藏出/入细胞库登记表说明" + :visible.sync="dialogVisible" + width="50%" + class="view-all-dialog" + > + <div class="dialog-content"> + <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" + > + <template #search> + <el-form :model="form" label-width="auto" inline> + <el-form-item label="菌种编号:"> + <el-input v-model="form.strainNo" placeholder="请输入"></el-input> + </el-form-item> + <el-form-item label="菌种名称:"> + <el-input v-model="form.strainName" placeholder="请输入"></el-input> + </el-form-item> + <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="4"></el-option> + </el-select> + </el-form-item> + <el-form-item class="search-btn-box"> + <el-button type="default" @click="resetForm">重置</el-button> + <el-button type="primary" @click="searchData">查询</el-button> + </el-form-item> + </el-form> + </template> + + <template #setting> + <div class="tableTitle"> + <div class="flex a-center"> + <div + class="title" + :class="{ active: currentType === 'list' }" + @click="handleTypeChange('list')" > - <div class="dialog-content"> - <p>1、菌种全部集中登记在【菌种源保藏出/入细胞库登记表】,请将来源有3 类菌经。</p> - <p>1.1 原净土管理日油性的源头菌种:入细胞细胞库(现代-O)。</p> - <p>1.2 是到菌的源头菌种:接种入主细胞库(现代-O),经过百种、验证后,菌种被保存日油管理沙土菌种,入细胞细胞库(现代-O)。</p> - <p>1.3 是否菌种能自己分离后获得的源头菌种,接种入主细胞库:经过产验证后,保藏为少土管理日油管,入细胞细胞库(现代-O)。</p> - </div> - </el-dialog> - </el-card> + 原始细胞列表 + </div> + <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> - <!-- Table --> - <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="菌种编号:"> - <el-input v-model="form.strainNo" placeholder="请输入"></el-input> - </el-form-item> - <el-form-item label="菌种名称:"> - <el-input v-model="form.strainName" placeholder="请输入"></el-input> - </el-form-item> - <el-form-item 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-select> - </el-form-item> - <el-form-item class="search-btn-box"> - <el-button type="default" @click="resetForm">重置</el-button> - <el-button type="primary" @click="searchData">查询</el-button> - </el-form-item> - </el-form> - </template> - - <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> - <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> - </div> - </template> - - <template #table> - <el-table-column prop="strainNo" 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="当前状态"> - <template #default="{ row }"> - <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> - </template> - </el-table-column> - </template> - </TableCustom> - <StrainDetail - :visible.sync="detailVisible" - :detail="currentDetail" - /> - </div> + <template #table> + <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="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> + <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', - components: { - StrainDetail + name: "StrainLibraryManage", + components: { + StrainDetail, + }, + data() { + return { + dialogVisible: false, + currentType: "list", + detailVisible: false, + currentDetail: {}, + form: { + strainNo: "", + strainName: "", + status: "", + }, + queryForm: { + pageSize: 10, + pageNum: 1, + }, + total: 800, + tableData: [], + roleType: "", + }; + }, + 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(); + }); + }); }, - data() { - return { - dialogVisible: false, - currentType: 'list', - detailVisible: false, - currentDetail: {}, - form: { - strainNo: '', - strainName: '', - status: '' - }, - queryForm: { - pageSize: 10, - pageNum: 1 - }, - total: 800, - tableData: [ - { - strainNo: 'YX-2024001', - strainName: '大肠杆菌', - source: '实验室分离', - method: '形态学鉴定、生理生化试验', - certificate: '革兰氏阴性杆菌,可发酵葡萄糖产酸产气,IMViC试验++--', - storage: '斜面培养', - amount: 'A区-01-001', - inventory: '50', - notes: '用于质粒转化', - status: '1' - }, - { - 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' - } - ] - } + handleRecord(row) { + this.$router.push({ + path: `/strain-library/strain-library-manage/record?id=${row.id}`, + }); }, - methods: { - handleViewMore() { - this.dialogVisible = true; - }, - 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) - }, - handleNewStrain() { - this.$router.push('/strain-library/strain-library-manage/add') - // Implement new strain logic - }, - handleBatchAdd() { - // Implement batch add logic - }, - handleDetail(row) { - this.currentDetail = row; - this.detailVisible = true; - }, - handleEdit(row) { - // Implement edit logic - }, - handleRecord(row) { - this.$router.push({ - path: '/strain-library/strain-library-manage/record', - query: { - id: row.strainNo - } - }) - }, - handleCurrentChange(page) { - this.queryForm.pageNum = page - // Implement page change logic - }, - handleSizeChange(size) { - this.queryForm.pageSize = size - // Implement size change logic - }, - handleTypeChange(type) { - this.currentType = type; - // Implement type change logic - }, - getStatusType(status) { - const types = { - 1: 'success', - 2: 'info', - 3: 'warning' - } - return types[status] || 'info' - }, - getStatusText(status) { - const texts = { - 1: '已入库', - 2: '已出库', - 3: '入库待确认' - } - return texts[status] || '未知状态' - } - } -} + handleNewStrain() { + this.$router.push({ path: "/strain-library/strain-library-manage/add" }); + }, + handleEdit(row) { + this.$router.push({ + path: `/strain-library/strain-library-manage/add?id=${row.id}`, + }); + }, + handleDetail(row) { + this.currentDetail = row; + this.detailVisible = true; + }, + handleViewMore() { + this.dialogVisible = true; + }, + 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; + this.searchData(); + }, + handleSizeChange(size) { + this.queryForm.pageSize = size; + this.searchData(); + }, + handleTypeChange(type) { + this.currentType = type; + this.searchData(); + }, + getStatusType(status) { + const types = { + 1: "warning", + 2: "warning", + 3: "success", + 4: "success", + }; + return types[status] || "info"; + }, + getStatusText(status) { + const texts = { + 1: "已出库", + 2: "出库待确认", + 3: "已入库", + 4: "入库待确认", + }; + return texts[status] || "未知状态"; + }, + }, +}; </script> <style scoped lang="less"> .list { - padding: 20px; + padding: 20px; } .header-box { - margin-bottom: 20px; - border-radius: 16px; - background: rgba(255, 255, 255, 0.8); + margin-bottom: 20px; + border-radius: 16px; + background: rgba(255, 255, 255, 0.8); - .box-title { - display: flex; - align-items: center; - font-size: 18px; - font-weight: bold; - margin-bottom: 15px; - position: relative; + .box-title { + display: flex; + align-items: center; + font-size: 18px; + font-weight: bold; + margin-bottom: 15px; + position: relative; - .header-icon { - width: 20px; - height: 20px; - margin-right: 10px; - } - - .view-more { - position: absolute; - right: 0; - color: #049C9A; - } + .header-icon { + width: 20px; + height: 20px; + margin-right: 10px; } - .header-content { - color: rgba(0, 0, 0, 0.88); - font-size: 14px; - line-height: 1.8; - margin-left: 30px; - transition: max-height 0.3s ease-in-out; - overflow: hidden; - - &.collapsed { - max-height: 48px; - overflow: hidden; - } - - p { - margin: 5px 0; - } + .view-more { + position: absolute; + right: 0; + color: #049c9a; } + } + + .header-content { + color: rgba(0, 0, 0, 0.88); + font-size: 14px; + line-height: 1.8; + margin-left: 30px; + transition: max-height 0.3s ease-in-out; + overflow: hidden; + + &.collapsed { + max-height: 48px; + overflow: hidden; + } + + p { + margin: 5px 0; + } + } } .search-form { - margin-bottom: 20px; - background: #F5F7FA; - padding: 24px; - border-radius: 8px; + margin-bottom: 20px; + background: #f5f7fa; + padding: 24px; + border-radius: 8px; - .el-form-item { - margin-right: 20px; - margin-bottom: 0; - } + .el-form-item { + margin-right: 20px; + margin-bottom: 0; + } - .el-button { - margin-left: 10px; - } + .el-button { + margin-left: 10px; + } } .action-buttons { - margin-bottom: 20px; + margin-bottom: 20px; - .el-button { - margin-right: 10px; - } + .el-button { + margin-right: 10px; + } } .tab-container { - display: flex; - margin-bottom: 20px; + 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; + .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; - } + &.active { + background: #fff; + border-color: #049c9a; + color: #049c9a; + font-weight: bold; } + } } .flex { - display: flex; - align-items: center; + display: flex; + align-items: center; } .tableTitle { - display: flex; - padding-bottom: 20px; - justify-content: space-between; - align-items: center; + 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; - width: unset; - cursor: pointer; - height: 50px; - line-height: 50px; - width: 166px; - text-align: center; + .title { + background: #fafafc; + border-radius: 8px 8px 0px 0px; + border: 1px solid #dcdfe6; + font-weight: bold; + font-size: 18px; + color: #606266; + width: unset; + cursor: pointer; + height: 50px; + line-height: 50px; + width: 166px; + 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: 166px; + 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: 166px; - text-align: center; - } - - .active { - color: #049c9a; - background: #ffffff; - border-radius: 8px 8px 0px 0px; - border: 1px solid #049c9a; - - } + .active { + color: #049c9a; + 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; - margin-right: 0; - - .el-dialog__title { - font-size: 18px; - font-weight: bold; - color: #303133; - } + :deep(.el-dialog__header) { + padding: 20px; + border-bottom: 1px solid #ebeef5; + margin-right: 0; + + .el-dialog__title { + font-size: 18px; + font-weight: bold; + color: #303133; } + } - :deep(.el-dialog__body) { - padding: 20px; + :deep(.el-dialog__body) { + padding: 20px; - .dialog-content { - font-size: 14px; - line-height: 1.8; - color: #606266; + .dialog-content { + font-size: 14px; + line-height: 1.8; + color: #606266; - p { - margin: 12px 0; - - &:first-child { - margin-top: 0; - } - - &:last-child { - margin-bottom: 0; - } - } + p { + margin: 12px 0; + + &:first-child { + margin-top: 0; } + + &:last-child { + margin-bottom: 0; + } + } } + } } - </style> diff --git a/culture/src/views/strain-library/strain-library-manage/record.vue b/culture/src/views/strain-library/strain-library-manage/record.vue index 3c145dc..03eab17 100644 --- a/culture/src/views/strain-library/strain-library-manage/record.vue +++ b/culture/src/views/strain-library/strain-library-manage/record.vue @@ -1,417 +1,438 @@ <template> - <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">{{ detail.strainNo }}</span> - </div> - <div class="info-item flex-column"> - <span class="label">鉴定方法:</span> - <span class="value">{{ detail.method }}</span> - </div> - <div class="info-item flex-column"> - <span class="label">保藏位置:</span> - <span class="value">{{ detail.amount }}</span> - </div> - </div> - - <!-- 第二行 --> - <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.certificate }}</span> - </div> - </div> - - <!-- 第三行 --> - <div class="info-row"> - <div class="info-item left-column"> - <span class="label">菌种来源:</span> - <span class="value">{{ detail.source }}</span> - </div> - <div class="info-item flex-column"> - <span class="label">菌种保存方法:</span> - <span class="value">{{ detail.storage }}</span> - </div> - </div> + <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">{{ detail.strainCode }}</span> + </div> + <div class="info-item flex-column"> + <span class="label">鉴定方法:</span> + <span class="value">{{ detail.appraisalMethod }}</span> + </div> + <div class="info-item flex-column"> + <span class="label">保藏位置:</span> + <span class="value">{{ detail.saveLocation }}</span> + </div> + </div> + + <!-- 第二行 --> + <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> + + <!-- 出入库记录表格 --> + <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> - </el-card> + <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> - <!-- 出入库记录表格 --> - <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> - <div class="flex a-center"> - <el-button @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-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> - <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="status" label="状态"> - <template #default="{ row }"> - <el-tag :type="row.status === '已确认' ? 'success' : 'warning'"> - {{ row.status }} - </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> - </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" - /> - - <add-record-dialog - :visible.sync="addDialogVisible" - @confirm="handleAddRecordConfirm" - /> - </div> + <!-- 详情弹窗 --> + <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 './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', - components: { - RecordDetailDialog, - AddRecordDialog, - RecordTimeline - }, - data() { - return { - currentType: 'table', - detail: {}, - currentPage: 1, - pageSize: 10, - total: 0, - queryForm: { - pageSize: 10, - 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: '已确认' - } - ], - dialogVisible: false, - currentRecord: {}, - addDialogVisible: false - } - }, - computed: { - timelineList() { - // 可根据需要处理数据格式,这里直接用 recordList - return this.recordList.map(item => ({ - ...item, - confirmTime: item.confirmTime || item.operateTime // 若无确认时间则用操作时间 - })) - } - }, - created() { - // 获取路由参数中的菌种信息 - const strainId = this.$route.query.id - if (strainId) { - this.getStrainDetail(strainId) - this.getRecordList() - } - }, - methods: { - getStrainDetail(id) { - // 这里应该调用接口获取菌种详情 - // 暂时使用模拟数据 - this.detail = { - strainNo: '3418732431', - strainName: '名称名称名称', - source: '来源11111111111', - method: '1231231', - certificate: '特性描述', - storage: '方法方法', - amount: '位置位置位置位置位置位置位置位置', - operator: '入库' - } - }, - getRecordList() { - // 这里应该调用接口获取出入库记录 - // 暂时使用已有模拟数据 - this.total = this.recordList.length - }, - 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 - }, - handlePageChange(page) { - this.queryForm.pageNum = page - // 这里应该调用接口获取对应页码的数据 - }, - handleTypeChange(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: '已取消删除' - }) - }) - }, - handleDialogClose() { - this.currentRecord = {} - this.dialogVisible = false - }, - handleOutbound(data) { - // 这里调用出库API - console.log('出库操作:', data) - this.$message.success('出库成功') - this.dialogVisible = false - // 刷新列表 - this.getRecordList() - }, - handleAddRecordConfirm(record) { - // 这里可以将新记录添加到 recordList 或调用后端API - this.$message.success('新增出入库记录成功') - // 例如:this.recordList.push(record) - this.getRecordList() // 或刷新列表 - }, - goBack() { - this.$router.go(-1) - } + name: "StrainRecord", + components: { + RecordDetailDialog, + AddRecordDialog, + RecordTimeline, + }, + data() { + return { + currentType: "table", + detail: {}, + currentPage: 1, + pageSize: 10, + total: 0, + queryForm: { + pageSize: 10, + pageNum: 1, + }, + recordList: [], + timelineList: [], + dialogVisible: false, + currentRecord: {}, + addDialogVisible: false, + dialogType: "detail", + roleType: "", + }; + }, + created() { + 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(); } -} + }, + methods: { + 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); + }, + }, +}; </script> <style lang="less" scoped> .record-page { - min-height: 100vh; + min-height: 100vh; - .header-box { - margin-bottom: 20px; - border-radius: 16px; - background: rgba(255, 255, 255, 0.8); - height: 130px; - overflow: hidden; + .header-box { + margin-bottom: 20px; + border-radius: 16px; + background: rgba(255, 255, 255, 0.8); + height: 130px; + overflow: hidden; - .header-content { - color: rgba(0, 0, 0, 0.88); - font-size: 14px; - line-height: 1.5; + .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: 8px; - - &:last-child { - margin-bottom: 0; - } - - .info-item { - 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 { - flex: 1; - min-width: 300px; - } - - .label { - color: #606266; - margin-right: 8px; - white-space: nowrap; - } - - .value { - flex: 1; - color: #303133; - word-break: break-all; - display: -webkit-box; - -webkit-line-clamp: 1; - -webkit-box-orient: vertical; - overflow: hidden; - text-overflow: ellipsis; - } - } - } - } - } - - .flex { + .info-row { display: flex; - align-items: center; - } + flex-wrap: wrap; + margin-bottom: 8px; - .tableTitle { - display: flex; - padding-bottom: 20px; - justify-content: space-between; - align-items: center; + &:last-child { + margin-bottom: 0; + } - .title { - background: #fafafc; - border-radius: 8px 8px 0px 0px; - border: 1px solid #dcdfe6; - font-weight: bold; - font-size: 18px; + .info-item { + 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 { + flex: 1; + min-width: 300px; + } + + .label { color: #606266; - cursor: pointer; - height: 50px; - line-height: 50px; - width: 280px; - text-align: center; - } + margin-right: 8px; + white-space: nowrap; + } - .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; + .value { + flex: 1; + color: #303133; + word-break: break-all; + display: -webkit-box; + -webkit-line-clamp: 1; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; + } } + } + } + } - .active { - color: #049c9a; - background: #ffffff; - border-radius: 8px 8px 0px 0px; - border: 1px solid #049c9a; - } + .flex { + display: flex; + align-items: center; + } + + .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; } - .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: bold; - } - - p { - margin: 5px 0; - font-size: 14px; - } - } + .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; } - .operation-btn { - margin-right: 12px; + .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: bold; + } + + p { + margin: 5px 0; + font-size: 14px; + } + } + } + + .operation-btn { + margin-right: 12px; + } } -</style> \ No newline at end of file +</style> diff --git a/culture/src/views/strain-library/strain-library-manage/service.js b/culture/src/views/strain-library/strain-library-manage/service.js new file mode 100644 index 0000000..08f789d --- /dev/null +++ b/culture/src/views/strain-library/strain-library-manage/service.js @@ -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 }) +} diff --git a/laboratory/src/router/index.js b/laboratory/src/router/index.js index c0077c8..d68bd14 100644 --- a/laboratory/src/router/index.js +++ b/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: { diff --git a/laboratory/src/views/dataManagement/approvalPlan/list.vue b/laboratory/src/views/dataManagement/approvalPlan/list.vue index e2858b2..994dc10 100644 --- a/laboratory/src/views/dataManagement/approvalPlan/list.vue +++ b/laboratory/src/views/dataManagement/approvalPlan/list.vue @@ -171,7 +171,7 @@ ], approvalDialogVisible: false, approvalDialogType: "approve", - currentApprovalData: null, + currentApprovalData: [], // 确认弹窗相关数据 changeStatus: false, changeStatusTitle: "", diff --git a/laboratory/src/views/reportLibrary/feasibilityReport/add.vue b/laboratory/src/views/reportLibrary/feasibilityReport/add.vue new file mode 100644 index 0000000..e39d9a7 --- /dev/null +++ b/laboratory/src/views/reportLibrary/feasibilityReport/add.vue @@ -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> \ No newline at end of file diff --git a/laboratory/src/views/reportLibrary/feasibilityReport/components/approval/index.vue b/laboratory/src/views/reportLibrary/feasibilityReport/components/approval/index.vue index 964ae96..7458017 100644 --- a/laboratory/src/views/reportLibrary/feasibilityReport/components/approval/index.vue +++ b/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() }, }, }; @@ -211,7 +281,7 @@ background: #ffffff; box-shadow: 0px 4px 12px 4px rgba(0, 0, 0, 0.08); border-radius: 10px; - + .flow-title { font-size: 16px; font-weight: bold; diff --git a/laboratory/src/views/reportLibrary/feasibilityReport/index.vue b/laboratory/src/views/reportLibrary/feasibilityReport/index.vue index 133cf68..a1d7fc2 100644 --- a/laboratory/src/views/reportLibrary/feasibilityReport/index.vue +++ b/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,118 +35,270 @@ </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', components: { - Approval + Approval }, 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() { - this.changeStatus = false - this.msgsuccess('操作成功') - this.rowId = '' - this.changeStatusTitle = '' - this.changeStatusTip = '' + revokeAudit({ id: this.rowId }).then(res => { + this.changeStatus = false + this.$message.success('操作成功') + this.rowId = '' + this.changeStatusTitle = '' + this.changeStatusTip = '' + this.getList() + }) + }, + handleCurrentChanges(page) { + this.form.pageNum = page this.getList() }, - handleCurrentChange(page) { - this.queryForm.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; diff --git a/laboratory/src/views/reportLibrary/feasibilityReport/service.js b/laboratory/src/views/reportLibrary/feasibilityReport/service.js new file mode 100644 index 0000000..e07b93f --- /dev/null +++ b/laboratory/src/views/reportLibrary/feasibilityReport/service.js @@ -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}`) +} + + + + + + diff --git a/laboratory/src/views/reportLibrary/feasibilityStudy/add.vue b/laboratory/src/views/reportLibrary/feasibilityStudy/add.vue index bb973f8..f812376 100644 --- a/laboratory/src/views/reportLibrary/feasibilityStudy/add.vue +++ b/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(){ - - }, - save(){ + 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 + }) + } + }) }, }, } diff --git a/laboratory/src/views/reportLibrary/feasibilityStudy/components/approval/index.vue b/laboratory/src/views/reportLibrary/feasibilityStudy/components/approval/index.vue index e0df73d..7458017 100644 --- a/laboratory/src/views/reportLibrary/feasibilityStudy/components/approval/index.vue +++ b/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,42 +157,94 @@ 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() @@ -220,7 +281,7 @@ background: #ffffff; box-shadow: 0px 4px 12px 4px rgba(0, 0, 0, 0.08); border-radius: 10px; - + .flow-title { font-size: 16px; font-weight: bold; diff --git a/laboratory/src/views/reportLibrary/feasibilityStudy/index.vue b/laboratory/src/views/reportLibrary/feasibilityStudy/index.vue index b7eaf55..3e49b8a 100644 --- a/laboratory/src/views/reportLibrary/feasibilityStudy/index.vue +++ b/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,63 +103,98 @@ }, data() { return { - form: { - name: '' - }, showDelConfirm: false, rowId: '', changeStatus: false, showApproval: false, changeStatusTitle: '', changeStatusTip: '', - tableData:[], + tableData: [], + isDraft: false, + rowData: {}, + roleType: '', // 1 超级管理员 2 审批人 3 工艺工程师 4化验师 5实验员 form: { pageSize: 10, pageNum: 1, - teamName:'', - status:'', - startTime:'', - reportType:1, - reportName:'', - reportCode:'', - endTime:'', - date:'' + teamName: '', + status: '', + startTime: '', + reportType: 1, + reportName: '', + reportCode: '', + endTime: '', + date: '' }, total: 0 } }, 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() { - this.changeStatus = false - this.msgsuccess('操作成功') - this.rowId = '' - this.changeStatusTitle = '' - this.changeStatusTip = '' - this.getList() + revokeAudit({ id: this.rowId }).then(res => { + this.changeStatus = false + this.$message.success('操作成功') + this.rowId = '' + this.changeStatusTitle = '' + this.changeStatusTip = '' + this.getList() + }) }, handleCurrentChanges(page) { this.form.pageNum = page @@ -160,21 +205,46 @@ 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() + } + }) } } } </script> <style scoped lang="less"> -.el-icon-plus{ +.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; } diff --git a/laboratory/src/views/reportLibrary/feasibilityStudy/service.js b/laboratory/src/views/reportLibrary/feasibilityStudy/service.js index c4bcf52..1ec73ac 100644 --- a/laboratory/src/views/reportLibrary/feasibilityStudy/service.js +++ b/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}`) +} + + + + + + diff --git a/laboratory/src/views/reportLibrary/processDevelopment/add.vue b/laboratory/src/views/reportLibrary/processDevelopment/add.vue new file mode 100644 index 0000000..82f7ce8 --- /dev/null +++ b/laboratory/src/views/reportLibrary/processDevelopment/add.vue @@ -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> \ No newline at end of file diff --git a/laboratory/src/views/reportLibrary/processDevelopment/components/approval/index.vue b/laboratory/src/views/reportLibrary/processDevelopment/components/approval/index.vue index 964ae96..7458017 100644 --- a/laboratory/src/views/reportLibrary/processDevelopment/components/approval/index.vue +++ b/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() }, }, }; @@ -211,7 +281,7 @@ background: #ffffff; box-shadow: 0px 4px 12px 4px rgba(0, 0, 0, 0.08); border-radius: 10px; - + .flow-title { font-size: 16px; font-weight: bold; diff --git a/laboratory/src/views/reportLibrary/processDevelopment/index.vue b/laboratory/src/views/reportLibrary/processDevelopment/index.vue index 013d8ec..bb62859 100644 --- a/laboratory/src/views/reportLibrary/processDevelopment/index.vue +++ b/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() { - this.changeStatus = false - this.msgsuccess('操作成功') - this.rowId = '' - this.changeStatusTitle = '' - this.changeStatusTip = '' + revokeAudit({ id: this.rowId }).then(res => { + this.changeStatus = false + this.$message.success('操作成功') + this.rowId = '' + this.changeStatusTitle = '' + this.changeStatusTip = '' + this.getList() + }) + }, + handleCurrentChanges(page) { + this.form.pageNum = page this.getList() }, - handleCurrentChange(page) { - this.queryForm.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; diff --git a/laboratory/src/views/reportLibrary/processDevelopment/service.js b/laboratory/src/views/reportLibrary/processDevelopment/service.js new file mode 100644 index 0000000..1ec73ac --- /dev/null +++ b/laboratory/src/views/reportLibrary/processDevelopment/service.js @@ -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}`) +} + + + + + + -- Gitblit v1.7.1