Files
his/openhis-ui-vue3/src/views/outpatient/exam-request/components/ExamItemSelector.vue
2026-05-27 03:07:18 +08:00

221 lines
5.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="exam-item-selector">
<!-- 左侧分类树 -->
<div class="panel-left">
<el-tree
:data="categoryTree"
:props="{ label: 'name', children: 'children' }"
node-key="id"
highlight-current
@node-click="handleCategoryClick"
/>
</div>
<!-- 中间项目列表 -->
<div class="panel-middle">
<el-table :data="currentItems" border stripe style="width: 100%" @selection-change="handleItemSelection">
<el-table-column type="selection" width="40" />
<el-table-column prop="code" label="编码" width="80" />
<el-table-column prop="name" label="项目名称" show-overflow-tooltip />
<el-table-column prop="price" label="价格" width="80" align="right" />
</el-table>
</div>
<!-- 右侧已选区域 -->
<div class="panel-right">
<div class="selected-header">已选择项目</div>
<div class="selected-list" v-if="selectedItems.length">
<div
v-for="item in selectedItems"
:key="item.id"
class="selected-card"
>
<!-- 卡片头部项目名称 + 展开/收起控制 -->
<div class="card-header" @click="toggleExpand(item)">
<span class="item-name" :title="item.name">{{ item.name }}</span>
<el-icon class="expand-icon">
<ArrowDown v-if="item.expanded" />
<ArrowRight v-else />
</el-icon>
</div>
<!-- 卡片明细检查方法列表默认收起 -->
<div v-show="item.expanded" class="card-details">
<div v-if="item.methods && item.methods.length" class="method-list">
<div v-for="method in item.methods" :key="method.id" class="method-item">
<el-checkbox
v-model="method.checked"
@change="handleMethodCheck(item, method)"
>
{{ method.name }}
</el-checkbox>
</div>
</div>
<div v-else class="empty-tip">无关联检查方法</div>
</div>
</div>
</div>
<el-empty v-else description="暂无已选项目" :image-size="60" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import { ArrowDown, ArrowRight } from '@element-plus/icons-vue';
import type { ElTree } from 'element-plus';
// 模拟数据结构(实际应从 API 获取)
interface ExamMethod {
id: string;
name: string;
checked: boolean;
}
interface ExamItem {
id: string;
code: string;
name: string;
price: number;
methods: ExamMethod[];
expanded: boolean;
}
const categoryTree = ref<any[]>([]);
const currentCategory = ref<string | null>(null);
const currentItems = ref<ExamItem[]>([]);
const selectedItems = ref<ExamItem[]>([]);
// 切换分类加载项目
const handleCategoryClick = (data: any) => {
currentCategory.value = data.id;
// 实际应调用 API: fetchItemsByCategory(data.id)
currentItems.value = data.items || [];
};
// 项目勾选处理:解耦联动,仅添加/移除项目,不自动勾选方法
const handleItemSelection = (selection: ExamItem[]) => {
// 过滤出新增的项目
const added = selection.filter(s => !selectedItems.value.find(i => i.id === s.id));
const removed = selectedItems.value.filter(i => !selection.find(s => s.id === s.id));
added.forEach(item => {
// 初始化状态:默认收起,方法未勾选
selectedItems.value.push({
...item,
expanded: false,
methods: item.methods?.map(m => ({ ...m, checked: false })) || []
});
});
removed.forEach(item => {
selectedItems.value = selectedItems.value.filter(i => i.id !== item.id);
});
};
// 切换明细展开/收起
const toggleExpand = (item: ExamItem) => {
item.expanded = !item.expanded;
};
// 检查方法独立勾选:与父项目解耦
const handleMethodCheck = (item: ExamItem, method: ExamMethod) => {
// 仅更新当前方法状态,不触发父级联动
console.log(`方法 ${method.name} 状态变更为: ${method.checked}`);
};
</script>
<style scoped>
.exam-item-selector {
display: flex;
height: 100%;
gap: 12px;
padding: 12px;
background: #fff;
}
.panel-left, .panel-middle, .panel-right {
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 8px;
}
.panel-left { width: 20%; overflow-y: auto; }
.panel-middle { width: 45%; }
.panel-right { width: 35%; display: flex; flex-direction: column; }
.selected-header {
font-weight: 600;
margin-bottom: 8px;
padding-bottom: 6px;
border-bottom: 1px solid #eee;
}
.selected-list {
flex: 1;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 8px;
}
.selected-card {
border: 1px solid #dcdfe6;
border-radius: 6px;
background: #fafafa;
overflow: hidden;
}
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 10px;
cursor: pointer;
background: #f5f7fa;
transition: background 0.2s;
}
.card-header:hover {
background: #e9ecef;
}
.item-name {
flex: 1;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-right: 8px;
}
.expand-icon {
font-size: 14px;
color: #909399;
}
.card-details {
padding: 8px 10px;
background: #fff;
border-top: 1px dashed #ebeef5;
}
.method-list {
display: flex;
flex-direction: column;
gap: 6px;
}
.method-item {
padding-left: 12px;
font-size: 13px;
}
.empty-tip {
color: #909399;
font-size: 12px;
text-align: center;
padding: 4px 0;
}
</style>