import moment from 'moment' import { getLatestSn, getLatestSnArr } from '@/api/template'; import { isValueEmpty, getStringWidth } from '@/utils/index'; import { isCommonUnit } from "@/utils/conTools"; import { sj_subpackage, sj_startConfiguration, sj_configurationCompleted } from '@/api/business/sj/sj'; import { convertConcentration } from "@/utils/conConverter";//浓度单位转换 import { volumeConverter } from "@/utils/volConverter";//体积单位转换 import * as XLSX from 'xlsx' import { EventBus } from "@/utils/eventBus"; export default { dicts: [ 'business_pztj', 'business_cctj', 'business_nddw', 'business_tjdw', 'business_zldw', 'business_yxqdw', 'business_rqcz', 'business_sp_jmdyzqdyp', //色谱-编号-准确度与精密度 'business_sp_nbgzy', //色谱-编号-内标工作液 'business_sp_zkgzy', //色谱-编号-质控工作液 'business_sp_bqgzy', //色谱-编号-标曲工作液 'business_sp_bzqxzkypzbb', //色谱-编号-标准曲线/质控样品制备表 'business_sp_tqhsl', //色谱-编号-提取回收率 'business_sp_qxwdx', //色谱-编号-全血稳定性 'business_sp_cbyhgzywdx', //色谱-编号-储备液和工作液稳定性 'business_sp_rxjzxy', //色谱-编号-溶血基质效应 'business_sp_jzxy', //色谱-编号-基质效应 'business_sp_xzxytyx', //色谱-编号-选择性与特异性 'business_sp_zdybs', //色谱-编号-最大样本数 'business_sp_xskkx', //色谱-编号-稀释可靠性 'business_sp_cbydb', //色谱-编号-储备液对比 'business_pcr_gzy', // PCR-编号-工作液 'business_lba_jg', // LBA006-结果 'business_cftj', // 存放条件 'business_yplx', // 样品类型 'business_sydd', // 毒理-Ames实验地点 'business_dl_ameswrqk', // 毒理-Ames污染情况 'business_dl_amescltj', // 毒理-Ames处理条件 'business_dl_amesbjjt', // 毒理-Ames背景菌苔 'business_dl_amescdqk', // 毒理-Ames沉淀情况 'business_dl_xbmc', // 毒理-Ames细胞名称 'business_dl_qsxjbltj', // 毒理-暴露条件 'business_rylx', // 溶液类型 'business_xbzb', // 细胞组别 'business_xblx', // 细胞类型 'business_pytj', // 毒理-培养条件 'business_fstj', // 毒理-复苏条件 'business_dsfs', //动技-递送方式 'business_knj', //动技-抗凝剂 'business_fhlr', //动技-复核内容 'business_fhfw', //动技-复核范围 'business_dwzs', // 动物种属 'business_cjdd', // 采集地点 'business_zpdd', // 制片地点 'business_cjbw', // 制片部位 'business_qywz', // 取样位置 'business_lba_ptjhbzqx_zkpx', // LBA-编号-配体结合分析标准曲线LBA005-质控平行 'business_lba_bqpx', //LBA-编号-配体结合分析标准曲线LBA005-标曲平行 'business_lba_ptjhfxyp', // LBA-编号-配体结合分析方法学样品制备表LBA007 'business_ada_kyktzkyp', // ADA-编号-抗药抗体质控样品配制记录表ADA005 'business_ada_kyktffxyp', // ADA-编号-抗药抗体方法学样品制备表ADA007 'business_dl_uav', // 毒理-紫外照射操作记录表-UVA-DL023 'business_pcr_fxffypzbb', //PCR-编号-分析方法学样品制备表 ], props: { templateData: { type: Object, default: () => { } } }, watch: { templateData: { immediate: true, deep: true, handler(v) { if (v) { const { studySubjectId, studyId, studyMc, studySn, templateMc, templateMcEn, templateId, templateSn, startDate, bdmc, endDate, id } = v if (v.resource) { //type 类型:1:试剂;3:给药制剂;5:麻精药;7:供试品 9:细胞;11:细菌 //试验试剂信息 试剂 // this.resource = _.filter(JSON.parse(v.resource), function (o) { // return o.type == 1 || o.elnType == 1 // }) // //试验物资信息 供试品、给药制剂、麻精药 // this.resourceWz = _.filter(JSON.parse(v.resource), function (o) { // return o.type == 3 || o.type == 5 || o.type == 7 || o.elnType == 3|| o.elnType == 5 || o.elnType == 7 // }) //试验试剂信息 this.resourceSj = _.filter(JSON.parse(v.resource), function (o) { return o.type == 1 || o.elnType == 1 }) //供试品信息 this.resourceGsp = _.filter(JSON.parse(v.resource), function (o) { return o.type == 7 || o.elnType == 7 }) //给药制剂信息 this.resourceGyzj = _.filter(JSON.parse(v.resource), function (o) { return o.type == 3 || o.elnType == 3 }) //麻精药信息 this.resourceMjy = _.filter(JSON.parse(v.resource), function (o) { return o.type == 5 || o.elnType == 5 }) } if (v.yqResource) { //仪器信息 this.yqResource = JSON.parse(v.yqResource) } this.product = v.product || null if (v.bdnr) { this.formData = { ...JSON.parse(v.bdnr), studySubjectId, studyId, studyMc, studySn, templateMc, templateMcEn, templateId, templateSn, startDate, bdmc, endDate, id } } else { this.formData = { studySubjectId, studyId, studyMc, studySn, templateMc, templateMcEn, templateId, templateSn, startDate, bdmc, endDate, id } } const { effectivePeriod, effectivePeriodUnit, expireDate } = this.formData //实际填报的时候,如果有了开始时间,并且有有效周期,但是没有失效日,就计算失效日为开始时间+有效周期 if ( startDate && this.fillType === 'actFill' && effectivePeriod && effectivePeriodUnit && !expireDate ) { let end; if (startDate === "NA") { end = "NA"; } else { const start = moment(startDate) const unit = effectivePeriodUnit === '天' ? 'days' : 'hours' end = start .add(Number(effectivePeriod), unit) .format('YYYY-MM-DD HH:mm:ss') } this.formData = { ...this.formData, expireDate: end } } console.log(this.formData, 'formData from templateData') this.setTemplateData(v) } } } }, data() { return { formData: {}, templateDetail: {}, product: null,//产物:1:试剂;3:给药制剂;5:麻精药 resource: [], //试验试剂信息--弃用 resourceWz: [], //物资信息--弃用 resourceSj: [],//试验试剂信息 resourceGsp: [],//供试品信息 resourceGyzj: [],//给药制剂信息 resourceMjy: [],//麻精药信息 yqResource: [], //仪器信息 jcbList: [], //检测板信息 xbxj: [], //细胞细菌信息 resourceTmp: [],//试验试剂信息提交用 yqResourceTmp: [], //仪器信息提交用 jcbListTmp: [], //检测板信息提交用 xbxjTmp: [],//细胞细菌信息提交用 sjColumns: [ { label: 'template.common.reagentName', prop: "mc" },//名称 { label: 'template.common.reagentCode', prop: "bh" },//编号 { label: 'template.common.reagentNo', prop: "ph" },//批号 试剂,供试品才有 { label: 'template.common.concentration', prop: "ndz" },//浓度 { label: 'template.common.source', prop: "ly" },//来源 { label: 'template.common.reagentExpireDate', prop: "sxrq" },//失效日期 ], gspColumns: [ { label: 'template.common.wzName', prop: "mc" },//名称 { label: 'template.common.wzCode', prop: "bh" },//编号 { label: 'template.common.reagentNo', prop: "ph" },//批号 试剂,供试品才有 { label: 'template.common.wzSource', prop: "ly" },//来源 { label: 'template.common.wzConcentration', prop: "ndz" },//浓度 { label: 'template.common.wzExpireDate', prop: "sxrq" },//失效日期 ], gyzjColumns: [ { label: 'template.common.wzName', prop: "mc" },//名称 { label: 'template.common.wzCode', prop: "bh" },//编号 { label: 'template.common.wzSource', prop: "ly" },//来源 { label: 'template.common.wzConcentration', prop: "ndz" },//浓度 { label: 'template.common.wzExpireDate', prop: "sxrq" },//失效日期 ], mjyColumns: [ { label: 'template.common.wzName', prop: "mc" },//名称 { label: 'template.common.wzCode', prop: "bh" },//编号 { label: 'template.common.wzSource', prop: "ly" },//来源 { label: 'template.common.wzConcentration', prop: "ndz" },//浓度 { label: 'template.common.wzExpireDate', prop: "sxrq" },//失效日期 ], //弃用 sysjColumns: [ { label: 'template.common.reagentName', prop: "mc" },//名称 { label: 'template.common.reagentCode', prop: "bh" },//编号 { label: 'template.common.reagentNo', prop: "ph" },//批号 试剂,供试品才有 { label: 'template.common.concentration', prop: "ndz" },//浓度 { label: 'template.common.source', prop: "ly" },//来源 { label: 'template.common.reagentExpireDate', prop: "sxrq" },//失效日期 ], //弃用 sywzColumns: [ { label: 'template.common.wzName', prop: "mc" },//名称 { label: 'template.common.wzCode', prop: "bh" },//编号 { label: 'template.common.wzSource', prop: "ly" },//来源 { label: 'template.common.wzConcentration', prop: "ndz" },//浓度 { label: 'template.common.wzExpireDate', prop: "sxrq" },//失效日期 ], yqColumns: [ { label: 'template.common.instrumentName', prop: "mc" },//仪器名称 { label: 'template.common.instrumentModel', prop: "xh" },//仪器型号 { label: 'template.common.instrumentCode', prop: "bh" },//仪器编号 { label: 'template.common.nextTestDate', prop: "jzrq" },//下次测试/校准/检定日期 ], } }, mounted() { }, unmounted() { this.setTemplateData({}) }, methods: { // 只是更新已填写的表单数据,不触发校验 justUpdateFilledFormData() { const params = { type: "fieldChanged", newRecord: null, resourceList: null, } EventBus.$emit('onModifyRecord', params,) }, //试剂弹窗确认前 onBeforeReagentSubmit(data) { const { selectData, callback, key, formFields } = data; const { subTargetStartSolution } = formFields; //判断选择的浓度单位和预制的浓度单位是否一致 if (key === "subStartSolution" && !isCommonUnit(selectData.row.nddw, [subTargetStartSolution])) {//选择起始源溶液需要同步更新table的变更记录。 callback.prevent('浓度单位与预制不符,请重新选择或申请废止并重新预制。'); } }, getFormDataByTemplateData() { let o = {}; const { bdnr } = this.templateData if (bdnr) { o = JSON.parse(bdnr) } return o; }, //开始配置 //postData: {bh: '123456'} async startConfigRequest(postData) { const res = await sj_startConfiguration(postData) if (res.code === 200) { this.$message.success('开始配置成功') } else { this.$message.error('开始配置失败') } }, //完成配置 async configCompleteRequest(postData) { const res = await sj_configurationCompleted(postData) if (res.code === 200) { this.$message.success('完成配置成功') } else { this.$message.error('完成配置失败') } }, //分装 async subPackageRequest(postData) { const res = await sj_subpackage(postData) if (res.code === 200) { this.$message.success('分装成功') } else { this.$message.error('分装失败') } }, //获取打印配置 getBasePrintConfig(data = {}) { const { printCode, type, row = {} } = data; const { stepStorageCondition, startDate, expireDate } = this.formData;; const { nickName, name } = this.$store.getters; const lang = this.$i18n.locale; const printConfig = { "品名": "暂时还不知道品名是哪个字段", "存储条件": stepStorageCondition, "配制日期": moment(startDate).format("YYYY-MM-DD"), "有效期至": moment(expireDate).format("YYYY-MM-DD HH:mm"), "配置者": lang === "zh_CN" ? nickName : name, //type==="subPackage"从分装打印过来的, //type==="row" 从列表上点过来的 "编号": type === "subPackage" ? printCode : (row.targetSolutionCode + row.subTargetSolutionCode), } return printConfig }, async getLatestSn(count = 1) { const res = await getLatestSn({ count }) if (res.code === 200) { return res.data } return [] }, //获取最新的多个编号 async getLatestSnArr(params) { const res = await getLatestSnArr(params) if (res.code === 200) { return res.data } return [] }, getResource() { return this.resourceTmp }, getYqResource() { return this.yqResourceTmp }, getJcbList() { return this.jcbListTmp }, getXbxj() { return this.xbxjTmp }, //根据ref数组获取直接formData getFilledFormDataByRefs(refArr = []) { let result = {} refArr.map((ref) => { const refData = this.$refs[ref]?.getFilledFormData() || {} for (const [key, value] of Object.entries(refData)) { let index = 0 // 如果当前键是可能重复的字段,且结果对象中已经存在该键,则添加索引后缀 if ( (key === 'headerSelectFields' || key === 'stepTableFormData') && result.hasOwnProperty(key) ) { // 为重复字段生成带索引的键名,从1开始(因为第一个组件不需要后缀) const newKey = `${key}_${++index}` result[newKey] = value } else { // 正常合并其他字段 result[key] = value } } }) return result }, //统一校验form表单是否填写 async validFormFields(refArr = []) { let result = {} const refs = refArr.map((ref) => { let refData = {} if (this.$refs[ref]) { if (this.$refs[ref][0]) { refData = this.$refs[ref][0]?.getFormData() || {} } else { refData = this.$refs[ref]?.getFormData() || {} } } return refData }) const validFormData = await Promise.all(refs).catch((err) => { // this.$message.error(err); if (err.errorType && err.errorType === 'step') { this.$message.error('请添加步骤') return } this.$message.error('表单内容未填完,请填写后再提交') }) if (validFormData) { validFormData.forEach((item) => { for (const [key, value] of Object.entries(item)) { let index = 0 // 如果当前键是可能重复的字段,且结果对象中已经存在该键,则添加索引后缀 if ( (key === 'headerSelectFields' || key === 'stepTableFormData') && result.hasOwnProperty(key) ) { // 为重复字段生成带索引的键名,从1开始(因为第一个组件不需要后缀) const newKey = `${key}_${++index}` result[newKey] = value } else { // 正常合并其他字段 result[key] = value } } }) return result } return false }, //试验配制条件options getDictOptions(dictType) { return this.dict.type[dictType] || [] }, setTemplateData(data) { this.$store.commit('template/SET_TEMPLATE_DATA', data) }, //统一处理删除行 deleteRow(index, ref) { if (ref) { this.$refs[ref].deleteRow(index); return; } this.$refs.stepTableRef.deleteRow(index) }, //统一处理浓度校验异常 onCommonVerifyNdException(formFields, data) { const { callback, rowIndex, key, dataSource } = data; const keys = [ 'actStartSolutionVolume', 'actDiluentVolume', ] if (keys.includes(key)) { const { targetAcSolution } = formFields; if (rowIndex === 0) {//第一行的浓度取选择的溶液, if (isValueEmpty(targetAcSolution)) { callback.prevent("请先选择起始源溶液") } } else { //非第一行的浓度取上一行的浓度 const prevItem = dataSource[rowIndex - 1] const prevConcentration = prevItem.actSolutionConcentration; if (!prevConcentration) { callback.prevent("请先计算上一行的实际目标溶液浓度") } } } }, //统一处理blur事件,因为有效周期和过期日期是相关的,所以需要在有效周期失焦时更新过期日期 onHandleBlur(fields) { const { key, codeSTD, targetStartSolution, subTargetStartSolution,//预设起始源溶液浓度单位 } = fields const { startDate } = this.formData if (key === 'codeSTD') { //起始编号STD失焦时,更新stepDataSource const arr = Array.from({ length: codeSTD }, (item, index) => ({ actSolutionVolumePrecision: 3, //小数点精度默认为3 actSolutionConcentrationPrecision: 3, //小数点精度默认为3 targetDiluentVolumePrecision: 3, //小数点精度默认为3 targetStartSolutionVolumePrecision: 3, //小数点精度默认为3 targetSolutionCode: `CA-WS-STD${Number(codeSTD) - index}` })) this.$refs.stepTableRef.updateDataSource(arr) } else if (key === 'targetStartSolution' || key === 'subTargetStartSolution') { //起始溶液体积失焦时,更新目标溶液预计浓度 const arr = this.$refs.stepTableRef?.getDataSource(); const { headerSelectFields } = this.$refs.stepTableRef?.getFilledFormData(); const params = { subTargetStartSolution, headerSelectFields } arr.forEach((item, rowIndex) => { this.updateTargetStartSolutionVolume( item, targetStartSolution, params ) }) } }, //批量更新目标起始源溶液体积 batchUpdateTargetStartSolutionVolume(arr, startConcentration, params) { arr.forEach((item, rowIndex) => { this.updateTargetStartSolutionVolume( item, startConcentration, params ) }) }, //统一处理table失焦事件 onHandleTableBlur(params) { const { rowIndex, colKey, value, item, dataSource, headerSelectFields } = params if ( colKey === 'targetSolutionVolume' || colKey === 'targetSolutionConcentration' || colKey === 'targetStartSolutionVolumePrecision' || colKey === 'targetDiluentVolumePrecision' ) { const volume = this.$refs.stepFormPackageRef?.getFormDataByKey( 'targetStartSolution' ) const subTargetStartSolution = this.$refs.stepFormPackageRef?.getFormDataByKey( 'subTargetStartSolution' ) const params = { subTargetStartSolution: subTargetStartSolution, headerSelectFields } if (isValueEmpty(volume)) { this.$message.error('请先选择预设起始源溶液浓度') } else { this.updateTargetStartSolutionVolume(item, volume, params) } } else if ( colKey === 'actStartSolutionVolume' || colKey === 'actDiluentVolume' ) { //实际起始溶液体积和实际目标溶液体积 const { targetAcSolution, subTargetAcSolutionUnit } = this.$refs.stepFormPackageRef?.getFilledFormData() //获取实际起始溶液浓度 const { headerSelectFields } = this.$refs.stepTableRef?.getFilledFormData(); const p = { targetStartSolutionUnit: subTargetAcSolutionUnit, headerSelectFields } let initNd = 0; if (rowIndex === 0) { initNd = targetAcSolution; } else { initNd = dataSource[rowIndex - 1].actSolutionConcentration; } if (isValueEmpty(initNd)) { this.$message.error('请先选择起始源溶液') return } else { const volResult = this.updateSjmbrynd(item, initNd, p) if (!volResult) { return } this.$refs.stepTableRef?.updateDataSourceByRowIndex(rowIndex, { actSolutionVolume: volResult.actVol, actSolutionConcentration: volResult.actNd }, "blur") } } }, //计算目标溶液浓度 calcNd(item, targetAcSolution, params) { console.log(params, "ppp") // debugger //targetStartSolutionUnit(实际起始源溶液浓度单位) const { targetStartSolutionUnit, headerSelectFields } = params; //actStartSolutionVolumeUnit(实际起始源溶液体积单位)actSolutionVolumeUnit(实际目标溶液体积单位) //actDiluentVolumeUnit(实际稀释液体积单位)actSolutionConcentrationUnit(实际目标溶液浓度单位) const { actStartSolutionVolumeUnit, actSolutionVolumeUnit, actDiluentVolumeUnit, actSolutionConcentrationUnit } = headerSelectFields; //实际源溶液浓度÷(实际终体积÷源溶液加入体积); const precision = item.actSolutionConcentrationPrecision const volPrecision = item.actSolutionVolumePrecision; //实际稀释液体积 const actDiluentVolume = item.actDiluentVolume //实际起始源溶液体积 const actStartSolutionVolume = item.actStartSolutionVolume if (isValueEmpty(actStartSolutionVolume) || isValueEmpty(actDiluentVolume) || isValueEmpty(targetStartSolutionUnit)) { return false } const converActDiluentVolume = volumeConverter.convert(actDiluentVolume + actDiluentVolumeUnit, actSolutionVolumeUnit) const converActStartSolutionVolume = volumeConverter.convert(actStartSolutionVolume + actStartSolutionVolumeUnit, actSolutionVolumeUnit) //实际源溶液加入体积+实际稀释液加入体积 const actVol = ( Number(converActDiluentVolume) + Number(converActStartSolutionVolume) ).toFixed(volPrecision) const targetStartVolUnit = actSolutionConcentrationUnit.split("/")[1];//先按照预设目标溶液浓度的单位标准 const converActVol = volumeConverter.convert(actVol + actSolutionVolumeUnit, targetStartVolUnit) const converActDiluentVolume1 = volumeConverter.convert(actStartSolutionVolume + actStartSolutionVolumeUnit, targetStartVolUnit) const converTargetAcSolution = convertConcentration.convert(targetAcSolution + targetStartSolutionUnit, actSolutionConcentrationUnit) // 实际目标溶液浓度 = 实际源溶液浓度÷(实际终体积÷源溶液加入体积); const actNd = ( parseFloat(converTargetAcSolution) / ( parseFloat(converActVol) / parseFloat(converActDiluentVolume1) ) ).toFixed(precision) const nd = actNd === 'Infinity' ? 0 : Number(actNd) console.log(actNd, targetAcSolution, actStartSolutionVolume, actVol, "actNd") // item.actSolutionConcentration = actNd === 'Infinity' ? 0 : actNd return { actVol: Number(actVol), actNd: nd } }, //计算并更新实际目标溶液浓度 先计算实际目标溶液体积再计算实际目标溶液浓度 updateSjmbrynd(item, targetAcSolution, params) { return this.calcNd(item, targetAcSolution, params) }, //更新起始溶液体积时,计算预设起始溶液体积和预设稀释液体积 updateTargetStartSolutionVolume(item, volume, unitParams) { const precision = item.targetStartSolutionVolumePrecision || 0 const concentration = item.targetSolutionConcentration const targetVolume = item.targetSolutionVolume const { subTargetStartSolution, headerSelectFields } = unitParams const { targetSolutionConcentrationUnit, targetSolutionVolumeUnit, targetStartSolutionVolumeUnit, targetDiluentVolumeUnit } = headerSelectFields if ( isValueEmpty(concentration) || isValueEmpty(targetVolume) || isValueEmpty(subTargetStartSolution) || isValueEmpty(targetSolutionConcentrationUnit) || isValueEmpty(targetSolutionVolumeUnit) || isValueEmpty(targetStartSolutionVolumeUnit) || isValueEmpty(targetDiluentVolumeUnit) ) { return; } const targetStartVolUnit = targetSolutionConcentrationUnit.split("/")[1];//先按照预设目标溶液浓度的单位标准 //将起始溶液浓度转换为和预设目标溶液浓度一样的单位再计算; const converStartCon = convertConcentration.convert(volume + subTargetStartSolution, targetSolutionConcentrationUnit) //将预设目标溶液体积转换为和预设目标溶液浓度单位的分母一样的单位再计算;如:预设目标溶液浓度单位为mg/mL,预设目标溶液体积单位为uL,则将预设目标溶液体积转换为mL const convertTargetVol = volumeConverter.convert(targetVolume + targetSolutionVolumeUnit, targetStartVolUnit) //预设起始溶液体积:(目标溶液预计浓度 乘以 目标溶液预计体积)除以 起始溶液浓度 const result = Number((concentration * convertTargetVol) / converStartCon) //根据预设起始溶液体积单位转换计算后的数据 const convertYsVol = volumeConverter.convert(result + targetStartVolUnit, targetStartSolutionVolumeUnit) item.targetStartSolutionVolume = Number(convertYsVol.toFixed( precision )) // this.$refs.stepTableRef.updateDataSourceByRowIndex(rowIndex, { targetStartSolutionVolume: result }); if (targetVolume) { //预设稀释液体积:目标溶液预计体积 减去 源溶液预计体积; const precision1 = item.targetDiluentVolumePrecision || 0; //将预设目标溶液体积转换为和预设起始溶液体积单位一样的单位再计算; const convertTargetVol1 = volumeConverter.convert(targetVolume + targetSolutionVolumeUnit, targetStartSolutionVolumeUnit) const result1 = (convertTargetVol1 - convertYsVol) item.targetDiluentVolume = Number(volumeConverter.convert(result1 + targetStartSolutionVolumeUnit, targetDiluentVolumeUnit).toFixed(precision1)) // this.$refs.stepTableRef.updateDataSourceByRowIndex(rowIndex, { targetDiluentVolume: result1 }); } }, // 导出excel模板 exportExcel(headerArray, title = '导出模板') { this.$modal.loading() const ws = XLSX.utils.aoa_to_sheet([headerArray]); const colWidths = headerArray.map(cell => getStringWidth(cell) + 2); ws['!cols'] = colWidths.map(width => ({ wch: width })); const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, 'Sheet1'); XLSX.writeFile(wb, `${title}.xlsx`); this.$modal.closeLoading() }, } }