Files
his/openhis-ui-vue3/src/views/outpatient/ExamRequest.vue
2026-05-26 23:33:21 +08:00

176 lines
6.2 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-request-container">
<el-row :gutter="16" class="main-layout">
<!-- 左侧检查分类 -->
<el-col :span="6" class="category-panel">
<el-tree :data="categories" :props="{ label: 'name', children: 'children' }" @node-click="handleCategoryClick" />
</el-col>
<!-- 中间检查项目 -->
<el-col :span="9" class="item-panel">
<el-table :data="currentItems" border style="width: 100%" @selection-change="handleItemSelection">
<el-table-column type="selection" width="40" />
<el-table-column prop="name" label="检查项目" />
<el-table-column prop="price" label="价格" width="80" />
</el-table>
</el-col>
<!-- 右侧已选择区域 -->
<el-col :span="9" class="selected-panel">
<div class="panel-header">
<h3>已选择</h3>
<el-button type="danger" size="small" @click="clearAll">清空</el-button>
</div>
<!-- 移除原项目套餐明细冗余标签 -->
<div class="selected-list">
<div v-for="item in selectedItems" :key="item.id" class="selected-card">
<div class="card-header" @click="toggleExpand(item)">
<el-checkbox
v-model="item.checked"
@change="onItemCheck(item)"
@click.stop
/>
<el-tooltip :content="item.displayName" placement="top" :show-after="300" :disabled="!item.isTruncated">
<span
ref="nameRefs"
class="item-name"
@mouseenter="checkTruncate(item)"
>{{ item.displayName }}</span>
</el-tooltip>
<el-icon class="expand-icon">
<ArrowDown v-if="item.expanded" />
<ArrowRight v-else />
</el-icon>
</div>
<!-- 检查方法明细严格遵循 项目 > 检查方法 层级默认收起 -->
<div v-show="item.expanded" class="method-list">
<div v-for="method in item.methods" :key="method.id" class="method-row">
<el-checkbox
v-model="method.checked"
@change="onMethodCheck(method)"
@click.stop
/>
<span class="method-name">{{ method.name }}</span>
</div>
</div>
</div>
<el-empty v-if="selectedItems.length === 0" description="暂无已选项目" :image-size="60" />
</div>
</el-col>
</el-row>
</div>
</template>
<script setup>
import { ref, reactive, computed, nextTick } from 'vue'
import { ArrowDown, ArrowRight } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
// 模拟数据源
const categories = ref([
{ id: 1, name: '彩超', children: [] }
])
const currentItems = ref([
{ id: 10, name: '128线排彩超套餐', price: 150, methods: [
{ id: 101, name: '常规腹部检查', checked: false },
{ id: 102, name: '心脏彩超', checked: false }
]}
])
// 已选项目状态管理
const selectedItems = reactive([])
const nameRefs = ref([])
// 计算属性:清理“套餐”字样,优化显示
const cleanName = (name) => name.replace(/套餐/g, '').trim()
// 添加项目到已选列表
const handleItemSelection = (selection) => {
// 解耦逻辑:仅同步选中项,不自动勾选子方法
const newIds = new Set(selection.map(i => i.id))
// 移除未选中的
for (let i = selectedItems.length - 1; i >= 0; i--) {
if (!newIds.has(selectedItems[i].id)) {
selectedItems.splice(i, 1)
}
}
// 新增已选中的
selection.forEach(item => {
if (!selectedItems.find(s => s.id === item.id)) {
selectedItems.push({
id: item.id,
name: item.name,
displayName: cleanName(item.name),
checked: true,
expanded: false, // 默认收起
isTruncated: false,
methods: item.methods.map(m => ({ ...m, checked: false })) // 方法默认不勾选
})
}
})
}
// 解耦:项目勾选独立,不级联方法
const onItemCheck = (item) => {
// 仅记录状态,不触发 methods 联动
console.log(`[解耦] 项目 ${item.displayName} 状态: ${item.checked}`)
}
// 解耦:方法勾选独立
const onMethodCheck = (method) => {
console.log(`[解耦] 方法 ${method.name} 状态: ${method.checked}`)
}
// 展开/收起明细
const toggleExpand = (item) => {
item.expanded = !item.expanded
}
// 检测文本是否截断以控制 Tooltip
const checkTruncate = async (item) => {
await nextTick()
const el = nameRefs.value.find(r => r?.textContent === item.displayName)
if (el) {
item.isTruncated = el.scrollWidth > el.clientWidth
}
}
const clearAll = () => {
selectedItems.length = 0
ElMessage.success('已清空选择')
}
const handleCategoryClick = (data) => {
// 加载对应分类下的项目逻辑(略)
}
</script>
<style scoped>
.exam-request-container { padding: 16px; background: #f5f7fa; min-height: 100vh; }
.main-layout { background: #fff; padding: 16px; border-radius: 8px; }
.category-panel, .item-panel, .selected-panel { height: 600px; overflow-y: auto; }
.selected-panel { border-left: 1px solid #ebeef5; padding-left: 16px; }
.panel-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
.panel-header h3 { margin: 0; font-size: 16px; color: #303133; }
.selected-list { display: flex; flex-direction: column; gap: 10px; }
.selected-card { border: 1px solid #e4e7ed; border-radius: 6px; background: #fafafa; overflow: hidden; }
.card-header { display: flex; align-items: center; gap: 8px; padding: 10px 12px; cursor: pointer; transition: background 0.2s; }
.card-header:hover { background: #f2f6fc; }
.item-name {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: 500;
color: #303133;
}
.expand-icon { color: #909399; font-size: 14px; }
.method-list { padding: 8px 12px 12px 32px; background: #fff; border-top: 1px dashed #ebeef5; }
.method-row { display: flex; align-items: center; gap: 8px; padding: 4px 0; }
.method-name { color: #606266; font-size: 13px; }
</style>