add event verify
This commit is contained in:
@@ -32,6 +32,12 @@ async function update(record: EventRecord): Promise<void> {
|
|||||||
await axios.post(`${BASE_URI}/update`, withCurrentStoreId(record));
|
await axios.post(`${BASE_URI}/update`, withCurrentStoreId(record));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function verify(id: string): Promise<void> {
|
||||||
|
await axios.post(`${BASE_URI}/verify`, undefined, {
|
||||||
|
params: { id }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function remove(id: string): Promise<void> {
|
async function remove(id: string): Promise<void> {
|
||||||
await axios.post(`${BASE_URI}/delete`, undefined, {
|
await axios.post(`${BASE_URI}/delete`, undefined, {
|
||||||
params: { id }
|
params: { id }
|
||||||
@@ -56,6 +62,7 @@ export default {
|
|||||||
createBatch,
|
createBatch,
|
||||||
createWithCustomer,
|
createWithCustomer,
|
||||||
update,
|
update,
|
||||||
|
verify,
|
||||||
remove,
|
remove,
|
||||||
eventRank
|
eventRank
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
<t-cell v-if="event.valueType === 'NUMBER'" title="数值单位" :note="getValueUnitText(event.valueUnit)" />
|
<t-cell v-if="event.valueType === 'NUMBER'" title="数值单位" :note="getValueUnitText(event.valueUnit)" />
|
||||||
<t-cell v-if="event.valueType === 'NUMBER'" title="登记必填数值" :note="event.requireValue ? '是' : '否'" />
|
<t-cell v-if="event.valueType === 'NUMBER'" title="登记必填数值" :note="event.requireValue ? '是' : '否'" />
|
||||||
<t-cell title="需要拍照" :note="event.requirePhoto ? '是' : '否'" />
|
<t-cell title="需要拍照" :note="event.requirePhoto ? '是' : '否'" />
|
||||||
|
<t-cell title="需要核销" :note="event.requireVerification ? '是' : '否'" />
|
||||||
<t-cell title="次数约束" :note="getLimitText(event)" />
|
<t-cell title="次数约束" :note="getLimitText(event)" />
|
||||||
</t-cell-group>
|
</t-cell-group>
|
||||||
<t-cell-group v-entrance-transition="210" v-if="event" title="生效时间" theme="card">
|
<t-cell-group v-entrance-transition="210" v-if="event" title="生效时间" theme="card">
|
||||||
|
|||||||
@@ -79,6 +79,9 @@
|
|||||||
<t-form-item label="需要拍照" content-align="right">
|
<t-form-item label="需要拍照" content-align="right">
|
||||||
<t-switch v-model="formModel.requirePhoto" />
|
<t-switch v-model="formModel.requirePhoto" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
|
<t-form-item label="需要核销" content-align="right">
|
||||||
|
<t-switch v-model="formModel.requireVerification" />
|
||||||
|
</t-form-item>
|
||||||
<t-form-item label="状态" arrow @click="openStatusPicker">
|
<t-form-item label="状态" arrow @click="openStatusPicker">
|
||||||
<div class="picker-text" v-text="getStatusText(formModel.status)" />
|
<div class="picker-text" v-text="getStatusText(formModel.status)" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
@@ -265,6 +268,7 @@ const formModel = reactive({
|
|||||||
requireValue: false,
|
requireValue: false,
|
||||||
status: "ACTIVE" as EventStatus,
|
status: "ACTIVE" as EventStatus,
|
||||||
requirePhoto: false,
|
requirePhoto: false,
|
||||||
|
requireVerification: false,
|
||||||
limitType: "NONE" as EventLimitType,
|
limitType: "NONE" as EventLimitType,
|
||||||
limitValueText: "",
|
limitValueText: "",
|
||||||
beginAtText: "",
|
beginAtText: "",
|
||||||
@@ -633,6 +637,7 @@ async function saveEventItem(): Promise<void> {
|
|||||||
requireValue: isNumberValueType(formModel.valueType) && formModel.requireValue,
|
requireValue: isNumberValueType(formModel.valueType) && formModel.requireValue,
|
||||||
status: formModel.status,
|
status: formModel.status,
|
||||||
requirePhoto: formModel.requirePhoto,
|
requirePhoto: formModel.requirePhoto,
|
||||||
|
requireVerification: formModel.requireVerification,
|
||||||
limitType: formModel.limitType,
|
limitType: formModel.limitType,
|
||||||
limitValue: resolveLimitValue(),
|
limitValue: resolveLimitValue(),
|
||||||
beginAt: resolveTimestamp(formModel.beginAtText),
|
beginAt: resolveTimestamp(formModel.beginAtText),
|
||||||
@@ -726,6 +731,7 @@ async function fillForm(): Promise<void> {
|
|||||||
requireValue: current.requireValue ?? false,
|
requireValue: current.requireValue ?? false,
|
||||||
status: current.status === "DRAFT" ? "DRAFT" : current.status === "INACTIVE" ? "INACTIVE" : "ACTIVE",
|
status: current.status === "DRAFT" ? "DRAFT" : current.status === "INACTIVE" ? "INACTIVE" : "ACTIVE",
|
||||||
requirePhoto: current.requirePhoto ?? false,
|
requirePhoto: current.requirePhoto ?? false,
|
||||||
|
requireVerification: current.requireVerification ?? false,
|
||||||
limitType: current.limitType || "NONE",
|
limitType: current.limitType || "NONE",
|
||||||
beginAtText: current.beginAt ? Time.toDateTime(current.beginAt) : "",
|
beginAtText: current.beginAt ? Time.toDateTime(current.beginAt) : "",
|
||||||
endAtText: current.endAt ? Time.toDateTime(current.endAt) : "",
|
endAtText: current.endAt ? Time.toDateTime(current.endAt) : "",
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
:content="getStatusTag(item).text"
|
:content="getStatusTag(item).text"
|
||||||
/>
|
/>
|
||||||
<t-tag v-if="item.requirePhoto" theme="warning" variant="outline">需拍照</t-tag>
|
<t-tag v-if="item.requirePhoto" theme="warning" variant="outline">需拍照</t-tag>
|
||||||
|
<t-tag v-if="item.requireVerification" theme="warning" variant="outline">需核销</t-tag>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #description>
|
<template #description>
|
||||||
|
|||||||
@@ -15,6 +15,13 @@
|
|||||||
:hover="canReadEvent && !!(record.event?.id || record.eventId)"
|
:hover="canReadEvent && !!(record.event?.id || record.eventId)"
|
||||||
@click="toEventDetail"
|
@click="toEventDetail"
|
||||||
/>
|
/>
|
||||||
|
<t-cell title="核销状态" :note="getVerificationStatusText(record.verificationStatus)" />
|
||||||
|
<t-cell v-if="record.verificationStatus === 'VERIFIED'" title="核销人" :note="Text.display(record.verifier?.nick || record.verifier?.name)" />
|
||||||
|
<t-cell v-if="record.verificationStatus === 'VERIFIED'" title="核销时间">
|
||||||
|
<template #note>
|
||||||
|
<pass-time-label :timestamp="record.verifiedAt" underline />
|
||||||
|
</template>
|
||||||
|
</t-cell>
|
||||||
<t-cell v-if="record.numberValue !== undefined && record.numberValue !== null" title="事件数值" :note="getNumberValueText(record)" />
|
<t-cell v-if="record.numberValue !== undefined && record.numberValue !== null" title="事件数值" :note="getNumberValueText(record)" />
|
||||||
<t-cell v-if="record.pointValue !== undefined && record.pointValue !== null" title="提交积分值" :note="getPointValueText(record.pointValue, pointLedgerChangeType)" />
|
<t-cell v-if="record.pointValue !== undefined && record.pointValue !== null" title="提交积分值" :note="getPointValueText(record.pointValue, pointLedgerChangeType)" />
|
||||||
<t-cell v-if="record.earnedPointValue !== undefined && record.earnedPointValue !== null" title="本次积分变化" :note="getPointValueText(record.earnedPointValue, pointLedgerChangeType)" />
|
<t-cell v-if="record.earnedPointValue !== undefined && record.earnedPointValue !== null" title="本次积分变化" :note="getPointValueText(record.earnedPointValue, pointLedgerChangeType)" />
|
||||||
@@ -26,7 +33,17 @@
|
|||||||
<t-cell title="登记人" :note="Text.display(record.operator?.nick || record.operator?.name)" />
|
<t-cell title="登记人" :note="Text.display(record.operator?.nick || record.operator?.name)" />
|
||||||
<t-cell title="备注" :note="Text.display(record.remark)" />
|
<t-cell title="备注" :note="Text.display(record.remark)" />
|
||||||
</t-cell-group>
|
</t-cell-group>
|
||||||
<div v-if="record" class="photo-wrapper" v-entrance-transition="140">
|
<t-button
|
||||||
|
v-if="canVerifyRecord && record?.verificationStatus === 'PENDING'"
|
||||||
|
class="verify-button cell-button"
|
||||||
|
theme="primary"
|
||||||
|
:loading="isVerifying"
|
||||||
|
@click="confirmVerifyRecord"
|
||||||
|
v-entrance-transition="140"
|
||||||
|
>
|
||||||
|
核销登记记录
|
||||||
|
</t-button>
|
||||||
|
<div v-if="record" class="photo-wrapper" v-entrance-transition="210">
|
||||||
<photo-scroller
|
<photo-scroller
|
||||||
:model-value="recordPhotoList"
|
:model-value="recordPhotoList"
|
||||||
:photo-items="recordPhotoItemList"
|
:photo-items="recordPhotoItemList"
|
||||||
@@ -37,11 +54,12 @@
|
|||||||
title="登记照片"
|
title="登记照片"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<t-empty v-else :description="isLoading ? '正在加载登记事件记录' : '登记事件记录不存在'" />
|
<t-empty v-if="!record" :description="isLoading ? '正在加载登记事件记录' : '登记事件记录不存在'" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { DialogPlugin } from "tdesign-mobile-vue";
|
||||||
import { PassTimeLabel, Text } from "timi-web";
|
import { PassTimeLabel, Text } from "timi-web";
|
||||||
import { useUserStore, type Attachment } from "timi-web-api";
|
import { useUserStore, type Attachment } from "timi-web-api";
|
||||||
import EventRecordAPI from "@/api/EventRecordAPI";
|
import EventRecordAPI from "@/api/EventRecordAPI";
|
||||||
@@ -53,6 +71,8 @@ import type { PhotoItem } from "@/types/Photo";
|
|||||||
import type { PointLedgerChangeType } from "@/types/Point";
|
import type { PointLedgerChangeType } from "@/types/Point";
|
||||||
import { getAttachmentReadURL } from "@/utils/attachment";
|
import { getAttachmentReadURL } from "@/utils/attachment";
|
||||||
import { formatEventNumberValue } from "@/utils/eventValue";
|
import { formatEventNumberValue } from "@/utils/eventValue";
|
||||||
|
import { getErrorMessage } from "@/utils/error";
|
||||||
|
import { Message } from "@/utils/message";
|
||||||
import { permissionCodes } from "@/utils/permission";
|
import { permissionCodes } from "@/utils/permission";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
@@ -68,8 +88,10 @@ const userStore = useUserStore();
|
|||||||
|
|
||||||
const record = ref<EventRecord>();
|
const record = ref<EventRecord>();
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
|
const isVerifying = ref(false);
|
||||||
|
|
||||||
const canUpdateRecord = computed(() => userStore.hasPermissions(permissionCodes.eventRecord.update));
|
const canUpdateRecord = computed(() => userStore.hasPermissions(permissionCodes.eventRecord.update));
|
||||||
|
const canVerifyRecord = computed(() => userStore.hasPermissions(permissionCodes.eventRecord.verify));
|
||||||
const canReadEvent = computed(() => userStore.hasPermissions(permissionCodes.event.read));
|
const canReadEvent = computed(() => userStore.hasPermissions(permissionCodes.event.read));
|
||||||
const recordPhotoItemList = computed(() => (record.value?.attachmentList || [])
|
const recordPhotoItemList = computed(() => (record.value?.attachmentList || [])
|
||||||
.filter(item => !!item.id)
|
.filter(item => !!item.id)
|
||||||
@@ -121,6 +143,19 @@ function getNumberValueText(value: EventRecord): string {
|
|||||||
return formatEventNumberValue(value.event, value.numberValue);
|
return formatEventNumberValue(value.event, value.numberValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getVerificationStatusText(status?: EventRecord["verificationStatus"]): string {
|
||||||
|
if (status === "PENDING") {
|
||||||
|
return "待核销";
|
||||||
|
}
|
||||||
|
if (status === "VERIFIED") {
|
||||||
|
return "已核销";
|
||||||
|
}
|
||||||
|
if (status === "NOT_REQUIRED") {
|
||||||
|
return "无需核销";
|
||||||
|
}
|
||||||
|
return "未知";
|
||||||
|
}
|
||||||
|
|
||||||
const pointLedgerChangeType = computed(() => record.value?.changeType || parsePointLedgerChangeType(route.query.changeType));
|
const pointLedgerChangeType = computed(() => record.value?.changeType || parsePointLedgerChangeType(route.query.changeType));
|
||||||
|
|
||||||
function getPointValueText(value?: number | null, changeType?: PointLedgerChangeType): string {
|
function getPointValueText(value?: number | null, changeType?: PointLedgerChangeType): string {
|
||||||
@@ -161,6 +196,44 @@ async function loadRecord(): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function confirmVerifyRecord(): Promise<void> {
|
||||||
|
const currentRecord = record.value;
|
||||||
|
if (!currentRecord?.id || currentRecord.verificationStatus !== "PENDING") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dialog = DialogPlugin.confirm({
|
||||||
|
title: "核销登记记录",
|
||||||
|
content: `确定核销 ${currentRecord.customer?.name || "未知客户"} 的 ${currentRecord.event?.name || "未知事件"} 记录吗?`,
|
||||||
|
cancelBtn: "取消",
|
||||||
|
confirmBtn: "核销",
|
||||||
|
onCancel() {
|
||||||
|
dialog.destroy();
|
||||||
|
},
|
||||||
|
async onConfirm() {
|
||||||
|
dialog.destroy();
|
||||||
|
await verifyRecord(currentRecord.id || "");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function verifyRecord(id: string): Promise<void> {
|
||||||
|
if (!id || isVerifying.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isVerifying.value = true;
|
||||||
|
try {
|
||||||
|
await EventRecordAPI.verify(id);
|
||||||
|
record.value = await EventRecordAPI.detail(id);
|
||||||
|
Message.success("登记记录已核销");
|
||||||
|
} catch (error) {
|
||||||
|
Message.error(getErrorMessage(error, "登记记录核销失败"));
|
||||||
|
} finally {
|
||||||
|
isVerifying.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- 导航 ----------
|
// ---------- 导航 ----------
|
||||||
|
|
||||||
async function toEdit(): Promise<void> {
|
async function toEdit(): Promise<void> {
|
||||||
@@ -239,6 +312,11 @@ onUnmounted(() => {
|
|||||||
box-shadow: var(--td-shadow-1);
|
box-shadow: var(--td-shadow-1);
|
||||||
border-radius: 1.4rem;
|
border-radius: 1.4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.verify-button {
|
||||||
|
width: calc(100% - 2rem);
|
||||||
|
margin: 0 1rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(html.theme-dark) {
|
:global(html.theme-dark) {
|
||||||
|
|||||||
@@ -23,6 +23,11 @@
|
|||||||
:options="actionOptions"
|
:options="actionOptions"
|
||||||
multiple
|
multiple
|
||||||
/>
|
/>
|
||||||
|
<t-dropdown-item
|
||||||
|
v-model="verificationFilter"
|
||||||
|
:label="verificationFilterLabel"
|
||||||
|
:options="verificationOptions"
|
||||||
|
/>
|
||||||
</t-dropdown-menu>
|
</t-dropdown-menu>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -64,12 +69,22 @@
|
|||||||
<t-cell
|
<t-cell
|
||||||
class="cell"
|
class="cell"
|
||||||
:title="getRecordEventName(item)"
|
:title="getRecordEventName(item)"
|
||||||
:description="getRecordCustomerName(item)"
|
|
||||||
:note="getRecordRegisteredAt(item)"
|
:note="getRecordRegisteredAt(item)"
|
||||||
arrow
|
arrow
|
||||||
hover
|
hover
|
||||||
@click="toDetail(item.id || '')"
|
@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>
|
<template #leftIcon>
|
||||||
<t-image
|
<t-image
|
||||||
v-if="getRecordPhotoURL(item)"
|
v-if="getRecordPhotoURL(item)"
|
||||||
@@ -81,15 +96,27 @@
|
|||||||
<div v-else class="avatar fallback" v-text="getRecordCustomerInitial(item)" />
|
<div v-else class="avatar fallback" v-text="getRecordCustomerInitial(item)" />
|
||||||
</template>
|
</template>
|
||||||
</t-cell>
|
</t-cell>
|
||||||
<template v-if="canDeleteRecord" #right>
|
<template v-if="canVerifyRecord || canDeleteRecord" #right>
|
||||||
<t-button
|
<div class="actions">
|
||||||
class="delete"
|
<t-button
|
||||||
theme="danger"
|
v-if="canVerifyRecord && item.verificationStatus === 'PENDING'"
|
||||||
:loading="isDeleting && deletingId === item.id"
|
class="action verify"
|
||||||
@click.stop="confirmRemoveRecord(item)"
|
theme="primary"
|
||||||
>
|
:loading="isVerifying && verifyingId === item.id"
|
||||||
删除
|
@click.stop="confirmVerifyRecord(item)"
|
||||||
</t-button>
|
>
|
||||||
|
核销
|
||||||
|
</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>
|
</template>
|
||||||
</t-swipe-cell>
|
</t-swipe-cell>
|
||||||
</t-cell-group>
|
</t-cell-group>
|
||||||
@@ -133,7 +160,7 @@ import EventAPI from "@/api/EventAPI";
|
|||||||
import EventRecordAPI from "@/api/EventRecordAPI";
|
import EventRecordAPI from "@/api/EventRecordAPI";
|
||||||
import { useNavBarStore } from "@/store/navBarStore";
|
import { useNavBarStore } from "@/store/navBarStore";
|
||||||
import type { EventItem } from "@/types/EventItem";
|
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 type { NavBarAction } from "@/types/NavBarAction";
|
||||||
import { getCustomerPhotoURL } from "@/utils/customer";
|
import { getCustomerPhotoURL } from "@/utils/customer";
|
||||||
import { getErrorMessage } from "@/utils/error";
|
import { getErrorMessage } from "@/utils/error";
|
||||||
@@ -142,6 +169,7 @@ import { permissionCodes } from "@/utils/permission";
|
|||||||
import { Message } from "@/utils/message.ts";
|
import { Message } from "@/utils/message.ts";
|
||||||
|
|
||||||
type DateFilterType = "today" | "yesterday" | "beforeYesterday" | "lastWeek" | "all" | "custom";
|
type DateFilterType = "today" | "yesterday" | "beforeYesterday" | "lastWeek" | "all" | "custom";
|
||||||
|
type VerificationFilter = "ALL" | VerificationStatus;
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "EventRecordList"
|
name: "EventRecordList"
|
||||||
@@ -170,6 +198,13 @@ const dateRange = ref(parseDateRange(dateFilterType.value));
|
|||||||
const startDraft = ref("");
|
const startDraft = ref("");
|
||||||
const endDraft = ref("");
|
const endDraft = ref("");
|
||||||
const actionFilter = ref<string[]>([]);
|
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 eventList = ref<EventItem[]>([]);
|
||||||
const startVisible = ref(false);
|
const startVisible = ref(false);
|
||||||
const endVisible = ref(false);
|
const endVisible = ref(false);
|
||||||
@@ -193,6 +228,7 @@ const eventFilterLabel = computed(() => {
|
|||||||
.map(item => item.name || "未命名事件")
|
.map(item => item.name || "未命名事件")
|
||||||
.join("、");
|
.join("、");
|
||||||
});
|
});
|
||||||
|
const verificationFilterLabel = computed(() => verificationOptions.find(item => item.value === verificationFilter.value)?.label || "全部核销状态");
|
||||||
const selectedNumberEvent = computed(() => {
|
const selectedNumberEvent = computed(() => {
|
||||||
if (actionFilter.value.length !== 1) {
|
if (actionFilter.value.length !== 1) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -202,6 +238,7 @@ const selectedNumberEvent = computed(() => {
|
|||||||
});
|
});
|
||||||
const canExportRecord = computed(() => userStore.hasPermissions(permissionCodes.eventRecord.export));
|
const canExportRecord = computed(() => userStore.hasPermissions(permissionCodes.eventRecord.export));
|
||||||
const canDeleteRecord = computed(() => userStore.hasPermissions(permissionCodes.eventRecord.delete));
|
const canDeleteRecord = computed(() => userStore.hasPermissions(permissionCodes.eventRecord.delete));
|
||||||
|
const canVerifyRecord = computed(() => userStore.hasPermissions(permissionCodes.eventRecord.verify));
|
||||||
const canShowActionMenu = computed(() => canCreateRecord.value || canExportRecord.value);
|
const canShowActionMenu = computed(() => canCreateRecord.value || canExportRecord.value);
|
||||||
|
|
||||||
const actionList = computed<NavBarAction[]>(() => {
|
const actionList = computed<NavBarAction[]>(() => {
|
||||||
@@ -246,6 +283,14 @@ watch(actionFilter, async () => {
|
|||||||
await reloadRecords();
|
await reloadRecords();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch(verificationFilter, async () => {
|
||||||
|
if (!initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await replaceFilterQuery();
|
||||||
|
await reloadRecords();
|
||||||
|
});
|
||||||
|
|
||||||
watch(dateRange, async (value) => {
|
watch(dateRange, async (value) => {
|
||||||
if (!initialized || dateFilterType.value !== "custom") {
|
if (!initialized || dateFilterType.value !== "custom") {
|
||||||
return;
|
return;
|
||||||
@@ -335,6 +380,8 @@ async function loadRecords(): Promise<void> {
|
|||||||
|
|
||||||
const isDeleting = ref(false);
|
const isDeleting = ref(false);
|
||||||
const deletingId = ref("");
|
const deletingId = ref("");
|
||||||
|
const isVerifying = ref(false);
|
||||||
|
const verifyingId = ref("");
|
||||||
|
|
||||||
async function loadActions(): Promise<void> {
|
async function loadActions(): Promise<void> {
|
||||||
eventList.value = await EventAPI.listValid();
|
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> {
|
async function toDetail(id: string): Promise<void> {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return;
|
return;
|
||||||
@@ -501,6 +588,12 @@ function getRecordCustomerInitial(record: EventRecordListItemView): string {
|
|||||||
return getRecordCustomerName(record).slice(0, 1);
|
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 {
|
function getRecordRegisteredAt(record: EventRecordListItemView): string {
|
||||||
if (!record.registeredAt) {
|
if (!record.registeredAt) {
|
||||||
return "-";
|
return "-";
|
||||||
@@ -519,7 +612,8 @@ function buildRangeReq(): Omit<EventRecordPage, "index" | "size"> {
|
|||||||
endAt,
|
endAt,
|
||||||
customerId: customerId.value || undefined,
|
customerId: customerId.value || undefined,
|
||||||
keyword: keyword.value.trim() || 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);
|
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> {
|
function buildFilterQuery(transitionDepth = getQueryText("transitionDepth")): Record<string, string> {
|
||||||
const query = {
|
const query = {
|
||||||
keyword: keyword.value.trim(),
|
keyword: keyword.value.trim(),
|
||||||
@@ -556,7 +655,8 @@ function buildFilterQuery(transitionDepth = getQueryText("transitionDepth")): Re
|
|||||||
endAt: dateRange.value[1] || "",
|
endAt: dateRange.value[1] || "",
|
||||||
customerId: customerId.value,
|
customerId: customerId.value,
|
||||||
customerName: getQueryText("customerName"),
|
customerName: getQueryText("customerName"),
|
||||||
eventIds: actionFilter.value.join(",")
|
eventIds: actionFilter.value.join(","),
|
||||||
|
verificationStatus: verificationFilter.value === "ALL" ? "" : verificationFilter.value
|
||||||
};
|
};
|
||||||
if (isValidTransitionDepth(transitionDepth)) {
|
if (isValidTransitionDepth(transitionDepth)) {
|
||||||
return {
|
return {
|
||||||
@@ -767,9 +867,20 @@ onUnmounted(() => {
|
|||||||
width: 3.5rem;
|
width: 3.5rem;
|
||||||
height: 3.5rem;
|
height: 3.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.description-line {
|
||||||
|
gap: .5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.delete {
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 0 1rem;
|
padding: 0 1rem;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export type EventItem = {
|
|||||||
requireValue?: boolean;
|
requireValue?: boolean;
|
||||||
status?: EventStatus;
|
status?: EventStatus;
|
||||||
requirePhoto?: boolean;
|
requirePhoto?: boolean;
|
||||||
|
requireVerification?: boolean;
|
||||||
limitType?: EventLimitType;
|
limitType?: EventLimitType;
|
||||||
limitValue?: number;
|
limitValue?: number;
|
||||||
beginAt?: number;
|
beginAt?: number;
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import type { EventItem } from "@/types/EventItem";
|
|||||||
import type { PointLedgerChangeType } from "@/types/Point";
|
import type { PointLedgerChangeType } from "@/types/Point";
|
||||||
|
|
||||||
export type EventRecord = {
|
export type EventRecord = {
|
||||||
|
verificationStatus?: VerificationStatus;
|
||||||
|
verifierUserId?: string;
|
||||||
|
verifiedAt?: number;
|
||||||
storeId?: string;
|
storeId?: string;
|
||||||
customerId?: string;
|
customerId?: string;
|
||||||
customerIdList?: string[];
|
customerIdList?: string[];
|
||||||
@@ -21,9 +24,12 @@ export type EventRecord = {
|
|||||||
customer?: Customer;
|
customer?: Customer;
|
||||||
event?: EventItem;
|
event?: EventItem;
|
||||||
operator?: Partial<User>;
|
operator?: Partial<User>;
|
||||||
|
verifier?: Partial<User>;
|
||||||
attachmentList?: Attachment[];
|
attachmentList?: Attachment[];
|
||||||
} & Model;
|
} & Model;
|
||||||
|
|
||||||
|
export type VerificationStatus = "NOT_REQUIRED" | "PENDING" | "VERIFIED";
|
||||||
|
|
||||||
export type EventRecordListItemView = {
|
export type EventRecordListItemView = {
|
||||||
id?: string;
|
id?: string;
|
||||||
customerId?: string;
|
customerId?: string;
|
||||||
@@ -31,6 +37,9 @@ export type EventRecordListItemView = {
|
|||||||
eventId?: string;
|
eventId?: string;
|
||||||
event?: EventItem;
|
event?: EventItem;
|
||||||
registeredAt?: number;
|
registeredAt?: number;
|
||||||
|
verificationStatus?: VerificationStatus;
|
||||||
|
verifierUserId?: string;
|
||||||
|
verifiedAt?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EventRecordPageResult = PageResult<EventRecordListItemView> & {
|
export type EventRecordPageResult = PageResult<EventRecordListItemView> & {
|
||||||
@@ -41,6 +50,7 @@ export type EventRecordPage = {
|
|||||||
storeId?: string;
|
storeId?: string;
|
||||||
customerId?: string;
|
customerId?: string;
|
||||||
eventIdList?: string[];
|
eventIdList?: string[];
|
||||||
|
verificationStatus?: VerificationStatus;
|
||||||
keyword?: string;
|
keyword?: string;
|
||||||
beginAt?: number;
|
beginAt?: number;
|
||||||
endAt?: number;
|
endAt?: number;
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export const permissionCodes = {
|
|||||||
eventRecord: {
|
eventRecord: {
|
||||||
create: "GAO:EVENT_RECORD:CREATE",
|
create: "GAO:EVENT_RECORD:CREATE",
|
||||||
read: "GAO:EVENT_RECORD:READ",
|
read: "GAO:EVENT_RECORD:READ",
|
||||||
|
verify: "GAO:EVENT_RECORD:VERIFY",
|
||||||
export: "GAO:EVENT_RECORD:EXPORT",
|
export: "GAO:EVENT_RECORD:EXPORT",
|
||||||
update: "GAO:EVENT_RECORD:UPDATE",
|
update: "GAO:EVENT_RECORD:UPDATE",
|
||||||
delete: "GAO:EVENT_RECORD:DELETE"
|
delete: "GAO:EVENT_RECORD:DELETE"
|
||||||
@@ -117,6 +118,7 @@ export const gaoStoreScopePermissionCodes = [
|
|||||||
permissionCodes.event.delete,
|
permissionCodes.event.delete,
|
||||||
permissionCodes.eventRecord.create,
|
permissionCodes.eventRecord.create,
|
||||||
permissionCodes.eventRecord.read,
|
permissionCodes.eventRecord.read,
|
||||||
|
permissionCodes.eventRecord.verify,
|
||||||
permissionCodes.eventRecord.export,
|
permissionCodes.eventRecord.export,
|
||||||
permissionCodes.eventRecord.update,
|
permissionCodes.eventRecord.update,
|
||||||
permissionCodes.eventRecord.delete,
|
permissionCodes.eventRecord.delete,
|
||||||
|
|||||||
Reference in New Issue
Block a user