Fix Bug #550: fallback修复
This commit is contained in:
@@ -1,168 +1,241 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="examination-apply-container">
|
<div class="examination-apply">
|
||||||
<el-row :gutter="16" style="height: 100%;">
|
<!-- 检查项目分类树 -->
|
||||||
<!-- 左侧:检查项目分类 -->
|
<el-tree
|
||||||
<el-col :span="6" class="panel">
|
:data="categoryTree"
|
||||||
<div class="panel-header">检查项目分类</div>
|
node-key="id"
|
||||||
<el-tree
|
:default-expand-all="true"
|
||||||
:data="categories"
|
@node-click="onCategorySelect"
|
||||||
:props="{ label: 'name', children: 'children' }"
|
data-cy="category-tree"
|
||||||
highlight-current
|
class="category-tree"
|
||||||
@node-click="handleCategorySelect"
|
/>
|
||||||
data-cy="category-tree"
|
|
||||||
/>
|
|
||||||
</el-col>
|
|
||||||
|
|
||||||
<!-- 中间:检查项目列表 -->
|
<!-- 检查项目列表 -->
|
||||||
<el-col :span="9" class="panel">
|
<div class="item-list" data-cy="item-list">
|
||||||
<div class="panel-header">检查项目</div>
|
<el-checkbox-group v-model="selectedItemIds">
|
||||||
<div class="item-list" data-cy="item-list">
|
<el-row :gutter="20">
|
||||||
<div v-for="item in currentCategoryItems" :key="item.id" class="item-row">
|
<el-col
|
||||||
<el-checkbox
|
v-for="item in filteredItems"
|
||||||
:label="item.id"
|
:key="item.id"
|
||||||
v-model="selectedItemIds"
|
:span="6"
|
||||||
@change="handleItemToggle(item)"
|
class="item-card"
|
||||||
>
|
>
|
||||||
{{ item.name }}
|
<el-card :body-style="{ padding: '10px' }">
|
||||||
</el-checkbox>
|
<div class="item-header">
|
||||||
</div>
|
<el-checkbox :label="item.id" />
|
||||||
</div>
|
<span class="item-name" :title="item.name">{{ item.name }}</span>
|
||||||
</el-col>
|
</div>
|
||||||
|
<!-- 检查方法(检查方式) -->
|
||||||
<!-- 右侧:已选择区域 -->
|
<el-checkbox-group
|
||||||
<el-col :span="9" class="panel">
|
v-model="selectedMethods[item.id]"
|
||||||
<div class="panel-header">已选择</div>
|
class="method-group"
|
||||||
<div class="selected-list">
|
@change="onMethodChange(item.id)"
|
||||||
<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">
|
|
||||||
<el-checkbox
|
<el-checkbox
|
||||||
v-model="method.checked"
|
v-for="method in item.methods"
|
||||||
@change="handleMethodToggle(method)"
|
:key="method.id"
|
||||||
data-cy="method-checkbox"
|
:label="method.id"
|
||||||
>
|
>
|
||||||
{{ method.name }}
|
{{ method.name }}
|
||||||
</el-checkbox>
|
</el-checkbox>
|
||||||
</div>
|
</el-checkbox-group>
|
||||||
</div>
|
</el-card>
|
||||||
</div>
|
</el-col>
|
||||||
<el-empty v-if="selectedGroups.length === 0" description="暂无已选项目" />
|
</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>
|
</div>
|
||||||
</el-col>
|
</template>
|
||||||
</el-row>
|
|
||||||
|
<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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed, watch } from 'vue'
|
||||||
import { ArrowDown, ArrowRight } from '@element-plus/icons-vue'
|
import { ElMessage } from 'element-plus'
|
||||||
|
import type { TreeNode } from 'element-plus/lib/components/tree/src/tree.type'
|
||||||
|
|
||||||
// 数据结构定义
|
// 模拟接口返回的数据结构(实际项目请改为真实 API)
|
||||||
interface ExamMethod { id: string; name: string; checked: boolean }
|
interface Method {
|
||||||
interface ExamItem { id: string; name: string; methods: ExamMethod[] }
|
id: number
|
||||||
interface Category { id: string; name: string; children: ExamItem[] }
|
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[]>([
|
const categoryTree = ref<Category[]>([
|
||||||
{ id: 'c1', name: '彩超', children: [
|
{
|
||||||
{ id: 'i1', name: '套餐:128线排彩超', methods: [{ id: 'm1', name: '常规检查', checked: false }, { id: 'm2', name: '血管多普勒', checked: false }] },
|
id: 1,
|
||||||
{ id: 'i2', name: '心脏彩超', methods: [{ id: 'm3', name: '二维超声', checked: false }] }
|
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 allItems = ref<Item[]>([
|
||||||
const selectedItemsMap = ref<Map<string, { item: ExamItem; expanded: boolean }>>(new Map())
|
{
|
||||||
|
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(() => {
|
// 当前选中的分类 ID(-1 表示全部)
|
||||||
const cat = categories.value.find(c => c.id === currentCategoryId.value)
|
const currentCategoryId = ref<number>(-1)
|
||||||
return cat ? cat.children : []
|
|
||||||
|
// 已选项目 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 selectedItems = computed(() => {
|
||||||
const selectedGroups = computed(() => {
|
return allItems.value.filter((it) => selectedItemIds.value.includes(it.id))
|
||||||
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 totalSelected = computed(() => selectedItemIds.value.length)
|
||||||
const formatName = (name: string) => name.replace(/^套餐[::]/, '').trim()
|
|
||||||
|
|
||||||
const handleCategorySelect = (node: Category) => {
|
// -------------------- 交互逻辑 --------------------
|
||||||
currentCategoryId.value = node.id
|
const onCategorySelect = (node: TreeNode) => {
|
||||||
|
// 若点击的是根节点,展示全部
|
||||||
|
currentCategoryId.value = node.id ?? -1
|
||||||
}
|
}
|
||||||
|
|
||||||
// 修复1:项目勾选与检查方法完全解耦,移除原有自动联动逻辑
|
// 方法勾选时,保持项目与方法的解耦
|
||||||
const handleItemToggle = (item: ExamItem) => {
|
const onMethodChange = (itemId: number) => {
|
||||||
if (selectedItemIds.value.includes(item.id)) {
|
// 若方法被全部取消,仍保留项目的选中状态
|
||||||
// 新增时默认收起明细,避免界面拥挤
|
// (业务上可以在提交时根据 selectedMethods 决定是否需要提示)
|
||||||
selectedItemsMap.value.set(item.id, { item, expanded: false })
|
|
||||||
} else {
|
|
||||||
selectedItemsMap.value.delete(item.id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 修复2:检查方法独立控制,不随父项目状态联动
|
// 删除已选项目
|
||||||
const handleMethodToggle = (method: ExamMethod) => {
|
const removeItem = (itemId: number) => {
|
||||||
// 仅更新当前方法勾选状态,不影响其他项
|
selectedItemIds.value = selectedItemIds.value.filter((id) => id !== itemId)
|
||||||
console.log(`方法 ${method.name} 状态变更为: ${method.checked}`)
|
delete selectedMethods.value[itemId]
|
||||||
}
|
}
|
||||||
|
|
||||||
// 修复3:点击卡片展开/收起明细
|
// -------------------- 卡片展开/收起 --------------------
|
||||||
const toggleGroupExpand = (id: string) => {
|
const expanded = ref(true)
|
||||||
const data = selectedItemsMap.value.get(id)
|
const toggleExpand = () => {
|
||||||
if (data) {
|
expanded.value = !expanded.value
|
||||||
data.expanded = !data.expanded
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------- 监听 & 提示 --------------------
|
||||||
|
watch(selectedItemIds, (newVal) => {
|
||||||
|
// 初始化对应的 methods 数组,防止 undefined
|
||||||
|
newVal.forEach((id) => {
|
||||||
|
if (!selectedMethods.value[id]) {
|
||||||
|
selectedMethods.value[id] = []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.examination-apply-container { padding: 16px; height: 100%; background: #f5f7fa; }
|
.examination-apply {
|
||||||
.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); }
|
padding: 20px;
|
||||||
.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; }
|
.category-tree {
|
||||||
.item-row { padding: 10px 0; border-bottom: 1px dashed #f0f0f0; }
|
max-height: 300px;
|
||||||
.selected-list { flex: 1; overflow-y: auto; }
|
overflow-y: auto;
|
||||||
.selected-group { margin-bottom: 12px; }
|
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 {
|
.selected-card {
|
||||||
display: flex; justify-content: space-between; align-items: center;
|
margin-top: 20px;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
.selected-card:hover { background: #d9ecff; }
|
.selected-header {
|
||||||
.card-name {
|
display: flex;
|
||||||
flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
justify-content: space-between;
|
||||||
margin-right: 8px; font-size: 14px; color: #409eff; font-weight: 500;
|
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>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user