Files
gao-customer-h5/src/pages/notify/NotifyList.vue
T
2026-09-01 16:27:53 +08:00

338 lines
8.0 KiB
Vue

<template>
<div v-can="corePermissionCodes.notify.read" class="notify-management">
<t-pull-down-refresh
v-model="isRefreshing"
:refresh-timeout="30000"
@refresh="refresh"
@scrolltolower="loadMore"
>
<t-cell-group
v-if="pageResult.list.length"
class="items fix-last-cell-line"
theme="card"
>
<t-swipe-cell
v-for="(item, index) in pageResult.list"
:key="item.id"
class="item"
v-entrance-transition="index * 30"
>
<t-cell arrow @click="toDetail(item)">
<template #leftIcon>
<div class="icon" :class="{ scheduled: isScheduled(item) }">
<t-icon :name="isScheduled(item) ? 'time' : 'notification'" size="1.25rem" />
</div>
</template>
<template #title>
<div class="title">
<span v-text="getSubject(item)" />
</div>
</template>
<template #description>
<span v-if="isScheduled(item)" v-text="`定时 ${Time.toDateTime(item.sendAt)}`" />
<pass-time-label v-else :timestamp="item.sendAt || item.createdAt" underline />
</template>
<template #note>
<t-tag size="small" :theme="getStatusTheme(item)" :content="getStatusText(item)" />
</template>
</t-cell>
<template #right>
<t-button
v-if="canCancel(item)"
class="cancel"
theme="primary"
@click.stop="confirmCancel(item)"
>取消</t-button>
<t-button
v-if="canDeleteNotify"
class="delete"
theme="danger"
@click.stop="confirmDelete(item)"
>删除</t-button>
</template>
</t-swipe-cell>
</t-cell-group>
<t-empty v-else-if="!isLoading" description="暂无已发送通知" />
<div class="center-tips" v-text="loadStateText" />
</t-pull-down-refresh>
</div>
</template>
<script setup lang="ts">
import { PassTimeLabel, Time } from "timi-web";
import { NotifyAPI, type Notify, type NotifyManagementItem, type Page, type PageResult, useUserStore } from "timi-web-api";
import { DialogPlugin } from "tdesign-mobile-vue";
import { Message } from "@/utils/message";
import { getErrorMessage } from "@/utils/error";
import { useNavBarStore } from "@/store/navBarStore";
import { corePermissionCodes } from "@/utils/permission";
type NotifyTagTheme = "default" | "success" | "warning" | "primary" | "danger";
defineOptions({
name: "NotifyList"
});
const pageSize = 20;
const router = useRouter();
const navBarStore = useNavBarStore();
const userStore = useUserStore();
const canReadNotify = computed(() => userStore.hasPermissions(corePermissionCodes.notify.read));
const canCreateNotify = computed(() => userStore.hasPermissions(corePermissionCodes.notify.create));
const canDeleteNotify = computed(() => userStore.hasPermissions(corePermissionCodes.notify.delete));
const canCancelNotify = computed(() => userStore.hasPermissions(corePermissionCodes.notify.cancel));
// ---------- 列表分页 ----------
const isRefreshing = ref(false);
const isLoading = ref(false);
const isNoMore = ref(false);
const page = reactive<Page<Notify>>({
index: 0,
size: pageSize,
orderMap: { createdAt: "DESC" }
});
const pageResult = reactive<PageResult<NotifyManagementItem>>({
total: 0,
list: []
});
const loadStateText = computed(() => {
if (isLoading.value) {
return "正在加载通知";
}
if (isNoMore.value) {
return pageResult.list.length ? "没有更多通知" : "";
}
return "上拉加载更多";
});
// ---------- 展示 ----------
function getSubject(item: NotifyManagementItem): string {
return item.subject?.trim() || "站内通知";
}
function isScheduled(item: NotifyManagementItem): boolean {
return Boolean(item.sendAt && item.waitingCount && Time.now() < item.sendAt);
}
function getStatusText(item: NotifyManagementItem): string {
if (isScheduled(item)) {
return "待发送";
}
const total = item.recipientCount || 0;
if (0 < total && item.cancelCount === total) {
return "已取消";
}
if (item.failCount || item.timeoutCount) {
return "部分失败";
}
return "已发送";
}
function getStatusTheme(item: NotifyManagementItem): NotifyTagTheme {
if (isScheduled(item)) {
return "warning";
}
if (getStatusText(item) === "已取消") {
return "default";
}
if (getStatusText(item) === "部分失败") {
return "danger";
}
return "success";
}
function canCancel(item: NotifyManagementItem): boolean {
return canCancelNotify.value && isScheduled(item);
}
// ---------- 列表操作 ----------
async function loadMore(): Promise<void> {
if (isLoading.value || isNoMore.value || !canReadNotify.value) {
return;
}
isLoading.value = true;
try {
const result = await NotifyAPI.listManagement(page);
pageResult.total = result.total || 0;
pageResult.list.push(...(result.list || []));
page.index += 1;
isNoMore.value = pageResult.list.length >= pageResult.total || (result.list || []).length < page.size;
} catch (error) {
Message.error(getErrorMessage(error, "通知列表加载失败"));
} finally {
isLoading.value = false;
}
}
async function reloadList(): Promise<void> {
page.index = 0;
pageResult.total = 0;
pageResult.list = [];
isNoMore.value = false;
await loadMore();
}
async function refresh(): Promise<void> {
isRefreshing.value = true;
try {
await reloadList();
} finally {
isRefreshing.value = false;
}
}
async function toDetail(item: NotifyManagementItem): Promise<void> {
if (!item.id) {
return;
}
await router.push({
name: "NotifyDetail",
params: { id: item.id }
});
}
async function toCreate(): Promise<void> {
await router.push({ name: "NotifyCreate" });
}
async function confirmCancel(item: NotifyManagementItem): Promise<void> {
if (!item.id || !canCancel(item)) {
return;
}
const notifyId = item.id;
const dialog = DialogPlugin.confirm({
title: "取消定时通知",
content: "确定取消这条尚未发送的通知吗?",
cancelBtn: "返回",
confirmBtn: "取消发送",
onCancel() {
dialog.destroy();
},
async onConfirm() {
dialog.destroy();
try {
await NotifyAPI.managementCancel(notifyId);
await reloadList();
Message.success("通知已取消");
} catch (error) {
Message.error(getErrorMessage(error, "通知取消失败"));
}
}
});
}
async function confirmDelete(item: NotifyManagementItem): Promise<void> {
if (!item.id || !canDeleteNotify.value) {
return;
}
const notifyId = item.id;
const dialog = DialogPlugin.confirm({
title: "删除通知",
content: "确定删除这条通知吗?删除后收件人也将无法查看。",
cancelBtn: "返回",
confirmBtn: "删除",
onCancel() {
dialog.destroy();
},
async onConfirm() {
dialog.destroy();
try {
await NotifyAPI.managementDelete(notifyId);
await reloadList();
Message.success("通知已删除");
} catch (error) {
Message.error(getErrorMessage(error, "通知删除失败"));
}
}
});
}
// ---------- 生命周期 ----------
onMounted(async () => {
navBarStore.clearRight();
if (!canReadNotify.value) {
return;
}
if (canCreateNotify.value) {
navBarStore.setRightText("新建", "var(--td-brand-color)", toCreate);
}
await reloadList();
});
onUnmounted(() => navBarStore.clearRight());
</script>
<style scoped lang="less">
.notify-management {
min-height: 100%;
padding: calc(var(--app-nav-height) + 1rem) 0 1rem;
.items {
overflow: hidden;
:deep(.t-cell__title) {
min-width: 0;
}
.item {
.cancel,
.delete {
height: 100%;
padding: 0 1rem;
border-radius: 0;
}
.icon {
width: 3.5rem;
height: 3.5rem;
color: #64748b;
display: flex;
background: #e2e8f0;
align-items: center;
border-radius: .875rem;
justify-content: center;
&.scheduled {
color: #b45309;
background: #fef3c7;
}
}
.title {
min-width: 0;
gap: .5rem;
display: flex;
align-items: center;
> span:first-child {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
}
:global(html.theme-dark) {
.notify-management {
.icon {
color: #94a3b8;
background: #1e293b;
&.scheduled {
color: #fbbf24;
background: rgba(180, 83, 9, .2);
}
}
}
}
</style>