103 增加医生个人报卡管理界面(需求)

This commit is contained in:
2026-04-14 17:23:44 +08:00
parent fe7778e6e0
commit 26e0665eeb
15 changed files with 833 additions and 507 deletions

View File

@@ -334,12 +334,12 @@
<el-col :span="12">
<el-form-item label="病人属于">
<el-select v-model="currentCard.patientBelong" :disabled="drawerMode !== 'edit'" style="width: 100%">
<el-option label="本县区" value="1" />
<el-option label="本市其他县区" value="2" />
<el-option label="本省其他地市" value="3" />
<el-option label="外省" value="4" />
<el-option label="港澳台" value="5" />
<el-option label="外籍" value="6" />
<el-option label="本县区" :value="1" />
<el-option label="本市其他县区" :value="2" />
<el-option label="本省其他地市" :value="3" />
<el-option label="外省" :value="4" />
<el-option label="港澳台" :value="5" />
<el-option label="外籍" :value="6" />
</el-select>
</el-form-item>
</el-col>

View File

@@ -130,12 +130,12 @@
<el-col :span="24" class="form-item full-width">
<span class="form-label required">病人属于</span>
<el-radio-group v-model="form.patientBelong">
<el-radio label="1">本县区</el-radio>
<el-radio label="2">本市其他县区</el-radio>
<el-radio label="3">本省其他地市</el-radio>
<el-radio label="4">外省</el-radio>
<el-radio label="5">港澳台</el-radio>
<el-radio label="6">外籍</el-radio>
<el-radio :label="1">本县区</el-radio>
<el-radio :label="2">本市其他县区</el-radio>
<el-radio :label="3">本省其他地市</el-radio>
<el-radio :label="4">外省</el-radio>
<el-radio :label="5">港澳台</el-radio>
<el-radio :label="6">外籍</el-radio>
</el-radio-group>
</el-col>
</el-row>
@@ -732,7 +732,7 @@ const form = ref({
addressTown: '',
addressVillage: '',
addressHouse: '',
patientBelong: '1',
patientBelong: 1,
occupation: '',
caseClass: '',
onsetDate: '',
@@ -1186,7 +1186,7 @@ function show(diagnosisData) {
addressHouse: diagnosisData?.addressHouse || '', // 门牌号
// 患者类型和职业
patientBelong: '1', // 患者属于(本县区/本市其他县区/本省其他地市/外省/港澳台/外籍)
patientBelong: 1, // 患者属于(本县区/本市其他县区/本省其他地市/外省/港澳台/外籍)
occupation: '', // 职业
caseClass: '', // 病例分类(疑似病例/临床诊断病例/确诊病例/病原携带者/阳性检测)
@@ -1297,7 +1297,7 @@ async function buildSubmitData() {
addressTown: formData.addressTown || '',
addressVillage: formData.addressVillage || '',
addressHouse: formData.addressHouse || '',
patientBelong: patientBelongMap[formData.patientBelong] || 1,
patientBelong: formData.patientBelong || 1,
occupation: formData.occupation || null,
diseaseCode: diseaseCode || null,
diseaseType: diseaseType || null,

View File

@@ -31,12 +31,12 @@
</el-col>
<el-col :span="8">
<el-form-item :label="'家长姓名' + (isChildPatient ? '' : '')" :prop="isChildPatient ? 'parentName' : ''" :required="isChildPatient">
<el-input v-model="form.parentName" placeholder="≤14岁必填" />
<el-input v-model="form.parentName" placeholder="≤14岁必填" :disabled="isReadOnly" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="身份证号" prop="idNo" required>
<el-input v-model="form.idNo" placeholder="请输入身份证号" maxlength="18" @change="handleIdCardChange" />
<el-input v-model="form.idNo" placeholder="请输入身份证号" maxlength="18" :disabled="isReadOnly" @change="isReadOnly ? null : handleIdCardChange($event)" />
</el-form-item>
</el-col>
</el-row>
@@ -45,7 +45,7 @@
<el-row :gutter="16" class="form-row">
<el-col :span="6">
<el-form-item label="性别" prop="sex" required>
<el-radio-group v-model="form.sex">
<el-radio-group v-model="form.sex" :disabled="isReadOnly">
<el-radio label="男"></el-radio>
<el-radio label="女"></el-radio>
<el-radio label="未知">未知</el-radio>
@@ -55,11 +55,11 @@
<el-col :span="10">
<el-form-item label="出生日期" required>
<div class="date-inputs">
<el-input v-model="form.birthYear" placeholder="年" maxlength="4" @change="calculateAge" style="width: 80px" />
<el-input v-model="form.birthYear" placeholder="年" maxlength="4" :disabled="isReadOnly" @change="isReadOnly ? null : calculateAge()" style="width: 80px" />
<span></span>
<el-input v-model="form.birthMonth" placeholder="月" maxlength="2" @change="calculateAge" style="width: 60px" />
<el-input v-model="form.birthMonth" placeholder="月" maxlength="2" :disabled="isReadOnly" @change="isReadOnly ? null : calculateAge()" style="width: 60px" />
<span></span>
<el-input v-model="form.birthDay" placeholder="日" maxlength="2" @change="calculateAge" style="width: 60px" />
<el-input v-model="form.birthDay" placeholder="日" maxlength="2" :disabled="isReadOnly" @change="isReadOnly ? null : calculateAge()" style="width: 60px" />
<span></span>
</div>
</el-form-item>
@@ -67,8 +67,8 @@
<el-col :span="8">
<el-form-item label="或 实足年龄">
<div class="age-inputs">
<el-input v-model="form.age" placeholder="年龄" style="width: 100px" />
<el-select v-model="form.ageUnit" style="width: 80px">
<el-input v-model="form.age" placeholder="年龄" style="width: 100px" :disabled="isReadOnly" />
<el-select v-model="form.ageUnit" style="width: 80px" :disabled="isReadOnly">
<el-option label="岁" value="岁" />
<el-option label="月" value="月" />
<el-option label="天" value="天" />
@@ -82,7 +82,7 @@
<el-row :gutter="16" class="form-row">
<el-col :span="24">
<el-form-item label="工作单位(学校)">
<el-input v-model="form.workplace" placeholder="请输入工作单位" />
<el-input v-model="form.workplace" placeholder="请输入工作单位" :disabled="isReadOnly" />
</el-form-item>
</el-col>
</el-row>
@@ -91,12 +91,12 @@
<el-row :gutter="16" class="form-row">
<el-col :span="12">
<el-form-item label="联系电话" prop="phone" required>
<el-input v-model="form.phone" placeholder="请输入联系电话" maxlength="11" />
<el-input v-model="form.phone" placeholder="请输入联系电话" maxlength="11" :disabled="isReadOnly" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="紧急联系人电话" prop="contactPhone" required>
<el-input v-model="form.contactPhone" placeholder="请输入紧急联系人电话" maxlength="11" />
<el-input v-model="form.contactPhone" placeholder="请输入紧急联系人电话" maxlength="11" :disabled="isReadOnly" />
</el-form-item>
</el-col>
</el-row>
@@ -112,6 +112,7 @@
placeholder="请选择省/市/区县/街道"
clearable
style="width: 100%"
:disabled="isReadOnly"
@change="handleAddressChange"
/>
</el-form-item>
@@ -119,10 +120,10 @@
</el-row>
<el-row :gutter="16" class="form-row">
<el-col :span="12">
<el-input v-model="form.addressVillage" placeholder="村(居)" />
<el-input v-model="form.addressVillage" placeholder="村(居)" :disabled="isReadOnly" />
</el-col>
<el-col :span="12">
<el-input v-model="form.addressHouse" placeholder="门牌号" />
<el-input v-model="form.addressHouse" placeholder="门牌号" :disabled="isReadOnly" />
</el-col>
</el-row>
@@ -130,13 +131,13 @@
<el-row :gutter="16" class="form-row">
<el-col :span="24">
<el-form-item label="病人属于" prop="patientBelong" required>
<el-radio-group v-model="form.patientBelong">
<el-radio label="1">本县区</el-radio>
<el-radio label="2">本市其他县区</el-radio>
<el-radio label="3">本省其他地市</el-radio>
<el-radio label="4">外省</el-radio>
<el-radio label="5">港澳台</el-radio>
<el-radio label="6">外籍</el-radio>
<el-radio-group v-model="form.patientBelong" :disabled="isReadOnly">
<el-radio :label="1">本县区</el-radio>
<el-radio :label="2">本市其他县区</el-radio>
<el-radio :label="3">本省其他地市</el-radio>
<el-radio :label="4">外省</el-radio>
<el-radio :label="5">港澳台</el-radio>
<el-radio :label="6">外籍</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
@@ -146,14 +147,14 @@
<el-row :gutter="16" class="form-row">
<el-col :span="12">
<el-form-item label="职业" prop="occupation" required>
<el-select v-model="form.occupation" placeholder="请选择职业" style="width: 100%">
<el-select v-model="form.occupation" placeholder="请选择职业" style="width: 100%" :disabled="isReadOnly">
<el-option v-for="item in occupationList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="病例分类" prop="caseClass" required>
<el-radio-group v-model="form.caseClass">
<el-radio-group v-model="form.caseClass" :disabled="isReadOnly">
<el-radio label="1">疑似病例</el-radio>
<el-radio label="2">临床诊断病例</el-radio>
<el-radio label="3">确诊病例</el-radio>
@@ -175,6 +176,7 @@
style="width: 100%"
value-format="YYYY-MM-DD"
format="YYYY-MM-DD"
:disabled="isReadOnly"
/>
</el-form-item>
</el-col>
@@ -187,6 +189,7 @@
style="width: 100%"
value-format="YYYY-MM-DD"
format="YYYY-MM-DD"
:disabled="isReadOnly"
/>
</el-form-item>
</el-col>
@@ -199,6 +202,7 @@
style="width: 100%"
value-format="YYYY-MM-DD"
format="YYYY-MM-DD"
:disabled="isReadOnly"
/>
</el-form-item>
</el-col>
@@ -215,11 +219,13 @@
<div class="disease-list">
<el-checkbox
:model-value="form.selectedClassA === '0101'"
@update:model-value="(checked) => handleClassACheckbox('0101', checked)"
:disabled="isReadOnly"
@update:model-value="(checked) => isReadOnly ? null : handleClassACheckbox('0101', checked)"
>鼠疫</el-checkbox>
<el-checkbox
:model-value="form.selectedClassA === '0102'"
@update:model-value="(checked) => handleClassACheckbox('0102', checked)"
:disabled="isReadOnly"
@update:model-value="(checked) => isReadOnly ? null : handleClassACheckbox('0102', checked)"
>霍乱</el-checkbox>
</div>
</div>
@@ -232,7 +238,8 @@
v-for="disease in classBDiseases"
:key="disease.code"
:model-value="form.selectedClassB === disease.code"
@update:model-value="(checked) => handleClassBCheckbox(disease.code, checked)"
:disabled="isReadOnly"
@update:model-value="(checked) => isReadOnly ? null : handleClassBCheckbox(disease.code, checked)"
>{{ disease.name }}</el-checkbox>
</div>
</div>
@@ -245,7 +252,8 @@
v-for="disease in classCDiseases"
:key="disease.code"
:model-value="form.selectedClassC === disease.code"
@update:model-value="(checked) => handleClassCCheckbox(disease.code, checked)"
:disabled="isReadOnly"
@update:model-value="(checked) => isReadOnly ? null : handleClassCCheckbox(disease.code, checked)"
>{{ disease.name }}</el-checkbox>
</div>
</div>
@@ -254,12 +262,12 @@
<el-row :gutter="16" class="form-row">
<el-col :span="showSubtypeSelect ? 16 : 24">
<el-form-item label="其他法定管理以及重点监测传染病">
<el-input v-model="form.otherDisease" placeholder="手动输入非列表疾病" />
<el-input v-model="form.otherDisease" placeholder="手动输入非列表疾病" :disabled="isReadOnly" />
</el-form-item>
</el-col>
<el-col v-if="showSubtypeSelect" :span="8">
<el-form-item label="分型" prop="diseaseType" :required="showSubtypeSelect">
<el-select v-model="form.diseaseType" placeholder="请选择分型" style="width: 100%">
<el-select v-model="form.diseaseType" placeholder="请选择分型" style="width: 100%" :disabled="isReadOnly">
<el-option
v-for="option in currentSubtypeOptions"
:key="option.value"
@@ -284,7 +292,7 @@
</el-col>
<el-col :span="8">
<el-form-item label="联系电话">
<el-input v-model="form.reportOrgPhone" placeholder="请输入联系电话" />
<el-input v-model="form.reportOrgPhone" placeholder="请输入联系电话" :disabled="isReadOnly" />
</el-form-item>
</el-col>
<el-col :span="8">
@@ -304,17 +312,18 @@
style="width: 100%"
value-format="YYYY-MM-DD"
format="YYYY-MM-DD"
:disabled="isReadOnly"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="订正病名">
<el-input v-model="form.correctName" placeholder="请输入订正病名" />
<el-input v-model="form.correctName" placeholder="请输入订正病名" :disabled="isReadOnly" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="退卡原因">
<el-input v-model="form.withdrawReason" placeholder="请输入退卡原因" />
<el-input v-model="form.withdrawReason" placeholder="请输入退卡原因" :disabled="isReadOnly" />
</el-form-item>
</el-col>
</el-row>
@@ -322,15 +331,15 @@
<el-row :gutter="16" class="form-row">
<el-col :span="24">
<el-form-item label="备注">
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请输入备注" />
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请输入备注" :disabled="isReadOnly" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<!-- 操作按钮区 -->
<div class="action-buttons">
<!-- 操作按钮区create/edit 模式下显示) -->
<div v-if="mode !== 'view'" class="action-buttons">
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">保 存</el-button>
<el-button @click="handleReset"> </el-button>
</div>
@@ -347,6 +356,9 @@ import { useDict } from '@/utils/dict';
const { proxy } = getCurrentInstance();
const userStore = useUserStore();
// 是否只读模式
const isReadOnly = computed(() => props.mode === 'view');
// 获取职业字典数据
const { prfs: occupationList } = useDict('prfs');
@@ -418,9 +430,19 @@ const props = defineProps({
type: String,
default: '',
},
// 'create' | 'view' | 'edit'
mode: {
type: String,
default: 'create',
},
// 已有报卡数据view/edit 模式下使用)
cardData: {
type: Object,
default: null,
},
});
const emit = defineEmits(['saved']);
const emit = defineEmits(['saved', 'submit-edit']);
// 地址级联选择器配置
const addressOptions = ref(pcas);
@@ -453,7 +475,7 @@ const form = ref({
addressTown: '',
addressVillage: '',
addressHouse: '',
patientBelong: '1',
patientBelong: 1,
occupation: '',
caseClass: '',
onsetDate: '',
@@ -567,19 +589,27 @@ const isChildPatient = computed(() => {
// 监听疾病选择变化,自动清空分型选择
watch(() => [form.value.selectedClassA, form.value.selectedClassB, form.value.selectedClassC], (newVal, oldVal) => {
if (JSON.stringify(newVal) !== JSON.stringify(oldVal)) {
// 在view/edit模式下初始化时不清空分型避免数据加载时被错误清空
if (JSON.stringify(newVal) !== JSON.stringify(oldVal) && props.mode !== 'view' && props.mode !== 'edit') {
form.value.diseaseType = '';
}
updateSelectedDiseases();
}, { deep: true });
// 监听患者信息变化,自动填充表单
// 监听患者信息变化,自动填充表单(仅 create 模式)
watch(() => props.patientInfo, (newVal) => {
if (newVal && Object.keys(newVal).length > 0) {
if (props.mode === 'create' && newVal && Object.keys(newVal).length > 0) {
initForm();
}
}, { deep: true, immediate: true });
// 监听 cardData 变化,在 view/edit 模式下初始化
watch(() => props.cardData, (newVal) => {
if ((props.mode === 'view' || props.mode === 'edit') && newVal) {
initFormFromCard(newVal);
}
}, { immediate: true });
// 监听Tab切换重新初始化
watch(() => props.activeTab, (newVal) => {
if (newVal === 'infectiousReport' && props.patientInfo && Object.keys(props.patientInfo).length > 0) {
@@ -808,7 +838,7 @@ async function initForm() {
addressTown: patientInfo.addressStreet || '',
addressVillage: '',
addressHouse: '',
patientBelong: '1',
patientBelong: 1,
occupation: '',
caseClass: '',
onsetDate: getCurrentDate(),
@@ -893,7 +923,7 @@ function buildSubmitData() {
addressTown: formData.addressTown || '',
addressVillage: formData.addressVillage || '',
addressHouse: formData.addressHouse || '',
patientBelong: parseInt(formData.patientBelong) || 1,
patientBelong: formData.patientBelong || 1,
occupation: formData.occupation || null,
diseaseCode: diseaseCode || null,
diseaseType: diseaseType || null,
@@ -1015,15 +1045,20 @@ async function handleSubmit() {
const submitData = buildSubmitData();
const res = await saveInfectiousDiseaseReport(submitData);
if (res.code === 200) {
proxy.$modal.msgSuccess('传染病报告卡保存成功');
emit('saved');
// 重新初始化表单,获取新的卡片编号
initForm();
if (props.mode === 'edit') {
// 编辑模式:通过事件通知父组件处理保存
emit('submit-edit', submitData);
} else {
proxy.$modal.msgError(res.msg || '保存失败');
// 新建模式
const res = await saveInfectiousDiseaseReport(submitData);
if (res.code === 200) {
proxy.$modal.msgSuccess('传染病报告卡保存成功');
emit('saved');
// 重新初始化表单,获取新的卡片编号
initForm();
} else {
proxy.$modal.msgError(res.msg || '保存失败');
}
}
} catch (err) {
console.error('传染病报告卡保存失败:', err);
@@ -1065,7 +1100,7 @@ function handleReset() {
form.value.contactPhone = '';
form.value.occupation = '';
form.value.caseClass = '';
form.value.patientBelong = '1';
form.value.patientBelong = 1;
form.value.otherDisease = '';
form.value.correctName = '';
form.value.withdrawReason = '';
@@ -1075,22 +1110,108 @@ function handleReset() {
updateSelectedDiseases();
}
// 从已有报卡数据初始化表单view/edit 模式)
function initFormFromCard(card) {
if (!card) return;
const ageUnitReverseMap = { '1': '岁', '2': '月', '3': '天' };
const sexMap = { '1': '男', '2': '女', '0': '未知' };
// 拆解出生日期
let birthYear = '', birthMonth = '', birthDay = '';
if (card.birthday) {
const parts = card.birthday.split('-');
birthYear = parts[0] || '';
birthMonth = parts[1] || '';
birthDay = parts[2] || '';
}
// 还原疾病分类选中状态
let selectedClassA = '', selectedClassB = '', selectedClassC = '';
if (card.diseaseCode) {
if (card.diseaseCode.startsWith('01')) selectedClassA = card.diseaseCode;
else if (card.diseaseCode.startsWith('02')) selectedClassB = card.diseaseCode;
else if (card.diseaseCode.startsWith('03')) selectedClassC = card.diseaseCode;
}
form.value = {
cardNo: card.cardNo || '',
patName: card.patName || '',
parentName: card.parentName || '',
idNo: card.idNo || '',
sex: sexMap[card.sex] || card.sex || '男',
birthYear,
birthMonth,
birthDay,
age: card.age != null ? String(card.age) : '',
ageUnit: ageUnitReverseMap[card.ageUnit] || '岁',
workplace: card.workplace || '',
phone: card.phone || '',
contactPhone: card.contactPhone || '',
addressProv: card.addressProv || '',
addressCity: card.addressCity || '',
addressCounty: card.addressCounty || '',
addressTown: card.addressTown || '',
addressVillage: card.addressVillage || '',
addressHouse: card.addressHouse || '',
patientBelong: card.patientBelong || 1,
occupation: card.occupation || '',
caseClass: card.caseClass != null ? String(card.caseClass) : '',
onsetDate: card.onsetDate || '',
diagDate: card.diagDate ? card.diagDate.substring(0, 10) : '',
deathDate: card.deathDate || '',
selectedDiseases: card.diseaseCode && card.diseaseCode !== 'OTHER' ? [card.diseaseCode] : [],
selectedClassA,
selectedClassB,
selectedClassC,
otherDisease: card.diseaseCode === 'OTHER' ? (card.otherDisease || '') : (card.otherDisease || ''),
diseaseType: card.diseaseType || '',
reportOrg: card.reportOrg || '',
reportOrgPhone: card.reportOrgPhone || '',
reportDoc: card.reportDoc || '',
reportDate: card.reportDate || '',
correctName: card.correctName || '',
withdrawReason: card.withdrawReason || '',
remark: card.remark || '',
encounterId: card.visitId || '',
patientId: card.patId || '',
diagnosisId: card.diagId || '',
};
// 设置地址级联选择器
const provName = card.addressProv || '';
const cityName = card.addressCity || '';
const countyName = card.addressCounty || '';
const townName = card.addressTown || '';
let names = [];
if (municipalities.includes(provName)) {
names = [provName, countyName, townName].filter(n => n);
} else {
names = [provName, cityName, countyName, townName].filter(n => n);
}
addressCodes.value = findCodesByNames(names);
}
// 组件挂载时初始化
onMounted(() => {
if (props.patientInfo && Object.keys(props.patientInfo).length > 0) {
if ((props.mode === 'view' || props.mode === 'edit') && props.cardData) {
initFormFromCard(props.cardData);
} else if (props.mode === 'create' && props.patientInfo && Object.keys(props.patientInfo).length > 0) {
initForm();
}
});
// 暴露方法供父组件调用
defineExpose({
initForm
initForm,
initFormFromCard
});
</script>
<style scoped>
.infectious-report-container {
height: 100%;
height: auto;
min-height: 100%;
display: flex;
flex-direction: column;
background: #fff;
@@ -1130,8 +1251,7 @@ defineExpose({
/* 表单区域样式 */
.report-form {
flex: 1;
overflow-y: auto;
overflow-y: visible;
padding-right: 8px;
}

View File

@@ -1,31 +1,33 @@
<template>
<div class="my-card-management-container">
<!-- 顶部标题 -->
<div class="page-header">
<h2>我的报卡</h2>
<h2 class="page-title">我的报卡</h2>
</div>
<!-- 统计卡片区 -->
<div class="statistics-section">
<div class="stat-card total">
<div class="stat-icon">
<el-icon><Document /></el-icon>
<div class="stat-card">
<div class="stat-icon-wrap stat-icon-purple">
<el-icon class="stat-icon-inner"><DataAnalysis /></el-icon>
</div>
<div class="stat-content">
<div class="stat-value">{{ statistics.totalCount || 0 }}</div>
<div class="stat-label">总报卡数</div>
</div>
</div>
<div class="stat-card pending">
<div class="stat-icon">
<el-icon><Clock /></el-icon>
<div class="stat-card">
<div class="stat-icon-wrap stat-icon-red">
<el-icon class="stat-icon-inner"><Warning /></el-icon>
</div>
<div class="stat-content">
<div class="stat-value">{{ statistics.pendingFailedCount || 0 }}</div>
<div class="stat-label">处理/失败</div>
<div class="stat-label">提交</div>
</div>
</div>
<div class="stat-card success">
<div class="stat-icon">
<el-icon><CircleCheck /></el-icon>
<div class="stat-card">
<div class="stat-icon-wrap stat-icon-green">
<el-icon class="stat-icon-inner"><CircleCheck /></el-icon>
</div>
<div class="stat-content">
<div class="stat-value">{{ statistics.reportedCount || 0 }}</div>
@@ -34,21 +36,25 @@
</div>
</div>
<!-- 高级筛选区 -->
<div class="filter-section">
<el-form :model="queryParams" :inline="true">
<el-form-item label="日期范围">
<div class="filter-title">高级筛选</div>
<div class="filter-row">
<div class="filter-item">
<span class="filter-label">日期范围</span>
<el-date-picker
v-model="queryParams.dateRange"
type="daterange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
start-placeholder="//"
end-placeholder="//"
value-format="YYYY-MM-DD"
style="width: 240px"
class="filter-date-picker"
/>
</el-form-item>
<el-form-item label="状态">
<el-select v-model="queryParams.status" placeholder="全部状态" clearable style="width: 140px">
</div>
<div class="filter-item">
<span class="filter-label">状态</span>
<el-select v-model="queryParams.status" placeholder="全部状态" clearable class="filter-select">
<el-option label="全部状态" value="" />
<el-option label="待提交" value="0" />
<el-option label="已提交" value="1" />
@@ -57,101 +63,65 @@
<el-option label="失败" value="4" />
<el-option label="作废" value="6" />
</el-select>
</el-form-item>
<el-form-item label="关键词">
</div>
<div class="filter-item">
<span class="filter-label">报卡名称</span>
<el-input
v-model="queryParams.keyword"
placeholder="患者姓名/报卡名称"
placeholder="输入报卡名称..."
clearable
style="width: 180px"
class="filter-input"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">
<el-icon><Search /></el-icon>
</div>
<div class="filter-actions">
<el-button class="btn-apply" type="primary" @click="handleQuery">
<el-icon><Check /></el-icon>
应用筛选
</el-button>
<el-button @click="handleReset">
<el-icon><Refresh /></el-icon>
<el-button class="btn-reset" @click="handleReset">
<el-icon><RefreshRight /></el-icon>
重置条件
</el-button>
</el-form-item>
</el-form>
</div>
<div class="action-section">
<el-checkbox v-model="isAllSelected" @change="handleSelectAll">全选</el-checkbox>
<el-button type="primary" :disabled="selectedRows.length === 0" @click="handleBatchSubmit">
批量提交
</el-button>
<el-button type="danger" :disabled="selectedRows.length === 0" @click="handleBatchDelete">
批量删除
</el-button>
</div>
</div>
</div>
<!-- 数据表格区 -->
<div class="table-section">
<el-table
ref="tableRef"
v-loading="loading"
:data="cardList"
@selection-change="handleSelectionChange"
border
stripe
:height="tableHeight"
virtualized
:virtualized-row-height="48"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="卡片ID" prop="cardNo" min-width="150" />
<el-table-column label="患者姓名" prop="patName" width="100" />
<el-table-column label="身份证号" prop="idNo" min-width="180" />
<el-table-column label="联系电话" prop="phone" width="130" />
<el-table-column label="就诊卡号" prop="visitCardNo" width="130" />
<el-table-column label="报卡名称" prop="cardName" min-width="200" />
<el-table-column label="提交时间" prop="submitTime" width="160" align="center" />
<el-table-column label="状态" prop="status" width="100" align="center">
<el-table-column type="selection" width="50" align="center" />
<el-table-column label="卡片ID" prop="cardNo" min-width="155" />
<el-table-column label="患者姓名" prop="patName" width="90" />
<el-table-column label="身份证号" prop="idNo" min-width="170" />
<el-table-column label="联系电话" prop="phone" width="120" />
<el-table-column label="就诊卡号" prop="visitCardNo" width="110" />
<el-table-column label="报卡名称" prop="cardName" min-width="120" />
<el-table-column label="提交时间" prop="submitTime" width="110" align="center" />
<el-table-column label="状态" prop="status" width="80" align="center">
<template #default="{ row }">
<el-tag :type="getStatusType(row.status)" size="small">
<span :class="['status-tag', 'status-' + row.status]">
{{ getStatusName(row.status) }}
</el-tag>
</span>
</template>
</el-table-column>
<el-table-column label="操作" width="200" align="center" fixed="right">
<el-table-column label="操作" width="160" align="center" fixed="right">
<template #default="{ row }">
<el-button type="primary" link size="small" @click="handleView(row)">查看</el-button>
<el-button
v-if="row.status === '0'"
type="primary"
link
size="small"
@click="handleEdit(row)"
>
编辑
</el-button>
<el-button
v-if="row.status === '0'"
type="success"
link
size="small"
@click="handleSubmit(row)"
>
提交
</el-button>
<el-button
v-if="row.status === '1'"
type="warning"
link
size="small"
@click="handleWithdraw(row)"
>
撤回
</el-button>
<el-button
v-if="row.status === '3'"
type="info"
link
size="small"
@click="handleExport(row)"
>
导出
</el-button>
<el-button v-if="row.status == 0" type="primary" link size="small" @click="handleEdit(row)">编辑</el-button>
<el-button v-if="row.status == 0" type="success" link size="small" @click="handleSubmit(row)">提交</el-button>
<el-button v-if="row.status == 1" type="warning" link size="small" @click="handleWithdraw(row)">撤回</el-button>
<el-button v-if="row.status == 3" type="primary" link size="small" @click="handleExport(row)">导出</el-button>
</template>
</el-table-column>
</el-table>
@@ -169,149 +139,43 @@
</div>
</div>
<!-- 底部批量操作栏 -->
<div class="bottom-action-bar">
<div class="bottom-action-left">
<el-checkbox v-model="isAllSelected" @change="handleSelectAll">全选</el-checkbox>
<span class="selected-info">已选择 {{ selectedRows.length }} 个项目</span>
</div>
<div class="bottom-action-right">
<el-button class="btn-batch-delete" :disabled="selectedRows.length === 0" @click="handleBatchDelete">批量删除</el-button>
<el-button class="btn-batch-submit" type="primary" :disabled="selectedRows.length === 0" @click="handleBatchSubmit">批量提交</el-button>
</div>
</div>
<el-dialog
v-model="detailVisible"
:title="detailMode === 'view' ? '报卡详情' : '编辑报卡'"
width="800px"
width="900px"
destroy-on-close
class="card-detail-dialog"
>
<el-descriptions v-if="detailMode === 'view'" :column="2" border>
<el-descriptions-item label="卡片编号">{{ currentCard.cardNo }}</el-descriptions-item>
<el-descriptions-item label="状态">
<el-tag :type="getStatusType(currentCard.status)">{{ getStatusName(currentCard.status) }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="患者姓名">{{ currentCard.patName }}</el-descriptions-item>
<el-descriptions-item label="身份证号">{{ currentCard.idNo }}</el-descriptions-item>
<el-descriptions-item label="联系电话">{{ currentCard.phone }}</el-descriptions-item>
<el-descriptions-item label="性别">{{ currentCard.sex === '1' ? '男' : currentCard.sex === '2' ? '女' : '未知' }}</el-descriptions-item>
<el-descriptions-item label="年龄">{{ currentCard.age }}{{ getAgeUnit(currentCard.ageUnit) }}</el-descriptions-item>
<el-descriptions-item label="疾病名称">{{ currentCard.diseaseName }}</el-descriptions-item>
<el-descriptions-item label="发病日期">{{ currentCard.onsetDate }}</el-descriptions-item>
<el-descriptions-item label="诊断日期">{{ currentCard.diagDate }}</el-descriptions-item>
<el-descriptions-item label="报告单位">{{ currentCard.reportOrg }}</el-descriptions-item>
<el-descriptions-item label="报告医生">{{ currentCard.reportDoc }}</el-descriptions-item>
<el-descriptions-item label="填卡日期">{{ currentCard.reportDate }}</el-descriptions-item>
<el-descriptions-item label="现住址" :span="2">
{{ currentCard.addressProv }}{{ currentCard.addressCity }}{{ currentCard.addressCounty }}{{ currentCard.addressTown }}{{ currentCard.addressVillage }}{{ currentCard.addressHouse }}
</el-descriptions-item>
</el-descriptions>
<el-form v-else ref="editFormRef" :model="editForm" :rules="editFormRules" label-width="100px">
<!-- 患者基本信息 -->
<el-divider content-position="left">患者基本信息</el-divider>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="患者姓名" prop="patName">
<el-input v-model="editForm.patName" disabled />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="身份证号" prop="idNo">
<el-input v-model="editForm.idNo" disabled />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="性别" prop="sex">
<el-input :value="editForm.sex === '1' ? '男' : editForm.sex === '2' ? '女' : '未知'" disabled />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="年龄" prop="age">
<el-input :value="`${editForm.age}${getAgeUnit(editForm.ageUnit)}`" disabled />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="联系电话" prop="phone" required>
<el-input v-model="editForm.phone" />
</el-form-item>
</el-col>
</el-row>
<!-- 临床信息 -->
<el-divider content-position="left">临床信息</el-divider>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="疾病名称" prop="diseaseName">
<el-input v-model="editForm.diseaseName" disabled />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="发病日期" prop="onsetDate">
<el-date-picker v-model="editForm.onsetDate" type="date" value-format="YYYY-MM-DD" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="诊断日期" prop="diagDate" required>
<el-date-picker v-model="editForm.diagDate" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="传染病类别" prop="diseaseType">
<el-select v-model="editForm.diseaseType" placeholder="请选择传染病类别" style="width: 100%">
<el-option label="甲类传染病" value="A" />
<el-option label="乙类传染病" value="B" />
<el-option label="丙类传染病" value="C" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<!-- 报告信息 -->
<el-divider content-position="left">报告信息</el-divider>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="报告单位" prop="reportOrg">
<el-input v-model="editForm.reportOrg" disabled />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="报告医生" prop="reportDoc">
<el-input v-model="editForm.reportDoc" disabled />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="填卡日期" prop="reportDate">
<el-input v-model="editForm.reportDate" disabled />
</el-form-item>
</el-col>
</el-row>
<!-- 现住址 -->
<el-divider content-position="left">现住址</el-divider>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="省" prop="addressProv">
<el-input v-model="editForm.addressProv" placeholder="请输入省份" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="市" prop="addressCity">
<el-input v-model="editForm.addressCity" placeholder="请输入城市" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="区县" prop="addressCounty">
<el-input v-model="editForm.addressCounty" placeholder="请输入区县" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="详细地址" prop="addressHouse">
<el-input v-model="editForm.addressHouse" type="textarea" placeholder="请输入详细地址" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<InfectiousReport
:mode=" detailMode"
:card-data="currentCard"
@submit-edit="handleSaveEdit"
style="max-height: 70vh; overflow-y: auto;"
/>
<template #footer>
<el-button @click="detailVisible = false">取消</el-button>
<el-button v-if="detailMode === 'edit'" type="primary" @click="handleSaveEdit">保存</el-button>
<el-button @click="detailVisible = false">关闭</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
import { ref, reactive, onMounted, onActivated, onBeforeUnmount, nextTick, computed } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { Document, Clock, CircleCheck, Search, Refresh } from '@element-plus/icons-vue';
import { DataAnalysis, Warning, CircleCheck, Check, RefreshRight } from '@element-plus/icons-vue';
import InfectiousReport from '../components/infectiousReport/index.vue';
import {
getDoctorCardStatistics,
getDoctorCardList,
@@ -329,6 +193,7 @@ const cardList = ref([]);
const total = ref(0);
const selectedRows = ref([]);
const isAllSelected = ref(false);
const tableRef = ref(null);
const statistics = ref({
totalCount: 0,
@@ -347,28 +212,25 @@ const queryParams = reactive({
const detailVisible = ref(false);
const detailMode = ref('view');
const currentCard = ref({});
const editForm = reactive({});
const editFormRef = ref(null);
// 编辑表单验证规则
const editFormRules = {
phone: [
{ required: true, message: '请输入联系电话', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur' }
],
diagDate: [
{ required: true, message: '请选择诊断日期', trigger: 'change' }
]
};
// 计算表格高度:根据视口高度动态调整
const tableHeight = computed(() => {
// 视口高度 - 顶部导航(约60px) - 标题(40px) - 统计卡片(120px) - 筛选区(100px)
// - 分页(60px) - 底部操作栏(60px) - 上下边距(80px) - 额外缓冲(20px)
return window.innerHeight - 540;
});
// 监听窗口大小变化,确保表格高度自适应
let resizeObserver = null;
const statusMap = {
'0': { name: '待提交', type: 'warning' },
'1': { name: '已提交', type: 'primary' },
'2': { name: '已审核', type: 'success' },
'3': { name: '已上报', type: 'success' },
'4': { name: '失败', type: 'danger' },
'5': { name: '退回', type: 'danger' },
'6': { name: '作废', type: 'info' },
0: { name: '待提交', type: 'warning' },
1: { name: '已提交', type: 'primary' },
2: { name: '已审核', type: 'success' },
3: { name: '已上报', type: 'success' },
4: { name: '失败', type: 'danger' },
5: { name: '退回', type: 'danger' },
6: { name: '作废', type: 'info' },
};
const ageUnitMap = {
@@ -454,11 +316,11 @@ function handleSelectionChange(selection) {
}
function handleSelectAll(val) {
if (val) {
selectedRows.value = [...cardList.value];
} else {
selectedRows.value = [];
}
nextTick(() => {
if (tableRef.value) {
tableRef.value.toggleAllSelection();
}
});
}
async function handleView(row) {
@@ -478,7 +340,7 @@ async function handleEdit(row) {
try {
const res = await getCardDetail(row.cardNo);
if (res.code === 200) {
Object.assign(editForm, res.data || {});
currentCard.value = res.data || {};
detailMode.value = 'edit';
detailVisible.value = true;
}
@@ -487,26 +349,33 @@ async function handleEdit(row) {
}
}
async function handleSaveEdit() {
// 验证表单
try {
await editFormRef.value.validate();
} catch (error) {
ElMessage.error('表单验证失败,请检查输入');
return;
}
async function handleSaveEdit(submitData) {
// submitData 来自 InfectiousReport 组件的 submit-edit 事件
try {
const updateData = {
cardNo: editForm.cardNo,
phone: editForm.phone,
onsetDate: editForm.onsetDate,
diagDate: editForm.diagDate,
diseaseType: editForm.diseaseType,
addressProv: editForm.addressProv,
addressCity: editForm.addressCity,
addressCounty: editForm.addressCounty,
addressHouse: editForm.addressHouse,
cardNo: submitData.cardNo,
phone: submitData.phone,
contactPhone: submitData.contactPhone,
onsetDate: submitData.onsetDate,
diagDate: submitData.diagDate,
diseaseCode: submitData.diseaseCode,
diseaseType: submitData.diseaseType,
otherDisease: submitData.otherDisease,
caseClass: submitData.caseClass,
occupation: submitData.occupation,
patientBelong: submitData.patientBelong,
addressProv: submitData.addressProv,
addressCity: submitData.addressCity,
addressCounty: submitData.addressCounty,
addressTown: submitData.addressTown,
addressVillage: submitData.addressVillage,
addressHouse: submitData.addressHouse,
workplace: submitData.workplace,
parentName: submitData.parentName,
deathDate: submitData.deathDate,
correctName: submitData.correctName,
withdrawReason: submitData.withdrawReason,
remark: submitData.remark,
};
const res = await updateDoctorCard(updateData);
@@ -567,7 +436,7 @@ async function handleWithdraw(row) {
}
async function handleBatchSubmit() {
const validRows = selectedRows.value.filter(row => row.status === '0');
const validRows = selectedRows.value.filter(row => row.status == 0);
if (validRows.length === 0) {
ElMessage.warning('只能提交待提交状态的报卡');
return;
@@ -595,7 +464,7 @@ async function handleBatchSubmit() {
}
async function handleBatchDelete() {
const validRows = selectedRows.value.filter(row => row.status === '0');
const validRows = selectedRows.value.filter(row => row.status == 0);
if (validRows.length === 0) {
ElMessage.warning('只能删除待提交状态的报卡');
return;
@@ -638,9 +507,35 @@ async function handleExport(row) {
}
onMounted(() => {
console.log('页面首次加载 - onMounted');
getStatistics();
getList();
// 添加窗口大小变化监听
window.addEventListener('resize', handleResize);
});
// 处理 keep-alive 缓存场景:从其他页面返回时重新加载数据
onActivated(() => {
console.log('页面被激活 - onActivated');
getStatistics();
getList();
});
// 组件卸载时移除事件监听
onBeforeUnmount(() => {
window.removeEventListener('resize', handleResize);
});
// 窗口大小变化处理函数
function handleResize() {
// 触发计算属性重新计算
nextTick(() => {
if (tableRef.value) {
tableRef.value.doLayout();
}
});
}
</script>
<style scoped>
@@ -648,109 +543,274 @@ onMounted(() => {
padding: 20px;
background-color: #f5f7fa;
min-height: calc(100vh - 84px);
display: flex;
flex-direction: column;
gap: 16px;
}
/* 页面标题 */
.page-header {
margin-bottom: 20px;
margin-bottom: 0;
}
.page-header h2 {
.page-title {
font-size: 20px;
font-weight: 600;
color: #1e293b;
margin: 0;
}
/* 统计卡片区 */
.statistics-section {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
margin-bottom: 20px;
gap: 16px;
}
.stat-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background: #fff;
border-radius: 12px;
padding: 24px;
padding: 20px 24px;
display: flex;
align-items: center;
gap: 16px;
color: #fff;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.06);
border: 1px solid #f0f0f0;
transition: box-shadow 0.2s;
}
.stat-card:hover {
box-shadow: 0 4px 18px rgba(0, 0, 0, 0.1);
}
.stat-card.pending {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
box-shadow: 0 4px 12px rgba(245, 87, 108, 0.3);
}
.stat-card.success {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
box-shadow: 0 4px 12px rgba(0, 242, 254, 0.3);
}
.stat-icon {
width: 56px;
height: 56px;
background: rgba(255, 255, 255, 0.2);
border-radius: 12px;
/* 图标容器 */
.stat-icon-wrap {
width: 52px;
height: 52px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
flex-shrink: 0;
}
.stat-icon-purple {
background: linear-gradient(135deg, #818cf8 0%, #6366f1 100%);
}
.stat-icon-red {
background: linear-gradient(135deg, #fca5a5 0%, #ef4444 100%);
}
.stat-icon-green {
background: linear-gradient(135deg, #6ee7b7 0%, #10b981 100%);
}
.stat-icon-inner {
font-size: 24px;
color: #fff;
}
.stat-content {
flex: 1;
}
.stat-value {
font-size: 32px;
font-weight: bold;
font-size: 28px;
font-weight: 700;
color: #1e293b;
line-height: 1.2;
}
.stat-label {
font-size: 14px;
opacity: 0.9;
font-size: 13px;
color: #64748b;
margin-top: 4px;
}
/* 高级筛选区 */
.filter-section {
background: #fff;
border-radius: 8px;
padding: 16px 20px;
margin-bottom: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
border: 1px solid #f0f0f0;
}
.action-section {
background: #fff;
border-radius: 8px;
padding: 12px 20px;
margin-bottom: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
.filter-title {
font-size: 14px;
font-weight: 600;
color: #1e293b;
margin-bottom: 14px;
}
.filter-row {
display: flex;
align-items: center;
gap: 16px;
flex-wrap: wrap;
gap: 12px;
}
.filter-item {
display: flex;
align-items: center;
gap: 8px;
}
.filter-label {
font-size: 13px;
color: #475569;
white-space: nowrap;
flex-shrink: 0;
}
.filter-date-picker {
width: 240px;
}
.filter-select {
width: 140px;
}
.filter-input {
width: 180px;
}
.filter-actions {
display: flex;
gap: 8px;
margin-left: auto;
}
.btn-apply {
background-color: #6366f1;
border-color: #6366f1;
}
.btn-apply:hover {
background-color: #4f46e5;
border-color: #4f46e5;
}
.btn-reset {
color: #475569;
}
/* 表格区 */
.table-section {
background: #fff;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
border: 1px solid #f0f0f0;
}
/* 状态标签 */
.status-tag {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
line-height: 1.6;
}
/* 已上报 - 绿 */
.status-3 {
color: #16a34a;
background: #dcfce7;
border: 1px solid #bbf7d0;
}
/* 已审核 - 蓝 */
.status-2 {
color: #2563eb;
background: #dbeafe;
border: 1px solid #bfdbfe;
}
/* 失败 - 红 */
.status-4 {
color: #dc2626;
background: #fee2e2;
border: 1px solid #fecaca;
}
/* 已提交 - 绿 */
.status-1 {
color: #16a34a;
background: #dcfce7;
border: 1px solid #bbf7d0;
}
/* 待提交 - 橙 */
.status-0 {
color: #d97706;
background: #fef3c7;
border: 1px solid #fde68a;
}
/* 作废 - 灰 */
.status-6 {
color: #64748b;
background: #f1f5f9;
border: 1px solid #e2e8f0;
}
/* 退回 - 红 */
.status-5 {
color: #dc2626;
background: #fee2e2;
border: 1px solid #fecaca;
}
/* 分页 */
.pagination-section {
margin-top: 16px;
display: flex;
justify-content: flex-end;
}
/* 底部批量操作栏 */
.bottom-action-bar {
background: #fff;
border-radius: 8px;
padding: 12px 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
border: 1px solid #f0f0f0;
display: flex;
align-items: center;
justify-content: space-between;
}
.bottom-action-left {
display: flex;
align-items: center;
gap: 12px;
}
.selected-info {
font-size: 13px;
color: #64748b;
}
.bottom-action-right {
display: flex;
gap: 8px;
}
.btn-batch-delete {
color: #dc2626;
border-color: #fecaca;
background: #fff;
}
.btn-batch-delete:hover {
background: #fee2e2;
border-color: #fca5a5;
}
.btn-batch-submit {
background-color: #6366f1;
border-color: #6366f1;
font-weight: 500;
}
.btn-batch-submit:hover {
background-color: #4f46e5;
border-color: #4f46e5;
}
@media (max-width: 992px) {
.statistics-section {
grid-template-columns: 1fr;
}
.filter-row {
flex-direction: column;
align-items: flex-start;
}
.filter-actions {
margin-left: 0;
}
}
/* 报卡详情弹窗 */
:deep(.card-detail-dialog .el-dialog__body) {
padding: 0;
overflow: hidden;
}
:deep(.card-detail-dialog .infectious-report-container) {
padding: 16px;
height: auto;
max-height: 70vh;
overflow-y: auto;
}
</style>