300,301,302预约挂号展示问题

This commit is contained in:
HuangXinQuan
2026-04-03 16:47:03 +08:00
parent 8a84b40ee5
commit 0c5353cf8b
6 changed files with 152 additions and 22 deletions

View File

@@ -142,6 +142,7 @@ public class TicketAppServiceImpl implements ITicketAppService {
// 基础字段映射
dto.setSlot_id(raw.getSlotId());
dto.setSeqNo(raw.getSeqNo());
dto.setBusNo(String.valueOf(raw.getSlotId()));
dto.setDoctor(raw.getDoctor());
dto.setDepartment(raw.getDepartmentName()); // 注意以前这里传成了ID导致前端出Bug现在修复成了真正的科室名
@@ -319,6 +320,7 @@ public class TicketAppServiceImpl implements ITicketAppService {
// --- 基础字段处理 ---
// 注意:这里已经变成了极其舒服的 .getSlotId() 方法调用,告别魔鬼字符串!
dto.setSlot_id(raw.getSlotId());
dto.setSeqNo(raw.getSeqNo());
dto.setBusNo(String.valueOf(raw.getSlotId())); // 暂时借用真实槽位ID做唯一流水号
dto.setDoctor(raw.getDoctor());
dto.setDepartment(raw.getDepartmentName());

View File

@@ -23,6 +23,11 @@ public class TicketDto {
@JsonSerialize(using = ToStringSerializer.class)
private Long slot_id;
/**
* 号源序号(对应 adm_schedule_slot.seq_no
*/
private Integer seqNo;
/**
* 号源编码
*/

View File

@@ -11,6 +11,7 @@ import java.time.LocalTime;
public class TicketSlotDTO {
// 基础信息
private Long slotId;
private Integer seqNo;
private Long scheduleId;
private String doctor;
private Long doctorId;

View File

@@ -43,6 +43,7 @@
<select id="selectAllTicketSlots" resultType="com.openhis.appointmentmanage.domain.TicketSlotDTO">
SELECT
s.id AS slotId,
s.seq_no AS seqNo,
p.schedule_id AS scheduleId,
p.doctor_name AS doctor,
p.dept_id AS departmentId,
@@ -91,6 +92,7 @@
<select id="selectTicketSlotById" resultType="com.openhis.appointmentmanage.domain.TicketSlotDTO">
SELECT
s.id AS slotId,
s.seq_no AS seqNo,
p.schedule_id AS scheduleId,
p.doctor_name AS doctor,
p.doctor_id AS doctorId,
@@ -205,6 +207,7 @@
<select id="selectTicketSlotsPage" resultType="com.openhis.appointmentmanage.domain.TicketSlotDTO">
SELECT
s.id AS slotId,
s.seq_no AS seqNo,
p.schedule_id AS scheduleId,
p.doctor_name AS doctor,
p.doctor_id AS doctorId,

View File

@@ -106,7 +106,7 @@
<div class="ticket-grid" v-if="filteredTickets.length > 0">
<div v-for="(item, index) in filteredTickets" :key="item.slot_id" class="ticket-card" @dblclick="handleDoubleClick(item)" @contextmenu.prevent="handleRightClick($event, item)">
<!-- 序号放在最右侧 -->
<div class="ticket-index">{{ index + 1 }}</div>
<div class="ticket-index">{{ item.seqNo != null ? item.seqNo : index + 1 }}</div>
<!-- 1.时间 -->
<div class="ticket-id-time">{{ item.dateTime }}</div>
<!-- 2. 状态标签 -->
@@ -126,7 +126,7 @@
<div class="ticket-type">{{ item.ticketType === 'general' ? '普通' : '专家' }}</div>
<!-- 7. 已预约患者信息 -->
<div v-if="(item.status === '已预约' || item.status === '已取号') && item.patientName" class="ticket-patient">
{{ item.patientName }}({{ item.patientId }})
{{ item.patientName }}({{ item.patientId }},{{ getGenderText(item.gender || item.patientGender) }})
</div>
<div class="ticket-actions">
<button class="action-button book-button" @click="openPatientSelectModal(item.slot_id)" :disabled="item.status !== '未预约'" :class="{ 'disabled': item.status !== '未预约' }">
@@ -195,6 +195,7 @@
>
<td>{{ index + 1 }}</td>
<td>{{ patient.name }}</td>
<td>{{ patient.identifierNo || patient.medicalCard || patient.id }}</td>
<td>{{ patient.identifierNo }}</td>
<td>{{ getGenderText(patient.genderEnum_enumText || patient.genderEnum || patient.gender || patient.sex) }}</td>
<td>{{ patient.idCard }}</td>
@@ -366,14 +367,23 @@ export default {
this.searchPatients();
},
getPatientUniqueId(patient, index = null) {
return patient.idCard || patient.medicalCard || patient.id || (index !== null ? `temp_${index}` : null);
return patient.id || patient.patientId || patient.identifierNo || patient.medicalCard || patient.idCard ||
(index !== null ? `temp_${index}` : null);
},
matchPatientKeyword(patient, keyword) {
const normalizedKeyword = String(keyword || '').trim().toLowerCase();
if (!normalizedKeyword) {
return true;
}
const fields = [patient.name, patient.id, patient.medicalCard, patient.idCard, patient.phone];
const fields = [
patient.name,
patient.id,
patient.patientId,
patient.identifierNo,
patient.medicalCard,
patient.idCard,
patient.phone
];
return fields.some(field => String(field || '').toLowerCase().includes(normalizedKeyword));
},
searchPatients() {
@@ -396,6 +406,8 @@ export default {
const rowKey = this.getPatientUniqueId(patient, index);
return {
...patient,
// 统一口径:预约场景的就诊卡号使用患者标识表中的 identifierNo
medicalCard: patient.identifierNo || patient.medicalCard || '',
_rowKey: rowKey
};
});
@@ -496,6 +508,7 @@ export default {
this.tickets[ticketIndex].patientName = null;
this.tickets[ticketIndex].patientId = null;
this.tickets[ticketIndex].patientGender = null;
this.tickets[ticketIndex].gender = null;
this.tickets[ticketIndex].medicalCard = null;
this.tickets[ticketIndex].phone = null;
}
@@ -556,12 +569,22 @@ export default {
try {
const userStore = useUserStore();
const patientPrimaryId = this.selectedPatient.id || this.selectedPatient.patientId;
const medicalCard = this.selectedPatient.identifierNo || this.selectedPatient.medicalCard;
if (!patientPrimaryId) {
ElMessage.error('患者ID缺失无法预约请重新选择患者');
return;
}
if (!medicalCard) {
ElMessage.error('就诊卡号缺失,无法预约,请先维护患者就诊卡号');
return;
}
const appointmentData = {
ticketId: this.currentTicket.slot_id,
slotId: this.currentTicket.slot_id,
patientId: this.selectedPatient.id || this.selectedPatient.idCard || this.selectedPatient.medicalCard,
patientId: patientPrimaryId,
patientName: this.selectedPatient.name,
medicalCard: this.selectedPatient.medicalCard || this.selectedPatient.id,
medicalCard,
phone: this.selectedPatient.phone,
gender: this.getGenderValueForBackend(this.selectedPatient),
fee: Number(this.currentTicket.fee) || 0,
@@ -580,8 +603,9 @@ export default {
if (ticketIndex !== -1) {
this.tickets[ticketIndex].status = '已预约';
this.tickets[ticketIndex].patientName = this.selectedPatient.name;
this.tickets[ticketIndex].patientId = this.selectedPatient.medicalCard || this.selectedPatient.id;
this.tickets[ticketIndex].patientId = medicalCard;
this.tickets[ticketIndex].patientGender = this.selectedPatient.genderEnum_enumText || this.selectedPatient.genderEnum || this.selectedPatient.gender || this.selectedPatient.sex;
this.tickets[ticketIndex].gender = this.getGenderText(this.tickets[ticketIndex].patientGender);
}
this.closePatientSelectModal();
@@ -603,9 +627,9 @@ export default {
},
// 获取性别文本
getGenderText(genderValue) {
// 如果值为null或undefined返回'-'
// 如果值为null或undefined返回"未知"
if (genderValue === null || genderValue === undefined) {
return '-';
return '未知';
}
// 将值转换为字符串进行比较
@@ -625,8 +649,8 @@ export default {
return '女';
}
// 如果都不是,返回原值或'-'
return '-';
// 如果都不是,返回"未知"
return '未知';
},
// 获取用于后端的性别值
getGenderValueForBackend(patient) {

View File

@@ -164,7 +164,14 @@
</el-col>
<el-col :span="8">
<el-form-item label="检查方法">
<el-input v-model="form.inspectionMethod" readonly />
<el-select v-model="form.inspectionMethod" placeholder="请选择" clearable filterable style="width: 100%;">
<el-option
v-for="method in availableMethods"
:key="method.id"
:label="method.name"
:value="method.name"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
@@ -308,6 +315,7 @@ import { ElMessage, ElMessageBox } from 'element-plus';
import { Printer, Delete } from '@element-plus/icons-vue';
import useUserStore from '@/store/modules/user';
import request from '@/utils/request';
import { listCheckMethod } from '@/api/system/checkType';
const props = defineProps({
patientInfo: { type: Object, default: () => ({}) },
@@ -373,10 +381,69 @@ const categoryList = ref([]); // 原始分类+项目数据
const dictSearchKey = ref('');
const activeNames = ref([]); // 当前展开的折叠项
const allMethods = ref([]);
// 加载所有检查方法
async function loadAllMethods() {
try {
const res = await listCheckMethod(); // 使用已导入的或者直接利用 request 请求
let methods = [];
if (res && res.data) {
if (Array.isArray(res.data)) {
methods = res.data;
} else if (res.data.records) {
methods = res.data.records;
} else if (res.data.data && Array.isArray(res.data.data)) {
methods = res.data.data;
}
} else if (Array.isArray(res)) {
methods = res;
} else if (res && res.rows) {
methods = res.rows;
}
allMethods.value = methods;
} catch (err) {
console.error('加载检查方法失败', err);
}
}
onMounted(async () => {
await loadAllMethods();
await loadCategoryList();
});
// 动态可用的检查方法(根据已选部位所属的检查类型进行过滤)
const normalizeTypeValue = value => String(value ?? '').trim().toLowerCase();
const availableMethods = computed(() => {
// 获取当前已选部位的检查类型(可取第一个选中的部位的 checkType
const currentType = form.examTypeCode || (selectedItems.value.length > 0 ? selectedItems.value[0].checkType : '');
const normalizedCurrentType = normalizeTypeValue(currentType);
if (normalizedCurrentType) {
// 兼容脏数据method 的类型可能落在 checkType/type/typeCode/code/typeName/categoryName 中
const filtered = allMethods.value.filter(m => {
const typeCandidates = [
m.checkType,
m.type,
m.typeCode,
m.code,
m.typeName,
m.categoryName
].map(normalizeTypeValue).filter(Boolean);
return typeCandidates.includes(normalizedCurrentType);
});
return filtered.length > 0 ? filtered : allMethods.value;
}
return allMethods.value;
});
// 当可选方法列表改变时,如果当前选中的方法不在新列表中,则清空
watch(availableMethods, (newMethods) => {
if (form.inspectionMethod && !newMethods.find(m => m.name === form.inspectionMethod)) {
form.inspectionMethod = '';
}
});
/**
* 加载检查类型(分类)和检查项目(部位/项目),按类型分组展示
*/
@@ -390,25 +457,45 @@ async function loadCategoryList() {
params: { pageNo: 1, pageSize: 500 } // 取全量分类数据
});
let types = [];
if (typeRes.data?.records) types = typeRes.data.records;
else if (Array.isArray(typeRes.data)) types = typeRes.data;
else if (Array.isArray(typeRes.rows)) types = typeRes.rows;
if (typeRes && typeRes.data) {
if (Array.isArray(typeRes.data)) {
types = typeRes.data;
} else if (typeRes.data.records) {
types = typeRes.data.records;
} else if (typeRes.data.data && Array.isArray(typeRes.data.data)) {
types = typeRes.data.data;
}
} else if (Array.isArray(typeRes)) {
types = typeRes;
} else if (typeRes && typeRes.rows) {
types = typeRes.rows;
}
// 2. 加载检查项目(检查部位项目)
const partRes = await request({ url: '/check/part/list', method: 'get' });
let parts = [];
if (Array.isArray(partRes)) parts = partRes;
else if (Array.isArray(partRes.data?.data)) parts = partRes.data.data; // 双层嵌套:{ data: { data: [...] } }
else if (Array.isArray(partRes.data)) parts = partRes.data;
else if (Array.isArray(partRes.rows)) parts = partRes.rows;
else if (partRes.data?.records) parts = partRes.data.records;
if (partRes && partRes.data) {
if (Array.isArray(partRes.data)) {
parts = partRes.data;
} else if (partRes.data.records) {
parts = partRes.data.records;
} else if (partRes.data.data && Array.isArray(partRes.data.data)) {
parts = partRes.data.data;
}
} else if (Array.isArray(partRes)) {
parts = partRes;
} else if (partRes && partRes.rows) {
parts = partRes.rows;
}
// 3. 按 checkType 归类
const dict = [];
for (const t of types) {
dict.push({
typeId: t.id,
typeCode: t.type,
typeCode: t.code, // 保存 code 用于后备匹配
orgType: t.type, // 保存 type 用于后备匹配
typeName: t.name, // 保存 name
categoryName: t.name,
items: []
});
@@ -425,7 +512,15 @@ async function loadCategoryList() {
nationalCode: p.nationalCode || '',
checked: false
};
const target = dict.find(d => d.typeCode === p.checkType);
// 增强匹配逻辑:部位的 checkType (如 'ECG', 'CT') 优先去匹配大类的 orgType
// 如果大类的 type 字段脏了(比如填了中文),则尝试匹配 code甚至是分类名称
const target = dict.find(d =>
d.orgType === p.checkType ||
d.typeCode === p.checkType ||
d.typeName === p.checkType
);
if (target) target.items.push(mapped);
else unclassified.push(mapped);
}