Files
his/openhis-ui-vue3/src/views/maintainSystem/checkprojectSettings/components/PackageSettings.vue

1254 lines
43 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="package-settings">
<!-- 顶部操作按钮区 -->
<div class="header-actions">
<el-button type="primary" @click="handlePackageManagement">套餐设置</el-button>
<el-button type="primary" v-if="!isReadOnly" @click="handleRefresh" :loading="loading">刷新</el-button>
<el-button v-if="!isReadOnly" type="success" @click="handleSave">保存</el-button>
</div>
<!-- 基本信息表单区 -->
<div class="basic-info-section">
<h3 class="section-title">基本信息</h3>
<el-form
ref="basicFormRef"
:model="formData"
:rules="formRules"
label-width="120px"
class="basic-form"
>
<el-row :gutter="20">
<el-col :xs="24" :sm="12" :md="8" :lg="6">
<el-form-item label="套餐类别" prop="packageType">
<el-select v-model="formData.packageType" placeholder="请选择套餐类别" style="width: 100%" :disabled="isReadOnly">
<el-option label="检查套餐" value="检查套餐" />
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8" :lg="6">
<el-form-item label="套餐级别" prop="packageLevel">
<el-select
v-model="formData.packageLevel"
placeholder="请选择套餐级别"
style="width: 100%"
@change="handlePackageLevelChange"
:disabled="isReadOnly"
>
<el-option
v-for="item in packageLevelOptions"
:key="item.dictValue"
:label="item.dictLabel"
:value="item.dictValue"
/>
</el-select>
</el-form-item>
</el-col>
<el-col v-if="formData.packageLevel === '2'" :xs="24" :sm="12" :md="8" :lg="6">
<el-form-item label="科室选择" prop="department">
<el-select v-model="formData.department" placeholder="请选择科室" style="width: 100%" :disabled="isReadOnly">
<el-option
v-for="dept in departments"
:key="dept.deptCode || dept.dictValue"
:label="dept.dictLabel"
:value="dept.deptCode || dept.busNoPrefix || dept.rawOrg?.busNo || dept.dictValue"
/>
</el-select>
</el-form-item>
</el-col>
<el-col v-if="formData.packageLevel === '3'" :xs="24" :sm="12" :md="8" :lg="6">
<el-form-item label="用户选择" prop="user">
<el-select v-model="formData.user" placeholder="请选择用户" style="width: 100%" :disabled="isReadOnly">
<el-option label="当前用户" value="当前用户" />
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8" :lg="6">
<el-form-item label="套餐名称" prop="packageName">
<el-input v-model="formData.packageName" placeholder="请输入套餐名称" :disabled="isReadOnly" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8" :lg="6">
<el-form-item label="卫生机构">
<el-select v-model="formData.organization" placeholder="请选择卫生机构" style="width: 100%" :disabled="isReadOnly">
<el-option label="演示医院" value="演示医院" />
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8" :lg="6">
<el-form-item label="套餐金额">
<el-input v-model="formData.packagePrice" :disabled="true" placeholder="自动计算">
<template #append></template>
</el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8" :lg="6">
<el-form-item label="折扣">
<el-input
v-model="formData.discount"
placeholder="请输入折扣"
@input="handleDiscountChange"
:disabled="isReadOnly"
>
<template #append>%</template>
</el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8" :lg="6">
<el-form-item label="制单人">
<el-input v-model="formData.creator" :disabled="true" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8" :lg="6">
<el-form-item label="是否停用">
<el-radio-group v-model="formData.isDisabled">
<el-radio :value="0">启用</el-radio>
<el-radio :value="1">停用</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8" :lg="6">
<el-form-item label="显示套餐名">
<el-radio-group v-model="formData.showPackageName">
<el-radio :value="1"></el-radio>
<el-radio :value="0"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8" :lg="6">
<el-form-item label="生成服务费">
<el-radio-group v-model="formData.generateServiceFee">
<el-radio :value="1"></el-radio>
<el-radio :value="0"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8" :lg="6">
<el-form-item label="套餐价格">
<el-radio-group v-model="formData.packagePriceEnabled">
<el-radio :value="1">启用</el-radio>
<el-radio :value="0">不启用</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8" :lg="6">
<el-form-item label="服务费">
<el-input-number
v-model="formData.serviceFee"
:precision="2"
:min="0"
placeholder="请输入服务费"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8" :lg="6">
<el-form-item label="日期">
<el-date-picker
v-model="formData.createDate"
type="date"
placeholder="选择日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
:disabled="true"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12">
<el-form-item label="备注">
<el-input v-model="formData.remark" type="textarea" placeholder="请输入备注" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<!-- 套餐明细表格区 -->
<div class="package-detail-section">
<h3 class="section-title">套餐明细</h3>
<el-table
:data="detailData"
border
style="width: 100%"
:max-height="450"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="code" label="编号" width="120" align="center">
<template #default="{ row }">
<el-input v-if="row.editing" v-model="row.code" placeholder="请输入编号" />
<span v-else>{{ row.code }}</span>
</template>
</el-table-column>
<el-table-column prop="itemName" label="项目名称/规格" min-width="200">
<template #default="{ row }">
<div v-if="row.editing">
<el-select
v-model="row.itemId"
placeholder="输入名称/首字母/编号搜索"
filterable
remote
:remote-method="(query) => handleProjectSearch(query, row)"
style="width: 100%"
@change="handleItemSelect(row)"
@focus="initializeSearchList(row)"
:loading="diagnosisTreatmentList.length === 0"
clearable
>
<el-option
v-for="item in (row.filteredList || diagnosisTreatmentList)"
:key="item.id"
:label="(item.name || item.itemName || '未命名') + (item.busNo ? ' [' + item.busNo + ']' : '')"
:value="item.id"
>
<span style="float: left">{{ item.name || item.itemName }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">
{{ item.busNo || item.code }}
</span>
</el-option>
<template #empty>
<div style="padding: 10px; text-align: center; color: #999;">
<div v-if="diagnosisTreatmentList.length === 0">
暂无项目数据<br/>
<small>请先在【系统管理-目录管理-诊疗项目】中添加项目</small>
</div>
<div v-else>
无匹配项目<br/>
<small>支持:名称模糊搜索、首字母搜索、编号搜索</small>
</div>
</div>
</template>
</el-select>
<div style="font-size: 12px; color: #999; margin-top: 4px;">
共 {{ diagnosisTreatmentList.length }} 个可选项目
<span v-if="row.filteredList && row.filteredList.length < diagnosisTreatmentList.length">
(搜索结果: {{ row.filteredList.length }} 个)
</span>
</div>
</div>
<span v-else>{{ row.itemName }}</span>
</template>
</el-table-column>
<el-table-column prop="dose" label="剂量" width="100" align="center">
<template #default="{ row }">
<el-input
v-if="row.editing"
v-model="row.dose"
placeholder="-"
@input="validateTableNumberInput($event, row, 'dose')"
/>
<span v-else>{{ row.dose || '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="method" label="途径" width="80" align="center">
<template #default="{ row }">
<el-input v-if="row.editing" v-model="row.method" placeholder="-" />
<span v-else>{{ row.method || '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="frequency" label="频次" width="80" align="center">
<template #default="{ row }">
<el-input v-if="row.editing" v-model="row.frequency" placeholder="-" />
<span v-else>{{ row.frequency || '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="days" label="天数" width="100" align="center">
<template #default="{ row }">
<el-input
v-if="row.editing"
v-model="row.days"
placeholder="-"
@input="validateTableNumberInput($event, row, 'days')"
/>
<span v-else>{{ row.days || '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="quantity" label="数量" width="100" align="center">
<template #default="{ row }">
<el-input-number
v-if="row.editing"
v-model="row.quantity"
:min="0"
:precision="0"
placeholder="请输入数量"
style="width: 100%"
@change="calculateAmount(row)"
/>
<span v-else>{{ row.quantity }}</span>
</template>
</el-table-column>
<el-table-column prop="unitPrice" label="单价" width="120" align="center">
<template #default="{ row }">
<el-input-number
v-if="row.editing"
v-model="row.unitPrice"
:min="0"
:precision="6"
placeholder="请输入单价"
style="width: 100%"
@change="calculateAmount(row)"
/>
<span v-else>{{ row.unitPrice?.toFixed(6) }}</span>
</template>
</el-table-column>
<el-table-column prop="amount" label="金额" width="100" align="center">
<template #default="{ row }">
{{ row.amount?.toFixed(2) || '0.00' }}
</template>
</el-table-column>
<el-table-column prop="serviceCharge" label="服务费" width="100" align="center">
<template #default="{ row }">
<el-input-number
v-if="row.editing"
v-model="row.serviceCharge"
:min="0"
:precision="2"
placeholder="服务费"
style="width: 100%"
@change="calculateTotal(row)"
/>
<span v-else>{{ row.serviceCharge?.toFixed(2) || '0.00' }}</span>
</template>
</el-table-column>
<el-table-column prop="total" label="总金额" width="100" align="center">
<template #default="{ row }">
{{ row.total?.toFixed(2) || '0.00' }}
</template>
</el-table-column>
<el-table-column prop="origin" label="产地" width="120">
<template #default="{ row }">
<el-input v-if="row.editing" v-model="row.origin" placeholder="-" />
<span v-else>{{ row.origin || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="{ row, $index }">
<el-button
v-if="!row.editing"
type="primary"
size="small"
icon="Edit"
circle
@click="handleEditRow(row)"
/>
<el-button
v-if="row.editing"
type="success"
size="small"
icon="Check"
circle
@click="handleConfirmRow(row)"
/>
<el-button
type="primary"
size="small"
icon="Plus"
circle
@click="handleAddRow"
/>
<el-button
type="danger"
size="small"
icon="Delete"
circle
@click="handleDeleteRow($index)"
/>
</template>
</el-table-column>
</el-table>
</div>
</div>
</template>
<script setup>
import { ref, reactive, computed, onMounted, watch } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { ArrowLeft } from '@element-plus/icons-vue'
import { getDicts } from '@/api/system/dict/data'
import { listDept } from '@/api/system/dept'
import { addCheckPackage, updateCheckPackage } from '@/api/system/checkType'
import { getDiagnosisTreatmentList } from '@/views/catalog/diagnosistreatment/components/diagnosistreatment'
import useUserStore from '@/store/modules/user'
import request from '@/utils/request' // 导入request工具用于调用Organization API
import cache from '@/plugins/cache' // 导入缓存工具
// 接收props
const props = defineProps({
mode: {
type: String,
default: 'add', // add-新增, edit-编辑, view-查看
validator: (value) => ['add', 'edit', 'view'].includes(value)
},
packageData: {
type: Object,
default: null
}
})
// 定义emit
const emit = defineEmits(['switch-to-management', 'save-success'])
const userStore = useUserStore()
// 页面标题
const pageTitle = computed(() => {
switch (props.mode) {
case 'add':
return '套餐设置'
case 'edit':
return '编辑套餐'
case 'view':
return '查看套餐'
default:
return '套餐设置'
}
})
// 是否只读模式
const isReadOnly = computed(() => props.mode === 'view')
// 表单引用
const basicFormRef = ref(null)
// 表单数据
const formData = reactive({
id: null,
packageType: '检查套餐',
packageLevel: '',
department: '',
user: '',
packageName: '',
organization: '演示医院',
packagePrice: '0.00',
discount: '',
creator: userStore.name || '当前用户',
isDisabled: 0,
showPackageName: 1,
generateServiceFee: 0,
packagePriceEnabled: 1,
serviceFee: 0,
remark: '',
createDate: new Date().toISOString().split('T')[0] // 自动生成当前系统日期
})
// 表单验证规则
const formRules = {
packageType: [
{ required: true, message: '请选择套餐类别', trigger: 'change' }
],
packageLevel: [
{ required: true, message: '请选择套餐级别', trigger: 'change' }
],
packageName: [
{ required: true, message: '请输入套餐名称', trigger: 'blur', type: 'string' }
]
}
// 套餐级别选项
const packageLevelOptions = ref([])
// 科室选项
const departments = ref([])
// 诊疗项目列表
const diagnosisTreatmentList = ref([])
// 过滤后的诊疗项目列表(用于搜索)
const filteredDiagnosisList = ref([])
// 加载状态
const loading = ref(false)
// 明细数据
const detailData = ref([])
// 初始化数据
// 监听packageData变化加载数据
watch(() => props.packageData, (newData) => {
if (newData) {
loadPackageData(newData)
}
}, { immediate: true })
// 加载套餐数据到表单
function loadPackageData(data) {
if (!data) return
// 填充基本信息
Object.keys(formData).forEach(key => {
if (data[key] !== undefined) {
// 特殊处理科室字段:检查编码格式并匹配
if (key === 'department' && data[key]) {
const deptValue = String(data[key]).trim()
const isOldFormat = /^[A-Z]\d{2}$/.test(deptValue)
if (isOldFormat && departments.value.length > 0) {
// 旧编码格式:在下拉框中添加临时选项
const tempDept = {
deptCode: deptValue,
dictLabel: `旧编码: ${deptValue} (请重新选择)`,
dictValue: deptValue,
isOldFormat: true
}
const exists = departments.value.find(d => d.deptCode === deptValue)
if (!exists) {
departments.value.unshift(tempDept)
}
formData[key] = deptValue
} else {
formData[key] = data[key]
}
} else {
formData[key] = data[key]
}
}
})
// 填充明细数据
if (data.details && Array.isArray(data.details)) {
detailData.value = data.details.map(item => ({
code: item.itemCode || '',
itemId: item.checkItemId,
itemName: item.itemName || '',
dose: item.dose || '',
method: item.method || '',
frequency: item.frequency || '',
days: item.days || '',
quantity: item.quantity || 1,
unitPrice: item.unitPrice || 0,
amount: item.amount || 0,
serviceCharge: item.serviceCharge || 0,
total: item.total || 0,
origin: item.origin || '',
editing: false,
filteredList: diagnosisTreatmentList.value
}))
}
}
onMounted(async () => {
console.log('=== PackageSettings 组件开始初始化 ===')
try {
// 获取套餐级别字典
try {
const levelResponse = await getDicts('examination_item_package_level')
if (levelResponse && levelResponse.data) {
packageLevelOptions.value = levelResponse.data
console.log('✓ 套餐级别数据加载成功:', packageLevelOptions.value.length)
}
} catch (error) {
console.error('✗ 获取套餐级别字典失败:', error)
}
// 获取科室列表 - 使用Organization完整API包含编码和名称
try {
// 使用Organization完整API获取科室列表包含busNo编码
const orgResponse = await request({
url: '/base-data-manage/organization/organization',
method: 'get',
params: {
pageNo: 1,
pageSize: 1000 // 获取足够多的数据
}
})
console.log('科室Organization完整API响应:', orgResponse)
let orgList = []
if (orgResponse) {
// 处理分页响应格式:{ code: 200, data: { records: [...], total: ... } }
if (orgResponse.data) {
if (orgResponse.data.records && Array.isArray(orgResponse.data.records)) {
// 分页格式提取records
orgList = orgResponse.data.records
} else if (Array.isArray(orgResponse.data)) {
orgList = orgResponse.data
}
} else if (Array.isArray(orgResponse)) {
orgList = orgResponse
}
}
// 如果是树结构需要展开所有节点包括children并过滤出科室类型typeEnum=2
if (orgList && orgList.length > 0) {
const flattenList = []
function flatten(nodes) {
nodes.forEach(node => {
// 只包含科室类型typeEnum=2且有busNo的节点
if (node.typeEnum === 2 && node.busNo && node.name) {
flattenList.push(node)
}
if (node.children && node.children.length > 0) {
flatten(node.children)
}
})
}
flatten(orgList)
orgList = flattenList
console.log('展开后的科室列表包含busNo:', orgList.length, orgList.slice(0, 3).map(o => ({ busNo: o.busNo, name: o.name })))
}
if (orgList && orgList.length > 0) {
// 使用Organization数据包含编码和名称
departments.value = orgList.map(org => {
const busNo = (org.busNo || org.code || '').trim()
const name = (org.name || org.deptName || '').trim()
// 如果busNo是层级编码如 "A01.001"),同时存储前缀("A01")用于匹配
const busNoPrefix = busNo ? busNo.split('.')[0] : ''
return {
dictValue: name,
dictLabel: name,
deptId: org.id || org.deptId,
deptCode: busNo || name, // 优先使用busNo如果没有则使用名称
busNoPrefix: busNoPrefix, // 存储前缀用于匹配
rawOrg: org // 保存原始数据用于调试
}
})
console.log('✓ 科室数据加载成功Organization:', departments.value.length)
console.log('科室映射关系前5个:', departments.value.slice(0, 5).map(d => ({
deptCode: `"${d.deptCode}"`,
busNoPrefix: `"${d.busNoPrefix}"`,
name: d.dictLabel,
rawBusNo: d.rawOrg?.busNo
})))
} else {
console.warn('Organization API没有返回科室数据尝试使用系统部门API')
// 如果Organization API没有数据使用系统部门API
const deptResponse = await listDept()
console.log('科室API响应:', deptResponse)
let deptList = []
if (deptResponse) {
if (Array.isArray(deptResponse)) {
deptList = deptResponse
} else if (deptResponse.data) {
if (Array.isArray(deptResponse.data)) {
deptList = deptResponse.data
} else if (deptResponse.data.data && Array.isArray(deptResponse.data.data)) {
deptList = deptResponse.data.data
}
} else if (deptResponse.code === 200 && deptResponse.data) {
deptList = Array.isArray(deptResponse.data) ? deptResponse.data : []
}
}
if (deptList && deptList.length > 0) {
departments.value = deptList.map(dept => ({
dictValue: dept.deptName || dept.name,
dictLabel: dept.deptName || dept.name,
deptId: dept.deptId || dept.id,
deptCode: dept.deptName || dept.name // 如果没有编码,使用名称
}))
console.log('✓ 科室数据加载成功Dept:', departments.value.length)
} else {
console.warn('科室列表为空,尝试使用字典方式')
// 如果获取失败,尝试使用字典方式
try {
const dictResponse = await getDicts('dept')
if (dictResponse && dictResponse.data) {
departments.value = dictResponse.data
console.log('✓ 使用字典方式加载科室数据成功:', departments.value.length)
}
} catch (dictError) {
console.error('✗ 获取科室字典也失败:', dictError)
}
}
}
} catch (error) {
console.error('✗ 获取科室列表失败:', error)
// Fallback to dicts if API fails
try {
const dictResponse = await getDicts('dept')
if (dictResponse && dictResponse.data) {
departments.value = dictResponse.data
}
} catch (dictError) {
console.error('✗ 获取科室字典也失败:', dictError)
}
}
// 加载诊疗项目列表(优先使用缓存)
await loadDiagnosisTreatmentList(false)
// 初始化一行空数据
handleAddRow()
} catch (error) {
console.error('✗ 初始化数据失败:', error)
}
})
// 诊疗项目列表缓存key和过期时间30分钟
const DIAGNOSIS_TREATMENT_CACHE_KEY = 'check_package_diagnosis_treatment_list'
const CACHE_EXPIRE_TIME = 30 * 60 * 1000 // 30分钟
// 加载诊疗项目列表(支持缓存)
async function loadDiagnosisTreatmentList(forceRefresh = false) {
// 如果不是强制刷新,先尝试从缓存加载
if (!forceRefresh) {
try {
const cachedData = cache.session.getJSON(DIAGNOSIS_TREATMENT_CACHE_KEY)
if (cachedData && cachedData.data && cachedData.timestamp) {
const now = Date.now()
const cacheAge = now - cachedData.timestamp
// 检查缓存是否过期
if (cacheAge < CACHE_EXPIRE_TIME) {
diagnosisTreatmentList.value = cachedData.data
// 静默加载,不显示消息
return
}
}
} catch (error) {
// 缓存读取失败继续从API加载
console.error('读取缓存失败:', error)
}
}
// 从API加载数据
loading.value = true
try {
let allItems = []
const maxBatches = 3
const batchSize = 50
for (let page = 1; page <= maxBatches; page++) {
try {
const treatmentResponse = await getDiagnosisTreatmentList({
pageNo: page,
pageSize: batchSize
})
let batchData = []
if (treatmentResponse) {
if (treatmentResponse.data && treatmentResponse.data.records) {
batchData = treatmentResponse.data.records
} else if (treatmentResponse.data && Array.isArray(treatmentResponse.data)) {
batchData = treatmentResponse.data
} else if (treatmentResponse.records && Array.isArray(treatmentResponse.records)) {
batchData = treatmentResponse.records
}
}
if (batchData.length > 0) {
allItems = allItems.concat(batchData)
if (batchData.length < batchSize) {
break
}
} else {
break
}
} catch (batchError) {
console.error(`第${page}批加载失败:`, batchError)
if (page === 1) {
throw batchError
}
break
}
}
if (allItems.length > 0) {
diagnosisTreatmentList.value = allItems
// 保存到缓存
try {
cache.session.setJSON(DIAGNOSIS_TREATMENT_CACHE_KEY, {
data: allItems,
timestamp: Date.now()
})
} catch (cacheError) {
console.error('保存缓存失败:', cacheError)
}
// 只在强制刷新时显示成功消息
if (forceRefresh) {
ElMessage.success(`成功加载${allItems.length}个诊疗项目`)
}
} else {
if (forceRefresh) {
ElMessage.warning({
message: '未获取到诊疗项目数据。请先在【系统管理-目录管理-诊疗项目】中添加数据',
duration: 6000,
showClose: true
})
}
}
} catch (error) {
console.error('获取诊疗项目列表失败:', error)
if (forceRefresh) {
ElMessage.error({
message: '获取诊疗项目列表失败,请检查网络连接或联系管理员',
duration: 6000,
showClose: true
})
}
} finally {
loading.value = false
}
}
// 套餐级别变更处理
function handlePackageLevelChange(value) {
// 重置关联字段
if (value !== '2') {
formData.department = ''
}
if (value !== '3') {
formData.user = ''
}
// 动态更新验证规则
if (value === '2') {
// 科室套餐,科室必填
formRules.department = [{ required: true, message: '请选择科室', trigger: 'change' }]
formRules.user = []
} else if (value === '3') {
// 个人套餐,用户必填
formRules.user = [{ required: true, message: '请选择用户', trigger: 'change' }]
formRules.department = []
} else {
// 全院套餐,都不必填
formRules.department = []
formRules.user = []
}
}
// 添加行
function handleAddRow() {
detailData.value.push({
code: '',
itemId: null,
itemName: '',
dose: '',
method: '',
frequency: '',
days: '',
quantity: 1,
unitPrice: 0,
amount: 0,
serviceCharge: 0,
total: 0,
origin: '',
editing: true,
filteredList: diagnosisTreatmentList.value // 初始化过滤列表
})
}
// 编辑行
function handleEditRow(row) {
row.editing = true
}
// 确认编辑
function handleConfirmRow(row) {
if (!row.itemName) {
ElMessage.warning('请选择项目')
return
}
if (!row.quantity || row.quantity <= 0) {
ElMessage.warning('请输入有效数量')
return
}
row.editing = false
}
// 删除行
function handleDeleteRow(index) {
ElMessageBox.confirm('确定要删除这一行吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
detailData.value.splice(index, 1)
calculatePackagePrice()
ElMessage.success('删除成功')
}).catch(() => {})
}
// 获取拼音首字母
function getPinYinFirstLetter(str) {
if (!str) return ''
// 简单的拼音首字母映射表(常用汉字)
const pinyinMap = {
'啊': 'A', '阿': 'A', '癌': 'A', '按': 'A', '暗': 'A',
'八': 'B', '白': 'B', '百': 'B', '班': 'B', '帮': 'B', '保': 'B', '报': 'B', '杯': 'B', '本': 'B', '鼻': 'B', '比': 'B', '变': 'B', '标': 'B', '表': 'B', '病': 'B', '部': 'B', '不': 'B',
'彩': 'C', '超': 'C', '查': 'C', '常': 'C', '肠': 'C', '成': 'C', '穿': 'C', '床': 'C', '次': 'C',
'大': 'D', '带': 'D', '单': 'D', '胆': 'D', '蛋': 'D', '导': 'D', '道': 'D', '的': 'D', '低': 'D', '地': 'D', '电': 'D', '点': 'D', '定': 'D', '动': 'D', '度': 'D', '多': 'D',
'二': 'E', '耳': 'E',
'发': 'F', '法': 'F', '反': 'F', '防': 'F', '房': 'F', '肺': 'F', '分': 'F', '风': 'F', '腹': 'F', '妇': 'F', '复': 'F',
'肝': 'G', '感': 'G', '高': 'G', '骨': 'G', '功': 'G', '宫': 'G', '管': 'G', '光': 'G', '广': 'G', '过': 'G',
'核': 'H', '黑': 'H', '红': 'H', '后': 'H', '化': 'H', '换': 'H', '患': 'H', '黄': 'H', '回': 'H', '会': 'H',
'基': 'J', '及': 'J', '急': 'J', '疾': 'J', '甲': 'J', '检': 'J', '简': 'J', '见': 'J', '降': 'J', '交': 'J', '结': 'J', '介': 'J', '金': 'J', '进': 'J', '经': 'J', '精': 'J', '颈': 'J', '静': 'J',
'开': 'K', '抗': 'K', '康': 'K', '科': 'K', '可': 'K', '口': 'K',
'来': 'L', '类': 'L', '理': 'L', '力': 'L', '例': 'L', '粒': 'L', '连': 'L', '量': 'L', '疗': 'L', '淋': 'L', '临': 'L', '流': 'L', '瘤': 'L', '路': 'L', '卵': 'L',
'慢': 'M', '门': 'M', '免': 'M', '面': 'M', '敏': 'M', '明': 'M', '模': 'M', '目': 'M',
'内': 'N', '脑': 'N', '尿': 'N', '凝': 'N', '女': 'N',
'排': 'P', '培': 'P', '皮': 'P', '片': 'P', '平': 'P', '普': 'P',
'期': 'Q', '其': 'Q', '前': 'Q', '强': 'Q', '切': 'Q', '清': 'Q', '球': 'Q', '全': 'Q',
'染': 'R', '热': 'R', '人': 'R', '容': 'R', '溶': 'R', '肉': 'R', '入': 'R', '乳': 'R',
'三': 'S', '色': 'S', '扫': 'S', '伤': 'S', '上': 'S', '少': 'S', '肾': 'S', '生': 'S', '声': 'S', '时': 'S', '实': 'S', '室': 'S', '试': 'S', '视': 'S', '手': 'S', '术': 'S', '双': 'S', '水': 'S', '速': 'S', '酸': 'S',
'胎': 'T', '糖': 'T', '套': 'T', '特': 'T', '体': 'T', '铁': 'T', '听': 'T', '通': 'T', '痛': 'T', '头': 'T', '图': 'T',
'外': 'W', '胃': 'W', '卫': 'W', '温': 'W', '五': 'W', '无': 'W',
'西': 'X', '洗': 'X', '系': 'X', '细': 'X', '下': 'X', '显': 'X', '线': 'X', '腺': 'X', '相': 'X', '消': 'X', '小': 'X', '心': 'X', '新': 'X', '型': 'X', '性': 'X', '胸': 'X', '血': 'X',
'压': 'Y', '炎': 'Y', '眼': 'Y', '验': 'Y', '腰': 'Y', '药': 'Y', '液': 'Y', '一': 'Y', '医': 'Y', '疫': 'Y', '阴': 'Y', '引': 'Y', '应': 'Y', '影': 'Y', '用': 'Y', '右': 'Y', '于': 'Y', '预': 'Y', '原': 'Y', '月': 'Y', '孕': 'Y', '运': 'Y',
'早': 'Z', '增': 'Z', '诊': 'Z', '正': 'Z', '症': 'Z', '支': 'Z', '脂': 'Z', '指': 'Z', '质': 'Z', '治': 'Z', '中': 'Z', '肿': 'Z', '重': 'Z', '注': 'Z', '主': 'Z', '状': 'Z', '子': 'Z', '自': 'Z', '总': 'Z', '组': 'Z', '左': 'Z', '做': 'Z'
}
let result = ''
for (let i = 0; i < str.length; i++) {
const char = str[i]
if (pinyinMap[char]) {
result += pinyinMap[char]
} else if (/[a-zA-Z]/.test(char)) {
result += char.toUpperCase()
} else if (/[0-9]/.test(char)) {
result += char
}
}
return result
}
// 初始化搜索列表
function initializeSearchList(row) {
if (!row.filteredList) {
row.filteredList = diagnosisTreatmentList.value
}
}
// 项目搜索处理(支持首字母和模糊搜索)
function handleProjectSearch(query, row) {
console.log('搜索关键字:', query)
if (!query || query.trim() === '') {
row.filteredList = diagnosisTreatmentList.value
return
}
const searchText = query.trim().toUpperCase()
row.filteredList = diagnosisTreatmentList.value.filter(item => {
const name = (item.name || item.itemName || '').toUpperCase()
const code = (item.busNo || item.code || '').toUpperCase()
// 1. 名称模糊匹配
if (name.includes(searchText)) {
return true
}
// 2. 编号模糊匹配
if (code.includes(searchText)) {
return true
}
// 3. 拼音首字母匹配
const pinyin = getPinYinFirstLetter(item.name || item.itemName || '')
if (pinyin.includes(searchText)) {
return true
}
return false
})
console.log(`搜索"${query}"找到 ${row.filteredList.length} 个结果`)
}
// 项目选择处理
function handleItemSelect(row) {
console.log('选择项目ID:', row.itemId)
const item = diagnosisTreatmentList.value.find(i => i.id === row.itemId)
console.log('找到的项目:', item)
if (item) {
row.itemName = item.name || item.itemName || ''
row.code = item.busNo || item.code || item.itemCode || ''
row.unitPrice = parseFloat(item.retailPrice || item.unitPrice || item.price || 0)
console.log('设置单价:', row.unitPrice)
calculateAmount(row)
} else {
ElMessage.warning('未找到该项目信息')
}
}
// 计算金额
function calculateAmount(row) {
row.amount = (row.quantity || 0) * (row.unitPrice || 0)
calculateTotal(row)
}
// 计算总金额
function calculateTotal(row) {
row.total = (row.amount || 0) + (row.serviceCharge || 0)
calculatePackagePrice()
}
// 计算套餐金额(应用折扣)
function calculatePackagePrice() {
// 计算所有明细项目的总金额
const total = detailData.value.reduce((sum, item) => sum + (item.total || 0), 0)
// 如果有折扣,应用折扣计算
let finalPrice = total
if (formData.discount && parseFloat(formData.discount) > 0 && parseFloat(formData.discount) <= 100) {
const discountRate = parseFloat(formData.discount) / 100 // 将百分比转换为小数如10% -> 0.1
const discountAmount = total * discountRate // 折扣金额
finalPrice = total - discountAmount // 折扣后金额
}
formData.packagePrice = finalPrice.toFixed(2)
}
// 折扣变更处理
function handleDiscountChange(value) {
// 验证并清理输入
const regex = /^\d*\.?\d*$/
if (!regex.test(value)) {
formData.discount = value.replace(/[^\d.]/g, '')
return
}
// 验证折扣范围
const discountValue = parseFloat(formData.discount)
if (discountValue && (discountValue < 0 || discountValue > 100)) {
ElMessage.warning('折扣必须在0-100之间')
// 重置为有效值
if (discountValue < 0) {
formData.discount = '0'
} else if (discountValue > 100) {
formData.discount = '100'
}
}
// 重新计算套餐金额
calculatePackagePrice()
}
// 套餐管理
// 套餐管理 - 切换到套餐管理界面
function handlePackageManagement() {
emit('switch-to-management')
}
// 刷新 - 清除缓存并重新加载诊疗项目数据
async function handleRefresh() {
// 清除缓存
cache.session.remove(DIAGNOSIS_TREATMENT_CACHE_KEY)
// 强制刷新加载
await loadDiagnosisTreatmentList(true)
}
// 数字输入验证 - 用于表单字段
function validateNumberInput(value, field) {
// 只允许数字和小数点
const regex = /^\d*\.?\d*$/
if (!regex.test(value)) {
formData[field] = value.replace(/[^\d.]/g, '')
}
}
// 数字输入验证 - 用于表格字段
function validateTableNumberInput(value, row, field) {
// 只允许数字和小数点
const regex = /^\d*\.?\d*$/
if (!regex.test(value)) {
row[field] = value.replace(/[^\d.]/g, '')
}
}
// 保存
async function handleSave() {
try {
console.log('开始保存,当前表单数据:', JSON.parse(JSON.stringify(formData)))
// 验证基本信息
try {
await basicFormRef.value.validate()
console.log('✓ 基本信息验证通过')
} catch (validationError) {
console.error('✗ 表单验证失败:', validationError)
ElMessage.error('请完善必填项:' + Object.keys(validationError).join(', '))
return
}
// 验证明细数据
if (detailData.value.length === 0) {
ElMessage.warning('请至少添加一条套餐明细')
return
}
console.log('明细数据条数:', detailData.value.length)
// 检查是否有未确认的编辑行
const hasEditingRow = detailData.value.some(row => row.editing)
if (hasEditingRow) {
ElMessage.warning('请先确认所有正在编辑的行')
return
}
console.log('✓ 所有行已确认')
// 检查明细数据完整性
const hasEmptyItem = detailData.value.some(row => !row.itemName || !row.quantity)
if (hasEmptyItem) {
console.error('✗ 存在空项目:', detailData.value.filter(row => !row.itemName || !row.quantity))
ElMessage.warning('请完善所有明细项目信息(项目名称和数量为必填项)')
return
}
console.log('✓ 明细数据完整性验证通过')
// 更新创建日期为当前系统时间
formData.createDate = new Date().toISOString().split('T')[0]
// 验证折扣范围
if (formData.discount && (parseFloat(formData.discount) < 0 || parseFloat(formData.discount) > 100)) {
ElMessage.warning('折扣必须在0-100之间')
return
}
// 构建保存数据
const saveData = {
id: formData.id,
packageName: String(formData.packageName || ''),
code: formData.code || '',
description: formData.description || '',
packageType: formData.packageType,
packageLevel: formData.packageLevel,
department: formData.department ? String(formData.department).trim() : '',
user: formData.user || '',
organization: formData.organization,
packagePrice: parseFloat(formData.packagePrice) || 0,
discount: formData.discount ? parseFloat(formData.discount) : null,
creator: formData.creator,
isDisabled: formData.isDisabled,
showPackageName: formData.showPackageName,
generateServiceFee: formData.generateServiceFee,
packagePriceEnabled: formData.packagePriceEnabled,
serviceFee: parseFloat(formData.serviceFee) || 0,
remark: formData.remark || '',
createDate: formData.createDate,
items: detailData.value.map((item, index) => ({
itemCode: item.code || '',
itemName: item.itemName || '',
checkItemId: item.itemId || null,
dose: item.dose || '',
method: item.method || '',
frequency: item.frequency || '',
days: item.days || '',
quantity: parseInt(item.quantity) || 1,
unitPrice: parseFloat(item.unitPrice) || 0,
amount: parseFloat(item.amount) || 0,
serviceCharge: parseFloat(item.serviceCharge) || 0,
total: parseFloat(item.total) || 0,
origin: item.origin || '',
orderNum: index + 1
}))
}
console.log('构建的保存数据:', JSON.parse(JSON.stringify(saveData)))
// 调用API保存 - 根据是否有ID判断新增还是更新
let response
try {
if (formData.id) {
console.log('执行更新操作...')
response = await updateCheckPackage(saveData)
} else {
console.log('执行新增操作...')
response = await addCheckPackage(saveData)
}
console.log('API响应:', response)
if (response && (response.code === 200 || response.code === 0)) {
ElMessage.success('套餐数据已保存')
// 如果有返回ID更新formData.id以便后续更新
if (response.data && !formData.id) {
formData.id = response.data
console.log('新增成功套餐ID:', formData.id)
}
} else {
console.error('保存失败,响应:', response)
ElMessage.error(response?.msg || response?.message || '保存失败,请检查表单数据')
}
} catch (apiError) {
console.error('API调用失败:', apiError)
throw apiError
}
} catch (error) {
console.error('保存过程出错:', error)
if (error !== 'cancel') {
const errorMsg = error.response?.data?.msg || error.message || '保存失败,请检查表单数据'
ElMessage.error('保存失败: ' + errorMsg)
}
}
}
</script>
<style scoped>
.package-settings {
padding: 24px;
background-color: #f5f7fa;
}
.header-actions {
display: flex;
gap: 12px;
margin-bottom: 24px;
}
.basic-info-section,
.package-detail-section {
background: white;
border-radius: 8px;
padding: 24px;
margin-bottom: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
}
.section-title {
font-size: 16px;
font-weight: 500;
color: #000000;
margin: 0 0 16px 0;
padding-bottom: 12px;
border-bottom: 1px solid #e8e8e8;
}
.basic-form {
margin-top: 16px;
}
.el-table {
margin-top: 16px;
}
/* 响应式设计 */
@media (max-width: 768px) {
.package-settings {
padding: 16px;
}
.header-actions {
flex-direction: column;
}
.header-actions .el-button {
width: 100%;
}
.basic-info-section,
.package-detail-section {
padding: 16px;
}
}
</style>