质控
This commit is contained in:
parent
36826154e0
commit
92b55c83dc
43
src/api/liswork/qualityControl/index.ts
Normal file
43
src/api/liswork/qualityControl/index.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// @ts-ignore
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
// 保存数据
|
||||||
|
export function saveOpt(data?: Object) {
|
||||||
|
return request({
|
||||||
|
url: '/qc/saveOpt',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 查询项目的质控规则
|
||||||
|
export function queryRules(data?: Object) {
|
||||||
|
return request({
|
||||||
|
url: '/qc/queryRules',
|
||||||
|
method: 'get',
|
||||||
|
params: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 保存质控项目数据
|
||||||
|
export function saveQCItem(data?: Object) {
|
||||||
|
return request({
|
||||||
|
url: '/qc/saveQCItem',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 普通标本和质控标本对应关系
|
||||||
|
export function queryDataFromSample(data?: Object) {
|
||||||
|
return request({
|
||||||
|
url: '/qc/queryDataFromSample',
|
||||||
|
method: 'get',
|
||||||
|
params: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 查询质控品数据
|
||||||
|
export function queryQcSample(data?: Object) {
|
||||||
|
return request({
|
||||||
|
url: '/qc/queryQcSample',
|
||||||
|
method: 'get',
|
||||||
|
params: data
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -18,6 +18,14 @@ export function getdef(query?: Object) {
|
|||||||
params: query,
|
params: query,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// 查询某个参数的值
|
||||||
|
export function queryValue(query?: Object) {
|
||||||
|
return request({
|
||||||
|
url: '/param/queryValue',
|
||||||
|
method: 'get',
|
||||||
|
params: query,
|
||||||
|
});
|
||||||
|
}
|
||||||
// 修改参数列表
|
// 修改参数列表
|
||||||
export function updateParam(query?: Object) {
|
export function updateParam(query?: Object) {
|
||||||
return request({
|
return request({
|
||||||
|
|||||||
288
src/views/liswork/qualityControl/components/setParams/index.vue
Normal file
288
src/views/liswork/qualityControl/components/setParams/index.vue
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
/**
|
||||||
|
* @file index.vue 参数配置
|
||||||
|
* @author: w
|
||||||
|
* @since: 2025-12-29
|
||||||
|
*/
|
||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<div class="table-toolbar">
|
||||||
|
<el-button type="primary" plain icon="Check" @click="handleSave">保存</el-button>
|
||||||
|
</div>
|
||||||
|
<div class="tips">说明: 本软件支持每天最多做6个质控品,质控品号固定为1-6,如果仪器上的质控标本号不是1-6,请在下面设定它们的对应关系,这样系统在采集仪器数据时,质控标本号据时能够自动转换!</div>
|
||||||
|
|
||||||
|
<div class="bb_box">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="14">
|
||||||
|
<div>
|
||||||
|
<span class="text"> 每日质控标本个数: </span>
|
||||||
|
<el-radio-group v-model="zkphValue" @change="zkphChange">
|
||||||
|
<el-radio value="1">1</el-radio>
|
||||||
|
<el-radio value="2">2</el-radio>
|
||||||
|
<el-radio value="3">3</el-radio>
|
||||||
|
<el-radio value="4">4</el-radio>
|
||||||
|
<el-radio value="5">5</el-radio>
|
||||||
|
<el-radio value="6">6</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="text"> 模式: </span>
|
||||||
|
<el-radio-group v-model="radio2" @change="radioChange">
|
||||||
|
<el-radio value="1">SD模式(WestGuard)</el-radio>
|
||||||
|
<el-radio value="2">CV模式(WestGuard)</el-radio>
|
||||||
|
<el-radio value="3">Gurbbs+Tn界值法</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="check-box">
|
||||||
|
<el-checkbox v-model="check1" label="质控图显示其他非画点图" value="1" />
|
||||||
|
<el-checkbox v-model="check2" label="质控图是否画虚线" value="1" />
|
||||||
|
<el-checkbox v-model="check3" label="质控图累计SD/CV值通过批号计算" value="1" />
|
||||||
|
</div>
|
||||||
|
<div class="bbh-box">
|
||||||
|
<span class="text">每日参考对照标本号: </span>
|
||||||
|
<el-input v-model="input" style="width:200px" />
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="2" class="text_btn">
|
||||||
|
<el-button type="primary" @click="addItem">新增</el-button>
|
||||||
|
<el-button type="danger" @click="delItem">删除</el-button>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<CustomTable ref="tableRef" :data="tableData" :columns="columns" :config="tableConfig"
|
||||||
|
@row-click="rowYbhClick">
|
||||||
|
<template #ybh="{ row }">
|
||||||
|
<el-input v-model="row.ybh" class="full-width-input" />
|
||||||
|
</template>
|
||||||
|
<template #zkph="{ row }">
|
||||||
|
<el-input v-model="row.zkph" class="full-width-input" />
|
||||||
|
</template>
|
||||||
|
<template #cs="{ row }">
|
||||||
|
<el-input v-model="row.cs" class="full-width-input" />
|
||||||
|
</template>
|
||||||
|
</CustomTable>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-row :gutter="10" class="table-row">
|
||||||
|
<el-col :span="5" class="table-col">
|
||||||
|
<div class="tips">指标设定</div>
|
||||||
|
<CustomTable ref="leftTableRef" :data="leftTableData" :columns="leftColumns" @row-click="rowClick"
|
||||||
|
:config="{ border: true, highlightCurrentRow: true, height: '100%' }">
|
||||||
|
<template #xmdh="{ row }">
|
||||||
|
{{ row.xmdh }} {{ row.xmmc }}
|
||||||
|
</template>
|
||||||
|
<template #qcuselog="{ row }">
|
||||||
|
<el-checkbox :model-value="row.qcuselog == 1" readonly></el-checkbox>
|
||||||
|
</template>
|
||||||
|
<template #qcusepos="{ row }">
|
||||||
|
<el-checkbox :model-value="row.qcusepos == 1" readonly></el-checkbox>
|
||||||
|
</template>
|
||||||
|
</CustomTable>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="19" class="table-col">
|
||||||
|
<el-button type="primary">新增</el-button>
|
||||||
|
<el-button type="danger">删除</el-button>
|
||||||
|
<el-button>取消改动</el-button>
|
||||||
|
<CustomTable ref="rightTableRef" :data="rightTableData" :columns="rightTolumns" :enableColumnDrag="true"
|
||||||
|
@column-drag-end="columnDragEnd" :config="{ border: true, highlightCurrentRow: true, height: '100%' }">
|
||||||
|
</CustomTable>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { queryOldxminfo } from '@/api/liswork/xtwh/xmReqItemVsApi'
|
||||||
|
import { queryDataFromSample, queryQcSample, } from '@/api/liswork/qualityControl'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
|
const yq = defineModel<string>()
|
||||||
|
const zkphValue = ref('1')
|
||||||
|
const radio2 = ref('1')
|
||||||
|
const check1 = ref(false)
|
||||||
|
const check2 = ref(false)
|
||||||
|
const check3 = ref(false)
|
||||||
|
const input = ref('')
|
||||||
|
const selectedRow = ref()
|
||||||
|
interface ybhItem {
|
||||||
|
ybh: string
|
||||||
|
zkph: string
|
||||||
|
cs: string
|
||||||
|
}
|
||||||
|
const tableData = ref<ybhItem[]>([])
|
||||||
|
const columns = [
|
||||||
|
{ label: '普通标准号', prop: 'ybh', visible: true, align: 'center', slot: 'ybh' },
|
||||||
|
{ label: '对应质控品号', prop: 'zkph', visible: true, align: 'center', slot: 'zkph' },
|
||||||
|
{ label: '次数', prop: 'cs', visible: true, align: 'center', slot: 'cs' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const tableConfig = {
|
||||||
|
border: true,
|
||||||
|
highlightCurrentRow: true,
|
||||||
|
height: '200px'
|
||||||
|
}
|
||||||
|
const tableRef = ref()
|
||||||
|
const ybhInfo = ref()
|
||||||
|
const rowYbhClick = (row: ybhItem) => {
|
||||||
|
ybhInfo.value = row
|
||||||
|
}
|
||||||
|
const addItem = () => {
|
||||||
|
tableData.value.push({
|
||||||
|
ybh: '',
|
||||||
|
zkph: '',
|
||||||
|
cs: ''
|
||||||
|
})
|
||||||
|
nextTick(() => {
|
||||||
|
tableRef.value.setScrollTop(tableData.value.length)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const delItem = () => {
|
||||||
|
const index = tableData.value.indexOf(ybhInfo.value)
|
||||||
|
tableData.value.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const leftTableData = ref([])
|
||||||
|
const leftColumns = [
|
||||||
|
{ label: '项目代号', prop: 'xmdh', visible: true, slot: 'xmdh', },
|
||||||
|
{ label: '值取对数', prop: 'qcuselog', visible: true, align: 'center', slot: 'qcuselog', width: 60 },
|
||||||
|
{ label: '半定量值', prop: 'qcusepos', visible: true, align: 'center', slot: 'qcusepos', width: 60 },
|
||||||
|
]
|
||||||
|
const rightTableData = ref([])
|
||||||
|
const rightTolumns = ref([
|
||||||
|
{ label: '启用日期', prop: 'ksrq', visible: true, align: 'center' },
|
||||||
|
{ label: '质控品', prop: 'zkph', visible: true, align: 'center' },
|
||||||
|
{ label: '批号', prop: 'zkpph', visible: true, align: 'center' },
|
||||||
|
{ label: '浓度', prop: 'ld', visible: true, align: 'center' },
|
||||||
|
{ label: '靶值', prop: 'zkbz', visible: true, align: 'center' },
|
||||||
|
{ label: '有效期至', prop: 'endrq', visible: true, align: 'center' },
|
||||||
|
{ label: 'CV(%)', prop: 'cv', visible: true, align: 'center' },
|
||||||
|
{ label: '目标CV(%)', prop: 'qccv', visible: true, align: 'center' },
|
||||||
|
{ label: '试剂', prop: 'sjph', visible: true, align: 'center' },
|
||||||
|
{ label: '质控品厂商', prop: 'qcsrc', visible: true, align: 'center' },
|
||||||
|
{ label: '波长', prop: 'bc', visible: true, align: 'center' },
|
||||||
|
{ label: '方法', prop: 'ff', visible: true, align: 'center' },
|
||||||
|
{ label: '校准品厂商', prop: 'standsrc', visible: true, align: 'center' },
|
||||||
|
])
|
||||||
|
|
||||||
|
const columnDragEnd = (data: any) => {
|
||||||
|
rightTolumns.value = data.columns
|
||||||
|
}
|
||||||
|
|
||||||
|
const zkphChange = (val: string) => {
|
||||||
|
rowClick(selectedRow.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const radioChange = (val: string) => {
|
||||||
|
if (val == "1") {
|
||||||
|
rightTolumns.value[6].label = 'Sd'
|
||||||
|
rightTolumns.value[6].visible = true
|
||||||
|
}
|
||||||
|
if (val == "2") {
|
||||||
|
rightTolumns.value[6].label = 'CV(%)'
|
||||||
|
rightTolumns.value[6].visible = true
|
||||||
|
}
|
||||||
|
if (val == "3") {
|
||||||
|
rightTolumns.value[6].visible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
watch(zkphValue, (val) => {
|
||||||
|
if (val) {
|
||||||
|
radioChange(zkphValue.value)
|
||||||
|
}
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
|
const rowClick = (row: any) => {
|
||||||
|
selectedRow.value = row
|
||||||
|
const data = {
|
||||||
|
xmdh: row.xmdh,
|
||||||
|
yq: yq.value,
|
||||||
|
zkph: zkphValue.value,
|
||||||
|
}
|
||||||
|
queryQcSample(data).then((res: any) => {
|
||||||
|
rightTableData.value = res.data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const getParamsInfo = () => {
|
||||||
|
queryDataFromSample({ yq: yq.value }).then((res: any) => {
|
||||||
|
tableData.value = res.data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const getXmList = () => {
|
||||||
|
queryOldxminfo({ yq: yq.value, }).then((res: any) => {
|
||||||
|
leftTableData.value = res.data
|
||||||
|
rowClick(leftTableData.value[0])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(yq, (val) => {
|
||||||
|
if (val) {
|
||||||
|
getParamsInfo()
|
||||||
|
getXmList()
|
||||||
|
}
|
||||||
|
}, { immediate: true })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.tips {
|
||||||
|
font-family: SourceHanSansCN, SourceHanSansCN;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #409eff;
|
||||||
|
font-weight: 600;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bb_box {
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
.text {
|
||||||
|
font-family: SourceHanSansCN, SourceHanSansCN;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #409eff;
|
||||||
|
display: inline-block;
|
||||||
|
width: 140px;
|
||||||
|
text-align: right;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text_btn {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.el-button {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.check-box {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bbh-box {
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-row {
|
||||||
|
height: calc(100% - 385px);
|
||||||
|
|
||||||
|
.table-col {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
162
src/views/liswork/qualityControl/components/setProject/index.vue
Normal file
162
src/views/liswork/qualityControl/components/setProject/index.vue
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
/**
|
||||||
|
* @file index.vue 规则设定
|
||||||
|
* @author: w
|
||||||
|
* @since: 2025-12-29
|
||||||
|
*/
|
||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<div class="table-toolbar">
|
||||||
|
<el-button type="primary" plain icon="Check" @click="handleSave">保存</el-button>
|
||||||
|
</div>
|
||||||
|
<div class="tips">提示:左边的列表已经列出所有检验项目,请在质控项目前面打勾,右边是当前项目的质控规则</div>
|
||||||
|
<el-row :gutter="20" class="table-row">
|
||||||
|
<el-col :span="12" class="table-col">
|
||||||
|
<CustomTable ref="tableLeftRef" :data="tableLeftData" :columns="leftColumns" :config="tableConfig"
|
||||||
|
@row-click="rowClick">
|
||||||
|
<template #xmdh="{ row }">
|
||||||
|
{{ row.xmdh }} {{ row.xmmc }}
|
||||||
|
</template>
|
||||||
|
<template #zkxm="{ row }">
|
||||||
|
<el-checkbox v-model="row.zkxm" true-value="Y" false-value="N"></el-checkbox>
|
||||||
|
</template>
|
||||||
|
<template #qcuselog="{ row }">
|
||||||
|
<el-checkbox v-model="row.qcuselog" true-value="1" false-value="0"></el-checkbox>
|
||||||
|
</template>
|
||||||
|
<template #qcusepos="{ row }">
|
||||||
|
<el-checkbox v-model="row.qcusepos" true-value="1" false-value="0"></el-checkbox>
|
||||||
|
</template>
|
||||||
|
<template #qcvs="{ row }">
|
||||||
|
<el-checkbox v-model="row.qcvs" true-value="Y" false-value="N"></el-checkbox>
|
||||||
|
</template>
|
||||||
|
<template #qccv="{ row }">
|
||||||
|
<el-input v-model="row.qccv" class="full-width-input" />
|
||||||
|
</template>
|
||||||
|
</CustomTable>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12" class="table-col">
|
||||||
|
<CustomTable ref="tableRef" :data="rightTableData" :columns="rightColumns" :config="tableConfig"
|
||||||
|
@selection-change="handleSelectionChange" />
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { saveQCItem, queryRules } from '@/api/liswork/qualityControl'
|
||||||
|
// import { getDictData, dictData } from '@/hooks'
|
||||||
|
import { getComDicts } from '@/api/liswork/dict/ComDict'
|
||||||
|
import { queryOldxminfo } from '@/api/liswork/xtwh/xmReqItemVsApi'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
|
|
||||||
|
const yq = defineModel<string>()
|
||||||
|
const tableLeftData = ref<Array<any>>([])
|
||||||
|
const leftColumns = [
|
||||||
|
{ label: '项目代号', prop: 'xmdh', visible: true, width: 200, slot: 'xmdh' },
|
||||||
|
{ label: '质控', prop: 'zkxm', visible: true, align: 'center', slot: 'zkxm' }, //Y/N
|
||||||
|
{ label: '值取对数', prop: 'qcuselog', visible: true, align: 'center', slot: 'qcuselog' },
|
||||||
|
{ label: '半定量值', prop: 'qcusepos', visible: true, align: 'center', slot: 'qcusepos' },
|
||||||
|
{ label: '室间质评项目', prop: 'qcvs', visible: true, align: 'center', slot: 'qcvs' },
|
||||||
|
{ label: '规定CV值(%)', prop: 'qccv', visible: true, align: 'center', slot: 'qccv', showOverflowTooltip: false },
|
||||||
|
];
|
||||||
|
const rightTableData = ref<Array<any>>([])
|
||||||
|
|
||||||
|
const tableRef = ref()
|
||||||
|
const rightColumns = [
|
||||||
|
{ label: '有效', type: 'selection', visible: true, width: 50, align: 'center' },
|
||||||
|
{ label: '规则', prop: 'zddh', visible: true, width: 100, align: 'center' },
|
||||||
|
{ label: '规则说明', prop: 'zdmc', visible: true, align: 'center' },
|
||||||
|
];
|
||||||
|
const tableConfig = {
|
||||||
|
border: true,
|
||||||
|
height: '100%',
|
||||||
|
highlightCurrentRow: true,
|
||||||
|
}
|
||||||
|
const zddhStr = ref<string>('')
|
||||||
|
|
||||||
|
const currentIndex = ref<number>(0)
|
||||||
|
|
||||||
|
const handleSelectionChange = (selection: any) => {
|
||||||
|
zddhStr.value = selection.map((item: any) => { return item.zddh }).join(',')
|
||||||
|
tableLeftData.value[currentIndex.value].qcRules = {
|
||||||
|
rules: zddhStr.value,
|
||||||
|
xmdh: tableLeftData.value[currentIndex.value].xmdh,
|
||||||
|
yq: yq.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const handleSave = () => {
|
||||||
|
const arr = tableLeftData.value.map((item: any) => {
|
||||||
|
return {
|
||||||
|
qcRules: item.qcRules || null,
|
||||||
|
xmInfoNew: item
|
||||||
|
}
|
||||||
|
})
|
||||||
|
saveQCItem(arr).then((res: any) => {
|
||||||
|
if (res.code == 0) {
|
||||||
|
ElMessage.success('保存成功')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const getRuleList = () => {
|
||||||
|
queryOldxminfo({ yq: yq.value }).then((res: any) => {
|
||||||
|
tableLeftData.value = res.data || []
|
||||||
|
getSelectRule(tableLeftData.value[0])
|
||||||
|
})
|
||||||
|
getComDicts({ zdlb: 'ZKGZ' }).then((res: any) => {
|
||||||
|
rightTableData.value = res.data || []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const rowClick = (row: any) => {
|
||||||
|
getSelectRule(row)
|
||||||
|
currentIndex.value = tableLeftData.value.findIndex((item: any) => item.xmdh === row.xmdh)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const getSelectRule = (row: any) => {
|
||||||
|
queryRules({ yq: yq.value, xmdh: row.xmdh }).then((res: any) => {
|
||||||
|
let arr = []
|
||||||
|
const info = tableLeftData.value[currentIndex.value]?.qcRules || {}
|
||||||
|
if (info?.rules) {
|
||||||
|
arr = info.rules.split(',')
|
||||||
|
} else {
|
||||||
|
if (res.data && res.data.rules) {
|
||||||
|
arr = res.data.rules.split(',')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tableRef.value.clearSelection()
|
||||||
|
arr.forEach((item: any) => {
|
||||||
|
const index = rightTableData.value.findIndex((val: any) => val.zddh === item)
|
||||||
|
if (index > -1) {
|
||||||
|
tableRef.value.toggleRowSelection(rightTableData.value[index], true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(yq, (val) => {
|
||||||
|
if (val) {
|
||||||
|
getRuleList()
|
||||||
|
}
|
||||||
|
}, { immediate: true })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.table-row {
|
||||||
|
height: calc(100% - 135px);
|
||||||
|
margin-top: 10px;
|
||||||
|
|
||||||
|
.table-col {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tips {
|
||||||
|
font-family: SourceHanSansCN, SourceHanSansCN;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #409eff;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,93 @@
|
|||||||
|
/**
|
||||||
|
* @file index.vue 规则设定
|
||||||
|
* @author: w
|
||||||
|
* @since: 2025-12-29
|
||||||
|
*/
|
||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<div class="table-toolbar">
|
||||||
|
<el-button type="primary" plain icon="Check" @click="handleSave">保存</el-button>
|
||||||
|
</div>
|
||||||
|
<div class="tips">说明:1_2S作为一个警告规则,如果选中这个规则,则表示所有其他失控规则必须先满足说明:25,则视为在控。</div>
|
||||||
|
<div class="table-row" style="margin-top: 10px;">
|
||||||
|
<CustomTable ref="tableRef" :data="tableData" :columns="columns" :config="tableConfig"
|
||||||
|
@selection-change="handleSelectionChange" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { saveOpt } from '@/api/liswork/qualityControl'
|
||||||
|
// import { getDictData, dictData } from '@/hooks'
|
||||||
|
import { getComDicts } from '@/api/liswork/dict/ComDict'
|
||||||
|
import { queryValue } from '@/api/liswork/xtwh/ComOpt'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
const yq = defineModel<string>()
|
||||||
|
|
||||||
|
const tableData = ref<Array<any>>([])
|
||||||
|
|
||||||
|
const tableRef = ref()
|
||||||
|
const columns = [
|
||||||
|
{ label: '有效', type: 'selection', visible: true, width: 50, align: 'center' },
|
||||||
|
{ label: '规则', prop: 'zddh', visible: true, width: 200, align: 'center' },
|
||||||
|
{ label: '规则说明', prop: 'zdmc', visible: true, align: 'center' },
|
||||||
|
];
|
||||||
|
const tableConfig = {
|
||||||
|
border: true,
|
||||||
|
height: '100%',
|
||||||
|
highlightCurrentRow: true,
|
||||||
|
}
|
||||||
|
const zddhStr = ref<string>('')
|
||||||
|
const handleSelectionChange = (selection: any) => {
|
||||||
|
zddhStr.value = selection.map((item: any) => { return item.zddh }).join(',')
|
||||||
|
}
|
||||||
|
const handleSave = () => {
|
||||||
|
if (!zddhStr.value) return ElMessage.warning('请至少选择一项规则')
|
||||||
|
saveOpt([{ yq: yq.value, xxqz: zddhStr.value, xxdh: 'QCRULES' }]).then((res: any) => {
|
||||||
|
if (res.code == 0) {
|
||||||
|
ElMessage.success('保存成功')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const getRuleList = () => {
|
||||||
|
getComDicts({ zdlb: 'ZKGZ' }).then((res: any) => {
|
||||||
|
tableData.value = res.data || []
|
||||||
|
|
||||||
|
queryValue({ yq: yq.value, xxdh: 'QCRULES' }).then((res: any) => {
|
||||||
|
tableRef.value.clearSelection()
|
||||||
|
|
||||||
|
if (res.data) {
|
||||||
|
const arr = res.data.split(',')
|
||||||
|
arr.forEach((item: any) => {
|
||||||
|
const index = tableData.value.findIndex((val: any) => val.zddh === item)
|
||||||
|
if (index > -1) {
|
||||||
|
tableRef.value.toggleRowSelection(tableData.value[index], true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(yq, (val) => {
|
||||||
|
if (val) {
|
||||||
|
getRuleList()
|
||||||
|
}
|
||||||
|
}, { immediate: true })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.table-row {
|
||||||
|
height: calc(100% - 135px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tips {
|
||||||
|
font-family: SourceHanSansCN, SourceHanSansCN;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #409eff;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
348
src/views/liswork/qualityControl/index.vue
Normal file
348
src/views/liswork/qualityControl/index.vue
Normal file
@ -0,0 +1,348 @@
|
|||||||
|
/**
|
||||||
|
* @file index.vue 质控
|
||||||
|
* @author: w
|
||||||
|
* @since: 2025-12-29
|
||||||
|
*/
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<el-row :gutter="20" class="main-row">
|
||||||
|
<!-- 左侧树区域 -->
|
||||||
|
<el-col :span="4" :xs="24" class="tree-col">
|
||||||
|
<el-input v-model="deptName" placeholder="请输入仪器名称" clearable prefix-icon="Search" class="tree-search-input" />
|
||||||
|
<el-radio-group v-model="radiotype" @change="radioChange">
|
||||||
|
<el-radio value="1" size="large">仪器组</el-radio>
|
||||||
|
<el-radio value="2" size="large">所有仪器</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
<div class="tree-container">
|
||||||
|
<el-tree :data="deptOptions" :props="treeProps" expand-on-click-node :filter-node-method="filterNode"
|
||||||
|
ref="deptTreeRef" node-key="id" highlight-current :expanded-keys="expandedKeys"
|
||||||
|
@node-click="handleNodeClick">
|
||||||
|
<!-- 自定义树节点模板:新增newflag徽章 -->
|
||||||
|
<template #default="{ node, data }">
|
||||||
|
<span class="tree-node-content">
|
||||||
|
{{ node.label }}
|
||||||
|
<!-- 新节点徽章:newflag=1时显示黄色"新"标 -->
|
||||||
|
<el-badge v-if="data.newflag === '1'" value="new" class="new-node-badge" />
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-tree>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
|
<!-- 右侧列表区域 -->
|
||||||
|
<el-col :span="20" :xs="24" class="table-col">
|
||||||
|
<el-row :gutter="10" class="yq-title-row">
|
||||||
|
<h1 class="yq-title">仪器: {{ yqmc }}</h1>
|
||||||
|
</el-row>
|
||||||
|
<el-tabs v-model="activeName" type="card" class="tabs_box">
|
||||||
|
<el-tab-pane v-for="tab in tabList" :key="tab.key" :label="tab.label" :name="tab.key" />
|
||||||
|
</el-tabs>
|
||||||
|
<!-- 动态组件渲染 -->
|
||||||
|
<component :is="currentComponent" :key="activeName" v-model="queryParams.yq" @updateTree="getDeptTree" />
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="ComOpt" lang="ts">
|
||||||
|
import { ref, watch, nextTick, computed } from 'vue'
|
||||||
|
import { ElMessage, ElTree, TreeNode, ElMessageBox, ElBadge } from 'element-plus'
|
||||||
|
import { paramList, paramTree, getdef, updateParam } from '@/api/liswork/xtwh/ComOpt'
|
||||||
|
import { getFirstLetter } from '@/utils/pinyin'
|
||||||
|
import { formatDict, getDictData, dictData } from '@/hooks';
|
||||||
|
import SetRule from './components/setRule/index.vue';
|
||||||
|
import SetProject from './components/setProject/index.vue';
|
||||||
|
import SetParams from './components/setParams/index.vue';
|
||||||
|
// ========== TypeScript 类型定义 ==========
|
||||||
|
interface InstrTreeNode {
|
||||||
|
id: string
|
||||||
|
label: string
|
||||||
|
lx: string
|
||||||
|
newflag: string // 新增newflag字段
|
||||||
|
children?: InstrTreeNode[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ParamTreeResponse {
|
||||||
|
code: string
|
||||||
|
msg: string
|
||||||
|
data: InstrTreeNode[]
|
||||||
|
url: null | string
|
||||||
|
method: null | string
|
||||||
|
params: null | any
|
||||||
|
problemId: null | string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface QueryParams {
|
||||||
|
yq?: string | undefined
|
||||||
|
xxdh?: string | undefined
|
||||||
|
xxqz?: string | undefined
|
||||||
|
groupId?: string | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const radiotype = ref<string>("1")
|
||||||
|
// ========== 响应式变量 ==========
|
||||||
|
// 树相关
|
||||||
|
const deptTreeRef = ref<InstanceType<typeof ElTree>>()
|
||||||
|
const deptOptions = ref<InstrTreeNode[]>([])
|
||||||
|
const deptName = ref<string>("")
|
||||||
|
const yqmc = ref<string>("")
|
||||||
|
const expandedKeys = ref<string[]>([])
|
||||||
|
const isFiltering = ref<boolean>(false)
|
||||||
|
const treeProps = ref({
|
||||||
|
label: 'label',
|
||||||
|
children: 'children',
|
||||||
|
isLeaf: (data: Record<string, any>) => !data.children || data.children.length === 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// 查询参数
|
||||||
|
const queryParams = ref<QueryParams>({
|
||||||
|
yq: undefined,
|
||||||
|
xxdh: undefined,
|
||||||
|
xxqz: undefined,
|
||||||
|
groupId: undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
const tabList = [
|
||||||
|
{ key: 'SetRule', label: '质控规则设定', component: SetRule },
|
||||||
|
{ key: 'SetProject', label: '质控项目设定', component: SetProject },
|
||||||
|
{ key: 'SetParams', label: '质控品参数设定', component: SetParams }
|
||||||
|
]
|
||||||
|
const activeName = ref('SetParams')
|
||||||
|
|
||||||
|
// 计算当前要渲染的组件
|
||||||
|
const currentComponent = computed(() => {
|
||||||
|
return tabList.find(tab => tab.key === activeName.value)?.component || 'SetRule'
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const treeData = ref<InstrTreeNode[]>([])
|
||||||
|
const radioChange = (val: string) => {
|
||||||
|
if (val == "1") {
|
||||||
|
deptOptions.value = treeData.value
|
||||||
|
} else {
|
||||||
|
const result = [];
|
||||||
|
// 遍历外层数组的每个分组
|
||||||
|
for (let i = 0; i < treeData.value.length; i++) {
|
||||||
|
const group = treeData.value[i];
|
||||||
|
if (group.children && Array.isArray(group.children)) {
|
||||||
|
for (let j = 0; j < group.children.length; j++) {
|
||||||
|
result.push(group.children[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deptOptions.value = result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 树操作核心方法 ==========
|
||||||
|
const forceExpandSingleNode = (nodeId: string): void => {
|
||||||
|
expandedKeys.value = []
|
||||||
|
nextTick(() => {
|
||||||
|
expandedKeys.value = [nodeId]
|
||||||
|
// deptTreeRef.value?.expand(nodeId, false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleNodeClick = (data: InstrTreeNode): void => {
|
||||||
|
if (isFiltering.value) {
|
||||||
|
queryParams.value.groupId = data.id === "-1" ? undefined : data.id
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.children && data.children.length > 0) {
|
||||||
|
const isExpanded = expandedKeys.value.includes(data.id)
|
||||||
|
expandedKeys.value = isExpanded ? [] : [data.id]
|
||||||
|
!isExpanded && forceExpandSingleNode(data.id)
|
||||||
|
} else {
|
||||||
|
queryParams.value.yq = data.id
|
||||||
|
yqmc.value = data.label
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ========== 检索过滤逻辑 ==========
|
||||||
|
const filterNode = (value: string, data: any, _node: TreeNode): boolean => {
|
||||||
|
if (!value) return true
|
||||||
|
|
||||||
|
const keyword = value.trim().toUpperCase()
|
||||||
|
const nodeLabel = data.label?.toLowerCase() || ''
|
||||||
|
const nodePinyinFirst = data.label ? getFirstLetter(data.label).toUpperCase() : ''
|
||||||
|
|
||||||
|
const currentMatch = nodeLabel.includes(keyword.toLowerCase()) || nodePinyinFirst.includes(keyword)
|
||||||
|
const childrenMatch = (data.children || []).some((child: any) => filterNode(value, child, _node))
|
||||||
|
return currentMatch || childrenMatch
|
||||||
|
}
|
||||||
|
|
||||||
|
const debounceFilter = (() => {
|
||||||
|
let timer: number | undefined
|
||||||
|
return (val: string): void => {
|
||||||
|
clearTimeout(timer)
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
isFiltering.value = true
|
||||||
|
deptTreeRef.value?.filter(val)
|
||||||
|
|
||||||
|
if (val) {
|
||||||
|
expandedKeys.value = []
|
||||||
|
const expandMatched = (nodes: InstrTreeNode[]) => {
|
||||||
|
nodes.forEach(node => {
|
||||||
|
if (filterNode(val, node, {} as TreeNode) && node.children?.length) {
|
||||||
|
expandedKeys.value.push(node.id)
|
||||||
|
}
|
||||||
|
node.children && expandMatched(node.children)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
expandMatched(deptOptions.value)
|
||||||
|
} else {
|
||||||
|
expandedKeys.value = []
|
||||||
|
}
|
||||||
|
isFiltering.value = false
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
watch(deptName, debounceFilter, { immediate: false })
|
||||||
|
|
||||||
|
// ========== 业务逻辑 ==========
|
||||||
|
const getDeptTree = async (): Promise<void> => {
|
||||||
|
try {
|
||||||
|
const response = await paramTree() as ParamTreeResponse
|
||||||
|
if (response.code === "0" && response.data) {
|
||||||
|
treeData.value = response.data
|
||||||
|
radioChange('1')
|
||||||
|
if (!expandedKeys.value.length) {
|
||||||
|
// 默认展开第一个一级节点的子级
|
||||||
|
nextTick(() => {
|
||||||
|
if (deptOptions.value.length > 0) {
|
||||||
|
const firstLevelNode = deptOptions.value[0]
|
||||||
|
if (firstLevelNode && firstLevelNode.children && firstLevelNode.children.length > 0) {
|
||||||
|
expandedKeys.value = [firstLevelNode.id]
|
||||||
|
const firstChildNode = firstLevelNode.children[0]
|
||||||
|
queryParams.value.yq = firstChildNode.id
|
||||||
|
yqmc.value = firstChildNode.label
|
||||||
|
|
||||||
|
deptTreeRef.value?.setCurrentKey(firstChildNode.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ElMessage.warning(response.msg || "获取树数据失败")
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error(`获取树数据异常:${(error as Error).message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
})
|
||||||
|
// ========== 初始化 ==========
|
||||||
|
getDeptTree()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.app-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 90vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-row {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-col {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
|
||||||
|
.tree-container {
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e6e6e6;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 8px;
|
||||||
|
|
||||||
|
// 核心:树选中节点样式
|
||||||
|
:deep(.el-tree) {
|
||||||
|
--el-tree-node-transition-duration: 0.2s;
|
||||||
|
|
||||||
|
.el-tree-node__children {
|
||||||
|
transition: all var(--el-tree-node-transition-duration) ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-tree-node__expand-icon.is-leaf {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选中节点的背景色(蓝色)+ 文字白色
|
||||||
|
.el-tree-node.is-current>.el-tree-node__content {
|
||||||
|
background-color: #409eff !important;
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选中节点hover样式(加深蓝色)
|
||||||
|
.el-tree-node.is-current>.el-tree-node__content:hover {
|
||||||
|
background-color: #1e88e5 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 树节点内容容器样式
|
||||||
|
.tree-node-content {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px; // 徽章和文字间距
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新节点徽章样式
|
||||||
|
.new-node-badge {
|
||||||
|
--el-badge-font-size: 10px !important;
|
||||||
|
--el-badge-height: 16px !important;
|
||||||
|
--el-badge-padding: 0 4px !important;
|
||||||
|
--el-badge-warning-color: #FFEC24 !important; // 黄色徽章
|
||||||
|
--el-badge-warning-bg-color: #fff7e6 !important; // 浅黄色背景
|
||||||
|
--el-badge-border-radius: 2px !important; // 圆角优化
|
||||||
|
|
||||||
|
// 徽章文字样式
|
||||||
|
.el-badge__content {
|
||||||
|
font-size: 10px !important;
|
||||||
|
line-height: 16px !important;
|
||||||
|
padding: 0 4px !important;
|
||||||
|
height: 16px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-col {
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
// 仪器标题行样式
|
||||||
|
.yq-title-row {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
|
||||||
|
.yq-title {
|
||||||
|
font-family: SourceHanSansCN, SourceHanSansCN;
|
||||||
|
font-size: 16px; // 缩小h1字体(默认24px→16px)
|
||||||
|
color: #409eff; // 蓝色(Element Plus主题色)
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0; // 清除默认margin
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
x
Reference in New Issue
Block a user