ykt-team-wxapp/pages/record/appointment-record.vue
2026-07-29 17:30:30 +08:00

346 lines
9.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<full-page pageClass="record-page" :customScroll="true">
<view class="page-body">
<view class="profile-card" @click="switchArchive">
<view class="avatar-wrap">
<view class="avatar-placeholder">{{ avatarText }}</view>
</view>
<view class="profile-main">
<view class="profile-title">{{ customerName || "请选择档案" }}</view>
<view class="profile-sub">所属机构{{ corpName || "-" }}</view>
</view>
<view class="profile-arrow">&gt;</view>
</view>
<picker mode="date" fields="month" :value="selectedMonth" @change="handleMonthChange">
<view class="month-bar">
<text>{{ monthText }}</text>
<text class="month-arrow"></text>
</view>
</picker>
<view v-if="loading" class="empty">加载中...</view>
<view v-else-if="!records.length" class="empty">暂无预约记录</view>
<view v-else class="record-list">
<view v-for="item in records" :key="item._id" class="record-card">
<view class="record-status">{{ statusText(item) }}</view>
<view class="record-row">
<text class="row-label">预约类型</text>
<text class="row-value strong">{{ appointmentTypeText(item) }}</text>
</view>
<view class="record-row">
<text class="row-label">{{ practitionerLabel(item) }}</text>
<text class="row-value strong">{{ practitionerText(item) }}</text>
</view>
<view class="record-row">
<text class="row-label">预约日期</text>
<text class="row-value">{{ appointmentDateText(item) }}</text>
</view>
<view class="record-row">
<text class="row-label">预约时间</text>
<text class="row-value">{{ item.timeRange || "-" }}</text>
</view>
<view class="record-row">
<text class="row-label">预约项目</text>
<text class="row-value">{{ projectText(item) }}</text>
</view>
</view>
</view>
</view>
</full-page>
</template>
<script setup>
import { computed, ref } from "vue";
import { onLoad, onShow } from "@dcloudio/uni-app";
import api from "@/utils/api";
import { normalizeCorpId } from "@/utils/api-base-config";
import { toast } from "@/utils/widget";
import FullPage from "@/components/full-page.vue";
const corpId = ref("");
const teamId = ref("");
const customerId = ref("");
const customerName = ref("");
const corpName = ref("");
const selectedMonth = ref(formatMonth(Date.now()));
const records = ref([]);
const staffNameMap = ref({});
const loading = ref(false);
const statusMap = {
pending: { label: "未到院" },
confirmed: { label: "已到院" },
no_show: { label: "爽约" },
};
const avatarText = computed(() => (customerName.value || "档案").slice(0, 1));
const monthText = computed(() => selectedMonth.value.replace("-", "年") + "月");
function pad(value) {
return String(value).padStart(2, "0");
}
function toDate(ts) {
if (!ts) return null;
const d = new Date(Number(ts));
return Number.isNaN(d.getTime()) ? null : d;
}
function formatMonth(ts) {
const d = toDate(ts) || new Date();
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}`;
}
function formatDate(ts) {
const d = toDate(ts);
if (!d) return "";
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
}
function monthRange(month) {
const [year, monthIndex] = String(month || "").split("-").map(Number);
const start = new Date(year, monthIndex - 1, 1);
const end = new Date(year, monthIndex, 0);
return {
start: `${start.getFullYear()}-${pad(start.getMonth() + 1)}-01`,
end: `${end.getFullYear()}-${pad(end.getMonth() + 1)}-${pad(end.getDate())}`,
};
}
function appointmentDateText(item) {
return item?.appointmentDateStr || formatDate(item?.appointmentDate || item?.startTime || item?.registrationTime) || "-";
}
function statusText(item) {
return statusMap[item?.status || "pending"]?.label || "未到院";
}
function appointmentTypeText(item) {
if (item?.appointmentType === "schedule") return item?.scheduleTtile || "排班";
if (item?.appointmentType === "medical") return "复诊预约";
return "治疗预约";
}
function practitionerLabel(item) {
return item?.appointmentType === "medical" ? "医生" : "治疗师";
}
function practitionerText(item) {
const id = item?.therapistUserId || "";
if (item?.therapistName) return item.therapistName;
if (id === "none-therapist" || id === "none-doctor") return "未指定人员";
return staffNameMap.value[id] || id || "未指定人员";
}
function projectText(item) {
const projects = item?.treatmentProject;
if (Array.isArray(projects)) {
const names = projects
.map((project) => {
if (!project) return "";
if (typeof project === "string") return project;
return project.projectName || project.name || project.title || "";
})
.filter(Boolean);
return names.length ? names.join("、") : "无";
}
if (typeof projects === "string" && projects) return projects;
return "无";
}
function switchArchive() {
const corp = encodeURIComponent(corpId.value || "");
const team = encodeURIComponent(teamId.value || "");
const customer = encodeURIComponent(customerId.value || "");
uni.navigateTo({
url: `/pages/experience-coupon/select-rights-archive?mode=target&target=appointment&corpId=${corp}&teamId=${team}&customerId=${customer}`,
});
}
async function loadStaffNameMap() {
if (!corpId.value) {
staffNameMap.value = {};
return;
}
const res = await api("getAllCorpMemberIncludeDeleted", { corpId: corpId.value }, false);
const list = res?.success && Array.isArray(res.data) ? res.data : [];
staffNameMap.value = list.reduce((map, staff) => {
if (staff?.userid) {
map[staff.userid] = staff.anotherName || staff.name || staff.userid;
}
return map;
}, {});
}
async function loadRecords() {
if (!corpId.value || !customerId.value) {
records.value = [];
return;
}
loading.value = true;
try {
const range = monthRange(selectedMonth.value);
const res = await api(
"getAppointmentRegistration",
{
corpId: corpId.value,
customerId: customerId.value,
appointmentDateStart: range.start,
appointmentDateEnd: range.end,
page: 1,
pageSize: 200,
sortBy: "appointmentDate",
sortOrder: "desc",
},
false
);
if (!res?.success) {
toast(res?.message || "加载预约记录失败");
records.value = [];
return;
}
records.value = (Array.isArray(res.data) ? res.data : []).sort((a, b) => {
const aTime = Number(a.appointmentDate || a.startTime || a.registrationTime || 0);
const bTime = Number(b.appointmentDate || b.startTime || b.registrationTime || 0);
return bTime - aTime;
});
} finally {
loading.value = false;
}
}
async function handleMonthChange(event) {
selectedMonth.value = event?.detail?.value || selectedMonth.value;
await loadRecords();
}
onLoad(async (options = {}) => {
uni.setNavigationBarTitle({ title: "预约记录" });
corpId.value = normalizeCorpId(options.corpId || "");
teamId.value = options.teamId || "";
customerId.value = options.customerId || options.id || "";
customerName.value = options.name ? decodeURIComponent(options.name) : "";
corpName.value = options.corpName ? decodeURIComponent(options.corpName) : "";
if (options.month) selectedMonth.value = options.month;
await loadStaffNameMap();
await loadRecords();
});
onShow(() => {
if (corpId.value && customerId.value) loadRecords();
});
</script>
<style scoped>
.page-body {
min-height: 100%;
box-sizing: border-box;
background: #eef1f6;
}
.profile-card {
display: flex;
align-items: center;
gap: 18rpx;
padding: 20rpx 24rpx;
background: #fff;
border-bottom: 1rpx solid #d9dde5;
}
.avatar-wrap {
width: 72rpx;
height: 72rpx;
border-radius: 50%;
background: #d8d8d8;
overflow: hidden;
}
.avatar-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 30rpx;
font-weight: 600;
}
.profile-main {
flex: 1;
min-width: 0;
}
.profile-title {
font-size: 30rpx;
font-weight: 600;
color: #333;
line-height: 40rpx;
}
.profile-sub {
margin-top: 4rpx;
font-size: 24rpx;
color: #999;
line-height: 34rpx;
}
.profile-arrow {
flex-shrink: 0;
color: #b4b7bf;
font-size: 32rpx;
}
.month-bar {
display: flex;
align-items: center;
gap: 8rpx;
height: 70rpx;
padding: 0 24rpx;
background: #e2e6ec;
color: #333;
font-size: 30rpx;
font-weight: 600;
border-bottom: 1rpx solid #d5dae3;
}
.month-arrow {
font-size: 18rpx;
color: #333;
}
.record-list {
padding-bottom: 24rpx;
}
.record-card {
position: relative;
padding: 20rpx 24rpx 20rpx 34rpx;
background: #fff;
border-bottom: 1rpx solid #d9dde5;
border-top: 14rpx solid #eef1f6;
}
.record-status {
position: absolute;
top: 22rpx;
right: 24rpx;
color: #333;
font-size: 26rpx;
}
.record-row {
display: flex;
align-items: flex-start;
padding: 7rpx 110rpx 7rpx 0;
font-size: 27rpx;
line-height: 36rpx;
}
.row-label {
flex-shrink: 0;
color: #666;
font-weight: 600;
}
.row-value {
min-width: 0;
color: #333;
word-break: break-all;
}
.strong {
font-weight: 600;
}
.empty {
padding: 120rpx 0;
text-align: center;
font-size: 28rpx;
color: #999;
}
</style>