检查项目设置->套餐设置->套餐管理

This commit is contained in:
2025-11-27 08:44:32 +08:00
parent 1bd2089047
commit 4120d4e001
5 changed files with 980 additions and 45 deletions

View File

@@ -0,0 +1,663 @@
<template>
<div class="package-management">
<!-- 顶部筛选栏 -->
<div class="filter-section">
<el-form :model="queryParams" :inline="true" label-width="80px">
<el-form-item label="日期">
<el-date-picker
v-model="dateRange"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 240px"
/>
</el-form-item>
<el-form-item label="卫生机构">
<el-select v-model="queryParams.organization" placeholder="请选择机构" style="width: 150px" clearable>
<el-option label="演示医院" value="演示医院" />
</el-select>
</el-form-item>
<el-form-item label="套餐名称">
<el-input
v-model="queryParams.packageName"
placeholder="请输入套餐名称"
style="width: 200px"
clearable
/>
</el-form-item>
<el-form-item label="套餐级别">
<el-select v-model="queryParams.packageLevel" placeholder="请选择套餐级别" style="width: 150px" clearable>
<el-option
v-for="item in packageLevelOptions"
:key="item.dictValue"
:label="item.dictLabel"
:value="item.dictValue"
/>
</el-select>
</el-form-item>
<el-form-item label="套餐类别">
<el-select v-model="queryParams.packageType" placeholder="请选择套餐类别" style="width: 150px" clearable>
<el-option label="检查套餐" value="检查套餐" />
</el-select>
</el-form-item>
<el-form-item label="科室">
<el-select v-model="queryParams.department" placeholder="请选择科室" style="width: 150px" clearable filterable>
<el-option
v-for="dept in departments"
:key="dept.dictValue"
:label="dept.dictLabel"
:value="dept.dictLabel"
/>
</el-select>
</el-form-item>
<el-form-item label="用户">
<el-input
v-model="queryParams.user"
placeholder="请输入用户名称"
style="width: 150px"
clearable
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery" icon="Search">查询</el-button>
<el-button @click="handleReset" icon="Refresh">重置</el-button>
<el-button type="success" @click="handleAdd" icon="Plus">新增</el-button>
</el-form-item>
</el-form>
</div>
<!-- 表格展示区 -->
<div class="table-section">
<el-table
:data="tableData"
border
style="width: 100%"
v-loading="loading"
:max-height="600"
>
<el-table-column prop="id" label="ID" width="80" align="center" />
<el-table-column prop="organization" label="卫生机构" width="120" align="center" />
<el-table-column prop="maintainDate" label="日期" width="120" align="center" />
<el-table-column prop="packageName" label="套餐名称" min-width="150" show-overflow-tooltip />
<el-table-column prop="packageType" label="套餐类别" width="100" align="center" />
<el-table-column prop="packageLevel" label="套餐级别" width="100" align="center">
<template #default="{ row }">
{{ getLevelLabel(row.packageLevel) }}
</template>
</el-table-column>
<el-table-column prop="department" label="科室" width="120" align="center">
<template #default="{ row }">
{{ getDeptName(row.department) }}
</template>
</el-table-column>
<el-table-column prop="user" label="用户" width="100" align="center" />
<el-table-column prop="packagePrice" label="金额" width="100" align="center">
<template #default="{ row }">
{{ (row.packagePrice || 0).toFixed(2) }}
</template>
</el-table-column>
<el-table-column prop="serviceFee" label="服务费" width="100" align="center">
<template #default="{ row }">
{{ (row.serviceFee || 0).toFixed(2) }}
</template>
</el-table-column>
<el-table-column label="总金额" width="100" align="center">
<template #default="{ row }">
{{ ((row.packagePrice || 0) + (row.serviceFee || 0)).toFixed(2) }}
</template>
</el-table-column>
<el-table-column label="组合套餐" width="100" align="center">
<template #default="{ row }">
<el-tag :type="row.packagePriceEnabled === 1 ? 'success' : 'danger'">
{{ row.packagePriceEnabled === 1 ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="显示套餐名" width="110" align="center">
<template #default="{ row }">
<el-tag :type="row.showPackageName === 1 ? 'success' : 'info'">
{{ row.showPackageName === 1 ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="启用标志" width="100" align="center">
<template #default="{ row }">
<el-tag :type="row.isDisabled === 0 ? 'success' : 'danger'">
{{ row.isDisabled === 0 ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="creator" label="操作人" width="100" align="center" />
<el-table-column label="操作" width="180" align="center" fixed="right">
<template #default="{ row }">
<el-button
type="primary"
size="small"
icon="Edit"
circle
@click="handleEdit(row)"
title="编辑"
/>
<el-button
type="info"
size="small"
icon="View"
circle
@click="handleView(row)"
title="查看"
/>
<el-button
type="danger"
size="small"
icon="Delete"
circle
@click="handleDelete(row)"
title="删除"
/>
</template>
</el-table-column>
</el-table>
</div>
<!-- 底部分页 -->
<div class="pagination-section">
<el-pagination
v-model:current-page="queryParams.pageNo"
v-model:page-size="queryParams.pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleQuery"
@current-change="handleQuery"
/>
</div>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getDicts } from '@/api/system/dict/data'
import { listDept } from '@/api/system/dept'
import { listCheckPackage, getCheckPackage, delCheckPackage } from '@/api/system/checkType'
import request from '@/utils/request'
// 定义emit事件
const emit = defineEmits(['switch-to-settings'])
// 查询参数
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
organization: '',
packageName: '',
packageLevel: '',
packageType: '',
department: '',
user: ''
})
// 日期范围
const dateRange = ref([])
// 表格数据
const tableData = ref([])
const total = ref(0)
const loading = ref(false)
// 套餐级别选项
const packageLevelOptions = ref([])
// 科室选项
const departments = ref([])
// 初始化数据
onMounted(async () => {
// 获取套餐级别字典
try {
const levelResponse = await getDicts('examination_item_package_level')
if (levelResponse && levelResponse.data) {
packageLevelOptions.value = levelResponse.data
}
} catch (error) {
console.error('获取套餐级别字典失败:', error)
}
// 获取科室列表 - 优先使用Organization API包含编码和名称
try {
// 尝试使用Organization API获取科室列表包含编码
const orgResponse = await request({
url: '/app-common/department-list',
method: 'get'
})
console.log('科室Organization API响应:', orgResponse)
let orgList = []
if (orgResponse) {
if (Array.isArray(orgResponse)) {
orgList = orgResponse
} else if (orgResponse.data) {
if (Array.isArray(orgResponse.data)) {
orgList = orgResponse.data
} else if (orgResponse.data.data && Array.isArray(orgResponse.data.data)) {
orgList = orgResponse.data.data
}
} else if (orgResponse.code === 200 && orgResponse.data) {
orgList = Array.isArray(orgResponse.data) ? orgResponse.data : []
}
}
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}"`,
codeLength: d.deptCode ? d.deptCode.length : 0,
name: d.dictLabel,
rawBusNo: d.rawOrg?.busNo
})))
} else {
// 如果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, departments.value)
} else {
console.warn('科室列表为空,尝试使用字典方式')
// 如果获取失败,尝试使用字典方式
try {
const dictResponse = await getDicts('dept')
console.log('科室字典响应:', dictResponse)
if (dictResponse && dictResponse.data) {
departments.value = dictResponse.data
console.log('使用字典方式加载科室数据成功:', departments.value.length)
}
} catch (dictError) {
console.error('获取科室字典也失败:', dictError)
}
}
}
} catch (error) {
console.error('获取科室列表失败:', error)
// 如果获取失败,尝试使用字典方式
try {
const dictResponse = await getDicts('dept')
if (dictResponse && dictResponse.data) {
departments.value = dictResponse.data
}
} catch (dictError) {
console.error('获取科室字典也失败:', dictError)
}
}
// 加载列表数据
handleQuery()
})
// 获取级别标签
function getLevelLabel(value) {
const item = packageLevelOptions.value.find(i => i.dictValue === value)
return item ? item.dictLabel : value
}
// 获取科室名称(根据编码或名称查找)
function getDeptName(deptValue) {
if (!deptValue) return ''
// 去除前后空格
const trimmedValue = String(deptValue).trim()
if (!trimmedValue) return ''
// 如果科室列表为空,直接返回原值
if (!departments.value || departments.value.length === 0) {
return trimmedValue
}
// 先尝试精确匹配编码(去除所有空格,转大写)
let dept = departments.value.find(d => {
const code = String(d.deptCode || '').trim().replace(/\s+/g, '').toUpperCase()
const searchValue = trimmedValue.replace(/\s+/g, '').toUpperCase()
return code && code === searchValue
})
// 如果找不到尝试通过busNo前缀匹配优先使用存储的前缀
if (!dept) {
dept = departments.value.find(d => {
const busNoPrefix = String(d.busNoPrefix || '').trim().replace(/\s+/g, '').toUpperCase()
const searchValue = trimmedValue.replace(/\s+/g, '').toUpperCase()
return busNoPrefix && busNoPrefix === searchValue
})
}
// 如果找不到尝试通过原始busNo精确匹配
if (!dept) {
dept = departments.value.find(d => {
const rawBusNo = String(d.rawOrg?.busNo || '').trim().replace(/\s+/g, '').toUpperCase()
const searchValue = trimmedValue.replace(/\s+/g, '').toUpperCase()
return rawBusNo && rawBusNo === searchValue
})
}
// 如果找不到尝试层级编码匹配busNo可能是 "A01.001",搜索值是 "A01"
if (!dept) {
dept = departments.value.find(d => {
const rawBusNo = String(d.rawOrg?.busNo || '').trim().replace(/\s+/g, '').toUpperCase()
const searchValue = trimmedValue.replace(/\s+/g, '').toUpperCase()
if (!rawBusNo) return false
// 如果busNo包含点号取点号前的部分进行匹配
const busNoPrefix = rawBusNo.split('.')[0]
// 匹配方式:前缀匹配、完全匹配、或者搜索值作为前缀
return busNoPrefix === searchValue ||
rawBusNo.startsWith(searchValue + '.') ||
rawBusNo === searchValue
})
}
// 如果找不到,尝试匹配名称
if (!dept) {
dept = departments.value.find(d => {
const label = String(d.dictLabel || '').trim()
const value = String(d.dictValue || '').trim()
return label === trimmedValue || value === trimmedValue
})
}
// 如果还是找不到,尝试部分匹配(编码可能不完整,或者编码格式不同)
if (!dept) {
dept = departments.value.find(d => {
const code = String(d.deptCode || '').trim().replace(/\s+/g, '').toUpperCase()
const rawBusNo = String(d.rawOrg?.busNo || '').trim().replace(/\s+/g, '').toUpperCase()
const searchValue = trimmedValue.replace(/\s+/g, '').toUpperCase()
if (!code && !rawBusNo) return false
// 尝试多种匹配方式
return (code && code.includes(searchValue)) ||
(code && searchValue.includes(code)) ||
(rawBusNo && rawBusNo.includes(searchValue)) ||
(rawBusNo && searchValue.includes(rawBusNo)) ||
(rawBusNo && rawBusNo.startsWith(searchValue)) ||
(code && code.length >= 3 && searchValue.length >= 3 && code.substring(0, 3) === searchValue.substring(0, 3))
})
}
if (dept && dept.dictLabel) {
return dept.dictLabel
}
// 如果找不到,输出详细信息用于调试
const deptCodes = departments.value.map(d => ({
deptCode: String(d.deptCode || '').trim(),
deptCodeUpper: String(d.deptCode || '').trim().toUpperCase(),
busNoPrefix: String(d.busNoPrefix || '').trim().toUpperCase(),
name: d.dictLabel,
rawBusNo: d.rawOrg?.busNo,
rawBusNoUpper: d.rawOrg?.busNo ? String(d.rawOrg.busNo).trim().toUpperCase() : '',
rawBusNoPrefix: d.rawOrg?.busNo ? String(d.rawOrg.busNo).trim().toUpperCase().split('.')[0] : ''
}))
// 输出详细的调试信息,包括所有科室的完整信息
console.group('🔍 科室匹配失败 - 详细信息')
console.log('搜索值:', trimmedValue)
console.log('搜索值(大写):', trimmedValue.toUpperCase())
console.log('搜索值长度:', trimmedValue.length)
console.log('可用科室总数:', deptCodes.length)
console.log('前5个科室详情:', deptCodes.slice(0, 5))
console.log('所有科室编码列表:', deptCodes.map(d => ({
deptCode: d.deptCode,
deptCodeUpper: d.deptCodeUpper,
busNoPrefix: d.busNoPrefix,
rawBusNo: d.rawBusNo,
rawBusNoUpper: d.rawBusNoUpper,
rawBusNoPrefix: d.rawBusNoPrefix,
name: d.name
})))
console.groupEnd()
return trimmedValue
}
// 查询
async function handleQuery() {
try {
loading.value = true
// 构建查询参数
const params = {
...queryParams
}
// 处理日期范围
if (dateRange.value && dateRange.value.length === 2) {
params.startDate = dateRange.value[0]
params.endDate = dateRange.value[1]
}
const response = await listCheckPackage(params)
if (response && response.data) {
// 处理不同的响应格式
if (Array.isArray(response.data)) {
tableData.value = response.data
total.value = response.data.length
} else if (response.data.records) {
tableData.value = response.data.records
total.value = response.data.total || 0
} else {
tableData.value = []
total.value = 0
}
console.log('查询成功,共', total.value, '条数据')
}
} catch (error) {
console.error('查询失败:', error)
ElMessage.error('查询失败: ' + (error.message || '未知错误'))
} finally {
loading.value = false
}
}
// 重置
function handleReset() {
queryParams.pageNo = 1
queryParams.pageSize = 10
queryParams.organization = ''
queryParams.packageName = ''
queryParams.packageLevel = ''
queryParams.packageType = ''
queryParams.department = ''
queryParams.user = ''
dateRange.value = []
handleQuery()
}
// 返回套餐设置界面
function handleBackToSettings() {
emit('back-to-settings')
}
// 新增
function handleAdd() {
console.log('新增套餐')
// 通知父组件切换到套餐设置界面,模式为新增
emit('switch-to-settings', { mode: 'add', data: null })
}
// 编辑
async function handleEdit(row) {
try {
console.log('编辑套餐:', row.id)
// 获取完整的套餐数据(包含明细)
const response = await getCheckPackage(row.id)
if (response && (response.code === 200 || response.code === 0) && response.data) {
// 通知父组件切换到套餐设置界面,模式为编辑
emit('switch-to-settings', { mode: 'edit', data: response.data })
} else {
ElMessage.error('加载套餐数据失败')
}
} catch (error) {
console.error('加载套餐数据失败:', error)
ElMessage.error('数据加载失败,请重试')
}
}
// 查看
async function handleView(row) {
try {
console.log('查看套餐:', row.id)
// 获取完整的套餐数据(包含明细)
const response = await getCheckPackage(row.id)
if (response && (response.code === 200 || response.code === 0) && response.data) {
// 通知父组件切换到套餐设置界面,模式为查看
emit('switch-to-settings', { mode: 'view', data: response.data })
} else {
ElMessage.error('加载套餐数据失败')
}
} catch (error) {
console.error('加载套餐数据失败:', error)
ElMessage.error('数据加载失败,请重试')
}
}
// 删除
function handleDelete(row) {
ElMessageBox.confirm(
`确认删除套餐ID:${row.id} - ${row.packageName}吗?删除后将无法恢复!`,
'删除确认',
{
confirmButtonText: '确定删除',
cancelButtonText: '取消',
type: 'warning',
buttonSize: 'default'
}
).then(async () => {
try {
const response = await delCheckPackage(row.id)
if (response && (response.code === 200 || response.code === 0)) {
ElMessage.success('删除成功')
// 刷新列表
handleQuery()
} else {
ElMessage.error(response?.msg || response?.message || '删除失败')
}
} catch (error) {
console.error('删除失败:', error)
ElMessage.error('删除失败: ' + (error.message || '未知错误'))
}
}).catch(() => {
console.log('用户取消删除')
})
}
</script>
<style scoped>
.package-management {
padding: 24px;
background-color: #f5f7fa;
min-height: 100vh;
}
.filter-section {
background: white;
border-radius: 8px;
padding: 20px;
margin-bottom: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
}
.filter-section :deep(.el-form-item) {
margin-bottom: 16px;
}
.table-section {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
}
.pagination-section {
display: flex;
justify-content: flex-end;
padding: 16px 20px;
background: white;
border-radius: 8px;
margin-top: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
}
/* 响应式设计 */
@media (max-width: 1200px) {
.filter-section :deep(.el-form) {
display: flex;
flex-wrap: wrap;
}
}
@media (max-width: 768px) {
.package-management {
padding: 16px;
}
.filter-section,
.table-section,
.pagination-section {
padding: 16px;
}
}
</style>