Fix Bug #550: AI修复
This commit is contained in:
@@ -1,228 +1,156 @@
|
||||
<template>
|
||||
<div class="exam-apply">
|
||||
<div class="exam-layout">
|
||||
<!-- 左侧:检查项目分类与项目树 -->
|
||||
<div class="tree-section">
|
||||
<el-tree
|
||||
ref="itemTreeRef"
|
||||
:data="itemTreeData"
|
||||
node-key="id"
|
||||
show-checkbox
|
||||
:check-strictly="true"
|
||||
:default-expand-all="false"
|
||||
:props="itemTreeProps"
|
||||
@check="handleTreeCheck"
|
||||
/>
|
||||
</div>
|
||||
<div class="exam-apply-container">
|
||||
<!-- 左侧:检查项目分类 -->
|
||||
<el-card class="category-panel" shadow="never">
|
||||
<template #header>检查项目分类</template>
|
||||
<el-tree
|
||||
:data="categories"
|
||||
node-key="id"
|
||||
highlight-current
|
||||
@node-click="handleCategorySelect"
|
||||
/>
|
||||
</el-card>
|
||||
|
||||
<!-- 右侧:已选项目与明细 -->
|
||||
<div class="selected-section">
|
||||
<div class="section-title">已选择</div>
|
||||
<div class="selected-tags">
|
||||
<el-tag
|
||||
v-for="group in selectedGroups"
|
||||
:key="group.id"
|
||||
closable
|
||||
size="default"
|
||||
show-overflow-tooltip
|
||||
:title="group.fullName"
|
||||
@close="removeGroup(group.id)"
|
||||
>
|
||||
{{ group.displayName }}
|
||||
</el-tag>
|
||||
<!-- 中间:检查项目列表 -->
|
||||
<el-card class="item-panel" shadow="never">
|
||||
<template #header>检查项目</template>
|
||||
<div class="item-list">
|
||||
<div v-for="item in currentItems" :key="item.id" class="item-row">
|
||||
<el-checkbox
|
||||
v-model="item.checked"
|
||||
@change="handleItemCheck(item)"
|
||||
/>
|
||||
<el-tooltip :content="item.name" placement="top" :show-after="300">
|
||||
<span class="item-name">{{ cleanName(item.name) }}</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
|
||||
<el-collapse v-model="activePanels" class="details-collapse">
|
||||
<el-collapse-item
|
||||
v-for="group in selectedGroups"
|
||||
:key="group.id"
|
||||
:name="group.id"
|
||||
>
|
||||
<template #title>
|
||||
<span class="collapse-title">{{ group.displayName }}</span>
|
||||
</template>
|
||||
<div class="method-container">
|
||||
<el-checkbox-group v-model="group.checkedMethods">
|
||||
<el-checkbox
|
||||
v-for="method in group.methods"
|
||||
:key="method.id"
|
||||
:label="method.id"
|
||||
@change="onMethodChange(group, method)"
|
||||
>
|
||||
{{ method.name }}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 下方:已选择区域 -->
|
||||
<el-card class="selected-panel" shadow="never">
|
||||
<template #header>已选择</template>
|
||||
<div v-if="selectedItems.length === 0" class="empty-tip">暂无已选项目</div>
|
||||
<el-collapse v-else v-model="activeCollapseNames" accordion>
|
||||
<el-collapse-item
|
||||
v-for="sel in selectedItems"
|
||||
:key="sel.id"
|
||||
:name="sel.id"
|
||||
>
|
||||
<template #title>
|
||||
<el-tooltip :content="sel.name" placement="top" :show-after="300">
|
||||
<span class="collapse-title">{{ cleanName(sel.name) }}</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<div class="method-container">
|
||||
<div v-for="method in sel.methods" :key="method.id" class="method-row">
|
||||
<el-checkbox
|
||||
v-model="method.checked"
|
||||
@change="handleMethodCheck(sel, method)"
|
||||
/>
|
||||
<span class="method-name">{{ method.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import type { TreeNode } from 'element-plus/lib/components/tree/src/tree.type';
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
// --------------------
|
||||
// 数据结构定义(简化示例)
|
||||
// --------------------
|
||||
interface Method {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
interface Group {
|
||||
id: number;
|
||||
name: string; // 原始名称,可能带 “套餐” 前缀
|
||||
methods: Method[];
|
||||
// 类型定义(实际项目中应从 API 或全局类型导入)
|
||||
interface ExamMethod { id: string; name: string; checked: boolean }
|
||||
interface ExamItem { id: string; name: string; checked: boolean; methods: ExamMethod[] }
|
||||
interface Category { id: string; name: string; children: ExamItem[] }
|
||||
|
||||
// 模拟数据源(实际应通过 API 获取)
|
||||
const categories = ref<Category[]>([])
|
||||
const currentCategory = ref<Category | null>(null)
|
||||
|
||||
// 修复 Bug #550:默认收起状态,不预设任何展开项
|
||||
const activeCollapseNames = ref<string[]>([])
|
||||
|
||||
const currentItems = computed(() => currentCategory.value?.children || [])
|
||||
|
||||
const selectedItems = computed(() => {
|
||||
const result: ExamItem[] = []
|
||||
categories.value.forEach(cat => {
|
||||
cat.children.forEach(item => {
|
||||
if (item.checked) result.push(item)
|
||||
})
|
||||
})
|
||||
return result
|
||||
})
|
||||
|
||||
const handleCategorySelect = (node: Category) => {
|
||||
currentCategory.value = node
|
||||
}
|
||||
|
||||
/**
|
||||
* 1️⃣ 读取树形数据(这里假设已经通过接口获取并赋值)
|
||||
* itemTreeData 的每个节点对应一个检查项目(Group),
|
||||
* 其 children 为检查方法(Method)。
|
||||
*/
|
||||
const itemTreeData = ref<TreeNode[]>([]);
|
||||
|
||||
/**
|
||||
* Tree 组件的属性映射
|
||||
*/
|
||||
const itemTreeProps = {
|
||||
label: 'name',
|
||||
children: 'children',
|
||||
};
|
||||
|
||||
/**
|
||||
* 已选中的项目(Group)集合
|
||||
* 结构:
|
||||
* id, displayName(去掉 “套餐” 前缀的名称),fullName(原始完整名称,用于 tooltip),
|
||||
* methods(所有可选方法),checkedMethods(已勾选的方法 id 列表)
|
||||
*/
|
||||
const selectedGroups = ref<Array<{
|
||||
id: number;
|
||||
displayName: string;
|
||||
fullName: string;
|
||||
methods: Method[];
|
||||
checkedMethods: number[];
|
||||
}>>([]);
|
||||
|
||||
/**
|
||||
* 当前展开的 Collapse 面板(使用 group.id)
|
||||
*/
|
||||
const activePanels = ref<number[]>([]);
|
||||
|
||||
/**
|
||||
* 处理树节点勾选
|
||||
*
|
||||
* 需求:
|
||||
* - 勾选项目节点时,只把项目本身加入 selectedGroups,**不**自动勾选其下的检查方法。
|
||||
* - 取消勾选时,移除对应的项目及其所有方法的选中状态。
|
||||
*
|
||||
* 通过 `check-strictly` 已经阻止了父子自动关联,但在业务层仍需要把
|
||||
* 勾选的节点(只保留叶子为方法的情况)过滤掉,只保留项目节点。
|
||||
*/
|
||||
function handleTreeCheck(
|
||||
data: any,
|
||||
checkedNodes: any[],
|
||||
indeterminateNodes: any[]
|
||||
) {
|
||||
// 1. 过滤出真正的项目节点(即没有 children,或 children 为方法列表的父节点)
|
||||
const projectNodes = checkedNodes.filter((node: any) => {
|
||||
// 项目节点的 children 为方法数组,且 node.type === 'group'(业务自行约定)
|
||||
// 为了兼容后端未返回 type 字段,这里判断是否存在 children 即可。
|
||||
return node.children && node.children.length > 0;
|
||||
});
|
||||
|
||||
// 2. 重新构建 selectedGroups,仅保留项目层级信息
|
||||
const newGroups = projectNodes.map((node: any) => {
|
||||
const originalName: string = node.name || '';
|
||||
// 去掉 “套餐” 前缀(如果有),并去除首尾空格
|
||||
const displayName = originalName.replace(/^套餐\s*/, '').trim();
|
||||
|
||||
// 已经存在的 group(用于保留已勾选的方法)
|
||||
const existed = selectedGroups.value.find(g => g.id === node.id);
|
||||
|
||||
return {
|
||||
id: node.id,
|
||||
displayName,
|
||||
fullName: originalName, // 用于 tooltip
|
||||
methods: (node.children || []).map((m: any) => ({
|
||||
id: m.id,
|
||||
name: m.name,
|
||||
})),
|
||||
// 保留之前已勾选的方法(若之前已存在),否则空数组
|
||||
checkedMethods: existed ? existed.checkedMethods : [],
|
||||
};
|
||||
});
|
||||
|
||||
selectedGroups.value = newGroups;
|
||||
// 修复 Bug #550:项目勾选与检查方法完全解耦
|
||||
const handleItemCheck = (item: ExamItem) => {
|
||||
// 仅切换项目自身的 checked 状态,绝不联动修改 item.methods
|
||||
// 保持父子状态独立,由医生手动分别勾选
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除已选项目
|
||||
*/
|
||||
function removeGroup(groupId: number) {
|
||||
selectedGroups.value = selectedGroups.value.filter(g => g.id !== groupId);
|
||||
const handleMethodCheck = (sel: ExamItem, method: ExamMethod) => {
|
||||
// 仅切换检查方法状态,不影响父级项目
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查方法勾选变化
|
||||
* - 只更新对应 group 的 checkedMethods。
|
||||
* - 不会影响其他 group,也不会触发项目自动勾选。
|
||||
*/
|
||||
function onMethodChange(group: any, method: any) {
|
||||
// 方法的勾选状态已经由 v-model 自动同步到 group.checkedMethods,
|
||||
// 这里仅做业务校验(如需要限制同一项目只能选择一种方法等),
|
||||
// 目前保持空实现以避免副作用。
|
||||
// 修复 Bug #550:清理冗余“套餐”字样,避免名称遮挡
|
||||
const cleanName = (name: string) => {
|
||||
if (!name) return ''
|
||||
// 移除常见冗余前缀/后缀,保留核心业务名称
|
||||
return name.replace(/(项目)?套餐(明细)?/g, '').trim()
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化(示例:从后端获取树形数据)
|
||||
*/
|
||||
async function loadTreeData() {
|
||||
// 这里使用 mock 数据,实际请调用接口
|
||||
const mockData = [
|
||||
{
|
||||
id: 1,
|
||||
name: '套餐 彩超',
|
||||
children: [
|
||||
{ id: 101, name: '128线排' },
|
||||
{ id: 102, name: '多普勒' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '普通 X 光',
|
||||
children: [{ id: 201, name: '胸片' }],
|
||||
},
|
||||
];
|
||||
itemTreeData.value = mockData;
|
||||
}
|
||||
loadTreeData();
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.exam-apply {
|
||||
padding: 20px;
|
||||
}
|
||||
.exam-layout {
|
||||
.exam-apply-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
padding: 16px;
|
||||
height: 100%;
|
||||
}
|
||||
.tree-section {
|
||||
width: 30%;
|
||||
margin-right: 20px;
|
||||
|
||||
.category-panel, .item-panel, .selected-panel {
|
||||
width: 100%;
|
||||
}
|
||||
.selected-section {
|
||||
flex: 1;
|
||||
|
||||
.item-list {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.selected-tags {
|
||||
margin-bottom: 10px;
|
||||
|
||||
.item-row, .method-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 0;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* 修复 Bug #550:名称超长时省略显示,配合 Tooltip 提示完整内容 */
|
||||
.item-name, .collapse-title {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 100%;
|
||||
display: inline-block;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.method-container {
|
||||
padding: 10px;
|
||||
padding-left: 24px;
|
||||
border-left: 2px solid #ebeef5;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.collapse-title {
|
||||
font-weight: 500;
|
||||
|
||||
.empty-tip {
|
||||
color: #909399;
|
||||
text-align: center;
|
||||
padding: 24px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -29,14 +29,14 @@ test.describe('Bug #550 Regression', () => {
|
||||
|
||||
// 1. 验证解耦:勾选项目不应自动勾选检查方法
|
||||
await page.locator('.el-tree-node__content:has-text("彩超")').click();
|
||||
await page.locator('.el-tree-node__content:has-text("128线排") .el-checkbox').click();
|
||||
await page.locator('.item-row:has-text("128线排") .el-checkbox').click();
|
||||
const methodCheckbox = page.locator('.method-container .el-checkbox').first();
|
||||
await expect(methodCheckbox).not.toBeChecked();
|
||||
|
||||
// 2. 验证卡片显示:无“套餐”前缀,支持悬浮提示完整名称
|
||||
const tag = page.locator('.selected-tags .el-tag').first();
|
||||
await expect(tag).not.toContainText('套餐');
|
||||
await expect(tag).toHaveAttribute('title');
|
||||
const collapseTitle = page.locator('.el-collapse-item__header .collapse-title').first();
|
||||
await expect(collapseTitle).not.toContainText('套餐');
|
||||
await expect(collapseTitle).toHaveAttribute('title');
|
||||
|
||||
// 3. 验证默认收起与层级结构(项目 > 检查方法)
|
||||
const collapseItems = page.locator('.el-collapse-item');
|
||||
|
||||
Reference in New Issue
Block a user