Просмотр исходного кода

试剂更换验证组件支持正负数

zhonghuizhen 7 месяцев назад
Родитель
Сommit
3786e274c8
1 измененных файлов с 760 добавлено и 0 удалено
  1. 760 0
      src/views/component/newReagent/newNegativeReagent.vue

+ 760 - 0
src/views/component/newReagent/newNegativeReagent.vue

@@ -0,0 +1,760 @@
+<!--该组件实际差值和实际偏倚有正负值-->
+<template>
+  <div>
+    <div v-if="show" class="reagentChange">
+      <el-row type="flex">
+        <el-col class="button">
+          <div class="title">平行实验/留样再测</div>
+          <div v-if="nodeId === 'Activity_0xkc1ji' || readonly" />
+          <div v-else>
+            <span style="color: red; margin-right: 20px; font-size: 16px"
+              >注意:手动计算与导入不可混用</span
+            >
+            <el-button
+              type="primary"
+              size="mini"
+              icon="ibps-icon-add"
+              @click="handleAdd"
+            >
+              添加</el-button
+            >
+            <el-button
+              type="primary"
+              size="mini"
+              icon="ibps-icon-copy"
+              @click="handleCopy"
+            >
+              复制</el-button
+            >
+            <el-button
+              type="danger"
+              size="mini"
+              icon="ibps-icon-remove"
+              @click="handleDelete"
+            >
+              删除</el-button
+            >
+            <el-button
+              type="warning"
+              size="mini"
+              icon="ibps-icon-calculator"
+              @click="computedResult"
+              >计算</el-button
+            >
+            <el-button
+              type="primary"
+              size="mini"
+              icon="ibps-icon-download"
+              @click="handleDownload"
+              >模版下载</el-button
+            >
+            <el-button
+              type="primary"
+              size="mini"
+              icon="ibps-icon-import"
+              @click="handleImport"
+              >导入</el-button
+            >
+          </div>
+        </el-col>
+      </el-row>
+      <el-row type="flex">
+        <el-col>
+          <el-table
+            ref="reagent"
+            :data="reagentDataFilter"
+            :span-method="spanMethod"
+            @selection-change="handleSelectionChange"
+          >
+            <el-table-column type="selection" width="55" />
+            <el-table-column label="检验项目" prop="jianCeXiangMu" />
+            <el-table-column label="浓度" prop="nongDu" width="100">
+              <template slot-scope="{ row }">
+                <el-input
+                  v-if="!disabled"
+                  v-model="row.nongDu"
+                  size="mini"
+                  placeholder="请输入"
+                />
+                <span v-else>{{ row.nongDu || '/' }}</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="样品编号" prop="biaoBenHao" width="100">
+              <template slot-scope="{ row }">
+                <el-input
+                  v-if="!disabled"
+                  v-model="row.biaoBenHao"
+                  size="mini"
+                  placeholder="请输入"
+                />
+                <span v-else>{{ row.biaoBenHao || '/' }}</span>
+              </template>
+            </el-table-column>
+            <el-table-column
+              label="旧试剂测得结果"
+              prop="jiuJieGuo"
+              width="130"
+            >
+              <template slot-scope="{ row }">
+                <el-input
+                  v-if="!disabled"
+                  v-model="row.jiuJieGuo"
+                  :min="0"
+                  size="mini"
+                  placeholder="请输入"
+                  :type="isCb ? 'text' : 'number'"
+                />
+                <span v-else>{{ row.jiuJieGuo || '/' }}</span>
+              </template>
+            </el-table-column>
+            <el-table-column
+              label="新试剂测得结果"
+              prop="xinJieGuo"
+              width="130"
+            >
+              <template slot-scope="{ row }">
+                <el-input
+                  v-if="!disabled"
+                  v-model="row.xinJieGuo"
+                  :min="0"
+                  size="mini"
+                  placeholder="请输入"
+                  :type="isCb ? 'text' : 'number'"
+                />
+                <span v-else>{{ row.xinJieGuo || '/' }}</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="计算方式" prop="jiSuanFangShi" width="140">
+              <template slot-scope="{ row }">
+                <el-select
+                  v-if="!disabled"
+                  v-model="row.jiSuanFangShi"
+                  size="mini"
+                  placeholder="请选择"
+                >
+                  <el-option label="Y-X" value="Y-X" />
+                  <el-option label="Y-X/X(%)" value="Y-X/X(%)" />
+                </el-select>
+                <span v-else>{{ row.jiSuanFangShi || '/' }}</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="实际差值" prop="shiJiChaZhi" />
+            <el-table-column label="实际偏倚" prop="jieGuo" />
+            <el-table-column
+              :label="isHz ? '允许差值' : '限定范围'"
+              prop="zuiXiaoFanWei"
+              width="110"
+            >
+              <template slot-scope="{ row }">
+                <el-input
+                  v-if="!disabled"
+                  v-model="row.zuiXiaoFanWei"
+                  size="mini"
+                  placeholder="请输入"
+                  type="number"
+                />
+                <span v-else>{{ row.zuiXiaoFanWei || '/' }}</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="允许偏倚" prop="yunXuPianYi" width="100">
+              <template slot-scope="{ row }">
+                <el-input
+                  v-if="!disabled"
+                  v-model="row.yunXuPianYi"
+                  size="mini"
+                  placeholder="请输入"
+                />
+                <span v-else>{{ row.yunXuPianYi || '/' }}</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="是否相符" prop="xiangFu" width="80" />
+            <el-table-column label="符合率" prop="biaoZhun" />
+            <el-table-column
+              label="符合率可接受标准"
+              prop="xiangMuFuHeLv"
+              width="140"
+            >
+              <template slot-scope="{ row }">
+                <el-select
+                  v-if="!disabled"
+                  v-model="row.xiangMuFuHeLv"
+                  size="mini"
+                  placeholder="请选择"
+                  @change="handelChange($event, row.jianCeXiangMu)"
+                >
+                  <el-option label="≥80%" value="≥80%" />
+                  <el-option label="=100%" value="=100%" />
+                </el-select>
+                <span v-else>{{ row.xiangMuFuHeLv || '/' }}</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="结论" prop="jieLun" />
+          </el-table>
+          <el-pagination
+            layout="total,sizes,prev, pager, next,jumper"
+            :current-page="requestPage.pageNo"
+            :page-size="requestPage.limit"
+            :page-sizes="[10, 15, 20, 30, 50, 100]"
+            :total="reagentData.length"
+            @size-change="handleSizeChange"
+            @current-change="handleCurrentChange"
+          />
+        </el-col>
+      </el-row>
+    </div>
+    <import-table
+      :visible="importTableDialogVisible"
+      title="导入"
+      @close="(visible) => (importTableDialogVisible = visible)"
+      @action-event="handleImportTableActionEvent"
+    />
+  </div>
+</template>
+<script>
+import importTable from '@/business/platform/form/formrender/dynamic-form/components/import-table'
+import IbpsImport from '@/plugins/import'
+import { downloadFile } from '@/business/platform/file/utils'
+import { cloneDeep } from 'lodash'
+export default {
+  components: {
+    importTable
+  },
+  props: {
+    formData: {
+      type: Object,
+      default: () => {}
+    },
+    readonly: {
+      type: Boolean,
+      default: false
+    },
+    params: {
+      type: Object,
+      default: () => {}
+    }
+  },
+  data() {
+    return {
+      reagentData: [],
+      copyDialogData: [],
+      disabled: false,
+      ypData: [],
+      nodeId: '',
+      show: true,
+      requestPage: {
+        limit: 20,
+        pageNo: 1
+      },
+      importTableDialogVisible: false,
+      multipleSelection: [],
+      isHz: false,
+      isCb: false
+    }
+  },
+  computed: {
+    reagentDataFilter() {
+      return this.reagentData.slice(
+        (this.requestPage.pageNo - 1) * this.requestPage.limit,
+        (this.requestPage.pageNo - 1) * this.requestPage.limit +
+          this.requestPage.limit
+      )
+    }
+  },
+  watch: {
+    'formData.sjghyzjlbbbzb': {
+      handler(value, old) {
+        if (value && value.length) {
+          this.reagentData = value
+        }
+      },
+      immediate: true
+    },
+    reagentData: {
+      handler(value, old) {
+        this.$emit('change-data', 'sjghyzjlbbbzb', value)
+      },
+      deep: true
+    },
+    'formData.fangAn': {
+      handler(val) {
+        this.showAndHide(val)
+      },
+      immediate: true
+    },
+    'formData.yunXuPianYi': {
+      handler(val) {
+        this.reagentData.forEach((item) => {
+          const { jianYanXiangMu } = this.formData
+          if (item.jianCeXiangMu === jianYanXiangMu) {
+            item.yunXuPianYi = val
+          }
+        })
+      },
+      immediate: true
+    },
+    'formData.xianDingFanWei': {
+      handler(val) {
+        this.reagentData.forEach((item) => {
+          const { jianYanXiangMu } = this.formData
+          if (item.jianCeXiangMu === jianYanXiangMu) {
+            item.zuiXiaoFanWei = val
+          }
+        })
+      }
+    }
+  },
+  mounted() {
+    const { first = '' } = this.$store.getters.level
+    const { deptList = [] } = this.$store.getters || {}
+    const t1 = deptList.find((i) => i.positionId === first) || {}
+    this.isHz = t1.positionName === '惠州市第三人民医院'
+    this.isCb = t1.positionName === '川北医学院附属医院'
+    // console.log(t1.positionName)
+    this.nodeId = this.params ? this.params.nodeId : ''
+    this.disabled = this.readonly || this.nodeId === 'Activity_0xkc1ji'
+    this.showAndHide(this.formData.fangAn)
+  },
+  methods: {
+    handelChange(event, jianCeXiangMu) {
+      this.reagentData.forEach((item) => {
+        if (item.jianCeXiangMu == jianCeXiangMu) {
+          item.xiangMuFuHeLv = event
+        }
+      })
+    },
+    // 新增
+    handleAdd() {
+      if (
+        !this.reagentData ||
+        (this.reagentData && this.reagentData.length === 0)
+      ) {
+        this.disabled = false
+      }
+      const { jianYanXiangMu, yunXuPianYi, xianDingFanWei } = this.formData
+      if (this.disabled && this.reagentData?.length > 0) {
+        return this.$message.warning('导入与手动添加功能不可混用!')
+      }
+      if (!this.formData.jianYanXiangMu) {
+        return this.$message.warning('请选择检验项目')
+      }
+      this.reagentData.push({
+        jianCeXiangMu: jianYanXiangMu,
+        nongDu: '',
+        biaoBenHao: '',
+        jiuJieGuo: '',
+        xinJieGuo: '',
+        jiSuanFangShi: 'Y-X/X(%)',
+        shiJiChaZhi: '',
+        jieGuo: '',
+        zuiXiaoFanWei: xianDingFanWei,
+        yunXuPianYi: yunXuPianYi,
+        xiangFu: '',
+        biaoZhun: '',
+        xiangMuFuHeLv: '≥80%',
+        jieLun: ''
+      })
+      const grouped = this.reagentData.reduce((acc, item) => {
+        const key = item.jianCeXiangMu
+        if (!acc[key]) acc[key] = []
+        acc[key].push(item)
+        return acc
+      }, {})
+      // 先清空数组,再填充新数据(确保 Vue 检测到变化)
+      this.reagentData.splice(
+        0,
+        this.reagentData.length,
+        ...Object.values(grouped).flat()
+      )
+      console.log(this.reagentData)
+    },
+    // 复制
+    handleCopy() {
+      if (this.multipleSelection.length > 0) {
+        this.reagentData = this.reagentData.concat(
+          cloneDeep(this.multipleSelection)
+        )
+        const grouped = this.reagentData.reduce((acc, item) => {
+          const key = item.jianCeXiangMu
+          if (!acc[key]) acc[key] = []
+          acc[key].push(item)
+          return acc
+        }, {})
+        // 先清空数组,再填充新数据(确保 Vue 检测到变化)
+        this.reagentData.splice(
+          0,
+          this.reagentData.length,
+          ...Object.values(grouped).flat()
+        )
+      } else {
+        this.$message.warning('请选择数据')
+      }
+    },
+    // 删除
+    handleDelete() {
+      this.$confirm('确定删除当前选中数据?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        if (this.multipleSelection.length > 0) {
+          this.reagentData = this.reagentData.filter(
+            (row) => !this.multipleSelection.includes(row)
+          )
+        } else {
+          this.$message.warning('请选择数据')
+        }
+      })
+    },
+    handleSelectionChange(val) {
+      this.multipleSelection = val
+    },
+    handleImport() {
+      this.importTableDialogVisible = true
+    },
+    handleDownload() {
+      downloadFile({
+        id: 'download_sjghdl',
+        fileName: '试剂更换验证定量模板',
+        ext: 'xlsx'
+      })
+    },
+    getColumns() {
+      return [
+        {
+          field_name: 'xiangFu',
+          label: '是否相符',
+          name: 'xiangFu'
+        },
+        {
+          field_name: 'jianCeXiangMu',
+          label: '检验项目',
+          name: 'jianCeXiangMu'
+        },
+        {
+          field_name: 'nongDu',
+          label: '浓度',
+          name: 'nongDu'
+        },
+        {
+          field_name: 'biaoBenHao',
+          label: '样品编号',
+          name: 'biaoBenHao'
+        },
+        {
+          field_name: 'jiuJieGuo',
+          label: '旧试剂测得结果',
+          name: 'jiuJieGuo'
+        },
+        {
+          field_name: 'xinJieGuo',
+          label: '新试剂测得结果',
+          name: 'xinJieGuo'
+        },
+        {
+          field_name: 'shiJiChaZhi',
+          label: '实际差值',
+          name: 'shiJiChaZhi'
+        },
+        {
+          field_name: 'jieGuo',
+          label: '实际偏倚',
+          name: 'jieGuo'
+        },
+        {
+          field_name: 'zuiXiaoFanWei',
+          label: this.isHz ? '允许差值' : '限定范围',
+          name: 'zuiXiaoFanWei'
+        },
+        {
+          field_name: 'yunXuPianYi',
+          label: '允许偏倚',
+          name: 'yunXuPianYi'
+        },
+        {
+          field_name: 'biaoZhun',
+          label: '符合率',
+          name: 'biaoZhun'
+        },
+        {
+          field_name: 'jieLun',
+          label: '结论',
+          name: 'jieLun'
+        },
+        {
+          field_name: 'xiangMuFuHeLv',
+          label: '符合率可接受标准',
+          name: 'xiangMuFuHeLv'
+        }
+      ]
+    },
+    getKeys(data) {
+      return Array.isArray(data)
+        ? data.reduce((acc, item) => ({ ...acc, [item.label]: item.name }), {})
+        : {}
+    },
+    handleImportTableActionEvent(file, options) {
+      IbpsImport.xlsx(file, options).then(({ header, results }) => {
+        const list = []
+        const keys = this.getKeys(this.getColumns())
+        results.forEach((item) => {
+          const obj = {}
+          Object.keys(item).forEach((key) => {
+            if (keys[key]) {
+              obj[keys[key]] = item[key]
+            }
+          })
+          list.push(obj)
+        })
+        const filteredArray = list
+          .map((item) => {
+            if (item.jianCeXiangMu && item.biaoZhun && item.jieLun) {
+              return item
+            }
+            return null
+          })
+          .filter((item) => item !== null)
+        filteredArray.forEach((item) => {
+          list.forEach((el) => {
+            if (el.jianCeXiangMu === item.jianCeXiangMu) {
+              el.biaoZhun = item.biaoZhun
+              el.jieLun = item.jieLun
+              el.xiangMuFuHeLv = item.xiangMuFuHeLv
+            }
+            /*
+            if (el.shiJiChaZhi) {
+              el.shiJiChaZhi = (el.shiJiChaZhi + '').replace('-', '')
+            }
+            if (el.jieGuo) {
+              el.jieGuo = (el.jieGuo + '').replace('-', '')
+            }*/
+          })
+        })
+        this.reagentData = list
+        this.disabled = true
+        this.importTableDialogVisible = false
+      })
+    },
+    // 当前页码改变
+    handleCurrentChange(val) {
+      this.requestPage.pageNo = val
+    },
+    // 页码选择器改变
+    handleSizeChange(val) {
+      this.requestPage.limit = val
+      this.requestPage.pageNo = 1
+    },
+    showAndHide(data) {
+      if (
+        data.includes('平行试验') ||
+        data.includes('留样再测') ||
+        data.includes('比对方案')
+      ) {
+        this.show = true
+      } else {
+        this.show = false
+      }
+    },
+    // 计算结果
+    computedResult() {
+      if (this.disabled) {
+        this.$message.warning('导入数据请勿使用计算功能!')
+        return
+      }
+      const hasInvalidData = this.reagentData.some(
+        (item) =>
+          !item.jiuJieGuo ||
+          item.jiuJieGuo <= 0 ||
+          !item.xinJieGuo ||
+          item.xinJieGuo <= 0
+      )
+
+      if (hasInvalidData || this.reagentData.length === 0) {
+        this.$message.warning('新、旧试剂测得结果必须大于0且不能为空!')
+        return
+      }
+
+      const hasPyOrCz = this.reagentData.some(
+        (item) =>
+          (item.jiSuanFangShi === 'Y-X/X(%)' && !item.yunXuPianYi) ||
+          (item.jiSuanFangShi === 'Y-X' && !item.zuiXiaoFanWei)
+      )
+
+      if (hasPyOrCz) {
+        this.$message.warning(
+          `计算方式为 Y-X/X(%) 请填写允许偏倚,计算方式为 Y-X 请填写${
+            this.isHz ? '允许差值' : '限定范围'
+          }`
+        )
+        return
+      }
+      // 预处理函数:统一处理百分比数值
+      const normalizePercent = (val) =>
+        Number(String(val).replace('%', '')) || 0
+
+      // 使用 Map 存储统计信息
+      const projectStats = new Map()
+
+      this.reagentData.forEach((item) => {
+        const {
+          jiSuanFangShi,
+          xinJieGuo,
+          jiuJieGuo,
+          zuiXiaoFanWei,
+          jianCeXiangMu
+        } = item
+
+        // 计算差值/偏倚
+        if (jiSuanFangShi === 'Y-X') {
+          //item.shiJiChaZhi = Math.abs((xinJieGuo * 100000 - jiuJieGuo * 100000) / 100000)
+          item.shiJiChaZhi = (xinJieGuo * 100000 - jiuJieGuo * 100000) / 100000
+          item.xiangFu =
+            normalizePercent(item.shiJiChaZhi) <=
+            normalizePercent(zuiXiaoFanWei)
+              ? '相符'
+              : '不相符'
+          item.jieGuo = ''
+        } else {
+          //item.jieGuo = this.deleteAccuracy(Math.abs(xinJieGuo - jiuJieGuo) / jiuJieGuo)
+          item.jieGuo =  this.deleteAccuracy((xinJieGuo - jiuJieGuo) / jiuJieGuo)
+          item.xiangFu =
+            normalizePercent(item.jieGuo) <= normalizePercent(item.yunXuPianYi)
+              ? '相符'
+              : '不相符'
+          item.shiJiChaZhi = ''
+        }
+
+        // 统计符合率
+        const stats = projectStats.get(jianCeXiangMu) || { total: 0, yes: 0 }
+        stats.total++
+        if (item.xiangFu === '相符') stats.yes++
+        projectStats.set(jianCeXiangMu, stats)
+      })
+
+      // 填充符合率标准,保留小数点后两位
+      this.reagentData.forEach((item) => {
+        const stats = projectStats.get(item.jianCeXiangMu)
+        item.biaoZhun = this.deleteAccuracy(stats.yes / stats.total)
+        console.log(parseFloat(item.biaoZhun), item.xiangMuFuHeLv)
+        item.jieLun =
+          parseFloat(item.biaoZhun) >=
+          parseFloat(item.xiangMuFuHeLv.replace(/[≥=%]/g, ''))
+            ? '可接受'
+            : '不可接受'
+      })
+    },
+    spanMethod({ row, column, rowIndex, columnIndex }) {
+      if (
+        columnIndex === 1 ||
+        columnIndex === 12 ||
+        columnIndex === 13 ||
+        columnIndex === 14
+      ) {
+        const currentValue = row[column.property]
+        const preRow = this.reagentData[rowIndex - 1]
+        // 上一行这一列的数据
+        const preValue = preRow ? preRow[column.property] : null
+        // 如果当前值和上一行的值相同,则将当前单元格隐藏
+        // 给第0,10,11,12列对数值相同且是同一个'jianCeXiangMu'进行表格合并
+        if (
+          currentValue === preValue &&
+          row['jianCeXiangMu'] === preRow['jianCeXiangMu']
+        ) {
+          return { rowspan: 0, colspan: 0 }
+        } else {
+          let rowspan = 1
+          // 计算应该合并的行数
+          for (let i = rowIndex + 1; i < this.reagentData.length; i++) {
+            const nextRow = this.reagentData[i]
+            const nextValue = nextRow[column.property]
+            if (
+              nextValue === currentValue &&
+              nextRow['jianCeXiangMu'] === row['jianCeXiangMu']
+            ) {
+              rowspan++
+            } else {
+              break
+            }
+          }
+          return { rowspan, colspan: 1 }
+        }
+        // if (rowIndex % rowspan === 0) {
+        //     return {
+        //         rowspan,
+        //         colspan: 1
+        //     }
+        // } else {
+        //     return {
+        //         rowspan: 0,
+        //         colspan: 0
+        //     }
+        // }
+      }
+    },
+      // 去除小数*100精度方法(支持负数)
+    deleteAccuracy(num) {
+      // 处理负数
+      const isNegative = num < 0
+      const absoluteNum = Math.abs(num)
+      
+      // 是否带小数点
+      if (absoluteNum.toString().includes('.')) {
+        // 保留小数点后面3位
+        const numArry = absoluteNum.toFixed(3).toString().split('.')
+        let result
+        
+        // 整数位是否大于0
+        if (numArry[0] > 0) {
+          result = Number(
+            numArry[0] +
+            numArry[1].substring(0, 2) +
+            '.' +
+            numArry[1].substring(2, 3)
+          ) + '%'
+        } else {
+          // 小数位第一位是否大于0
+          if (numArry[1][0] > 0) {
+            result = Number(
+              numArry[1].substring(0, 2) + '.' + numArry[1].substring(2, 3)
+            ) + '%'
+          } else {
+            // 小数位第二位是否大于0
+            if (numArry[1][1] > 0) {
+              result = Number(
+                numArry[1].substring(1, 2) + '.' + numArry[1].substring(2, 3)
+              ) + '%'
+            } else {
+              result = Number(0 + '.' + numArry[1].substring(2, 3)) + '%'
+            }
+          }
+        }
+        
+        // 恢复负号
+        return isNegative ? '-' + result : result
+      } else {
+        const result = absoluteNum * 100 + '%'
+        return isNegative ? '-' + result : result
+      }
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.reagentChange {
+  margin-bottom: 20px;
+  .button {
+    display: flex;
+    justify-content: space-between;
+    padding: 0px 0px 0px 15px;
+    background: #f0ffff;
+    .title {
+      color: #999;
+      font-size: 12px;
+      font-weight: bold;
+      margin-bottom: 0;
+    }
+    .el-button {
+      margin: 0;
+    }
+  }
+}
+</style>