Merge branch 'main' of http://120.76.84.145:10101/gitblit/r/H5/leshan-laboratory
2 文件已重命名
26个文件已修改
39个文件已删除
9个文件已添加
| | |
| | | "lint": "vue-cli-service lint" |
| | | }, |
| | | "dependencies": { |
| | | "@antv/g6": "^4.8.24", |
| | | "@tinymce/tinymce-vue": "^3.2.8", |
| | | "aieditor": "^1.3.6", |
| | | "axios": "^0.24.0", |
| | | "core-js": "^3.41.0", |
| | | "element-ui": "^2.15.6", |
| | | "moment": "^2.30.1", |
| | | "sm-crypto": "^0.3.13", |
| | | "vue": "^2.7.16", |
| | | "vue-router": "^3.6.5", |
| | | "vuex": "^3.6.2" |
| | |
| | | <meta name="viewport" content="width=device-width,initial-scale=1.0"> |
| | | <link rel="icon" href="<%= BASE_URL %>logo.jpg"> |
| | | <!-- <link rel="icon" href="data:,"> --> |
| | | <title>实验室流程</title> |
| | | <title>菌种库</title> |
| | | </head> |
| | | |
| | | <body> |
| | |
| | | // 退出登录 |
| | | outLogin() { |
| | | sessionStorage.clear() |
| | | this.$router.replace({ path: "/" }); |
| | | this.$router.replace({ path: "/login" }); |
| | | }, |
| | | // 关闭标签 |
| | | closeTag(tag) { |
| | |
| | | <div class="image"> |
| | | <img src="../assets/logo.jpg" alt="" srcset="" /> |
| | | </div> |
| | | <div class="title">实验室流程</div> |
| | | <div class="title">菌种库</div> |
| | | </div> |
| | | <!-- 左侧菜单 --> |
| | | <div v-if="!isFold" class="sidebar-container"> |
| | |
| | | import "aieditor/dist/style.css" |
| | | import './assets/tailwind.css' |
| | | import './styles/element-variables.less' |
| | | import { fetchEncryptionKey } from './utils/encryption' |
| | | |
| | | Vue.config.productionTip = false; |
| | | Vue.use(ElementUI, { size: 'small' }) |
| | |
| | | return height |
| | | } |
| | | |
| | | // 获取加密密钥 |
| | | // fetchEncryptionKey() |
| | | |
| | | new Vue({ |
| | | router, |
| | | store, |
| | |
| | | } |
| | | */ |
| | | |
| | | const routes = [{ |
| | | const routes = [ |
| | | { |
| | | path: "/", |
| | | redirect: "/projectList/list", |
| | | }, |
| | |
| | | title: "项目组管理", |
| | | }, |
| | | component: Layouts, |
| | | children: [{ |
| | | children: [ |
| | | { |
| | | path: "list", |
| | | name: "ProjectList", |
| | | meta: { |
| | |
| | | title: "系统管理", |
| | | }, |
| | | component: Layouts, |
| | | children: [{ |
| | | children: [ |
| | | { |
| | | path: "user", |
| | | name: "User", |
| | | meta: { |
| | |
| | | meta: { |
| | | title: "菌种库", |
| | | }, |
| | | children: [{ |
| | | children: [ |
| | | { |
| | | path: "/strain-library", |
| | | component: Parent, |
| | | meta: { |
| | | title: "菌种库管理", |
| | | }, |
| | | children: [{ |
| | | children: [ |
| | | { |
| | | path: "strain-library-manage", |
| | | name: "StrainLibraryManage", |
| | | meta: { |
| | |
| | | title: "菌种传代生产谱系图", |
| | | }, |
| | | component: () => import("../views/pedigree-chart"), |
| | | }, |
| | | { |
| | | path: 'add-pedigree', |
| | | name: 'AddPedigree', |
| | | meta: { |
| | | title: "新增菌种传代生产谱系图", |
| | | }, |
| | | component: () => import("../views/pedigree-chart/add"), |
| | | }, |
| | | { |
| | | path: 'add-pedigree', |
| | | name: 'AddPedigree', |
| | | meta: { |
| | | title: "新增菌种传代生产谱系图", |
| | | }, |
| | | component: () => import("../views/pedigree-chart/add"), |
| | | } |
| | | ] |
| | | }, |
| | | |
| | | { |
| | | path: "/dataManagement", |
| | | component: Layouts, |
| | | meta: { |
| | | title: "实验室数据管理", |
| | | }, |
| | | children: [{ |
| | | path: "approvalPlan", |
| | | meta: { |
| | | title: "项目课题方案审批", |
| | | keepAlive: true, |
| | | }, |
| | | component: () => import("../views/dataManagement/approvalPlan/list.vue"), |
| | | }, |
| | | { |
| | | path: "addPlan", |
| | | name: "addPlan", |
| | | meta: { |
| | | title: "新增项目课题方案", |
| | | hide: true, |
| | | keepAlive: true, |
| | | }, |
| | | component: () => import("../views/dataManagement/approvalPlan/addPlan"), |
| | | }, |
| | | { |
| | | path: "dispatching", |
| | | meta: { |
| | | title: "实验调度管理", |
| | | keepAlive: true, |
| | | }, |
| | | component: () => import("../views/dataManagement/dispatching/list.vue"), |
| | | }, |
| | | { |
| | | path: "addDispatch", |
| | | meta: { |
| | | title: "新增实验调度", |
| | | hide: true, |
| | | keepAlive: true, |
| | | }, |
| | | component: () => import("../views/dataManagement/dispatching/addDispatch"), |
| | | }, |
| | | { |
| | | path: "confirmation-sheet", |
| | | name: "ConfirmationSheet", |
| | | meta: { |
| | | title: "检验方法确认单", |
| | | }, |
| | | component: () => import("../views/dataManagement/confirmation-sheet"), |
| | | }, |
| | | { |
| | | path: "confirmation-sheet/add", |
| | | name: "AddConfirmationSheet", |
| | | meta: { |
| | | title: "检验方法确认单", |
| | | }, |
| | | component: () => import("../views/dataManagement/confirmation-sheet/components/add.vue"), |
| | | }, |
| | | { |
| | | path: "/sampleManage", |
| | | meta: { |
| | | title: "样品管理", |
| | | // keepAlive: true, |
| | | }, |
| | | component: Parent, |
| | | children: [{ |
| | | path: "manage", |
| | | meta: { |
| | | title: "样品管理", |
| | | keepAlive: true, |
| | | }, |
| | | component: () => import("../views/dataManagement/sampleManage/list.vue"), |
| | | }, |
| | | { |
| | | path: "addSample", |
| | | name: "addPaddSamplelan", |
| | | meta: { |
| | | title: "新增样品", |
| | | hide: true, |
| | | keepAlive: true, |
| | | }, |
| | | component: () => import("../views/dataManagement/sampleManage/addSample"), |
| | | }, { |
| | | path: "record", |
| | | meta: { |
| | | title: "取样操作记录列表", |
| | | keepAlive: true, |
| | | }, |
| | | component: () => import("../views/dataManagement/sampleRecordList/list.vue"), |
| | | }, |
| | | { |
| | | path: "changeRecord", |
| | | name: "changeRecord", |
| | | meta: { |
| | | title: "填写取样操作记录", |
| | | hide: true, |
| | | keepAlive: true, |
| | | }, |
| | | component: () => import("../views/dataManagement/sampleRecordList/changeRecord"), |
| | | }, { |
| | | path: "submissionList", |
| | | meta: { |
| | | title: "送样单列表", |
| | | keepAlive: true, |
| | | }, |
| | | component: () => import("../views/dataManagement/sampleSubmissionList/list.vue"), |
| | | }, |
| | | { |
| | | path: "submission", |
| | | name: "submission", |
| | | meta: { |
| | | title: "送样单详情", |
| | | hide: true, |
| | | keepAlive: true, |
| | | }, |
| | | component: () => import("../views/dataManagement/sampleSubmissionList/submission"), |
| | | }, |
| | | ] |
| | | |
| | | }, |
| | | { |
| | | path: "deliveryRecord", |
| | | meta: { |
| | | title: "取样送样记录", |
| | | keepAlive: true, |
| | | }, |
| | | component: () => import("../views/dataManagement/SampleDeliveryRecord/list.vue"), |
| | | }, |
| | | { |
| | | path: "delivery", |
| | | name: "delivery", |
| | | meta: { |
| | | title: "送样详情", |
| | | hide: true, |
| | | keepAlive: true, |
| | | }, |
| | | component: () => import("../views/dataManagement/SampleDeliveryRecord/deliveryRecord"), |
| | | }, |
| | | { |
| | | path: "originalRecordTest", |
| | | meta: { |
| | | title: "原始检验记录", |
| | | keepAlive: true, |
| | | }, |
| | | component: () => import("../views/dataManagement/originalRecordTest/list.vue"), |
| | | }, |
| | | { |
| | | path: "originalRecordTest/detail", |
| | | name: "OriginalRecordTestDetail", |
| | | meta: { |
| | | title: "原始检验记录详情", |
| | | hide: true, |
| | | keepAlive: true, |
| | | }, |
| | | component: () => import("../views/dataManagement/originalRecordTest/detail.vue"), |
| | | }, |
| | | { |
| | | path: "inspectionReport", |
| | | meta: { |
| | | title: "检验报告管理", |
| | | keepAlive: true, |
| | | }, |
| | | component: () => import("../views/dataManagement/inspectionReport/list.vue"), |
| | | }, |
| | | { |
| | | path: "inspectionReport/detail", |
| | | name: "inspectionReportDetail", |
| | | meta: { |
| | | title: "检验报告详情", |
| | | hide: true, |
| | | keepAlive: true, |
| | | }, |
| | | component: () => import("../views/dataManagement/inspectionReport/detail.vue"), |
| | | }, |
| | | { |
| | | path: "testResultReport", |
| | | meta: { |
| | | title: "实验结果汇报", |
| | | keepAlive: true, |
| | | }, |
| | | component: () => import("../views/dataManagement/testResultReport/list.vue"), |
| | | }, |
| | | { |
| | | path: "testResultReport/detail", |
| | | name: "testResultReportDetail", |
| | | meta: { |
| | | title: "实验结果详情", |
| | | hide: true, |
| | | keepAlive: true, |
| | | }, |
| | | component: () => import("../views/dataManagement/testResultReport/detail.vue"), |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | path: "/reportLibrary", |
| | | component: Layouts, |
| | | meta: { |
| | | title: "专业报告库审批", |
| | | }, |
| | | children: [{ |
| | | path: "feasibilityStudy", |
| | | meta: { |
| | | title: "可研报告库", |
| | | keepAlive: true, |
| | | }, |
| | | component: () => import("../views/reportLibrary/feasibilityStudy/index.vue"), |
| | | }, |
| | | { |
| | | path: "add", |
| | | meta: { |
| | | title: "新增可行报告", |
| | | hide: true, |
| | | keepAlive: true, |
| | | }, |
| | | component: () => import("../views/reportLibrary/feasibilityStudy/add.vue"), |
| | | }, |
| | | { |
| | | path: "feasibilityReport", |
| | | meta: { |
| | | title: "可行报告库", |
| | | keepAlive: true, |
| | | }, |
| | | component: () => import("../views/reportLibrary/feasibilityReport/index.vue"), |
| | | }, |
| | | { |
| | | path: "processDevelopment", |
| | | meta: { |
| | | title: "工艺开发工具", |
| | | keepAlive: true, |
| | | }, |
| | | component: () => import("../views/reportLibrary/processDevelopment/index.vue"), |
| | | }, |
| | | { |
| | | path: "verificationRelease", |
| | | meta: { |
| | | title: "验证与发布", |
| | | keepAlive: true, |
| | | }, |
| | | component: () => import("../views/reportLibrary/verificationRelease/index.vue"), |
| | | }, |
| | | { |
| | | path: "projectProposalLibrary", |
| | | meta: { |
| | | title: "立项报告库", |
| | | keepAlive: true, |
| | | }, |
| | | component: () => import("../views/reportLibrary/projectProposalLibrary/index.vue"), |
| | | }, |
| | | ], |
| | | }, { |
| | | path: "/strainReportLibrary", |
| | | component: Layouts, |
| | |
| | | title: "菌种报告库", |
| | | }, |
| | | children: [{ |
| | | path: "reportLibraryOne", |
| | | meta: { |
| | | title: "报告库一", |
| | | keepAlive: true, |
| | | }, |
| | | component: () => import("../views/strainReportLibrary/reportLibraryOne/index.vue"), |
| | | path: "reportLibraryOne", |
| | | meta: { |
| | | title: "报告库一", |
| | | keepAlive: true, |
| | | }, |
| | | { |
| | | path: "add", |
| | | meta: { |
| | | title: "新增报告", |
| | | hide: true, |
| | | keepAlive: true, |
| | | }, |
| | | component: () => import("../views/strainReportLibrary/reportLibraryOne/add.vue"), |
| | | component: () => import("../views/strainReportLibrary/reportLibraryOne/index.vue"), |
| | | }, |
| | | { |
| | | path: "add", |
| | | meta: { |
| | | title: "新增报告", |
| | | hide: true, |
| | | keepAlive: true, |
| | | }, |
| | | { |
| | | path: "reportLibraryTwo", |
| | | meta: { |
| | | title: "报告库二", |
| | | keepAlive: true, |
| | | }, |
| | | component: () => import("../views/strainReportLibrary/reportLibraryOneTWO/index.vue"), |
| | | component: () => import("../views/strainReportLibrary/reportLibraryOne/add.vue"), |
| | | }, |
| | | { |
| | | path: "reportLibraryTwo", |
| | | meta: { |
| | | title: "报告库二", |
| | | keepAlive: true, |
| | | }, |
| | | { |
| | | path: "addTwo", |
| | | meta: { |
| | | title: "新增报告", |
| | | hide: true, |
| | | keepAlive: true, |
| | | }, |
| | | component: () => import("../views/strainReportLibrary/reportLibraryOneTWO/add.vue"), |
| | | component: () => import("../views/strainReportLibrary/reportLibraryOneTWO/index.vue"), |
| | | }, |
| | | { |
| | | path: "addTwo", |
| | | meta: { |
| | | title: "新增报告", |
| | | hide: true, |
| | | keepAlive: true, |
| | | }, |
| | | { |
| | | path: "reportLibraryThree", |
| | | meta: { |
| | | title: "报告库三", |
| | | keepAlive: true, |
| | | }, |
| | | component: () => import("../views/strainReportLibrary/reportLibraryOneThree/index.vue"), |
| | | component: () => import("../views/strainReportLibrary/reportLibraryOneTWO/add.vue"), |
| | | }, |
| | | { |
| | | path: "reportLibraryThree", |
| | | meta: { |
| | | title: "报告库三", |
| | | keepAlive: true, |
| | | }, |
| | | { |
| | | path: "addThree", |
| | | meta: { |
| | | title: "新增报告", |
| | | hide: true, |
| | | keepAlive: true, |
| | | }, |
| | | component: () => import("../views/strainReportLibrary/reportLibraryOneThree/add.vue"), |
| | | component: () => import("../views/strainReportLibrary/reportLibraryOneThree/index.vue"), |
| | | }, |
| | | { |
| | | path: "addThree", |
| | | meta: { |
| | | title: "新增报告", |
| | | hide: true, |
| | | keepAlive: true, |
| | | }, |
| | | { |
| | | path: "reportLibraryFour", |
| | | meta: { |
| | | title: "报告库四", |
| | | keepAlive: true, |
| | | }, |
| | | component: () => import("../views/strainReportLibrary/reportLibraryOneFour/index.vue"), |
| | | component: () => import("../views/strainReportLibrary/reportLibraryOneThree/add.vue"), |
| | | }, |
| | | { |
| | | path: "reportLibraryFour", |
| | | meta: { |
| | | title: "报告库四", |
| | | keepAlive: true, |
| | | }, |
| | | { |
| | | path: "addFour", |
| | | meta: { |
| | | title: "新增报告", |
| | | hide: true, |
| | | keepAlive: true, |
| | | }, |
| | | component: () => import("../views/strainReportLibrary/reportLibraryOneFour/add.vue"), |
| | | component: () => import("../views/strainReportLibrary/reportLibraryOneFour/index.vue"), |
| | | }, |
| | | { |
| | | path: "addFour", |
| | | meta: { |
| | | title: "新增报告", |
| | | hide: true, |
| | | keepAlive: true, |
| | | }, |
| | | |
| | | component: () => import("../views/strainReportLibrary/reportLibraryOneFour/add.vue"), |
| | | }, |
| | | |
| | | ], |
| | | }, |
| | | { |
| | |
| | | title: "菌种报告评定", |
| | | }, |
| | | children: [{ |
| | | path: "projectTeamIntegral", |
| | | meta: { |
| | | title: "菌种项目组评定表", |
| | | }, |
| | | component: () => import("../views/deliveryAssessment/projectTeamIntegral"), |
| | | path: "projectTeamIntegral", |
| | | meta: { |
| | | title: "菌种项目组评定表", |
| | | }, |
| | | { |
| | | path: "projectTeamIntegral-detail", |
| | | meta: { |
| | | title: "评定详情", |
| | | hide: true |
| | | }, |
| | | component: () => import("../views/deliveryAssessment/projectTeamIntegral/detail.vue"), |
| | | component: () => import("../views/deliveryAssessment/projectTeamIntegral"), |
| | | }, |
| | | { |
| | | path: "projectTeamIntegral-detail", |
| | | meta: { |
| | | title: "评定详情", |
| | | hide: true |
| | | }, |
| | | component: () => import("../views/deliveryAssessment/projectTeamIntegral/detail.vue"), |
| | | }, |
| | | ] |
| | | } |
| | | ]; |
| | |
| | | // } |
| | | }); |
| | | |
| | | export default router; |
| | | export default router; |
| | |
| | | const apiConfig = { |
| | | // 开发环境 |
| | | development: { |
| | | baseURL: "", |
| | | baseURL: "http://192.168.110.34:8081", |
| | | imgUrl: "", |
| | | }, |
| | | // 生产环境 |
New file |
| | |
| | | import axios from 'axios' |
| | | import apiConfig from './baseurl' |
| | | |
| | | let encryptionKey = '2022lab02ora12to' // 默认密钥 |
| | | |
| | | // 从接口获取密钥 |
| | | export const fetchEncryptionKey = async () => { |
| | | try { |
| | | const response = await axios.get(`${apiConfig.baseURL}/api/system/getEncryptionKey`) |
| | | if (response.data && response.data.code === 200) { |
| | | // 转换为Buffer并验证字节长度 |
| | | const keyBuffer = Buffer.from(response.data.data, 'utf-8') |
| | | |
| | | if (keyBuffer.length !== 16) { |
| | | console.warn('无效密钥长度,使用默认密钥') |
| | | return encryptionKey // 保持原有密钥 |
| | | } |
| | | |
| | | // 存储原始字符串和Buffer两种格式 |
| | | encryptionKey = response.data.data |
| | | return encryptionKey |
| | | } |
| | | } catch (error) { |
| | | console.error('获取加密密钥失败:', error) |
| | | } |
| | | } |
| | | |
| | | // 新增方法获取Buffer格式的密钥 |
| | | export const getEncryptionKeyBuffer = () => { |
| | | return Buffer.from(encryptionKey, 'utf-8') |
| | | } |
| | | |
| | | // 获取当前密钥(保持字符串格式) |
| | | export const getEncryptionKey = () => encryptionKey |
| | |
| | | import axios from 'axios' |
| | | import apiConfig from './baseurl' |
| | | |
| | | import { |
| | | Message |
| | | } from 'element-ui' |
| | | |
| | | import { Message } from 'element-ui' |
| | | import { encryptBySM4, decryptBySM4 } from './sm4' // 添加decryptBySM4 |
| | | |
| | | const service = axios.create({ |
| | | baseURL: apiConfig.baseURL, |
| | | // baseURL: apiConfig.baseURL, |
| | | withCredentials: false, // 当跨域请求时发送cookie |
| | | timeout: 30000, // request timeout |
| | | }) |
| | |
| | | service.interceptors.request.use( |
| | | config => { |
| | | config['headers']['Authorization'] = `${sessionStorage.getItem('token')}` |
| | | |
| | | // 判断是否需要加密(只对/api开头的请求进行加密) |
| | | const needEncrypt = config.url.startsWith('/api'); |
| | | |
| | | if (config.method == 'get') { |
| | | if (!config.params) config.params = {}; |
| | | config.params = { |
| | |
| | | } |
| | | if (config.method == 'post') { |
| | | if (!config.data) config.data = {}; |
| | | config.data = { |
| | | ...config.data, |
| | | if (needEncrypt) { |
| | | config.data = { param: encryptBySM4(config.data) }; |
| | | } |
| | | } |
| | | return config |
| | |
| | | return |
| | | } |
| | | const res = response; |
| | | |
| | | // 新增解密处理:仅处理/api路径的POST响应 |
| | | if (res.config.method === 'post' && res.config.url.startsWith('/api')) { |
| | | try { |
| | | if (res.data && res.data.data) { |
| | | // 这里假设使用decryptBySM4进行解密 |
| | | res.data.data = decryptBySM4(res.data.data); |
| | | } |
| | | } catch (e) { |
| | | console.error('数据解密失败:', e); |
| | | } |
| | | } |
| | | |
| | | if (res.data.code == 200) { |
| | | if (!res.data.data) { |
| | | if (!res.data) { |
| | | return Promise.resolve({}) |
| | | } |
| | | return Promise.resolve(res.data.data) |
| | | return Promise.resolve(res.data) |
| | | } else { |
| | | if (res.data.code == 103 || res.data.code == 401) { |
| | | Message({ |
New file |
| | |
| | | import { sm4 } from 'sm-crypto'; |
| | | import { getEncryptionKey, fetchEncryptionKey, getEncryptionKeyBuffer } from './encryption'; |
| | | |
| | | // SM4加密函数 |
| | | export const encryptBySM4 = (data) => { |
| | | const key = getEncryptionKeyBuffer(); // 获取当前密钥 |
| | | return sm4.encrypt(JSON.stringify(data), key); |
| | | }; |
| | | |
| | | // SM4解密函数 |
| | | export const decryptBySM4 = (data) => { |
| | | const key = getEncryptionKeyBuffer(); // 获取当前密钥 |
| | | return JSON.parse(sm4.decrypt(data, key)); |
| | | }; |
| | |
| | | <div class="login-form"> |
| | | <div class="form-item flex"> |
| | | <img class="form-item-icon" :src="require('../../assets/login/account@2x.png')" alt=""> |
| | | <el-input v-model="loginForm.account" placeholder="请输入账号"></el-input> |
| | | <el-input v-model="loginForm.username" placeholder="请输入账号"></el-input> |
| | | </div> |
| | | |
| | | <div class="form-item flex mt-40"> |
| | |
| | | </div> |
| | | </template> |
| | | <script> |
| | | import { loginReq } from './service' |
| | | export default { |
| | | name: 'Login', |
| | | data() { |
| | |
| | | windowWidth: window.innerWidth, |
| | | |
| | | loginForm: { |
| | | account: '', |
| | | username: '', |
| | | password: '' |
| | | }, |
| | | viewWidth: '', |
| | |
| | | console.log(this.viewWidth) |
| | | }, |
| | | login() { |
| | | this.$router.push('/') |
| | | console.log(this.loginForm) |
| | | if (this.loginForm.username == '') { |
| | | this.$message.warning('请输入账号') |
| | | return |
| | | } |
| | | if (this.loginForm.password == '') { |
| | | this.$message.warning('请输入密码') |
| | | return |
| | | } |
| | | loginReq(this.loginForm).then(res => { |
| | | sessionStorage.setItem('token', res.token) |
| | | sessionStorage.setItem('userInfo', JSON.stringify(res.userInfo.user)) |
| | | this.$router.push('/') |
| | | }) |
| | | } |
| | | } |
| | | } |
| | |
| | | import axios from '@/utils/request'; |
| | | |
| | | // 登录 |
| | | export const login = (data) => { |
| | | export const loginReq = (data) => { |
| | | return axios.post('/login', { ...data }) |
| | | } |
New file |
| | |
| | | <template> |
| | | <el-form |
| | | :model="form" |
| | | :rules="rules" |
| | | ref="pedigreeForm" |
| | | label-position="top" |
| | | class="strain-form" |
| | | > |
| | | <div class="card"> |
| | | <div class="form-items-row"> |
| | | <el-form-item label="菌种源" prop="strainSource" required> |
| | | <div class="flex-row"> |
| | | <div class="input-wrapper"> |
| | | <el-input |
| | | v-model="form.strainSource" |
| | | placeholder="请输入" |
| | | class="fixed-width-input" |
| | | ></el-input> |
| | | </div> |
| | | <span class="form-text">代—</span> |
| | | <div class="input-wrapper"> |
| | | <el-input |
| | | v-model="form.generation" |
| | | placeholder="请输入" |
| | | class="fixed-width-input" |
| | | ></el-input> |
| | | </div> |
| | | <span class="form-text">细胞库</span> |
| | | </div> |
| | | </el-form-item> |
| | | <el-form-item label="传代菌种编号" prop="strainNo" required> |
| | | <el-input |
| | | v-model="form.strainNo" |
| | | placeholder="请输入" |
| | | class="fixed-width-input" |
| | | ></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="传代菌种名称" prop="strainName" required> |
| | | <el-input |
| | | v-model="form.strainName" |
| | | placeholder="请输入" |
| | | class="fixed-width-input" |
| | | ></el-input> |
| | | </el-form-item> |
| | | </div> |
| | | </div> |
| | | <div class="chart"> |
| | | <div class="header"> |
| | | <div class="title">菌种传代生产谱系图</div> |
| | | <div class="option-btn"> |
| | | <el-button type="primary" class="el-icon-plus"> 新增</el-button> |
| | | <el-button type="primary">设置传代计划数</el-button> |
| | | <el-button type="primary">详情</el-button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="end-btn"> |
| | | <el-button type="primary" @click="handleSubmit">提交</el-button> |
| | | <el-button @click="handleDraft">存草稿</el-button> |
| | | <el-button @click="handleCancel">取消</el-button> |
| | | </div> |
| | | <!-- 签字确认组件 --> |
| | | <SignatureCanvas |
| | | :visible.sync="signatureVisible" |
| | | @confirm="handleSignatureConfirm" |
| | | /> |
| | | </el-form> |
| | | </template> |
| | | |
| | | <script> |
| | | import SignatureCanvas from "@/components/SignatureCanvas.vue"; |
| | | |
| | | export default { |
| | | name: "AddPedigree", |
| | | components: { |
| | | SignatureCanvas, |
| | | }, |
| | | data() { |
| | | return { |
| | | signatureVisible: false, |
| | | form: { |
| | | strainSource: "", |
| | | generation: "", |
| | | cellBank: "", |
| | | strainNo: "", |
| | | strainName: "", |
| | | remarks: "", |
| | | }, |
| | | rules: { |
| | | strainSource: [ |
| | | { required: true, message: "请输入菌种源", trigger: "blur" }, |
| | | ], |
| | | strainNo: [ |
| | | { required: true, message: "请输入传代菌种编号", trigger: "blur" }, |
| | | ], |
| | | strainName: [ |
| | | { required: true, message: "请输入传代菌种名称", trigger: "blur" }, |
| | | ], |
| | | }, |
| | | }; |
| | | }, |
| | | methods: { |
| | | handleSubmit() { |
| | | this.$refs.pedigreeForm.validate((valid) => { |
| | | if (valid) { |
| | | this.signatureVisible = true; |
| | | } |
| | | }); |
| | | }, |
| | | handleDraft() { |
| | | // 实现存草稿逻辑 |
| | | console.log("save draft", this.form); |
| | | }, |
| | | handleCancel() { |
| | | this.$router.back(); |
| | | }, |
| | | handleSignatureConfirm(signatureImage) { |
| | | this.signatureVisible = false; |
| | | // 处理提交逻辑 |
| | | console.log("submit form with signature:", this.form, signatureImage); |
| | | this.$router.back(); |
| | | }, |
| | | }, |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="less"> |
| | | .card { |
| | | min-height: 145px; |
| | | background: rgba(255, 255, 255, 0.8); |
| | | box-shadow: 0px 10px 19px 0px rgba(0, 0, 0, 0.06); |
| | | border-radius: 16px; |
| | | border: 4px solid #ffffff; |
| | | padding: 0 20px; |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | .chart { |
| | | padding: 20px 38px; |
| | | background: rgba(255,255,255,0.8); |
| | | box-shadow: 0px 10px 19px 0px rgba(0,0,0,0.06); |
| | | border-radius: 16px; |
| | | border: 4px solid #FFFFFF; |
| | | margin-top: 30px; |
| | | .header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | .title { |
| | | font-size: 18px; |
| | | } |
| | | .option-btn { |
| | | display: flex; |
| | | gap: 10px; |
| | | .el-button { |
| | | margin-left: 0; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .form-items-row { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | width: 100%; |
| | | align-items: center; |
| | | |
| | | @media (min-width: 1200px) { |
| | | flex-direction: row; |
| | | justify-content: space-between; |
| | | } |
| | | |
| | | @media (max-width: 1199px) { |
| | | flex-direction: column; |
| | | align-items: flex-start; |
| | | } |
| | | } |
| | | |
| | | .flex-row { |
| | | display: flex; |
| | | align-items: center; |
| | | flex-wrap: wrap; |
| | | |
| | | @media (max-width: 768px) { |
| | | flex-direction: column; |
| | | align-items: flex-start; |
| | | width: 100%; |
| | | } |
| | | } |
| | | |
| | | .strain-form { |
| | | width: 100%; |
| | | |
| | | :deep(.el-form-item) { |
| | | margin-bottom: 15px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: flex-start; |
| | | |
| | | @media (min-width: 1200px) { |
| | | margin-right: 10px; |
| | | } |
| | | |
| | | @media (max-width: 1199px) { |
| | | width: 100%; |
| | | margin-right: 0; |
| | | } |
| | | |
| | | .el-form-item__label { |
| | | padding: 0 0 8px; |
| | | line-height: 1.2; |
| | | text-align: left; |
| | | } |
| | | |
| | | .el-form-item__content { |
| | | width: 100%; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .input-wrapper { |
| | | @media (min-width: 769px) { |
| | | width: 290px; |
| | | min-width: 290px; |
| | | } |
| | | |
| | | @media (max-width: 768px) { |
| | | width: 100%; |
| | | } |
| | | } |
| | | |
| | | .fixed-width-input { |
| | | width: 100%; |
| | | |
| | | @media (min-width: 769px) { |
| | | width: 290px !important; |
| | | min-width: 290px !important; |
| | | } |
| | | } |
| | | |
| | | .form-text { |
| | | margin: 0 8px; |
| | | white-space: nowrap; |
| | | |
| | | @media (max-width: 768px) { |
| | | margin: 8px 0; |
| | | } |
| | | } |
| | | |
| | | .end-btn { |
| | | margin-top: 20px; |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 10px; |
| | | } |
| | | </style> |
| | |
| | | status: "", |
| | | }; |
| | | }, |
| | | handleNewStrain() { |
| | | this.$router.push({ |
| | | path: "/strain/add-pedigree", |
| | | }); |
| | | }, |
| | | handleSearch() { |
| | | // 实现查询逻辑 |
| | | console.log("查询条件:", this.form); |
| | |
| | | <TableCustom :queryForm="queryForm" :tableData="tableData" :total="total" @currentChange="handleCurrentChange" |
| | | @sizeChange="handleSizeChange"> |
| | | <template #search> |
| | | <el-form :model="form" label-width="140px" inline> |
| | | <el-form label-width="140px" inline> |
| | | <el-form-item label="项目组名称:"> |
| | | <el-input v-model="form.name" placeholder="请输入"></el-input> |
| | | <el-input v-model="queryForm.name" placeholder="请输入"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="项目负责人:"> |
| | | <el-input v-model="form.name" placeholder="请输入"></el-input> |
| | | <el-input v-model="queryForm.name" placeholder="请输入"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="创建日期:"> |
| | | <el-date-picker v-model="value1" type="daterange" range-separator="至" start-placeholder="开始日期" |
| | | end-placeholder="结束日期"> |
| | | <el-date-picker v-model="queryForm.createdDate" type="daterange" range-separator="至" |
| | | start-placeholder="开始日期" end-placeholder="结束日期"> |
| | | </el-date-picker> |
| | | </el-form-item> |
| | | <el-form-item class="search-btn-box"> |
| | |
| | | </template> |
| | | |
| | | <script> |
| | | import { getProjectList } from './service' |
| | | export default { |
| | | name: 'ProjectList', |
| | | data() { |
| | | return { |
| | | form: { |
| | | name: '' |
| | | }, |
| | | showDelConfirm: false, |
| | | rowId: '', |
| | | changeStatus: false, |
| | |
| | | }, |
| | | total: 0 |
| | | } |
| | | }, |
| | | created() { |
| | | this.getList() |
| | | }, |
| | | methods: { |
| | | handleAddProject() { |
| | |
| | | this.getList() |
| | | }, |
| | | getList() { |
| | | |
| | | getProjectList(this.queryForm).then(res => { |
| | | console.log(res); |
| | | }) |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | import axios from '@/utils/request'; |
| | | |
| | | // 列表 |
| | | export const getProjectList = (data) => { |
| | | return axios.post('/api/t-project-team/pageList', { ...data }) |
| | | } |
New file |
| | |
| | | <template> |
| | | <div class="strain-flow-chart"> |
| | | <div class="toolbar"> |
| | | <el-button type="primary" size="small" @click="addNode" :disabled="!canAddNode">新增</el-button> |
| | | <el-button size="small" @click="setGenerationPlan" :disabled="!selectedNode">设置传代计划数</el-button> |
| | | <el-button size="small" @click="showDetail" :disabled="!selectedNode">详情</el-button> |
| | | </div> |
| | | <div id="mountNode"></div> |
| | | |
| | | <!-- 节点详情弹窗 --> |
| | | <el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="500px" @close="handleDialogClose" |
| | | :close-on-click-modal="false"> |
| | | <el-form :model="form" :rules="rules" ref="form" label-width="120px"> |
| | | <el-form-item :label="formLabel" prop="value"> |
| | | <el-input v-model="form.value" :type="inputType"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="废弃状态" prop="isDiscarded" v-if="showDiscarded"> |
| | | <el-switch v-model="form.isDiscarded"></el-switch> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div slot="footer" class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">取 消</el-button> |
| | | <el-button type="primary" @click="handleSubmit">确 定</el-button> |
| | | </div> |
| | | </el-dialog> |
| | | |
| | | <!-- 新增母代弹窗 --> |
| | | <el-dialog title="新增母代" :visible.sync="addParentDialogVisible" width="500px" :close-on-click-modal="false"> |
| | | <el-form :model="parentForm" :rules="parentRules" ref="parentForm" label-width="120px"> |
| | | <el-form-item label="代传菌种编号" prop="number"> |
| | | <el-input v-model="parentForm.number"></el-input> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div slot="footer" class="dialog-footer"> |
| | | <el-button @click="addParentDialogVisible = false">取 消</el-button> |
| | | <el-button type="primary" @click="handleAddParent">确 定</el-button> |
| | | </div> |
| | | </el-dialog> |
| | | |
| | | <!-- 设置传代计划数弹窗 --> |
| | | <el-dialog title="设置传代计划数" :visible.sync="planDialogVisible" width="500px" :close-on-click-modal="false"> |
| | | <el-form :model="planForm" :rules="planRules" ref="planForm" label-width="120px"> |
| | | <el-form-item label="计划数" prop="count"> |
| | | <el-input v-model="planForm.count" type="number" :min="1"></el-input> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div slot="footer" class="dialog-footer"> |
| | | <el-button @click="planDialogVisible = false">取 消</el-button> |
| | | <el-button type="primary" @click="handleSetPlan">确 定</el-button> |
| | | </div> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | import G6 from '@antv/g6'; |
| | | import dagre from 'dagre'; |
| | | |
| | | export default { |
| | | name: 'StrainFlowChart', |
| | | data() { |
| | | return { |
| | | graph: null, |
| | | nodeCount: 0, |
| | | selectedNode: null, |
| | | graphData: { |
| | | nodes: [], |
| | | edges: [] |
| | | }, |
| | | // 弹窗相关数据 |
| | | dialogVisible: false, |
| | | dialogTitle: '', |
| | | formLabel: '', |
| | | inputType: 'text', |
| | | showDiscarded: false, |
| | | isAddingNode: false, |
| | | form: { |
| | | value: '', |
| | | isDiscarded: false |
| | | }, |
| | | rules: { |
| | | value: [ |
| | | { required: true, message: '不能为空', trigger: 'blur' } |
| | | ] |
| | | }, |
| | | // 新增母代弹窗数据 |
| | | addParentDialogVisible: false, |
| | | parentForm: { |
| | | number: '' |
| | | }, |
| | | parentRules: { |
| | | number: [ |
| | | { required: true, message: '菌种编号不能为空', trigger: 'blur' } |
| | | ] |
| | | }, |
| | | // 设置传代计划数弹窗数据 |
| | | planDialogVisible: false, |
| | | planForm: { |
| | | count: 1 |
| | | }, |
| | | planRules: { |
| | | count: [ |
| | | { required: true, message: '计划数不能为空', trigger: 'blur' } |
| | | ] |
| | | } |
| | | }; |
| | | }, |
| | | computed: { |
| | | canAddNode() { |
| | | // 如果没有节点,可以新增母代 |
| | | if (this.graphData.nodes.length === 0) { |
| | | return true; |
| | | } |
| | | // 如果选中了传代计划数节点,可以新增下一代 |
| | | if (this.selectedNode && this.selectedNode.label === '传代计划数') { |
| | | return true; |
| | | } |
| | | return false; |
| | | } |
| | | }, |
| | | mounted() { |
| | | this.initGraph(); |
| | | this.initEvents(); |
| | | }, |
| | | beforeDestroy() { |
| | | this.graph?.destroy(); |
| | | window.removeEventListener('resize', this.handleResize); |
| | | }, |
| | | methods: { |
| | | initGraph() { |
| | | const container = document.getElementById('mountNode'); |
| | | const width = container.scrollWidth; |
| | | const height = container.scrollHeight || 600; |
| | | |
| | | // 自定义节点 |
| | | G6.registerNode('custom-node', { |
| | | draw(cfg, group) { |
| | | const width = 120; |
| | | const titleHeight = 30; |
| | | const contentHeight = 40; |
| | | const gap = 4; |
| | | const totalHeight = titleHeight + gap + contentHeight; |
| | | |
| | | // 根据节点状态设置颜色 |
| | | const isDiscarded = cfg.isDiscarded; |
| | | const titleFill = isDiscarded ? 'rgba(245, 248, 250, 1)' : (cfg.selected ? 'l(0) 0:#0ACBCA 1:#049C9A' : 'l(0) 0:#0ACBCA 1:#049C9A'); |
| | | const contentFill = isDiscarded ? 'rgba(245, 248, 250, 1)' : (cfg.selected ? 'rgba(4,156,154,0.2)' : 'rgba(4,156,154,0.1)'); |
| | | const textFill = isDiscarded ? 'rgba(144, 147, 153, 1)' : '#049C9A'; |
| | | const stroke = isDiscarded ? '#DCDFE6' : (cfg.selected ? '#049C9A' : 'transparent'); |
| | | |
| | | // 创建渐变 |
| | | const gradient = group.addShape('rect', { |
| | | attrs: { |
| | | x: -width / 2, |
| | | y: -totalHeight / 2, |
| | | width: width, |
| | | height: titleHeight, |
| | | radius: 20, |
| | | fill: titleFill, |
| | | cursor: 'move', |
| | | stroke: stroke, |
| | | lineWidth: isDiscarded ? 1 : (cfg.selected ? 2 : 0), |
| | | }, |
| | | name: 'title-box', |
| | | }); |
| | | |
| | | // 下部分 - 内容背景 |
| | | const contentBox = group.addShape('rect', { |
| | | attrs: { |
| | | x: -width / 2, |
| | | y: -totalHeight / 2 + titleHeight + gap, |
| | | width: width, |
| | | height: contentHeight, |
| | | fill: contentFill, |
| | | radius: 15, |
| | | cursor: 'move', |
| | | stroke: stroke, |
| | | lineWidth: isDiscarded ? 1 : (cfg.selected ? 2 : 0), |
| | | }, |
| | | name: 'content-box', |
| | | }); |
| | | |
| | | // 标题文本 |
| | | if (cfg.label) { |
| | | group.addShape('text', { |
| | | attrs: { |
| | | text: cfg.label, |
| | | x: 0, |
| | | y: -totalHeight / 2 + titleHeight / 2, |
| | | fill: isDiscarded ? 'rgba(144, 147, 153, 1)' : '#fff', |
| | | fontSize: 12, |
| | | textAlign: 'center', |
| | | textBaseline: 'middle', |
| | | fontWeight: 'bold', |
| | | cursor: 'move', |
| | | }, |
| | | name: 'title-text', |
| | | }); |
| | | } |
| | | |
| | | // 内容文本 |
| | | let content = ''; |
| | | if (cfg.label === '传代计划数') { |
| | | content = `${cfg.planCount || 0}`; |
| | | } else if (cfg.number) { |
| | | content = cfg.label === '母代' ? `代传菌种编号:${cfg.number}` : `接种菌种编号:${cfg.number}`; |
| | | } |
| | | |
| | | if (content) { |
| | | group.addShape('text', { |
| | | attrs: { |
| | | text: content, |
| | | x: 0, |
| | | y: -totalHeight / 2 + titleHeight + gap + contentHeight / 2, |
| | | fill: textFill, |
| | | fontSize: 10, |
| | | textAlign: 'center', |
| | | textBaseline: 'middle', |
| | | cursor: 'move', |
| | | }, |
| | | name: 'content-text', |
| | | }); |
| | | } |
| | | |
| | | return gradient; |
| | | }, |
| | | getAnchorPoints() { |
| | | return [ |
| | | [0.5, 0], // 上 |
| | | [1, 0.5], // 右 |
| | | [0.5, 1], // 下 |
| | | [0, 0.5], // 左 |
| | | ]; |
| | | }, |
| | | setState(name, value, item) { |
| | | // 移除悬浮效果,保持节点样式始终一致 |
| | | }, |
| | | }); |
| | | |
| | | this.graph = new G6.Graph({ |
| | | container: 'mountNode', |
| | | width, |
| | | height, |
| | | fitView: true, |
| | | fitViewPadding: 30, |
| | | animate: false, |
| | | enabledStack: false, |
| | | renderer: 'canvas', |
| | | minZoom: 0.3, |
| | | maxZoom: 2, |
| | | defaultZoom: 1, |
| | | layout: { |
| | | type: 'dagre', |
| | | rankdir: 'LR', |
| | | align: 'UL', |
| | | nodesep: 30, // 减小节点间距 |
| | | ranksep: 50, // 减小层级间距 |
| | | controlPoints: true, |
| | | }, |
| | | modes: { |
| | | default: [ |
| | | { |
| | | type: 'drag-canvas', |
| | | enableOptimize: true, |
| | | direction: 'both', |
| | | scalableRange: 0.1, |
| | | dragTimesOfScale: 0.1, |
| | | onlyChangeComputeZoom: true, |
| | | }, |
| | | { |
| | | type: 'zoom-canvas', |
| | | sensitivity: 1.5, |
| | | enableOptimize: true, |
| | | }, |
| | | { |
| | | type: 'drag-node', |
| | | enableDelegate: true, |
| | | delegateStyle: { |
| | | fill: '#f3f3f3', |
| | | stroke: '#ccc', |
| | | opacity: 0.5, |
| | | }, |
| | | updateEdge: false, |
| | | enableOptimize: true, |
| | | optimizeZoom: 0.7, |
| | | damping: 0.1, |
| | | } |
| | | ] |
| | | }, |
| | | defaultNode: { |
| | | type: 'custom-node', |
| | | style: { |
| | | fill: 'l(0) 0:#0ACBCA 1:#049C9A', |
| | | }, |
| | | }, |
| | | defaultEdge: { |
| | | type: 'cubic-horizontal', |
| | | style: { |
| | | stroke: 'rgba(4, 156, 154, 1)', |
| | | lineWidth: 1, |
| | | opacity: 0.5, |
| | | endArrow: { |
| | | path: G6.Arrow.triangle(6, 6), |
| | | fill: 'rgba(4, 156, 154, 1)', |
| | | stroke: 'rgba(4, 156, 154, 1)', |
| | | }, |
| | | }, |
| | | }, |
| | | optimizeEdge: true, |
| | | optimizeLayoutAnimation: true, |
| | | }); |
| | | |
| | | const canvas = this.graph.get('canvas'); |
| | | canvas.set('localRefresh', false); |
| | | canvas.set('autoDraw', true); |
| | | canvas.set('animating', false); |
| | | |
| | | let throttleTimer = null; |
| | | const throttleInterval = 16; |
| | | |
| | | this.graph.on('node:dragstart', () => { |
| | | canvas.set('localRefresh', false); |
| | | this.graph.get('canvas').draw(); |
| | | }); |
| | | |
| | | this.graph.on('node:drag', (e) => { |
| | | if (throttleTimer) return; |
| | | throttleTimer = setTimeout(() => { |
| | | const model = e.item.get('model'); |
| | | const edges = this.graph.getEdges().filter(edge => { |
| | | const source = edge.getSource(); |
| | | const target = edge.getTarget(); |
| | | return source.get('id') === model.id || target.get('id') === model.id; |
| | | }); |
| | | edges.forEach(edge => { |
| | | this.graph.refreshItem(edge); |
| | | }); |
| | | throttleTimer = null; |
| | | }, throttleInterval); |
| | | }); |
| | | |
| | | this.graph.on('node:dragend', (e) => { |
| | | if (throttleTimer) { |
| | | clearTimeout(throttleTimer); |
| | | throttleTimer = null; |
| | | } |
| | | const model = e.item.get('model'); |
| | | const edges = this.graph.getEdges().filter(edge => { |
| | | const source = edge.getSource(); |
| | | const target = edge.getTarget(); |
| | | return source.get('id') === model.id || target.get('id') === model.id; |
| | | }); |
| | | edges.forEach(edge => { |
| | | this.graph.refreshItem(edge); |
| | | }); |
| | | canvas.set('localRefresh', true); |
| | | this.graph.get('canvas').draw(); |
| | | }); |
| | | |
| | | this.graph.data(this.graphData); |
| | | this.graph.render(); |
| | | |
| | | let debounceTimer = null; |
| | | this.graph.on('afterchange', () => { |
| | | if (debounceTimer) clearTimeout(debounceTimer); |
| | | debounceTimer = setTimeout(() => { |
| | | if (!canvas.get('destroyed')) { |
| | | canvas.draw(); |
| | | } |
| | | }, 16); |
| | | }); |
| | | }, |
| | | initEvents() { |
| | | // 监听窗口大小变化 |
| | | window.addEventListener('resize', this.handleResize); |
| | | |
| | | // 节点点击事件 |
| | | this.graph.on('node:click', (evt) => { |
| | | 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('canvas:click', () => { |
| | | this.selectedNode = null; |
| | | this.graphData.nodes.forEach(n => { |
| | | n.selected = false; |
| | | }); |
| | | this.graph.changeData(this.graphData); |
| | | }); |
| | | }, |
| | | handleResize() { |
| | | if (this.graph) { |
| | | const container = document.getElementById('mountNode'); |
| | | const width = container.scrollWidth; |
| | | const height = container.scrollHeight || 600; |
| | | this.graph.changeSize(width, height); |
| | | } |
| | | }, |
| | | addNode() { |
| | | // 如果没有节点,新增母代 |
| | | if (this.graphData.nodes.length === 0) { |
| | | this.addParentDialogVisible = true; |
| | | this.parentForm.number = ''; |
| | | return; |
| | | } |
| | | |
| | | // 如果选中了传代计划数节点,新增下一代 |
| | | if (this.selectedNode && this.selectedNode.label === '传代计划数') { |
| | | const nodeModel = this.selectedNode; |
| | | |
| | | // 检查是否已达到计划数 |
| | | if (nodeModel.currentCount >= nodeModel.planCount) { |
| | | this.$message.warning('已达到计划数,不能再添加'); |
| | | return; |
| | | } |
| | | |
| | | // 获取父节点 |
| | | const parentEdge = this.graphData.edges.find(e => e.target === nodeModel.id); |
| | | const parentNode = this.graphData.nodes.find(n => n.id === parentEdge.source); |
| | | |
| | | // 如果父节点是孙代,不允许添加 |
| | | if (parentNode.label === '孙代') { |
| | | this.$message.warning('孙代节点不能再生成下一代'); |
| | | return; |
| | | } |
| | | |
| | | const isParent = parentNode.label === '母代'; |
| | | const nextLevel = isParent ? '子代' : '孙代'; |
| | | |
| | | // 使用对话框输入节点信息 |
| | | this.dialogVisible = true; |
| | | this.dialogTitle = `新增${nextLevel}`; |
| | | this.formLabel = '接种菌种编号'; |
| | | this.form.value = ''; |
| | | this.form.isDiscarded = false; |
| | | this.showDiscarded = true; |
| | | this.inputType = 'text'; |
| | | this.isAddingNode = true; |
| | | } |
| | | }, |
| | | handleAddParent() { |
| | | this.$refs.parentForm.validate((valid) => { |
| | | if (valid) { |
| | | const parentId = `parent-${++this.nodeCount}`; |
| | | const container = document.getElementById('mountNode'); |
| | | const height = container.scrollHeight || 600; |
| | | |
| | | this.graphData.nodes.push({ |
| | | id: parentId, |
| | | label: '母代', |
| | | number: this.parentForm.number.trim(), |
| | | x: 200, |
| | | y: 200, |
| | | style: { |
| | | fill: '#00B5AA', |
| | | }, |
| | | }); |
| | | |
| | | this.graph.changeData(this.graphData); |
| | | this.$message.success('母代节点添加成功'); |
| | | this.addParentDialogVisible = false; |
| | | } |
| | | }); |
| | | }, |
| | | handleDialogClose() { |
| | | this.$refs.form.resetFields(); |
| | | }, |
| | | handleSubmit() { |
| | | this.$refs.form.validate((valid) => { |
| | | if (valid) { |
| | | if (this.isAddingNode) { |
| | | // 新增节点的处理逻辑 |
| | | const nodeModel = this.selectedNode; |
| | | const parentEdge = this.graphData.edges.find(e => e.target === nodeModel.id); |
| | | const parentNode = this.graphData.nodes.find(n => n.id === parentEdge.source); |
| | | const isParent = parentNode.label === '母代'; |
| | | const nextLevel = isParent ? '子代' : '孙代'; |
| | | |
| | | const childId = `child-${++this.nodeCount}`; |
| | | this.graphData.nodes.push({ |
| | | id: childId, |
| | | label: nextLevel, |
| | | number: this.form.value.trim(), |
| | | isDiscarded: this.form.isDiscarded, |
| | | style: { |
| | | fill: this.form.isDiscarded ? '#999' : '#00B5AA', |
| | | opacity: this.form.isDiscarded ? 0.3 : (isParent ? 0.6 : 0.4), |
| | | }, |
| | | }); |
| | | |
| | | this.graphData.edges.push({ |
| | | source: nodeModel.id, |
| | | target: childId, |
| | | style: { |
| | | stroke: 'rgba(4, 156, 154, 1)', |
| | | lineWidth: 1, |
| | | }, |
| | | }); |
| | | |
| | | const nodeIndex = this.graphData.nodes.findIndex(n => n.id === nodeModel.id); |
| | | this.graphData.nodes[nodeIndex].currentCount++; |
| | | |
| | | this.graph.changeData(this.graphData); |
| | | this.$message.success(`${nextLevel}添加成功`); |
| | | |
| | | this.isAddingNode = false; |
| | | this.dialogVisible = false; |
| | | } else { |
| | | // 编辑节点的处理逻辑 |
| | | const nodeModel = this.selectedNode; |
| | | const nodeIndex = this.graphData.nodes.findIndex(n => n.id === nodeModel.id); |
| | | |
| | | if (nodeIndex > -1) { |
| | | if (nodeModel.label === '传代计划数') { |
| | | this.graphData.nodes[nodeIndex].planCount = parseInt(this.form.value); |
| | | } else { |
| | | this.graphData.nodes[nodeIndex].number = this.form.value.trim(); |
| | | if (this.showDiscarded) { |
| | | this.graphData.nodes[nodeIndex].isDiscarded = this.form.isDiscarded; |
| | | // 如果设置为废弃状态,同时废弃所有子节点 |
| | | if (this.form.isDiscarded) { |
| | | const discardChildren = (parentId) => { |
| | | const childEdges = this.graphData.edges.filter(e => e.source === parentId); |
| | | childEdges.forEach(edge => { |
| | | const childNode = this.graphData.nodes.find(n => n.id === edge.target); |
| | | if (childNode) { |
| | | const childIndex = this.graphData.nodes.findIndex(n => n.id === childNode.id); |
| | | if (childIndex > -1) { |
| | | this.graphData.nodes[childIndex].isDiscarded = true; |
| | | this.graphData.nodes[childIndex].style.fill = '#999'; |
| | | this.graphData.nodes[childIndex].style.opacity = 0.3; |
| | | // 递归处理子节点的子节点 |
| | | discardChildren(childNode.id); |
| | | } |
| | | } |
| | | }); |
| | | }; |
| | | discardChildren(nodeModel.id); |
| | | } |
| | | } |
| | | } |
| | | this.graph.changeData(this.graphData); |
| | | this.$message.success('修改成功'); |
| | | this.dialogVisible = false; |
| | | } |
| | | } |
| | | } |
| | | }); |
| | | }, |
| | | setGenerationPlan() { |
| | | if (!this.selectedNode) { |
| | | this.$message.warning('请先选择节点'); |
| | | return; |
| | | } |
| | | |
| | | const nodeModel = this.selectedNode; |
| | | |
| | | if (nodeModel.label === '孙代') { |
| | | this.$message.warning('孙代节点不能再生成传代计划数'); |
| | | return; |
| | | } |
| | | |
| | | if (nodeModel.label === '传代计划数') { |
| | | this.$message.warning('传代计划数节点不能再设置计划数'); |
| | | return; |
| | | } |
| | | |
| | | const hasGenerationNode = this.graphData.edges.some(e => |
| | | e.source === nodeModel.id && |
| | | this.graphData.nodes.some(n => n.id === e.target && n.label === '传代计划数') |
| | | ); |
| | | |
| | | if (hasGenerationNode) { |
| | | this.$message.warning('该节点已经存在传代计划数节点'); |
| | | return; |
| | | } |
| | | |
| | | this.planDialogVisible = true; |
| | | this.planForm.count = 1; |
| | | }, |
| | | handleSetPlan() { |
| | | this.$refs.planForm.validate((valid) => { |
| | | if (valid) { |
| | | const nodeModel = this.selectedNode; |
| | | const planCount = this.planForm.count; |
| | | const generationId = `generation-${++this.nodeCount}`; |
| | | |
| | | this.graphData.nodes.push({ |
| | | id: generationId, |
| | | label: '传代计划数', |
| | | planCount: planCount, |
| | | currentCount: 0, |
| | | style: { |
| | | fill: '#00B5AA', |
| | | }, |
| | | }); |
| | | |
| | | this.graphData.edges.push({ |
| | | source: nodeModel.id, |
| | | target: generationId, |
| | | style: { |
| | | stroke: 'rgba(4, 156, 154, 1)', |
| | | lineWidth: 1, |
| | | }, |
| | | }); |
| | | |
| | | this.graph.changeData(this.graphData); |
| | | this.$message.success('传代计划数设置成功'); |
| | | this.planDialogVisible = false; |
| | | } |
| | | }); |
| | | }, |
| | | showDetail() { |
| | | if (!this.selectedNode) { |
| | | this.$message.warning('请先选择节点'); |
| | | return; |
| | | } |
| | | |
| | | const nodeModel = this.selectedNode; |
| | | |
| | | if (nodeModel.label === '母代') { |
| | | this.dialogTitle = '母代详情'; |
| | | this.formLabel = '代传菌种编号'; |
| | | this.form.value = nodeModel.number || ''; |
| | | this.showDiscarded = false; |
| | | this.inputType = 'text'; |
| | | } else if (nodeModel.label === '子代' || nodeModel.label === '孙代') { |
| | | this.dialogTitle = `${nodeModel.label}详情`; |
| | | this.formLabel = '接种菌种编号'; |
| | | this.form.value = nodeModel.number || ''; |
| | | this.form.isDiscarded = nodeModel.isDiscarded || false; |
| | | this.showDiscarded = true; |
| | | this.inputType = 'text'; |
| | | } else if (nodeModel.label === '传代计划数') { |
| | | this.dialogTitle = '传代计划数详情'; |
| | | this.formLabel = '计划数'; |
| | | this.form.value = nodeModel.planCount || ''; |
| | | this.showDiscarded = false; |
| | | this.inputType = 'number'; |
| | | } |
| | | |
| | | this.dialogVisible = true; |
| | | } |
| | | }, |
| | | }; |
| | | </script> |
| | | |
| | | <style lang="less" scoped> |
| | | .strain-flow-chart { |
| | | width: 100%; |
| | | height: 100%; |
| | | min-height: 600px; |
| | | background-color: #fff; |
| | | padding: 24px; |
| | | border-radius: 4px; |
| | | box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
| | | display: flex; |
| | | flex-direction: column; |
| | | |
| | | .toolbar { |
| | | margin-bottom: 16px; |
| | | display: flex; |
| | | gap: 12px; |
| | | |
| | | .el-button { |
| | | margin-right: 0; |
| | | } |
| | | } |
| | | |
| | | #mountNode { |
| | | flex: 1; |
| | | width: 100%; |
| | | min-height: 500px; |
| | | border: 1px solid #eee; |
| | | border-radius: 4px; |
| | | } |
| | | } |
| | | </style> |
| | |
| | | return path.join(__dirname, dir) |
| | | } |
| | | module.exports = { |
| | | outputDir: 'laboratory', // 配置打包后的文件夹名称 |
| | | outputDir: 'culture', // 配置打包后的文件夹名称 |
| | | lintOnSave: false, |
| | | publicPath: '/', |
| | | devServer: { |
| | | disableHostCheck: true, //禁用主机检查 |
| | | proxy: { |
| | | "/api": { // 设置以什么前缀开头的请求用来代理 |
| | | target: "http://localhost:8080", //要访问的跨域的域名 |
| | | target: "http://192.168.110.34:8081", //要访问的跨域的域名 |
| | | secure: false, // 使用的是http协议则设置为false,https协议则设置为true |
| | | changOrigin: true, //开启代理 |
| | | pathRewrite: { |
| | | "^/api": "", |
| | | "^/api": "/api", |
| | | }, |
| | | }, |
| | | }, |
| | |
| | | "version": "0.1.0", |
| | | "private": true, |
| | | "scripts": { |
| | | "serve:dev": "cross-env VUE_APP_ENV=development vue-cli-service serve --port 8087", |
| | | "serve:dev": "cross-env VUE_APP_ENV=development vue-cli-service serve --port 8090", |
| | | "serve:prod": "cross-env VUE_APP_ENV=production vue-cli-service serve", |
| | | "build:dev": "cross-env VUE_APP_ENV=development vue-cli-service build", |
| | | "build:prod": "cross-env VUE_APP_ENV=production vue-cli-service build", |
| | |
| | | "core-js": "^3.41.0", |
| | | "element-ui": "^2.15.6", |
| | | "moment": "^2.30.1", |
| | | "sm-crypto": "^0.3.13", |
| | | "vue": "^2.7.16", |
| | | "vue-router": "^3.6.5", |
| | | "vuex": "^3.6.2" |
New file |
| | |
| | | <template> |
| | | <div> |
| | | <div class="choose-material" :class="title ? '' : 'has-title'"> |
| | | <div class="add-group" v-if="title"> |
| | | <div>*</div> |
| | | <span>{{ title }}</span> |
| | | </div> |
| | | |
| | | <!-- 动态渲染组件 --> |
| | | <div |
| | | v-for="(item, idx) in components" |
| | | :key="item.id" |
| | | class="dynamic-component" |
| | | > |
| | | <!-- 富文本 --> |
| | | <div v-if="item.type == 'richText'"> |
| | | <AiEditor |
| | | :ref="`editor_${item.id}`" |
| | | v-model="item.data.content" |
| | | height="200px" |
| | | placeholder="请输入内容..." |
| | | /> |
| | | </div> |
| | | <!-- 自定义表格 --> |
| | | <div v-else-if="item.type == 'customTable'" style="flex: 1"> |
| | | <Table |
| | | :data="item.data.rows" |
| | | :total="null" |
| | | :height="null" |
| | | class="groupTable" |
| | | > |
| | | <el-table-column |
| | | v-for="(header, hidx) in item.data.headers" |
| | | :key="hidx" |
| | | :label="header.name" |
| | | :prop="header.name" |
| | | > |
| | | <template slot-scope="scope"> |
| | | <!-- 文本类型 --> |
| | | <span v-if="header.type === 'text'">{{ scope.row[header.name] }}</span> |
| | | <!-- 图片类型 --> |
| | | <div v-else-if="header.type === 'image'" class="image-preview"> |
| | | <el-image |
| | | v-for="(img, imgIndex) in scope.row[header.name]" |
| | | :key="imgIndex" |
| | | :src="img.url" |
| | | :preview-src-list="[img.url]" |
| | | fit="cover" |
| | | class="preview-image" |
| | | /> |
| | | </div> |
| | | <!-- 日期类型 --> |
| | | <span v-else-if="header.type === 'date'">{{ scope.row[header.name] }}</span> |
| | | <!-- 用户类型 --> |
| | | <div v-else-if="header.type === 'user'" class="user-tags"> |
| | | <el-tag |
| | | v-for="user in scope.row[header.name]" |
| | | :key="user" |
| | | class="user-tag" |
| | | > |
| | | {{ getUserName(user) }} |
| | | </el-tag> |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column |
| | | label="更新时间" |
| | | prop="updateTime" |
| | | min-width="180" |
| | | ></el-table-column> |
| | | </Table> |
| | | </div> |
| | | <!-- 文件上传 --> |
| | | <div v-else-if="item.type == 'fileUpload'"> |
| | | <el-upload |
| | | action="#" |
| | | :file-list="item.data.fileList" |
| | | :disabled="true" |
| | | list-type="text" |
| | | > |
| | | <el-button style="display: none">点击上传</el-button> |
| | | </el-upload> |
| | | </div> |
| | | <!-- 图片上传 --> |
| | | <div v-else-if="item.type == 'imageUpload'"> |
| | | <el-image |
| | | v-for="(img, imgIndex) in item.data.images" |
| | | :key="imgIndex" |
| | | :src="img.url" |
| | | :preview-src-list="[img.url]" |
| | | fit="cover" |
| | | class="preview-image" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | import Table from "../Table/index.vue"; |
| | | import AiEditor from "../AiEditor/index.vue"; |
| | | |
| | | export default { |
| | | name: "ViewDynamicComponent", |
| | | components: { |
| | | Table, |
| | | AiEditor |
| | | }, |
| | | props: { |
| | | title: { |
| | | type: String, |
| | | default: "", |
| | | }, |
| | | components: { |
| | | type: Array, |
| | | default: () => [], |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | | userOptions: [ |
| | | { value: '1', label: '用户1' }, |
| | | { value: '2', label: '用户2' }, |
| | | { value: '3', label: '用户3' }, |
| | | { value: '4', label: '用户4' }, |
| | | { value: '5', label: '用户5' } |
| | | ] |
| | | }; |
| | | }, |
| | | methods: { |
| | | getUserName(userId) { |
| | | const user = this.userOptions.find(u => u.value === userId); |
| | | return user ? user.label : userId; |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="less"> |
| | | .preview-image{ |
| | | width: 120px; |
| | | height: 120px; |
| | | border-radius: 4px; |
| | | object-fit: cover; |
| | | margin-right: 20px; |
| | | } |
| | | .choose-material { |
| | | background: #eff8fa; |
| | | padding: 20px; |
| | | margin-top: 37px; |
| | | } |
| | | .has-title{ |
| | | margin-top: 0px !important; |
| | | } |
| | | .add-group { |
| | | display: flex; |
| | | align-items: center; |
| | | margin-bottom: 19px; |
| | | |
| | | div { |
| | | color: #f56c6c; |
| | | } |
| | | |
| | | span { |
| | | font-weight: 500; |
| | | font-size: 14px; |
| | | color: #222222; |
| | | line-height: 21px; |
| | | margin: 0 32px 0 8px; |
| | | } |
| | | } |
| | | .dynamic-component { |
| | | background: #ffffff; |
| | | padding: 15px 20px; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .rich-text-content { |
| | | width: 100%; |
| | | min-height: 200px; |
| | | padding: 10px; |
| | | border: 1px solid #dcdfe6; |
| | | border-radius: 4px; |
| | | background-color: #f5f7fa; |
| | | } |
| | | |
| | | .image-preview { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 8px; |
| | | |
| | | .preview-image { |
| | | width: 120px; |
| | | height: 120px; |
| | | border-radius: 4px; |
| | | object-fit: cover; |
| | | } |
| | | } |
| | | |
| | | .user-tags { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 4px; |
| | | } |
| | | |
| | | .user-tag { |
| | | margin-right: 4px; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | |
| | | .uploaf-notice { |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: rgba(0, 0, 0, 0.85); |
| | | line-height: 22px; |
| | | margin-top: 8px; |
| | | } |
| | | |
| | | .groupTable { |
| | | width: 100%; |
| | | margin-top: 10px; |
| | | ::v-deep .el-input__inner { |
| | | width: unset !important; |
| | | } |
| | | } |
| | | </style> |
| | |
| | | }) |
| | | .catch(() => {}); |
| | | }, |
| | | confirmAddRow(formData) { |
| | | const { idx, rowIndex, isEdit } = this.rowDialog; |
| | | if (isEdit) { |
| | | // 编辑模式:替换原有行数据 |
| | | this.components[idx].data.rows.splice(rowIndex, 1, formData); |
| | | } else { |
| | | // 新增模式:添加新行数据 |
| | | this.components[idx].data.rows.push(formData); |
| | | } |
| | | this.rowDialog.visible = false; |
| | | // 重置对话框数据 |
| | | this.rowDialog = { |
| | | visible: false, |
| | | idx: null, |
| | | rowIndex: null, |
| | | isEdit: false, |
| | | headers: [], |
| | | form: {}, |
| | | }; |
| | | }, |
| | | handleFileChange(idx, fileList) { |
| | | this.components[idx].data.fileList = fileList; |
| | | }, |
| | |
| | | file.url = res.url; |
| | | this.components[idx].data.imageList = fileList; |
| | | }, |
| | | // 获取所有组件数据 |
| | | getComponentsData() { |
| | | // 整理数据,图片只保留url |
| | | const submitData = this.components.map((item) => { |
| | | if (item.type === "richText") { |
| | | // 获取富文本编辑器的内容 |
| | | const editorRef = this.$refs[`editor_${item.id}`]; |
| | | return { |
| | | ...item, |
| | | data: { |
| | | content: editorRef ? editorRef.getContent() : item.data.content |
| | | } |
| | | }; |
| | | } |
| | | if (item.type === "imageUpload") { |
| | | return { |
| | | ...item, |
| | | data: { |
| | | imageList: item.data.imageList.map((img) => ({ url: img.url })), |
| | | }, |
| | | }; |
| | | } |
| | | return item; |
| | | }); |
| | | return submitData; |
| | | }, |
| | | // 验证所有组件数据 |
| | | validateComponents() { |
| | | // 验证富文本编辑器 |
| | | const richTextValid = this.components.every(item => { |
| | | if (item.type === 'richText') { |
| | | const editorRef = this.$refs[`editor_${item.id}`]; |
| | | return editorRef && editorRef.getContent().trim() !== ''; |
| | | } |
| | | return true; |
| | | }); |
| | | |
| | | if (!richTextValid) { |
| | | this.$message.error('请填写所有富文本内容'); |
| | | return false; |
| | | } |
| | | |
| | | // 验证表格数据 |
| | | const tableValid = this.components.every(item => { |
| | | if (item.type === 'customTable') { |
| | | return item.data.rows.length > 0; |
| | | } |
| | | return true; |
| | | }); |
| | | |
| | | if (!tableValid) { |
| | | this.$message.error('请至少添加一行表格数据'); |
| | | return false; |
| | | } |
| | | |
| | | return true; |
| | | }, |
| | | // 提交数据 |
| | | submit() { |
| | | if (this.validateComponents()) { |
| | | const data = this.getComponentsData(); |
| | | this.$emit('submit', data); |
| | | } |
| | | } |
| | | }, |
| | | }; |
| | | </script> |
| | |
| | | const routes = [ |
| | | { |
| | | path: "/", |
| | | redirect: "/projectList/list", |
| | | redirect: "/login", |
| | | }, |
| | | { |
| | | path: "/login", |
| | |
| | | keepAlive: true, |
| | | }, |
| | | component: () => import("../views/dataManagement/testResultReport/detail.vue"), |
| | | }, |
| | | { |
| | | path: "suspendExperiment", |
| | | meta: { |
| | | title: "实验中止审批", |
| | | keepAlive: true, |
| | | }, |
| | | component: () => import("../views/dataManagement/suspendExperiment/list.vue"), |
| | | }, |
| | | ], |
| | | }, |
| | |
| | | document.title = to.meta.title || '实验室流程'; |
| | | |
| | | // 登录验证 |
| | | // if (to.path === "/login") { |
| | | // sessionStorage.removeItem('userInfo') |
| | | // next() |
| | | // } else if (!sessionStorage.getItem('userInfo')) { |
| | | // next('/login') |
| | | // } else { |
| | | // // 判断是否拥有要跳转菜单权限 |
| | | // let menus = store.state.menus |
| | | // if (to.meta.hasOwnProperty('privilege') && !menus.includes(to.meta.privilege)) { |
| | | // return |
| | | // } |
| | | // 排除登录页的校验 |
| | | if (to.path === "/login") { |
| | | if (sessionStorage.getItem('token')) { |
| | | next('/system'); // 已登录状态访问登录页时重定向到系统首页 |
| | | return; |
| | | } |
| | | next(); |
| | | 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) { |
| | |
| | | } |
| | | |
| | | next() |
| | | // } |
| | | }); |
| | | |
| | | export default router; |
| | |
| | | const apiConfig = { |
| | | // 开发环境 |
| | | development: { |
| | | baseURL: "", |
| | | baseURL: "http://192.168.110.34:8081", |
| | | imgUrl: "", |
| | | }, |
| | | // 生产环境 |
New file |
| | |
| | | import axios from 'axios' |
| | | import apiConfig from './baseurl' |
| | | |
| | | let encryptionKey = '2022lab02ora12to' // 默认密钥 |
| | | |
| | | // 从接口获取密钥 |
| | | export const fetchEncryptionKey = async () => { |
| | | try { |
| | | const response = await axios.get(`${apiConfig.baseURL}/api/system/getEncryptionKey`) |
| | | if (response.data && response.data.code === 200) { |
| | | // 转换为Buffer并验证字节长度 |
| | | const keyBuffer = Buffer.from(response.data.data, 'utf-8') |
| | | |
| | | if (keyBuffer.length !== 16) { |
| | | console.warn('无效密钥长度,使用默认密钥') |
| | | return encryptionKey // 保持原有密钥 |
| | | } |
| | | |
| | | // 存储原始字符串和Buffer两种格式 |
| | | encryptionKey = response.data.data |
| | | return encryptionKey |
| | | } |
| | | } catch (error) { |
| | | console.error('获取加密密钥失败:', error) |
| | | } |
| | | } |
| | | |
| | | // 新增方法获取Buffer格式的密钥 |
| | | export const getEncryptionKeyBuffer = () => { |
| | | return Buffer.from(encryptionKey, 'utf-8') |
| | | } |
| | | |
| | | // 获取当前密钥(保持字符串格式) |
| | | export const getEncryptionKey = () => encryptionKey |
| | |
| | | import axios from 'axios' |
| | | import apiConfig from './baseurl' |
| | | |
| | | import { |
| | | Message |
| | | } from 'element-ui' |
| | | |
| | | import { Message } from 'element-ui' |
| | | import { encryptBySM4, decryptBySM4 } from './sm4' // 添加decryptBySM4 |
| | | |
| | | const service = axios.create({ |
| | | baseURL: apiConfig.baseURL, |
| | | // baseURL: apiConfig.baseURL, |
| | | withCredentials: false, // 当跨域请求时发送cookie |
| | | timeout: 30000, // request timeout |
| | | }) |
| | |
| | | service.interceptors.request.use( |
| | | config => { |
| | | config['headers']['Authorization'] = `${sessionStorage.getItem('token')}` |
| | | |
| | | // 判断是否需要加密(只对/api开头的请求进行加密) |
| | | const needEncrypt = config.url.startsWith('/api'); |
| | | |
| | | if (config.method == 'get') { |
| | | if (!config.params) config.params = {}; |
| | | config.params = { |
| | |
| | | } |
| | | if (config.method == 'post') { |
| | | if (!config.data) config.data = {}; |
| | | config.data = { |
| | | ...config.data, |
| | | if (needEncrypt) { |
| | | config.data = { param: encryptBySM4(config.data) }; |
| | | } |
| | | } |
| | | return config |
| | |
| | | return |
| | | } |
| | | const res = response; |
| | | |
| | | // 新增解密处理:仅处理/api路径的POST响应 |
| | | if (res.config.method === 'post' && res.config.url.startsWith('/api')) { |
| | | try { |
| | | if (res.data && res.data.data) { |
| | | // 这里假设使用decryptBySM4进行解密 |
| | | res.data.data = decryptBySM4(res.data.data); |
| | | } |
| | | } catch (e) { |
| | | console.error('数据解密失败:', e); |
| | | } |
| | | } |
| | | |
| | | if (res.data.code == 200) { |
| | | if (!res.data.data) { |
| | | if (!res.data) { |
| | | return Promise.resolve({}) |
| | | } |
| | | return Promise.resolve(res.data.data) |
| | | return Promise.resolve(res.data) |
| | | } else { |
| | | if (res.data.code == 103 || res.data.code == 401) { |
| | | Message({ |
New file |
| | |
| | | import { sm4 } from 'sm-crypto'; |
| | | import { getEncryptionKey, fetchEncryptionKey, getEncryptionKeyBuffer } from './encryption'; |
| | | |
| | | // SM4加密函数 |
| | | export const encryptBySM4 = (data) => { |
| | | const key = getEncryptionKeyBuffer(); // 获取当前密钥 |
| | | return sm4.encrypt(JSON.stringify(data), key); |
| | | }; |
| | | |
| | | // SM4解密函数 |
| | | export const decryptBySM4 = (data) => { |
| | | const key = getEncryptionKeyBuffer(); // 获取当前密钥 |
| | | return JSON.parse(sm4.decrypt(data, key)); |
| | | }; |
| | |
| | | </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 type="primary" @click="handleSearch" style="margin-left: 20px;">查询</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </template> |
| | |
| | | |
| | | <script> |
| | | import ApprovalDialog from './components/approvalDialog.vue' |
| | | import { getProposalList } from './service' |
| | | |
| | | export default { |
| | | name: "ProjectList", |
| | |
| | | createTime: [], |
| | | approver: "", |
| | | status: "", |
| | | pageNum: 1, |
| | | pageSize: 10, |
| | | }, |
| | | tableData: [], |
| | | total: 0, |
| | |
| | | createTime: [], |
| | | approver: "", |
| | | status: "", |
| | | pageNum: 1, |
| | | pageSize: 10, |
| | | }; |
| | | }, |
| | | handleSearch() { |
| | |
| | | getTableData() { |
| | | // 根据currentType请求不同的数据 |
| | | if (this.currentType === 'list') { |
| | | getProposalList(this.form).then(res => { |
| | | console.log(res,'1111111111111111111111111') |
| | | }) |
| | | this.tableData = this.mockListData; |
| | | this.total = this.mockListData.length; |
| | | } else { |
New file |
| | |
| | | import axios from '@/utils/request'; |
| | | |
| | | // 添加项目课题方案 |
| | | export function addProposal(data) { |
| | | return axios.post('/api/t-project-proposal/add', { ...data }) |
| | | } |
| | | //修改方案 |
| | | export function updateProposal(data) { |
| | | return axios.post('/api/t-project-proposal/update', { ...data }) |
| | | } |
| | | //查询方案列表 |
| | | export function getProposalList(data) { |
| | | return axios.post('/api/t-project-proposal/pageList', { ...data }) |
| | | } |
| | | //上下架 |
| | | export function upAndDown(data) { |
| | | return axios.post('/api/t-project-proposal/upAndDown', { ...data }) |
| | | } |
| | | //审批 |
| | | export function audit(data) { |
| | | return axios.post('/api/t-project-proposal/audit', { ...data }) |
| | | } |
| | | |
| | | //删除 |
| | | export function deleteById(data) { |
| | | return axios.delete('/open/t-project-proposal/deleteById', { ...data }) |
| | | } |
| | | |
| | | //批量删除 |
| | | export function deleteByIds(data) { |
| | | return axios.delete('/open/t-project-proposal/deleteByIds', { ...data }) |
| | | } |
| | | //根据id查询方案详情 |
| | | export function getDetailById(data) { |
| | | return axios.get('/open/t-project-proposal/getDetailById', { ...data }) |
| | | } |
| | |
| | | <template> |
| | | <Card> |
| | | <template style="position: relative"> |
| | | <el-form |
| | | ref="form" |
| | | :model="form" |
| | | :rules="rules" |
| | | inline |
| | | label-position="top" |
| | | > |
| | | <div class="header-title" style="margin-bottom: 38px;justify-content: space-between;"> |
| | | <div style="display: flex;align-items: center;gap: 13px;"> |
| | | <el-form ref="form" :model="form" :rules="rules" inline label-position="top"> |
| | | <div class="header-title" style="margin-bottom: 38px; justify-content: space-between"> |
| | | <div style="display: flex; align-items: center; gap: 13px"> |
| | | <div class="header-title-left"> |
| | | <img src="@/assets/public/headercard.png" /> |
| | | <div>所属实验调度</div> |
| | | </div> |
| | | <el-button |
| | | @click="showScheduling = true" |
| | | class="el-icon-plus" |
| | | type="primary" |
| | | > |
| | | 选择实验调度</el-button |
| | | > |
| | | <el-button @click="showScheduling = true" class="el-icon-plus" type="primary"> |
| | | 选择实验调度</el-button> |
| | | </div> |
| | | <el-button |
| | | @click="handleStopExperiment" |
| | | type="danger" |
| | | > |
| | | 申请终止实验</el-button |
| | | > |
| | | <el-button @click="handleStopExperiment" type="danger"> |
| | | 申请终止实验</el-button> |
| | | </div> |
| | | <Table |
| | | :data="groupTableData" |
| | | :total="0" |
| | | :height="null" |
| | | class="groupTable" |
| | | > |
| | | <el-table-column |
| | | type="index" |
| | | label="序号" |
| | | width="80" |
| | | ></el-table-column> |
| | | <Table :data="groupTableData" :total="0" :height="null" class="groupTable"> |
| | | <el-table-column type="index" label="序号" width="80"></el-table-column> |
| | | <el-table-column prop="groupName" label="组别"></el-table-column> |
| | | <el-table-column prop="remark" label="备注"></el-table-column> |
| | | <el-table-column label="操作" width="200"> |
| | | <template slot-scope="scope"> |
| | | <el-button type="text" @click="handleEditGroup(scope.row)" |
| | | >编辑</el-button |
| | | > |
| | | <el-button type="text" @click="handleDeleteGroup(scope.row)" |
| | | >移除</el-button |
| | | > |
| | | <el-button type="text" @click="handleEditGroup(scope.row)">编辑</el-button> |
| | | <el-button type="text" @click="handleDeleteGroup(scope.row)">移除</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </Table> |
| | |
| | | <!-- <el-button type="primary" class="el-icon-plus" @click="handleAddGroup">添加组别</el-button> --> |
| | | </div> |
| | | |
| | | <Table |
| | | :data="groupTableData" |
| | | :total="0" |
| | | :height="null" |
| | | class="groupTable" |
| | | > |
| | | <el-table-column |
| | | type="index" |
| | | label="序号" |
| | | width="80" |
| | | ></el-table-column> |
| | | <Table :data="groupTableData" :total="0" :height="null" class="groupTable"> |
| | | <el-table-column type="index" label="序号" width="80"></el-table-column> |
| | | <el-table-column prop="groupName" label="组别"></el-table-column> |
| | | <el-table-column prop="remark" label="备注"></el-table-column> |
| | | </Table> |
| | |
| | | <div class="add-group"> |
| | | <div>*</div> |
| | | <span>参加人员</span> |
| | | <el-button type="primary" class="el-icon-plus" @click="addMember" |
| | | >选择参加人员</el-button |
| | | > |
| | | <el-button type="primary" class="el-icon-plus" @click="addMember">选择参加人员</el-button> |
| | | </div> |
| | | <div class="member-list"> |
| | | <div v-for="item in 3" :key="item" class="member-list-card"> |
| | |
| | | <div class="member-title"> |
| | | {{ ["工艺工程师", "实验员", "化验师"][item - 1] }} |
| | | </div> |
| | | <div |
| | | :class="item == 1 || item == 2 ? 'member-name-box' : 'flex1'" |
| | | > |
| | | <div |
| | | :class=" |
| | | item == 1 || item == 2 |
| | | ? 'member-name-box' |
| | | : 'member-name-box-2' |
| | | " |
| | | > |
| | | <div |
| | | v-for="i in memberList(item)" |
| | | :key="i" |
| | | class="member-name" |
| | | > |
| | | <div :class="item == 1 || item == 2 ? 'member-name-box' : 'flex1'"> |
| | | <div :class="item == 1 || item == 2 ? 'member-name-box': 'member-name-box-2'"> |
| | | <div v-for="i in memberList(item)" :key="i" class="member-name"> |
| | | 张三 |
| | | </div> |
| | | </div> |
| | |
| | | </div> |
| | | </div> |
| | | <div class="content-box"> |
| | | <AiEditor |
| | | ref="purposeEditor" |
| | | v-model="editorContents.purpose" |
| | | height="200px" |
| | | placeholder="请输入实验目的..." |
| | | /> |
| | | <AiEditor ref="purposeEditor" v-model="editorContents.purpose" height="200px" placeholder="请输入实验目的..." /> |
| | | </div> |
| | | |
| | | <div class="header-title" style="margin-bottom: 38px"> |
| | |
| | | </div> |
| | | </div> |
| | | <div class="content-box"> |
| | | <AiEditor |
| | | ref="processEditor" |
| | | v-model="editorContents.process" |
| | | height="200px" |
| | | placeholder="请输入工艺参数及路线..." |
| | | /> |
| | | <AiEditor ref="processEditor" v-model="editorContents.process" height="200px" placeholder="请输入工艺参数及路线..." /> |
| | | </div> |
| | | |
| | | <div class="header-title" style="margin-bottom: 38px"> |
| | |
| | | <div>三、实验材料及设备</div> |
| | | </div> |
| | | </div> |
| | | <DynamicComponent |
| | | ref="materialComponent" |
| | | title="实验材料" |
| | | @submit="handleMaterialSubmit" |
| | | /> |
| | | <DynamicComponent |
| | | ref="equipmentComponent" |
| | | title="实验所用设备" |
| | | @submit="handleEquipmentSubmit" |
| | | /> |
| | | <DynamicComponent ref="materialComponent" title="实验材料" @submit="handleMaterialSubmit" /> |
| | | <DynamicComponent ref="equipmentComponent" title="实验所用设备" @submit="handleEquipmentSubmit" /> |
| | | |
| | | <div class="header-title" style="margin-bottom: 38px"> |
| | | <div class="header-title-left"> |
| | |
| | | <div>四、实验操作步骤记录</div> |
| | | </div> |
| | | <el-button @click="handleAddStep" class="el-icon-plus" type="primary"> |
| | | 添加步骤</el-button |
| | | > |
| | | 添加步骤</el-button> |
| | | </div> |
| | | |
| | | <div class="step-list" v-for="(item, idx) in stepList" :key="idx"> |
| | |
| | | </div> |
| | | <div class="step-list-item-control"> |
| | | <div class="controlBtn edit" @click="handleEditStep(idx)"> |
| | | <img |
| | | src="@/assets/public/edit.png" |
| | | alt="编辑" |
| | | class="edit-icon" |
| | | /> |
| | | <img src="@/assets/public/edit.png" alt="编辑" class="edit-icon" /> |
| | | 编辑 |
| | | </div> |
| | | <div class="controlBtn delete" @click="handleDeleteStep(idx)"> |
| | | <img |
| | | src="@/assets/public/delete.png" |
| | | alt="删除" |
| | | class="delete-icon" |
| | | /> |
| | | <img src="@/assets/public/delete.png" alt="删除" class="delete-icon" /> |
| | | 删除 |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <DynamicComponent |
| | | :ref="'stepContent' + idx" |
| | | @submit="(content) => handleStepContentSubmit(idx, content)" |
| | | /> |
| | | <DynamicComponent :ref="'stepContent' + idx" @submit="(content) => handleStepContentSubmit(idx, content)" /> |
| | | </div> |
| | | |
| | | <div class="add-project-footer"> |
| | | <el-button type="primary" class="save-btn" @click="handleSave" |
| | | >发送</el-button |
| | | > |
| | | <el-button type="primary" class="save-btn" @click="handleSave">发送</el-button> |
| | | <el-button @click="handleSaveDraft">存草稿</el-button> |
| | | </div> |
| | | </el-form> |
| | |
| | | this.$message.success("删除成功"); |
| | | } |
| | | }) |
| | | .catch(() => {}); |
| | | .catch(() => { }); |
| | | }, |
| | | handleGroupSubmit(form) { |
| | | const index = this.groupTableData.findIndex( |
| | |
| | | this.$message.success("删除成功"); |
| | | } |
| | | }) |
| | | .catch(() => {}); |
| | | .catch(() => { }); |
| | | }, |
| | | handleTaskSubmit(form) { |
| | | const index = this.taskTableData.findIndex( |
| | |
| | | this.stepList.splice(index, 1); |
| | | this.$message.success("删除成功"); |
| | | }) |
| | | .catch(() => {}); |
| | | .catch(() => { }); |
| | | }, |
| | | handleEditStep(index) { |
| | | this.editingStepIndex = index; |
| | |
| | | return true; |
| | | }, |
| | | handleStopExperiment() { |
| | | this.$router.push('/dataManagement/scheme-management/stop-experiment') |
| | | this.$router.push("/dataManagement/scheme-management/stop-experiment"); |
| | | }, |
| | | }, |
| | | }; |
| | |
| | | flex-wrap: wrap; |
| | | gap: 13px; |
| | | margin-top: 38px; |
| | | |
| | | .header-title-left { |
| | | display: flex; |
| | | align-items: center; |
| | |
| | | |
| | | .header-title:first-child { |
| | | margin-top: 0px; |
| | | |
| | | .header-title-left { |
| | | margin-top: 0; |
| | | } |
| | |
| | | width: 65%; |
| | | padding-left: 40px; |
| | | } |
| | | |
| | | .rwuTable { |
| | | width: 85%; |
| | | padding-left: 40px; |
| | |
| | | border: 1px solid #dcdfe6; |
| | | |
| | | &:nth-child(1) { |
| | | background: linear-gradient( |
| | | to bottom, |
| | | rgba(4, 156, 154, 0.2) 0%, |
| | | rgba(5, 242, 194, 0) 70% |
| | | ); |
| | | background: linear-gradient(to bottom, |
| | | rgba(4, 156, 154, 0.2) 0%, |
| | | rgba(5, 242, 194, 0) 70%); |
| | | } |
| | | |
| | | &:nth-child(2) { |
| | | background: linear-gradient( |
| | | to bottom, |
| | | rgba(5, 160, 193, 0.2) 0%, |
| | | rgba(5, 242, 194, 0) 70% |
| | | ); |
| | | background: linear-gradient(to bottom, |
| | | rgba(5, 160, 193, 0.2) 0%, |
| | | rgba(5, 242, 194, 0) 70%); |
| | | } |
| | | |
| | | &:nth-child(3) { |
| | | background: linear-gradient( |
| | | to bottom, |
| | | rgba(255, 77, 79, 0.2) 0%, |
| | | rgba(255, 242, 194, 0) 70% |
| | | ); |
| | | background: linear-gradient(to bottom, |
| | | rgba(255, 77, 79, 0.2) 0%, |
| | | rgba(255, 242, 194, 0) 70%); |
| | | } |
| | | |
| | | &:nth-child(4) { |
| | | background: linear-gradient( |
| | | to bottom, |
| | | rgba(250, 199, 20, 0.21) 0%, |
| | | rgba(255, 242, 194, 0) 70% |
| | | ); |
| | | background: linear-gradient(to bottom, |
| | | rgba(250, 199, 20, 0.21) 0%, |
| | | rgba(255, 242, 194, 0) 70%); |
| | | } |
| | | |
| | | .member-item { |
| | |
| | | line-height: 16px; |
| | | text-align: center; |
| | | } |
| | | |
| | | .flex1 { |
| | | flex: 1; |
| | | } |
| | |
| | | padding: 10px 0; |
| | | margin-top: auto; |
| | | cursor: pointer; |
| | | |
| | | .member-change-btn { |
| | | background: #fff1f0; |
| | | border-radius: 4px; |
| | |
| | | padding: 20px; |
| | | margin-top: 37px; |
| | | } |
| | | |
| | | .step-list { |
| | | background: #eff8fa; |
| | | padding: 20px; |
| | | |
| | | .step-list-item { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | padding: 25px; |
| | | background: #ffffff; |
| | | |
| | | .step-list-item-title { |
| | | font-weight: 500; |
| | | font-size: 14px; |
| | |
| | | flex-wrap: wrap; |
| | | flex: 1; |
| | | } |
| | | |
| | | .step-list-item-control { |
| | | display: flex; |
| | | align-items: center; |
| | | |
| | | .controlBtn { |
| | | height: 24px; |
| | | background: #ffffff; |
| | |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .edit { |
| | | border: 1px solid #44be09; |
| | | font-family: PingFangSC, PingFang SC; |
| | |
| | | line-height: 24px; |
| | | margin-right: 50px; |
| | | } |
| | | |
| | | .delete { |
| | | border: 1px solid #ff4d4f; |
| | | font-family: PingFangSC, PingFang SC; |
| | |
| | | color: #ff4d4f; |
| | | line-height: 24px; |
| | | } |
| | | |
| | | .edit-icon { |
| | | width: 14px; |
| | | height: 14px; |
| | | margin-right: 8px; |
| | | } |
| | | |
| | | .delete-icon { |
| | | width: 13px; |
| | | height: 13px; |
| | |
| | | <template> |
| | | <el-dialog |
| | | :title="dialogTitle" |
| | | :visible.sync="visible" |
| | | width="80%" |
| | | :close-on-click-modal="false" |
| | | @close="handleClose" |
| | | > |
| | | <div class="approval-dialog"> |
| | | <!-- 左侧审批内容 --> |
| | | <div class="approval-content"> |
| | | <Card class="approval-content-card"> |
| | | <template style="position: relative"> |
| | | <div class="header-title"> |
| | | <div class="header-title-left"> |
| | | <img src="@/assets/public/headercard.png" /> |
| | | <span>项目课题方案信息</span> |
| | | </div> |
| | | </div> |
| | | <el-form |
| | | ref="form" |
| | | :model="form" |
| | | :rules="rules" |
| | | inline |
| | | label-position="top" |
| | | style="margin-top: 38px" |
| | | > |
| | | <el-form-item prop="name" label="项目课题方案名称"> |
| | | <el-input v-model="form.name" placeholder="请输入" /> |
| | | </el-form-item> |
| | | <el-form-item prop="description" label="项目阶段"> |
| | | <el-input v-model="form.description" placeholder="请输入" /> |
| | | </el-form-item> |
| | | <el-form-item prop="description" label="项目课题方案编号"> |
| | | <el-input v-model="form.description" placeholder="请输入" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div class="header-title"> |
| | | <div class="header-title-left"> |
| | | <img src="@/assets/public/headercard.png" /> |
| | | <div>一 、实验目的</div> |
| | | </div> |
| | | </div> |
| | | <div class="header-title"> |
| | | <div class="header-title-left"> |
| | | <img src="@/assets/public/headercard.png" /> |
| | | <div>二 、实验拆料和设备</div> |
| | | </div> |
| | | </div> |
| | | <div class="item-title"> |
| | | <span>1.实验材料</span> |
| | | </div> |
| | | <div class="item-title"> |
| | | <span>2.实验设备</span> |
| | | </div> |
| | | <div> |
| | | <el-dialog |
| | | title="实验方案详情" |
| | | :visible.sync="visible" |
| | | width="80%" |
| | | :close-on-click-modal="false" |
| | | @close="handleClose" |
| | | > |
| | | <div class="approval-dialog"> |
| | | <!-- 左侧审批内容 --> |
| | | <div class="approval-content"> |
| | | <Card class="approval-content-card"> |
| | | <template style="position: relative"> |
| | | <el-form |
| | | ref="form" |
| | | :model="form" |
| | | :rules="rules" |
| | | inline |
| | | label-position="top" |
| | | :disabled="type === 'view'" |
| | | > |
| | | <div class="header-title" style="margin-bottom: 38px"> |
| | | <div class="header-title-left"> |
| | | <img src="@/assets/public/headercard.png" /> |
| | | <div>所属实验调度</div> |
| | | </div> |
| | | </div> |
| | | <Table :data="groupTableData" :total="0" :height="null"> |
| | | <el-table-column |
| | | type="index" |
| | | label="序号" |
| | | width="80" |
| | | ></el-table-column> |
| | | <el-table-column |
| | | prop="groupName" |
| | | label="组别" |
| | | ></el-table-column> |
| | | <el-table-column prop="remark" label="备注"></el-table-column> |
| | | </Table> |
| | | |
| | | <div class="header-title"> |
| | | <div class="header-title-left"> |
| | | <img src="@/assets/public/headercard.png" /> |
| | | <div>三 、检测方法及开发</div> |
| | | </div> |
| | | </div> |
| | | <div class="header-title"> |
| | | <div class="header-title-left"> |
| | | <img src="@/assets/public/headercard.png" /> |
| | | <div>四 、实验步骤</div> |
| | | </div> |
| | | </div> |
| | | <div class="header-title"> |
| | | <div class="header-title-left"> |
| | | <img src="@/assets/public/headercard.png" /> |
| | | <div>五 、数据采集及分析</div> |
| | | </div> |
| | | </div> |
| | | <div class="header-title"> |
| | | <div class="header-title-left"> |
| | | <img src="@/assets/public/headercard.png" /> |
| | | <div>六 、结果评估</div> |
| | | </div> |
| | | </div> |
| | | <div class="header-title"> |
| | | <div class="header-title-left"> |
| | | <img src="@/assets/public/headercard.png" /> |
| | | <span>注意事项</span> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | <SelectMember ref="selectMember" /> |
| | | </Card> |
| | | </div> |
| | | <!-- 右侧审批流程 --> |
| | | <div class="approval-flow"> |
| | | <div class="flow-content"> |
| | | <approval-process |
| | | :status="form.status" |
| | | :submit-time="form.createTime" |
| | | :approver="form.approver" |
| | | :approve-time="form.approveTime" |
| | | /> |
| | | <div class="header-title" style="margin-bottom: 38px"> |
| | | <div class="header-title-left"> |
| | | <img src="@/assets/public/headercard.png" /> |
| | | <span>基础信息</span> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="add-group"> |
| | | <span>组别列表</span> |
| | | </div> |
| | | <Table |
| | | :data="groupTableData" |
| | | :total="0" |
| | | :height="null" |
| | | class="groupTable" |
| | | > |
| | | <el-table-column |
| | | type="index" |
| | | label="序号" |
| | | width="80" |
| | | ></el-table-column> |
| | | <el-table-column |
| | | prop="groupName" |
| | | label="组别" |
| | | ></el-table-column> |
| | | <el-table-column prop="remark" label="备注"></el-table-column> |
| | | </Table> |
| | | |
| | | <div style="padding-left: 25px; margin-top: 20px"> |
| | | <el-form-item prop="testTime" label="试验时间"> |
| | | <el-date-picker |
| | | v-model="form.testTime" |
| | | type="datetime" |
| | | placeholder="选择日期时间" |
| | | value-format="yyyy-MM-dd HH:mm:ss" |
| | | /> |
| | | </el-form-item> |
| | | </div> |
| | | <div class="add-group"> |
| | | <div>*</div> |
| | | <span>实验人员</span> |
| | | </div> |
| | | <div class="member-list"> |
| | | <div v-for="item in 3" :key="item" class="member-list-card"> |
| | | <div class="member-item"> |
| | | <div class="member-title"> |
| | | {{ ["工艺工程师", "实验员", "化验师"][item - 1] }} |
| | | </div> |
| | | <div |
| | | :class=" |
| | | item == 1 || item == 2 || item == 3 |
| | | ? 'member-name-box' |
| | | : 'flex1' |
| | | " |
| | | > |
| | | <div |
| | | :class=" |
| | | item == 1 || item == 2 || item == 3 |
| | | ? 'member-name-box' |
| | | : 'member-name-box-2' |
| | | " |
| | | > |
| | | <div |
| | | v-for="i in memberList(item)" |
| | | :key="i" |
| | | class="member-name" |
| | | > |
| | | 张三 |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="member-change" v-if="type !== 'view'"> |
| | | <div class="member-change-btn">修改</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="header-title" style="margin-bottom: 38px"> |
| | | <div class="header-title-left"> |
| | | <img src="@/assets/public/headercard.png" /> |
| | | <div>一、实验目的</div> |
| | | </div> |
| | | </div> |
| | | <AiEditor |
| | | ref="purposeEditor" |
| | | v-model="form.purpose" |
| | | height="200px" |
| | | placeholder="请输入实验目的..." |
| | | /> |
| | | |
| | | <div class="header-title" style="margin-bottom: 38px"> |
| | | <div class="header-title-left"> |
| | | <img src="@/assets/public/headercard.png" /> |
| | | <div>二、工艺参数及路线</div> |
| | | </div> |
| | | </div> |
| | | <AiEditor |
| | | ref="processEditor" |
| | | v-model="form.process" |
| | | height="200px" |
| | | placeholder="请输入工艺参数及路线..." |
| | | /> |
| | | |
| | | <div class="header-title" style="margin-bottom: 38px"> |
| | | <div class="header-title-left"> |
| | | <img src="@/assets/public/headercard.png" /> |
| | | <div>三、实验材料及设备</div> |
| | | </div> |
| | | </div> |
| | | <ViewDynamicComponent |
| | | title="实验材料" |
| | | :components="form.materialsAndEquipment || []" |
| | | /> |
| | | <ViewDynamicComponent |
| | | title="实验所用设备" |
| | | :components="form.materialsAndEquipment || []" |
| | | /> |
| | | |
| | | <div class="header-title" style="margin-bottom: 38px"> |
| | | <div class="header-title-left"> |
| | | <img src="@/assets/public/headercard.png" /> |
| | | <div>四、实验操作步骤记录</div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="step-list" v-for="(item, idx) in form.operationSteps" :key="idx"> |
| | | <div class="step-list-item"> |
| | | <div class="step-list-item-title"> |
| | | 步骤{{ idx + 1 }}:{{ item.stepName }} |
| | | </div> |
| | | </div> |
| | | <ViewDynamicComponent |
| | | :ref="'stepContent' + idx" |
| | | :components="[item]" |
| | | /> |
| | | </div> |
| | | </el-form> |
| | | </template> |
| | | </Card> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="approval-dialog-approve"> |
| | | <div class="status"> |
| | | <div class="status-title">审批结果</div> |
| | | <div class="status-content"> |
| | | <div |
| | | class="resolve" |
| | | :class="status == '1' && 'activeStatus'" |
| | | @click.stop="status = 1" |
| | | > |
| | | 通过 |
| | | </div> |
| | | <div |
| | | class="reject" |
| | | :class="status == '2' && 'activeStatus'" |
| | | @click.stop="status = 2" |
| | | > |
| | | 驳回 |
| | | <!-- 右侧审批流程 --> |
| | | <div class="approval-flow" v-if="type === 'view'"> |
| | | <div class="flow-content"> |
| | | <approval-process |
| | | :status="form.status" |
| | | :submit-time="form.createTime" |
| | | :approver="form.approver" |
| | | :approve-time="form.approveTime" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="remark"> |
| | | <div class="remark-title">审批意见</div> |
| | | <el-input type="textarea" v-model="remark" placeholder="请输入审批意见" /> |
| | | </div> |
| | | </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 |
| | | > |
| | | </div> |
| | | </el-dialog> |
| | | </el-dialog> |
| | | <SignatureCanvas |
| | | :visible="signatureDialogVisible" |
| | | @confirm="handleSignatureConfirm" |
| | | /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | import ApprovalProcess from '@/components/approvalProcess' |
| | | import ApprovalProcess from "@/components/approvalProcess"; |
| | | import SignatureCanvas from "@/components/SignatureCanvas.vue"; |
| | | import ViewDynamicComponent from "@/components/DynamicComponent/ViewDynamicComponent.vue"; |
| | | import AiEditor from "@/components/AiEditor/index.vue"; |
| | | |
| | | export default { |
| | | name: "ApprovalDialog", |
| | | components: { |
| | | ApprovalProcess |
| | | ApprovalProcess, |
| | | SignatureCanvas, |
| | | ViewDynamicComponent, |
| | | AiEditor, |
| | | }, |
| | | props: { |
| | | visible: { |
| | |
| | | planName: "", |
| | | planCode: "", |
| | | stage: "", |
| | | testDate: "", |
| | | testName: "", |
| | | testCode: "", |
| | | testTime: "", |
| | | creator: "", |
| | | createTime: "", |
| | | approvalComment: "", |
| | | status: "pending", |
| | | status: "approved", |
| | | approver: "", |
| | | approveTime: "" |
| | | approveTime: "", |
| | | materialsAndEquipment: [ |
| | | { |
| | | id: 1, |
| | | type: "richText", |
| | | data: { |
| | | content: |
| | | "<p>1. 实验材料说明</p><p>2. 设备使用说明</p><p>3. 安全注意事项</p>", |
| | | }, |
| | | }, |
| | | { |
| | | id: 2, |
| | | type: "customTable", |
| | | data: { |
| | | headers: [ |
| | | { name: "材料名称", type: "text" }, |
| | | { name: "规格", type: "text" }, |
| | | { name: "数量", type: "text" }, |
| | | { name: "用途", type: "text" }, |
| | | ], |
| | | rows: [ |
| | | { |
| | | 材料名称: "催化剂A", |
| | | 规格: "工业级", |
| | | 数量: "100g", |
| | | 用途: "反应催化剂", |
| | | updateTime: "2024-01-01 12:00:00", |
| | | }, |
| | | { |
| | | 材料名称: "溶剂B", |
| | | 规格: "分析纯", |
| | | 数量: "500ml", |
| | | 用途: "反应溶剂", |
| | | updateTime: "2024-01-01 12:00:00", |
| | | }, |
| | | ], |
| | | }, |
| | | }, |
| | | { |
| | | id: 3, |
| | | type: "fileUpload", |
| | | data: { |
| | | fileList: [ |
| | | { |
| | | name: "材料安全说明书.pdf", |
| | | url: "https://example.com/msds.pdf", |
| | | }, |
| | | { |
| | | name: "设备操作手册.docx", |
| | | url: "https://example.com/manual.docx", |
| | | }, |
| | | ], |
| | | }, |
| | | }, |
| | | { |
| | | id: 4, |
| | | type: "imageGallery", |
| | | data: { |
| | | images: [ |
| | | { |
| | | url: "https://example.com/equipment1.jpg", |
| | | title: "实验设备1", |
| | | description: "主要反应设备", |
| | | }, |
| | | { |
| | | url: "https://example.com/equipment2.jpg", |
| | | title: "实验设备2", |
| | | description: "辅助设备", |
| | | }, |
| | | ], |
| | | }, |
| | | }, |
| | | ], |
| | | operationSteps: [ |
| | | { |
| | | id: 7, |
| | | type: "richText", |
| | | data: { |
| | | content: |
| | | "<p>1. 准备工作</p><p>2. 设备检查</p><p>3. 实验操作</p><p>4. 数据记录</p>", |
| | | }, |
| | | }, |
| | | { |
| | | id: 8, |
| | | type: "customTable", |
| | | data: { |
| | | headers: [ |
| | | { name: "步骤", type: "text" }, |
| | | { name: "操作内容", type: "text" }, |
| | | { name: "操作人", type: "user" }, |
| | | { name: "操作图片", type: "image" }, |
| | | ], |
| | | rows: [ |
| | | { |
| | | 步骤: "步骤1", |
| | | 操作内容: "称取催化剂", |
| | | 操作人: ["1"], |
| | | 操作图片: [{ url: "https://example.com/step1.jpg" }], |
| | | updateTime: "2024-01-01 12:00:00", |
| | | }, |
| | | { |
| | | 步骤: "步骤2", |
| | | 操作内容: "加入溶剂", |
| | | 操作人: ["2"], |
| | | 操作图片: [{ url: "https://example.com/step2.jpg" }], |
| | | updateTime: "2024-01-01 12:00:00", |
| | | }, |
| | | ], |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | radio1: 1, |
| | | rules: {}, |
| | | rules: { |
| | | planName: [ |
| | | { |
| | | required: true, |
| | | message: "请输入项目课题方案名称", |
| | | trigger: "blur", |
| | | }, |
| | | ], |
| | | planCode: [ |
| | | { |
| | | required: true, |
| | | message: "请输入项目课题方案编号", |
| | | trigger: "blur", |
| | | }, |
| | | ], |
| | | stage: [{ required: true, message: "请输入项目阶段", trigger: "blur" }], |
| | | testDate: [ |
| | | { required: true, message: "请选择试验日期", trigger: "change" }, |
| | | ], |
| | | testName: [ |
| | | { required: true, message: "请输入实验名称", trigger: "blur" }, |
| | | ], |
| | | testCode: [ |
| | | { required: true, message: "请输入实验编号", trigger: "blur" }, |
| | | ], |
| | | testTime: [ |
| | | { required: true, message: "请选择试验时间", trigger: "change" }, |
| | | ], |
| | | }, |
| | | imgSrc: "", |
| | | signatureDialogVisible: false, |
| | | status: "1", |
| | | remark: "", |
| | | groupTableData: [], |
| | | taskTableData: [], |
| | | }; |
| | | }, |
| | | computed: { |
| | | dialogTitle() { |
| | | return this.type === "approve" ? "审批" : "审批详情"; |
| | | return this.type === "approve" ? "确认实验调度" : "实验调度详情"; |
| | | }, |
| | | }, |
| | | watch: { |
| | | data: { |
| | | handler(val) { |
| | | if (val) { |
| | | this.form = { ...val }; |
| | | // 深拷贝数据,避免直接修改props |
| | | this.form = JSON.parse( |
| | | JSON.stringify({ |
| | | ...this.form, |
| | | ...val, |
| | | // 确保这些字段存在,如果不存在则使用默认值 |
| | | materialsAndEquipment: val.materialsAndEquipment || [], |
| | | operationSteps: val.operationSteps || [], |
| | | }) |
| | | ); |
| | | console.log("接收到的数据:", this.form); |
| | | } |
| | | }, |
| | | immediate: true, |
| | | deep: true, |
| | | }, |
| | | visible: { |
| | | handler(val) { |
| | | if (val && this.type === "view") { |
| | | // 当弹窗打开且是查看模式时,获取详情数据 |
| | | this.getPlanDetail(); |
| | | } |
| | | }, |
| | | immediate: true, |
| | | }, |
| | | }, |
| | | methods: { |
| | | memberList(i) { |
| | | switch (i) { |
| | | case 1: |
| | | return [1]; |
| | | case 2: |
| | | return [1]; |
| | | case 3: |
| | | return [1, 2, 3, 4, 5, 6, 7, 8]; |
| | | case 4: |
| | | return [1, 2, 3, 4, 5, 6, 7, 8]; |
| | | default: |
| | | break; |
| | | } |
| | | }, |
| | | handleClose() { |
| | | this.$emit("update:visible", false); |
| | | this.form.approvalComment = ""; |
| | |
| | | status: "rejected", |
| | | }); |
| | | }, |
| | | memberList(item) { |
| | | return item === 1 ? 2 : item === 2 ? 3 : 1; |
| | | }, |
| | | openSignature() { |
| | | this.signatureDialogVisible = true; |
| | | }, |
| | | handleSignatureConfirm(imageData) { |
| | | console.log("imageData imageData", imageData); |
| | | this.signatureDialogVisible = false; |
| | | this.imgSrc = imageData; |
| | | |
| | | // 这里处理签名确认后的逻辑 |
| | | // this.$confirm('确认该实验调度吗?', '提示', { |
| | | // confirmButtonText: '确定', |
| | | // cancelButtonText: '取消', |
| | | // type: 'warning' |
| | | // }).then(() => { |
| | | // // 这里可以将签名图片数据(imageData)连同其他数据一起提交到后端 |
| | | // this.$message.success('确认成功'); |
| | | // this.signatureDialogVisible = false; |
| | | // this.getTableData(); |
| | | // }).catch(() => { |
| | | // this.signatureDialogVisible = false; |
| | | // }); |
| | | }, |
| | | // 获取方案详情 |
| | | async getPlanDetail() { |
| | | try { |
| | | // TODO: 替换为实际的接口调用 |
| | | // const { data } = await this.$api.getPlanDetail({ planCode: this.data.planCode }); |
| | | |
| | | // 模拟接口返回数据 |
| | | const mockDetailData = { |
| | | planCode: this.data.planCode, |
| | | planName: "2024年度实验室设备升级方案", |
| | | stage: "设备升级实验", |
| | | testDate: "2024-03-15", |
| | | testTime: "2024-03-15 14:00:00", |
| | | tester: "张三", |
| | | creator: "张三", |
| | | createTime: "2024-03-15", |
| | | status: "pending", |
| | | approver: "李四", |
| | | approveTime: "2024-03-16", |
| | | materialsAndEquipment: [ |
| | | { |
| | | id: 1, |
| | | type: "richText", |
| | | data: { |
| | | content: |
| | | "<p>1. 实验材料说明</p><p>2. 设备使用说明</p><p>3. 安全注意事项</p>", |
| | | }, |
| | | }, |
| | | { |
| | | id: 2, |
| | | type: "customTable", |
| | | data: { |
| | | headers: [ |
| | | { name: "材料名称", type: "text" }, |
| | | { name: "规格", type: "text" }, |
| | | { name: "数量", type: "text" }, |
| | | { name: "用途", type: "text" }, |
| | | ], |
| | | rows: [ |
| | | { |
| | | 材料名称: "催化剂A", |
| | | 规格: "工业级", |
| | | 数量: "100g", |
| | | 用途: "反应催化剂", |
| | | updateTime: "2024-01-01 12:00:00", |
| | | }, |
| | | { |
| | | 材料名称: "溶剂B", |
| | | 规格: "分析纯", |
| | | 数量: "500ml", |
| | | 用途: "反应溶剂", |
| | | updateTime: "2024-01-01 12:00:00", |
| | | }, |
| | | ], |
| | | }, |
| | | }, |
| | | { |
| | | id: 3, |
| | | type: "fileUpload", |
| | | data: { |
| | | fileList: [ |
| | | { |
| | | name: "材料安全说明书.pdf", |
| | | url: "https://example.com/msds.pdf", |
| | | }, |
| | | { |
| | | name: "设备操作手册.docx", |
| | | url: "https://example.com/manual.docx", |
| | | }, |
| | | ], |
| | | }, |
| | | }, |
| | | { |
| | | id: 4, |
| | | type: "imageUpload", |
| | | data: { |
| | | images: [ |
| | | { |
| | | url: "https://example.com/equipment1.jpg", |
| | | title: "实验设备1", |
| | | description: "主要反应设备", |
| | | }, |
| | | { |
| | | url: "https://example.com/equipment2.jpg", |
| | | title: "实验设备2", |
| | | description: "辅助设备", |
| | | }, |
| | | ], |
| | | }, |
| | | }, |
| | | ], |
| | | operationSteps: [ |
| | | { |
| | | id: 4, |
| | | type: "richText", |
| | | data: { |
| | | content: |
| | | "<p>1. 准备工作</p><p>2. 设备检查</p><p>3. 实验操作</p><p>4. 数据记录</p>", |
| | | }, |
| | | }, |
| | | ], |
| | | }; |
| | | |
| | | // 更新表单数据 |
| | | this.form = { |
| | | ...this.form, |
| | | ...mockDetailData, |
| | | }; |
| | | } catch (error) { |
| | | console.error("获取方案详情失败:", error); |
| | | this.$message.error("获取方案详情失败"); |
| | | this.handleClose(); |
| | | } |
| | | }, |
| | | }, |
| | | }; |
| | | </script> |
| | |
| | | |
| | | .approval-dialog { |
| | | display: flex; |
| | | height: 300px; |
| | | height: 60vh; |
| | | |
| | | .approval-content { |
| | | flex: 1; |
| | |
| | | } |
| | | } |
| | | |
| | | .approval-dialog-approve { |
| | | margin-top: 26px; |
| | | } |
| | | |
| | | .approval-content-card { |
| | | height: calc(100% - 100px) !important; |
| | | box-shadow: none !important; |
| | |
| | | align-items: center; |
| | | flex-wrap: wrap; |
| | | gap: 13px; |
| | | margin-top: 38px; |
| | | |
| | | .header-title-left { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 13px; |
| | | margin-top: 38px; |
| | | |
| | | img { |
| | | width: 12px; |
| | |
| | | } |
| | | |
| | | .header-title:first-child { |
| | | margin-top: 0 !important; |
| | | |
| | | .header-title-left { |
| | | margin-top: 0; |
| | | } |
| | |
| | | } |
| | | |
| | | .approval-dialog-approve { |
| | | padding: 38px 20px; |
| | | display: flex; |
| | | align-content: center; |
| | | .status { |
| | | margin-right: 40px; |
| | | } |
| | | // align-items: center; |
| | | .status-title { |
| | | color: #222222; |
| | | font-family: "SourceHanSansCN-Medium"; |
| | | line-height: 14px; |
| | | margin-bottom: 16px; |
| | | } |
| | | .status-content { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 16px; |
| | | background: #ffffff; |
| | | border-radius: 10px; |
| | | border: 1px solid rgba(4, 156, 154, 0.5); |
| | | .resolve { |
| | | border-radius: 10px; |
| | | font-size: 16px; |
| | | padding: 5px 55px; |
| | | font-weight: 400; |
| | | color: #333333; |
| | | cursor: pointer; |
| | | } |
| | | .reject { |
| | | border-radius: 10px; |
| | | font-size: 16px; |
| | | padding: 5px 55px; |
| | | font-weight: 400; |
| | | color: #333333; |
| | | cursor: pointer; |
| | | } |
| | | .activeStatus { |
| | | background: #ebfefd; |
| | | color: #049c9a; |
| | | box-shadow: 0px 0px 6px 0px rgba(10, 109, 108, 0.25); |
| | | border-radius: 10px; |
| | | } |
| | | } |
| | | .remark-title { |
| | | color: #222222; |
| | | font-family: "SourceHanSansCN-Medium"; |
| | | line-height: 14px; |
| | | margin-bottom: 16px; |
| | | img { |
| | | border: 2px dashed #049c9a; |
| | | } |
| | | } |
| | | |
| | | .dialog-footer{ |
| | | align-items: center; |
| | | display: flex; |
| | | justify-content: center; |
| | | button{ |
| | | width: 150px; |
| | | .add-group { |
| | | padding-left: 25px; |
| | | margin-top: 14px; |
| | | display: flex; |
| | | align-items: center; |
| | | margin-bottom: 19px; |
| | | |
| | | div { |
| | | color: #f56c6c; |
| | | } |
| | | |
| | | span { |
| | | font-weight: 500; |
| | | font-size: 14px; |
| | | color: #222222; |
| | | line-height: 21px; |
| | | margin: 0 32px 0 8px; |
| | | } |
| | | } |
| | | |
| | | .groupTable { |
| | | width: 65%; |
| | | padding-left: 40px; |
| | | } |
| | | |
| | | .rwuTable { |
| | | width: 85%; |
| | | padding-left: 40px; |
| | | } |
| | | |
| | | .member-list { |
| | | margin-top: 18px; |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 28px; |
| | | margin-left: 38px; |
| | | |
| | | .member-list-card { |
| | | width: 280px; |
| | | height: 300px; |
| | | border-radius: 8px; |
| | | border: 1px solid #dcdfe6; |
| | | |
| | | &:nth-child(1) { |
| | | background: linear-gradient( |
| | | to bottom, |
| | | rgba(4, 156, 154, 0.2) 0%, |
| | | rgba(5, 242, 194, 0) 70% |
| | | ); |
| | | } |
| | | |
| | | &:nth-child(2) { |
| | | background: linear-gradient( |
| | | to bottom, |
| | | rgba(5, 160, 193, 0.2) 0%, |
| | | rgba(5, 242, 194, 0) 70% |
| | | ); |
| | | } |
| | | |
| | | &:nth-child(3) { |
| | | background: linear-gradient( |
| | | to bottom, |
| | | rgba(255, 77, 79, 0.2) 0%, |
| | | rgba(255, 242, 194, 0) 70% |
| | | ); |
| | | } |
| | | |
| | | &:nth-child(4) { |
| | | background: linear-gradient( |
| | | to bottom, |
| | | rgba(250, 199, 20, 0.21) 0%, |
| | | rgba(255, 242, 194, 0) 70% |
| | | ); |
| | | } |
| | | |
| | | .member-item { |
| | | height: 100%; |
| | | display: flex; |
| | | flex-direction: column; |
| | | |
| | | .member-title { |
| | | margin-top: 20px; |
| | | width: 100%; |
| | | font-family: "Source Han Sans CN Bold Bold"; |
| | | font-weight: bold; |
| | | font-size: 16px; |
| | | color: rgba(0, 0, 0, 0.8); |
| | | line-height: 16px; |
| | | text-align: center; |
| | | } |
| | | |
| | | .flex1 { |
| | | flex: 1; |
| | | } |
| | | |
| | | .member-name-box { |
| | | flex: 1; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .member-name-box-2 { |
| | | flex: 1; |
| | | padding: 0 20px; |
| | | padding-top: 40px; |
| | | display: grid; |
| | | grid-template-columns: repeat(4, 1fr); |
| | | gap: 20px; |
| | | justify-items: center; |
| | | align-items: start; |
| | | } |
| | | |
| | | .member-name { |
| | | width: 60px; |
| | | height: 60px; |
| | | background: #7d8b79; |
| | | border-radius: 50%; |
| | | text-align: center; |
| | | line-height: 60px; |
| | | font-weight: 500; |
| | | font-size: 16px; |
| | | color: #ffffff; |
| | | margin: 0; |
| | | } |
| | | |
| | | .member-change { |
| | | display: flex; |
| | | justify-content: center; |
| | | padding: 10px 0; |
| | | margin-top: auto; |
| | | cursor: pointer; |
| | | |
| | | .member-change-btn { |
| | | background: #fff1f0; |
| | | border-radius: 4px; |
| | | border: 1px solid #ffccc7; |
| | | padding: 1px 8px; |
| | | font-weight: 400; |
| | | font-size: 12px; |
| | | color: #ff4d4f; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .step-list { |
| | | background: #eff8fa; |
| | | padding: 20px; |
| | | .step-list-item { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | padding: 25px; |
| | | background: #ffffff; |
| | | .step-list-item-title { |
| | | font-weight: 500; |
| | | font-size: 14px; |
| | | color: rgba(0, 0, 0, 0.8); |
| | | line-height: 20px; |
| | | flex-wrap: wrap; |
| | | flex: 1; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .dialog-footer { |
| | | align-items: center; |
| | | display: flex; |
| | | justify-content: center; |
| | | |
| | | button { |
| | | width: 150px; |
| | | } |
| | | } |
| | | </style> |
| | |
| | | createTime: '2024-03-15', |
| | | status: 'pending', |
| | | approver: '李四', |
| | | approveTime: '2024-03-16' |
| | | approveTime: '2024-03-16', |
| | | purpose: '<p>1. 研究新型催化剂的性能</p><p>2. 优化反应条件</p><p>3. 提高产品收率</p>', |
| | | processParameters: [ |
| | | { |
| | | '工艺参数': '反应温度', |
| | | '参数值': '25℃', |
| | | '操作人员': ['1', '2'], |
| | | updateTime: '2024-01-01 12:00:00' |
| | | }, |
| | | { |
| | | '工艺参数': '反应压力', |
| | | '参数值': '1.0MPa', |
| | | '操作人员': ['3'], |
| | | updateTime: '2024-01-01 12:00:00' |
| | | } |
| | | ], |
| | | materials: [ |
| | | { |
| | | '材料名称': '催化剂A', |
| | | '规格': '工业级', |
| | | '数量': '100g', |
| | | updateTime: '2024-01-01 12:00:00' |
| | | }, |
| | | { |
| | | '材料名称': '溶剂B', |
| | | '规格': '分析纯', |
| | | '数量': '500ml', |
| | | updateTime: '2024-01-01 12:00:00' |
| | | } |
| | | ], |
| | | steps: '<p>1. 准备工作</p><p>2. 设备检查</p><p>3. 实验操作</p><p>4. 数据记录</p>' |
| | | }, |
| | | { |
| | | planCode: 'PLAN-2024-002', |
| | |
| | | createTime: '2024-03-14', |
| | | status: 'approved', |
| | | approver: '赵六', |
| | | approveTime: '2024-03-15' |
| | | approveTime: '2024-03-15', |
| | | purpose: '<p>1. 评估现有安全管理制度</p><p>2. 制定新的安全规范</p><p>3. 进行安全培训</p>', |
| | | processParameters: [ |
| | | { |
| | | '工艺参数': '培训时间', |
| | | '参数值': '2小时', |
| | | '操作人员': ['1', '2', '3'], |
| | | updateTime: '2024-01-01 12:00:00' |
| | | } |
| | | ], |
| | | materials: [ |
| | | { |
| | | '材料名称': '培训材料', |
| | | '规格': 'A4', |
| | | '数量': '50份', |
| | | updateTime: '2024-01-01 12:00:00' |
| | | } |
| | | ], |
| | | steps: '<p>1. 安全评估</p><p>2. 制度更新</p><p>3. 人员培训</p><p>4. 效果评估</p>' |
| | | }, |
| | | { |
| | | planCode: 'PLAN-2024-003', |
| | |
| | | console.log("删除数据:", row); |
| | | }, |
| | | handleDetail(row) { |
| | | this.currentApprovalData = row; |
| | | // 打开弹窗 |
| | | this.approvalDialogType = 'view'; |
| | | this.approvalDialogVisible = true; |
| | | |
| | | // 调用获取详情接口 |
| | | this.getPlanDetail(row.planCode); |
| | | }, |
| | | // 获取方案详情 |
| | | async getPlanDetail(planCode) { |
| | | try { |
| | | // TODO: 替换为实际的接口调用 |
| | | // const { data } = await this.$api.getPlanDetail({ planCode }); |
| | | |
| | | // 模拟接口返回数据 |
| | | const mockDetailData = { |
| | | planCode: planCode, |
| | | planName: '2024年度实验室设备升级方案', |
| | | stage: '设备升级实验', |
| | | testDate: '2024-03-15', |
| | | testTime: '2024-03-15 14:00:00', |
| | | tester: '张三', |
| | | creator: '张三', |
| | | createTime: '2024-03-15', |
| | | status: 'pending', |
| | | approver: '李四', |
| | | approveTime: '2024-03-16', |
| | | experimentPurpose: [ |
| | | { |
| | | id: 1, |
| | | type: 'richText', |
| | | data: { |
| | | content: '<p>1. 研究新型催化剂的性能</p><p>2. 优化反应条件</p><p>3. 提高产品收率</p>' |
| | | } |
| | | } |
| | | ], |
| | | processParameters: [ |
| | | { |
| | | id: 2, |
| | | type: 'customTable', |
| | | data: { |
| | | headers: [ |
| | | { name: '工艺参数', type: 'text' }, |
| | | { name: '参数值', type: 'text' }, |
| | | { name: '操作人员', type: 'user' } |
| | | ], |
| | | rows: [ |
| | | { |
| | | '工艺参数': '反应温度', |
| | | '参数值': '25℃', |
| | | '操作人员': ['1', '2'], |
| | | updateTime: '2024-01-01 12:00:00' |
| | | }, |
| | | { |
| | | '工艺参数': '反应压力', |
| | | '参数值': '1.0MPa', |
| | | '操作人员': ['3'], |
| | | updateTime: '2024-01-01 12:00:00' |
| | | } |
| | | ] |
| | | } |
| | | } |
| | | ], |
| | | materialsAndEquipment: [ |
| | | { |
| | | id: 3, |
| | | type: 'customTable', |
| | | data: { |
| | | headers: [ |
| | | { name: '材料名称', type: 'text' }, |
| | | { name: '规格', type: 'text' }, |
| | | { name: '数量', type: 'text' } |
| | | ], |
| | | rows: [ |
| | | { |
| | | '材料名称': '催化剂A', |
| | | '规格': '工业级', |
| | | '数量': '100g', |
| | | updateTime: '2024-01-01 12:00:00' |
| | | }, |
| | | { |
| | | '材料名称': '溶剂B', |
| | | '规格': '分析纯', |
| | | '数量': '500ml', |
| | | updateTime: '2024-01-01 12:00:00' |
| | | } |
| | | ] |
| | | } |
| | | } |
| | | ], |
| | | operationSteps: [ |
| | | { |
| | | id: 4, |
| | | type: 'richText', |
| | | data: { |
| | | content: '<p>1. 准备工作</p><p>2. 设备检查</p><p>3. 实验操作</p><p>4. 数据记录</p>' |
| | | } |
| | | } |
| | | ] |
| | | }; |
| | | |
| | | // 更新弹窗数据 |
| | | this.currentApprovalData = mockDetailData; |
| | | } catch (error) { |
| | | console.error('获取方案详情失败:', error); |
| | | this.$message.error('获取方案详情失败'); |
| | | this.approvalDialogVisible = false; |
| | | } |
| | | }, |
| | | handleTypeChange(type) { |
| | | this.currentType = type; |
| | |
| | | |
| | | <div slot="footer" class="dialog-footer"> |
| | | <el-button @click="handleDialogClose">取 消</el-button> |
| | | <el-button type="primary" @click="handleConfirm" :disabled="!imgSrc">确 认</el-button> |
| | | <el-button type="primary" @click="handleConfirm">确 认</el-button> |
| | | </div> |
| | | </el-dialog> |
| | | |
File was renamed from culture/src/views/dataManagement/sampleManage/components/approvalDialog.vue |
| | |
| | | <template> |
| | | <el-dialog |
| | | :title="dialogTitle" |
| | | :visible.sync="visible" |
| | | width="80%" |
| | | :close-on-click-modal="false" |
| | | @close="handleClose" |
| | | > |
| | | <el-dialog :title="dialogTitle" :visible.sync="visible" width="80%" :close-on-click-modal="false" |
| | | @close="handleClose"> |
| | | <div class="approval-dialog"> |
| | | <!-- 左侧审批内容 --> |
| | | <div class="approval-content"> |
| | |
| | | <div class="header-title"> |
| | | <div class="header-title-left"> |
| | | <img src="@/assets/public/headercard.png" /> |
| | | <span>项目课题方案信息</span> |
| | | <div>所属实验调度</div> |
| | | </div> |
| | | </div> |
| | | <el-form |
| | | ref="form" |
| | | :model="form" |
| | | :rules="rules" |
| | | inline |
| | | label-position="top" |
| | | style="margin-top: 38px" |
| | | > |
| | | <el-form-item prop="name" label="项目课题方案名称"> |
| | | <el-input v-model="form.name" placeholder="请输入" /> |
| | | </el-form-item> |
| | | <el-form-item prop="description" label="项目阶段"> |
| | | <el-input v-model="form.description" placeholder="请输入" /> |
| | | </el-form-item> |
| | | <el-form-item prop="description" label="项目课题方案编号"> |
| | | <el-input v-model="form.description" placeholder="请输入" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div class="header-title"> |
| | | <div class="header-title-left"> |
| | | <img src="@/assets/public/headercard.png" /> |
| | | <div>一 、实验目的</div> |
| | | </div> |
| | | </div> |
| | | <div class="header-title"> |
| | | <div class="header-title-left"> |
| | | <img src="@/assets/public/headercard.png" /> |
| | | <div>二 、实验拆料和设备</div> |
| | | </div> |
| | | </div> |
| | | <div class="item-title"> |
| | | <span>1.实验材料</span> |
| | | </div> |
| | | <div class="item-title"> |
| | | <span>2.实验设备</span> |
| | | </div> |
| | | <Table :data="groupTableData" :total="0" :height="null" class="groupTable"> |
| | | <el-table-column type="index" label="所属项目课题方案" width="80"></el-table-column> |
| | | <el-table-column prop="groupName" label="实验编号"></el-table-column> |
| | | <el-table-column prop="stage" label="实验名称"></el-table-column> |
| | | <el-table-column prop="creator" label="通知时间"></el-table-column> |
| | | <el-table-column prop="createTime" label="实验开始时间"></el-table-column> |
| | | <el-table-column prop="approver" label="实验结束时间"></el-table-column> |
| | | <el-table-column prop="approveTime" label="参加人员"></el-table-column> |
| | | <el-table-column prop="status" label="状态"> |
| | | </el-table-column> |
| | | </Table> |
| | | |
| | | <div class="header-title"> |
| | | <div class="header-title-left"> |
| | | <img src="@/assets/public/headercard.png" /> |
| | | <div>三 、检测方法及开发</div> |
| | | <div>中止原因说明</div> |
| | | </div> |
| | | </div> |
| | | <div class="header-title"> |
| | | <div class="header-title-left"> |
| | | <img src="@/assets/public/headercard.png" /> |
| | | <div>四 、实验步骤</div> |
| | | </div> |
| | | </div> |
| | | <div class="header-title"> |
| | | <div class="header-title-left"> |
| | | <img src="@/assets/public/headercard.png" /> |
| | | <div>五 、数据采集及分析</div> |
| | | </div> |
| | | </div> |
| | | <div class="header-title"> |
| | | <div class="header-title-left"> |
| | | <img src="@/assets/public/headercard.png" /> |
| | | <div>六 、结果评估</div> |
| | | </div> |
| | | </div> |
| | | <div class="header-title"> |
| | | <div class="header-title-left"> |
| | | <img src="@/assets/public/headercard.png" /> |
| | | <span>注意事项</span> |
| | | </div> |
| | | </div> |
| | | <AiEditor ref="purposeEditor" v-model="form.purpose" height="200px" placeholder="请输入文字" /> |
| | | |
| | | </template> |
| | | <SelectMember ref="selectMember" /> |
| | | </Card> |
| | | </div> |
| | | <!-- 右侧审批流程 --> |
| | | <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 :status="form.status" :submit-time="form.createTime" :approver="form.approver" |
| | | :approve-time="form.approveTime" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | <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 == '1' && 'activeStatus'" @click.stop="status = 1"> |
| | | 通过 |
| | | </div> |
| | | <div |
| | | class="reject" |
| | | :class="status == '2' && 'activeStatus'" |
| | | @click.stop="status = 2" |
| | | > |
| | | <div class="reject" :class="status == '2' && 'activeStatus'" @click.stop="status = 2"> |
| | | 驳回 |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="remark"> |
| | | <div class="remark-title">审批意见</div> |
| | | <el-input type="textarea" v-model="remark" placeholder="请输入审批意见" /> |
| | | <el-input type="textarea" v-model="remark" placeholder="请输入审批意见" /> |
| | | </div> |
| | | </div> |
| | | <div slot="footer" class="dialog-footer select-member-footer"> |
| | | <div slot="footer" class="dialog-footer"> |
| | | <el-button @click="handleClose">取 消</el-button> |
| | | <el-button type="primary" @click="handleApprove" v-if="type === 'approve'" |
| | | >通过</el-button |
| | | > |
| | | style="margin-left: 20px;">通过</el-button> |
| | | </div> |
| | | </el-dialog> |
| | | </template> |
| | | |
| | | <script> |
| | | import ApprovalProcess from '@/components/approvalProcess' |
| | | |
| | | import AiEditor from '@/components/AiEditor' |
| | | export default { |
| | | name: "ApprovalDialog", |
| | | components: { |
| | | ApprovalProcess |
| | | ApprovalProcess, AiEditor |
| | | }, |
| | | props: { |
| | | visible: { |
| | |
| | | }, |
| | | computed: { |
| | | dialogTitle() { |
| | | return this.type === "approve" ? "审批" : "审批详情"; |
| | | return this.type === "approve" ? "审批实验中止申请" : "实验中止申请审批详情"; |
| | | }, |
| | | }, |
| | | watch: { |
| | |
| | | align-items: center; |
| | | flex-wrap: wrap; |
| | | gap: 13px; |
| | | margin-bottom: 38px; |
| | | |
| | | .header-title-left { |
| | | display: flex; |
| | |
| | | padding: 38px 20px; |
| | | display: flex; |
| | | align-content: center; |
| | | |
| | | .status { |
| | | margin-right: 40px; |
| | | } |
| | | |
| | | // align-items: center; |
| | | .status-title { |
| | | color: #222222; |
| | |
| | | line-height: 14px; |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | .status-content { |
| | | display: flex; |
| | | align-items: center; |
| | |
| | | background: #ffffff; |
| | | border-radius: 10px; |
| | | border: 1px solid rgba(4, 156, 154, 0.5); |
| | | |
| | | .resolve { |
| | | border-radius: 10px; |
| | | font-size: 16px; |
| | |
| | | color: #333333; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .reject { |
| | | border-radius: 10px; |
| | | font-size: 16px; |
| | |
| | | color: #333333; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .activeStatus { |
| | | background: #ebfefd; |
| | | color: #049c9a; |
| | |
| | | border-radius: 10px; |
| | | } |
| | | } |
| | | |
| | | .remark-title { |
| | | color: #222222; |
| | | font-family: "SourceHanSansCN-Medium"; |
| | |
| | | } |
| | | } |
| | | |
| | | .dialog-footer{ |
| | | align-items: center; |
| | | display: flex; |
| | | justify-content: center; |
| | | button{ |
| | | width: 150px; |
| | | } |
| | | .dialog-footer { |
| | | align-items: center; |
| | | display: flex; |
| | | justify-content: center; |
| | | |
| | | button { |
| | | width: 150px; |
| | | } |
| | | } |
| | | </style> |
File was renamed from culture/src/views/dataManagement/approvalPlan/list.vue |
| | |
| | | <TableCustom :queryForm="form" :tableData="tableData" :total="total"> |
| | | <template #search> |
| | | <el-form :model="form" labelWidth="auto" inline> |
| | | <el-form-item label="项目课题方案名称:"> |
| | | <el-form-item label="所属项目课题方案:"> |
| | | <el-input v-model="form.planName" placeholder="请输入"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="项目课题方案编号:"> |
| | | <el-form-item label="实验编号:"> |
| | | <el-input v-model="form.planCode" placeholder="请输入"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="创建人:"> |
| | | <el-input v-model="form.creator" placeholder="请输入"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="创建时间:"> |
| | | <el-date-picker |
| | | v-model="form.createTime" |
| | | type="daterange" |
| | | range-separator="至" |
| | | start-placeholder="开始日期" |
| | | end-placeholder="结束日期" |
| | | value-format="yyyy-MM-dd" |
| | | ></el-date-picker> |
| | | <el-date-picker v-model="form.createTime" type="daterange" range-separator="至" start-placeholder="开始日期" |
| | | end-placeholder="结束日期" value-format="yyyy-MM-dd"></el-date-picker> |
| | | </el-form-item> |
| | | <el-form-item label="审批人:"> |
| | | <el-form-item label="状态"> |
| | | <el-input v-model="form.approver" placeholder="请输入"></el-input> |
| | | </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 type="primary" @click="handleSearch" style="margin-left: 20px;">查询</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 class="title" :class="{ active: currentType === 'list' }" @click="handleTypeChange('list')">申请中止试验方案列表 |
| | | </div> |
| | | </div> |
| | | <el-button @click="handleAddPlan" class="el-icon-plus" type="primary"> |
| | | 新增项目课题方案</el-button |
| | | > |
| | | </div> |
| | | </template> |
| | | <template #table> |
| | | <el-table-column |
| | | prop="planCode" |
| | | label="项目课题方案编号" |
| | | ></el-table-column> |
| | | <el-table-column |
| | | prop="planName" |
| | | label="项目课题方案名称" |
| | | ></el-table-column> |
| | | <el-table-column prop="stage" label="项目阶段"></el-table-column> |
| | | <el-table-column prop="creator" label="创建人"></el-table-column> |
| | | <el-table-column prop="createTime" label="创建日期"></el-table-column> |
| | | <el-table-column prop="status" label="审批状态"> |
| | | <el-table-column prop="planCode" label="所属项目课题方案"></el-table-column> |
| | | <el-table-column prop="planName" label="实验编号"></el-table-column> |
| | | <el-table-column prop="stage" label="实验名称"></el-table-column> |
| | | <el-table-column prop="creator" label="试验日期"></el-table-column> |
| | | <el-table-column prop="createTime" label="实验员"></el-table-column> |
| | | <el-table-column prop="approver" label="提交时间"></el-table-column> |
| | | <el-table-column prop="approveTime" label="提交人"></el-table-column> |
| | | <el-table-column prop="status" label="当前状态"> |
| | | <template slot-scope="scope"> |
| | | <el-tag :type="getStatusType(scope.row.status)"> |
| | | {{ getStatusText(scope.row.status) }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="approver" label="审批人"></el-table-column> |
| | | <el-table-column prop="approveTime" label="审批时间"></el-table-column> |
| | | |
| | | <el-table-column label="操作" width="250"> |
| | | <template slot-scope="scope"> |
| | | <el-button |
| | | v-if="scope.row.status === 'pending'" |
| | | type="text" |
| | | @click="handleApprove(scope.row)" |
| | | >审批</el-button |
| | | > |
| | | <el-button |
| | | v-if="scope.row.status === 'approved'" |
| | | type="text" |
| | | @click="handleRevokeApprove(scope.row)" |
| | | >撤销审批</el-button |
| | | > |
| | | <el-button |
| | | v-if="scope.row.status === 'rejected'" |
| | | type="text" |
| | | @click="handleEdit(scope.row)" |
| | | >编辑</el-button |
| | | > |
| | | <el-button |
| | | v-if="scope.row.status === 'rejected'" |
| | | type="text" |
| | | @click="handleDelete(scope.row)" |
| | | >删除</el-button |
| | | > |
| | | <el-button type="text" @click="handleDetail(scope.row)" |
| | | >详情</el-button |
| | | > |
| | | <el-button v-if="scope.row.status === 'pending'" type="text" |
| | | @click="handleApprove(scope.row)">审批</el-button> |
| | | |
| | | <el-button type="text" @click="handleDetail(scope.row)">详情</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </template> |
| | | </TableCustom> |
| | | <!-- 审批弹窗 --> |
| | | <approval-dialog |
| | | :visible.sync="approvalDialogVisible" |
| | | :type="approvalDialogType" |
| | | :data="currentApprovalData" |
| | | @approve="handleApproveSubmit" |
| | | @reject="handleRejectSubmit" |
| | | /> |
| | | <approval-dialog :visible.sync="approvalDialogVisible" :type="approvalDialogType" :data="currentApprovalData" |
| | | @approve="handleApproveSubmit" @reject="handleRejectSubmit" /> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | .list { |
| | | height: 100%; |
| | | } |
| | | |
| | | .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; |
| | |
| | | width: unset; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .drafts { |
| | | padding: 16px 65px; |
| | | background: #fafafc; |
| | |
| | | margin-left: 16px; |
| | | cursor: pointer; |
| | | } |
| | | .active{ |
| | | |
| | | .active { |
| | | color: #049c9a; |
| | | background: #ffffff; |
| | | border-radius: 8px 8px 0px 0px; |
| | |
| | | <div class="login-form"> |
| | | <div class="form-item flex"> |
| | | <img class="form-item-icon" :src="require('../../assets/login/account@2x.png')" alt=""> |
| | | <el-input v-model="loginForm.account" placeholder="请输入账号"></el-input> |
| | | <el-input v-model="loginForm.username" placeholder="请输入账号"></el-input> |
| | | </div> |
| | | |
| | | <div class="form-item flex mt-40"> |
| | |
| | | </div> |
| | | </template> |
| | | <script> |
| | | import { loginReq } from './service' |
| | | export default { |
| | | name: 'Login', |
| | | data() { |
| | |
| | | windowWidth: window.innerWidth, |
| | | |
| | | loginForm: { |
| | | account: '', |
| | | username: '', |
| | | password: '' |
| | | }, |
| | | viewWidth: '', |
| | |
| | | // 添加处理窗口大小变化的方法 |
| | | handleResize() { |
| | | this.viewWidth = window.innerWidth |
| | | console.log(this.viewWidth) |
| | | }, |
| | | login() { |
| | | this.$router.push('/') |
| | | console.log(this.loginForm) |
| | | if (this.loginForm.username == '') { |
| | | this.$message.warning('请输入账号') |
| | | return |
| | | } |
| | | if (this.loginForm.password == '') { |
| | | this.$message.warning('请输入密码') |
| | | return |
| | | } |
| | | loginReq(this.loginForm).then(res => { |
| | | sessionStorage.setItem('token', res.token) |
| | | sessionStorage.setItem('userInfo', JSON.stringify(res.userInfo.user)) |
| | | this.$router.push('/system') |
| | | }) |
| | | } |
| | | } |
| | | } |
| | |
| | | import axios from '@/utils/request'; |
| | | |
| | | // 登录 |
| | | export const login = (data) => { |
| | | export const loginReq = (data) => { |
| | | return axios.post('/login', { ...data }) |
| | | } |
| | |
| | | disableHostCheck: true, //禁用主机检查 |
| | | proxy: { |
| | | "/api": { // 设置以什么前缀开头的请求用来代理 |
| | | target: "http://localhost:8080", //要访问的跨域的域名 |
| | | target: "http://192.168.110.34:8081", //要访问的跨域的域名 |
| | | secure: false, // 使用的是http协议则设置为false,https协议则设置为true |
| | | changOrigin: true, //开启代理 |
| | | pathRewrite: { |
| | | "^/api": "", |
| | | "^/api": "/api", |
| | | }, |
| | | }, |
| | | "/": { // 设置以什么前缀开头的请求用来代理 |
| | | target: "http://192.168.110.34:8081", //要访问的跨域的域名 |
| | | secure: false, // 使用的是http协议则设置为false,https协议则设置为true |
| | | changOrigin: true, //开启代理 |
| | | pathRewrite: { |
| | | "^/": "/", |
| | | }, |
| | | }, |
| | | }, |