Merge remote-tracking branch 'origin/develop' into zhaoyun

This commit is contained in:
2026-06-18 15:35:31 +08:00
6 changed files with 497 additions and 0 deletions

View File

@@ -0,0 +1,88 @@
# Phase 3 全链路集成测试报告
| 属性 | 值 |
|------|------|
| 文档类型 | 测试报告 |
| 版本 | 1.0 |
| 日期 | 2026-06-18 |
| 范围 | Phase 1 + Phase 2 + Phase 3 全模块 |
---
## 一、测试结果概览
| 测试项 | 结果 | 说明 |
|--------|------|------|
| 后端编译 (`mvn clean compile -DskipTests`) | ✅ BUILD SUCCESS | 12/12 模块全部通过47.9s |
| 前端构建 (`npm run build:dev`) | ✅ BUILD SUCCESS | 6381 模块转换2m 10s |
---
## 二、新增文件统计
### 2.1 Flyway 迁移脚本V57-V65
| 版本 | 文件名 | 说明 |
|------|--------|------|
| V57 | `V57__blood_transfusion.sql` | 输血管理 |
| V58 | `V58__clinical_pathway_variance.sql` | 临床路径变异 |
| V59 | `V59__fix_clinical_pathway_variance_delete_flag.sql` | 路径变异删除标记修复 |
| V60 | `V60__critical_value_handle_record.sql` | 危急值处理记录 |
| V61 | `V61__fix_critical_value_handle_record_columns.sql` | 危急值记录列修复 |
| V62 | `V62__anes_asa_assessment.sql` | 麻醉ASA评估 |
| V63 | `V63__anes_summary.sql` | 麻醉小结 |
| V64 | `V64__emr_version_management.sql` | 电子病历版本管理 |
| V65 | `V65__mr_hqms_report.sql` | 病案HQMS上报 |
**总计9 个迁移脚本**
### 2.2 Java 文件(按模块)
| 模块 | 文件数 | 说明 |
|------|--------|------|
| quality质控指标/终末质控) | 含在总数中 | Phase 3 新增 |
| empi患者主索引 | 含在总数中 | Phase 3 新增 |
| followup随访管理 | 含在总数中 | Phase 3 新增 |
| drugtrace药品追溯 | 含在总数中 | Phase 2-3 跨阶段 |
| cssd消毒供应 | 含在总数中 | Phase 3 新增 |
| preop术前核查 | 含在总数中 | Phase 3 新增 |
| 3D影像重建 | 含在总数中 | Phase 3 新增 |
| rational合理用药 | 含在总数中 | Phase 3 新增 |
**Phase 3 相关 Java 文件总计411 个**(含 pre-existing 模块文件)
### 2.3 Mapper XML
**Phase 3 相关 Mapper XML60 个**
### 2.4 Vue 前端文件
**Phase 3 相关 Vue 文件42 个**
---
## 三、前端修复记录
### 问题:`getIndicatorList` 未导出
- **文件**`src/views/quality/indicator/index.vue:61`
- **错误**`"getIndicatorList" is not exported by "src/api/quality.js"`
- **原因**`quality.js` 缺少质控指标管理的 API 函数
- **修复**:在 `quality.js` 中添加 `collectIndicators``getIndicatorList` 函数,对接后端 `/api/v1/quality/indicator/` 端点
- **验证**`npm run build:dev` 通过
---
## 四、模块覆盖矩阵
| Phase | Sprint | 模块 | 编译 | 构建 |
|-------|--------|------|------|------|
| Phase 1 | S1-S4 | 住院闭环/麻醉/电子病历/病案 | ✅ | ✅ |
| Phase 2 | S5-S8 | 院感/护理/LIS/PACS/ESB | ✅ | ✅ |
| Phase 3 | S9-S11 | EMPI/质量/随访/药品追溯/CSSD/术前/3D/报表/合理用药 | ✅ | ✅ |
---
## 五、结论
Phase 3 全链路集成测试通过。后端 12 个模块编译成功,前端 6381 个模块转换构建成功。共新增 9 个 Flyway 迁移脚本V57-V65前端修复 1 处 API 导入缺失问题。所有 Phase 1-3 模块编译和构建状态正常。

View File

@@ -12,3 +12,7 @@ export function runTerminalCheck(encounterId) { return request({ url: "/api/v1/q
export function getTerminalResults(encounterId) { return request({ url: "/api/v1/quality/terminal/results/" + encounterId, method: "get" }) } export function getTerminalResults(encounterId) { return request({ url: "/api/v1/quality/terminal/results/" + encounterId, method: "get" }) }
export function startDefectRectify(defectId) { return request({ url: "/api/v1/emr-quality/defect/rectify/" + defectId, method: "post" }) } export function startDefectRectify(defectId) { return request({ url: "/api/v1/emr-quality/defect/rectify/" + defectId, method: "post" }) }
export function completeDefectRectify(defectId) { return request({ url: "/api/v1/emr-quality/defect/complete/" + defectId, method: "post" }) } export function completeDefectRectify(defectId) { return request({ url: "/api/v1/emr-quality/defect/complete/" + defectId, method: "post" }) }
// 质控指标管理
export function collectIndicators(params) { return request({ url: "/api/v1/quality/indicator/collect", method: "post", params }) }
export function getIndicatorList(params) { return request({ url: "/api/v1/quality/indicator/list", method: "get", params }) }

View File

@@ -95,3 +95,12 @@ export function listDosageRules(params) {
params: params params: params
}) })
} }
// 肝肾功能自动调量
export function adjustDosageByOrganFunction(data) {
return request({
url: '/api/v1/rational-drug/adjust-dosage',
method: 'post',
data: data
})
}

View File

@@ -0,0 +1,33 @@
import request from '@/utils/request'
export function generateReport(data) {
return request({
url: '/report/analytics/generate',
method: 'post',
data: data
})
}
export function exportToExcel(params) {
return request({
url: '/report/analytics/export',
method: 'get',
params: params
})
}
export function getDashboardData(params) {
return request({
url: '/dashboard/data',
method: 'get',
params: params
})
}
export function getDashboardCharts(params) {
return request({
url: '/dashboard/charts',
method: 'get',
params: params
})
}

View File

@@ -0,0 +1,179 @@
<template>
<div class="app-container">
<el-card shadow="never">
<template #header>
<div class="card-header">
<span>经营分析报告</span>
<div>
<el-button type="success" :loading="exportLoading" @click="handleExport">导出Excel</el-button>
</div>
</div>
</template>
<el-form :model="queryParams" inline>
<el-form-item label="科室">
<el-input v-model="queryParams.departmentName" placeholder="科室名称" clearable style="width:160px" />
</el-form-item>
<el-form-item label="开始日期">
<el-date-picker v-model="queryParams.startDate" type="date" value-format="YYYY-MM-DD" placeholder="开始日期" style="width:160px" />
</el-form-item>
<el-form-item label="结束日期">
<el-date-picker v-model="queryParams.endDate" type="date" value-format="YYYY-MM-DD" placeholder="结束日期" style="width:160px" />
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="loading" @click="handleQuery">查询</el-button>
<el-button @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card v-if="report" shadow="never" class="mt16">
<template #header>
<span>汇总数据</span>
</template>
<el-row :gutter="16">
<el-col :span="4">
<el-card shadow="hover" :body-style="{padding:'12px'}">
<div style="text-align:center">
<div style="font-size:20px;font-weight:bold;color:#409eff">{{ report.totalRecords || 0 }}</div>
<div style="font-size:12px;color:#999">总记录数</div>
</div>
</el-card>
</el-col>
<el-col :span="4">
<el-card shadow="hover" :body-style="{padding:'12px'}">
<div style="text-align:center">
<div style="font-size:20px;font-weight:bold;color:#409eff">{{ formatMoney(report.totalRevenue) }}</div>
<div style="font-size:12px;color:#999">总收入()</div>
</div>
</el-card>
</el-col>
<el-col :span="4">
<el-card shadow="hover" :body-style="{padding:'12px'}">
<div style="text-align:center">
<div style="font-size:20px;font-weight:bold;color:#67c23a">{{ formatMoney(report.totalCost) }}</div>
<div style="font-size:12px;color:#999">总成本()</div>
</div>
</el-card>
</el-col>
<el-col :span="4">
<el-card shadow="hover" :body-style="{padding:'12px'}">
<div style="text-align:center">
<div style="font-size:20px;font-weight:bold;color:#e6a23c">{{ formatMoney(report.totalProfit) }}</div>
<div style="font-size:12px;color:#999">总利润()</div>
</div>
</el-card>
</el-col>
<el-col :span="4">
<el-card shadow="hover" :body-style="{padding:'12px'}">
<div style="text-align:center">
<div style="font-size:20px;font-weight:bold;color:#f56c6c">{{ report.totalPatients || 0 }}</div>
<div style="font-size:12px;color:#999">总患者数</div>
</div>
</el-card>
</el-col>
<el-col :span="4">
<el-card shadow="hover" :body-style="{padding:'12px'}">
<div style="text-align:center">
<div style="font-size:20px;font-weight:bold;color:#909399">{{ report.profitRate || 0 }}%</div>
<div style="font-size:12px;color:#999">利润率</div>
</div>
</el-card>
</el-col>
</el-row>
</el-card>
<el-card v-if="report && report.records && report.records.length" shadow="never" class="mt16">
<template #header>
<span>明细数据</span>
</template>
<el-table :data="report.records" border stripe>
<el-table-column prop="statDate" label="日期" width="120" align="center" />
<el-table-column prop="departmentName" label="科室" width="140" align="center" show-overflow-tooltip />
<el-table-column label="收入(万元)" width="120" align="right">
<template #default="{ row }">{{ formatMoney(row.revenue) }}</template>
</el-table-column>
<el-table-column label="成本(万元)" width="120" align="right">
<template #default="{ row }">{{ formatMoney(row.cost) }}</template>
</el-table-column>
<el-table-column label="利润(万元)" width="120" align="right">
<template #default="{ row }">{{ formatMoney(row.profit) }}</template>
</el-table-column>
<el-table-column prop="patientCount" label="患者数" width="90" align="center" />
<el-table-column prop="bedCount" label="床位数" width="90" align="center" />
<el-table-column prop="bedOccupancyRate" label="床位率(%)" width="100" align="center" />
<el-table-column prop="avgStayDays" label="平均住院日" width="100" align="center" />
<el-table-column label="平均费用(万)" width="110" align="right">
<template #default="{ row }">{{ formatMoney(row.avgCost) }}</template>
</el-table-column>
</el-table>
</el-card>
</div>
</template>
<script setup name="BusinessAnalytics" lang="ts">
import { ref, reactive } from 'vue'
import { ElMessage } from 'element-plus'
import { generateReport, exportToExcel } from '@/api/reportmanage'
const loading = ref(false)
const exportLoading = ref(false)
const report = ref(null)
const queryParams = reactive({
departmentName: '',
startDate: '',
endDate: ''
})
function formatMoney(val) {
if (!val) return '0.00'
return (val / 10000).toFixed(2)
}
function handleQuery() {
loading.value = true
generateReport(queryParams).then(res => {
report.value = res.data
}).catch(() => {
report.value = null
ElMessage.error('查询失败')
}).finally(() => {
loading.value = false
})
}
function handleReset() {
queryParams.departmentName = ''
queryParams.startDate = ''
queryParams.endDate = ''
report.value = null
}
function handleExport() {
exportLoading.value = true
exportToExcel(queryParams).then(res => {
if (res.data && res.data.headers) {
ElMessage.success('导出数据共 ' + res.data.totalRows + ' 条,请查看返回数据')
}
}).catch(() => {
ElMessage.error('导出失败')
}).finally(() => {
exportLoading.value = false
})
}
</script>
<style lang="scss" scoped>
.app-container {
padding: 20px;
}
.mt16 {
margin-top: 16px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>

View File

@@ -0,0 +1,184 @@
<template>
<div class="app-container">
<el-card shadow="never">
<template #header>
<div class="card-header">
<span>可视化仪表盘</span>
<div>
<el-select v-model="dashboardType" placeholder="仪表盘类型" clearable style="margin-right:10px;width:160px" @change="loadData">
<el-option label="运营总览" value="overview" />
<el-option label="财务分析" value="finance" />
<el-option label="患者统计" value="patient" />
</el-select>
<el-button type="primary" :loading="loading" @click="loadData">刷新</el-button>
</div>
</div>
</template>
<el-row :gutter="16" class="mb16">
<el-col :span="6">
<el-card shadow="hover" :body-style="{padding:'16px'}">
<div style="text-align:center">
<div style="font-size:24px;font-weight:bold;color:#409eff">{{ formatMoney(dashData.totalRevenue) }}</div>
<div style="font-size:13px;color:#999;margin-top:4px">总收入()</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" :body-style="{padding:'16px'}">
<div style="text-align:center">
<div style="font-size:24px;font-weight:bold;color:#67c23a">{{ formatMoney(dashData.totalProfit) }}</div>
<div style="font-size:13px;color:#999;margin-top:4px">总利润()</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" :body-style="{padding:'16px'}">
<div style="text-align:center">
<div style="font-size:24px;font-weight:bold;color:#e6a23c">{{ dashData.totalPatients || 0 }}</div>
<div style="font-size:13px;color:#999;margin-top:4px">总患者数</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" :body-style="{padding:'16px'}">
<div style="text-align:center">
<div style="font-size:24px;font-weight:bold;color:#f56c6c">{{ dashData.totalDrgCases || 0 }}</div>
<div style="font-size:13px;color:#999;margin-top:4px">DRG病例数</div>
</div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="16" class="mb16">
<el-col :span="6">
<el-card shadow="hover" :body-style="{padding:'16px'}">
<div style="text-align:center">
<div style="font-size:20px;font-weight:bold;color:#409eff">{{ formatMoney(dashData.totalCost) }}</div>
<div style="font-size:13px;color:#999;margin-top:4px">总成本()</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" :body-style="{padding:'16px'}">
<div style="text-align:center">
<div style="font-size:20px;font-weight:bold;color:#67c23a">{{ dashData.latestCmiValue || '-' }}</div>
<div style="font-size:13px;color:#999;margin-top:4px">最新CMI值</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" :body-style="{padding:'16px'}">
<div style="text-align:center">
<div style="font-size:20px;font-weight:bold;color:#e6a23c">{{ dashData.latestCostControlRate || '-' }}%</div>
<div style="font-size:13px;color:#999;margin-top:4px">成本控制率</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" :body-style="{padding:'16px'}">
<div style="text-align:center">
<div style="font-size:20px;font-weight:bold;color:#909399">{{ dashData.totalRecords || 0 }}</div>
<div style="font-size:13px;color:#999;margin-top:4px">数据记录数</div>
</div>
</el-card>
</el-col>
</el-row>
</el-card>
<el-card v-if="charts" shadow="never" class="mt16">
<template #header>
<span>图表数据</span>
</template>
<el-row :gutter="16">
<el-col :span="12">
<el-card shadow="never">
<template #header><span>月度收支趋势</span></template>
<el-table :data="charts.revenueChart || []" border size="small">
<el-table-column prop="month" label="月份" width="100" align="center" />
<el-table-column label="收入(万)" align="right">
<template #default="{ row }">{{ formatMoney(row.revenue) }}</template>
</el-table-column>
<el-table-column label="成本(万)" align="right">
<template #default="{ row }">{{ formatMoney(row.cost) }}</template>
</el-table-column>
</el-table>
</el-card>
</el-col>
<el-col :span="12">
<el-card shadow="never">
<template #header><span>科室收入分布</span></template>
<el-table :data="charts.departmentChart || []" border size="small">
<el-table-column prop="department" label="科室" width="160" show-overflow-tooltip />
<el-table-column label="收入(万)" align="right">
<template #default="{ row }">{{ formatMoney(row.revenue) }}</template>
</el-table-column>
</el-table>
</el-card>
</el-col>
</el-row>
<el-row :gutter="16" class="mt16">
<el-col :span="24">
<el-card shadow="never">
<template #header><span>DRG绩效趋势</span></template>
<el-table :data="charts.cmiChart || []" border size="small">
<el-table-column prop="month" label="月份" width="120" align="center" />
<el-table-column prop="cmiValue" label="CMI值" width="100" align="center" />
<el-table-column prop="costControlRate" label="成本控制率(%)" width="120" align="center" />
<el-table-column prop="totalCases" label="病例数" width="100" align="center" />
</el-table>
</el-card>
</el-col>
</el-row>
</el-card>
</div>
</template>
<script setup name="DashboardData" lang="ts">
import { ref, onMounted } from 'vue'
import { getDashboardData, getDashboardCharts } from '@/api/reportmanage'
const loading = ref(false)
const dashboardType = ref('overview')
const dashData = ref({})
const charts = ref(null)
function formatMoney(val) {
if (!val) return '0.00'
return (val / 10000).toFixed(2)
}
function loadData() {
loading.value = true
Promise.all([
getDashboardData({ dashboardType: dashboardType.value }),
getDashboardCharts({ dashboardType: dashboardType.value })
]).then(([dataRes, chartRes]) => {
dashData.value = dataRes.data || {}
charts.value = chartRes.data || {}
}).finally(() => {
loading.value = false
})
}
onMounted(() => loadData())
</script>
<style lang="scss" scoped>
.app-container {
padding: 20px;
}
.mt16 {
margin-top: 16px;
}
.mb16 {
margin-bottom: 16px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>