Files
gao-customer-h5/src/pages/notify/NotifyDetail.vue
T
2026-08-31 20:28:38 +08:00

285 lines
7.7 KiB
Vue

<template>
<div v-can="corePermissionCodes.notify.read" class="notify-detail">
<t-empty
v-if="!detail"
:description="isLoading ? '正在加载通知' : '通知不存在'"
/>
<template v-else>
<t-cell-group title="通知状态" theme="card" v-entrance-transition>
<t-cell title="发送用户" :note="`${detail.detailList?.length || 0} 人`" arrow @click="openRecipientList"/>
<t-cell title="已读用户" :note="`${readCount} 人`" arrow @click="openReadRecipientList"/>
<t-cell title="未读用户" :note="`${unreadCount} 人`" arrow @click="openUnreadRecipientList"/>
<t-cell title="当前状态">
<template #note>
<t-tag size="small" :theme="getSummaryTheme" :content="getSummaryStatus" />
</template>
</t-cell>
</t-cell-group>
<t-button
v-if="canCancelNotify && canCancel"
class="bg-white red cell-button"
variant="text"
block
:loading="isCanceling"
v-entrance-transition="70"
@click="confirmCancel"
content="取消发送"
/>
<notify-content
class="preview"
:detail="mainDetail"
:subject="getSubject(detail)"
:time="getSendTime(mainDetail)"
:resolve-attachment-url="resolveAttachmentUrl"
v-entrance-transition="140"
/>
</template>
</div>
</template>
<script setup lang="ts">
import { Time } from "timi-web";
import { NotifyAPI, type Notify, type NotifyDetail, useUserStore } from "timi-web-api";
import { DialogPlugin } from "tdesign-mobile-vue";
import { Message } from "@/utils/message";
import { getErrorMessage } from "@/utils/error";
import { getAttachmentReadURL } from "@/utils/attachment";
import { useNavBarStore } from "@/store/navBarStore";
import { corePermissionCodes } from "@/utils/permission";
import NotifyContent from "@/pages/notify/component/NotifyContent.vue";
type NotifyTagTheme = "default" | "success" | "warning" | "primary" | "danger";
type RecipientFilter = "all" | "unread" | "read";
defineOptions({
name: "NotifyDetail"
});
const route = useRoute();
const router = useRouter();
const navBarStore = useNavBarStore();
const userStore = useUserStore();
const canDeleteNotify = computed(() => userStore.hasPermissions(corePermissionCodes.notify.delete));
const canCancelNotify = computed(() => userStore.hasPermissions(corePermissionCodes.notify.cancel));
// ---------- 通知数据 ----------
const detail = ref<Notify>();
const isLoading = ref(false);
const isCanceling = ref(false);
const isDeleting = ref(false);
let loadToken = 0;
const mainDetail = computed(() => detail.value?.detailList?.[0]);
const readCount = computed(() => detail.value?.detailList?.filter(item => Boolean(item.readAt)).length || 0);
const unreadCount = computed(() => detail.value?.detailList?.filter(item => !item.readAt).length || 0);
const canCancel = computed(() => detail.value?.detailList?.some(item => isCancelable(item)) || false);
const getSummaryStatus = computed(() => {
const detailList = detail.value?.detailList || [];
if (canCancel.value) {
return "待发送";
}
if (detailList.length && detailList.every(item => item.status === "CANCEL")) {
return "已取消";
}
if (detailList.some(item => item.status === "FAIL" || item.status === "TIMEOUT")) {
return "部分失败";
}
return "已发送";
});
const getSummaryTheme = computed<NotifyTagTheme>(() => {
if (getSummaryStatus.value === "待发送") {
return "warning";
}
if (getSummaryStatus.value === "已取消") {
return "default";
}
if (getSummaryStatus.value === "部分失败") {
return "danger";
}
return "success";
});
function resolveAttachmentUrl(attachmentId: string): string {
return getAttachmentReadURL(attachmentId);
}
async function loadDetail(): Promise<void> {
const requestToken = loadToken + 1;
loadToken = requestToken;
detail.value = undefined;
navBarStore.clearRight();
const notifyId = String(route.params.id || "");
if (!notifyId) {
return;
}
isLoading.value = true;
try {
const result = await NotifyAPI.managementDetail(notifyId);
if (requestToken !== loadToken) {
return;
}
detail.value = result;
setRightAction();
} catch (error) {
Message.error(getErrorMessage(error, "通知详情加载失败"));
} finally {
if (requestToken === loadToken) {
isLoading.value = false;
}
}
}
function setRightAction(): void {
if (detail.value?.id && canDeleteNotify.value) {
navBarStore.setRightText("删除", "var(--td-error-color)", confirmDelete);
}
}
// ---------- 通知操作 ----------
function isCancelable(item: NotifyDetail): boolean {
return item.status === "WAITING" && Boolean(item.sendAt && Time.now() < item.sendAt);
}
async function confirmCancel(): Promise<void> {
const notifyId = detail.value?.id;
if (!notifyId || !canCancelNotify.value || !canCancel.value || isCanceling.value) {
return;
}
const dialog = DialogPlugin.confirm({
title: "取消定时通知",
content: "确定取消这条尚未发送的通知吗?",
cancelBtn: "返回",
confirmBtn: "取消发送",
onCancel() {
dialog.destroy();
},
async onConfirm() {
dialog.destroy();
isCanceling.value = true;
try {
await NotifyAPI.managementCancel(notifyId);
await loadDetail();
Message.success("通知已取消");
} catch (error) {
Message.error(getErrorMessage(error, "通知取消失败"));
} finally {
isCanceling.value = false;
}
}
});
}
async function confirmDelete(): Promise<void> {
const notifyId = detail.value?.id;
if (!notifyId || !canDeleteNotify.value || isDeleting.value) {
return;
}
const dialog = DialogPlugin.confirm({
title: "删除通知",
content: "确定删除这条通知吗?删除后收件人也将无法查看。",
cancelBtn: "返回",
confirmBtn: "删除",
onCancel() {
dialog.destroy();
},
async onConfirm() {
dialog.destroy();
isDeleting.value = true;
navBarStore.clearRight();
try {
await NotifyAPI.managementDelete(notifyId);
Message.success("通知已删除");
await router.replace({ name: "NotifyList" });
} catch (error) {
Message.error(getErrorMessage(error, "通知删除失败"));
setRightAction();
} finally {
isDeleting.value = false;
}
}
});
}
// ---------- 展示 ----------
function getSubject(item: Notify): string {
return item.detailList?.[0]?.subject?.trim() || "站内通知";
}
function getSendTime(item?: NotifyDetail): string {
if (!item?.sendAt) {
return "-";
}
if (isCancelable(item)) {
return `定时 ${Time.toDateTime(item.sendAt)}`;
}
return Time.toDateTime(item.sendAt);
}
async function openRecipientList(): Promise<void> {
const notifyId = detail.value?.id;
if (!notifyId) {
return;
}
await router.push({
name: "NotifyRecipientList",
params: { id: notifyId },
query: { filter: "all" satisfies RecipientFilter }
});
}
async function openReadRecipientList(): Promise<void> {
await openRecipientListByFilter("read");
}
async function openUnreadRecipientList(): Promise<void> {
await openRecipientListByFilter("unread");
}
async function openRecipientListByFilter(filter: RecipientFilter): Promise<void> {
const notifyId = detail.value?.id;
if (!notifyId) {
return;
}
await router.push({
name: "NotifyRecipientList",
params: { id: notifyId },
query: { filter }
});
}
// ---------- 生命周期 ----------
watch(() => route.params.id, async () => {
await loadDetail();
}, { immediate: true });
onMounted(() => navBarStore.clearRight());
onUnmounted(() => navBarStore.clearRight());
</script>
<style scoped lang="less">
.notify-detail {
gap: 1rem;
min-height: 100%;
padding: var(--app-nav-height) 0 1rem;
display: flex;
flex-direction: column;
.article {
padding: 1.25rem 1rem;
margin: 0 1rem;
background: var(--td-bg-color-container);
box-shadow: var(--td-shadow-1);
border-radius: 1.4rem;
}
.preview {
background: #FFF;
}
}
</style>