Files
jiapuapp/pages/profile/ceremony-invitations.vue
T

324 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>
<view class="invitation-page">
<ModulePageBackground module="profile" />
<view class="page-layer"
><PageHeader title="我的活动邀请" custom-back @back="requestBack"
/></view>
<view class="page-content page-layer">
<AppLoading v-if="ceremonyInvitationListState === 'loading'" text="正在加载活动邀请" />
<view v-else-if="ceremonyInvitationListState === 'error'" class="state-card">
<text>暂时无法加载活动邀请</text>
<text>请检查网络后重新查看</text>
<AppButton block type="secondary" label="重新查看" @click="loadInvitations" />
</view>
<view v-else-if="ceremonyInvitationListState === 'empty'" class="state-card">
<text>暂时没有活动邀请</text>
<text>收到家族礼仪活动邀请后会在这里显示</text>
<AppButton block type="secondary" label="返回我的" @click="requestBack" />
</view>
<view v-else class="invitation-list">
<text class="page-note">你可以在这里选择接受或拒绝待处理的活动邀请</text>
<text v-if="actionError" class="action-error">{{ actionError }}</text>
<view
v-for="invitation in invitations"
:key="invitation.id"
class="invitation-card"
>
<view class="invitation-card__heading">
<text>{{ invitation.ceremonyTitle || "未命名活动" }}</text>
<text :class="`status status--${invitation.inviteStatus.toLowerCase()}`">
{{ invitationStatusLabel(invitation.inviteStatus) }}
</text>
</view>
<text v-if="invitation.ceremonyTime" class="invitation-card__meta">
时间{{ invitation.ceremonyTime }}
</text>
<text v-if="invitationLocation(invitation)" class="invitation-card__meta">
地点{{ invitationLocation(invitation) }}
</text>
<text v-if="invitation.deliveredTime" class="invitation-card__meta">
送达{{ invitation.deliveredTime }}
</text>
<text v-if="invitation.responseTime" class="invitation-card__meta">
处理时间{{ invitation.responseTime }}
</text>
<view
v-if="invitation.inviteStatus === CEREMONY_INVITATION_STATUS.PENDING"
class="invitation-card__actions"
>
<AppButton
compact
type="secondary"
:disabled="Boolean(respondingId)"
label="拒绝"
@click="openResponseDialog(invitation, CEREMONY_INVITATION_STATUS.DECLINED)"
/>
<AppButton
compact
:disabled="Boolean(respondingId)"
:label="respondingId === invitation.id ? '正在提交' : '接受'"
@click="openResponseDialog(invitation, CEREMONY_INVITATION_STATUS.ACCEPTED)"
/>
</view>
</view>
</view>
</view>
<AppDialog
:visible="Boolean(responseTarget)"
:close-on-mask="false"
:title="responseDialogTitle"
:message="responseDialogMessage"
:confirm-text="responseTarget?.status === CEREMONY_INVITATION_STATUS.ACCEPTED ? '确认接受' : '确认拒绝'"
cancel-text="暂不处理"
show-cancel
@confirm="submitResponse"
@cancel="closeResponseDialog"
/>
</view>
</template>
<script setup>
import { computed, ref } from "vue";
import { onBackPress, onShow, onUnload } from "@dcloudio/uni-app";
import AppButton from "@/components/AppButton.vue";
import AppDialog from "@/components/AppDialog.vue";
import AppLoading from "@/components/AppLoading.vue";
import ModulePageBackground from "@/components/ModulePageBackground.vue";
import PageHeader from "@/components/PageHeader.vue";
import {
CEREMONY_INVITATION_STATUS,
CEREMONY_INVITATION_STATUS_LABELS
} from "@/services/api/ceremony-contract.js";
import {
createRequestController,
isRequestCancelled
} from "@/services/api/request-controller.js";
import { ceremonyApi } from "@/services/api/ceremony-service.js";
import { getRequestErrorMessage } from "@/services/api/request-error-message.js";
import { handleBackPress, returnTo } from "@/utils/navigation/gateway.js";
const invitations = ref([]);
const ceremonyInvitationListState = ref("loading");
const actionError = ref("");
const responseTarget = ref(null);
const respondingId = ref("");
const invitationListController = createRequestController();
const invitationResponseController = createRequestController();
let isPageActive = true;
const responseDialogTitle = computed(() =>
responseTarget.value?.status === CEREMONY_INVITATION_STATUS.ACCEPTED
? "接受活动邀请?"
: "拒绝活动邀请?",
);
const responseDialogMessage = computed(() => {
const title = responseTarget.value?.item?.ceremonyTitle || "此活动";
return responseTarget.value?.status === CEREMONY_INVITATION_STATUS.ACCEPTED
? `确认接受「${title}」的邀请?`
: `确认拒绝「${title}」的邀请?`;
});
const invitationStatusLabel = (status) =>
CEREMONY_INVITATION_STATUS_LABELS[status] || "暂未确认";
const invitationLocation = (invitation) =>
[invitation.location, invitation.locationAddress].filter(Boolean).join(" · ");
const loadInvitations = async () => {
if (respondingId.value) return;
invitationListController.abort();
ceremonyInvitationListState.value = "loading";
actionError.value = "";
try {
const rows = await ceremonyApi.getMyCeremonyInvitations({
requestController: invitationListController,
});
if (!isPageActive) return;
invitations.value = rows;
ceremonyInvitationListState.value = rows.length ? "ready" : "empty";
} catch (error) {
if (!isPageActive || isRequestCancelled(error)) return;
ceremonyInvitationListState.value = "error";
}
};
const openResponseDialog = (invitation, status) => {
if (
respondingId.value ||
invitation.inviteStatus !== CEREMONY_INVITATION_STATUS.PENDING
) return;
responseTarget.value = { item: invitation, status };
actionError.value = "";
};
const closeResponseDialog = () => {
if (!respondingId.value) responseTarget.value = null;
};
const submitResponse = async () => {
const target = responseTarget.value;
if (!target || respondingId.value) return;
respondingId.value = target.item.id;
actionError.value = "";
try {
await ceremonyApi.respondToCeremonyInvitation(
target.item.genealogyId,
target.item.ceremonyId,
{ inviteStatus: target.status },
{ requestController: invitationResponseController },
);
if (!isPageActive) return;
responseTarget.value = null;
respondingId.value = "";
await loadInvitations();
} catch (error) {
if (!isPageActive || isRequestCancelled(error)) return;
responseTarget.value = null;
actionError.value = getRequestErrorMessage(error, "处理未完成,请稍后重试。");
} finally {
if (isPageActive) respondingId.value = "";
}
};
const requestBack = () => returnTo("M01", {});
onShow(() => {
if (!respondingId.value) loadInvitations();
});
onBackPress((event) => handleBackPress(event, requestBack));
onUnload(() => {
isPageActive = false;
invitationListController.abort();
invitationResponseController.abort();
});
</script>
<style scoped lang="scss">
.invitation-page {
min-height: 100vh;
color: #35251d;
}
.page-layer,
.page-content {
position: relative;
z-index: 1;
}
.page-content {
padding: 36rpx 32rpx calc(68rpx + env(safe-area-inset-bottom));
}
.state-card,
.invitation-card {
display: flex;
flex-direction: column;
gap: 18rpx;
padding: 34rpx 30rpx;
border: 1rpx solid rgba(172, 121, 37, 0.48);
border-radius: 16rpx;
background: rgba(255, 249, 236, 0.9);
box-shadow: 0 8rpx 24rpx rgba(90, 44, 20, 0.08);
}
.state-card {
margin-top: 32rpx;
text-align: center;
color: #76665b;
}
.state-card text:first-child {
color: #3d2b20;
font-size: clamp(20px, 36rpx, 25px);
font-weight: 700;
}
.invitation-list {
display: flex;
flex-direction: column;
gap: 22rpx;
}
.page-note {
color: #887467;
font-size: clamp(16px, 26rpx, 19px);
}
.action-error {
padding: 18rpx 22rpx;
border-radius: 10rpx;
background: rgba(177, 43, 31, 0.1);
color: #a22b20;
font-size: clamp(16px, 26rpx, 19px);
}
.invitation-card__heading,
.invitation-card__actions {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20rpx;
}
.invitation-card__heading {
align-items: flex-start;
}
.invitation-card__heading text:first-child {
flex: 1;
color: #40291e;
font-size: clamp(19px, 34rpx, 24px);
font-weight: 700;
}
.invitation-card__meta {
color: #746256;
font-size: clamp(16px, 27rpx, 20px);
line-height: 1.55;
}
.status {
flex: 0 0 auto;
padding: 6rpx 14rpx;
border-radius: 999rpx;
font-size: clamp(15px, 24rpx, 18px);
}
.status--pending {
background: rgba(183, 124, 30, 0.13);
color: #9c6412;
}
.status--accepted {
background: rgba(47, 113, 71, 0.12);
color: #2f7147;
}
.status--declined,
.status--canceled {
background: rgba(137, 67, 51, 0.1);
color: #874333;
}
.invitation-card__actions {
flex-wrap: wrap;
justify-content: flex-end;
padding-top: 8rpx;
}
.invitation-card__actions .app-button {
min-width: 152rpx;
}
@media (max-width: 340px) {
.page-content {
padding-right: 22rpx;
padding-left: 22rpx;
}
.state-card,
.invitation-card {
padding-right: 24rpx;
padding-left: 24rpx;
}
}
</style>