<template>
|
<div class="app-container">
|
<!-- 查询栏 -->
|
<el-form :inline="true" :model="queryParams" size="small" class="query-form">
|
<el-form-item label="患者姓名">
|
<el-input v-model="queryParams.name" placeholder="请输入姓名" clearable @keyup.enter.native="handleQuery" />
|
</el-form-item>
|
<el-form-item label="主治医生">
|
<el-input v-model="queryParams.drname" placeholder="请输入医生姓名" clearable @keyup.enter.native="handleQuery" />
|
</el-form-item>
|
<el-form-item label="科室/病区">
|
<el-select v-model="queryParams.deptWard" placeholder="全部" clearable filterable>
|
<el-option-group label="科室">
|
<el-option v-for="d in deptOptions" :key="'dept:' + d" :label="d" :value="'dept:' + d" />
|
</el-option-group>
|
<el-option-group label="病区">
|
<el-option v-for="w in wardOptions" :key="'ward:' + w" :label="w" :value="'ward:' + w" />
|
</el-option-group>
|
</el-select>
|
</el-form-item>
|
<el-form-item label="心功能分级">
|
<el-select v-model="queryParams.nyhaClass" placeholder="全部" clearable>
|
<el-option v-for="g in NYHA_CLASSES" :key="g" :label="g" :value="g" />
|
</el-select>
|
</el-form-item>
|
<el-form-item>
|
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
|
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
|
</el-form-item>
|
</el-form>
|
|
<!-- 操作栏 -->
|
<el-row :gutter="10" class="mb8">
|
<el-col :span="1.5">
|
<el-button type="primary" icon="el-icon-plus" size="mini" @click="openAddDialog">添加患者</el-button>
|
</el-col>
|
<el-col :span="1.5">
|
<el-button type="danger" icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete">删除</el-button>
|
</el-col>
|
<el-col :span="1.5">
|
<el-button type="warning" icon="el-icon-download" size="mini" @click="handleExport">导出</el-button>
|
</el-col>
|
</el-row>
|
|
<!-- 列表 -->
|
<el-table :data="currentPageData" v-loading="loading" border @selection-change="handleSelectionChange">
|
<el-table-column type="selection" width="50" align="center" />
|
<el-table-column label="姓名" prop="name" min-width="90" align="center" fixed="left" />
|
<el-table-column label="性别" prop="sex" width="60" align="center" />
|
<el-table-column label="年龄" prop="age" width="70" align="center" />
|
<el-table-column label="联系电话" prop="telcode" width="120" align="center" />
|
<el-table-column label="科室" prop="deptname" min-width="110" align="center" show-overflow-tooltip />
|
<el-table-column label="病区" prop="wardname" min-width="90" align="center" />
|
<el-table-column label="主治医生" prop="drname" width="90" align="center" />
|
<el-table-column label="心脏诊断" prop="heartDiagnosis" min-width="120" align="center" show-overflow-tooltip />
|
<el-table-column label="心功能分级" prop="nyhaClass" width="100" align="center">
|
<template slot-scope="scope">
|
<el-tag :type="nyhaTagType(scope.row.nyhaClass)" size="small">{{ scope.row.nyhaClass }}</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column label="心率(次/分)" prop="heartRate" width="100" align="center" />
|
<el-table-column label="血压(mmHg)" prop="bloodPressure" width="120" align="center" />
|
<el-table-column label="随访计划" prop="followPlan" width="100" align="center" />
|
<el-table-column label="来源" prop="source" width="90" align="center" />
|
<el-table-column label="加入时间" prop="addTime" width="150" align="center" />
|
<el-table-column label="操作" width="120" align="center" fixed="right">
|
<template slot-scope="scope">
|
<el-button type="text" size="mini" icon="el-icon-view" @click="openDetail(scope.row)">详情</el-button>
|
<el-button type="text" size="mini" icon="el-icon-close" class="danger-text" @click="removeRow(scope.row)">移除</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
|
<!-- 分页 -->
|
<el-pagination
|
class="pagination-wrap"
|
background
|
layout="total, sizes, prev, pager, next, jumper"
|
:total="filteredList.length"
|
:current-page.sync="pageNum"
|
:page-size.sync="pageSize"
|
:page-sizes="[10, 20, 50]"
|
/>
|
|
<!-- 添加患者弹窗 -->
|
<el-dialog title="添加专病患者" :visible.sync="addVisible" width="80%" top="6vh">
|
<el-tabs v-model="activeSource" @tab-click="onSourceTabChange">
|
<el-tab-pane label="患者档案" name="archive" />
|
<el-tab-pane label="出院记录" name="hospital" />
|
<el-tab-pane label="门诊记录" name="outpatient" />
|
<el-tab-pane label="在院记录" name="inpatient" />
|
</el-tabs>
|
<el-form :inline="true" size="small">
|
<el-form-item label="姓名/诊断">
|
<el-input v-model="poolKeyword" placeholder="输入姓名或诊断关键字" clearable style="width: 220px" @input="handlePoolSearch" />
|
</el-form-item>
|
<span class="pool-tip">共 {{ filteredPool.length }} 条,已选 {{ addSelected.length }} 条</span>
|
</el-form>
|
<el-table ref="addTable" :data="filteredPool" border size="small" max-height="420" @selection-change="onAddSelectionChange">
|
<el-table-column type="selection" width="50" align="center" />
|
<el-table-column label="姓名" prop="name" width="90" align="center" />
|
<el-table-column label="性别" prop="sex" width="60" align="center" />
|
<el-table-column label="年龄" prop="age" width="70" align="center" />
|
<el-table-column label="联系电话" prop="telcode" width="120" align="center" />
|
<el-table-column label="科室" prop="deptname" min-width="110" align="center" show-overflow-tooltip />
|
<el-table-column label="病区" prop="wardname" min-width="90" align="center" />
|
<el-table-column label="诊断" prop="diagname" min-width="130" align="center" show-overflow-tooltip />
|
<el-table-column label="医生" prop="drname" width="90" align="center" />
|
<el-table-column label="日期" prop="sourceDate" width="110" align="center" />
|
</el-table>
|
<div slot="footer" class="dialog-footer">
|
<el-button @click="addVisible = false">取 消</el-button>
|
<el-button type="primary" @click="confirmAdd">确定添加</el-button>
|
</div>
|
</el-dialog>
|
|
<!-- 详情弹窗 -->
|
<el-dialog title="专病患者详情" :visible.sync="detailVisible" width="720px">
|
<el-descriptions :column="2" border size="medium" v-if="detailRow">
|
<el-descriptions-item label="姓名">{{ detailRow.name }}</el-descriptions-item>
|
<el-descriptions-item label="性别">{{ detailRow.sex }}</el-descriptions-item>
|
<el-descriptions-item label="年龄">{{ detailRow.age }}</el-descriptions-item>
|
<el-descriptions-item label="联系电话">{{ detailRow.telcode }}</el-descriptions-item>
|
<el-descriptions-item label="科室">{{ detailRow.deptname }}</el-descriptions-item>
|
<el-descriptions-item label="病区">{{ detailRow.wardname || '—' }}</el-descriptions-item>
|
<el-descriptions-item label="主治医生">{{ detailRow.drname }}</el-descriptions-item>
|
<el-descriptions-item label="心脏诊断">{{ detailRow.heartDiagnosis }}</el-descriptions-item>
|
<el-descriptions-item label="心功能分级">{{ detailRow.nyhaClass }}</el-descriptions-item>
|
<el-descriptions-item label="心率">{{ detailRow.heartRate }} 次/分</el-descriptions-item>
|
<el-descriptions-item label="血压">{{ detailRow.bloodPressure }} mmHg</el-descriptions-item>
|
<el-descriptions-item label="随访计划">{{ detailRow.followPlan }}</el-descriptions-item>
|
<el-descriptions-item label="患者来源">{{ detailRow.source }}</el-descriptions-item>
|
<el-descriptions-item label="加入时间">{{ detailRow.addTime }}</el-descriptions-item>
|
</el-descriptions>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script>
|
// 心脏专病患者 —— 模拟数据,接口就绪后替换 getList / confirmAdd 中的本地数据逻辑
|
const PERSONS = [
|
{ name: '张伟', sex: '男', age: 68 },
|
{ name: '李娜', sex: '女', age: 55 },
|
{ name: '王强', sex: '男', age: 72 },
|
{ name: '刘敏', sex: '女', age: 61 },
|
{ name: '陈静', sex: '女', age: 66 },
|
{ name: '杨军', sex: '男', age: 59 },
|
{ name: '赵丽', sex: '女', age: 70 },
|
{ name: '黄勇', sex: '男', age: 64 },
|
{ name: '周艳', sex: '女', age: 58 },
|
{ name: '吴刚', sex: '男', age: 75 },
|
];
|
const HEART_DIAGNOSES = ['冠心病', '心力衰竭', '高血压', '心房颤动', '心肌梗死', '心律失常', '心脏瓣膜病', '扩张型心肌病', '肥厚型心肌病', '急性冠脉综合征'];
|
const DEPTS = ['心血管内科', '老年科', '重症医学科', '康复医学科', '心外科', '内分泌科', '神经内科', '呼吸内科'];
|
const WARDS = ['一病区', '二病区', '三病区', '四病区', '五病区'];
|
const DOCTORS = ['王主任', '李医生', '张医生', '刘医生', '陈医生'];
|
const NYHA_CLASSES = ['Ⅰ级', 'Ⅱ级', 'Ⅲ级', 'Ⅳ级'];
|
const FOLLOW_PLANS = ['每周', '每两周', '每月', '每季度'];
|
const SOURCE_LABEL = { archive: '患者档案', hospital: '出院记录', outpatient: '门诊记录', inpatient: '在院记录' };
|
|
export default {
|
name: 'HeartDedicated',
|
data() {
|
return {
|
loading: false,
|
// 专病患者列表(已加入)
|
patientList: [],
|
ids: [],
|
single: true,
|
multiple: true,
|
// 查询条件
|
queryParams: { name: '', drname: '', deptWard: '', nyhaClass: '' },
|
pageNum: 1,
|
pageSize: 10,
|
deptOptions: DEPTS,
|
wardOptions: WARDS,
|
NYHA_CLASSES,
|
// 添加弹窗
|
addVisible: false,
|
activeSource: 'archive',
|
poolKeyword: '',
|
addSelected: [],
|
mockPool: { archive: [], hospital: [], outpatient: [], inpatient: [] },
|
// 详情弹窗
|
detailVisible: false,
|
detailRow: null,
|
};
|
},
|
computed: {
|
filteredList() {
|
const { name, drname, deptWard, nyhaClass } = this.queryParams;
|
return this.patientList.filter((r) => {
|
if (name && !r.name.includes(name)) return false;
|
if (drname && !r.drname.includes(drname)) return false;
|
if (deptWard) {
|
const label = deptWard.split(':')[1];
|
if (label && r.deptname !== label && r.wardname !== label) return false;
|
}
|
if (nyhaClass && r.nyhaClass !== nyhaClass) return false;
|
return true;
|
});
|
},
|
currentPageData() {
|
const start = (this.pageNum - 1) * this.pageSize;
|
return this.filteredList.slice(start, start + this.pageSize);
|
},
|
filteredPool() {
|
const kw = this.poolKeyword.trim();
|
const list = this.mockPool[this.activeSource] || [];
|
if (!kw) return list;
|
return list.filter((r) => r.name.includes(kw) || r.diagname.includes(kw));
|
},
|
},
|
created() {
|
this.buildPool();
|
this.buildInitialList();
|
},
|
methods: {
|
// 生成各来源的模拟患者池
|
buildPool() {
|
const mk = (idx, source, noPrefix) => {
|
const p = PERSONS[idx % PERSONS.length];
|
const isWard = source === 'hospital' || source === 'inpatient';
|
return {
|
id: noPrefix + (idx + 1),
|
name: p.name,
|
sex: p.sex,
|
age: p.age,
|
telcode: '139' + (10000000 + idx * 1111111),
|
deptname: DEPTS[idx % DEPTS.length],
|
wardname: isWard ? WARDS[idx % WARDS.length] : '',
|
drname: DOCTORS[idx % DOCTORS.length],
|
diagname: HEART_DIAGNOSES[idx % HEART_DIAGNOSES.length],
|
sourceDate: this.recentDate(idx * 3 + (source === 'outpatient' ? 2 : 5)),
|
};
|
};
|
this.mockPool = {
|
archive: PERSONS.map((_, i) => mk(i, 'archive', 'da')),
|
hospital: PERSONS.map((_, i) => mk(i, 'hospital', 'zy')),
|
outpatient: PERSONS.map((_, i) => mk(i, 'outpatient', 'mz')),
|
inpatient: PERSONS.map((_, i) => mk(i, 'inpatient', 'zy')),
|
};
|
},
|
// 预置若干已加入的患者,保证首屏有数据
|
buildInitialList() {
|
const take = (source, n) =>
|
this.mockPool[source].slice(0, n).map((row) => this.toDedicated(row, source));
|
this.patientList = [
|
...take('hospital', 3),
|
...take('inpatient', 2),
|
...take('archive', 2),
|
...take('outpatient', 1),
|
];
|
},
|
// 池患者 => 专病患者(补充心脏相关字段)
|
toDedicated(row, source) {
|
const n = Number(String(row.id).replace(/\D/g, '')) || 0;
|
const nyhaClass = NYHA_CLASSES[n % NYHA_CLASSES.length];
|
const heartRate = 58 + (n % 5) * 8;
|
const high = 118 + (n % 4) * 8;
|
const low = 72 + (n % 3) * 4;
|
return {
|
id: row.id,
|
name: row.name,
|
sex: row.sex,
|
age: row.age,
|
telcode: row.telcode,
|
deptname: row.deptname,
|
wardname: row.wardname,
|
drname: row.drname,
|
heartDiagnosis: row.diagname,
|
nyhaClass,
|
heartRate,
|
bloodPressure: `${high}/${low}`,
|
followPlan: FOLLOW_PLANS[n % FOLLOW_PLANS.length],
|
source: SOURCE_LABEL[source] || source,
|
addTime: this.now(),
|
};
|
},
|
nyhaTagType(grade) {
|
const map = { 'Ⅰ级': 'success', 'Ⅱ级': 'primary', 'Ⅲ级': 'warning', 'Ⅳ级': 'danger' };
|
return map[grade] || 'info';
|
},
|
handleQuery() {
|
this.pageNum = 1;
|
},
|
resetQuery() {
|
this.queryParams = { name: '', drname: '', deptWard: '', nyhaClass: '' };
|
this.pageNum = 1;
|
},
|
handleSelectionChange(selection) {
|
this.ids = selection.map((item) => item.id);
|
this.single = selection.length !== 1;
|
this.multiple = !selection.length;
|
},
|
openAddDialog() {
|
this.addSelected = [];
|
this.poolKeyword = '';
|
this.activeSource = 'archive';
|
this.addVisible = true;
|
this.$nextTick(() => this.$refs.addTable && this.$refs.addTable.clearSelection());
|
},
|
onSourceTabChange() {
|
this.addSelected = [];
|
this.$nextTick(() => this.$refs.addTable && this.$refs.addTable.clearSelection());
|
},
|
handlePoolSearch() {
|
// 本地过滤,由 filteredPool 计算属性完成
|
},
|
onAddSelectionChange(selection) {
|
this.addSelected = selection;
|
},
|
confirmAdd() {
|
if (!this.addSelected.length) {
|
this.$modal.msgWarning('请先选择要添加的患者');
|
return;
|
}
|
const existing = new Set(this.patientList.map((r) => r.id));
|
const toAdd = this.addSelected
|
.filter((row) => !existing.has(row.id))
|
.map((row) => this.toDedicated(row, this.activeSource));
|
this.patientList = [...toAdd, ...this.patientList];
|
this.addVisible = false;
|
this.$modal.msgSuccess(`成功添加 ${toAdd.length} 位患者`);
|
},
|
removeRow(row) {
|
this.$modal
|
.confirm(`是否确认将患者"${row.name}"移出心脏专病列表?`)
|
.then(() => {
|
this.patientList = this.patientList.filter((r) => r.id !== row.id);
|
this.$modal.msgSuccess('移除成功');
|
})
|
.catch(() => {});
|
},
|
handleDelete() {
|
this.$modal
|
.confirm(`是否确认删除选中的 ${this.ids.length} 位患者?`)
|
.then(() => {
|
this.patientList = this.patientList.filter((r) => !this.ids.includes(r.id));
|
this.$modal.msgSuccess('删除成功');
|
})
|
.catch(() => {});
|
},
|
openDetail(row) {
|
this.detailRow = row;
|
this.detailVisible = true;
|
},
|
handleExport() {
|
const rows = this.filteredList;
|
if (!rows.length) {
|
this.$modal.msgWarning('暂无数据可导出');
|
return;
|
}
|
const head = ['姓名', '性别', '年龄', '联系电话', '科室', '病区', '主治医生', '心脏诊断', '心功能分级', '心率(次/分)', '血压(mmHg)', '随访计划', '来源', '加入时间'];
|
const keys = ['name', 'sex', 'age', 'telcode', 'deptname', 'wardname', 'drname', 'heartDiagnosis', 'nyhaClass', 'heartRate', 'bloodPressure', 'followPlan', 'source', 'addTime'];
|
const csv = [head.join(',')]
|
.concat(rows.map((r) => keys.map((k) => `"${(r[k] ?? '').toString().replace(/"/g, '""')}"`).join(',')))
|
.join('\n');
|
const blob = new Blob(['' + csv], { type: 'text/csv;charset=utf-8;' });
|
const link = document.createElement('a');
|
link.href = URL.createObjectURL(blob);
|
link.download = `心脏专病患者_${Date.now()}.csv`;
|
link.click();
|
URL.revokeObjectURL(link.href);
|
},
|
recentDate(daysAgo) {
|
const d = new Date();
|
d.setDate(d.getDate() - daysAgo);
|
const mm = String(d.getMonth() + 1).padStart(2, '0');
|
const dd = String(d.getDate()).padStart(2, '0');
|
return `${d.getFullYear()}-${mm}-${dd}`;
|
},
|
now() {
|
const d = new Date();
|
const pad = (x) => String(x).padStart(2, '0');
|
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
},
|
},
|
};
|
</script>
|
|
<style lang="scss" scoped>
|
.query-form { margin-bottom: 4px; }
|
.mb8 { margin-bottom: 8px; }
|
.pagination-wrap { margin-top: 16px; text-align: right; }
|
.pool-tip { font-size: 12px; color: #909399; margin-left: 8px; }
|
.danger-text { color: #f56c6c; }
|
</style>
|