Fix Bug #550: fallback修复

This commit is contained in:
2026-05-27 03:43:06 +08:00
parent 2d2368480c
commit 8cc9288886

View File

@@ -1,168 +1,241 @@
<template>
<div class="examination-apply-container">
<el-row :gutter="16" style="height: 100%;">
<!-- 左侧检查项目分类 -->
<el-col :span="6" class="panel">
<div class="panel-header">检查项目分类</div>
<el-tree
:data="categories"
:props="{ label: 'name', children: 'children' }"
highlight-current
@node-click="handleCategorySelect"
data-cy="category-tree"
/>
</el-col>
<div class="examination-apply">
<!-- 检查项目分类树 -->
<el-tree
:data="categoryTree"
node-key="id"
:default-expand-all="true"
@node-click="onCategorySelect"
data-cy="category-tree"
class="category-tree"
/>
<!-- 中间检查项目列表 -->
<el-col :span="9" class="panel">
<div class="panel-header">检查项目</div>
<div class="item-list" data-cy="item-list">
<div v-for="item in currentCategoryItems" :key="item.id" class="item-row">
<el-checkbox
:label="item.id"
v-model="selectedItemIds"
@change="handleItemToggle(item)"
>
{{ item.name }}
</el-checkbox>
</div>
</div>
</el-col>
<!-- 右侧已选择区域 -->
<el-col :span="9" class="panel">
<div class="panel-header">已选择</div>
<div class="selected-list">
<div v-for="group in selectedGroups" :key="group.id" class="selected-group">
<!-- 项目卡片自适应宽度悬停提示完整名称点击展开/收起 -->
<div
class="selected-card"
:class="{ 'is-expanded': group.expanded }"
@click="toggleGroupExpand(group.id)"
data-cy="selected-card"
>
<el-tooltip :content="group.originalName" placement="top" :show-after="300">
<span class="card-name">{{ group.displayName }}</span>
</el-tooltip>
<el-icon class="expand-icon">
<ArrowDown v-if="group.expanded" />
<ArrowRight v-else />
</el-icon>
</div>
<!-- 检查方法明细默认收起独立勾选严格遵循 项目 > 方法 层级 -->
<div v-if="group.expanded" class="method-list" data-cy="method-list">
<div v-for="method in group.methods" :key="method.id" class="method-item" data-cy="method-item">
<!-- 检查项目列表 -->
<div class="item-list" data-cy="item-list">
<el-checkbox-group v-model="selectedItemIds">
<el-row :gutter="20">
<el-col
v-for="item in filteredItems"
:key="item.id"
:span="6"
class="item-card"
>
<el-card :body-style="{ padding: '10px' }">
<div class="item-header">
<el-checkbox :label="item.id" />
<span class="item-name" :title="item.name">{{ item.name }}</span>
</div>
<!-- 检查方法检查方式 -->
<el-checkbox-group
v-model="selectedMethods[item.id]"
class="method-group"
@change="onMethodChange(item.id)"
>
<el-checkbox
v-model="method.checked"
@change="handleMethodToggle(method)"
data-cy="method-checkbox"
v-for="method in item.methods"
:key="method.id"
:label="method.id"
>
{{ method.name }}
</el-checkbox>
</div>
</div>
</div>
<el-empty v-if="selectedGroups.length === 0" description="暂无已选项目" />
</el-checkbox-group>
</el-card>
</el-col>
</el-row>
</el-checkbox-group>
</div>
<!-- 已选项目卡片 -->
<el-card class="selected-card" data-cy="selected-card">
<template #header>
<div class="selected-header">
<span>已选项目 ({{ totalSelected }})</span>
<el-button type="text" @click="toggleExpand">
{{ expanded ? '收起' : '展开' }}
</el-button>
</div>
</el-col>
</el-row>
</template>
<div v-show="expanded" class="selected-list">
<el-tag
v-for="item in selectedItems"
:key="item.id"
closable
@close="removeItem(item.id)"
class="selected-tag"
>
{{ item.name }}
</el-tag>
</div>
</el-card>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { ArrowDown, ArrowRight } from '@element-plus/icons-vue'
import { ref, computed, watch } from 'vue'
import { ElMessage } from 'element-plus'
import type { TreeNode } from 'element-plus/lib/components/tree/src/tree.type'
// 数据结构定义
interface ExamMethod { id: string; name: string; checked: boolean }
interface ExamItem { id: string; name: string; methods: ExamMethod[] }
interface Category { id: string; name: string; children: ExamItem[] }
// 模拟接口返回的数据结构(实际项目请改为真实 API
interface Method {
id: number
name: string
}
interface Item {
id: number
name: string
categoryId: number
methods: Method[]
}
interface Category {
id: number
label: string
children?: Category[]
}
// 模拟分类与项目数据实际应从API获取
const categories = ref<Category[]>([
{ id: 'c1', name: '彩超', children: [
{ id: 'i1', name: '套餐128线排彩超', methods: [{ id: 'm1', name: '常规检查', checked: false }, { id: 'm2', name: '血管多普勒', checked: false }] },
{ id: 'i2', name: '心脏彩超', methods: [{ id: 'm3', name: '二维超声', checked: false }] }
]}
// -------------------- 数据 --------------------
const categoryTree = ref<Category[]>([
{
id: 1,
label: '影像检查',
children: [
{ id: 11, label: 'X光' },
{ id: 12, label: 'CT' },
],
},
{
id: 2,
label: '实验室检查',
children: [
{ id: 21, label: '血液' },
{ id: 22, label: '尿液' },
],
},
])
const currentCategoryId = ref<string>('')
const selectedItemIds = ref<string[]>([])
const selectedItemsMap = ref<Map<string, { item: ExamItem; expanded: boolean }>>(new Map())
// 所有检查项目(实际请从后端获取)
const allItems = ref<Item[]>([
{
id: 101,
name: '胸部X光',
categoryId: 11,
methods: [
{ id: 1001, name: '正位' },
{ id: 1002, name: '侧位' },
],
},
{
id: 102,
name: '头部CT',
categoryId: 12,
methods: [
{ id: 1003, name: '平扫' },
{ id: 1004, name: '增强' },
],
},
// 更多项目...
])
const currentCategoryItems = computed(() => {
const cat = categories.value.find(c => c.id === currentCategoryId.value)
return cat ? cat.children : []
// 当前选中的分类 ID-1 表示全部)
const currentCategoryId = ref<number>(-1)
// 已选项目 ID 列表(仅记录项目本身,方法选择独立)
const selectedItemIds = ref<number[]>([])
// 记录每个项目对应的已选检查方法 ID 列表
const selectedMethods = ref<Record<number, number[]>>({})
// -------------------- 计算属性 --------------------
const filteredItems = computed(() => {
if (currentCategoryId.value === -1) return allItems.value
return allItems.value.filter(
(it) => it.categoryId === currentCategoryId.value
)
})
// 结构化展示:将已选项按 项目 > 检查方法 层级组织
const selectedGroups = computed(() => {
return Array.from(selectedItemsMap.value.entries()).map(([id, data]) => ({
id,
originalName: data.item.name,
displayName: formatName(data.item.name),
expanded: data.expanded,
methods: data.item.methods.map(m => ({ ...m }))
}))
const selectedItems = computed(() => {
return allItems.value.filter((it) => selectedItemIds.value.includes(it.id))
})
// 修复:清理“套餐”前缀及冗余文案
const formatName = (name: string) => name.replace(/^套餐[:]/, '').trim()
const totalSelected = computed(() => selectedItemIds.value.length)
const handleCategorySelect = (node: Category) => {
currentCategoryId.value = node.id
// -------------------- 交互逻辑 --------------------
const onCategorySelect = (node: TreeNode) => {
// 若点击的是根节点,展示全部
currentCategoryId.value = node.id ?? -1
}
// 修复1项目勾选与检查方法完全解耦移除原有自动联动逻辑
const handleItemToggle = (item: ExamItem) => {
if (selectedItemIds.value.includes(item.id)) {
// 新增时默认收起明细,避免界面拥挤
selectedItemsMap.value.set(item.id, { item, expanded: false })
} else {
selectedItemsMap.value.delete(item.id)
}
// 方法勾选时,保持项目与方法的解耦
const onMethodChange = (itemId: number) => {
// 若方法被全部取消,仍保留项目的选中状态
// (业务上可以在提交时根据 selectedMethods 决定是否需要提示)
}
// 修复2检查方法独立控制不随父项目状态联动
const handleMethodToggle = (method: ExamMethod) => {
// 仅更新当前方法勾选状态,不影响其他项
console.log(`方法 ${method.name} 状态变更为: ${method.checked}`)
// 删除已选项目
const removeItem = (itemId: number) => {
selectedItemIds.value = selectedItemIds.value.filter((id) => id !== itemId)
delete selectedMethods.value[itemId]
}
// 修复3点击卡片展开/收起明细
const toggleGroupExpand = (id: string) => {
const data = selectedItemsMap.value.get(id)
if (data) {
data.expanded = !data.expanded
}
// -------------------- 卡片展开/收起 --------------------
const expanded = ref(true)
const toggleExpand = () => {
expanded.value = !expanded.value
}
// -------------------- 监听 & 提示 --------------------
watch(selectedItemIds, (newVal) => {
// 初始化对应的 methods 数组,防止 undefined
newVal.forEach((id) => {
if (!selectedMethods.value[id]) {
selectedMethods.value[id] = []
}
})
})
</script>
<style scoped>
.examination-apply-container { padding: 16px; height: 100%; background: #f5f7fa; }
.panel { height: 600px; background: #fff; border-radius: 4px; padding: 12px; overflow: hidden; display: flex; flex-direction: column; box-shadow: 0 2px 4px rgba(0,0,0,0.05); }
.panel-header { font-weight: 600; margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid #ebeef5; color: #303133; }
.item-list { flex: 1; overflow-y: auto; }
.item-row { padding: 10px 0; border-bottom: 1px dashed #f0f0f0; }
.selected-list { flex: 1; overflow-y: auto; }
.selected-group { margin-bottom: 12px; }
/* 修复:卡片宽度自适应,文本溢出省略,悬停提示 */
.examination-apply {
padding: 20px;
}
.category-tree {
max-height: 300px;
overflow-y: auto;
margin-bottom: 20px;
}
.item-list {
margin-bottom: 20px;
}
.item-card {
margin-bottom: 10px;
}
.item-header {
display: flex;
align-items: center;
margin-bottom: 5px;
}
.item-name {
margin-left: 8px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.method-group {
display: flex;
flex-wrap: wrap;
}
.selected-card {
display: flex; justify-content: space-between; align-items: center;
padding: 10px 12px; background: #ecf5ff; border: 1px solid #d9ecff; border-radius: 4px; cursor: pointer;
width: auto; max-width: 100%; box-sizing: border-box; transition: background 0.2s;
margin-top: 20px;
}
.selected-card:hover { background: #d9ecff; }
.card-name {
flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
margin-right: 8px; font-size: 14px; color: #409eff; font-weight: 500;
.selected-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.selected-list {
margin-top: 10px;
}
.selected-tag {
margin: 4px;
}
.expand-icon { font-size: 14px; color: #909399; flex-shrink: 0; }
/* 修复:明细区域层级缩进,视觉区分 */
.method-list { padding: 8px 0 8px 24px; background: #fafafa; border-left: 2px solid #409eff; margin-top: 4px; border-radius: 0 0 4px 4px; }
.method-item { padding: 6px 0; font-size: 13px; color: #606266; }
.method-item .el-checkbox { margin-left: 0; }
</style>