bug550
This commit is contained in:
@@ -316,13 +316,13 @@
|
||||
<!-- Bug #384修复: 单价显示套餐价格(如果选中)或部位价格 -->
|
||||
<el-table-column label="单价" width="75" align="right">
|
||||
<template #default="scope">
|
||||
{{ scope.row.selectedMethod?.packagePrice || scope.row.price }}
|
||||
{{ formatDetailAmount(getSelectedItemAmount(scope.row)) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- Bug #384修复: 金额使用有效价格计算 -->
|
||||
<el-table-column label="金额" width="80" align="right">
|
||||
<template #default="scope">
|
||||
{{ ((scope.row.selectedMethod?.packagePrice || scope.row.price || 0) * (scope.row.quantity || 1)).toFixed(2) }}
|
||||
{{ formatDetailAmount(getSelectedItemAmount(scope.row) * (scope.row.quantity || 1)) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="类型" prop="checkType" width="70" align="center" />
|
||||
@@ -392,37 +392,6 @@
|
||||
<div v-if="categoryLoadingSet.has(cat.typeId)" class="category-loading-hint">
|
||||
加载中...
|
||||
</div>
|
||||
<!-- Bug #428修复: 渲染分类联动加载的检查方法列表 -->
|
||||
<!-- Bug #500修复: v-if 改为 v-show,避免方法列表加载时 DOM 突然插入导致高度跳变 -->
|
||||
<div
|
||||
v-show="cat.methods && cat.methods.length > 0"
|
||||
class="method-section"
|
||||
>
|
||||
<div class="method-section-title">检查方法</div>
|
||||
<div
|
||||
v-for="method in cat.methods"
|
||||
:key="method.id"
|
||||
class="method-row"
|
||||
>
|
||||
<el-checkbox
|
||||
:model-value="isMethodSelected(method, cat)"
|
||||
@change="(val) => handleMethodSelect(val, method, cat)"
|
||||
class="method-checkbox"
|
||||
>
|
||||
{{ method.name }}
|
||||
</el-checkbox>
|
||||
<span class="method-price-tag">¥{{ method.packagePrice || method.price || 0 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Bug #500修复: 加载中的方法列表骨架占位,提前预留空间避免高度跳变 -->
|
||||
<div
|
||||
v-if="categoryLoadingSet.has(cat.typeId) && (!cat.methods || cat.methods.length === 0)"
|
||||
class="method-section method-section-skeleton"
|
||||
>
|
||||
<div class="method-section-title">检查方法</div>
|
||||
<div class="skeleton-method-row"></div>
|
||||
<div class="skeleton-method-row"></div>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
@@ -440,46 +409,103 @@
|
||||
class="selected-item-card"
|
||||
:class="{ 'is-expanded': item.expanded }"
|
||||
>
|
||||
<!-- Bug #384修复 + #426修复: 项目卡片头部,可展开/收起 -->
|
||||
<!-- 项目卡片头部:项目和检查方法解耦,点击展开查看方法/明细 -->
|
||||
<div class="card-header" @click="toggleItemExpand(item)">
|
||||
<el-tag v-if="item.isPackage || item.packageName" size="small" type="warning" style="margin-right: 4px; flex-shrink: 0;">套餐</el-tag>
|
||||
<el-tooltip :content="item.name" placement="top" :show-after="400">
|
||||
<span class="card-name">{{ item.name }}</span>
|
||||
<el-tooltip :content="getDisplayItemName(item)" placement="top" :show-after="400">
|
||||
<span class="card-name">{{ getDisplayItemName(item) }}</span>
|
||||
</el-tooltip>
|
||||
<span class="card-price">¥{{ formatDetailAmount(item.price) }}</span>
|
||||
<span class="card-price">¥{{ formatDetailAmount(getSelectedItemAmount(item)) }}</span>
|
||||
<el-icon :class="['expand-icon', { expanded: item.expanded }]">
|
||||
<ArrowDown v-if="!item.expanded" />
|
||||
<ArrowUp v-if="item.expanded" />
|
||||
<ArrowDown />
|
||||
</el-icon>
|
||||
<!-- 删除按钮 -->
|
||||
<el-button link type="danger" size="small" @click.stop="handleRemoveItem(idx, item)">
|
||||
<el-icon><Close /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- Bug #428: 有套餐 ID 时默认展开;加载中/空/明细均在本区域展示 -->
|
||||
<div v-if="item.expanded && shouldShowPackageBody(item)" class="selected-card-body">
|
||||
<div v-if="item.packageDetailsLoading" class="package-details-loading">加载中...</div>
|
||||
<template v-else>
|
||||
<div v-if="getPackageDetailsList(item).length === 0" class="package-details-empty">
|
||||
暂无套餐明细
|
||||
<div v-if="item.expanded" class="selected-card-body">
|
||||
<div v-if="shouldShowItemPackageBody(item)">
|
||||
<div class="package-toggle" @click.stop="toggleItemPackageExpand(item)">
|
||||
<span>项目套餐明细</span>
|
||||
<el-icon :class="['expand-icon', { expanded: item.itemPackageExpanded }]">
|
||||
<ArrowDown />
|
||||
</el-icon>
|
||||
</div>
|
||||
<div v-else class="package-details-list">
|
||||
<div class="package-details-head">套餐明细</div>
|
||||
<div
|
||||
v-for="(detail, dIdx) in getPackageDetailsList(item)"
|
||||
:key="detail.id ?? detail.itemCode ?? `d-${dIdx}`"
|
||||
class="detail-row"
|
||||
>
|
||||
<el-tooltip :content="detail.name" placement="top" :show-after="500">
|
||||
<span class="detail-name">{{ detail.name }}</span>
|
||||
</el-tooltip>
|
||||
<div class="detail-meta">
|
||||
<span class="detail-qty">×{{ detail.quantity || 1 }}</span>
|
||||
<span class="detail-price">¥{{ formatDetailAmount(detail.price) }}</span>
|
||||
<div v-show="item.itemPackageExpanded">
|
||||
<div v-if="item.packageDetailsLoading" class="package-details-loading">加载中...</div>
|
||||
<template v-else>
|
||||
<div v-if="getPackageDetailsList(item).length === 0" class="package-details-empty">
|
||||
暂无套餐明细
|
||||
</div>
|
||||
<div v-else class="package-details-list">
|
||||
<div
|
||||
v-for="(detail, dIdx) in getPackageDetailsList(item)"
|
||||
:key="detail.id ?? detail.itemCode ?? `d-${dIdx}`"
|
||||
class="detail-row"
|
||||
>
|
||||
<el-tooltip :content="detail.name" placement="top" :show-after="500">
|
||||
<span class="detail-name">{{ detail.name }}</span>
|
||||
</el-tooltip>
|
||||
<div class="detail-meta">
|
||||
<span class="detail-qty">×{{ detail.quantity || 1 }}</span>
|
||||
<span class="detail-price">¥{{ formatDetailAmount(detail.price) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="selected-card-section">
|
||||
<div class="selected-section-title">检查方法</div>
|
||||
<div v-if="!item.methods || item.methods.length === 0" class="selected-method-empty">
|
||||
暂无检查方法
|
||||
</div>
|
||||
<div
|
||||
v-for="method in item.methods"
|
||||
:key="method.id"
|
||||
class="selected-method-option"
|
||||
>
|
||||
<el-checkbox
|
||||
:model-value="item.selectedMethod?.id === method.id"
|
||||
@change="(val) => selectMethodCheckbox(val, item, method)"
|
||||
class="method-checkbox"
|
||||
>
|
||||
{{ method.name }}
|
||||
</el-checkbox>
|
||||
<span class="method-price-tag">¥{{ method.packagePrice || method.price || 0 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="shouldShowMethodPackageBody(item)">
|
||||
<div class="package-toggle" @click.stop="toggleMethodPackageExpand(item)">
|
||||
<span>检查方法套餐明细</span>
|
||||
<el-icon :class="['expand-icon', { expanded: item.methodPackageExpanded }]">
|
||||
<ArrowDown />
|
||||
</el-icon>
|
||||
</div>
|
||||
<div v-show="item.methodPackageExpanded">
|
||||
<div v-if="item.methodPackageLoading" class="package-details-loading">加载中...</div>
|
||||
<template v-else>
|
||||
<div v-if="getMethodPackageDetailsList(item).length === 0" class="package-details-empty">
|
||||
暂无检查方法套餐明细
|
||||
</div>
|
||||
<div v-else class="package-details-list method-package-list">
|
||||
<div
|
||||
v-for="(detail, dIdx) in getMethodPackageDetailsList(item)"
|
||||
:key="detail.id ?? detail.itemCode ?? `md-${dIdx}`"
|
||||
class="detail-row"
|
||||
>
|
||||
<el-tooltip :content="detail.name" placement="top" :show-after="500">
|
||||
<span class="detail-name">{{ detail.name }}</span>
|
||||
</el-tooltip>
|
||||
<div class="detail-meta">
|
||||
<span class="detail-qty">×{{ detail.quantity || 1 }}</span>
|
||||
<span class="detail-price">¥{{ formatDetailAmount(detail.price) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -493,7 +519,7 @@
|
||||
<script setup>
|
||||
import { ref, reactive, computed, watch, onMounted, nextTick } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { Printer, Delete, ArrowDown, ArrowUp, Close } from '@element-plus/icons-vue';
|
||||
import { Printer, Delete, ArrowDown, Close } from '@element-plus/icons-vue';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
import request from '@/utils/request';
|
||||
import { listCheckMethod, searchCheckMethod, listCheckPackage } from '@/api/system/checkType';
|
||||
@@ -624,24 +650,48 @@ async function loadPackageDetails(row, treeNode, resolve) {
|
||||
}
|
||||
}
|
||||
|
||||
// #428修复 + #426修复: 为已选择项目加载套餐明细(通过packageId或packageName查询)
|
||||
/** 套餐明细挂在「部位」或已选的「检查方法」上(方法可带 packageId) */
|
||||
function getPackageCarrier(item) {
|
||||
return item?.selectedMethod?.packageId ? item.selectedMethod : item;
|
||||
}
|
||||
|
||||
function getPackageDetailsList(item) {
|
||||
// 明细挂在行对象上,避免仅写入 methods 内嵌对象时首帧不触发视图更新(体感需点两次才展开)
|
||||
if (Array.isArray(item?.packageDetailsDisplay)) {
|
||||
return item.packageDetailsDisplay;
|
||||
}
|
||||
const carrier = getPackageCarrier(item);
|
||||
return Array.isArray(carrier?.packageDetails) ? carrier.packageDetails : [];
|
||||
return Array.isArray(item?.packageDetails) ? item.packageDetails : [];
|
||||
}
|
||||
|
||||
function getMethodPackageDetailsList(item) {
|
||||
if (Array.isArray(item?.methodPackageDetails)) {
|
||||
return item.methodPackageDetails;
|
||||
}
|
||||
return Array.isArray(item?.selectedMethod?.packageDetails) ? item.selectedMethod.packageDetails : [];
|
||||
}
|
||||
|
||||
/** 有套餐 ID 或 packageName 的已选行才展示右侧套餐区(加载中 / 空 / 明细列表) */
|
||||
function shouldShowPackageBody(item) {
|
||||
return !!(getPackageCarrier(item)?.packageId || item.packageName || item.packageId);
|
||||
return shouldShowItemPackageBody(item) || shouldShowMethodPackageBody(item);
|
||||
}
|
||||
|
||||
function hasItemPackage(item) {
|
||||
return !!(item?.packageId || item?.packageName);
|
||||
}
|
||||
|
||||
function hasMethodPackage(item) {
|
||||
return !!(item?.selectedMethod?.packageId || item?.selectedMethod?.packageName);
|
||||
}
|
||||
|
||||
function isSamePackage(item) {
|
||||
if (!hasItemPackage(item) || !hasMethodPackage(item)) return false;
|
||||
if (item.packageId && item.selectedMethod?.packageId) {
|
||||
return String(item.packageId) === String(item.selectedMethod.packageId);
|
||||
}
|
||||
return String(item.packageName || '') === String(item.selectedMethod?.packageName || '');
|
||||
}
|
||||
|
||||
function shouldShowItemPackageBody(item) {
|
||||
return hasItemPackage(item);
|
||||
}
|
||||
|
||||
function shouldShowMethodPackageBody(item) {
|
||||
return hasMethodPackage(item) && !isSamePackage(item);
|
||||
}
|
||||
|
||||
/** 金额展示:统一两位小数 */
|
||||
@@ -650,20 +700,18 @@ function formatDetailAmount(value) {
|
||||
return Number.isFinite(n) ? n.toFixed(2) : '0.00';
|
||||
}
|
||||
|
||||
/** 默认检查方法:优先与部位 packageId 一致的方法,否则取首个带套餐的方法,否则取第一个 */
|
||||
function pickDefaultMethod(methods, partItem) {
|
||||
if (!methods?.length) return null;
|
||||
if (methods.length === 1) return methods[0];
|
||||
const pid = partItem?.packageId ?? null;
|
||||
if (pid != null && pid !== '') {
|
||||
const matched = methods.find(
|
||||
(x) => x.packageId != null && String(x.packageId) === String(pid)
|
||||
);
|
||||
if (matched) return matched;
|
||||
/** 已选卡片名称:去掉 UI 上冗余的“套餐”前缀,完整名称通过 tooltip 展示 */
|
||||
function getDisplayItemName(item) {
|
||||
return String(item?.name || '').replace(/^套餐[::\-\s]*/, '');
|
||||
}
|
||||
|
||||
function getSelectedItemAmount(item) {
|
||||
const itemPrice = Number(item?.price || 0);
|
||||
const methodPrice = Number(item?.selectedMethod?.packagePrice || 0);
|
||||
if (!hasMethodPackage(item) || isSamePackage(item)) {
|
||||
return itemPrice;
|
||||
}
|
||||
const withPkg = methods.find((x) => x.packageId != null);
|
||||
if (withPkg) return withPkg;
|
||||
return methods[0];
|
||||
return itemPrice + methodPrice;
|
||||
}
|
||||
|
||||
function parsePackageDetailsPayload(res) {
|
||||
@@ -679,15 +727,15 @@ function parsePackageDetailsPayload(res) {
|
||||
|
||||
// #428: 为已选择项目加载套餐明细(后端:CheckTypeController /system/check-type/package/{id}/details)
|
||||
async function loadPackageDetailsForItem(item) {
|
||||
const carrier = getPackageCarrier(item);
|
||||
let packageId = item.packageId || carrier?.packageId;
|
||||
if (!packageId && !item.packageName) {
|
||||
let packageId = item.packageId;
|
||||
const packageName = item.packageName;
|
||||
if (!packageId && !packageName) {
|
||||
return;
|
||||
}
|
||||
item.packageDetailsLoading = true;
|
||||
try {
|
||||
if (!packageId && item.packageName) {
|
||||
const pkgRes = await listCheckPackage({ packageName: item.packageName });
|
||||
if (!packageId && packageName) {
|
||||
const pkgRes = await listCheckPackage({ packageName });
|
||||
let packages = pkgRes?.data || [];
|
||||
if (!Array.isArray(packages)) {
|
||||
packages = packages.records || packages.data || [];
|
||||
@@ -699,6 +747,7 @@ async function loadPackageDetailsForItem(item) {
|
||||
}
|
||||
packageId = packages[0].id;
|
||||
item.packageId = packageId;
|
||||
item.packageName = item.packageName || packageName;
|
||||
}
|
||||
if (!packageId) {
|
||||
item.packageDetails = [];
|
||||
@@ -718,7 +767,6 @@ async function loadPackageDetailsForItem(item) {
|
||||
quantity: detail.quantity || 1
|
||||
}));
|
||||
item.packageDetailsDisplay = mapped;
|
||||
carrier.packageDetails = mapped;
|
||||
if (res.code === 200 && res.data) {
|
||||
item.packageDetails = Array.isArray(res.data)
|
||||
? res.data.map((detail) => ({
|
||||
@@ -735,7 +783,6 @@ async function loadPackageDetailsForItem(item) {
|
||||
} catch (err) {
|
||||
console.error('加载套餐明细失败:', err);
|
||||
item.packageDetailsDisplay = [];
|
||||
carrier.packageDetails = [];
|
||||
item.packageDetails = [];
|
||||
} finally {
|
||||
item.packageDetailsLoading = false;
|
||||
@@ -1058,7 +1105,10 @@ async function loadCategoryList() {
|
||||
|
||||
// 默认展开第一个
|
||||
if (categoryList.value.length > 0) {
|
||||
activeNames.value = categoryList.value[0].typeId;
|
||||
const firstCat = categoryList.value[0];
|
||||
activeNames.value = firstCat.typeId;
|
||||
await nextTick();
|
||||
await handleCategoryExpand(firstCat);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('加载检查项目分类失败', err);
|
||||
@@ -1079,10 +1129,9 @@ const filteredCategoryList = computed(() => {
|
||||
});
|
||||
|
||||
// ====== 合计 ======
|
||||
// Bug #384修复: 如果选中了检查方法,使用套餐价格;否则使用部位价格
|
||||
const totalAmountCalc = computed(() => {
|
||||
const total = selectedItems.value.reduce((sum, item) => {
|
||||
const effectivePrice = item.selectedMethod?.packagePrice || item.price;
|
||||
const effectivePrice = getSelectedItemAmount(item);
|
||||
return sum + (effectivePrice * (item.quantity || 1));
|
||||
}, 0);
|
||||
return total.toFixed(2);
|
||||
@@ -1215,8 +1264,7 @@ function handleSave() {
|
||||
itemCode: String(item.id),
|
||||
itemName: item.name,
|
||||
bodyPartCode: item.checkType || 'unknown',
|
||||
// Bug #384修复: 如果选中了检查方法且有套餐价格,使用套餐价格;否则使用部位价格
|
||||
itemFee: item.selectedMethod?.packagePrice || item.price,
|
||||
itemFee: getSelectedItemAmount(item),
|
||||
performDeptCode: form.performDeptCode || '',
|
||||
itemStatus: 0,
|
||||
itemSeq: index + 1,
|
||||
@@ -1269,6 +1317,8 @@ function handleRowClick(row) {
|
||||
methods: [],
|
||||
selectedMethod: null,
|
||||
expanded: false,
|
||||
itemPackageExpanded: true,
|
||||
methodPackageExpanded: true,
|
||||
packageDetailsLoading: false,
|
||||
isPackage: false,
|
||||
packageId: null,
|
||||
@@ -1298,17 +1348,10 @@ function handleRowClick(row) {
|
||||
if (m.checkMethodId) {
|
||||
item.selectedMethod = item.methods.find(md => String(md.id) === String(m.checkMethodId)) || null;
|
||||
if (item.selectedMethod?.packageId) {
|
||||
item.isPackage = true;
|
||||
item.packageId = item.selectedMethod.packageId;
|
||||
item.hasChildren = true; // #426修复
|
||||
}
|
||||
}
|
||||
if (!item.selectedMethod && item.methods.length) {
|
||||
item.selectedMethod = pickDefaultMethod(item.methods, { packageId: item.packageId });
|
||||
}
|
||||
if (item.selectedMethod?.packageId) {
|
||||
item.packageId = item.selectedMethod.packageId;
|
||||
item.isPackage = true;
|
||||
item.hasChildren = true; // #426修复
|
||||
}
|
||||
}
|
||||
@@ -1322,14 +1365,21 @@ function handleRowClick(row) {
|
||||
selectedItems.value = itemsWithMethods;
|
||||
// 加载套餐明细(单个失败不影响其他项目和明细显示)
|
||||
for (const it of selectedItems.value) {
|
||||
if (getPackageCarrier(it)?.packageId) {
|
||||
if (hasItemPackage(it)) {
|
||||
try {
|
||||
await loadPackageDetailsForItem(it);
|
||||
} catch (e) {
|
||||
console.error('加载套餐明细失败:', it.name, e);
|
||||
}
|
||||
}
|
||||
it.expanded = !!getPackageCarrier(it)?.packageId;
|
||||
if (hasMethodPackage(it) && !isSamePackage(it)) {
|
||||
try {
|
||||
await loadMethodPackageDetails(it, it.selectedMethod);
|
||||
} catch (e) {
|
||||
console.error('加载检查方法套餐明细失败:', it.name, e);
|
||||
}
|
||||
}
|
||||
it.expanded = shouldShowPackageBody(it);
|
||||
}
|
||||
syncCategoryChecked();
|
||||
// Bug #384修复: 回充后更新检查方法显示
|
||||
@@ -1359,97 +1409,6 @@ function handleDelete(row) {
|
||||
});
|
||||
}
|
||||
|
||||
// Bug #428修复: 判断某个检查方法是否已被选中(任意项目关联了该方法)
|
||||
function isMethodSelected(method, cat) {
|
||||
return selectedItems.value.some(item =>
|
||||
item.selectedMethod?.id === method.id && item.checkType === cat.typeName
|
||||
);
|
||||
}
|
||||
|
||||
// Bug #428修复: 勾选检查方法
|
||||
async function handleMethodSelect(checked, method, cat) {
|
||||
if (checked) {
|
||||
// 找到该方法所属的第一个检查项目
|
||||
const targetItem = cat.items[0];
|
||||
if (!targetItem) {
|
||||
// 如果分类下没有项目,尝试从其他分类找同名项目或创建
|
||||
console.warn('分类下没有检查项目,无法关联方法');
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果该项目已存在,只更新 selectedMethod
|
||||
const existingItem = selectedItems.value.find(s => s.id === targetItem.id);
|
||||
if (existingItem) {
|
||||
existingItem.selectedMethod = method;
|
||||
// 从方法中获取套餐信息(支持 packageId 或 packageName 解析)
|
||||
if (method.packageId || method.packageName) {
|
||||
existingItem.isPackage = true;
|
||||
existingItem.packageId = method.packageId || existingItem.packageId;
|
||||
existingItem.hasChildren = true; // #426修复
|
||||
existingItem.packageName = method.packageName || existingItem.packageName; // #428修复: 确保 packageName 同步
|
||||
// 预加载套餐明细
|
||||
loadPackageDetailsForItem(existingItem);
|
||||
}
|
||||
updateMethodDisplay();
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果该项目不存在,创建一个并关联方法
|
||||
if (selectedItems.value.length > 0) {
|
||||
const currentCategory = selectedItems.value[0].checkType;
|
||||
// Bug #428修复: 使用 cat.typeName 进行比较(与 newItem.checkType 保持一致)
|
||||
const newCategory = cat.typeName || '';
|
||||
if (currentCategory !== newCategory) {
|
||||
ElMessage.warning('一个检查单不能同时选择多个项目类型的检查项目');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const newItem = {
|
||||
id: targetItem.id, name: targetItem.name,
|
||||
price: targetItem.price, quantity: 1,
|
||||
serviceFee: targetItem.serviceFee || 0,
|
||||
unit: targetItem.unit || '次',
|
||||
applyPart: targetItem.name,
|
||||
checkType: cat.typeName,
|
||||
nationalCode: targetItem.nationalCode || '',
|
||||
checked: true,
|
||||
methods: cat.methods || [method], // #428修复: 复制分类下全部方法,允许用户切换
|
||||
selectedMethod: method,
|
||||
expanded: false,
|
||||
// 从方法或项目中获取套餐信息
|
||||
isPackage: !!method.packageId || !!targetItem.packageName,
|
||||
packageId: method.packageId || targetItem.packageId || null,
|
||||
packageName: method.packageName || targetItem.packageName || null, // #428修复: 复制 packageName,确保套餐明细可加载
|
||||
hasChildren: !!(method.packageId || method.packageName || targetItem.packageId || targetItem.packageName) // #426修复: 树形表格懒加载展开标记,支持 packageName 解析
|
||||
};
|
||||
selectedItems.value.push(newItem);
|
||||
|
||||
// 如果是套餐,预加载套餐明细
|
||||
if (newItem.isPackage && (newItem.packageId || newItem.packageName)) {
|
||||
loadPackageDetailsForItem(newItem);
|
||||
}
|
||||
|
||||
// 自动回填执行科室
|
||||
if (selectedItems.value.length === 1 && cat?.performDeptName) {
|
||||
form.performDeptCode = cat.performDeptName;
|
||||
}
|
||||
|
||||
// 同时勾选左侧项目的 checkbox
|
||||
targetItem.checked = true;
|
||||
|
||||
} else {
|
||||
// 取消选择方法:将 selectedItems 中关联该方法的项的 selectedMethod 清空
|
||||
const itemsWithMethod = selectedItems.value.filter(
|
||||
item => item.selectedMethod?.id === method.id
|
||||
);
|
||||
for (const item of itemsWithMethod) {
|
||||
item.selectedMethod = null;
|
||||
}
|
||||
}
|
||||
updateMethodDisplay();
|
||||
}
|
||||
|
||||
// ====== 勾选逻辑 ======
|
||||
async function handleItemSelect(checked, item, cat) {
|
||||
if (checked) {
|
||||
@@ -1506,6 +1465,8 @@ async function handleItemSelect(checked, item, cat) {
|
||||
methods: methods,
|
||||
selectedMethod: null,
|
||||
expanded: false,
|
||||
itemPackageExpanded: true,
|
||||
methodPackageExpanded: true,
|
||||
isPackage: !!(item.packageId || item.packageName),
|
||||
packageName: item.packageName || null,
|
||||
packageDetailsLoading: false,
|
||||
@@ -1516,15 +1477,15 @@ async function handleItemSelect(checked, item, cat) {
|
||||
// 必须用数组里的响应式行,不能继续改局部 newRow:push 后列表内是 proxy,改 raw 对象不会触发右侧卡片更新(会一直卡在「加载中」)
|
||||
const row = selectedItems.value[selectedItems.value.length - 1];
|
||||
|
||||
// 右侧不再展示「检查方法」列表:自动选默认方法(保存、计价仍依赖 selectedMethod)
|
||||
if (methods.length >= 1) {
|
||||
row.selectedMethod = pickDefaultMethod(methods, item);
|
||||
}
|
||||
// 勾选项目只加入项目列表,检查方法由用户在“检查方法”区域手动选择
|
||||
row.selectedMethod = null;
|
||||
updateMethodDisplay();
|
||||
|
||||
// 有套餐 ID 时默认展开(先显示加载区,明细写入行对象 packageDetailsDisplay)
|
||||
row.expanded = !!getPackageCarrier(row)?.packageId;
|
||||
if (getPackageCarrier(row)?.packageId) {
|
||||
// 新勾选项目后默认展开,直接展示检查方法状态和套餐明细
|
||||
row.expanded = true;
|
||||
row.itemPackageExpanded = true;
|
||||
row.methodPackageExpanded = true;
|
||||
if (hasItemPackage(row)) {
|
||||
await loadPackageDetailsForItem(row);
|
||||
}
|
||||
|
||||
@@ -1550,13 +1511,35 @@ async function handleItemSelect(checked, item, cat) {
|
||||
// Bug #384修复 + #426修复: 展开/收起项目卡片
|
||||
async function toggleItemExpand(item) {
|
||||
item.expanded = !item.expanded;
|
||||
if (item.expanded && (item.isPackage || item.packageName) && (!item.packageDetails || item.packageDetails.length === 0) && !item.packageDetailsLoading) {
|
||||
if (item.expanded && hasItemPackage(item) && getPackageDetailsList(item).length === 0 && !item.packageDetailsLoading) {
|
||||
await loadPackageDetailsForItem(item);
|
||||
}
|
||||
if (item.expanded && shouldShowPackageBody(item)) {
|
||||
if (getPackageDetailsList(item).length === 0 && !item.packageDetailsLoading) {
|
||||
await loadPackageDetailsForItem(item);
|
||||
}
|
||||
if (
|
||||
item.expanded &&
|
||||
shouldShowMethodPackageBody(item) &&
|
||||
getMethodPackageDetailsList(item).length === 0 &&
|
||||
!item.methodPackageLoading
|
||||
) {
|
||||
await loadMethodPackageDetails(item, item.selectedMethod);
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleItemPackageExpand(item) {
|
||||
item.itemPackageExpanded = !item.itemPackageExpanded;
|
||||
if (item.itemPackageExpanded && getPackageDetailsList(item).length === 0 && !item.packageDetailsLoading) {
|
||||
await loadPackageDetailsForItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleMethodPackageExpand(item) {
|
||||
item.methodPackageExpanded = !item.methodPackageExpanded;
|
||||
if (
|
||||
item.methodPackageExpanded &&
|
||||
item.selectedMethod &&
|
||||
getMethodPackageDetailsList(item).length === 0 &&
|
||||
!item.methodPackageLoading
|
||||
) {
|
||||
await loadMethodPackageDetails(item, item.selectedMethod);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1564,9 +1547,8 @@ async function toggleItemExpand(item) {
|
||||
async function selectMethodCheckbox(checked, item, method) {
|
||||
if (checked) {
|
||||
item.selectedMethod = method;
|
||||
if (item.expanded && (method.packageId || method.packageName)) {
|
||||
loadPackageDetailsForItem(item);
|
||||
}
|
||||
item.expanded = true;
|
||||
item.methodPackageExpanded = true;
|
||||
// 动态加载该方法对应的套餐明细
|
||||
await loadMethodPackageDetails(item, method);
|
||||
} else {
|
||||
@@ -1587,36 +1569,43 @@ async function loadMethodPackageDetails(item, method) {
|
||||
item.methodPackageLoading = true;
|
||||
item.methodPackageDetails = [];
|
||||
try {
|
||||
if (!method.packageName) {
|
||||
let packageId = method.packageId;
|
||||
if (!packageId && !method.packageName) {
|
||||
item.methodPackageLoading = false;
|
||||
return;
|
||||
}
|
||||
// 通过packageName查询套餐获取packageId
|
||||
const pkgRes = await listCheckPackage({ packageName: method.packageName });
|
||||
let packages = pkgRes?.data || [];
|
||||
if (!Array.isArray(packages)) {
|
||||
packages = packages.records || packages.data || [];
|
||||
if (!packageId && method.packageName) {
|
||||
const pkgRes = await listCheckPackage({ packageName: method.packageName });
|
||||
let packages = pkgRes?.data || [];
|
||||
if (!Array.isArray(packages)) {
|
||||
packages = packages.records || packages.data || [];
|
||||
}
|
||||
if (packages.length === 0) {
|
||||
item.methodPackageLoading = false;
|
||||
return;
|
||||
}
|
||||
packageId = packages[0].id;
|
||||
method.packageId = packageId;
|
||||
}
|
||||
if (packages.length === 0) {
|
||||
item.methodPackageLoading = false;
|
||||
return;
|
||||
}
|
||||
const packageId = packages[0].id;
|
||||
// 查询套餐明细
|
||||
const detailRes = await request({
|
||||
url: `/system/package/${packageId}/details`,
|
||||
url: `/system/check-type/package/${packageId}/details`,
|
||||
method: 'get'
|
||||
});
|
||||
if (detailRes.code === 200 && detailRes.data) {
|
||||
item.methodPackageDetails = detailRes.data.map(d => ({
|
||||
const detailList = parsePackageDetailsPayload(detailRes);
|
||||
if (detailList.length > 0) {
|
||||
const mapped = detailList.map(d => ({
|
||||
id: d.id,
|
||||
name: d.itemName || d.name,
|
||||
name: d.name || d.itemName,
|
||||
quantity: d.quantity || 1,
|
||||
unit: d.unit || '次',
|
||||
price: d.unitPrice || d.price || 0,
|
||||
price: d.price ?? d.unitPrice ?? d.itemPrice ?? 0,
|
||||
amount: d.amount || d.total || 0,
|
||||
checked: true // 默认勾选
|
||||
}));
|
||||
item.methodPackageDetails = mapped;
|
||||
method.packageDetails = mapped;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('加载方法套餐明细失败:', err);
|
||||
@@ -1630,21 +1619,19 @@ async function loadMethodPackageDetails(item, method) {
|
||||
async function onDetailMethodChange(row, val) {
|
||||
row.selectedMethod = val || null;
|
||||
if (val?.packageId || val?.packageName) {
|
||||
row.packageId = val.packageId || row.packageId;
|
||||
row.packageName = val.packageName || row.packageName;
|
||||
row.isPackage = true;
|
||||
row.hasChildren = true; // #426修复
|
||||
}
|
||||
row.packageDetailsDisplay = undefined;
|
||||
const carrier = getPackageCarrier(row);
|
||||
if (carrier) {
|
||||
carrier.packageDetails = undefined;
|
||||
}
|
||||
row.methodPackageDetails = [];
|
||||
updateMethodDisplay();
|
||||
row.expanded = !!getPackageCarrier(row)?.packageId;
|
||||
if (getPackageCarrier(row)?.packageId) {
|
||||
row.expanded = shouldShowPackageBody(row);
|
||||
row.itemPackageExpanded = true;
|
||||
row.methodPackageExpanded = true;
|
||||
if (hasItemPackage(row)) {
|
||||
await loadPackageDetailsForItem(row);
|
||||
}
|
||||
if (val?.packageId || val?.packageName) {
|
||||
await loadMethodPackageDetails(row, val);
|
||||
}
|
||||
nextTick(() => {
|
||||
form.totalAmount = totalAmountCalc.value;
|
||||
});
|
||||
@@ -1794,7 +1781,7 @@ defineExpose({ getList });
|
||||
|
||||
/* 右:分类面板 */
|
||||
.category-panel {
|
||||
width: 420px;
|
||||
width: 560px;
|
||||
flex-shrink: 0;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
@@ -1951,9 +1938,9 @@ defineExpose({ getList });
|
||||
/* 已选择 tags */
|
||||
/* 已选择:加宽,避免套餐明细挤成一团 */
|
||||
.selected-panel {
|
||||
width: 220px;
|
||||
min-width: 200px;
|
||||
max-width: 280px;
|
||||
width: 260px;
|
||||
min-width: 240px;
|
||||
max-width: 320px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -2013,9 +2000,8 @@ defineExpose({ getList });
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
line-height: 1.4;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.card-price {
|
||||
@@ -2030,12 +2016,11 @@ defineExpose({ getList });
|
||||
color: #909399;
|
||||
transition: transform 0.2s ease;
|
||||
flex-shrink: 0;
|
||||
transition: transform 0.2s;
|
||||
transform: rotate(0deg);
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.expand-icon.expanded {
|
||||
transform: rotate(90deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
/* Bug #428修复: 套餐明细列表样式 */
|
||||
@@ -2078,6 +2063,55 @@ defineExpose({ getList });
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
.selected-card-section {
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.selected-section-title {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #409eff;
|
||||
margin-bottom: 8px;
|
||||
padding-bottom: 6px;
|
||||
border-bottom: 1px dashed #d9ecff;
|
||||
}
|
||||
|
||||
.selected-method-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.selected-method-option .method-checkbox {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.selected-method-empty {
|
||||
color: #c0c4cc;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.package-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #909399;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px dashed #dcdfe6;
|
||||
background: #fffbe6;
|
||||
}
|
||||
|
||||
.package-toggle:hover {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.package-details-loading,
|
||||
.package-details-empty {
|
||||
padding: 12px 10px;
|
||||
|
||||
Reference in New Issue
Block a user