shanghai_vue3/src/views/analysis/HOLTER.vue
2025-07-15 10:57:51 +08:00

800 lines
19 KiB
Vue

<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-card class="search-card" shadow="never">
<el-form
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="80px"
class="search-form"
>
<div class="search-fields">
<div class="search-field name-field">
<el-form-item label="患者姓名" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入患者姓名"
clearable
@keyup.enter="handleQuery"
style="width: 180px"
>
<template #prefix>
<Icon icon="ep:user" />
</template>
</el-input>
</el-form-item>
</div>
<div class="search-field gender-field">
<el-form-item label="性别" prop="gender">
<el-select
v-model="queryParams.gender"
placeholder="请选择性别"
clearable
style="width: 150px"
>
<el-option label="男" value="1" />
<el-option label="女" value="2" />
</el-select>
</el-form-item>
</div>
<div class="search-field examid-field">
<el-form-item label="检查ID" prop="examid">
<el-input
v-model="queryParams.examid"
placeholder="请输入检查ID"
clearable
@keyup.enter="handleQuery"
style="width: 180px"
>
<template #prefix>
<Icon icon="ep:document" />
</template>
</el-input>
</el-form-item>
</div>
<div class="search-field date-field">
<el-form-item label="检查日期" prop="examDate">
<el-date-picker
v-model="queryParams.examDate"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD"
style="width: 280px"
/>
</el-form-item>
</div>
</div>
<el-row>
<el-col :span="24" class="search-buttons-row">
<div class="search-buttons-group">
<el-button type="primary" @click="handleQuery" :icon="Search"> 搜索 </el-button>
<el-button @click="resetQuery" :icon="Refresh"> 重置 </el-button>
<el-button type="success" @click="handleSelectPatients" :icon="Plus">
选择患者
</el-button>
<el-button
type="warning"
@click="handleExport"
:loading="exportLoading"
:icon="Download"
>
导出数据
</el-button>
</div>
</el-col>
</el-row>
</el-form>
</el-card>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-card shadow="never" class="table-card">
<template #header>
<div class="card-header">
<span class="card-title">Holter检查记录</span>
<div class="card-extra">
<el-tag type="info" size="small"> {{ total }} 条记录</el-tag>
</div>
</div>
</template>
<el-table
v-loading="loading"
:data="tableData"
:stripe="true"
:show-overflow-tooltip="true"
class="modern-table"
row-key="id"
>
<el-table-column prop="examid" label="检查ID" width="320" align="left">
<template #default="{ row }">
<div class="exam-id">
<Icon icon="ep:document" />
<span>{{ row.examid }}</span>
</div>
</template>
</el-table-column>
<el-table-column prop="name" label="姓名" width="120" align="left">
<template #default="{ row }">
<div class="patient-name">
<el-avatar :size="28" class="patient-avatar">
{{ row.name?.charAt(0) }}
</el-avatar>
<span>{{ row.name }}</span>
</div>
</template>
</el-table-column>
<el-table-column prop="gender" label="性别" width="70" align="center">
<template #default="{ row }">
<el-tag :type="row.gender === '1' ? 'primary' : 'danger'" class="gender-tag">
<Icon :icon="row.gender === '1' ? 'ep:male' : 'ep:female'" />
{{ row.gender === '1' ? '男' : row.gender === '2' ? '女' : '未知' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="age" label="年龄" width="90" align="center">
<template #default="{ row }">
<div class="age-badge">{{ row.age }}岁</div>
</template>
</el-table-column>
<el-table-column prop="wearstarttime" label="佩戴开始时间" width="210" align="center">
<template #default="{ row }">
<el-date-picker
v-model="row.wearstarttime"
type="datetime"
placeholder="选择佩戴时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
size="small"
style="width: 190px"
@change="handleWearTimeChange(row)"
/>
</template>
</el-table-column>
<el-table-column prop="duration" label="时长" width="140" align="center">
<template #default="{ row }">
<el-time-picker
v-model="row.duration"
placeholder="选择时长"
format="HH:mm:ss"
value-format="HH:mm:ss"
size="small"
style="width: 120px"
@change="handleDurationChange(row)"
/>
</template>
</el-table-column>
<el-table-column label="分析" width="100" align="center">
<template #default="{ row }">
<el-button
type="primary"
size="small"
class="analysis-btn"
@click="handleAnalysis(row)"
>
<Icon icon="ep:monitor" />
Holter
</el-button>
</template>
</el-table-column>
<el-table-column prop="reportgenerated" label="报告" width="100" align="center">
<template #default="{ row }">
<el-tag :type="row.reportgenerated === 1 ? 'success' : 'warning'" class="status-tag">
<Icon :icon="row.reportgenerated === 1 ? 'ep:circle-check' : 'ep:clock'" />
{{ row.reportgenerated === 1 ? '已生成' : '未生成' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="上传" width="100" align="center">
<template #default="{ row }">
<el-button type="primary" size="small" class="upload-btn" @click="handleUpload(row)">
<Icon icon="ep:upload" />
上传
</el-button>
</template>
</el-table-column>
<el-table-column prop="superiorrequest" label="申请" width="100" align="center">
<template #default="{ row }">
<el-tag :type="row.superiorrequest === 1 ? 'success' : 'info'" class="status-tag">
<Icon :icon="row.superiorrequest === 1 ? 'ep:check' : 'ep:circle-close'" />
{{ row.superiorrequest === 1 ? '已申请' : '未申请' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="下载" width="100" align="center">
<template #default="{ row }">
<el-button
type="success"
size="small"
class="download-btn"
@click="handleDownload(row)"
>
<Icon icon="ep:download" />
下载
</el-button>
</template>
</el-table-column>
<el-table-column prop="orgname" label="机构" width="160" align="left" show-overflow-tooltip>
<template #default="{ row }">
<div class="org-info">
<Icon icon="ep:office-building" />
<span>{{ row.orgname }}</span>
</div>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-wrapper">
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</div>
</el-card>
</ContentWrap>
<!-- 患者选择组件 -->
<PatientSelect
ref="patientSelectRef"
title="选择患者"
type="holter"
:multiple="true"
:max-select="50"
@confirm="handlePatientConfirm"
@cancel="handlePatientCancel"
/>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import PatientSelect from '@/patientcom/index.vue'
import { ecgdataApi, ecgdataVO } from '@/api/ecgdata'
import { Search, Refresh, Plus, Download } from '@element-plus/icons-vue'
defineOptions({ name: 'AnalysisHolter' })
const loading = ref(false)
const exportLoading = ref(false)
const total = ref(0)
const tableData = ref<ecgdataVO[]>([])
const queryFormRef = ref()
const patientSelectRef = ref()
// 查询参数
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: '',
gender: '',
examid: '',
examDate: [],
orgname: ''
})
/** 格式化日期 */
const formatDate = (date: Date | string) => {
if (!date) return ''
const d = new Date(date)
return d.toLocaleDateString('zh-CN', {
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
})
}
/** 获取列表数据 */
const getList = async () => {
loading.value = true
try {
// 构建查询参数
const params = {
pageNo: queryParams.pageNo,
pageSize: queryParams.pageSize,
name: queryParams.name || undefined,
gender: queryParams.gender || undefined,
examid: queryParams.examid || undefined,
orgname: queryParams.orgname || undefined
}
// 如果有日期范围,添加到参数中
if (queryParams.examDate && queryParams.examDate.length === 2) {
params['wearstarttimeBegin'] = queryParams.examDate[0]
params['wearstarttimeEnd'] = queryParams.examDate[1]
}
const data = await ecgdataApi.getecgdataPage(params)
tableData.value = data.list || []
total.value = data.total || 0
} catch (error) {
console.error('获取数据失败:', error)
ElMessage.error('获取数据失败')
} finally {
loading.value = false
}
}
/** 搜索 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置 */
const resetQuery = () => {
queryFormRef.value?.resetFields()
handleQuery()
}
/** 分析 */
const handleAnalysis = (row) => {
ElMessage.success('开始分析...')
// 这里调用分析API
}
/** 上传 */
const handleUpload = (row) => {
ElMessage.success('上传功能')
// 这里实现上传逻辑
}
/** 下载 */
const handleDownload = (row) => {
if (row.reportgenerated !== 1) {
ElMessage.warning('请等待报告生成后再下载')
return
}
ElMessage.success('开始下载...')
// 这里实现下载逻辑
}
/** 导出 */
const handleExport = async () => {
exportLoading.value = true
try {
await ecgdataApi.exportecgdata(queryParams)
ElMessage.success('导出成功')
} catch (error) {
console.error('导出失败:', error)
ElMessage.error('导出失败')
} finally {
exportLoading.value = false
}
}
/** 操作分发 */
const handleCommand = (command, row) => {
switch (command) {
case 'view':
ElMessage.info('查看详情')
break
case 'edit':
ElMessage.info('编辑信息')
break
case 'delete':
handleDelete(row)
break
}
}
/** 删除 */
const handleDelete = async (row) => {
try {
await ElMessageBox.confirm('确定要删除这条记录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
await ecgdataApi.deleteecgdata(row.id)
ElMessage.success('删除成功')
getList()
} catch (error) {
if (error !== 'cancel') {
console.error('删除失败:', error)
ElMessage.error('删除失败')
}
}
}
/** 选择患者 */
const handleSelectPatients = () => {
patientSelectRef.value?.open()
}
/** 患者选择确认 */
const handlePatientConfirm = (patients: any[], type: string) => {
console.log('选中的患者:', patients)
console.log('操作类型:', type)
}
/** 患者选择取消 */
const handlePatientCancel = () => {
console.log('取消选择患者')
}
/** 处理佩戴时间变化 */
const handleWearTimeChange = async (row: ecgdataVO) => {
try {
console.log('佩戴时间已更新:', row.wearstarttime)
// 这里可以调用API更新佩戴时间
// await ecgdataApi.updateecgdata({
// id: row.id,
// wearstarttime: row.wearstarttime
// })
ElMessage.success('佩戴时间更新成功')
} catch (error) {
console.error('更新佩戴时间失败:', error)
ElMessage.error('更新佩戴时间失败')
}
}
/** 处理时长变化 */
const handleDurationChange = async (row: ecgdataVO) => {
try {
console.log('时长已更新:', row.duration)
// 这里可以调用API更新时长
// await ecgdataApi.updateecgdata({
// id: row.id,
// duration: row.duration
// })
ElMessage.success('时长更新成功')
} catch (error) {
console.error('更新时长失败:', error)
ElMessage.error('更新时长失败')
}
}
/** 初始化 */
onMounted(() => {
getList()
})
</script>
<style lang="scss" scoped>
.search-card {
margin-bottom: 8px;
border-radius: 8px;
padding: 12px 16px;
.search-form {
.search-fields {
display: flex;
flex-wrap: wrap;
gap: 16px;
margin-bottom: 12px;
}
.search-field {
display: flex;
align-items: center;
&.name-field {
margin-right: 16px;
}
&.gender-field {
margin-right: 16px;
}
&.examid-field {
margin-right: 16px;
}
&.date-field {
margin-right: 16px;
}
}
.el-form-item {
margin-bottom: 0;
margin-right: 0;
:deep(.el-form-item__label) {
padding-right: 6px;
font-size: 13px;
line-height: 32px;
white-space: nowrap;
}
:deep(.el-form-item__content) {
line-height: 1;
}
}
// 确保下拉框有足够的宽度
:deep(.el-select) {
width: 100%;
.el-input__wrapper {
width: 100%;
}
.el-input__inner {
width: 100%;
}
}
}
// 响应式设计
@media (max-width: 768px) {
.search-fields {
flex-direction: column;
gap: 12px;
}
.search-field {
margin-right: 0;
margin-bottom: 8px;
}
}
}
.search-buttons-row {
display: flex;
justify-content: flex-end;
align-items: center;
margin-top: 8px;
}
.search-buttons-group {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.table-card {
border-radius: 8px;
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
.card-title {
font-size: 16px;
font-weight: 600;
color: #303133;
}
.card-extra {
display: flex;
align-items: center;
}
}
}
.modern-table {
.exam-id {
display: flex;
align-items: center;
gap: 4px;
font-weight: 600;
color: #667eea;
.ep-document {
font-size: 12px;
}
}
.patient-name {
display: flex;
align-items: center;
gap: 6px;
.patient-avatar {
background: linear-gradient(135deg, #667eea, #764ba2);
font-weight: 600;
font-size: 11px;
}
}
.gender-tag {
border-radius: 12px;
padding: 1px 6px;
font-weight: 500;
font-size: 11px;
.ep-male,
.ep-female {
margin-right: 1px;
}
}
.age-badge {
background: linear-gradient(135deg, #f093fb, #f5576c);
color: white;
padding: 1px 6px;
border-radius: 12px;
font-weight: 500;
font-size: 11px;
}
// 时间选择器样式
:deep(.el-date-picker),
:deep(.el-time-picker) {
.el-input__wrapper {
border-radius: 6px;
border: 1px solid #dcdfe6;
&:hover {
border-color: #c0c4cc;
}
&.is-focus {
border-color: #409eff;
}
}
.el-input__inner {
font-size: 12px;
padding: 0 8px;
}
}
// 确保时间选择器在表格中正确显示
:deep(.el-table__cell) {
.el-date-picker,
.el-time-picker {
width: 100%;
}
}
.time-info {
display: flex;
align-items: center;
gap: 4px;
color: #666;
font-size: 12px;
.ep-clock {
color: #667eea;
}
}
.duration-tag {
border-radius: 12px;
padding: 1px 6px;
font-weight: 500;
font-size: 11px;
.ep-timer {
margin-right: 1px;
}
}
.analysis-btn,
.upload-btn,
.download-btn {
border-radius: 12px;
padding: 2px 8px;
font-weight: 500;
font-size: 11px;
border: none;
transition: all 0.3s ease;
&:hover {
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
}
.analysis-btn {
background: linear-gradient(135deg, #667eea, #764ba2);
}
.upload-btn {
background: linear-gradient(135deg, #4facfe, #00f2fe);
}
.download-btn {
background: linear-gradient(135deg, #43e97b, #38f9d7);
}
.status-tag {
border-radius: 12px;
padding: 1px 6px;
font-weight: 500;
font-size: 11px;
.ep-circle-check,
.ep-clock,
.ep-check,
.ep-circle-close {
margin-right: 2px;
font-size: 10px;
}
}
.org-info {
display: flex;
align-items: center;
gap: 4px;
color: #666;
font-size: 12px;
.ep-office-building {
color: #667eea;
}
}
}
.pagination-wrapper {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
:deep(.el-table) {
border-radius: 8px;
overflow: hidden;
.el-table__header {
background: #f5f7fa;
th {
background: #f5f7fa;
color: #303133;
font-weight: 600;
padding: 12px 8px; // 增加表头padding
}
}
.el-table__row {
transition: all 0.3s ease;
&:hover {
background: #f0f9ff;
}
td {
padding: 12px 8px; // 增加单元格padding
}
}
// 增加列间距
.el-table__cell {
border-right: 1px solid #ebeef5;
&:last-child {
border-right: none;
}
}
}
:deep(.el-card) {
border: none;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
:deep(.el-button) {
border-radius: 6px;
&.el-button--primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
&:hover {
background: linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%);
}
}
&.el-button--success {
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
border: none;
&:hover {
background: linear-gradient(135deg, #0f8a7d 0%, #2dd66e 100%);
}
}
}
</style>