This commit is contained in:
lxd 2025-07-24 17:41:40 +08:00
commit 713c9f5b71
5 changed files with 965 additions and 0 deletions

76
src/api/arterial/index.ts Normal file
View File

@ -0,0 +1,76 @@
import request from '@/config/axios'
import { patientinfoVO } from '../patientinfo'
// 动脉硬化检测患者 VO
export interface arterialVO {
id: number // 主键
examid: string // 检查ID
regid: string // 患者注册ID
name: string // 患者姓名
gender: string // 性别
age: string // 年龄
orgid: string // 机构ID
orgname: string // 机构名称
managerorg: string // 管理机构
deviceid: string // 设备ID
devicename: string // 设备名称
status: number // 状态: 0=申请中, 1=已申请
weartime: Date // 佩戴时间
analysisresult: string // 分析结果
createtime: Date // 创建时间
updatetime: Date // 更新时间
}
// 动脉硬化检测患者 API
export const arterialApi = {
// 查询动脉硬化检测患者分页
getarterialPage: async (params: any) => {
return await request.get({ url: `/system/arterial/page`, params })
},
// 查询动脉硬化检测患者详情
getarterial: async (id: number) => {
return await request.get({ url: `/system/arterial/get?id=` + id })
},
// 新增动脉硬化检测患者
createarterial: async (data: arterialVO) => {
return await request.post({ url: `/system/arterial/create`, data })
},
// 修改动脉硬化检测患者
updatearterial: async (data: arterialVO) => {
return await request.put({ url: `/system/arterial/update`, data })
},
// 删除动脉硬化检测患者
deletearterial: async (id: number) => {
return await request.delete({ url: `/system/arterial/delete?id=` + id })
},
// 导出动脉硬化检测患者 Excel
exportarterial: async (params) => {
return await request.download({ url: `/system/arterial/export-excel`, params })
},
// 批量新增动脉硬化患者数据
insertArterialPatientData: async (data: patientinfoVO[]) => {
return await request.post({ url: `/system/arterial/insertArterialPatientData`, data })
},
// 根据examid更新动脉硬化分析结果
updateArterialAnalysis: async (examid: string, analysisResult: string) => {
return await request.put({
url: `/system/arterial/updateArterialAnalysis`,
params: {
examid,
analysisResult
}
})
},
// 根据examid查询动脉硬化分析结果
getArterialAnalysis: async (examid: string) => {
return await request.get({ url: `/system/arterial/getArterialAnalysis`, params: { examid } })
}
}

View File

@ -209,6 +209,7 @@ import { Search, Refresh, Delete } from '@element-plus/icons-vue'
import { StaticecgApi } from '@/api/staticecg'
import { Spo2infoApi } from '@/api/spo2info'
import { CgmApi } from '@/api/cgm/index'
import { arterialApi } from '@/api/arterial/index'
const message = useMessage() //
@ -368,6 +369,10 @@ const handleConfirm = async () => {
// CGM
await CgmApi.insertCgmPatientData(selectedPatients.value)
}
if (props.type === 'arteriosclerosis') {
//
await arterialApi.insertArterialPatientData(selectedPatients.value)
}
handleCancel()
} catch (error) {
message.error('操作失败,请重试')

View File

@ -0,0 +1,437 @@
<template>
<Dialog
v-model="dialogVisible"
:title="`动脉硬化分析 - ${patientData.name}`"
width="90%"
:before-close="handleClose"
>
<div class="arteriosclerosis-analysis-container">
<!-- 患者信息 -->
<div class="patient-info">
<el-row :gutter="20">
<el-col :span="6">
<div class="info-item">
<span class="label">姓名</span>
<span class="value">{{ patientData.name }}</span>
</div>
</el-col>
<el-col :span="6">
<div class="info-item">
<span class="label">性别</span>
<span class="value">{{ patientData.gender }}</span>
</div>
</el-col>
<el-col :span="6">
<div class="info-item">
<span class="label">年龄</span>
<span class="value">{{ patientData.age }}</span>
</div>
</el-col>
<el-col :span="6">
<div class="info-item">
<span class="label">检测时间</span>
<span class="value">{{ formatDateTime(patientData.measuretime) }}</span>
</div>
</el-col>
</el-row>
</div>
<!-- 动脉硬化趋势图 -->
<div class="chart-section">
<h3>动脉硬化指标趋势</h3>
<div ref="chartContainer" class="chart-container"></div>
</div>
<!-- 数据统计表格 -->
<div class="statistics-section">
<h3>检测数据统计</h3>
<el-table :data="statisticsData" border style="width: 100%">
<el-table-column prop="parameter" label="参数" width="200" />
<el-table-column prop="value" label="数值" width="150" />
<el-table-column prop="unit" label="单位" width="100" />
<el-table-column prop="reference" label="参考范围" width="200" />
<el-table-column prop="status" label="状态" width="120">
<template #default="{ row }">
<el-tag :type="getStatusType(row.status)" size="small">
{{ row.status }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="description" label="说明" />
</el-table>
</div>
<!-- 分析结果 -->
<div class="analysis-section">
<h3>分析结果</h3>
<el-form :model="analysisResult" label-width="120px">
<el-form-item label="总体评估:">
<el-input
v-model="analysisResult.overallAssessment"
type="textarea"
:rows="3"
placeholder="请输入总体评估结果"
/>
</el-form-item>
<el-form-item label="风险等级:">
<el-select v-model="analysisResult.riskLevel" placeholder="请选择风险等级">
<el-option label="低风险" value="low" />
<el-option label="中风险" value="medium" />
<el-option label="高风险" value="high" />
</el-select>
</el-form-item>
<el-form-item label="建议措施:">
<el-input
v-model="analysisResult.recommendations"
type="textarea"
:rows="4"
placeholder="请输入建议措施"
/>
</el-form-item>
<el-form-item label="医生签名:">
<el-input v-model="analysisResult.doctorSignature" placeholder="请输入医生姓名" />
</el-form-item>
</el-form>
</div>
</div>
<!-- 操作按钮 -->
<template #footer>
<div class="dialog-footer">
<el-button @click="handleClose">关闭</el-button>
<el-button type="primary" @click="handleSave">保存分析结果</el-button>
<el-button type="success" @click="handlePreviewReport">报告预览</el-button>
</div>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, nextTick, watch, onUnmounted } from 'vue'
import * as echarts from 'echarts'
import { formatDate } from '@/utils/formatTime'
import { ElMessage, ElMessageBox } from 'element-plus'
//
interface Props {
modelValue: boolean
patientData: any
recordId?: string
}
const props = withDefaults(defineProps<Props>(), {
modelValue: false,
patientData: () => ({}),
recordId: ''
})
const emit = defineEmits(['update:modelValue', 'saved'])
//
const dialogVisible = ref(false)
const chartContainer = ref()
let chartInstance: echarts.ECharts | null = null
//
const analysisResult = reactive({
overallAssessment: '',
riskLevel: '',
recommendations: '',
doctorSignature: ''
})
//
const statisticsData = ref([
{
parameter: 'PWV (脉搏波传导速度)',
value: '8.5',
unit: 'm/s',
reference: '<10.0',
status: '正常',
description: '反映动脉弹性'
},
{
parameter: 'ABI (踝臂指数)',
value: '1.12',
unit: '',
reference: '0.9-1.3',
status: '正常',
description: '下肢血管状况'
},
{
parameter: 'IMT (内膜中层厚度)',
value: '0.8',
unit: 'mm',
reference: '<1.0',
status: '正常',
description: '颈动脉内膜厚度'
},
{
parameter: 'AI (增强指数)',
value: '25',
unit: '%',
reference: '<30',
status: '正常',
description: '动脉硬化程度'
}
])
//
watch(
() => props.modelValue,
(newVal) => {
dialogVisible.value = newVal
if (newVal) {
nextTick(() => {
initChart()
loadAnalysisData()
})
}
},
{ immediate: true }
)
watch(
() => dialogVisible.value,
(newVal) => {
emit('update:modelValue', newVal)
}
)
//
const initChart = () => {
if (!chartContainer.value) return
chartInstance = echarts.init(chartContainer.value)
const option = {
title: {
text: '动脉硬化指标变化趋势',
left: 'center'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
}
},
legend: {
data: ['PWV', 'ABI', 'IMT', 'AI'],
top: 30
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: ['检测点1', '检测点2', '检测点3', '检测点4', '检测点5']
},
yAxis: [
{
type: 'value',
name: 'PWV (m/s)',
position: 'left'
},
{
type: 'value',
name: '其他指标',
position: 'right'
}
],
series: [
{
name: 'PWV',
type: 'line',
yAxisIndex: 0,
data: [8.2, 8.5, 8.3, 8.7, 8.5],
itemStyle: { color: '#5470c6' }
},
{
name: 'ABI',
type: 'line',
yAxisIndex: 1,
data: [1.1, 1.12, 1.08, 1.15, 1.12],
itemStyle: { color: '#91cc75' }
},
{
name: 'IMT',
type: 'line',
yAxisIndex: 1,
data: [0.7, 0.8, 0.75, 0.85, 0.8],
itemStyle: { color: '#fac858' }
},
{
name: 'AI',
type: 'line',
yAxisIndex: 1,
data: [22, 25, 23, 28, 25],
itemStyle: { color: '#ee6666' }
}
]
}
chartInstance.setOption(option)
}
//
const loadAnalysisData = async () => {
try {
// TODO: API
// const response = await ArterApi.getAnalysis(props.recordId)
// analysisResult.overallAssessment = response.data.overallAssessment
// analysisResult.riskLevel = response.data.riskLevel
// analysisResult.recommendations = response.data.recommendations
// analysisResult.doctorSignature = response.data.doctorSignature
//
analysisResult.overallAssessment =
'根据检测结果,患者动脉硬化程度处于正常范围内,各项指标均在参考值范围内。'
analysisResult.riskLevel = 'low'
analysisResult.recommendations = '建议保持健康的生活方式,定期进行体检,注意饮食和运动。'
analysisResult.doctorSignature = ''
} catch (error) {
console.error('加载分析数据失败:', error)
ElMessage.error('加载分析数据失败')
}
}
//
const getStatusType = (status: string) => {
switch (status) {
case '正常':
return 'success'
case '异常':
return 'danger'
case '临界':
return 'warning'
default:
return 'info'
}
}
//
const formatDateTime = (dateTime: string | Date) => {
if (!dateTime) return ''
return formatDate(dateTime, 'YYYY-MM-DD HH:mm:ss')
}
//
const handleSave = async () => {
try {
if (!analysisResult.overallAssessment.trim()) {
ElMessage.warning('请输入总体评估')
return
}
if (!analysisResult.riskLevel) {
ElMessage.warning('请选择风险等级')
return
}
if (!analysisResult.doctorSignature.trim()) {
ElMessage.warning('请输入医生签名')
return
}
// TODO: API
// await ArterApi.saveAnalysis({
// recordId: props.recordId,
// ...analysisResult
// })
ElMessage.success('分析结果保存成功')
emit('saved')
} catch (error) {
console.error('保存分析结果失败:', error)
ElMessage.error('保存分析结果失败')
}
}
//
const handlePreviewReport = () => {
if (!analysisResult.overallAssessment.trim()) {
ElMessage.warning('请先完成分析结果填写')
return
}
// TODO:
ElMessage.info('报告预览功能开发中')
}
//
const handleClose = () => {
dialogVisible.value = false
if (chartInstance) {
chartInstance.dispose()
chartInstance = null
}
}
//
onUnmounted(() => {
if (chartInstance) {
chartInstance.dispose()
chartInstance = null
}
})
</script>
<style scoped>
.arteriosclerosis-analysis-container {
padding: 20px;
}
.patient-info {
background: #f5f7fa;
padding: 15px;
border-radius: 8px;
margin-bottom: 20px;
}
.info-item {
display: flex;
align-items: center;
margin-bottom: 8px;
}
.info-item .label {
font-weight: bold;
color: #606266;
margin-right: 8px;
}
.info-item .value {
color: #303133;
}
.chart-section,
.statistics-section,
.analysis-section {
margin-bottom: 30px;
}
.chart-section h3,
.statistics-section h3,
.analysis-section h3 {
margin-bottom: 15px;
color: #303133;
font-size: 16px;
font-weight: bold;
}
.chart-container {
width: 100%;
height: 400px;
border: 1px solid #e4e7ed;
border-radius: 4px;
}
.dialog-footer {
text-align: right;
}
.dialog-footer .el-button {
margin-left: 10px;
}
</style>

View File

@ -0,0 +1,447 @@
<template>
<div class="arteriosclerosis-analysis">
<!-- 动脉硬化分析弹窗组件 -->
<Arter_analysis ref="arterAnalysisRef" />
<!-- 患者选择组件后续可补充 -->
<PatientSelect
ref="patientSelectRef"
title="选择患者"
type="arteriosclerosis"
:multiple="true"
:max-select="50"
@confirm="handlePatientConfirm"
@cancel="handlePatientCancel"
/>
<!-- 顶部筛选区 -->
<el-card class="filter-card" shadow="never">
<el-form :inline="true" :model="queryParams" class="filter-form">
<el-form-item label="姓名:">
<el-input
v-model="queryParams.name"
placeholder="请输入姓名"
clearable
style="width: 120px"
/>
</el-form-item>
<el-form-item label="性别:">
<el-select
v-model="queryParams.gender"
placeholder="请选择"
clearable
style="width: 100px"
>
<el-option label="男" value="1" />
<el-option label="女" value="2" />
</el-select>
</el-form-item>
<el-form-item label="日期:">
<el-date-picker
v-model="queryParams.dateRange"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
style="width: 240px"
value-format="YYYY-MM-DD"
/>
</el-form-item>
<el-form-item label="状态:">
<el-select
v-model="queryParams.status"
placeholder="请选择"
clearable
style="width: 140px"
>
<el-option label="已申请" value="1" />
<el-option label="未申请" value="0" />
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"
><el-icon><Search /></el-icon></el-button
>
<el-button @click="resetQuery"
><el-icon><Refresh /></el-icon></el-button
>
<el-button type="primary" @click="handleSelectPatients"
><el-icon><Plus /></el-icon></el-button
>
</el-form-item>
</el-form>
</el-card>
<!-- 数据表格区 -->
<el-card class="table-card" shadow="never" style="margin-top: 16px">
<el-table v-loading="loading" :data="tableData" border stripe style="width: 100%">
<el-table-column prop="examid" label="检查ID" align="center" min-width="220" />
<el-table-column prop="name" label="姓名" align="center" min-width="90" />
<el-table-column prop="gender" label="性别" align="center" min-width="60">
<template #default="{ row }">
<span>{{ row.gender === '1' ? '男' : row.gender === '2' ? '女' : '未知' }}</span>
</template>
</el-table-column>
<el-table-column prop="age" label="年龄" align="center" min-width="60">
<template #default="{ row }">
<span>{{ row.age + '岁' }}</span>
</template>
</el-table-column>
<el-table-column prop="weartime" label="佩戴时间" align="center" min-width="180">
<template #default="{ row, $index }">
<!-- 编辑状态 -->
<div v-if="row.editingWearTime" style="display: flex; align-items: center; gap: 8px">
<el-date-picker
v-model="row.weartime"
type="datetime"
placeholder="选择时间"
size="small"
style="width: 130px"
value-format="YYYY-MM-DD HH:mm:ss"
/>
<el-button type="primary" size="small" @click="saveWearTime(row, $index)" link>
<el-icon><Check /></el-icon>
</el-button>
<el-button size="small" @click="cancelEditWearTime(row, $index)" link>
<el-icon><Close /></el-icon>
</el-button>
</div>
<!-- 显示状态 -->
<div
v-else
style="display: flex; align-items: center; justify-content: center; gap: 8px"
>
<span>{{ formatTime(row.weartime) }}</span>
<el-button type="primary" size="small" @click="editWearTime(row, $index)" link>
<el-icon><Edit /></el-icon>
</el-button>
</div>
</template>
</el-table-column>
<el-table-column prop="device" label="检测设备" align="center" min-width="150">
<template #default="{ row, $index }">
<!-- 编辑状态 -->
<div v-if="row.editingDevice" style="display: flex; align-items: center; gap: 8px">
<el-select
v-model="row.deviceid"
placeholder="选择设备"
size="small"
style="width: 110px"
clearable
>
<el-option
v-for="item in deviceList"
:key="item.id"
:label="item.deviceName"
:value="item.id"
/>
</el-select>
<el-button type="primary" size="small" @click="saveDevice(row, $index)" link>
<el-icon><Check /></el-icon>
</el-button>
<el-button size="small" @click="cancelEditDevice(row, $index)" link>
<el-icon><Close /></el-icon>
</el-button>
</div>
<!-- 显示状态 -->
<div
v-else
style="display: flex; align-items: center; justify-content: center; gap: 8px"
>
<span>{{ row.devicename }}</span>
<el-button type="primary" size="small" @click="editDevice(row, $index)" link>
<el-icon><Edit /></el-icon>
</el-button>
</div>
</template>
</el-table-column>
<el-table-column prop="orgname" label="机构" align="center" min-width="120" />
<el-table-column label="分析" align="center" min-width="70">
<template #default="{ row }">
<el-link type="primary" @click="onAnalyze(row)">分析</el-link>
</template>
</el-table-column>
<el-table-column label="报告" align="center" min-width="70">
<template #default="{ row }">
<el-link type="primary" @click="onViewReport(row)">查看</el-link>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" align="center" min-width="70">
<template #default="{ row }">
<el-link v-if="row.status == '1'" type="success" :underline="false" disabled
>已申请</el-link
>
<el-link v-else type="primary" @click="handleApply(row)">申请</el-link>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</el-card>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import PatientSelect from '@/patientcom/index.vue'
import { getUserProfile } from '@/api/system/user/profile'
import { OrgApi } from '@/api/org'
import { arterialApi } from '@/api/arterial'
import { Search, Refresh, Plus, Edit, Check, Close } from '@element-plus/icons-vue'
import dayjs from 'dayjs'
import Arter_analysis from './Arter_analysis.vue'
const loading = ref(false)
const total = ref(0)
//
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: '',
gender: '',
dateRange: [],
status: '',
orgid: '',
orgname: '',
age: ''
})
//
const tableData = ref<any[]>([])
const patientSelectRef = ref()
const arterAnalysisRef = ref()
//
const deviceList = ref<any[]>([])
const getDeviceList = () => {
deviceList.value = [
{ id: '1001', deviceName: '动脉硬化检测仪-1001' },
{ id: '1002', deviceName: '动脉硬化检测仪-1002' },
{ id: '1003', deviceName: '动脉硬化检测仪-1003' }
]
}
onMounted(() => {
getDeviceList()
getList()
})
//
const getList = async () => {
loading.value = true
try {
//
const params = { ...queryParams }
if (params.dateRange && params.dateRange.length === 2) {
params.startDate = params.dateRange[0]
params.endDate = params.dateRange[1]
delete params.dateRange
}
//
const userinfo = await getUserProfile()
const orginfo = await OrgApi.getOrgByOrgId(userinfo.orgid)
// ID[0]
if (userinfo.orgid != 0 && userinfo.orgid != null) {
// ID&ID&ID
if (userinfo.orgid != orginfo.parentOrgId) {
params.orgid = userinfo.orgid
const data = await arterialApi.getarterialPage(params)
tableData.value = data.list
total.value = data.total
} else if (userinfo.orgid == orginfo.parentOrgId) {
// &ID
const childinfo = await OrgApi.getOrgByParentOrgId(userinfo.orgid)
params.orgid = childinfo.orgId
params.status = '1'
const data = await arterialApi.getarterialPage(params)
tableData.value = data.list
total.value = data.total
}
} else if (userinfo.orgid == 0) {
//
const data = await arterialApi.getarterialPage(params)
tableData.value = data.list
total.value = data.total
} else {
console.error('用户机构为空')
return
}
} catch (error) {
console.error('获取动脉硬化数据失败:', error)
ElMessage.error('获取数据失败')
} finally {
loading.value = false
}
}
//
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
const resetQuery = () => {
Object.assign(queryParams, {
pageNo: 1,
pageSize: 10,
name: '',
gender: '',
dateRange: [],
status: '',
age: ''
})
getList()
}
const handleSelectPatients = () => {
patientSelectRef.value?.open()
}
const handlePatientConfirm = async (selectedPatients: any[]) => {
try {
await arterialApi.insertArterialPatientData(selectedPatients)
ElMessage.success(`已成功添加 ${selectedPatients.length} 位患者到动脉硬化检测系统`)
//
handleQuery()
} catch (error) {
console.error('添加动脉硬化患者数据失败:', error)
ElMessage.error('添加患者数据失败,请重试')
}
}
const handlePatientCancel = () => {
handleQuery()
}
const onAnalyze = async (row: any) => {
try {
//
const res = await arterialApi.getArterialAnalysis(row.examid)
if (res) {
arterAnalysisRef.value?.open(row)
} else {
ElMessage.error('暂无分析数据')
}
} catch (error) {
console.error('获取分析数据失败:', error)
//
arterAnalysisRef.value?.open(row)
}
}
const onViewReport = (row: any) => {
ElMessage.success(`查看报告:${row.name}`)
}
const handleApply = async (row: any) => {
try {
const userinfo = await getUserProfile()
const orginfo = await OrgApi.getOrg(userinfo.orgid)
if (orginfo.parentOrgId != null) {
const updateData = { ...row, status: 1, managerorg: orginfo.parentOrgId }
await arterialApi.updatearterial(updateData)
ElMessage.success(`${row.name} 申请成功`)
getList()
} else {
ElMessage.error('无上级机构')
return
}
} catch (error) {
console.error('申请失败:', error)
ElMessage.error('申请失败,请重试')
}
}
const formatTime = (timestamp: number | string) => {
if (!timestamp) return ''
const ts = typeof timestamp === 'string' ? parseInt(timestamp) : timestamp
return dayjs(ts).isValid() ? dayjs(ts).format('YYYY-MM-DD HH:mm:ss') : timestamp
}
//
const editWearTime = (row: any, index: number) => {
row.editingWearTime = true
row.originalWeartime = row.weartime //
}
//
const saveWearTime = async (row: any, index: number) => {
if (!row.weartime) {
ElMessage.error('佩戴时间不能为空')
return
}
try {
const timestamp =
typeof row.weartime === 'string' ? new Date(row.weartime).getTime() : row.weartime
await arterialApi.updatearterial({ ...row, weartime: timestamp })
row.weartime = timestamp //
ElMessage.success('佩戴时间更新成功')
row.editingWearTime = false
getList()
} catch (error) {
console.error('更新佩戴时间失败:', error)
ElMessage.error('更新佩戴时间失败,请重试')
row.weartime = row.originalWeartime //
}
}
//
const cancelEditWearTime = (row: any, index: number) => {
row.weartime = row.originalWeartime
row.editingWearTime = false
}
//
const editDevice = (row: any, index: number) => {
row.editingDevice = true
row.originalDeviceId = row.deviceid //
}
//
const saveDevice = async (row: any, index: number) => {
if (!row.deviceid) {
ElMessage.error('设备不能为空')
return
}
try {
await arterialApi.updatearterial(row)
ElMessage.success('设备更新成功')
row.editingDevice = false
getList()
} catch (error) {
console.error('更新设备失败:', error)
ElMessage.error('更新设备失败,请重试')
row.deviceid = row.originalDeviceId //
}
}
//
const cancelEditDevice = (row: any, index: number) => {
row.deviceid = row.originalDeviceId
row.editingDevice = false
}
</script>
<style scoped>
.arteriosclerosis-analysis {
padding: 24px;
background: #f6f8fa;
min-height: 100vh;
}
.filter-card {
margin-bottom: 0;
background: #fff;
}
.filter-form {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px 16px;
}
.table-card {
background: #fff;
}
</style>