tuqin
2026-10-29 ae6a06dbd59c02f10e73a0df4eb7cbde4bddcb2c
feat(LIMS): 新增资源管理、系统配置页面

资源管理:仪器设备台账 + 校准维护记录弹窗;试剂耗材台账 + 采购入库/领用出库(自动回写库存并写出入库流水)。
系统配置:检测项目库 / 计量单位库 / 判定标准库,判定标准可关联检测项目并带出单位。
已添加9个文件
1299 ■■■■■ 文件已修改
src/api/lims/instrument.js 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/lims/instrumentCalibration.js 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/lims/judgmentStandard.js 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/lims/reagent.js 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/lims/reagentRecord.js 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/lims/testItem.js 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/lims/unit.js 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/lims/resourceManagement/index.vue 535 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/lims/systemConfig/index.vue 442 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/lims/instrument.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,46 @@
// ä»ªå™¨è®¾å¤‡æŽ¥å£
import request from '@/utils/request'
// åˆ†é¡µæŸ¥è¯¢ä»ªå™¨è®¾å¤‡
export function listInstrument(query) {
    return request({
        url: '/lims/instrument/listPage',
        method: 'get',
        params: query
    })
}
// æŸ¥è¯¢ä»ªå™¨è®¾å¤‡è¯¦ç»†
export function getInstrument(id) {
    return request({
        url: '/lims/instrument/' + id,
        method: 'get'
    })
}
// æ–°å¢žä»ªå™¨è®¾å¤‡
export function addInstrument(data) {
    return request({
        url: '/lims/instrument/add',
        method: 'post',
        data: data
    })
}
// ä¿®æ”¹ä»ªå™¨è®¾å¤‡
export function updateInstrument(data) {
    return request({
        url: '/lims/instrument/update',
        method: 'post',
        data: data
    })
}
// åˆ é™¤ä»ªå™¨è®¾å¤‡
export function delInstrument(ids) {
    return request({
        url: '/lims/instrument/delete',
        method: 'delete',
        data: ids
    })
}
src/api/lims/instrumentCalibration.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,46 @@
// æ ¡å‡†è®°å½•接口
import request from '@/utils/request'
// åˆ†é¡µæŸ¥è¯¢æ ¡å‡†è®°å½•
export function listInstrumentCalibration(query) {
    return request({
        url: '/lims/instrumentCalibration/listPage',
        method: 'get',
        params: query
    })
}
// æŸ¥è¯¢æ ¡å‡†è®°å½•详细
export function getInstrumentCalibration(id) {
    return request({
        url: '/lims/instrumentCalibration/' + id,
        method: 'get'
    })
}
// æ–°å¢žæ ¡å‡†è®°å½•
export function addInstrumentCalibration(data) {
    return request({
        url: '/lims/instrumentCalibration/add',
        method: 'post',
        data: data
    })
}
// ä¿®æ”¹æ ¡å‡†è®°å½•
export function updateInstrumentCalibration(data) {
    return request({
        url: '/lims/instrumentCalibration/update',
        method: 'post',
        data: data
    })
}
// åˆ é™¤æ ¡å‡†è®°å½•
export function delInstrumentCalibration(ids) {
    return request({
        url: '/lims/instrumentCalibration/delete',
        method: 'delete',
        data: ids
    })
}
src/api/lims/judgmentStandard.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,46 @@
// åˆ¤å®šæ ‡å‡†æŽ¥å£
import request from '@/utils/request'
// åˆ†é¡µæŸ¥è¯¢åˆ¤å®šæ ‡å‡†
export function listJudgmentStandard(query) {
    return request({
        url: '/lims/judgmentStandard/listPage',
        method: 'get',
        params: query
    })
}
// æŸ¥è¯¢åˆ¤å®šæ ‡å‡†è¯¦ç»†
export function getJudgmentStandard(id) {
    return request({
        url: '/lims/judgmentStandard/' + id,
        method: 'get'
    })
}
// æ–°å¢žåˆ¤å®šæ ‡å‡†
export function addJudgmentStandard(data) {
    return request({
        url: '/lims/judgmentStandard/add',
        method: 'post',
        data: data
    })
}
// ä¿®æ”¹åˆ¤å®šæ ‡å‡†
export function updateJudgmentStandard(data) {
    return request({
        url: '/lims/judgmentStandard/update',
        method: 'post',
        data: data
    })
}
// åˆ é™¤åˆ¤å®šæ ‡å‡†
export function delJudgmentStandard(ids) {
    return request({
        url: '/lims/judgmentStandard/delete',
        method: 'delete',
        data: ids
    })
}
src/api/lims/reagent.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,46 @@
// è¯•剂耗材接口
import request from '@/utils/request'
// åˆ†é¡µæŸ¥è¯¢è¯•剂耗材
export function listReagent(query) {
    return request({
        url: '/lims/reagent/listPage',
        method: 'get',
        params: query
    })
}
// æŸ¥è¯¢è¯•剂耗材详细
export function getReagent(id) {
    return request({
        url: '/lims/reagent/' + id,
        method: 'get'
    })
}
// æ–°å¢žè¯•剂耗材
export function addReagent(data) {
    return request({
        url: '/lims/reagent/add',
        method: 'post',
        data: data
    })
}
// ä¿®æ”¹è¯•剂耗材
export function updateReagent(data) {
    return request({
        url: '/lims/reagent/update',
        method: 'post',
        data: data
    })
}
// åˆ é™¤è¯•剂耗材
export function delReagent(ids) {
    return request({
        url: '/lims/reagent/delete',
        method: 'delete',
        data: ids
    })
}
src/api/lims/reagentRecord.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,46 @@
// å‡ºå…¥åº“流水接口
import request from '@/utils/request'
// åˆ†é¡µæŸ¥è¯¢å‡ºå…¥åº“流水
export function listReagentRecord(query) {
    return request({
        url: '/lims/reagentRecord/listPage',
        method: 'get',
        params: query
    })
}
// æŸ¥è¯¢å‡ºå…¥åº“流水详细
export function getReagentRecord(id) {
    return request({
        url: '/lims/reagentRecord/' + id,
        method: 'get'
    })
}
// æ–°å¢žå‡ºå…¥åº“流水
export function addReagentRecord(data) {
    return request({
        url: '/lims/reagentRecord/add',
        method: 'post',
        data: data
    })
}
// ä¿®æ”¹å‡ºå…¥åº“流水
export function updateReagentRecord(data) {
    return request({
        url: '/lims/reagentRecord/update',
        method: 'post',
        data: data
    })
}
// åˆ é™¤å‡ºå…¥åº“流水
export function delReagentRecord(ids) {
    return request({
        url: '/lims/reagentRecord/delete',
        method: 'delete',
        data: ids
    })
}
src/api/lims/testItem.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,46 @@
// æ£€æµ‹é¡¹ç›®æŽ¥å£
import request from '@/utils/request'
// åˆ†é¡µæŸ¥è¯¢æ£€æµ‹é¡¹ç›®
export function listTestItem(query) {
    return request({
        url: '/lims/testItem/listPage',
        method: 'get',
        params: query
    })
}
// æŸ¥è¯¢æ£€æµ‹é¡¹ç›®è¯¦ç»†
export function getTestItem(id) {
    return request({
        url: '/lims/testItem/' + id,
        method: 'get'
    })
}
// æ–°å¢žæ£€æµ‹é¡¹ç›®
export function addTestItem(data) {
    return request({
        url: '/lims/testItem/add',
        method: 'post',
        data: data
    })
}
// ä¿®æ”¹æ£€æµ‹é¡¹ç›®
export function updateTestItem(data) {
    return request({
        url: '/lims/testItem/update',
        method: 'post',
        data: data
    })
}
// åˆ é™¤æ£€æµ‹é¡¹ç›®
export function delTestItem(ids) {
    return request({
        url: '/lims/testItem/delete',
        method: 'delete',
        data: ids
    })
}
src/api/lims/unit.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,46 @@
// è®¡é‡å•位接口
import request from '@/utils/request'
// åˆ†é¡µæŸ¥è¯¢è®¡é‡å•位
export function listUnit(query) {
    return request({
        url: '/lims/unit/listPage',
        method: 'get',
        params: query
    })
}
// æŸ¥è¯¢è®¡é‡å•位详细
export function getUnit(id) {
    return request({
        url: '/lims/unit/' + id,
        method: 'get'
    })
}
// æ–°å¢žè®¡é‡å•位
export function addUnit(data) {
    return request({
        url: '/lims/unit/add',
        method: 'post',
        data: data
    })
}
// ä¿®æ”¹è®¡é‡å•位
export function updateUnit(data) {
    return request({
        url: '/lims/unit/update',
        method: 'post',
        data: data
    })
}
// åˆ é™¤è®¡é‡å•位
export function delUnit(ids) {
    return request({
        url: '/lims/unit/delete',
        method: 'delete',
        data: ids
    })
}
src/views/lims/resourceManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,535 @@
<template>
  <div class="app-container">
    <el-tabs v-model="activeTab" type="border-card">
      <!-- ==================== Tab1: ä»ªå™¨è®¾å¤‡ ==================== -->
      <el-tab-pane label="仪器设备" name="instrument">
        <div class="search_form">
          <div>
            <span class="search_title">仪器编号:</span>
            <el-input v-model="insSearch.instrumentCode" style="width: 180px" placeholder="请输入" clearable @change="loadInstrument" />
            <span class="search_title" style="margin-left: 16px">仪器名称:</span>
            <el-input v-model="insSearch.instrumentName" style="width: 180px" placeholder="请输入" clearable @change="loadInstrument" />
            <span class="search_title" style="margin-left: 16px">状态:</span>
            <el-select v-model="insSearch.status" style="width: 140px" placeholder="全部" clearable @change="loadInstrument">
              <el-option label="正常" value="normal" />
              <el-option label="维修中" value="maintenance" />
              <el-option label="闲置" value="idle" />
              <el-option label="已报废" value="scrapped" />
            </el-select>
            <el-button type="primary" style="margin-left: 10px" @click="loadInstrument">搜索</el-button>
          </div>
          <div>
            <el-button type="primary" @click="openInsForm('add')">新增仪器</el-button>
            <el-button type="success" plain @click="openCalDialog">校准记录</el-button>
            <el-button type="danger" plain @click="delInstrument">删除</el-button>
          </div>
        </div>
        <div class="table_list">
          <PIMTable
            rowKey="id"
            :column="insColumns"
            :tableData="insData"
            :page="insPage"
            :isSelection="true"
            :tableLoading="insLoading"
            @selection-change="(v) => (insSelected = v)"
            @pagination="paginationIns"
          />
        </div>
      </el-tab-pane>
      <!-- ==================== Tab2: è¯•剂耗材 ==================== -->
      <el-tab-pane label="试剂耗材" name="reagent">
        <div class="search_form">
          <div>
            <span class="search_title">编号:</span>
            <el-input v-model="reSearch.reagentCode" style="width: 180px" placeholder="请输入" clearable @change="loadReagent" />
            <span class="search_title" style="margin-left: 16px">名称:</span>
            <el-input v-model="reSearch.reagentName" style="width: 180px" placeholder="请输入" clearable @change="loadReagent" />
            <span class="search_title" style="margin-left: 16px">类别:</span>
            <el-select v-model="reSearch.category" style="width: 140px" placeholder="全部" clearable @change="loadReagent">
              <el-option label="试剂" value="reagent" />
              <el-option label="耗材" value="consumable" />
              <el-option label="标准品" value="standard" />
              <el-option label="质控品" value="control" />
            </el-select>
            <el-button type="primary" style="margin-left: 10px" @click="loadReagent">搜索</el-button>
          </div>
          <div>
            <el-button type="primary" @click="openReForm('add')">新增试剂/耗材</el-button>
            <el-button type="success" plain @click="openStockDialog('in')">采购入库</el-button>
            <el-button type="warning" plain @click="openStockDialog('out')">领用出库</el-button>
            <el-button type="danger" plain @click="delReagent">删除</el-button>
          </div>
        </div>
        <div class="table_list">
          <PIMTable
            rowKey="id"
            :column="reColumns"
            :tableData="reData"
            :page="rePage"
            :isSelection="true"
            :tableLoading="reLoading"
            @selection-change="(v) => (reSelected = v)"
            @pagination="paginationRe"
          />
        </div>
      </el-tab-pane>
    </el-tabs>
    <!-- ==================== ä»ªå™¨è¡¨å• ==================== -->
    <el-dialog v-model="insDialogVisible" :title="insOpType === 'add' ? '新增仪器' : '编辑仪器'" width="70%">
      <el-form ref="insFormRef" :model="insForm" label-position="top" :rules="insRules">
        <el-row :gutter="24">
          <el-col :span="12"><el-form-item label="仪器编号" prop="instrumentCode"><el-input v-model="insForm.instrumentCode" placeholder="请输入" /></el-form-item></el-col>
          <el-col :span="12"><el-form-item label="仪器名称" prop="instrumentName"><el-input v-model="insForm.instrumentName" placeholder="请输入" /></el-form-item></el-col>
          <el-col :span="12"><el-form-item label="规格型号"><el-input v-model="insForm.instrumentModel" placeholder="请输入" /></el-form-item></el-col>
          <el-col :span="12"><el-form-item label="生产厂商"><el-input v-model="insForm.manufacturer" placeholder="请输入" /></el-form-item></el-col>
          <el-col :span="12"><el-form-item label="出厂序列号"><el-input v-model="insForm.serialNo" placeholder="请输入" /></el-form-item></el-col>
          <el-col :span="12"><el-form-item label="存放位置"><el-input v-model="insForm.location" placeholder="请输入" /></el-form-item></el-col>
          <el-col :span="12"><el-form-item label="购置日期"><el-date-picker v-model="insForm.purchaseDate" type="date" value-format="YYYY-MM-DD" style="width: 100%" /></el-form-item></el-col>
          <el-col :span="12"><el-form-item label="保修截止"><el-date-picker v-model="insForm.warrantyEndDate" type="date" value-format="YYYY-MM-DD" style="width: 100%" /></el-form-item></el-col>
          <el-col :span="12">
            <el-form-item label="状态">
              <el-select v-model="insForm.status" style="width: 100%">
                <el-option label="正常" value="normal" /><el-option label="维修中" value="maintenance" />
                <el-option label="闲置" value="idle" /><el-option label="已报废" value="scrapped" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12"><el-form-item label="责任人"><el-input v-model="insForm.responsiblePerson" placeholder="请输入" /></el-form-item></el-col>
          <el-col :span="24"><el-form-item label="备注"><el-input v-model="insForm.remark" type="textarea" :rows="2" /></el-form-item></el-col>
        </el-row>
      </el-form>
      <template #footer>
        <el-button @click="insDialogVisible = false">取消</el-button>
        <el-button type="primary" @click="submitIns">确定</el-button>
      </template>
    </el-dialog>
    <!-- ==================== è¯•剂表单 ==================== -->
    <el-dialog v-model="reDialogVisible" :title="reOpType === 'add' ? '新增试剂/耗材' : '编辑试剂/耗材'" width="70%">
      <el-form ref="reFormRef" :model="reForm" label-position="top" :rules="reRules">
        <el-row :gutter="24">
          <el-col :span="12"><el-form-item label="编号" prop="reagentCode"><el-input v-model="reForm.reagentCode" placeholder="请输入" /></el-form-item></el-col>
          <el-col :span="12"><el-form-item label="名称" prop="reagentName"><el-input v-model="reForm.reagentName" placeholder="请输入" /></el-form-item></el-col>
          <el-col :span="12"><el-form-item label="规格"><el-input v-model="reForm.specification" placeholder="请输入" /></el-form-item></el-col>
          <el-col :span="12">
            <el-form-item label="类别">
              <el-select v-model="reForm.category" style="width: 100%">
                <el-option label="试剂" value="reagent" /><el-option label="耗材" value="consumable" />
                <el-option label="标准品" value="standard" /><el-option label="质控品" value="control" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12"><el-form-item label="计量单位"><el-input v-model="reForm.unit" placeholder="如 g / mL / ç“¶" /></el-form-item></el-col>
          <el-col :span="12"><el-form-item label="供应商"><el-input v-model="reForm.supplier" placeholder="请输入" /></el-form-item></el-col>
          <el-col :span="12"><el-form-item label="存储条件"><el-input v-model="reForm.storageCondition" placeholder="如 2-8℃避光" /></el-form-item></el-col>
          <el-col :span="12"><el-form-item label="存放位置"><el-input v-model="reForm.location" placeholder="请输入" /></el-form-item></el-col>
          <el-col :span="8"><el-form-item label="当前库存"><el-input-number v-model="reForm.currentStock" :precision="4" :min="0" style="width: 100%" /></el-form-item></el-col>
          <el-col :span="8"><el-form-item label="安全库存"><el-input-number v-model="reForm.safetyStock" :precision="4" :min="0" style="width: 100%" /></el-form-item></el-col>
          <el-col :span="8"><el-form-item label="有效期至"><el-date-picker v-model="reForm.expiryDate" type="date" value-format="YYYY-MM-DD" style="width: 100%" /></el-form-item></el-col>
          <el-col :span="24"><el-form-item label="备注"><el-input v-model="reForm.remark" type="textarea" :rows="2" /></el-form-item></el-col>
        </el-row>
      </el-form>
      <template #footer>
        <el-button @click="reDialogVisible = false">取消</el-button>
        <el-button type="primary" @click="submitRe">确定</el-button>
      </template>
    </el-dialog>
    <!-- ==================== å‡ºå…¥åº“弹窗 ==================== -->
    <el-dialog v-model="stockDialogVisible" :title="stockType === 'in' ? '采购入库' : '领用出库'" width="560px">
      <el-form :model="stockForm" label-position="top">
        <el-form-item label="试剂/耗材">
          <el-select v-model="stockForm.reagentId" style="width: 100%" placeholder="请选择" filterable @change="onStockReagentChange">
            <el-option v-for="r in reOptions" :key="r.id" :label="`${r.reagentName}(库存 ${r.currentStock ?? 0}${r.unit || ''})`" :value="r.id" />
          </el-select>
        </el-form-item>
        <el-row :gutter="16">
          <el-col :span="12"><el-form-item label="数量"><el-input-number v-model="stockForm.quantity" :precision="4" :min="0.0001" style="width: 100%" /></el-form-item></el-col>
          <el-col :span="12"><el-form-item label="批次号"><el-input v-model="stockForm.batchNo" /></el-form-item></el-col>
        </el-row>
        <el-row :gutter="16" v-if="stockType === 'in'">
          <el-col :span="12"><el-form-item label="供应商"><el-input v-model="stockForm.supplier" /></el-form-item></el-col>
          <el-col :span="12"><el-form-item label="单价"><el-input-number v-model="stockForm.unitPrice" :precision="4" :min="0" style="width: 100%" /></el-form-item></el-col>
        </el-row>
        <el-form-item v-if="stockType === 'out'" label="用途/领用事由">
          <el-input v-model="stockForm.purpose" placeholder="如:样品前处理" />
        </el-form-item>
        <el-form-item label="备注"><el-input v-model="stockForm.remark" type="textarea" :rows="2" /></el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="stockDialogVisible = false">取消</el-button>
        <el-button type="primary" @click="submitStock">确定</el-button>
      </template>
    </el-dialog>
    <!-- ==================== æ ¡å‡†è®°å½•弹窗 ==================== -->
    <el-dialog v-model="calDialogVisible" title="仪器校准 / ç»´æŠ¤è®°å½•" width="80%">
      <div style="margin-bottom: 12px; display: flex; justify-content: space-between">
        <el-select v-model="calInstrumentId" style="width: 320px" placeholder="选择仪器查看记录" filterable @change="loadCalibration">
          <el-option v-for="i in insOptions" :key="i.id" :label="`${i.instrumentName}(${i.instrumentCode})`" :value="i.id" />
        </el-select>
        <el-button type="primary" @click="openCalForm('add')" :disabled="!calInstrumentId">新增记录</el-button>
      </div>
      <el-table :data="calData" border size="small" max-height="420">
        <el-table-column prop="recordType" label="类型" width="100">
          <template #default="{ row }">{{ recordTypeText(row.recordType) }}</template>
        </el-table-column>
        <el-table-column prop="calibrationDate" label="本次日期" width="110" />
        <el-table-column prop="nextCalibrationDate" label="下次到期" width="110" />
        <el-table-column prop="calibrationOrg" label="机构" show-overflow-tooltip />
        <el-table-column prop="certificateNo" label="证书编号" width="140" show-overflow-tooltip />
        <el-table-column prop="calibrationResult" label="结果" width="90">
          <template #default="{ row }">
            <el-tag :type="row.calibrationResult === 'pass' ? 'success' : row.calibrationResult === 'fail' ? 'danger' : 'warning'" size="small">
              {{ resultText(row.calibrationResult) }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="cost" label="费用" width="100" />
        <el-table-column prop="executor" label="执行人" width="100" />
        <el-table-column label="操作" width="120" align="center">
          <template #default="{ row }">
            <el-button link type="primary" @click="openCalForm('edit', row)">编辑</el-button>
            <el-button link type="danger" @click="delCalibration(row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
    </el-dialog>
    <!-- ==================== æ ¡å‡†è®°å½•表单 ==================== -->
    <el-dialog v-model="calFormVisible" :title="calOpType === 'add' ? '新增校准记录' : '编辑校准记录'" width="600px">
      <el-form :model="calForm" label-position="top">
        <el-row :gutter="16">
          <el-col :span="12">
            <el-form-item label="类型">
              <el-select v-model="calForm.recordType" style="width: 100%">
                <el-option label="校准" value="calibration" /><el-option label="维护保养" value="maintenance" /><el-option label="ç»´ä¿®" value="repair" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12"><el-form-item label="本次日期"><el-date-picker v-model="calForm.calibrationDate" type="date" value-format="YYYY-MM-DD" style="width: 100%" /></el-form-item></el-col>
          <el-col :span="12"><el-form-item label="下次到期"><el-date-picker v-model="calForm.nextCalibrationDate" type="date" value-format="YYYY-MM-DD" style="width: 100%" /></el-form-item></el-col>
          <el-col :span="12"><el-form-item label="校准/维保机构"><el-input v-model="calForm.calibrationOrg" /></el-form-item></el-col>
          <el-col :span="12"><el-form-item label="证书编号"><el-input v-model="calForm.certificateNo" /></el-form-item></el-col>
          <el-col :span="12">
            <el-form-item label="结果">
              <el-select v-model="calForm.calibrationResult" style="width: 100%">
                <el-option label="合格" value="pass" /><el-option label="不合格" value="fail" /><el-option label="有条件通过" value="conditional" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12"><el-form-item label="费用"><el-input-number v-model="calForm.cost" :precision="2" :min="0" style="width: 100%" /></el-form-item></el-col>
          <el-col :span="12"><el-form-item label="执行人"><el-input v-model="calForm.executor" /></el-form-item></el-col>
          <el-col :span="24"><el-form-item label="备注"><el-input v-model="calForm.remark" type="textarea" :rows="2" /></el-form-item></el-col>
        </el-row>
      </el-form>
      <template #footer>
        <el-button @click="calFormVisible = false">取消</el-button>
        <el-button type="primary" @click="submitCal">确定</el-button>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted, getCurrentInstance } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import {
  listInstrument, getInstrument, addInstrument, updateInstrument, delInstrument as apiDelInstrument,
} from "@/api/lims/instrument";
import {
  listInstrumentCalibration, addInstrumentCalibration, updateInstrumentCalibration, delInstrumentCalibration,
} from "@/api/lims/instrumentCalibration";
import {
  listReagent, getReagent, addReagent, updateReagent, delReagent as apiDelReagent,
} from "@/api/lims/reagent";
import { addReagentRecord } from "@/api/lims/reagentRecord";
const { proxy } = getCurrentInstance();
const activeTab = ref("instrument");
/* ==================== ä»ªå™¨ ==================== */
const insSearch = ref({});
const insData = ref([]);
const insSelected = ref([]);
const insLoading = ref(false);
const insPage = reactive({ pageNum: 1, pageSize: 10, total: 0 });
const insOptions = ref([]);
const insColumns = ref([
  { label: "仪器编号", prop: "instrumentCode", width: 140 },
  { label: "仪器名称", prop: "instrumentName", width: 180 },
  { label: "规格型号", prop: "instrumentModel", width: 150 },
  { label: "生产厂商", prop: "manufacturer", width: 150 },
  { label: "存放位置", prop: "location", width: 130 },
  { label: "购置日期", prop: "purchaseDate", width: 110 },
  { label: "保修截止", prop: "warrantyEndDate", width: 110 },
  { label: "状态", prop: "status", width: 90 },
  {
    dataType: "action", label: "操作", align: "center", fixed: "right", width: 140,
    operation: [
      { name: "编辑", type: "text", clickFun: (row) => openInsForm("edit", row) },
      { name: "删除", type: "text", clickFun: (row) => doDelInstrument([row.id]) },
    ],
  },
]);
const loadInstrument = () => {
  insLoading.value = true;
  listInstrument({ ...insSearch.value, ...insPage }).then((res) => {
    insLoading.value = false;
    insData.value = res.data.records || [];
    insPage.total = res.data.total || 0;
    insOptions.value = insData.value;
  }).catch(() => (insLoading.value = false));
};
const paginationIns = (p) => { insPage.pageNum = p.pageNum; insPage.pageSize = p.pageSize; loadInstrument(); };
const insDialogVisible = ref(false);
const insOpType = ref("add");
const insFormRef = ref(null);
const insForm = ref({});
const insRules = {
  instrumentCode: [{ required: true, message: "请输入仪器编号", trigger: "blur" }],
  instrumentName: [{ required: true, message: "请输入仪器名称", trigger: "blur" }],
};
const openInsForm = (type, row) => {
  insOpType.value = type;
  insForm.value = { status: "normal" };
  if (type === "edit") {
    getInstrument(row.id).then((res) => { insForm.value = { ...res.data }; });
  }
  insDialogVisible.value = true;
};
const submitIns = () => {
  insFormRef.value.validate((valid) => {
    if (!valid) return;
    const fn = insOpType.value === "add" ? addInstrument : updateInstrument;
    fn(insForm.value).then(() => {
      ElMessage.success("保存成功");
      insDialogVisible.value = false;
      loadInstrument();
    });
  });
};
const doDelInstrument = (ids) => {
  ElMessageBox.confirm(`确认删除选中的 ${ids.length} æ¡ä»ªå™¨è®°å½•?`, "提示", { type: "warning" })
    .then(() => apiDelInstrument(ids).then(() => { ElMessage.success("删除成功"); loadInstrument(); }))
    .catch(() => {});
};
const delInstrument = () => {
  const ids = insSelected.value.map((r) => r.id);
  if (!ids.length) return ElMessage.warning("请先选择要删除的记录");
  doDelInstrument(ids);
};
/* ==================== æ ¡å‡†è®°å½• ==================== */
const calDialogVisible = ref(false);
const calInstrumentId = ref(null);
const calData = ref([]);
const calFormVisible = ref(false);
const calOpType = ref("add");
const calForm = ref({});
const openCalDialog = () => {
  if (!insOptions.value.length) return ElMessage.warning("请先新增仪器");
  calInstrumentId.value = insOptions.value[0].id;
  loadCalibration();
  calDialogVisible.value = true;
};
const loadCalibration = () => {
  if (!calInstrumentId.value) return;
  listInstrumentCalibration({ instrumentId: calInstrumentId.value, pageNum: 1, pageSize: 100 }).then((res) => {
    calData.value = res.data.records || [];
  });
};
const openCalForm = (type, row) => {
  calOpType.value = type;
  const ins = insOptions.value.find((i) => i.id === calInstrumentId.value) || {};
  calForm.value = type === "edit"
    ? { ...row }
    : { instrumentId: calInstrumentId.value, instrumentCode: ins.instrumentCode, instrumentName: ins.instrumentName, recordType: "calibration" };
  calFormVisible.value = true;
};
const submitCal = () => {
  const fn = calOpType.value === "add" ? addInstrumentCalibration : updateInstrumentCalibration;
  fn(calForm.value).then(() => {
    ElMessage.success("保存成功");
    calFormVisible.value = false;
    loadCalibration();
  });
};
const delCalibration = (row) => {
  ElMessageBox.confirm("确认删除这条记录?", "提示", { type: "warning" })
    .then(() => delInstrumentCalibration([row.id]).then(() => { ElMessage.success("删除成功"); loadCalibration(); }))
    .catch(() => {});
};
const recordTypeText = (t) => ({ calibration: "校准", maintenance: "维护保养", repair: "ç»´ä¿®" }[t] || t || "-");
const resultText = (t) => ({ pass: "合格", fail: "不合格", conditional: "有条件通过" }[t] || t || "-");
/* ==================== è¯•剂耗材 ==================== */
const reSearch = ref({});
const reData = ref([]);
const reSelected = ref([]);
const reLoading = ref(false);
const rePage = reactive({ pageNum: 1, pageSize: 10, total: 0 });
const reOptions = ref([]);
const reColumns = ref([
  { label: "编号", prop: "reagentCode", width: 130 },
  { label: "名称", prop: "reagentName", width: 180 },
  { label: "规格", prop: "specification", width: 130 },
  { label: "类别", prop: "category", width: 100 },
  { label: "当前库存", prop: "currentStock", width: 110 },
  { label: "安全库存", prop: "safetyStock", width: 110 },
  { label: "单位", prop: "unit", width: 80 },
  { label: "有效期至", prop: "expiryDate", width: 110 },
  { label: "存放位置", prop: "location", width: 130 },
  { label: "供应商", prop: "supplier", width: 150 },
  {
    dataType: "action", label: "操作", align: "center", fixed: "right", width: 140,
    operation: [
      { name: "编辑", type: "text", clickFun: (row) => openReForm("edit", row) },
      { name: "删除", type: "text", clickFun: (row) => doDelReagent([row.id]) },
    ],
  },
]);
const loadReagent = () => {
  reLoading.value = true;
  listReagent({ ...reSearch.value, ...rePage }).then((res) => {
    reLoading.value = false;
    reData.value = res.data.records || [];
    rePage.total = res.data.total || 0;
  }).catch(() => (reLoading.value = false));
};
const paginationRe = (p) => { rePage.pageNum = p.pageNum; rePage.pageSize = p.pageSize; loadReagent(); };
const loadReagentOptions = () => {
  listReagent({ pageNum: 1, pageSize: 500 }).then((res) => { reOptions.value = res.data.records || []; });
};
const reDialogVisible = ref(false);
const reOpType = ref("add");
const reFormRef = ref(null);
const reForm = ref({});
const reRules = {
  reagentCode: [{ required: true, message: "请输入编号", trigger: "blur" }],
  reagentName: [{ required: true, message: "请输入名称", trigger: "blur" }],
};
const openReForm = (type, row) => {
  reOpType.value = type;
  reForm.value = { category: "reagent", currentStock: 0, safetyStock: 0 };
  if (type === "edit") {
    getReagent(row.id).then((res) => { reForm.value = { ...res.data }; });
  }
  reDialogVisible.value = true;
};
const submitRe = () => {
  reFormRef.value.validate((valid) => {
    if (!valid) return;
    const fn = reOpType.value === "add" ? addReagent : updateReagent;
    fn(reForm.value).then(() => {
      ElMessage.success("保存成功");
      reDialogVisible.value = false;
      loadReagent();
      loadReagentOptions();
    });
  });
};
const doDelReagent = (ids) => {
  ElMessageBox.confirm(`确认删除选中的 ${ids.length} æ¡è®°å½•?`, "提示", { type: "warning" })
    .then(() => apiDelReagent(ids).then(() => { ElMessage.success("删除成功"); loadReagent(); loadReagentOptions(); }))
    .catch(() => {});
};
const delReagent = () => {
  const ids = reSelected.value.map((r) => r.id);
  if (!ids.length) return ElMessage.warning("请先选择要删除的记录");
  doDelReagent(ids);
};
/* ==================== å‡ºå…¥åº“ ==================== */
const stockDialogVisible = ref(false);
const stockType = ref("in");
const stockForm = ref({});
const stockReagent = ref(null);
const openStockDialog = (type) => {
  stockType.value = type;
  stockForm.value = { reagentId: null, quantity: null, batchNo: "" };
  stockReagent.value = null;
  loadReagentOptions();
  stockDialogVisible.value = true;
};
const onStockReagentChange = (id) => {
  stockReagent.value = reOptions.value.find((r) => r.id === id) || null;
};
const submitStock = () => {
  const f = stockForm.value;
  if (!f.reagentId) return ElMessage.warning("请选择试剂/耗材");
  if (!f.quantity || f.quantity <= 0) return ElMessage.warning("请输入正确数量");
  const re = stockReagent.value || {};
  const before = Number(re.currentStock || 0);
  const qty = Number(f.quantity);
  if (stockType.value === "out" && qty > before) {
    return ElMessage.warning(`库存不足,当前库存 ${before}`);
  }
  const after = stockType.value === "in" ? before + qty : before - qty;
  const payload = {
    ...f,
    reagentCode: re.reagentCode,
    reagentName: re.reagentName,
    recordType: stockType.value,
    beforeStock: before,
    afterStock: after,
    operateTime: formatNow(),
    totalAmount: f.unitPrice ? Number(f.unitPrice) * qty : null,
  };
  addReagentRecord(payload).then(() => {
    // åŒæ­¥æ›´æ–°å°è´¦åº“å­˜
    updateReagent({ id: re.id, currentStock: after }).then(() => {
      ElMessage.success(stockType.value === "in" ? "入库成功" : "出库成功");
      stockDialogVisible.value = false;
      loadReagent();
      loadReagentOptions();
    });
  });
};
const formatNow = () => {
  const d = new Date();
  const p = (n) => String(n).padStart(2, "0");
  return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
};
onMounted(() => {
  loadInstrument();
  loadReagent();
  loadReagentOptions();
});
</script>
<style scoped>
.search_form {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
  flex-wrap: wrap;
  gap: 8px;
}
.search_title {
  font-size: 14px;
  color: #606266;
}
.table_list {
  margin-top: 8px;
}
</style>
src/views/lims/systemConfig/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,442 @@
<template>
  <div class="app-container">
    <el-tabs v-model="activeTab" type="border-card">
      <!-- ==================== Tab1: æ£€æµ‹é¡¹ç›® ==================== -->
      <el-tab-pane label="检测项目库" name="testItem">
        <div class="search_form">
          <div>
            <span class="search_title">项目编号:</span>
            <el-input v-model="itemSearch.itemCode" style="width: 180px" placeholder="请输入" clearable @change="loadItem" />
            <span class="search_title" style="margin-left: 16px">项目名称:</span>
            <el-input v-model="itemSearch.itemName" style="width: 180px" placeholder="请输入" clearable @change="loadItem" />
            <span class="search_title" style="margin-left: 16px">类型:</span>
            <el-select v-model="itemSearch.itemType" style="width: 140px" placeholder="全部" clearable @change="loadItem">
              <el-option label="化学分析" value="chemical" /><el-option label="物理测试" value="physical" />
              <el-option label="微生物" value="microbial" /><el-option label="其它" value="other" />
            </el-select>
            <el-button type="primary" style="margin-left: 10px" @click="loadItem">搜索</el-button>
          </div>
          <div>
            <el-button type="primary" @click="openItemForm('add')">新增项目</el-button>
            <el-button type="danger" plain @click="delItem">删除</el-button>
          </div>
        </div>
        <PIMTable
          rowKey="id"
          :column="itemColumns"
          :tableData="itemData"
          :page="itemPage"
          :isSelection="true"
          :tableLoading="itemLoading"
          @selection-change="(v) => (itemSelected = v)"
          @pagination="paginationItem"
        />
      </el-tab-pane>
      <!-- ==================== Tab2: è®¡é‡å•位 ==================== -->
      <el-tab-pane label="计量单位库" name="unit">
        <div class="search_form">
          <div>
            <span class="search_title">单位编码:</span>
            <el-input v-model="unitSearch.unitCode" style="width: 160px" placeholder="请输入" clearable @change="loadUnit" />
            <span class="search_title" style="margin-left: 16px">单位名称:</span>
            <el-input v-model="unitSearch.unitName" style="width: 160px" placeholder="请输入" clearable @change="loadUnit" />
            <span class="search_title" style="margin-left: 16px">类型:</span>
            <el-select v-model="unitSearch.unitType" style="width: 150px" placeholder="全部" clearable @change="loadUnit">
              <el-option label="重量" value="weight" /><el-option label="体积" value="volume" />
              <el-option label="长度" value="length" /><el-option label="温度" value="temperature" />
              <el-option label="浓度" value="concentration" /><el-option label="计数" value="count" />
            </el-select>
            <el-button type="primary" style="margin-left: 10px" @click="loadUnit">搜索</el-button>
          </div>
          <div>
            <el-button type="primary" @click="openUnitForm('add')">新增单位</el-button>
            <el-button type="danger" plain @click="delUnit">删除</el-button>
          </div>
        </div>
        <PIMTable
          rowKey="id"
          :column="unitColumns"
          :tableData="unitData"
          :page="unitPage"
          :isSelection="true"
          :tableLoading="unitLoading"
          @selection-change="(v) => (unitSelected = v)"
          @pagination="paginationUnit"
        />
      </el-tab-pane>
      <!-- ==================== Tab3: åˆ¤å®šæ ‡å‡† ==================== -->
      <el-tab-pane label="判定标准库" name="standard">
        <div class="search_form">
          <div>
            <span class="search_title">标准编号:</span>
            <el-input v-model="stdSearch.standardCode" style="width: 180px" placeholder="请输入" clearable @change="loadStd" />
            <span class="search_title" style="margin-left: 16px">标准名称:</span>
            <el-input v-model="stdSearch.standardName" style="width: 180px" placeholder="请输入" clearable @change="loadStd" />
            <el-button type="primary" style="margin-left: 10px" @click="loadStd">搜索</el-button>
          </div>
          <div>
            <el-button type="primary" @click="openStdForm('add')">新增标准</el-button>
            <el-button type="danger" plain @click="delStd">删除</el-button>
          </div>
        </div>
        <PIMTable
          rowKey="id"
          :column="stdColumns"
          :tableData="stdData"
          :page="stdPage"
          :isSelection="true"
          :tableLoading="stdLoading"
          @selection-change="(v) => (stdSelected = v)"
          @pagination="paginationStd"
        />
      </el-tab-pane>
    </el-tabs>
    <!-- ==================== æ£€æµ‹é¡¹ç›®è¡¨å• ==================== -->
    <el-dialog v-model="itemDialogVisible" :title="itemOpType === 'add' ? '新增检测项目' : '编辑检测项目'" width="70%">
      <el-form ref="itemFormRef" :model="itemForm" label-position="top" :rules="itemRules">
        <el-row :gutter="24">
          <el-col :span="12"><el-form-item label="项目编号" prop="itemCode"><el-input v-model="itemForm.itemCode" placeholder="如 PH、COD、SS" /></el-form-item></el-col>
          <el-col :span="12"><el-form-item label="项目名称" prop="itemName"><el-input v-model="itemForm.itemName" placeholder="如 pH值、化学需氧量" /></el-form-item></el-col>
          <el-col :span="12">
            <el-form-item label="类型">
              <el-select v-model="itemForm.itemType" style="width: 100%">
                <el-option label="化学分析" value="chemical" /><el-option label="物理测试" value="physical" />
                <el-option label="微生物" value="microbial" /><el-option label="其它" value="other" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12"><el-form-item label="计量单位"><el-input v-model="itemForm.unit" placeholder="如 mg/L、%" /></el-form-item></el-col>
          <el-col :span="12"><el-form-item label="检测方法"><el-input v-model="itemForm.method" placeholder="如 çŽ»ç’ƒç”µæžæ³•" /></el-form-item></el-col>
          <el-col :span="12"><el-form-item label="执行标准"><el-input v-model="itemForm.standard" placeholder="如 GB/T 6920-1986" /></el-form-item></el-col>
          <el-col :span="12"><el-form-item label="合格下限"><el-input-number v-model="itemForm.limitMin" :precision="4" style="width: 100%" /></el-form-item></el-col>
          <el-col :span="12"><el-form-item label="合格上限"><el-input-number v-model="itemForm.limitMax" :precision="4" style="width: 100%" /></el-form-item></el-col>
          <el-col :span="24"><el-form-item label="备注"><el-input v-model="itemForm.remark" type="textarea" :rows="2" /></el-form-item></el-col>
        </el-row>
      </el-form>
      <template #footer>
        <el-button @click="itemDialogVisible = false">取消</el-button>
        <el-button type="primary" @click="submitItem">确定</el-button>
      </template>
    </el-dialog>
    <!-- ==================== è®¡é‡å•位表单 ==================== -->
    <el-dialog v-model="unitDialogVisible" :title="unitOpType === 'add' ? '新增计量单位' : '编辑计量单位'" width="600px">
      <el-form ref="unitFormRef" :model="unitForm" label-position="top" :rules="unitRules">
        <el-row :gutter="16">
          <el-col :span="12"><el-form-item label="单位编码" prop="unitCode"><el-input v-model="unitForm.unitCode" placeholder="如 mg" /></el-form-item></el-col>
          <el-col :span="12"><el-form-item label="单位名称" prop="unitName"><el-input v-model="unitForm.unitName" placeholder="如 æ¯«å…‹" /></el-form-item></el-col>
          <el-col :span="12">
            <el-form-item label="类型">
              <el-select v-model="unitForm.unitType" style="width: 100%">
                <el-option label="重量" value="weight" /><el-option label="体积" value="volume" />
                <el-option label="长度" value="length" /><el-option label="温度" value="temperature" />
                <el-option label="浓度" value="concentration" /><el-option label="计数" value="count" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12"><el-form-item label="基准单位"><el-input v-model="unitForm.baseUnit" placeholder="如 g(重量基准)" /></el-form-item></el-col>
          <el-col :span="12"><el-form-item label="换算系数"><el-input-number v-model="unitForm.conversionRate" :precision="6" :min="0" style="width: 100%" /></el-form-item></el-col>
          <el-col :span="24"><el-form-item label="备注"><el-input v-model="unitForm.remark" type="textarea" :rows="2" /></el-form-item></el-col>
        </el-row>
      </el-form>
      <template #footer>
        <el-button @click="unitDialogVisible = false">取消</el-button>
        <el-button type="primary" @click="submitUnit">确定</el-button>
      </template>
    </el-dialog>
    <!-- ==================== åˆ¤å®šæ ‡å‡†è¡¨å• ==================== -->
    <el-dialog v-model="stdDialogVisible" :title="stdOpType === 'add' ? '新增判定标准' : '编辑判定标准'" width="70%">
      <el-form ref="stdFormRef" :model="stdForm" label-position="top" :rules="stdRules">
        <el-row :gutter="24">
          <el-col :span="12"><el-form-item label="标准编号" prop="standardCode"><el-input v-model="stdForm.standardCode" placeholder="请输入" /></el-form-item></el-col>
          <el-col :span="12"><el-form-item label="标准名称" prop="standardName"><el-input v-model="stdForm.standardName" placeholder="请输入" /></el-form-item></el-col>
          <el-col :span="12">
            <el-form-item label="关联检测项目">
              <el-select v-model="stdForm.itemId" style="width: 100%" placeholder="请选择" filterable clearable @change="onStdItemChange">
                <el-option v-for="i in itemOptions" :key="i.id" :label="`${i.itemName}(${i.itemCode})`" :value="i.id" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="判定类型">
              <el-select v-model="stdForm.standardType" style="width: 100%">
                <el-option label="区间(下限~上限)" value="range" /><el-option label="不低于" value="min" />
                <el-option label="不高于" value="max" /><el-option label="等于" value="equal" />
                <el-option label="目标值±偏差" value="target" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="8"><el-form-item label="下限"><el-input-number v-model="stdForm.minValue" :precision="4" style="width: 100%" /></el-form-item></el-col>
          <el-col :span="8"><el-form-item label="上限"><el-input-number v-model="stdForm.maxValue" :precision="4" style="width: 100%" /></el-form-item></el-col>
          <el-col :span="8"><el-form-item label="单位"><el-input v-model="stdForm.unit" placeholder="如 mg/L" /></el-form-item></el-col>
          <el-col :span="8"><el-form-item label="目标值"><el-input-number v-model="stdForm.targetValue" :precision="4" style="width: 100%" /></el-form-item></el-col>
          <el-col :span="8"><el-form-item label="允许偏差"><el-input-number v-model="stdForm.deviation" :precision="4" style="width: 100%" /></el-form-item></el-col>
          <el-col :span="8"><el-form-item label="依据标准"><el-input v-model="stdForm.sourceStandard" placeholder="如 GB 5749-2022" /></el-form-item></el-col>
          <el-col :span="24"><el-form-item label="备注"><el-input v-model="stdForm.remark" type="textarea" :rows="2" /></el-form-item></el-col>
        </el-row>
      </el-form>
      <template #footer>
        <el-button @click="stdDialogVisible = false">取消</el-button>
        <el-button type="primary" @click="submitStd">确定</el-button>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { listTestItem, getTestItem, addTestItem, updateTestItem, delTestItem } from "@/api/lims/testItem";
import { listUnit, getUnit, addUnit, updateUnit, delUnit as apiDelUnit } from "@/api/lims/unit";
import {
  listJudgmentStandard, getJudgmentStandard, addJudgmentStandard, updateJudgmentStandard, delJudgmentStandard,
} from "@/api/lims/judgmentStandard";
const activeTab = ref("testItem");
const itemOptions = ref([]);
/* ==================== æ£€æµ‹é¡¹ç›® ==================== */
const itemSearch = ref({});
const itemData = ref([]);
const itemSelected = ref([]);
const itemLoading = ref(false);
const itemPage = reactive({ pageNum: 1, pageSize: 10, total: 0 });
const itemColumns = ref([
  { label: "项目编号", prop: "itemCode", width: 130 },
  { label: "项目名称", prop: "itemName", width: 200 },
  { label: "类型", prop: "itemType", width: 110 },
  { label: "单位", prop: "unit", width: 90 },
  { label: "检测方法", prop: "method", width: 200 },
  { label: "执行标准", prop: "standard", width: 160 },
  { label: "下限", prop: "limitMin", width: 90 },
  { label: "上限", prop: "limitMax", width: 90 },
  {
    dataType: "action", label: "操作", align: "center", fixed: "right", width: 140,
    operation: [
      { name: "编辑", type: "text", clickFun: (row) => openItemForm("edit", row) },
      { name: "删除", type: "text", clickFun: (row) => doDelItem([row.id]) },
    ],
  },
]);
const loadItem = () => {
  itemLoading.value = true;
  listTestItem({ ...itemSearch.value, ...itemPage }).then((res) => {
    itemLoading.value = false;
    itemData.value = res.data.records || [];
    itemPage.total = res.data.total || 0;
  }).catch(() => (itemLoading.value = false));
};
const paginationItem = (p) => { itemPage.pageNum = p.pageNum; itemPage.pageSize = p.pageSize; loadItem(); };
const loadItemOptions = () => { listTestItem({ pageNum: 1, pageSize: 500 }).then((res) => { itemOptions.value = res.data.records || []; }); };
const itemDialogVisible = ref(false);
const itemOpType = ref("add");
const itemFormRef = ref(null);
const itemForm = ref({});
const itemRules = {
  itemCode: [{ required: true, message: "请输入项目编号", trigger: "blur" }],
  itemName: [{ required: true, message: "请输入项目名称", trigger: "blur" }],
};
const openItemForm = (type, row) => {
  itemOpType.value = type;
  itemForm.value = { itemType: "chemical" };
  if (type === "edit") getTestItem(row.id).then((res) => { itemForm.value = { ...res.data }; });
  itemDialogVisible.value = true;
};
const submitItem = () => {
  itemFormRef.value.validate((valid) => {
    if (!valid) return;
    const fn = itemOpType.value === "add" ? addTestItem : updateTestItem;
    fn(itemForm.value).then(() => {
      ElMessage.success("保存成功");
      itemDialogVisible.value = false;
      loadItem();
      loadItemOptions();
    });
  });
};
const doDelItem = (ids) => {
  ElMessageBox.confirm(`确认删除选中的 ${ids.length} æ¡è®°å½•?`, "提示", { type: "warning" })
    .then(() => delTestItem(ids).then(() => { ElMessage.success("删除成功"); loadItem(); loadItemOptions(); }))
    .catch(() => {});
};
const delItem = () => {
  const ids = itemSelected.value.map((r) => r.id);
  if (!ids.length) return ElMessage.warning("请先选择记录");
  doDelItem(ids);
};
/* ==================== è®¡é‡å•位 ==================== */
const unitSearch = ref({});
const unitData = ref([]);
const unitSelected = ref([]);
const unitLoading = ref(false);
const unitPage = reactive({ pageNum: 1, pageSize: 10, total: 0 });
const unitColumns = ref([
  { label: "单位编码", prop: "unitCode", width: 130 },
  { label: "单位名称", prop: "unitName", width: 160 },
  { label: "类型", prop: "unitType", width: 130 },
  { label: "基准单位", prop: "baseUnit", width: 130 },
  { label: "换算系数", prop: "conversionRate", width: 130 },
  {
    dataType: "action", label: "操作", align: "center", fixed: "right", width: 140,
    operation: [
      { name: "编辑", type: "text", clickFun: (row) => openUnitForm("edit", row) },
      { name: "删除", type: "text", clickFun: (row) => doDelUnit([row.id]) },
    ],
  },
]);
const loadUnit = () => {
  unitLoading.value = true;
  listUnit({ ...unitSearch.value, ...unitPage }).then((res) => {
    unitLoading.value = false;
    unitData.value = res.data.records || [];
    unitPage.total = res.data.total || 0;
  }).catch(() => (unitLoading.value = false));
};
const paginationUnit = (p) => { unitPage.pageNum = p.pageNum; unitPage.pageSize = p.pageSize; loadUnit(); };
const unitDialogVisible = ref(false);
const unitOpType = ref("add");
const unitFormRef = ref(null);
const unitForm = ref({});
const unitRules = {
  unitCode: [{ required: true, message: "请输入单位编码", trigger: "blur" }],
  unitName: [{ required: true, message: "请输入单位名称", trigger: "blur" }],
};
const openUnitForm = (type, row) => {
  unitOpType.value = type;
  unitForm.value = { unitType: "weight", conversionRate: 1 };
  if (type === "edit") getUnit(row.id).then((res) => { unitForm.value = { ...res.data }; });
  unitDialogVisible.value = true;
};
const submitUnit = () => {
  unitFormRef.value.validate((valid) => {
    if (!valid) return;
    const fn = unitOpType.value === "add" ? addUnit : updateUnit;
    fn(unitForm.value).then(() => {
      ElMessage.success("保存成功");
      unitDialogVisible.value = false;
      loadUnit();
    });
  });
};
const doDelUnit = (ids) => {
  ElMessageBox.confirm(`确认删除选中的 ${ids.length} æ¡è®°å½•?`, "提示", { type: "warning" })
    .then(() => apiDelUnit(ids).then(() => { ElMessage.success("删除成功"); loadUnit(); }))
    .catch(() => {});
};
const delUnit = () => {
  const ids = unitSelected.value.map((r) => r.id);
  if (!ids.length) return ElMessage.warning("请先选择记录");
  doDelUnit(ids);
};
/* ==================== åˆ¤å®šæ ‡å‡† ==================== */
const stdSearch = ref({});
const stdData = ref([]);
const stdSelected = ref([]);
const stdLoading = ref(false);
const stdPage = reactive({ pageNum: 1, pageSize: 10, total: 0 });
const stdColumns = ref([
  { label: "标准编号", prop: "standardCode", width: 140 },
  { label: "标准名称", prop: "standardName", width: 200 },
  { label: "检测项目", prop: "itemName", width: 160 },
  { label: "判定类型", prop: "standardType", width: 120 },
  { label: "下限", prop: "minValue", width: 100 },
  { label: "上限", prop: "maxValue", width: 100 },
  { label: "单位", prop: "unit", width: 90 },
  { label: "依据标准", prop: "sourceStandard", width: 160 },
  {
    dataType: "action", label: "操作", align: "center", fixed: "right", width: 140,
    operation: [
      { name: "编辑", type: "text", clickFun: (row) => openStdForm("edit", row) },
      { name: "删除", type: "text", clickFun: (row) => doDelStd([row.id]) },
    ],
  },
]);
const loadStd = () => {
  stdLoading.value = true;
  listJudgmentStandard({ ...stdSearch.value, ...stdPage }).then((res) => {
    stdLoading.value = false;
    stdData.value = res.data.records || [];
    stdPage.total = res.data.total || 0;
  }).catch(() => (stdLoading.value = false));
};
const paginationStd = (p) => { stdPage.pageNum = p.pageNum; stdPage.pageSize = p.pageSize; loadStd(); };
const stdDialogVisible = ref(false);
const stdOpType = ref("add");
const stdFormRef = ref(null);
const stdForm = ref({});
const stdRules = {
  standardCode: [{ required: true, message: "请输入标准编号", trigger: "blur" }],
  standardName: [{ required: true, message: "请输入标准名称", trigger: "blur" }],
};
const openStdForm = (type, row) => {
  stdOpType.value = type;
  stdForm.value = { standardType: "range" };
  if (type === "edit") getJudgmentStandard(row.id).then((res) => { stdForm.value = { ...res.data }; });
  stdDialogVisible.value = true;
};
const onStdItemChange = (id) => {
  const it = itemOptions.value.find((i) => i.id === id);
  if (it) {
    stdForm.value.itemName = it.itemName;
    if (!stdForm.value.unit) stdForm.value.unit = it.unit;
  }
};
const submitStd = () => {
  stdFormRef.value.validate((valid) => {
    if (!valid) return;
    const fn = stdOpType.value === "add" ? addJudgmentStandard : updateJudgmentStandard;
    fn(stdForm.value).then(() => {
      ElMessage.success("保存成功");
      stdDialogVisible.value = false;
      loadStd();
    });
  });
};
const doDelStd = (ids) => {
  ElMessageBox.confirm(`确认删除选中的 ${ids.length} æ¡è®°å½•?`, "提示", { type: "warning" })
    .then(() => delJudgmentStandard(ids).then(() => { ElMessage.success("删除成功"); loadStd(); }))
    .catch(() => {});
};
const delStd = () => {
  const ids = stdSelected.value.map((r) => r.id);
  if (!ids.length) return ElMessage.warning("请先选择记录");
  doDelStd(ids);
};
onMounted(() => {
  loadItem();
  loadItemOptions();
  loadUnit();
  loadStd();
});
</script>
<style scoped>
.search_form {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
  flex-wrap: wrap;
  gap: 8px;
}
.search_title {
  font-size: 14px;
  color: #606266;
}
</style>