1420 lines
48 KiB
Vue
1420 lines
48 KiB
Vue
<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
|
||
v-for="org in organizationOptions"
|
||
:key="org.value"
|
||
:label="org.label"
|
||
:value="org.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="packagePriceDisplay" :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" :disabled="isReadOnly">
|
||
<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" :disabled="isReadOnly">
|
||
<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" :disabled="isReadOnly">
|
||
<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" :disabled="isReadOnly">
|
||
<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%"
|
||
disabled
|
||
/>
|
||
</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="请输入备注" :disabled="isReadOnly" />
|
||
</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"
|
||
>
|
||
<div style="display: flex; justify-content: space-between; align-items: center; width: 100%;">
|
||
<span>{{ item.name || item.itemName }}</span>
|
||
<span style="display: flex; align-items: center; gap: 8px;">
|
||
<span style="color: #8492a6; font-size: 13px;">{{ item.busNo || item.code }}</span>
|
||
<span style="color: #E6A23C; font-size: 13px; font-weight: 500;">¥{{ item.retailPrice || item.price || item.unitPrice || 0 }}</span>
|
||
</span>
|
||
</div>
|
||
</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>
|
||
<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%"
|
||
:controls="false"
|
||
@change="calculateAmount(row)"
|
||
/>
|
||
<span v-else>{{ row.quantity }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="unit" label="单位" width="80" align="center">
|
||
<template #default="{ row }">
|
||
<span v-if="row.editing">{{ row.unit || '-' }}</span>
|
||
<span v-else>{{ row.unit || '-' }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="unitPrice" label="单价" width="150" align="center">
|
||
<template #default="{ row }">
|
||
<el-input
|
||
v-if="row.editing"
|
||
v-model="row.unitPrice"
|
||
placeholder="自动获取"
|
||
style="width: 100%"
|
||
size="small"
|
||
disabled
|
||
/>
|
||
<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%"
|
||
:controls="false"
|
||
@input="(val) => handleServiceChargeInput(val, 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="操作" v-if="!isReadOnly" width="150" align="center" fixed="right">
|
||
<template #default="{ row, $index }">
|
||
<div class="actions">
|
||
<el-button
|
||
v-if="!row.editing"
|
||
class="btn btn-edit"
|
||
size="small"
|
||
circle
|
||
@click="handleEditRow(row)"
|
||
title="编辑"
|
||
>
|
||
✏️
|
||
</el-button>
|
||
<el-button
|
||
v-if="row.editing"
|
||
class="btn btn-confirm"
|
||
size="small"
|
||
circle
|
||
@click="handleConfirmRow(row)"
|
||
title="保存"
|
||
>
|
||
✓
|
||
</el-button>
|
||
<el-button
|
||
class="btn btn-add"
|
||
size="small"
|
||
circle
|
||
@click="handleAddRow"
|
||
title="添加"
|
||
>
|
||
+
|
||
</el-button>
|
||
<el-button
|
||
class="btn btn-delete"
|
||
size="small"
|
||
circle
|
||
@click="handleDeleteRow($index)"
|
||
title="删除"
|
||
>
|
||
✕
|
||
</el-button>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import {computed, onMounted, reactive, ref, watch} from 'vue'
|
||
import {ElMessage, ElMessageBox} from 'element-plus'
|
||
import {getDicts} from '@/api/system/dict/data'
|
||
import {listDept} from '@/api/system/dept'
|
||
import {addCheckPackage, updateCheckPackage} from '@/api/system/checkType'
|
||
import {getDiagnosisTreatmentSimpleList} from '@/views/catalog/diagnosistreatment/components/diagnosistreatment'
|
||
import {getTenantPage} from '@/api/system/tenant'
|
||
import useUserStore from '@/store/modules/user'
|
||
import request from '@/utils/request'
|
||
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,
|
||
discount: '',
|
||
creator: userStore.name || '当前用户',
|
||
isDisabled: 0,
|
||
showPackageName: 1,
|
||
generateServiceFee: 0,
|
||
packagePriceEnabled: 1,
|
||
serviceFee: 0,
|
||
remark: '',
|
||
createDate: new Date().toISOString().split('T')[0] // 自动生成当前系统日期
|
||
})
|
||
|
||
// 套餐金额显示值(格式化后的字符串)
|
||
const packagePriceDisplay = computed({
|
||
get: () => formData.packagePrice?.toFixed(2) || '0.00',
|
||
set: (value) => {
|
||
// 只读,不允许直接修改
|
||
}
|
||
})
|
||
|
||
// 表单验证规则
|
||
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 organizationOptions = 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
|
||
|
||
console.log('=== 加载套餐数据 ===')
|
||
console.log('套餐数据:', data)
|
||
|
||
// 填充基本信息
|
||
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.items && Array.isArray(data.items)) {
|
||
detailData.value = data.items.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,
|
||
unit: item.unit || '',
|
||
amount: item.amount || 0,
|
||
serviceCharge: item.serviceCharge || 0,
|
||
total: item.total || 0,
|
||
origin: item.origin || '',
|
||
editing: false,
|
||
filteredList: diagnosisTreatmentList.value
|
||
}))
|
||
console.log('明细数据加载成功,条数:', detailData.value.length)
|
||
} else {
|
||
console.log('没有明细数据')
|
||
}
|
||
|
||
console.log('formData 加载后:', formData)
|
||
console.log('detailData 加载后:', detailData.value)
|
||
|
||
// 加载数据后自动计算总服务费
|
||
calculateTotalServiceFee()
|
||
}
|
||
|
||
onMounted(async () => {
|
||
console.log('=== PackageSettings 组件开始初始化 ===')
|
||
console.log('当前模式:', props.mode)
|
||
|
||
try {
|
||
// 新增模式初始化 formData
|
||
if (props.mode === 'add') {
|
||
console.log('新增模式:初始化 formData')
|
||
// 确保表单字段正确初始化
|
||
formData.id = null
|
||
formData.packageType = '检查套餐'
|
||
formData.packageLevel = ''
|
||
formData.department = ''
|
||
formData.user = ''
|
||
formData.packageName = ''
|
||
formData.organization = ''
|
||
formData.packagePrice = 0
|
||
formData.discount = ''
|
||
formData.isDisabled = 0
|
||
formData.showPackageName = 1
|
||
formData.generateServiceFee = 0
|
||
formData.packagePriceEnabled = 1
|
||
formData.serviceFee = 0
|
||
formData.remark = ''
|
||
formData.createDate = new Date().toISOString().split('T')[0]
|
||
// 清空明细数据并初始化一行
|
||
detailData.value = []
|
||
handleAddRow()
|
||
}
|
||
|
||
// 获取套餐级别字典
|
||
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)
|
||
}
|
||
}
|
||
|
||
// 加载诊疗项目列表(默认加载500条)
|
||
await loadDiagnosisTreatmentList(false)
|
||
|
||
// 加载卫生机构列表(只获取启用的租户)
|
||
await loadOrganizationList()
|
||
|
||
} catch (error) {
|
||
console.error('✗ 初始化数据失败:', error)
|
||
}
|
||
})
|
||
|
||
// 加载卫生机构列表(从租户列表获取,只获取启用的)
|
||
async function loadOrganizationList() {
|
||
try {
|
||
const response = await getTenantPage({
|
||
pageNo: 1,
|
||
pageSize: 1000,
|
||
status: '0'
|
||
})
|
||
|
||
if (response && response.code === 200) {
|
||
const records = response.data?.records || response.data || []
|
||
organizationOptions.value = records.map(item => ({
|
||
value: item.tenantName || item.name,
|
||
label: item.tenantName || item.name,
|
||
tenantId: item.tenantId
|
||
}))
|
||
console.log('✓ 卫生机构数据加载成功:', organizationOptions.value.length)
|
||
|
||
// 优先使用当前登录账号的机构名称作为默认值
|
||
const currentOrgName = userStore.tenantName
|
||
if (currentOrgName && organizationOptions.value.find(o => o.value === currentOrgName)) {
|
||
formData.organization = currentOrgName
|
||
} else if (!formData.organization && organizationOptions.value.length > 0) {
|
||
// 其次设置为列表第一个
|
||
formData.organization = organizationOptions.value[0].value
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('✗ 获取卫生机构列表失败:', error)
|
||
}
|
||
}
|
||
// 诊疗项目缓存key和过期时间
|
||
const DIAGNOSIS_TREATMENT_CACHE_KEY = 'check_package_diagnosis_treatment_cache'
|
||
const CACHE_EXPIRE_TIME = 30 * 60 * 1000 // 30分钟
|
||
|
||
// 诊疗项目ID到项目信息的映射缓存(用于选择后获取详情)
|
||
const diagnosisTreatmentCache = ref({})
|
||
|
||
// 加载诊疗项目详情到缓存
|
||
async function loadDiagnosisTreatmentItem(itemId, itemData) {
|
||
if (!itemData) return
|
||
diagnosisTreatmentCache.value[itemId] = itemData
|
||
}
|
||
|
||
// 加载诊疗项目列表(默认加载500条,支持缓存)
|
||
async function loadDiagnosisTreatmentList(forceRefresh = false) {
|
||
// 如果不是强制刷新且已有数据且未过期,直接返回
|
||
if (!forceRefresh && diagnosisTreatmentList.value.length > 0) {
|
||
// 由于缓存过期时间改为0,始终视为过期,需要重新加载
|
||
// 这里直接跳过,不返回,让它重新加载
|
||
}
|
||
|
||
// 从session缓存读取
|
||
let useCache = false
|
||
try {
|
||
const cachedData = cache.session.getJSON(DIAGNOSIS_TREATMENT_CACHE_KEY)
|
||
if (cachedData && cachedData.timestamp) {
|
||
const now = Date.now()
|
||
const cacheAge = now - cachedData.timestamp
|
||
|
||
if (cacheAge < CACHE_EXPIRE_TIME) {
|
||
diagnosisTreatmentList.value = cachedData.data || []
|
||
return
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('读取缓存失败:', error)
|
||
}
|
||
|
||
// 从API加载数据
|
||
loading.value = true
|
||
try {
|
||
const response = await getDiagnosisTreatmentSimpleList(2)
|
||
|
||
let allItems = []
|
||
if (response && response.code === 200 && response.data) {
|
||
allItems = Array.isArray(response.data) ? response.data : response.data.records || []
|
||
}
|
||
|
||
if (allItems.length > 0) {
|
||
diagnosisTreatmentList.value = allItems
|
||
|
||
// 保存到缓存
|
||
cache.session.setJSON(DIAGNOSIS_TREATMENT_CACHE_KEY, {
|
||
data: allItems,
|
||
timestamp: Date.now()
|
||
})
|
||
|
||
// 只在强制刷新时显示成功消息
|
||
if (forceRefresh) {
|
||
ElMessage.success(`成功加载${allItems.length}个诊疗项目`)
|
||
}
|
||
} else if (forceRefresh) {
|
||
ElMessage.warning('未获取到诊疗项目数据')
|
||
}
|
||
} catch (error) {
|
||
console.error('获取诊疗项目列表失败:', error)
|
||
if (forceRefresh) {
|
||
ElMessage.error('获取诊疗项目列表失败')
|
||
}
|
||
} 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 = []
|
||
// 自动获取当前登录账号名称赋值给用户选择字段
|
||
if (!formData.user && userStore.name) {
|
||
formData.user = userStore.name
|
||
}
|
||
} else {
|
||
// 全院套餐,都不必填
|
||
formRules.department = []
|
||
formRules.user = []
|
||
}
|
||
}
|
||
|
||
// 添加行
|
||
function handleAddRow() {
|
||
detailData.value.push({
|
||
code: '',
|
||
itemId: null,
|
||
itemName: '',
|
||
dose: '',
|
||
method: '',
|
||
frequency: '',
|
||
days: '',
|
||
quantity: 1,
|
||
unit: '',
|
||
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()
|
||
calculateTotalServiceFee()
|
||
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
|
||
}
|
||
|
||
// 初始化搜索列表(聚焦时显示默认加载的500条数据)
|
||
function initializeSearchList(row) {
|
||
row.filteredList = diagnosisTreatmentList.value
|
||
}
|
||
|
||
// 项目搜索处理(支持名称/编号/拼音模糊搜索)
|
||
async function handleProjectSearch(query, row) {
|
||
|
||
// 如果输入为空,显示默认列表
|
||
if (!query || query.trim() === '') {
|
||
row.filteredList = diagnosisTreatmentList.value
|
||
return
|
||
}
|
||
|
||
const searchText = query.trim()
|
||
|
||
// 调用后端接口搜索
|
||
loading.value = true
|
||
try {
|
||
const response = await getDiagnosisTreatmentSimpleList(2, searchText)
|
||
|
||
let items = []
|
||
if (response && response.code === 200 && response.data) {
|
||
items = Array.isArray(response.data) ? response.data : []
|
||
}
|
||
|
||
// 如果搜索结果为空,显示默认列表
|
||
if (items.length === 0) {
|
||
row.filteredList = diagnosisTreatmentList.value
|
||
} else {
|
||
row.filteredList = items
|
||
}
|
||
|
||
console.log(`搜索"${searchText}"找到 ${items.length} 个结果`)
|
||
} catch (error) {
|
||
console.error('搜索失败:', error)
|
||
row.filteredList = diagnosisTreatmentList.value
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
// 项目选择处理(从搜索结果或缓存中获取)
|
||
function handleItemSelect(row) {
|
||
console.log('选择项目ID:', row.itemId)
|
||
|
||
// 优先从搜索结果中获取
|
||
let item = row.filteredList?.find(i => i.id === row.itemId)
|
||
|
||
// 其次从缓存中获取
|
||
if (!item) {
|
||
item = diagnosisTreatmentCache.value[row.itemId]
|
||
}
|
||
|
||
// 最后从诊断列表中获取
|
||
if (!item) {
|
||
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)
|
||
// permittedUnitCode_dictText是字典翻译后的值,permittedUnitCode是后端返回的原始值
|
||
row.unit = item.permittedUnitCode_dictText || item.permittedUnitCode || ''
|
||
|
||
|
||
// 缓存选中的项目
|
||
loadDiagnosisTreatmentItem(row.itemId, item)
|
||
|
||
calculateAmount(row)
|
||
} else {
|
||
ElMessage.warning('未找到该项目信息')
|
||
}
|
||
}
|
||
|
||
// 计算金额
|
||
function calculateAmount(row) {
|
||
row.amount = (row.quantity || 0) * (row.unitPrice || 0)
|
||
calculateTotal(row)
|
||
}
|
||
|
||
// 处理服务费输入
|
||
function handleServiceChargeInput(val, row) {
|
||
row.serviceCharge = val || 0
|
||
calculateTotal(row)
|
||
}
|
||
|
||
// 计算总金额
|
||
function calculateTotal(row) {
|
||
row.total = (row.amount || 0) + (row.serviceCharge || 0)
|
||
calculatePackagePrice()
|
||
calculateTotalServiceFee()
|
||
}
|
||
|
||
// 计算总服务费(合计所有明细行的服务费)
|
||
function calculateTotalServiceFee() {
|
||
const totalServiceFee = detailData.value.reduce((sum, item) => sum + (item.serviceCharge || 0), 0)
|
||
formData.serviceFee = totalServiceFee
|
||
}
|
||
|
||
// 计算套餐金额(应用折扣)
|
||
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
|
||
}
|
||
|
||
// 折扣变更处理
|
||
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) => ({
|
||
// 基本字段(与检查套餐 CheckPackageDetail 对应)
|
||
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,
|
||
unit: item.unit || '',
|
||
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,
|
||
// 兼容字段(部分日志/历史代码使用的命名:dosage/route/serviceFee/totalAmount)
|
||
// 后端当前不会用到这些别名字段,但保留便于排查和兼容
|
||
dosage: item.dose || '',
|
||
route: item.method || '',
|
||
serviceFee: parseFloat(item.serviceCharge) || 0,
|
||
totalAmount: parseFloat(item.total) || 0
|
||
}))
|
||
}
|
||
|
||
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('套餐数据已保存')
|
||
emit('save-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;
|
||
}
|
||
|
||
/* 统一的操作按钮样式 */
|
||
.actions {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 6px;
|
||
position: relative;
|
||
z-index: 10;
|
||
}
|
||
|
||
.btn {
|
||
width: 30px;
|
||
height: 30px;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
border: none;
|
||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||
font-size: 14px;
|
||
font-weight: bold;
|
||
position: relative;
|
||
z-index: 10;
|
||
color: white;
|
||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12);
|
||
}
|
||
|
||
.btn:hover {
|
||
transform: translateY(-2px) scale(1.1);
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);
|
||
}
|
||
|
||
.btn:active {
|
||
transform: translateY(0) scale(0.95);
|
||
}
|
||
|
||
.btn-confirm {
|
||
background: linear-gradient(135deg, #52C41A 0%, #73d13d 100%);
|
||
}
|
||
|
||
.btn-confirm:hover {
|
||
background: linear-gradient(135deg, #389E0D 0%, #52C41A 100%);
|
||
}
|
||
|
||
.btn-edit {
|
||
background: linear-gradient(135deg, #1890FF 0%, #40a9ff 100%);
|
||
}
|
||
|
||
.btn-edit:hover {
|
||
background: linear-gradient(135deg, #096DD9 0%, #1890FF 100%);
|
||
}
|
||
|
||
.btn-add {
|
||
background: linear-gradient(135deg, #1890FF 0%, #40a9ff 100%);
|
||
}
|
||
|
||
.btn-add:hover {
|
||
background: linear-gradient(135deg, #096DD9 0%, #1890FF 100%);
|
||
}
|
||
|
||
.btn-delete {
|
||
background: linear-gradient(135deg, #FF4D4F 0%, #ff7875 100%);
|
||
z-index: 20;
|
||
pointer-events: auto;
|
||
}
|
||
|
||
.btn-delete:hover {
|
||
background: linear-gradient(135deg, #CF1322 0%, #FF4D4F 100%);
|
||
}
|
||
|
||
/* 响应式设计 */
|
||
@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>
|