You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

353 lines
8.6 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div v-loading="loading">
<div class="page-container">
<!-- 左侧排班列表 -->
<div class="left-panel">
<div class="panel-header">
<span class="panel-title">排班</span>
</div>
<div class="device-list">
<div v-for="item in deviceList" :key="item.id"
class="device-item"
:class="{ active: selectedDevice?.id === item.id }"
@click="selectDevice(item)">
<div class="device-name">{{ item.device_name }}</div>
<div class="device-count">已绑定 {{ item.bind_count || 0 }} 项</div>
</div>
<div v-if="deviceList.length === 0" class="empty-tip">
暂无服务组
</div>
</div>
</div>
<!-- 右侧:已绑定的检查项目 -->
<div class="right-panel">
<div class="panel-header">
<span class="panel-title">
{{ selectedDevice ? selectedDevice.device_name + ' - 已绑定的检查项目' : '请选择一个排班' }}
</span>
</div>
<div v-if="selectedDevice" class="right-content">
<div style="margin-bottom: 12px;">
<el-button type="primary" @click="openBindDialog">添加绑定</el-button>
</div>
<el-table :data="boundItems" style="width: 100%;" row-key="id" v-loading="boundLoading">
<el-table-column prop="item_code" label="项目编号" width="160" />
<el-table-column prop="item_name" label="检查项目名称" />
<el-table-column prop="item_class_name" label="医嘱类别" />
<el-table-column prop="" label="操作" width="100">
<template #default="scope">
<el-button type="danger" size="small" @click="removeBind(scope.row)">解绑</el-button>
</template>
</el-table-column>
</el-table>
<div v-if="boundItems.length === 0 && !boundLoading" class="empty-tip" style="margin-top: 20px;">
暂无绑定的检查项目
</div>
</div>
</div>
</div>
</div>
<!-- 添加绑定弹窗 -->
<el-dialog v-model="showBindDialog" title="添加检查项目绑定" width="60%" @closed="closeBindDialog">
<div>
<div style="display: flex; gap: 12px; margin-bottom: 12px;">
<el-input v-model="bindSearchKeyword" placeholder="输入项目编号/名称搜索"
clearable style="flex: 1;" @input="onBindSearchInput" />
<el-button type="primary" @click="doBindSearch">搜索</el-button>
<el-button @click="refreshBindList">刷新</el-button>
</div>
<el-table :data="bindPageData" style="width: 100%;" row-key="id" v-loading="bindSearchLoading">
<el-table-column prop="item_code" label="项目编号" width="160" />
<el-table-column prop="item_name" label="检查项目名称" />
<el-table-column prop="item_class_name" label="医嘱类别" />
<el-table-column prop="" label="操作" width="100">
<template #default="scope">
<el-button type="primary" size="small" @click="addBindFromDialog(scope.row)"
:loading="bindItemLoading === scope.row.id">添加</el-button>
</template>
</el-table-column>
</el-table>
<div style="display: flex; justify-content: space-between; align-items: center; margin-top: 12px;">
<span style="color: #909399; font-size: 13px;"> {{ bindTotal }} </span>
<el-pagination v-model:current-page="bindCurrentPage" v-model:page-size="bindPageSize"
:page-sizes="[10]" layout="prev, pager, next" :total="bindTotal"
@current-change="onBindPageChange" />
</div>
</div>
</el-dialog>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
GetDeptDeviceList,
GetDeviceBindItems,
GetUnboundCheckItems,
BindItemToDevice,
UnbindItemFromDevice
} from '@/api/api.js'
let loading = ref(false)
let deviceList = ref([])
let selectedDevice = ref(null)
let boundItems = ref([])
let boundLoading = ref(false)
let showBindDialog = ref(false)
let bindSearchKeyword = ref('')
let bindSearchResults = ref([])
let bindSearchLoading = ref(false)
let bindSearchTimer = null
let bindCurrentPage = ref(1)
let bindPageSize = ref(10)
let bindTotal = ref(0)
let bindItemLoading = ref(null)
// 分页数据(直接从后端返回的结果)
const bindPageData = computed(() => bindSearchResults.value)
// 获取设备列表
const getDeviceList = () => {
loading.value = true
GetDeptDeviceList().then(res => {
loading.value = false
if (res.status) {
deviceList.value = res.data
// 默认选中第一个
if (deviceList.value.length > 0) {
selectDevice(deviceList.value[0])
}
} else {
ElMessage.error(res.msg)
}
}).catch(() => {
loading.value = false
})
}
const selectDevice = (device) => {
selectedDevice.value = device
getBoundItems(device.id)
}
const getBoundItems = (deviceId) => {
boundLoading.value = true
GetDeviceBindItems({ device_id: deviceId }).then(res => {
boundLoading.value = false
if (res.status) {
boundItems.value = res.data
} else {
ElMessage.error(res.msg)
}
}).catch(() => {
boundLoading.value = false
})
}
// 分页查询后端分页每页10条已排除已绑定的项目
const loadPage = () => {
bindSearchLoading.value = true
GetUnboundCheckItems({
device_id: selectedDevice.value.id,
keyword: bindSearchKeyword.value,
page: bindCurrentPage.value,
pageSize: bindPageSize.value
}).then(res => {
bindSearchLoading.value = false
if (res.status) {
bindSearchResults.value = res.data.list || []
bindTotal.value = res.data.count || 0
} else {
ElMessage.error(res.msg)
}
}).catch(() => {
bindSearchLoading.value = false
})
}
const openBindDialog = () => {
showBindDialog.value = true
bindSearchKeyword.value = ''
bindCurrentPage.value = 1
loadPage()
}
// 防抖搜索
const onBindSearchInput = () => {
if (bindSearchTimer) clearTimeout(bindSearchTimer)
bindSearchTimer = setTimeout(() => {
bindCurrentPage.value = 1
loadPage()
}, 300)
}
const doBindSearch = () => {
bindCurrentPage.value = 1
loadPage()
}
const refreshBindList = () => {
loadPage()
}
const onBindPageChange = (page) => {
bindCurrentPage.value = page
loadPage()
}
const addBindFromDialog = (item) => {
bindItemLoading.value = item.id
BindItemToDevice({
item_id: item.id,
device_id: selectedDevice.value.id
}).then(res => {
bindItemLoading.value = null
if (res.status) {
ElMessage.success(`已绑定 ${item.item_name}`)
// 从当前页中移除
const idx = bindSearchResults.value.findIndex(i => i.id === item.id)
if (idx !== -1) {
bindSearchResults.value.splice(idx, 1)
}
bindTotal.value--
// 如果当前页没有项目了,回到前一页
if (bindSearchResults.value.length === 0 && bindCurrentPage.value > 1) {
bindCurrentPage.value--
loadPage()
}
// 刷新右侧列表
getBoundItems(selectedDevice.value.id)
} else {
ElMessage.error(res.msg)
}
}).catch(() => {
bindItemLoading.value = null
})
}
const closeBindDialog = () => {
bindSearchKeyword.value = ''
bindSearchResults.value = []
bindTotal.value = 0
bindCurrentPage.value = 1
}
const removeBind = (row) => {
ElMessageBox.confirm(`确定解绑 ${row.item_name} 吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
UnbindItemFromDevice({
item_id: row.id,
device_id: selectedDevice.value.id
}).then(res => {
if (res.status) {
ElMessage.success('解绑成功')
getBoundItems(selectedDevice.value.id)
} else {
ElMessage.error(res.msg)
}
})
}).catch(() => {})
}
onMounted(() => {
getDeviceList()
})
</script>
<style scoped>
.page-container {
display: flex;
height: calc(100vh - 120px);
gap: 16px;
}
.left-panel {
width: 280px;
min-width: 280px;
border: 1px solid #e4e7ed;
border-radius: 8px;
background: #fff;
display: flex;
flex-direction: column;
overflow: hidden;
}
.panel-header {
padding: 14px 16px;
border-bottom: 1px solid #e4e7ed;
background: #f5f7fa;
display: flex;
align-items: center;
justify-content: space-between;
}
.panel-title {
font-size: 15px;
font-weight: 600;
color: #303133;
}
.device-list {
flex: 1;
overflow-y: auto;
padding: 8px 0;
}
.device-item {
padding: 12px 16px;
cursor: pointer;
border-left: 3px solid transparent;
transition: all 0.2s;
}
.device-item:hover {
background: #f0f5ff;
}
.device-item.active {
background: #ecf5ff;
border-left-color: #409eff;
}
.device-name {
font-size: 14px;
color: #303133;
font-weight: 500;
}
.device-count {
font-size: 12px;
color: #909399;
margin-top: 4px;
}
.right-panel {
flex: 1;
border: 1px solid #e4e7ed;
border-radius: 8px;
background: #fff;
display: flex;
flex-direction: column;
overflow: hidden;
}
.right-content {
flex: 1;
padding: 16px;
overflow-y: auto;
}
.empty-tip {
text-align: center;
color: #c0c4cc;
font-size: 14px;
padding: 40px 0;
}
</style>