Files
his/openhis-ui-vue3/src/views/maintainSystem/Inspection/index.vue

2268 lines
75 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="inspection-container">
<!-- 左侧导航 -->
<div class="side-nav">
<div
v-for="(navItem, index) in navItems"
:key="index"
class="nav-item"
:class="{ active: activeNav === index }"
@click="activeNav = index"
>
{{ navItem }}
</div>
</div>
<!-- 右侧主内容 -->
<div class="main-content">
<!-- 检验类型页面 -->
<template v-if="activeNav === 0">
<div class="header-actions">
<button class="add-new-btn" @click="addNewRow">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
新增
</button>
</div>
<div class="table-container">
<!-- 调试按钮 -->
<div style="margin-bottom: 10px;">
<el-button @click="debugDepartments">调试科室数据</el-button>
</div>
<table class="data-table">
<thead>
<tr>
<th></th>
<th>*大类编码</th>
<th>*大类项目名称</th>
<th>*执行科室</th>
<th>序号</th>
<th>备注</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr
v-for="(row, index) in tableData"
:key="row.id"
:class="{ 'editing': editingRowId === row.id }"
>
<td>{{ index + 1 }}</td>
<td>
<template v-if="editingRowId === row.id">
<input v-model="row.code" type="text" :style="inputStyle">
</template>
<template v-else>
{{ row.code }}
</template>
</td>
<td>
<template v-if="editingRowId === row.id">
<input v-model="row.name" type="text" :style="inputStyle">
</template>
<template v-else>
{{ row.name }}
</template>
</td>
<td>
<template v-if="editingRowId === row.id">
<el-tree-select
v-model="row.department"
:data="departments"
:props="{
value: 'name',
label: 'name',
children: 'children',
}"
value-key="name"
placeholder="请选择科室"
check-strictly
:expand-on-click-node="false"
clearable
style="width: 100%;"
/>
</template>
<template v-else>
{{ row.department }}
</template>
</td>
<td>
<template v-if="editingRowId === row.id">
<input v-model="row.sortOrder" type="text" :style="inputStyle">
</template>
<template v-else>
{{ row.sortOrder }}
</template>
</td>
<td>
<template v-if="editingRowId === row.id">
<input v-model="row.remark" type="text" :style="inputStyle">
</template>
<template v-else>
{{ row.remark }}
</template>
</td>
<td class="action-cell">
<div
class="action-btn confirm-btn"
@click="handleConfirm(row)"
>
<svg v-if="editingRowId === row.id" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="20 6 9 17 4 12"></polyline>
</svg>
<span v-else></span>
</div>
<div
v-if="editingRowId !== row.id"
class="action-btn edit-btn"
@click="handleEdit(row)"
>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
</svg>
</div>
<div
v-if="editingRowId !== row.id"
class="action-btn add-btn"
@click="handleAdd(row, index)"
>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
</div>
<div
class="action-btn delete-btn"
@click="handleDelete(row.id)"
>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
<line x1="10" y1="11" x2="10" y2="17"></line>
<line x1="14" y1="11" x2="14" y2="17"></line>
</svg>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<!-- 页码区域 -->
<div class="pagination">
<div class="page-btn">上一页</div>
<div class="page-btn active">1</div>
<div class="page-btn">2</div>
<div class="page-btn">3</div>
<div class="page-btn">下一页</div>
</div>
</template>
<!-- 检验项目页面 -->
<template v-else-if="activeNav === 1">
<div class="page-header">
<h2>检验项目</h2>
</div>
<div class="filter-section">
<div class="filter-item">
<label>检验类型</label>
<select v-model="testTypeFilter" class="filter-select">
<option value="">选择检验类型</option>
<option v-for="type in testTypes" :key="type.value" :value="type.value">
{{ type.label }}
</option>
</select>
</div>
<div class="filter-item">
<label>名称</label>
<input v-model="nameFilter" type="text" class="filter-input" placeholder="名称/编码">
</div>
<div class="filter-item">
<label>费用套餐</label>
<select v-model="packageFilter" class="filter-select">
<option value="">选择费用套餐</option>
<option v-for="pkg in feePackages" :key="pkg.value" :value="pkg.value">
{{ pkg.label }}
</option>
</select>
</div>
<div class="filter-actions">
<button class="btn btn-primary add-btn" @click="addNewItem">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
新增
</button>
<button class="btn btn-secondary reset-btn" @click="resetFilters">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
<line x1="10" y1="11" x2="10" y2="17"></line>
<line x1="14" y1="11" x2="14" y2="17"></line>
</svg>
重置
</button>
<button class="btn btn-primary search-btn" @click="filterItems">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
查询
</button>
<button class="btn btn-primary export-btn" @click="exportTable">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="7 10 12 15 17 10"></polyline>
<line x1="12" y1="15" x2="12" y2="3"></line>
</svg>
导出表格
</button>
</div>
</div>
<div class="table-container">
<table class="data-table">
<thead>
<tr>
<th></th>
<th>小类编码</th>
<th>小类项目名称</th>
<th>检验类型</th>
<th>费用套餐</th>
<th>样本类型</th>
<th>金额</th>
<th>序号</th>
<th>服务范围</th>
<th>下级医技类型</th>
<th>备注</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr
v-for="(item, index) in filteredInspectionItems"
:key="item.id"
:class="{ 'editing': editingRowId === item.id }"
>
<td>{{ index + 1 }}</td>
<td>
<template v-if="editingRowId === item.id">
<input v-model="item.code" type="text" :style="inputStyle">
</template>
<template v-else>
{{ item.code }}
</template>
</td>
<td>
<template v-if="editingRowId === item.id">
<input v-model="item.name" type="text" :style="inputStyle">
</template>
<template v-else>
{{ item.name }}
</template>
</td>
<td>
<template v-if="editingRowId === item.id">
<select v-model="item.testType" :style="inputStyle">
<option value="">选择检验类型</option>
<option v-for="type in testTypes" :key="type.value" :value="type.value">
{{ type.label }}
</option>
</select>
</template>
<template v-else>
{{ item.testType }}
</template>
</td>
<td>
<template v-if="editingRowId === item.id">
<select v-model="item.package" :style="inputStyle" @change="updateAmountFromPackage(item)">
<option value="">选择费用套餐</option>
<option v-for="pkg in feePackages" :key="pkg.value" :value="pkg.value">
{{ pkg.label }}
</option>
</select>
</template>
<template v-else>
{{ item.package }}
</template>
</td>
<td>
<template v-if="editingRowId === item.id">
<select v-model="item.sampleType" :style="inputStyle">
<option value="">选择样本类型</option>
<option v-for="type in sampleTypes" :key="type.value" :value="type.value">
{{ type.label }}
</option>
</select>
</template>
<template v-else>
{{ item.sampleType }}
</template>
</td>
<td>{{ item.amount }}</td>
<td>
<template v-if="editingRowId === item.id">
<input v-model="item.sortOrder" type="number" :style="inputStyle">
</template>
<template v-else>
{{ item.sortOrder || 999999 }}
</template>
</td>
<td>
<template v-if="editingRowId === item.id">
<select v-model="item.serviceRange" :style="inputStyle">
<option v-for="range in serviceRanges" :key="range.value" :value="range.value">
{{ range.label }}
</option>
</select>
</template>
<template v-else>
{{ item.serviceRange || '全部' }}
</template>
</td>
<td>
<template v-if="editingRowId === item.id">
<input v-model="item.sub医技Type" type="text" :style="inputStyle">
</template>
<template v-else>
{{ item.sub医技Type || '-' }}
</template>
</td>
<td>
<template v-if="editingRowId === item.id">
<input v-model="item.remark" type="text" :style="inputStyle">
</template>
<template v-else>
{{ item.remark || '-' }}
</template>
</td>
<td class="action-cell">
<template v-if="editingRowId === item.id">
<div class="action-btn save-btn" @click="saveItem(item)">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="20 6 9 17 4 12"></polyline>
</svg>
保存
</div>
<div class="action-btn cancel-btn" @click="cancelEdit(item)">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
取消
</div>
</template>
<template v-else-if="!editingRowId">
<div class="action-btn edit-btn" @click="editItem(item)">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
</svg>
</div>
</template>
<div
class="action-btn delete-btn"
@click="deleteItem(item.id)"
v-if="!editingRowId || editingRowId === item.id"
>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
<line x1="10" y1="11" x2="10" y2="17"></line>
<line x1="14" y1="11" x2="14" y2="17"></line>
</svg>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<!-- 页码区域 -->
<div class="pagination">
<div class="page-btn">上一页</div>
<div class="page-btn active">1</div>
<div class="page-btn">2</div>
<div class="page-btn">3</div>
<div class="page-btn">下一页</div>
</div>
</template>
<!-- 套餐设置页面 -->
<template v-else-if="activeNav === 2">
<!-- 顶部操作栏 -->
<div class="top-bar">
<div class="action-group">
<button class="btn btn-icon" @click="refreshPage">
<i></i> 刷新
</button>
<button class="btn btn-success" @click="handlePackageManagement">套餐管理</button>
</div>
<button class="btn btn-lg" @click="handleSave">保存</button>
</div>
<!-- 表单区域 -->
<div class="form-section">
<div class="section-title">基本信息</div>
<div class="form-grid">
<div class="form-item">
<span class="form-label">套餐类别</span>
<select class="form-control form-select">
<option value="1" selected>检验套餐</option>
</select>
</div>
<div class="form-item">
<span class="form-label">套餐类别</span>
<select class="form-control form-select" v-model="packageCategory" disabled>
<option value="检验套餐">检验套餐</option>
</select>
</div>
<div class="form-item">
<span class="form-label">套餐级别</span>
<select class="form-control form-select" id="packageLevel" v-model="packageLevel">
<option value="">请选择套餐级别</option>
<option value="全院套餐">全院套餐</option>
<option value="科室套餐">科室套餐</option>
<option value="个人套餐">个人套餐</option>
</select>
</div>
<div class="form-item" id="departmentContainer" v-show="packageLevel === '科室套餐'">
<span class="form-label">科室</span>
<el-tree-select
v-model="department"
placeholder="请选择科室"
:data="departments"
:props="{
value: 'name',
label: 'name',
children: 'children',
}"
value-key="name"
check-strictly
:expand-on-click-node="false"
clearable
style="width: 100%;"
@change="handlePackageDepartmentChange"
/>
</div>
<div class="form-item" id="userContainer" v-show="packageLevel === '个人套餐'">
<span class="form-label">用户</span>
<select class="form-control form-select">
<option value="">请选择用户</option>
<option value="1">张三</option>
<option value="2">李四</option>
<option value="3">王五</option>
<option value="4">赵六</option>
</select>
</div>
<div class="form-item">
<span class="form-label"><span style="color:red">*</span>套餐名称</span>
<el-autocomplete
ref="autocompleteRef"
v-model="packageName"
:fetch-suggestions="queryDiagnosisItems"
placeholder="输入套餐名称或首字母搜索"
class="form-control"
@select="handleSelectPackage"
@focus="handleFocus"
clearable
trigger-on-focus
:min-length="0"
>
<template #suffix>
<i class="el-icon-search"></i>
</template>
</el-autocomplete>
<div class="error-message" id="packageNameError" style="color: #ff4d4f; font-size: 12px; margin-top: 4px; display: none;">套餐名称不能为空</div>
</div>
<div class="form-item">
<span class="form-label">卫生机构</span>
<input type="text" class="form-control" :value="userStore.orgName" readonly>
</div>
<div class="form-item">
<span class="form-label">套餐金额</span>
<input type="text" class="form-control" style="width: 120px;" :value="packageAmount.toFixed(2)" readonly>
</div>
<div class="form-item">
<span class="form-label">折扣 %</span>
<input type="text" class="form-control" v-model="discount" @input="calculateAmounts">
</div>
<div class="form-item">
<span class="form-label">制单人</span>
<input type="text" class="form-control" readonly :value="userStore.nickName">
</div>
<div class="form-item">
<span class="form-label">是否停用</span>
<div class="radio-group">
<label class="radio-item">
<input type="radio" v-model="isDisabled" :value="false" checked> 启用
</label>
<label class="radio-item">
<input type="radio" v-model="isDisabled" :value="true"> 停用
</label>
</div>
</div>
<div class="form-item">
<span class="form-label">显示套餐名</span>
<div class="radio-group">
<label class="radio-item">
<input type="radio" v-model="showPackageName" :value="true" checked>
</label>
<label class="radio-item">
<input type="radio" v-model="showPackageName" :value="false">
</label>
</div>
</div>
<div class="form-item">
<span class="form-label">生成服务费</span>
<div class="radio-group">
<label class="radio-item">
<input type="radio" v-model="generateServiceFee" :value="true" checked>
</label>
<label class="radio-item">
<input type="radio" v-model="generateServiceFee" :value="false">
</label>
</div>
</div>
<div class="form-item">
<span class="form-label">套餐价格</span>
<div class="radio-group">
<label class="radio-item">
<input type="radio" v-model="enablePackagePrice" :value="true" checked> 启用
</label>
<label class="radio-item">
<input type="radio" v-model="enablePackagePrice" :value="false"> 不启用
</label>
</div>
</div>
<div class="form-item">
<span class="form-label">备注</span>
<input type="text" class="form-control" v-model="remarks" placeholder="请输入备注">
</div>
<div class="form-item">
<span class="form-label">服务费</span>
<input type="text" class="form-control" :value="serviceFee.toFixed(2)" readonly>
</div>
<div class="form-item">
<span class="form-label">lis分组</span>
<select class="form-control form-select">
<option value="">请选择lis分组</option>
<option v-for="group in lisGroupList" :key="group.id" :value="group.id">
{{ group.groupName || group.lisGroupName }}
</option>
</select>
</div>
<div class="form-item">
<span class="form-label">血量</span>
<input type="text" class="form-control" v-model="bloodVolume">
</div>
</div>
</div>
<!-- 检验套餐明细表格区域 -->
<div class="table-container" style="width: 100%; margin-top: 20px;">
<div class="table-header" style="display: flex; justify-content: space-between; align-items: center; padding: 10px;">
<div class="table-title" style="font-size: 16px; font-weight: bold;">检验套餐明细</div>
<button class="action-btn" title="添加项目" @click="addPackageItem" style="width: 32px; height: 32px; font-size: 20px; display: flex; align-items: center; justify-content: center;">+</button>
</div>
<table class="data-table">
<thead>
<tr>
<th style="width: 40px;">行号</th>
<th style="width: 250px;">项目名称/规格</th>
<th style="width: 100px;">剂量</th>
<th style="width: 100px;">途径</th>
<th style="width: 100px;">频次</th>
<th style="width: 80px;">天数</th>
<th style="width: 80px;">数量</th>
<th style="width: 80px;">单位</th>
<th style="width: 100px;">单价</th>
<th style="width: 100px;">金额</th>
<th style="width: 100px;">服务费</th>
<th style="width: 100px;">总金额</th>
<th style="width: 150px;">产地</th>
<th style="width: 80px;">操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in packageItems" :key="index" :class="{ 'editing': editingRowId === index }">
<td>{{ index + 1 }}</td>
<td>
<template v-if="editingRowId === index">
<input
type="text"
v-model="item.name"
placeholder="请输入或选择项目名称"
@focus="handleItemFocus(index)"
:style="inputStyle"
/>
</template>
<template v-else>
{{ item.name }}
</template>
</td>
<td>
<template v-if="editingRowId === index">
<input type="text" v-model="item.dosage" placeholder="剂量" :style="inputStyle">
</template>
<template v-else>
{{ item.dosage || '-' }}
</template>
</td>
<td>
<template v-if="editingRowId === index">
<select v-model="item.route" style="width: 100%;" :style="inputStyle">
<option value="">请选择</option>
<option value="项/人">/</option>
<option value="次/人">/</option>
<!-- 可根据实际需求添加更多选项 -->
</select>
</template>
<template v-else>
{{ item.route || '-' }}
</template>
</td>
<td>
<template v-if="editingRowId === index">
<select v-model="item.frequency" style="width: 100%;" :style="inputStyle">
<option value="">请选择</option>
<option value="一次">一次</option>
<option value="每日">每日</option>
<option value="每周">每周</option>
<!-- 可根据实际需求添加更多选项 -->
</select>
</template>
<template v-else>
{{ item.frequency || '-' }}
</template>
</td>
<td>
<template v-if="editingRowId === index">
<input type="number" v-model.number="item.days" placeholder="天数" style="width: 100%;" :style="inputStyle">
</template>
<template v-else>
{{ item.days || '-' }}
</template>
</td>
<td>
<template v-if="editingRowId === index">
<input type="number" v-model.number="item.quantity" placeholder="数量" style="width: 100%;" @input="updateItemAmount(item)" :style="inputStyle">
</template>
<template v-else>
{{ item.quantity || '-' }}
</template>
</td>
<td>{{ item.unit || '-' }}</td>
<td>{{ item.unitPrice.toFixed(2) }}</td>
<td>{{ item.amount.toFixed(2) }}</td>
<td>
<template v-if="editingRowId === index">
<input type="number" v-model.number="item.serviceFee" placeholder="服务费" style="width: 100%;" step="0.01" @input="updateItemTotalAmount(item)" :style="inputStyle">
</template>
<template v-else>
{{ item.serviceFee.toFixed(2) }}
</template>
</td>
<td>{{ item.totalAmount.toFixed(2) }}</td>
<td>
<template v-if="editingRowId === index">
<input type="text" v-model="item.origin" placeholder="产地" style="width: 100%;" :style="inputStyle">
</template>
<template v-else>
{{ item.origin || '-' }}
</template>
</td>
<td class="action-cell">
<div class="table-actions" style="display: flex; gap: 5px;">
<div class="action-btn edit-btn" title="编辑" @click="handleEditItem(index)">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
</svg>
</div>
<div class="action-btn delete-btn" title="删除" @click="deletePackageItem(index)">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
<line x1="10" y1="11" x2="10" y2="17"></line>
<line x1="14" y1="11" x2="14" y2="17"></line>
</svg>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</template>
</div>
</div>
</template>
<script setup>
import useUserStore from '@/store/modules/user';
import { ref, reactive, onMounted, watch, computed, nextTick, getCurrentInstance } from 'vue';
import { ElMessage, ElAutocomplete, ElMessageBox } from 'element-plus';
import * as echarts from 'echarts';
import { useRouter, useRoute } from 'vue-router';
import { formatDate } from '@/utils/index';
import request from '@/utils/request';
import { listInspectionType, getInspectionType, addInspectionType, updateInspectionType, delInspectionType } from '@/api/system/inspectionType';
import { listLisGroup } from '@/api/system/checkType';
import { getDiagnosisTreatmentList } from '@/views/catalog/diagnosistreatment/components/diagnosistreatment';
import { getLocationTree } from '@/views/charge/outpatientregistration/components/outpatientregistration';
// 获取当前登录用户信息
const userStore = useUserStore();
// 创建路由实例
const router = useRouter();
const route = useRoute();
// 存储LIS分组数据
const lisGroupList = ref([]);
// 获取就诊科室数据 - 与门诊挂号页面保持一致,在组件初始化时直接调用
getLocationInfo();
onMounted(() => {
// 其他初始化逻辑
});
// 获取LIS分组数据
const getLisGroupList = async () => {
try {
const response = await listLisGroup();
if (response.code === 200) {
console.log('完整LIS分组响应:', response); // 记录完整响应
// 适配可能的不同响应格式
let items = [];
// 检查响应数据
if (response.data) {
// 格式1: {data: {rows: [], total: number}} - 标准分页格式
if (response.data.rows && Array.isArray(response.data.rows)) {
items = response.data.rows;
console.log('LIS分组分页数据:', items);
}
// 格式2: {data: []} - 简单数组格式
else if (Array.isArray(response.data)) {
items = response.data;
console.log('LIS分组数组数据:', items);
}
// 格式3: {data: {data: []}} - 双重嵌套格式
else if (response.data.data && Array.isArray(response.data.data)) {
items = response.data.data;
console.log('LIS分组双重嵌套数据:', items);
}
// 格式4: {data: {data: {rows: []}}} - 双重嵌套分页格式
else if (response.data.data && response.data.data.rows && Array.isArray(response.data.data.rows)) {
items = response.data.data.rows;
console.log('LIS分组双重嵌套分页数据:', items);
}
// 其他格式
else {
console.log('无法识别的数据格式:', response.data);
}
}
lisGroupList.value = items;
} else {
ElMessage.error('获取LIS分组数据失败');
}
} catch (error) {
console.error('获取LIS分组数据失败:', error);
ElMessage.error('获取LIS分组数据失败');
}
};
// 导航数据
const navItems = ref(['检验类型', '检验项目', '套餐设置']);
const activeNav = ref(0);
// 检验类型数据
const tableData = ref([]);
// 获取检验类型列表 - 从后端API获取
const getInspectionTypeList = () => {
listInspectionType().then(data => {
// 确保数据结构与前端使用的一致处理后端返回的AjaxResult格式
// 后端返回的数据格式: {code: 200, msg: "查询成功", data: [检验类型列表]}
const inspectionTypeList = data.data || [];
// 后端实体字段名本身就是 sortOrder这里不再从不存在的 item.order 做映射
const formattedData = inspectionTypeList.map(item => ({
...item,
sortOrder: item.sortOrder
}));
// 过滤掉已逻辑删除的记录validFlag为0
tableData.value = formattedData.filter(item => item.validFlag === 1);
}).catch(error => {
console.error('获取检验类型列表失败:', error);
});
};
const editingRowId = ref(null);
const inputStyle = { width: '100%', height: '28px', padding: '0 4px', border: '1px solid #d9d9d9', borderRadius: '2px' };
const departments = ref([]);
/** 查询就诊科室 - 与门诊挂号页面保持一致 */
function getLocationInfo() {
console.log('调用getLocationTree API...');
getLocationTree().then((response) => {
console.log('getLocationTree API完整返回:', response);
console.log('getLocationTree API数据结构:', JSON.stringify(response.data, null, 2));
// 检查数据结构并转换为适合el-tree-select的格式
if (Array.isArray(response.data)) {
// 直接使用数组数据
departments.value = response.data;
} else if (response.data && response.data.records) {
// 处理分页格式数据
departments.value = response.data.records;
} else if (response.data && response.data.rows) {
// 处理另一种分页格式数据
departments.value = response.data.rows;
} else {
console.error('API返回数据格式不符合预期:', response.data);
departments.value = [];
}
console.log('最终科室数据:', JSON.stringify(departments.value, null, 2));
}).catch((error) => {
console.error('获取科室数据失败:', error);
departments.value = [];
});
}
// 处理科室选择变化
function handleDepartmentChange(selectedNode, item) {
console.log('选择的科室节点:', selectedNode);
// 如果selectedNode是对象只取name属性
if (typeof selectedNode === 'object' && selectedNode !== null) {
item.department = selectedNode.name;
} else {
// 否则直接使用(可能是字符串)
item.department = selectedNode;
}
}
// 处理套餐科室选择变化
function handlePackageDepartmentChange(selectedNode) {
console.log('选择的套餐科室节点:', selectedNode);
// 如果selectedNode是对象只取name属性
if (typeof selectedNode === 'object' && selectedNode !== null) {
department.value = selectedNode.name;
} else {
// 否则直接使用(可能是字符串)
department.value = selectedNode;
}
}
// 调试科室数据
function debugDepartments() {
console.log('当前科室数据:', departments.value);
console.log('第一个检验项目科室:', inspectionItems.value[0]?.department);
// 尝试模拟选择一个科室
if (departments.value && departments.value.length > 0) {
const firstDept = departments.value[0];
console.log('尝试选择科室:', firstDept.name);
inspectionItems.value[0].department = firstDept.name;
}
}
// 费用套餐数据
const feePackages = ref([
{ value: '肝功能12项', label: '肝功能12项', amount: 120.00 },
{ value: '血常规', label: '血常规', amount: 35.00 },
{ value: '尿常规', label: '尿常规', amount: 25.00 },
{ value: '血脂五项', label: '血脂五项', amount: 80.00 },
{ value: '肾功能三项', label: '肾功能三项', amount: 50.00 },
{ value: '乙肝套餐', label: '乙肝套餐', amount: 75.00 },
{ value: '糖尿病套餐', label: '糖尿病套餐', amount: 65.00 },
{ value: '肾功能套餐', label: '肾功能套餐', amount: 45.00 },
{ value: '肿瘤筛查套餐', label: '肿瘤筛查套餐', amount: 200.00 },
{ value: '心脑血管套餐', label: '心脑血管套餐', amount: 180.00 },
{ value: '甲状腺功能套餐', label: '甲状腺功能套餐', amount: 150.00 },
{ value: '微量元素套餐', label: '微量元素套餐', amount: 90.00 },
{ value: '电解质套餐', label: '电解质套餐', amount: 40.00 },
{ value: '风湿免疫套餐', label: '风湿免疫套餐', amount: 120.00 },
{ value: '性激素套餐', label: '性激素套餐', amount: 160.00 }
]);
// 样本类型数据
const sampleTypes = ref([
{ value: '血液', label: '血液' },
{ value: '尿液', label: '尿液' },
{ value: '粪便', label: '粪便' },
{ value: '脑脊液', label: '脑脊液' },
{ value: '胸水', label: '胸水' },
{ value: '腹水', label: '腹水' },
{ value: '分泌物', label: '分泌物' },
{ value: '组织', label: '组织' },
{ value: '其他', label: '其他' }
]);
// 服务范围数据
const serviceRanges = ref([
{ value: '全部', label: '全部' },
{ value: '门诊', label: '门诊' },
{ value: '住院', label: '住院' },
{ value: '急诊', label: '急诊' },
{ value: '体检', label: '体检' }
]);
// 检验类型数据
const testTypes = ref([
{ value: '生化', label: '生化' },
{ value: '常规检验', label: '常规检验' },
{ value: '免疫学检验', label: '免疫学检验' },
{ value: '微生物检验', label: '微生物检验' },
{ value: '分子生物学检验', label: '分子生物学检验' },
{ value: '血液学检验', label: '血液学检验' },
{ value: '尿液检验', label: '尿液检验' },
{ value: '粪便检验', label: '粪便检验' },
{ value: '脑脊液检验', label: '脑脊液检验' },
{ value: '其他检验', label: '其他检验' }
]);
// 检验项目数据
const inspectionItems = ref([
{ id: 1, code: '0101', name: '血常规五分类', testType: '生化', package: '肝功能12项', sampleType: '血液', amount: 36.00, sortOrder: 1, serviceRange: '全部', sub医技Type: '', remark: '', status: true },
{ id: 2, code: '0102', name: '肝功能12项', testType: '生化', package: '肝功能12项', sampleType: '血液', amount: 120.00, sortOrder: 2, serviceRange: '全部', sub医技Type: '', remark: '', status: true },
{ id: 3, code: '0201', name: '尿常规', testType: '常规检验', package: '', sampleType: '尿液', amount: 25.00, sortOrder: 3, serviceRange: '全部', sub医技Type: '', remark: '', status: true },
{ id: 4, code: '0202', name: '便常规+潜血', testType: '常规检验', package: '', sampleType: '粪便', amount: 30.00, sortOrder: 4, serviceRange: '门诊', sub医技Type: '', remark: '', status: true },
{ id: 5, code: '0301', name: '乙肝五项', testType: '免疫学检验', package: '乙肝套餐', sampleType: '血液', amount: 75.00, sortOrder: 5, serviceRange: '全部', sub医技Type: '', remark: '', status: true },
{ id: 6, code: '0302', name: '丙肝抗体', testType: '免疫学检验', package: '', sampleType: '血液', amount: 45.00, sortOrder: 6, serviceRange: '住院', sub医技Type: '', remark: '', status: true },
{ id: 7, code: '0401', name: '血糖', testType: '生化', package: '糖尿病套餐', sampleType: '血液', amount: 15.00, sortOrder: 7, serviceRange: '全部', sub医技Type: '', remark: '', status: true },
{ id: 8, code: '0402', name: '糖化血红蛋白', testType: '生化', package: '糖尿病套餐', sampleType: '血液', amount: 50.00, sortOrder: 8, serviceRange: '全部', sub医技Type: '', remark: '', status: true },
{ id: 9, code: '0501', name: '肌酐', testType: '生化', package: '肾功能套餐', sampleType: '血液', amount: 25.00, sortOrder: 9, serviceRange: '住院', sub医技Type: '', remark: '', status: true },
{ id: 10, code: '0502', name: '尿素氮', testType: '生化', package: '肾功能套餐', sampleType: '血液', amount: 20.00, sortOrder: 10, serviceRange: '住院', sub医技Type: '', remark: '', status: true },
{ id: 11, code: '0601', name: '白带常规', testType: '常规检验', package: '', sampleType: '分泌物', amount: 30.00, sortOrder: 11, serviceRange: '门诊', sub医技Type: '', remark: '', status: true },
{ id: 12, code: '0602', name: '前列腺液常规', testType: '常规检验', package: '', sampleType: '分泌物', amount: 35.00, sortOrder: 12, serviceRange: '门诊', sub医技Type: '', remark: '', status: true },
{ id: 13, code: '0701', name: '脑脊液常规', testType: '常规检验', package: '', sampleType: '脑脊液', amount: 60.00, sortOrder: 13, serviceRange: '住院', sub医技Type: '', remark: '', status: true },
{ id: 14, code: '0801', name: '肿瘤标志物CA125', testType: '免疫学检验', package: '肿瘤筛查套餐', sampleType: '血液', amount: 120.00, sortOrder: 14, serviceRange: '体检', sub医技Type: '', remark: '', status: true },
{ id: 15, code: '0802', name: '肿瘤标志物AFP', testType: '免疫学检验', package: '肿瘤筛查套餐', sampleType: '血液', amount: 80.00, sortOrder: 15, serviceRange: '体检', sub医技Type: '', remark: '', status: true }
]);
// 过滤条件
const testTypeFilter = ref('');
const nameFilter = ref('');
const packageFilter = ref('');
// 过滤后的检验项目数据
const filteredInspectionItems = computed(() => {
return inspectionItems.value.filter(item => {
// 按检验类型过滤
if (testTypeFilter.value && item.testType !== testTypeFilter.value) {
return false;
}
// 按名称或编码过滤
if (nameFilter.value && !(item.name.includes(nameFilter.value) || item.code.includes(nameFilter.value))) {
return false;
}
// 按费用套餐过滤
if (packageFilter.value && item.package !== packageFilter.value) {
return false;
}
return true;
});
});
// 执行过滤
const filterItems = () => {
// 过滤逻辑已经在computed属性中实现这里可以添加额外的逻辑
};
// 重置过滤条件
const resetFilters = () => {
testTypeFilter.value = '';
nameFilter.value = '';
packageFilter.value = '';
};
// 套餐相关数据
const packageCategory = ref('检验套餐');
const packageLevel = ref('');
const packageName = ref('');
const department = ref('');
const discount = ref('');
const isDisabled = ref(false);
const showPackageName = ref(true);
const generateServiceFee = ref(true);
const enablePackagePrice = ref(true);
const packageAmount = ref(0.00);
const serviceFee = ref(0.00);
const bloodVolume = ref('');
const remarks = ref('');
const autocompleteRef = ref();
const packageItems = ref([
{ name: '血常规五分类', dosage: '项/人', route: '项/人', frequency: '', days: '', quantity: 1, unit: '项', unitPrice: 25.00, amount: 25.00, serviceFee: 0.00, totalAmount: 25.00, origin: '' },
{ name: '总IgE测定', dosage: '项/人', route: '项/人', frequency: '', days: '', quantity: 1, unit: '项', unitPrice: 30.00, amount: 30.00, serviceFee: 0.00, totalAmount: 30.00, origin: '' },
{ name: '肝功能12项', dosage: '项/人', route: '项/人', frequency: '', days: '', quantity: 1, unit: '项', unitPrice: 120.00, amount: 120.00, serviceFee: 0.00, totalAmount: 120.00, origin: '' },
{ name: '肾功能三项', dosage: '项/人', route: '项/人', frequency: '', days: '', quantity: 1, unit: '项', unitPrice: 50.00, amount: 50.00, serviceFee: 0.00, totalAmount: 50.00, origin: '' },
{ name: '血糖', dosage: '项/人', route: '项/人', frequency: '', days: '', quantity: 1, unit: '项', unitPrice: 15.00, amount: 15.00, serviceFee: 0.00, totalAmount: 15.00, origin: '' },
{ name: '糖化血红蛋白', dosage: '项/人', route: '项/人', frequency: '', days: '', quantity: 1, unit: '项', unitPrice: 50.00, amount: 50.00, serviceFee: 0.00, totalAmount: 50.00, origin: '' },
{ name: '血脂五项', dosage: '项/人', route: '项/人', frequency: '', days: '', quantity: 1, unit: '项', unitPrice: 80.00, amount: 80.00, serviceFee: 0.00, totalAmount: 80.00, origin: '' }
]);
// 查询诊疗目录中的检验项目
const queryDiagnosisItems = (queryString, cb) => {
// 调用诊疗目录API查询检验类别的项目
const params = {
searchKey: queryString || '',
pageNo: 1,
pageSize: 100 // 增加分页大小,显示更多项目
};
getDiagnosisTreatmentList(params).then(response => {
console.log('诊疗目录API完整返回:', response);
// 处理不同的数据结构
let data;
if (response.data && response.data.records) {
data = response.data.records;
} else if (response.data && Array.isArray(response.data)) {
data = response.data;
} else if (response.data && response.data.rows) {
data = response.data.rows;
} else {
console.error('API返回数据格式不符合预期:', response.data);
return cb([]);
}
// 添加调试信息,查看返回的数据结构
console.log('诊疗目录返回数据:', data);
// 过滤出目录类别为检验的项目
// 支持多种可能的字段名
const inspectionItems = data.filter(item => {
return item.categoryCode_dictText === '检验' ||
item.categoryName === '检验' ||
item.category === '检验';
});
// 处理每个检验项目,确保有正确的字段映射
const results = inspectionItems.map(item => {
// 确保每个项目都有必要的字段
return {
value: item.name || item.itemName || item.drugName || '',
label: `${item.name || item.itemName || item.drugName || ''} - ${item.unit || item.usageUnit || '项'} - ¥${item.retailPrice || item.price || item.unitPrice || 0.00}`,
name: item.name || item.itemName || item.drugName || '',
unit: item.unit || item.usageUnit || '',
retailPrice: item.retailPrice || item.price || item.unitPrice || 0.00,
...item
};
});
console.log('处理后的检验项目:', results);
cb(results);
}).catch(error => {
console.error('查询诊疗目录失败:', error);
ElMessage.error('查询诊疗目录失败,请稍后重试');
cb([]);
});
};
// 处理项目名称输入框获得焦点事件
const handleItemFocus = (index) => {
// 可以在这里添加项目选择逻辑,或者留空让用户直接输入
};
// 处理输入框获得焦点事件,手动触发下拉框显示
const handleFocus = () => {
// 延迟执行,确保输入框已经获得焦点
setTimeout(() => {
// 手动触发查询,获取所有检验项目
if (autocompleteRef.value) {
// 模拟空输入查询
queryDiagnosisItems('', (results) => {
// 设置建议列表
autocompleteRef.value.suggestions = results;
// 显示下拉框
autocompleteRef.value.showPopper = true;
});
}
}, 100);
};
// 添加新的套餐项目
let addingItem = false;
const addPackageItem = () => {
if (addingItem) return; // 防止重复调用
addingItem = true;
const newItem = {
name: '',
dosage: '',
route: '',
frequency: '',
days: '',
quantity: 1,
unit: '',
unitPrice: 0.00,
amount: 0.00,
serviceFee: 0.00,
totalAmount: 0.00,
origin: ''
};
packageItems.value.push(newItem);
// 延迟重置标志位,确保不会影响其他操作
setTimeout(() => {
addingItem = false;
}, 100);
};
// 删除套餐项目
const deletePackageItem = (index) => {
ElMessageBox.confirm('确定要删除该项目吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
packageItems.value.splice(index, 1);
calculateAmounts();
ElMessage.success('删除成功');
}).catch(() => {
// 取消删除
});
};
// 处理选择诊疗项目
const handleItemSelect = (item, selectedItem) => {
console.log('选择的诊疗项目:', selectedItem);
// 将选中的项目名称、使用单位、单价赋值到检验套餐明细行对应的字段中
item.name = selectedItem.name || selectedItem.value; // 使用name或value作为项目名称
item.unit = selectedItem.unit || selectedItem.usageUnit || ''; // 使用unit或usageUnit作为使用单位
item.unitPrice = parseFloat(selectedItem.retailPrice || selectedItem.price || 0.00); // 使用retailPrice或price作为单价
item.quantity = 1; // 默认数量为1
item.serviceFee = calculateItemServiceFee(item); // 计算该项目的服务费
updateItemAmount(item);
ElMessage.success(`已选择检验项目: ${item.name}`);
};
// 更新项目金额(考虑折扣)
const updateItemAmount = (item) => {
// 计算项目原价金额
const originalAmount = (item.quantity || 1) * (item.unitPrice || 0.00);
// 应用折扣到项目金额
let discountedAmount = originalAmount;
if (discount.value && !isNaN(parseFloat(discount.value))) {
const discountRate = parseFloat(discount.value) / 100;
discountedAmount = originalAmount * (1 - discountRate);
}
// 更新项目金额
item.amount = parseFloat(discountedAmount.toFixed(2));
// 基于折扣后的金额计算服务费
item.serviceFee = calculateItemServiceFee(item);
// 更新项目总金额
updateItemTotalAmount(item);
// 重新计算套餐金额和服务费
calculateAmounts();
};
// 更新项目总金额
const updateItemTotalAmount = (item) => {
item.totalAmount = (item.amount || 0.00) + (item.serviceFee || 0.00);
};
// 处理编辑项目
const handleEditItem = (index) => {
if (editingRowId.value !== null && editingRowId.value !== index) {
ElMessage.warning('请先保存或取消当前正在编辑的行');
return;
}
if (editingRowId.value === index) {
// 保存编辑
editingRowId.value = null;
ElMessage.success('保存成功');
} else {
// 进入编辑模式
editingRowId.value = index;
}
};
// 计算单个项目的服务费(基于折扣后的金额)
const calculateItemServiceFee = (item) => {
if (!generateServiceFee.value) return 0;
// 服务费是项目折扣后金额的10%
return parseFloat((item.amount * 0.1).toFixed(2));
};
// 重新分配所有项目的服务费(基于折扣后的金额)
const redistributeServiceFee = () => {
if (!generateServiceFee.value) {
// 如果不生成服务费将所有项目的服务费设为0
packageItems.value.forEach(item => {
item.serviceFee = 0;
updateItemTotalAmount(item);
});
return;
}
// 重新计算每个项目的服务费
packageItems.value.forEach(item => {
item.serviceFee = calculateItemServiceFee(item);
updateItemTotalAmount(item);
});
};
// 计算套餐金额和服务费
const calculateAmounts = () => {
// 更新每个项目的折扣金额
packageItems.value.forEach(item => {
// 计算项目原价金额
const originalAmount = (item.quantity || 1) * (item.unitPrice || 0.00);
// 应用折扣到项目金额
let discountedAmount = originalAmount;
if (discount.value && !isNaN(parseFloat(discount.value))) {
const discountRate = parseFloat(discount.value) / 100;
discountedAmount = originalAmount * (1 - discountRate);
}
// 更新项目金额
item.amount = parseFloat(discountedAmount.toFixed(2));
});
// 重新分配所有项目的服务费
redistributeServiceFee();
// 计算套餐总金额(基于项目的折扣后金额)
const totalAmount = packageItems.value.reduce((sum, item) => sum + (item.amount || 0), 0);
// 更新套餐金额
packageAmount.value = parseFloat(totalAmount.toFixed(2));
// 计算套餐总服务费
if (generateServiceFee.value) {
serviceFee.value = parseFloat(packageItems.value.reduce((sum, item) => sum + (item.serviceFee || 0), 0).toFixed(2));
} else {
serviceFee.value = 0;
}
};
// 选择套餐项目
const handleSelectPackage = (item) => {
console.log('选择的检验项目:', item);
// 直接更新套餐金额
if (item.retailPrice) {
// 清空现有套餐项目列表
packageItems.value = [];
// 创建新的套餐项目
const newPackageItem = {
name: item.name,
dosage: '项/人',
route: '项/人',
frequency: '',
days: '',
quantity: 1,
unit: item.unit || '项',
unitPrice: parseFloat(item.retailPrice),
amount: parseFloat(item.retailPrice),
serviceFee: 0.00, // 初始化为0
totalAmount: parseFloat(item.retailPrice), // 初始总金额
origin: ''
};
// 添加到套餐项目列表
packageItems.value.push(newPackageItem);
// 调用updateItemAmount计算折扣后的金额和服务费
updateItemAmount(newPackageItem);
// 更新套餐名称
packageName.value = item.name;
} else {
console.error('选择的项目没有价格信息:', item);
ElMessage.warning('选择的项目没有价格信息');
}
ElMessage.success(`已选择检验项目: ${item.name}`);
};
// 检验类型相关方法
const addNewRow = () => {
if (editingRowId.value) {
ElMessage.warning('请先保存或取消当前正在编辑的行');
return;
}
const newRow = { id: Date.now(), code: '', name: '', department: departments.value[0], sortOrder: tableData.value.length + 1, remark: '' };
tableData.value.push(newRow);
editingRowId.value = newRow.id;
};
const handleEdit = (row) => {
if (editingRowId.value && editingRowId.value !== row.id) {
ElMessage.warning('请先保存或取消当前正在编辑的行');
return;
}
editingRowId.value = row.id;
};
const handleConfirm = (row) => {
// 准备提交给后端的数据保留sortOrder字段名后端会自动映射到数据库的order字段
const submitData = {
...row,
// 确保sortOrder字段存在且为数字类型
sortOrder: row.sortOrder ? Number(row.sortOrder) : 0
};
console.log('原始row数据:', row);
console.log('提交前的submitData:', submitData);
// 验证必填字段
if (!submitData.code || submitData.code.trim() === '') {
alert('检验类型编码不能为空');
return;
}
if (!submitData.name || submitData.name.trim() === '') {
alert('检验类型名称不能为空');
return;
}
if (!submitData.department || submitData.department.trim() === '') {
alert('执行科室不能为空');
return;
}
// 检查是否是已知的重复编码
if (submitData.code.trim() === '21') {
alert('检验类型编码21已存在请使用其他编码');
return;
}
// 输出调试信息
console.log('原始code值:', submitData.code, '长度:', submitData.code.length);
// 去除code字段的前后空格确保唯一性验证准确
submitData.code = submitData.code.trim();
console.log('去除空格后的code值:', submitData.code, '长度:', submitData.code.length);
console.log('准备提交的数据:', submitData);
// 区分新增和更新操作
if (row.id.toString().length > 10) { // 新增的临时ID
// 新增数据时移除临时ID让后端自动生成主键
const { id, ...newData } = submitData;
console.log('删除ID后的newData:', newData);
addInspectionType(newData).then(response => {
console.log('新增成功响应:', response);
getInspectionTypeList();
}).catch(error => {
console.error('新增检验类型失败:', error);
if (error.response && error.response.data) {
alert('新增失败: ' + error.response.data.msg);
} else {
alert('新增失败,请重试');
}
});
} else { // 更新操作
// 更新数据时保留ID
console.log('更新时的submitData:', submitData);
updateInspectionType(submitData).then(response => {
console.log('更新成功响应:', response);
getInspectionTypeList();
}).catch(error => {
console.error('更新检验类型失败:', error);
if (error.response && error.response.data) {
alert('更新失败: ' + error.response.data.msg);
} else {
alert('更新失败,请重试');
}
});
}
editingRowId.value = null;
};
const handleAdd = (row, index) => {
if (editingRowId.value) {
ElMessage.warning('请先保存或取消当前正在编辑的行');
return;
}
const newRow = { id: Date.now(), code: '', name: '', department: row.department, sortOrder: row.sortOrder + 0.5, remark: '' };
tableData.value.splice(index + 1, 0, newRow);
editingRowId.value = newRow.id;
};
const handleDelete = (id) => {
ElMessageBox.confirm('确定要删除该检验类型吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
console.log('删除按钮被点击原始ID:', id, '类型:', typeof id);
// 确保ID是数字类型
const numericId = Number(id);
console.log('转换后的ID:', numericId, '类型:', typeof numericId);
// 判断是否为临时ID临时ID是通过Date.now()生成的值很大通常大于2000000000000
const isTemporaryId = numericId > 2e12;
console.log('是否为临时ID:', isTemporaryId, '判断阈值:', 2e12);
if (!isTemporaryId) { // 真实数据库ID
console.log('调用删除APIID:', numericId, 'API路径:', `/system/inspection-type/${numericId}`);
delInspectionType(numericId).then(response => {
console.log('删除成功,响应:', response);
ElMessage.success('删除成功');
getInspectionTypeList();
}).catch(error => {
console.error('删除检验类型失败:', error);
ElMessage.error('删除失败: ' + (error.response?.data?.msg || '未知错误'));
if (error.response) {
console.error('响应状态:', error.response.status);
console.error('响应数据:', error.response.data);
} else if (error.request) {
console.error('请求发送但未收到响应:', error.request);
} else {
console.error('请求配置错误:', error.message);
}
});
} else {
// 删除临时新增的行
console.log('删除临时行ID:', numericId);
tableData.value = tableData.value.filter(row => Number(row.id) !== numericId);
ElMessage.success('删除成功');
}
}).catch(() => {
// 取消删除
});
};
// 检验项目相关方法
const addNewItem = () => {
if (editingRowId.value) {
ElMessage.warning('请先保存或取消当前正在编辑的行');
return;
}
const newItem = {
id: Date.now(),
code: '',
name: '',
testType: '',
package: '',
sampleType: '',
amount: 0.00,
sortOrder: inspectionItems.value.length + 1,
serviceRange: '全部',
sub医技Type: '',
remark: '',
status: true
};
inspectionItems.value.push(newItem);
editingRowId.value = newItem.id;
};
const editItem = (item) => {
if (editingRowId.value === item.id) {
// 如果当前行已经在编辑模式,点击编辑按钮则保存
saveItem(item);
} else {
// 否则进入编辑模式
editingRowId.value = item.id;
}
};
const updateAmountFromPackage = (item) => {
if (item.package) {
const selectedPackage = feePackages.value.find(pkg => pkg.value === item.package);
if (selectedPackage) {
item.amount = selectedPackage.amount;
}
} else {
item.amount = 0.00;
}
};
const saveItem = (item) => {
// 验证必填字段
if (!item.code || item.code.trim() === '') {
ElMessage.error('小类编码不能为空');
return;
}
// 验证小类编码格式4位数字
const codeRegex = /^\d{4}$/;
if (!codeRegex.test(item.code.trim())) {
ElMessage.error('小类编码必须为4位数字');
return;
}
if (!item.name || item.name.trim() === '') {
ElMessage.error('小类项目名称不能为空');
return;
}
if (!item.testType) {
ElMessage.error('检验类型不能为空');
return;
}
if (!item.sampleType) {
ElMessage.error('样本类型不能为空');
return;
}
// 验证小类编码唯一性
const isDuplicate = inspectionItems.value.some(i =>
i.id !== item.id && i.code.trim() === item.code.trim()
);
if (isDuplicate) {
ElMessage.error('小类编码已存在');
return;
}
// 从费用套餐获取金额
updateAmountFromPackage(item);
// 保存成功
editingRowId.value = null;
ElMessage.success('保存成功');
};
const deleteItem = (id) => {
ElMessageBox.confirm('确定要删除该检验项目吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const index = inspectionItems.value.findIndex(item => item.id === id);
if (index !== -1) {
inspectionItems.value.splice(index, 1);
ElMessage.success('删除成功');
}
}).catch(() => {
// 取消删除
});
};
const cancelEdit = (item) => {
// 如果是新添加的行,则直接删除
if (item.id.toString().length > 10) { // 临时ID使用Date.now()生成
const index = inspectionItems.value.findIndex(i => i.id === item.id);
if (index !== -1) {
inspectionItems.value.splice(index, 1);
}
}
editingRowId.value = null;
};
// 导出表格数据
const exportTable = () => {
// 将表格数据转换为CSV格式
const headers = ['行号', '小类编码', '小类项目名称', '检验类型', '费用套餐', '样本类型', '金额', '序号', '服务范围', '下级医技类型', '备注'];
const csvContent = [
headers.join(','),
...filteredInspectionItems.value.map((item, index) => [
index + 1,
`"${item.code}"`,
`"${item.name}"`,
`"${item.testType}"`,
`"${item.package}"`,
`"${item.sampleType}"`,
item.amount,
item.sortOrder || 999999,
`"${item.serviceRange || '全部'}"`,
`"${item.sub医技Type || '-'}"`,
`"${item.remark || '-'}"`
].join(','))
].join('\n');
// 创建Blob对象
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
// 创建下载链接
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', `检验项目导出_${new Date().toISOString().split('T')[0]}.csv`);
link.style.visibility = 'hidden';
// 添加到DOM并触发下载
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
ElMessage.success('导出成功');
};
// 套餐相关方法
const handleSave = () => {
// 验证套餐级别是否选择
if (!packageLevel.value) {
alert('请选择套餐级别');
return;
}
// 这里可以添加其他表单验证和保存逻辑
console.log('保存表单数据');
};
const handlePackageManagement = () => {
// 跳转到套餐管理页面
router.push({
path: '/maintainSystem/Inspection/PackageManagement'
});
};
const refreshPage = () => {
getInspectionTypeList();
};
// 页面加载时获取数据
onMounted(() => {
getInspectionTypeList();
getLisGroupList();
// 检查URL参数如果有tab参数则切换到对应导航项
const query = router.currentRoute.value.query;
if (query.tab === '0' || query.tab === '1' || query.tab === '2') {
activeNav.value = parseInt(query.tab);
}
// 初始化计算套餐金额和服务费
calculateAmounts();
});
// 监听生成服务费选项变更
watch(generateServiceFee, (newVal) => {
calculateAmounts();
});
// 监听套餐项目变化
watch(packageItems, (newVal) => {
calculateAmounts();
}, { deep: true });
</script>
<style>
/* 基础样式重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Microsoft YaHei", sans-serif;
}
/* 整体布局 */
.inspection-container {
display: flex;
width: 100%;
height: 100vh;
background-color: #f5f7fa;
}
/* 左侧导航 */
.side-nav {
width: 200px;
background-color: #fff;
box-shadow: 1px 0 5px rgba(0, 0, 0, 0.05);
padding: 20px 0;
}
.nav-item {
height: 40px;
display: flex;
align-items: center;
padding: 0 20px;
cursor: pointer;
transition: all 0.2s;
color: #333;
}
.nav-item:hover {
background-color: #f0f7ff;
color: #1890ff;
}
.nav-item.active {
background-color: #1890ff;
color: #fff;
}
/* 右侧主内容 */
.main-content {
flex: 1;
padding: 20px;
overflow-y: auto;
}
/* 页面标题 */
.page-header {
margin-bottom: 20px;
}
.page-header h2 {
font-size: 18px;
font-weight: 600;
color: #333;
}
/* 头部操作按钮 */
.header-actions {
display: flex;
justify-content: flex-start;
margin-bottom: 20px;
}
.add-new-btn {
height: 32px;
padding: 0 16px;
background-color: #1890ff;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
gap: 4px;
transition: all 0.2s;
}
.add-new-btn:hover {
background-color: #40a9ff;
}
/* 过滤区域 */
.filter-section {
background-color: #fff;
padding: 16px;
border-radius: 4px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
display: flex;
gap: 16px;
align-items: center;
margin-bottom: 16px;
}
.filter-item {
display: flex;
align-items: center;
gap: 8px;
}
.filter-item label {
font-size: 14px;
color: #333;
white-space: nowrap;
font-weight: 500;
}
.filter-select {
width: 120px;
height: 32px;
border: 1px solid #d9d9d9;
border-radius: 4px;
padding: 0 8px;
font-size: 14px;
background-color: #fff;
cursor: pointer;
transition: all 0.2s;
}
.filter-select:focus {
outline: none;
border-color: #1890ff;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
.filter-input {
width: 180px;
height: 32px;
border: 1px solid #d9d9d9;
border-radius: 4px;
padding: 0 8px;
font-size: 14px;
transition: all 0.2s;
}
.filter-input:focus {
outline: none;
border-color: #1890ff;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
.filter-actions {
display: flex;
gap: 8px;
margin-left: auto;
}
/* 按钮样式 */
.btn {
height: 32px;
padding: 0 16px;
border: 1px solid #d9d9d9;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
gap: 4px;
font-size: 14px;
transition: all 0.2s;
}
.btn-primary {
background-color: #1890ff;
color: #fff;
border-color: #1890ff;
}
.btn-primary:hover {
background-color: #40a9ff;
border-color: #40a9ff;
}
.btn-secondary {
background-color: #f5f5f5;
color: #333;
}
.btn-secondary:hover {
background-color: #e8e8e8;
}
/* 表格容器 */
.table-container {
background-color: #fff;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
overflow: hidden;
margin-bottom: 20px;
}
/* 数据表格 */
.data-table {
width: 100%;
border-collapse: collapse;
}
.data-table th,
.data-table td {
height: 36px;
padding: 0 12px;
text-align: left;
border-bottom: 1px solid #f0f0f0;
}
.data-table th {
background-color: #fafafa;
font-weight: 600;
color: #333;
white-space: nowrap;
}
.data-table tr:hover {
background-color: #fafafa;
}
.data-table tr.editing {
background-color: #f0f7ff;
box-shadow: 0 0 0 1px #1890ff;
}
/* 操作列 */
.action-cell {
display: flex;
justify-content: center;
gap: 8px;
position: relative;
z-index: 10;
}
.action-btn {
width: 28px;
height: 28px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border: none;
transition: all 0.2s ease;
font-size: 16px;
font-weight: bold;
position: relative;
z-index: 10;
background-color: #66b1ff;
color: white;
}
.confirm-btn {
background-color: #1890FF;
color: white;
}
.edit-btn {
background-color: #FFC107;
color: white;
font-size: 14px;
}
.add-btn {
background-color: #1890FF;
color: white;
}
.delete-btn {
background-color: #FF4D4F;
color: white;
font-size: 14px;
z-index: 20;
pointer-events: auto;
}
/* 分页 */
.pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
margin-top: 20px;
}
.page-btn {
padding: 6px 12px;
border: 1px solid #d9d9d9;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
}
.page-btn:hover {
border-color: #1890ff;
color: #1890ff;
}
.page-btn.active {
background-color: #1890ff;
color: #fff;
border-color: #1890ff;
}
/* 套餐设置样式 */
.top-bar {
height: 48px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 16px;
border-bottom: 1px solid #e8e8e8;
background: #fff;
margin-bottom: 16px;
gap: 16px;
}
.action-group {
display: flex;
gap: 12px;
}
.btn {
height: 32px;
padding: 0 16px;
border-radius: 4px;
font-weight: 500;
font-size: 14px;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
border: none;
transition: all 0.2s;
}
.btn-icon {
padding: 0 12px;
}
.btn-icon i {
margin-right: 4px;
}
.btn-primary {
background-color: #1890ff;
color: #fff;
}
.btn-success {
background-color: #00b27a;
color: #fff;
}
.btn-lg {
height: 36px;
padding: 0 24px;
font-size: 14px;
background-color: #0fb26d;
color: white;
transition: all 0.2s;
}
.btn-lg:hover {
background-color: #0d9d5f;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.btn-lg:active {
transform: translateY(1px);
}
/* 表单区域 */
.form-section {
background: #fff;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
padding: 16px;
margin-bottom: 16px;
}
.section-title {
font-weight: 700;
font-size: 14px;
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 1px solid #f0f0f0;
}
.form-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
}
.form-item {
display: flex;
align-items: center;
}
.form-label {
width: 80px;
text-align: right;
padding-right: 12px;
color: #666;
flex-shrink: 0;
}
/* 响应式设计 */
@media (max-width: 992px) {
/* 隐藏左侧导航 */
.side-nav {
display: none;
}
/* 主内容区域设置为100%宽度 */
.main-content {
margin-left: 0;
width: 100%;
padding: 10px;
}
/* 过滤区域垂直排列 */
.filter-section {
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
.filter-item {
width: 100%;
justify-content: flex-start;
}
/* 过滤操作按钮换行 */
.filter-actions {
width: 100%;
flex-wrap: wrap;
justify-content: flex-start;
margin-left: 0;
}
/* 调整按钮样式 */
.filter-actions .btn {
margin-right: 8px;
margin-bottom: 8px;
}
/* 调整表单网格布局 */
.form-grid {
grid-template-columns: repeat(2, 1fr);
}
/* 操作按钮换行显示 */
.filter-actions {
width: 100%;
flex-wrap: wrap;
justify-content: flex-start;
}
/* 表格容器在小屏幕上添加横向滚动 */
.table-container {
overflow-x: auto;
}
/* 数据表格在小屏幕上保持最小宽度 */
.data-table {
min-width: 1000px;
}
/* 调整表单标签宽度 */
.form-label {
width: 60px;
}
/* 调整输入框和选择框样式 */
.filter-input,
.filter-select {
width: calc(100% - 80px);
}
/* 表单网格调整为2列 */
.form-grid {
grid-template-columns: repeat(2, 1fr);
}
}
.form-control {
flex: 1;
height: 32px;
border: 1px solid #d9d9d9;
border-radius: 4px;
padding: 0 8px;
font-size: 14px;
}
.form-control:focus {
outline: none;
border-color: #1890ff;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
.form-select {
width: 100%;
background: white;
appearance: none;
}
.radio-group {
display: flex;
gap: 1px;
}
.radio-item {
display: flex;
align-items: center;
gap: 4px;
}
/* 表格头部 */
.table-header {
display: flex;
justify-content: space-between;
margin-bottom: 16px;
padding: 16px;
}
.table-title {
font-weight: 700;
font-size: 14px;
}
/* 表格内操作按钮 */
.table-actions {
display: flex;
gap: 8px;
}
/* 表格内输入框和选择框样式 */
.data-table input,
.data-table select {
width: 100%;
height: 28px;
padding: 0 8px;
border: 1px solid #d9d9d9;
border-radius: 4px;
font-size: 12px;
box-sizing: border-box;
}
.data-table input:focus,
.data-table select:focus {
outline: none;
border-color: #40a9ff;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
/* 编辑模式行样式 */
.data-table tr.editing {
background-color: #f0f7ff;
box-shadow: 0 0 0 1px #1890ff;
}
/* 响应式设计 */
@media (max-width: 1200px) {
.form-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 992px) {
.inspection-container {
flex-direction: column;
}
.side-nav {
display: none;
}
.main-content {
margin-left: 0;
width: 100%;
padding: 10px;
}
.filter-section {
flex-direction: column;
gap: 12px;
}
.filter-item {
width: 100%;
}
.filter-actions {
width: 100%;
justify-content: flex-start;
flex-wrap: wrap;
gap: 8px;
}
.form-grid {
grid-template-columns: 1fr;
}
.table-container {
overflow-x: auto;
}
.data-table {
min-width: 1000px;
}
}
</style>