| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760 |
- <!--该组件实际差值和实际偏倚有正负值-->
- <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) .toFixed(3)
- item.xiangFu =
- Math.abs(normalizePercent(item.shiJiChaZhi)) <=
- Math.abs(normalizePercent(zuiXiaoFanWei))
- ? '相符'
- : '不相符'
- item.jieGuo = ''
- } else {
- //item.jieGuo = this.deleteAccuracy(Math.abs(xinJieGuo - jiuJieGuo) / jiuJieGuo)
- item.jieGuo = this.deleteAccuracy((xinJieGuo - jiuJieGuo) / jiuJieGuo)
- item.xiangFu =
- Math.abs(normalizePercent(item.jieGuo)) <= Math.abs(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>
|