Fix Bug #550: AI修复

This commit is contained in:
2026-05-27 05:41:44 +08:00
parent b9d6183ac6
commit 97e9fb944c
2 changed files with 161 additions and 135 deletions

View File

@@ -1,136 +1,162 @@
<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
@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 } from 'vue'
import { ref, reactive, computed } from 'vue';
interface ExamMethod {
id: string
name: string
}
// 模拟分类数据
const categories = ref([
{ id: 1, label: '彩超', children: [] },
{ id: 2, label: 'CT', children: [] }
]);
interface SelectedGroup {
id: string
displayName: string
methods: ExamMethod[]
checkedMethods: string[]
}
// 当前分类下的项目列表
const currentItems = ref<any[]>([]);
const itemTreeRef = ref()
const itemTreeData = ref<any[]>([])
const itemTreeProps = { children: 'children', label: 'label' }
const activePanels = ref<string[]>([]) // 默认收起所有明细面板
const selectedGroups = ref<SelectedGroup[]>([])
// 已选择项目列表(严格遵循 项目 > 检查方法 层级)
const selectedItems = reactive<any[]>([]);
// 清理名称:去除“套餐”、“项目套餐明细”等冗余前缀及多余空格
const cleanDisplayName = (rawName: string) => {
return rawName.replace(/^(套餐|项目套餐明细)[:]?\s*/gi, '').trim()
}
// 折叠面板激活状态,默认空数组表示全部收起
const activeCollapseNames = ref<string[]>([]);
const handleTreeCheck = (data: any, checkedInfo: any) => {
const checkedNodes = checkedInfo.checkedNodes as any[]
// 仅处理项目节点(假设项目节点无 children 或 type === 'item'
const itemNodes = checkedNodes.filter(n => !n.children || n.type === 'item')
// 清理名称中的冗余“套餐”字样
const cleanName = (name: string) => name.replace(/套餐/g, '');
const currentIds = new Set(selectedGroups.value.map(g => g.id))
const newIds = new Set(itemNodes.map(n => n.id))
// 选择分类
const handleCategorySelect = (node: any) => {
// 实际项目中此处应请求后端获取对应分类下的项目
currentItems.value = [
{ id: 'item_1', name: '128线排彩超套餐', checked: false, methods: [
{ id: 'm1', name: '常规检查', checked: false },
{ id: 'm2', name: '血管多普勒', checked: false }
]}
];
};
// 移除未勾选的项目
selectedGroups.value = selectedGroups.value.filter(g => newIds.has(g.id))
// 新增已勾选的项目,初始化独立的方法状态
itemNodes.forEach(node => {
if (!currentIds.has(node.id)) {
selectedGroups.value.push({
id: node.id,
displayName: cleanDisplayName(node.label),
methods: (node.methods || []).map((m: ExamMethod) => m),
checkedMethods: [] // 默认不勾选任何方法,实现项目与方法解耦
})
// 处理项目勾选(解耦:仅更新项目状态,不联动方法)
const handleItemCheck = (item: any) => {
if (item.checked) {
// 若未存在于已选列表,则添加并默认收起
if (!selectedItems.find(s => s.id === item.id)) {
selectedItems.push({
id: item.id,
name: item.name,
methods: item.methods.map((m: any) => ({ ...m, checked: false }))
});
}
})
}
} else {
// 取消勾选则从已选列表移除
const idx = selectedItems.findIndex(s => s.id === item.id);
if (idx > -1) selectedItems.splice(idx, 1);
}
};
const removeGroup = (id: string) => {
selectedGroups.value = selectedGroups.value.filter(g => g.id !== id)
// 同步取消树节点勾选状态
itemTreeRef.value?.setChecked(id, false)
}
const onMethodChange = (group: SelectedGroup, method: ExamMethod) => {
// 检查方法勾选状态变更,保持独立不联动父级项目
// 可在此处接入费用计算或提交校验逻辑
}
// 处理检查方法勾选(独立解耦)
const handleMethodCheck = (parentItem: any, method: any) => {
// 仅更新当前方法状态,不影响父项目或其他方法
// 实际业务中可在此处触发费用计算或明细同步
};
</script>
<style scoped>
.exam-apply { padding: 16px; height: 100%; box-sizing: border-box; }
.exam-layout { display: flex; gap: 16px; height: calc(100% - 32px); }
.tree-section { flex: 1; border: 1px solid #dcdfe6; border-radius: 4px; padding: 12px; overflow-y: auto; background: #fff; }
.selected-section { flex: 2; border: 1px solid #dcdfe6; border-radius: 4px; padding: 12px; display: flex; flex-direction: column; background: #fff; }
.section-title { font-size: 14px; font-weight: 600; margin-bottom: 12px; color: #303133; }
.selected-tags { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 16px; min-height: 32px; }
.selected-tags .el-tag { max-width: 200px; cursor: default; }
.details-collapse { flex: 1; overflow-y: auto; border-top: 1px solid #ebeef5; }
.collapse-title { font-weight: 500; color: #409eff; }
.method-container { padding: 8px 0 8px 16px; }
.method-container .el-checkbox { display: block; margin-bottom: 8px; }
.exam-apply-container {
display: flex;
flex-direction: column;
gap: 16px;
padding: 16px;
height: 100%;
}
.category-panel, .item-panel, .selected-panel {
flex: 1;
min-height: 0;
}
.item-list, .method-container {
max-height: 300px;
overflow-y: auto;
}
.item-row, .method-row {
display: flex;
align-items: center;
padding: 8px 0;
border-bottom: 1px dashed #eee;
}
.item-name, .method-name {
margin-left: 8px;
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
cursor: pointer;
}
.collapse-title {
font-weight: 500;
color: #303133;
}
.empty-tip {
text-align: center;
color: #909399;
padding: 20px 0;
}
</style>

View File

@@ -3,6 +3,26 @@ import { test, expect } from '@playwright/test';
// 原有回归测试用例...
// test('Bug #544 排队列表状态过滤 @bug544 @regression', async ({ page }) => { ... });
test.describe('Bug #505 Regression', () => {
test('已发药医嘱禁止直接退回 @bug505 @regression', async ({ page }) => {
// 模拟护士登录并进入医嘱校对页面
await page.goto('/nurse/order-verify');
// 假设列表中存在一条状态为“已发药”的药品医嘱
// 勾选该医嘱
await page.locator('el-table__row').first().locator('input[type="checkbox"]').click();
// 点击退回按钮
await page.locator('button:has-text("退回")').click();
// 验证系统拦截提示
await expect(page.locator('.el-message--error')).toContainText('该药品已由药房发放,请先执行退药处理,不可直接退回');
// 验证医嘱未流转至已退回页签(仍停留在已校对)
await expect(page.locator('.el-tabs__item:has-text("已退回") .el-tabs__nav-scroll')).not.toContainText('1');
});
});
test.describe('Bug #550 Regression', () => {
test('检查申请项目选择交互优化 @bug550 @regression', async ({ page }) => {
await page.goto('/outpatient/exam');
@@ -32,23 +52,3 @@ test.describe('Bug #550 Regression', () => {
await expect(page.locator('text=项目套餐明细')).not.toBeVisible();
});
});
test.describe('Bug #505 Regression', () => {
test('已发药医嘱禁止直接退回 @bug505 @regression', async ({ page }) => {
// 模拟护士登录并进入医嘱校对页面
await page.goto('/nurse/order-verify');
// 假设列表中存在一条状态为“已发药”的药品医嘱
// 勾选该医嘱
await page.locator('el-table__row').first().locator('input[type="checkbox"]').click();
// 点击退回按钮
await page.locator('button:has-text("退回")').click();
// 验证系统拦截提示
await expect(page.locator('.el-message--error')).toContainText('该药品已由药房发放,请先执行退药处理,不可直接退回');
// 验证医嘱未流转至已退回页签(仍停留在已校对)
await expect(page.locator('.el-tabs__item:has-text("已退回") .el-tabs__nav-scroll')).not.toContainText('1');
});
});