add event verify

This commit is contained in:
Timi
2026-08-29 15:35:28 +08:00
parent 24cf6fc714
commit 904af04cdd
9 changed files with 233 additions and 16 deletions
+125 -14
View File
@@ -23,6 +23,11 @@
:options="actionOptions"
multiple
/>
<t-dropdown-item
v-model="verificationFilter"
:label="verificationFilterLabel"
:options="verificationOptions"
/>
</t-dropdown-menu>
</div>
</div>
@@ -64,12 +69,22 @@
<t-cell
class="cell"
:title="getRecordEventName(item)"
:description="getRecordCustomerName(item)"
:note="getRecordRegisteredAt(item)"
arrow
hover
@click="toDetail(item.id || '')"
>
<template #description>
<div class="description-line">
<t-tag
v-if="item.verificationStatus && item.verificationStatus !== 'NOT_REQUIRED'"
:theme="getVerificationTag(item).theme"
variant="light-outline"
:content="getVerificationTag(item).text"
/>
<span v-text="getRecordCustomerName(item)" />
</div>
</template>
<template #leftIcon>
<t-image
v-if="getRecordPhotoURL(item)"
@@ -81,15 +96,27 @@
<div v-else class="avatar fallback" v-text="getRecordCustomerInitial(item)" />
</template>
</t-cell>
<template v-if="canDeleteRecord" #right>
<t-button
class="delete"
theme="danger"
:loading="isDeleting && deletingId === item.id"
@click.stop="confirmRemoveRecord(item)"
>
删除
</t-button>
<template v-if="canVerifyRecord || canDeleteRecord" #right>
<div class="actions">
<t-button
v-if="canVerifyRecord && item.verificationStatus === 'PENDING'"
class="action verify"
theme="primary"
:loading="isVerifying && verifyingId === item.id"
@click.stop="confirmVerifyRecord(item)"
>
核销
</t-button>
<t-button
v-if="canDeleteRecord"
class="action delete"
theme="danger"
:loading="isDeleting && deletingId === item.id"
@click.stop="confirmRemoveRecord(item)"
>
删除
</t-button>
</div>
</template>
</t-swipe-cell>
</t-cell-group>
@@ -133,7 +160,7 @@ import EventAPI from "@/api/EventAPI";
import EventRecordAPI from "@/api/EventRecordAPI";
import { useNavBarStore } from "@/store/navBarStore";
import type { EventItem } from "@/types/EventItem";
import type { EventRecordListItemView, EventRecordPage, EventRecordPageResult } from "@/types/EventRecord";
import type { EventRecordListItemView, EventRecordPage, EventRecordPageResult, VerificationStatus } from "@/types/EventRecord";
import type { NavBarAction } from "@/types/NavBarAction";
import { getCustomerPhotoURL } from "@/utils/customer";
import { getErrorMessage } from "@/utils/error";
@@ -142,6 +169,7 @@ import { permissionCodes } from "@/utils/permission";
import { Message } from "@/utils/message.ts";
type DateFilterType = "today" | "yesterday" | "beforeYesterday" | "lastWeek" | "all" | "custom";
type VerificationFilter = "ALL" | VerificationStatus;
defineOptions({
name: "EventRecordList"
@@ -170,6 +198,13 @@ const dateRange = ref(parseDateRange(dateFilterType.value));
const startDraft = ref("");
const endDraft = ref("");
const actionFilter = ref<string[]>([]);
const verificationOptions: Array<{ label: string; value: VerificationFilter }> = [
{ label: "全部核销状态", value: "ALL" },
{ label: "待核销", value: "PENDING" },
{ label: "已核销", value: "VERIFIED" },
{ label: "无需核销", value: "NOT_REQUIRED" }
];
const verificationFilter = ref<VerificationFilter>(parseVerificationFilter(route.query.verificationStatus));
const eventList = ref<EventItem[]>([]);
const startVisible = ref(false);
const endVisible = ref(false);
@@ -193,6 +228,7 @@ const eventFilterLabel = computed(() => {
.map(item => item.name || "未命名事件")
.join("、");
});
const verificationFilterLabel = computed(() => verificationOptions.find(item => item.value === verificationFilter.value)?.label || "全部核销状态");
const selectedNumberEvent = computed(() => {
if (actionFilter.value.length !== 1) {
return undefined;
@@ -202,6 +238,7 @@ const selectedNumberEvent = computed(() => {
});
const canExportRecord = computed(() => userStore.hasPermissions(permissionCodes.eventRecord.export));
const canDeleteRecord = computed(() => userStore.hasPermissions(permissionCodes.eventRecord.delete));
const canVerifyRecord = computed(() => userStore.hasPermissions(permissionCodes.eventRecord.verify));
const canShowActionMenu = computed(() => canCreateRecord.value || canExportRecord.value);
const actionList = computed<NavBarAction[]>(() => {
@@ -246,6 +283,14 @@ watch(actionFilter, async () => {
await reloadRecords();
});
watch(verificationFilter, async () => {
if (!initialized) {
return;
}
await replaceFilterQuery();
await reloadRecords();
});
watch(dateRange, async (value) => {
if (!initialized || dateFilterType.value !== "custom") {
return;
@@ -335,6 +380,8 @@ async function loadRecords(): Promise<void> {
const isDeleting = ref(false);
const deletingId = ref("");
const isVerifying = ref(false);
const verifyingId = ref("");
async function loadActions(): Promise<void> {
eventList.value = await EventAPI.listValid();
@@ -419,6 +466,46 @@ async function exportExcel(): Promise<void> {
}
}
async function confirmVerifyRecord(record: EventRecordListItemView): Promise<void> {
if (!record.id || record.verificationStatus !== "PENDING") {
return;
}
const recordId = record.id;
const dialog = DialogPlugin.confirm({
title: "核销登记记录",
content: `确定核销 ${getRecordCustomerName(record)} 的 ${getRecordEventName(record)} 记录吗?`,
cancelBtn: "取消",
confirmBtn: "核销",
onCancel() {
dialog.destroy();
},
async onConfirm() {
dialog.destroy();
await verifyRecord(recordId);
}
});
}
async function verifyRecord(id: string): Promise<void> {
if (isVerifying.value) {
return;
}
isVerifying.value = true;
verifyingId.value = id;
try {
await EventRecordAPI.verify(id);
await reloadRecords();
Message.success("登记记录已核销");
} catch (error) {
Message.error(getErrorMessage(error, "登记记录核销失败"));
} finally {
isVerifying.value = false;
verifyingId.value = "";
}
}
async function toDetail(id: string): Promise<void> {
if (!id) {
return;
@@ -501,6 +588,12 @@ function getRecordCustomerInitial(record: EventRecordListItemView): string {
return getRecordCustomerName(record).slice(0, 1);
}
function getVerificationTag(record: EventRecordListItemView): { text: string; theme: "warning" | "success" } {
return record.verificationStatus === "VERIFIED"
? { text: "已核销", theme: "success" }
: { text: "待核销", theme: "warning" };
}
function getRecordRegisteredAt(record: EventRecordListItemView): string {
if (!record.registeredAt) {
return "-";
@@ -519,7 +612,8 @@ function buildRangeReq(): Omit<EventRecordPage, "index" | "size"> {
endAt,
customerId: customerId.value || undefined,
keyword: keyword.value.trim() || undefined,
eventIdList: actionFilter.value.length === 1 ? actionFilter.value : selectedAllEvent ? undefined : actionFilter.value
eventIdList: actionFilter.value.length === 1 ? actionFilter.value : selectedAllEvent ? undefined : actionFilter.value,
verificationStatus: verificationFilter.value === "ALL" ? undefined : verificationFilter.value
};
}
@@ -548,6 +642,11 @@ function parseEventIdList(value: unknown): string[] {
return text.split(",").map(item => item.trim()).filter(Boolean);
}
function parseVerificationFilter(value: unknown): VerificationFilter {
const text = Array.isArray(value) ? String(value[0] || "") : String(value || "");
return verificationOptions.some(item => item.value === text) ? text as VerificationFilter : "ALL";
}
function buildFilterQuery(transitionDepth = getQueryText("transitionDepth")): Record<string, string> {
const query = {
keyword: keyword.value.trim(),
@@ -556,7 +655,8 @@ function buildFilterQuery(transitionDepth = getQueryText("transitionDepth")): Re
endAt: dateRange.value[1] || "",
customerId: customerId.value,
customerName: getQueryText("customerName"),
eventIds: actionFilter.value.join(",")
eventIds: actionFilter.value.join(","),
verificationStatus: verificationFilter.value === "ALL" ? "" : verificationFilter.value
};
if (isValidTransitionDepth(transitionDepth)) {
return {
@@ -767,9 +867,20 @@ onUnmounted(() => {
width: 3.5rem;
height: 3.5rem;
}
.description-line {
gap: .5rem;
display: flex;
align-items: center;
}
}
.delete {
.actions {
display: flex;
height: 100%;
}
.action {
height: 100%;
padding: 0 1rem;
border-radius: 0;