814 lines
22 KiB
Vue
814 lines
22 KiB
Vue
<template>
|
||
<div v-can="permissionCodes.eventRecord.read" class="registration-record-list">
|
||
<div class="search-bar">
|
||
<div class="search-date">
|
||
<t-input
|
||
v-model.trim="keyword"
|
||
class="search-input"
|
||
type="search"
|
||
borderless
|
||
placeholder="搜索客户、姓名拼音、编码、事件、备注、登记人"
|
||
>
|
||
<template #suffix-icon>
|
||
<t-button
|
||
size="extra-small"
|
||
theme="primary"
|
||
variant="outline"
|
||
content="选择客户"
|
||
@click.stop="customerSelectorVisible = true"
|
||
/>
|
||
</template>
|
||
</t-input>
|
||
<t-dropdown-menu class="date-filter">
|
||
<t-dropdown-item
|
||
v-model="dateFilterType"
|
||
:label="dateFilterLabel"
|
||
:options="dateOptions"
|
||
@change="selectDateFilter"
|
||
/>
|
||
</t-dropdown-menu>
|
||
</div>
|
||
<t-dropdown-menu class="filter-menu">
|
||
<t-dropdown-item
|
||
v-model="actionFilter"
|
||
:label="eventFilterLabel"
|
||
options-columns="2"
|
||
:options="actionOptions"
|
||
multiple
|
||
/>
|
||
<t-dropdown-item
|
||
v-model="verificationFilter"
|
||
:label="verificationFilterLabel"
|
||
:options="verificationOptions"
|
||
/>
|
||
<t-dropdown-item
|
||
v-model="optionFilter"
|
||
:label="optionFilterLabel"
|
||
:footer="!optionFilterOptions.length ? optionFilterUnavailableFooter : undefined"
|
||
options-columns="2"
|
||
:options="optionFilterOptions"
|
||
multiple
|
||
/>
|
||
</t-dropdown-menu>
|
||
</div>
|
||
<t-pull-down-refresh
|
||
v-model="isRefreshing"
|
||
:refresh-timeout="30000"
|
||
@refresh="reloadRecords"
|
||
@scrolltolower="loadRecords"
|
||
>
|
||
<div class="list-wrapper">
|
||
<t-cell-group v-entrance-transition class="summary" theme="card">
|
||
<t-cell
|
||
title="开始时间"
|
||
:note="dateRange[0] || '-'"
|
||
arrow
|
||
@click="openStartTime"
|
||
/>
|
||
<t-cell
|
||
title="结束时间"
|
||
:note="dateRange[1] || '-'"
|
||
arrow
|
||
@click="openEndTime"
|
||
/>
|
||
<t-cell title="事件" :note="eventFilterLabel" />
|
||
<t-cell v-if="optionFilterLabel !== '全部选项'" title="选项" :note="optionFilterLabel" />
|
||
<t-cell title="记录数" :note="Text.display(pageResult.total)" />
|
||
<t-cell title="客户数" :note="Text.display(pageResult.customerCount)" />
|
||
<t-cell v-if="summaryNumberValueText" title="数值合计" :note="summaryNumberValueText" />
|
||
</t-cell-group>
|
||
<t-cell-group v-if="hasSelectedOption && optionStatList.length" title="选项统计" class="option-stat" theme="card">
|
||
<t-cell
|
||
v-for="item in optionStatList"
|
||
:key="`${item.groupId}-${item.optionId}`"
|
||
:title="`${item.groupName || '选项组'}:${item.optionLabel || '未命名选项'}`"
|
||
:note="`${item.recordCount || 0} 条`"
|
||
/>
|
||
</t-cell-group>
|
||
<t-cell-group
|
||
v-if="pageResult.list.length"
|
||
class="record-list"
|
||
theme="card"
|
||
>
|
||
<event-record-cell
|
||
v-entrance-transition="index * 30"
|
||
v-for="(item, index) in pageResult.list"
|
||
:key="item.id"
|
||
:record="item"
|
||
@updated="reloadRecords"
|
||
/>
|
||
</t-cell-group>
|
||
<div v-else class="center-tips" v-text="isLoading ? '正在加载登记事件记录' : '暂无登记事件记录'" />
|
||
<div v-if="pageResult.list.length" class="center-tips" v-text="loadStateText" />
|
||
</div>
|
||
</t-pull-down-refresh>
|
||
</div>
|
||
<nav-bar-action-menu v-if="canShowActionMenu" :actions="actionList" />
|
||
<customer-selector
|
||
v-model="customerId"
|
||
v-model:visible="customerSelectorVisible"
|
||
@change="handleCustomerChange"
|
||
/>
|
||
<t-popup v-model="startVisible" placement="bottom">
|
||
<t-date-time-picker
|
||
v-if="startVisible"
|
||
v-model="startDraft"
|
||
format="YYYY-MM-DD HH:mm"
|
||
:mode="['date', 'minute']"
|
||
title="开始时间"
|
||
:default-value="dateRange[0]"
|
||
@cancel="cancelStartTime"
|
||
@confirm="confirmStartTime"
|
||
/>
|
||
</t-popup>
|
||
<t-popup v-model="endVisible" placement="bottom">
|
||
<t-date-time-picker
|
||
v-if="endVisible"
|
||
v-model="endDraft"
|
||
:mode="['date', 'minute']"
|
||
format="YYYY-MM-DD HH:mm"
|
||
title="结束时间"
|
||
:default-value="dateRange[1]"
|
||
@cancel="cancelEndTime"
|
||
@confirm="confirmEndTime"
|
||
/>
|
||
</t-popup>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import type { DropdownValue } from "tdesign-mobile-vue";
|
||
import { Time, Text, WebToolkit } from "timi-web";
|
||
import { useUserStore } from "timi-web-api";
|
||
import EventAPI from "@/api/EventAPI";
|
||
import EventRecordAPI from "@/api/EventRecordAPI";
|
||
import CustomerSelector from "@/components/CustomerSelector.vue";
|
||
import EventRecordCell from "@/pages/event/component/EventRecordCell.vue";
|
||
import { useNavBarStore } from "@/store/navBarStore";
|
||
import type { EventItem, EventStatus } from "@/types/EventItem";
|
||
import type { EventOptionStat, EventRecordPage, EventRecordPageResult, VerificationStatus } from "@/types/EventRecord";
|
||
import type { NavBarAction } from "@/types/NavBarAction";
|
||
import { isRetiredEvent } from "@/utils/event";
|
||
import { formatEventNumberValue } from "@/utils/eventValue";
|
||
import { buildPresetDateRange, parseDateFilterType, parseDateTime } from "@/utils/date";
|
||
import { permissionCodes } from "@/utils/permission";
|
||
import { Message } from "@/utils/message.ts";
|
||
|
||
type DateFilterType = "today" | "yesterday" | "lastWeek" | "thisMonth" | "lastMonth" | "all" | "custom";
|
||
type VerificationFilter = "ALL" | VerificationStatus;
|
||
|
||
defineOptions({
|
||
name: "EventRecordList"
|
||
});
|
||
|
||
const pageSize = 20;
|
||
const route = useRoute();
|
||
const router = useRouter();
|
||
const navBarStore = useNavBarStore();
|
||
const userStore = useUserStore();
|
||
const dateOptions: Array<{ label: string; value: DateFilterType }> = [
|
||
{ label: "今天", value: "today" },
|
||
{ label: "昨天", value: "yesterday" },
|
||
{ label: "上周", value: "lastWeek" },
|
||
{ label: "本月", value: "thisMonth" },
|
||
{ label: "上个月", value: "lastMonth" },
|
||
{ label: "全部", value: "all" },
|
||
{ label: "自定义范围", value: "custom" }
|
||
];
|
||
|
||
// ---------- 筛选 ----------
|
||
|
||
const keyword = ref(getInitialKeyword());
|
||
const customerId = ref(getQueryText("customerId"));
|
||
const customerSelectorVisible = ref(false);
|
||
const dateFilterType = ref<DateFilterType>(parseDateFilterType(route.query.dateType, dateOptions, "today"));
|
||
const dateRange = ref(parseDateRange(dateFilterType.value));
|
||
const startDraft = ref("");
|
||
const endDraft = ref("");
|
||
const actionFilter = ref<string[]>([]);
|
||
const optionFilter = 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);
|
||
let keywordTimer: number | undefined;
|
||
let initialized = false;
|
||
const isExporting = ref(false);
|
||
let suppressOptionFilterReload = false;
|
||
|
||
const canCreateRecord = computed(() => userStore.hasPermissions(permissionCodes.eventRecord.create));
|
||
const canCreateEvent = computed(() => userStore.hasPermissions(permissionCodes.event.create));
|
||
const dateFilterLabel = computed(() => dateOptions.find(item => item.value === dateFilterType.value)?.label || "今天");
|
||
const actionOptions = computed(() => eventList.value.map(item => ({
|
||
label: getEventDisplayName(item),
|
||
value: item.id || ""
|
||
})).filter(item => item.value));
|
||
const eventFilterLabel = computed(() => {
|
||
if (!actionFilter.value.length || actionFilter.value.length === eventList.value.length) {
|
||
return "全部事件";
|
||
}
|
||
|
||
return eventList.value
|
||
.filter(item => item.id && actionFilter.value.includes(item.id))
|
||
.map(item => getEventDisplayName(item))
|
||
.join("、");
|
||
});
|
||
const selectedOptionEvent = computed(() => {
|
||
if (actionFilter.value.length !== 1) {
|
||
return undefined;
|
||
}
|
||
const event = eventList.value.find(item => item.id === actionFilter.value[0]);
|
||
return event?.optionGroupList?.some(group => group.optionList?.some(option => option.id)) ? event : undefined;
|
||
});
|
||
const optionFilterOptions = computed(() => {
|
||
const event = selectedOptionEvent.value;
|
||
if (!event) {
|
||
return [];
|
||
}
|
||
return (event.optionGroupList || []).flatMap(group => (group.optionList || [])
|
||
.filter(option => option.id)
|
||
.map(option => ({
|
||
label: `${group.name}:${option.label}`,
|
||
value: String(option.id)
|
||
})));
|
||
});
|
||
const optionFilterUnavailableReason = computed(() => actionFilter.value.length === 1
|
||
? "当前选择的事件没有可用选项"
|
||
: "请先只选择一个事件,才能按选项筛选");
|
||
const optionFilterUnavailableFooter = (h: typeof import("vue").h) => h(
|
||
"div",
|
||
{ class: "center-tips red" },
|
||
optionFilterUnavailableReason.value
|
||
);
|
||
const optionFilterLabel = computed(() => {
|
||
if (!optionFilter.value.length) {
|
||
return "全部选项";
|
||
}
|
||
return optionFilterOptions.value
|
||
.filter(item => optionFilter.value.includes(item.value))
|
||
.map(item => item.label)
|
||
.join("、") || "全部选项";
|
||
});
|
||
const verificationFilterLabel = computed(() => verificationOptions.find(item => item.value === verificationFilter.value)?.label || "全部核销状态");
|
||
const selectedNumberEvent = computed(() => {
|
||
if (actionFilter.value.length !== 1) {
|
||
return undefined;
|
||
}
|
||
const event = eventList.value.find(item => item.id === actionFilter.value[0]);
|
||
return event?.valueType === "NUMBER" ? event : undefined;
|
||
});
|
||
const hasSelectedOption = computed(() => !!selectedOptionEvent.value);
|
||
const canExportRecord = computed(() => userStore.hasPermissions(permissionCodes.eventRecord.export));
|
||
const canShowActionMenu = computed(() => canCreateRecord.value || canExportRecord.value);
|
||
|
||
const actionList = computed<NavBarAction[]>(() => {
|
||
const list: NavBarAction[] = [];
|
||
if (canCreateRecord.value) {
|
||
list.push({
|
||
action: toCreate,
|
||
theme: "primary",
|
||
icon: "add",
|
||
key: "create",
|
||
text: "添加"
|
||
});
|
||
}
|
||
if (canExportRecord.value) {
|
||
list.push({
|
||
action: exportExcel,
|
||
icon: "download",
|
||
key: "export",
|
||
loading: isExporting.value,
|
||
text: "导出 Excel"
|
||
});
|
||
}
|
||
return list;
|
||
});
|
||
|
||
async function handleCustomerChange(value: string | string[]): Promise<void> {
|
||
customerId.value = Array.isArray(value) ? String(value[0] || "") : value;
|
||
await replaceFilterQuery();
|
||
await reloadRecords();
|
||
}
|
||
|
||
watch(keyword, () => {
|
||
if (!initialized) {
|
||
return;
|
||
}
|
||
window.clearTimeout(keywordTimer);
|
||
keywordTimer = window.setTimeout(async () => {
|
||
await replaceFilterQuery();
|
||
await reloadRecords();
|
||
}, 300);
|
||
});
|
||
|
||
watch(actionFilter, async () => {
|
||
if (!initialized) {
|
||
return;
|
||
}
|
||
const optionIdSet = new Set(optionFilterOptions.value.map(item => item.value));
|
||
const nextOptionFilter = optionFilter.value.filter(optionId => optionIdSet.has(optionId));
|
||
if (nextOptionFilter.length !== optionFilter.value.length) {
|
||
suppressOptionFilterReload = true;
|
||
optionFilter.value = nextOptionFilter;
|
||
}
|
||
await replaceFilterQuery();
|
||
await reloadRecords();
|
||
});
|
||
|
||
watch(optionFilter, async () => {
|
||
if (!initialized) {
|
||
return;
|
||
}
|
||
if (suppressOptionFilterReload) {
|
||
suppressOptionFilterReload = false;
|
||
return;
|
||
}
|
||
await replaceFilterQuery();
|
||
await reloadRecords();
|
||
});
|
||
|
||
watch(verificationFilter, async () => {
|
||
if (!initialized) {
|
||
return;
|
||
}
|
||
await replaceFilterQuery();
|
||
await reloadRecords();
|
||
});
|
||
|
||
watch(dateRange, async (value) => {
|
||
if (!initialized || dateFilterType.value !== "custom") {
|
||
return;
|
||
}
|
||
|
||
if (value[0] && value[1] && value[1] < value[0]) {
|
||
dateRange.value = [value[1], value[0]];
|
||
return;
|
||
}
|
||
|
||
await replaceFilterQuery();
|
||
await reloadRecords();
|
||
}, { deep: true });
|
||
|
||
// ---------- 列表分页 ----------
|
||
|
||
const isRefreshing = ref(false);
|
||
const isLoading = ref(false);
|
||
const isNoMore = ref(false);
|
||
const page = reactive<EventRecordPage>({
|
||
index: 0,
|
||
size: pageSize
|
||
});
|
||
const pageResult = reactive<EventRecordPageResult>({
|
||
total: 0,
|
||
list: [],
|
||
numberValueTotal: null,
|
||
customerCount: 0
|
||
});
|
||
const optionStatList = ref<EventOptionStat[]>([]);
|
||
let loadToken = 0;
|
||
|
||
const summaryNumberValueText = computed(() => {
|
||
if (!selectedNumberEvent.value || pageResult.numberValueTotal === undefined || pageResult.numberValueTotal === null) {
|
||
return "";
|
||
}
|
||
return `${formatEventNumberValue(selectedNumberEvent.value, pageResult.numberValueTotal)}`;
|
||
});
|
||
const loadStateText = computed(() => {
|
||
if (isLoading.value) {
|
||
return "正在加载更多";
|
||
}
|
||
return isNoMore.value ? "没有更多记录" : "上拉加载更多";
|
||
});
|
||
|
||
async function reloadRecords(): Promise<void> {
|
||
isRefreshing.value = true;
|
||
try {
|
||
page.index = 0;
|
||
pageResult.total = 0;
|
||
pageResult.list = [];
|
||
pageResult.numberValueTotal = null;
|
||
pageResult.customerCount = 0;
|
||
optionStatList.value = [];
|
||
isNoMore.value = false;
|
||
await Promise.all([loadRecords(), loadOptionStats()]);
|
||
} finally {
|
||
isRefreshing.value = false;
|
||
}
|
||
}
|
||
|
||
async function loadRecords(): Promise<void> {
|
||
if (isLoading.value || isNoMore.value) {
|
||
return;
|
||
}
|
||
if (!eventList.value.length) {
|
||
isNoMore.value = true;
|
||
return;
|
||
}
|
||
const token = loadToken + 1;
|
||
loadToken = token;
|
||
isLoading.value = true;
|
||
try {
|
||
const result = await EventRecordAPI.list({
|
||
index: page.index,
|
||
size: page.size,
|
||
...buildRangeReq()
|
||
});
|
||
if (token !== loadToken) {
|
||
return;
|
||
}
|
||
pageResult.list = pageResult.list.concat(result.list || []);
|
||
pageResult.total = result.total || 0;
|
||
pageResult.numberValueTotal = result.numberValueTotal ?? null;
|
||
pageResult.customerCount = result.customerCount ?? 0;
|
||
page.index += 1;
|
||
isNoMore.value = pageResult.list.length >= pageResult.total || (result.list || []).length < page.size;
|
||
} finally {
|
||
if (token === loadToken) {
|
||
isLoading.value = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
// ---------- 操作 ----------
|
||
|
||
async function loadActions(): Promise<void> {
|
||
// 历史登记记录需要关联已停用或已删除的事件,不能使用只返回 ACTIVE 事件的 listValid。
|
||
const result = canCreateEvent.value
|
||
? await EventAPI.listHistory()
|
||
: await EventAPI.list({ index: 0, size: 1000 });
|
||
eventList.value = (result.list || []).filter(item => !isRetiredEvent(item));
|
||
const eventIdList = eventList.value.map(item => item.id).filter((item): item is string => Boolean(item));
|
||
const eventIdSet = new Set(eventIdList);
|
||
const selectedEventIdList = parseEventIdList(route.query.eventIds).filter(eventId => eventIdSet.has(eventId));
|
||
actionFilter.value = selectedEventIdList.length ? selectedEventIdList : eventIdList;
|
||
const optionIdSet = new Set(optionFilterOptions.value.map(item => item.value));
|
||
optionFilter.value = parseEventIdList(route.query.optionIds).filter(optionId => optionIdSet.has(optionId));
|
||
}
|
||
|
||
function getEventDisplayName(event: EventItem): string {
|
||
const name = event.name || "未命名事件";
|
||
const statusText = getEventStatusText(event.status);
|
||
return statusText ? `${name}(${statusText})` : name;
|
||
}
|
||
|
||
function getEventStatusText(status?: EventStatus): string {
|
||
if (status === "DRAFT") {
|
||
return "草稿";
|
||
}
|
||
if (status === "INACTIVE") {
|
||
return "不可登记";
|
||
}
|
||
if (status === "DELETED") {
|
||
return "已删除";
|
||
}
|
||
return "";
|
||
}
|
||
|
||
async function selectDateFilter(type: DropdownValue): Promise<void> {
|
||
if (Array.isArray(type)) {
|
||
return;
|
||
}
|
||
|
||
dateFilterType.value = String(type) as DateFilterType;
|
||
if (dateFilterType.value !== "custom") {
|
||
dateRange.value = buildPresetDateRange(dateFilterType.value);
|
||
await replaceFilterQuery();
|
||
await reloadRecords();
|
||
} else {
|
||
await replaceFilterQuery();
|
||
}
|
||
}
|
||
|
||
function openStartTime(): void {
|
||
startDraft.value = dateRange.value[0];
|
||
startVisible.value = true;
|
||
}
|
||
|
||
function openEndTime(): void {
|
||
endDraft.value = dateRange.value[1];
|
||
endVisible.value = true;
|
||
}
|
||
|
||
function cancelStartTime(): void {
|
||
startVisible.value = false;
|
||
startDraft.value = dateRange.value[0];
|
||
}
|
||
|
||
function cancelEndTime(): void {
|
||
endVisible.value = false;
|
||
endDraft.value = dateRange.value[1];
|
||
}
|
||
|
||
async function confirmStartTime(value?: string | number): Promise<void> {
|
||
startVisible.value = false;
|
||
dateRange.value = [String(value || startDraft.value), dateRange.value[1]];
|
||
await confirmCustomRange();
|
||
}
|
||
|
||
async function confirmEndTime(value?: string | number): Promise<void> {
|
||
endVisible.value = false;
|
||
dateRange.value = [dateRange.value[0], String(value || endDraft.value)];
|
||
await confirmCustomRange();
|
||
}
|
||
|
||
async function confirmCustomRange(): Promise<void> {
|
||
if (dateFilterType.value === "custom") {
|
||
return;
|
||
}
|
||
|
||
dateFilterType.value = "custom";
|
||
await replaceFilterQuery();
|
||
await reloadRecords();
|
||
}
|
||
|
||
async function exportExcel(): Promise<void> {
|
||
if (isExporting.value) {
|
||
return;
|
||
}
|
||
isExporting.value = true;
|
||
try {
|
||
const blob = await EventRecordAPI.exportXSSF({
|
||
index: 0,
|
||
size: pageSize,
|
||
...buildRangeReq()
|
||
});
|
||
WebToolkit.downloadBlob(blob, `登记事件记录-${Time.formatDate(new Date())}.xlsx`);
|
||
Message.success("导出已开始");
|
||
} finally {
|
||
isExporting.value = false;
|
||
}
|
||
}
|
||
|
||
async function loadOptionStats(): Promise<void> {
|
||
if (!hasSelectedOption.value) {
|
||
optionStatList.value = [];
|
||
return;
|
||
}
|
||
try {
|
||
optionStatList.value = await EventRecordAPI.optionStat({
|
||
index: 0,
|
||
size: 1,
|
||
...buildRangeReq()
|
||
});
|
||
} catch (error) {
|
||
optionStatList.value = [];
|
||
console.warn("登记选项统计加载失败", error);
|
||
}
|
||
}
|
||
|
||
async function toCreate(): Promise<void> {
|
||
await router.push({
|
||
path: "/management/registration-event-records/create",
|
||
query: {
|
||
transitionDepth: String(getCurrentRouteDepth() + 1)
|
||
}
|
||
});
|
||
}
|
||
|
||
function buildRangeReq(): Omit<EventRecordPage, "index" | "size"> {
|
||
const [beginAt, endAt] = buildUnixRange();
|
||
const eventIdList = actionFilter.value.length
|
||
? actionFilter.value
|
||
: eventList.value.map(item => item.id).filter((item): item is string => Boolean(item));
|
||
return {
|
||
beginAt,
|
||
endAt,
|
||
customerId: customerId.value || undefined,
|
||
keyword: keyword.value.trim() || undefined,
|
||
eventIdList,
|
||
optionIdList: optionFilter.value.length ? optionFilter.value : undefined,
|
||
verificationStatus: verificationFilter.value === "ALL" ? undefined : verificationFilter.value
|
||
};
|
||
}
|
||
|
||
function getQueryText(key: string): string {
|
||
const value = route.query[key];
|
||
return Array.isArray(value) ? String(value[0] || "") : String(value || "");
|
||
}
|
||
|
||
function parseDateRange(type: DateFilterType): [string, string] {
|
||
if (type !== "custom") {
|
||
return buildPresetDateRange(type);
|
||
}
|
||
|
||
const beginAt = getQueryText("beginAt");
|
||
const endAt = getQueryText("endAt");
|
||
return [beginAt, endAt];
|
||
}
|
||
|
||
function parseEventIdList(value: unknown): string[] {
|
||
const text = Array.isArray(value) ? String(value[0] || "") : String(value || "");
|
||
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(),
|
||
dateType: dateFilterType.value,
|
||
beginAt: dateRange.value[0] || "",
|
||
endAt: dateRange.value[1] || "",
|
||
customerId: customerId.value,
|
||
customerName: getQueryText("customerName"),
|
||
eventIds: actionFilter.value.join(","),
|
||
optionIds: optionFilter.value.join(","),
|
||
verificationStatus: verificationFilter.value === "ALL" ? "" : verificationFilter.value
|
||
};
|
||
if (isValidTransitionDepth(transitionDepth)) {
|
||
return {
|
||
...query,
|
||
transitionDepth
|
||
};
|
||
}
|
||
return query;
|
||
}
|
||
|
||
function getInitialKeyword(): string {
|
||
return getQueryText("keyword") || getQueryText("customerName");
|
||
}
|
||
|
||
function getCurrentRouteDepth(): number {
|
||
const transitionDepth = getQueryText("transitionDepth").trim();
|
||
if (isValidTransitionDepth(transitionDepth)) {
|
||
return Number(transitionDepth);
|
||
}
|
||
|
||
return Number(route.meta.depth ?? 0);
|
||
}
|
||
|
||
function isValidTransitionDepth(value: string): boolean {
|
||
const depth = Number(value);
|
||
return value !== "" && Number.isInteger(depth) && 0 <= depth && depth <= 20;
|
||
}
|
||
|
||
async function replaceFilterQuery(): Promise<void> {
|
||
await router.replace({
|
||
name: "EventRecordList",
|
||
query: buildFilterQuery()
|
||
});
|
||
}
|
||
|
||
function buildUnixRange(): [number, number] {
|
||
if (dateFilterType.value === "all") {
|
||
return [0, Time.toTimestamp(new Date())];
|
||
}
|
||
|
||
return [
|
||
Time.toTimestamp(parseDateTime(dateRange.value[0], false)),
|
||
Time.toTimestamp(parseDateTime(dateRange.value[1], true))
|
||
];
|
||
}
|
||
|
||
onMounted(async () => {
|
||
navBarStore.clearRight();
|
||
navBarStore.setCustomRightVisible(canShowActionMenu.value);
|
||
await loadActions();
|
||
initialized = true;
|
||
await reloadRecords();
|
||
});
|
||
|
||
onUnmounted(() => {
|
||
navBarStore.clearRight();
|
||
window.clearTimeout(keywordTimer);
|
||
});
|
||
</script>
|
||
|
||
<style scoped lang="less">
|
||
.registration-record-list {
|
||
gap: 1rem;
|
||
display: flex;
|
||
padding: calc(var(--app-nav-height)) 0 6rem 0;
|
||
flex-direction: column;
|
||
|
||
.search-bar {
|
||
top: var(--app-nav-height);
|
||
left: 0;
|
||
right: 0;
|
||
z-index: 10;
|
||
display: flex;
|
||
padding: .75rem 1rem;
|
||
position: sticky;
|
||
background: #F5F5F8;
|
||
box-shadow: var(--td-shadow-1);
|
||
flex-direction: column;
|
||
|
||
.search-date {
|
||
display: flex;
|
||
|
||
.search-input {
|
||
--td-button-border-radius: .75rem;
|
||
--td-input-vertical-padding: .25rem 1rem;
|
||
|
||
flex: 1;
|
||
border: 1px solid rgba(148, 163, 184, .28);
|
||
z-index: 2;
|
||
background: #f8fbff;
|
||
padding-right: .3rem;
|
||
border-radius: 1rem;
|
||
}
|
||
|
||
.date-filter {
|
||
--td-spacer: 4px;
|
||
--td-spacer-1: 6px;
|
||
|
||
border: 1px solid rgba(148, 163, 184, .18);
|
||
z-index: 1;
|
||
margin-left: -2.5rem;
|
||
padding-left: 3rem;
|
||
padding-right: .7rem;
|
||
border-radius: 1rem;
|
||
|
||
&:after {
|
||
display: none;
|
||
}
|
||
}
|
||
}
|
||
|
||
.filter-menu {
|
||
--td-spacer-1: 6px;
|
||
|
||
width: 100%;
|
||
overflow: hidden;
|
||
margin-top: -1rem;
|
||
padding-top: .8rem;
|
||
background: #fff;
|
||
border: 1px solid rgba(148, 163, 184, .18);
|
||
border-top: none;
|
||
border-radius: 0 0 1rem 1rem;
|
||
|
||
.option-empty-tip {
|
||
color: #64748b;
|
||
padding: .75rem 1rem;
|
||
font-size: .8125rem;
|
||
text-align: center;
|
||
}
|
||
}
|
||
}
|
||
|
||
.list-wrapper {
|
||
gap: 1rem;
|
||
display: flex;
|
||
flex-direction: column;
|
||
|
||
.summary {
|
||
overflow: hidden;
|
||
|
||
:deep(.t-cell__title) {
|
||
flex: 0 0 4.5rem;
|
||
}
|
||
|
||
:deep(.t-cell__note) {
|
||
flex: 1 1 0;
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
text-align: right;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
}
|
||
|
||
.record-list {
|
||
|
||
:deep(.item:last-child .cell:after) {
|
||
display: none;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
:global(html.theme-dark) {
|
||
.registration-record-list {
|
||
|
||
.search-bar {
|
||
background: #10161f;
|
||
|
||
.search-input {
|
||
background: rgba(15, 23, 42, .72);
|
||
border-color: rgba(148, 163, 184, .16);
|
||
}
|
||
|
||
.date-filter,
|
||
.filter-menu {
|
||
background: #172033;
|
||
border-color: rgba(148, 163, 184, .16);
|
||
}
|
||
}
|
||
|
||
.empty-block,
|
||
.center-tips,
|
||
.summary {
|
||
color: #94a3b8;
|
||
}
|
||
}
|
||
}
|
||
</style>
|