Fix Bug #550: fallback修复
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user