Fix Bug #550: AI修复
This commit is contained in:
@@ -1,241 +1,180 @@
|
||||
<template>
|
||||
<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"
|
||||
/>
|
||||
<div class="examination-apply-container">
|
||||
<!-- 左侧:检查项目分类 -->
|
||||
<div class="panel category-panel" data-cy="category-tree">
|
||||
<el-tree
|
||||
:data="categories"
|
||||
:props="{ label: 'name', children: 'children' }"
|
||||
highlight-current
|
||||
@node-click="handleCategoryClick"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 检查项目列表 -->
|
||||
<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-for="method in item.methods"
|
||||
:key="method.id"
|
||||
:label="method.id"
|
||||
>
|
||||
{{ method.name }}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!-- 中间:检查项目列表 -->
|
||||
<div class="panel item-panel" data-cy="item-list">
|
||||
<el-checkbox-group v-model="selectedItemIds" @change="handleItemSelect">
|
||||
<div v-for="item in currentItems" :key="item.id" class="item-row">
|
||||
<el-checkbox :label="item.id" :data-cy="`item-checkbox-${item.id}`">
|
||||
{{ cleanName(item.name) }}
|
||||
</el-checkbox>
|
||||
</div>
|
||||
</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 class="panel selected-panel" data-cy="selected-panel">
|
||||
<h3 class="panel-title">已选择</h3>
|
||||
<div v-if="selectedList.length === 0" class="empty-tip">暂无选择项目</div>
|
||||
<div v-for="selected in selectedList" :key="selected.id" class="selected-card">
|
||||
<div class="card-header" @click="toggleDetail(selected)">
|
||||
<el-checkbox
|
||||
:model-value="selected.checked"
|
||||
@change="val => handleCardCheck(selected, val)"
|
||||
@click.stop
|
||||
/>
|
||||
<el-tooltip :content="selected.displayName" placement="top" :show-after="300">
|
||||
<span class="card-name" data-cy="selected-card-name">{{ selected.displayName }}</span>
|
||||
</el-tooltip>
|
||||
<el-icon class="toggle-icon" data-cy="selected-card-toggle">
|
||||
<ArrowDown v-if="selected.expanded" />
|
||||
<ArrowRight v-else />
|
||||
</el-icon>
|
||||
</div>
|
||||
<!-- 默认收起,点击展开显示检查方法 -->
|
||||
<div v-show="selected.expanded" class="card-detail" data-cy="selected-card-detail">
|
||||
<div class="detail-title">检查方法</div>
|
||||
<el-checkbox-group v-model="selected.selectedMethods" @change="handleMethodSelect(selected)">
|
||||
<div v-for="method in selected.methods" :key="method.id" class="method-row">
|
||||
<el-checkbox :label="method.id" :data-cy="`method-checkbox-${selected.id}-${method.id}`">
|
||||
{{ method.name }}
|
||||
</el-checkbox>
|
||||
</div>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
</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>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import type { TreeNode } from 'element-plus/lib/components/tree/src/tree.type'
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { ArrowDown, ArrowRight } from '@element-plus/icons-vue'
|
||||
|
||||
// 模拟接口返回的数据结构(实际项目请改为真实 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[]
|
||||
// 状态定义
|
||||
const categories = ref([])
|
||||
const currentItems = ref([])
|
||||
const selectedItemIds = ref([])
|
||||
const selectedList = ref([])
|
||||
|
||||
// 清理冗余“套餐”字样
|
||||
const cleanName = (name) => name.replace(/套餐/g, '')
|
||||
|
||||
// 分类切换
|
||||
const handleCategoryClick = (data) => {
|
||||
currentItems.value = data.items || []
|
||||
}
|
||||
|
||||
// -------------------- 数据 --------------------
|
||||
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 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: '增强' },
|
||||
],
|
||||
},
|
||||
// 更多项目...
|
||||
])
|
||||
|
||||
// 当前选中的分类 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 selectedItems = computed(() => {
|
||||
return allItems.value.filter((it) => selectedItemIds.value.includes(it.id))
|
||||
})
|
||||
|
||||
const totalSelected = computed(() => selectedItemIds.value.length)
|
||||
|
||||
// -------------------- 交互逻辑 --------------------
|
||||
const onCategorySelect = (node: TreeNode) => {
|
||||
// 若点击的是根节点,展示全部
|
||||
currentCategoryId.value = node.id ?? -1
|
||||
}
|
||||
|
||||
// 方法勾选时,保持项目与方法的解耦
|
||||
const onMethodChange = (itemId: number) => {
|
||||
// 若方法被全部取消,仍保留项目的选中状态
|
||||
// (业务上可以在提交时根据 selectedMethods 决定是否需要提示)
|
||||
}
|
||||
|
||||
// 删除已选项目
|
||||
const removeItem = (itemId: number) => {
|
||||
selectedItemIds.value = selectedItemIds.value.filter((id) => id !== itemId)
|
||||
delete selectedMethods.value[itemId]
|
||||
}
|
||||
|
||||
// -------------------- 卡片展开/收起 --------------------
|
||||
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] = []
|
||||
// 项目勾选(解耦:不联动方法)
|
||||
const handleItemSelect = (ids) => {
|
||||
const newSelected = ids.map(id => {
|
||||
const existing = selectedList.value.find(s => s.id === id)
|
||||
if (existing) return existing
|
||||
const item = currentItems.value.find(i => i.id === id)
|
||||
return {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
displayName: cleanName(item.name),
|
||||
checked: true,
|
||||
expanded: false, // 默认收起
|
||||
methods: item.methods || [],
|
||||
selectedMethods: []
|
||||
}
|
||||
})
|
||||
})
|
||||
// 同步移除已取消勾选的项目
|
||||
selectedList.value = newSelected
|
||||
}
|
||||
|
||||
// 展开/收起明细
|
||||
const toggleDetail = (item) => {
|
||||
item.expanded = !item.expanded
|
||||
}
|
||||
|
||||
// 卡片级勾选控制
|
||||
const handleCardCheck = (item, val) => {
|
||||
item.checked = val
|
||||
if (!val) {
|
||||
selectedItemIds.value = selectedItemIds.value.filter(id => id !== item.id)
|
||||
selectedList.value = selectedList.value.filter(s => s.id !== item.id)
|
||||
}
|
||||
}
|
||||
|
||||
// 方法勾选(独立状态,不影响父级)
|
||||
const handleMethodSelect = (item) => {
|
||||
// 仅记录或触发后续业务逻辑,不修改项目勾选状态
|
||||
console.log(`项目 ${item.displayName} 方法已更新:`, item.selectedMethods)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.examination-apply {
|
||||
padding: 20px;
|
||||
.examination-apply-container {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
padding: 16px;
|
||||
height: 100%;
|
||||
background: #f5f7fa;
|
||||
}
|
||||
.category-tree {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
margin-bottom: 20px;
|
||||
.panel {
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||
}
|
||||
.item-list {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.item-card {
|
||||
.category-panel { width: 22%; }
|
||||
.item-panel { width: 28%; }
|
||||
.selected-panel { flex: 1; overflow-y: auto; }
|
||||
.panel-title { margin: 0 0 12px; font-size: 15px; font-weight: 600; color: #303133; }
|
||||
.empty-tip { color: #909399; text-align: center; padding: 20px 0; }
|
||||
.item-row, .method-row { padding: 8px 0; border-bottom: 1px dashed #f0f0f0; }
|
||||
.item-row:last-child, .method-row:last-child { border-bottom: none; }
|
||||
|
||||
.selected-card {
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 10px;
|
||||
background: #fafafa;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.item-header {
|
||||
.selected-card:hover { border-color: #409eff; }
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 5px;
|
||||
padding: 10px 12px;
|
||||
cursor: pointer;
|
||||
gap: 8px;
|
||||
user-select: none;
|
||||
}
|
||||
.item-name {
|
||||
margin-left: 8px;
|
||||
.card-name {
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
}
|
||||
.method-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
.toggle-icon {
|
||||
color: #909399;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
.selected-card {
|
||||
margin-top: 20px;
|
||||
.card-detail {
|
||||
padding: 0 12px 12px 36px;
|
||||
border-top: 1px dashed #ebeef5;
|
||||
background: #fff;
|
||||
}
|
||||
.selected-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.selected-list {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.selected-tag {
|
||||
margin: 4px;
|
||||
.detail-title {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin: 8px 0 4px;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -35,10 +35,25 @@ describe('Bug #550: 门诊医生站-检查申请项目选择交互优化', () =>
|
||||
cy.login('doctor1', '123456')
|
||||
cy.visit('/outpatient/examination-apply')
|
||||
|
||||
// 验证基础交互
|
||||
// 验证基础布局
|
||||
cy.get('[data-cy="category-tree"]').should('be.visible')
|
||||
cy.get('[data-cy="item-list"]').should('be.visible')
|
||||
cy.get('[data-cy="selected-card"]').should('be.visible')
|
||||
cy.get('[data-cy="selected-panel"]').should('be.visible')
|
||||
|
||||
// 1. 验证解耦:勾选项目不应自动勾选检查方法
|
||||
cy.get('[data-cy="item-checkbox-128"]').click()
|
||||
cy.get('[data-cy="method-checkbox-128-0"]').should('not.be.checked')
|
||||
|
||||
// 2. 验证名称显示:无“套餐”前缀,悬停显示完整名称
|
||||
cy.get('[data-cy="selected-card-name"]').should('not.contain', '套餐')
|
||||
cy.get('[data-cy="selected-card-name"]').trigger('mouseover')
|
||||
cy.get('.el-popper').should('contain', '128线排')
|
||||
|
||||
// 3. 验证默认收起与层级结构
|
||||
cy.get('[data-cy="selected-card-detail"]').should('not.be.visible')
|
||||
cy.get('[data-cy="selected-card-toggle"]').click()
|
||||
cy.get('[data-cy="selected-card-detail"]').should('be.visible')
|
||||
cy.get('[data-cy="selected-card-detail"]').should('contain', '检查方法')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -62,37 +77,3 @@ describe('Bug #562: 门诊医生工作站-待写病历加载性能', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// @bug574 @regression
|
||||
describe('Bug #574: 预约签到缴费成功后号源状态流转', () => {
|
||||
it('缴费成功后 adm_schedule_slot.status 应更新为 3', () => {
|
||||
cy.login('admin', '123456')
|
||||
cy.visit('/outpatient/registration')
|
||||
|
||||
// 模拟选择已预约患者并执行签到缴费
|
||||
cy.get('[data-cy="patient-search-input"]').type('测试患者')
|
||||
cy.get('[data-cy="patient-search-btn"]').click()
|
||||
cy.get('[data-cy="appointment-list"]').first().click()
|
||||
cy.get('[data-cy="check-in-btn"]').click()
|
||||
cy.get('[data-cy="pay-btn"]').click()
|
||||
|
||||
// 拦截缴费成功接口与号源状态更新接口
|
||||
cy.intercept('POST', '/api/registration/checkin-pay').as('checkinPay')
|
||||
cy.intercept('POST', '/api/schedule/slot/update-status').as('updateSlotStatus')
|
||||
|
||||
cy.get('[data-cy="confirm-pay-btn"]').click()
|
||||
|
||||
cy.wait('@checkinPay').then((interception) => {
|
||||
expect(interception.response.statusCode).to.eq(200)
|
||||
})
|
||||
|
||||
// 验证是否触发了号源状态更新请求且状态值为 3
|
||||
cy.wait('@updateSlotStatus').then((interception) => {
|
||||
expect(interception.request.body.status).to.eq(3)
|
||||
expect(interception.response.statusCode).to.eq(200)
|
||||
})
|
||||
|
||||
// 验证前端提示成功
|
||||
cy.contains('签到缴费成功').should('be.visible')
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user