add time between

This commit is contained in:
Timi
2026-09-09 14:57:41 +08:00
parent ce4a45fb65
commit 2e40391c45
+218 -8
View File
@@ -10,16 +10,35 @@
@click="customerSelectorVisible = true"
>
<template #suffix-icon>
<t-button
size="extra-small"
theme="primary"
variant="outline"
:content="scannerVisible ? '关闭扫码' : '扫码'"
@click.stop="toggleScanner"
/>
<div class="filter-actions">
<t-button
v-if="customerId"
shape="circle"
theme="danger"
variant="text"
size="extra-small"
aria-label="清除客户选择"
@click.stop="clearCustomer"
>
<t-icon name="close" size="1rem" />
</t-button>
<t-button
size="extra-small"
theme="primary"
variant="outline"
:content="scannerVisible ? '关闭扫码' : '扫码'"
@click.stop="toggleScanner"
/>
</div>
</template>
</t-input>
<t-dropdown-menu class="menus">
<t-dropdown-item
v-model="dateFilterType"
:label="dateFilterLabel"
:options="dateOptions"
@change="selectDateFilter"
/>
<t-dropdown-item
v-model="sourceType"
label="消费来源"
@@ -51,6 +70,20 @@
/>
</div>
<t-cell-group class="summary" theme="card">
<t-cell
v-if="dateFilterType !== 'all'"
title="开始时间"
:note="dateRange[0] || '-'"
arrow
@click="openStartTime"
/>
<t-cell
v-if="dateFilterType !== 'all'"
title="结束时间"
:note="dateRange[1] || '-'"
arrow
@click="openEndTime"
/>
<t-cell title="记录数" :note="`共 ${total} 条`" />
<t-cell title="消费金额合计" :note="`¥${formatPackageAmount(amountTotal)}`" />
</t-cell-group>
@@ -110,12 +143,36 @@
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"
format="YYYY-MM-DD HH:mm"
:mode="['date', 'minute']"
title="结束时间"
:default-value="dateRange[1]"
@cancel="cancelEndTime"
@confirm="confirmEndTime"
/>
</t-popup>
</div>
</template>
<script setup lang="ts">
import type { DropdownValue } from "tdesign-mobile-vue";
import { PassTimeLabel } from "timi-web";
import { PassTimeLabel, Time } from "timi-web";
import { useUserStore } from "timi-web-api";
import ConsumptionRecordAPI from "@/api/ConsumptionRecordAPI";
import CustomerAPI from "@/api/CustomerAPI";
@@ -137,16 +194,33 @@ import { permissionCodes } from "@/utils/permission";
defineOptions({ name: "ConsumptionRecordList" });
type DateFilterType = "today" | "yesterday" | "lastWeek" | "thisMonth" | "lastMonth" | "all" | "custom";
const pageSize = 20;
const route = useRoute();
const router = useRouter();
const navBarStore = useNavBarStore();
const userStore = useUserStore();
const dateOptions: Array<{ label: string; value: DateFilterType }> = [
{ label: "全部时间", value: "all" },
{ label: "今天", value: "today" },
{ label: "昨天", value: "yesterday" },
{ label: "上周", value: "lastWeek" },
{ label: "本月", value: "thisMonth" },
{ label: "上个月", value: "lastMonth" },
{ label: "自定义范围", value: "custom" }
];
const sourceType = ref<ConsumptionRecordSourceType | "ALL">("ALL");
const status = ref<ConsumptionRecordStatus | "ALL">("ALL");
const customerId = ref("");
const selectedCustomer = ref<Customer>();
const customerSelectorVisible = ref(false);
const dateFilterType = ref<DateFilterType>("all");
const dateRange = ref<[string, string]>(buildPresetRange("all"));
const startDraft = ref("");
const endDraft = ref("");
const startVisible = ref(false);
const endVisible = ref(false);
const scannerVisible = ref(false);
const scanStatus = ref("点击扫码开始识别。");
let latestDecodedText = "";
@@ -162,14 +236,18 @@ const statusOptions = [{ label: "全部状态", value: "ALL" }, { label: "有效
const canReadEventRecord = computed(() => userStore.hasPermissions(permissionCodes.eventRecord.read));
const loadStateText = computed(() => isLoading.value ? "正在加载更多" : isNoMore.value ? "没有更多记录" : "上拉加载更多");
const customerDisplayText = computed(() => selectedCustomer.value?.name || selectedCustomer.value?.code || "");
const dateFilterLabel = computed(() => dateOptions.find(item => item.value === dateFilterType.value)?.label || "全部时间");
function applyRouteQuery(): void {
const getText = (value: unknown): string => Array.isArray(value) ? String(value[0] || "") : String(value || "");
const routeSourceType = getText(route.query.sourceType);
const routeStatus = getText(route.query.status);
const routeDateType = getText(route.query.dateType);
customerId.value = getText(route.query.customerId);
sourceType.value = ["ALL", "LEGACY_EVENT", "SALES_ORDER"].includes(routeSourceType) ? routeSourceType as ConsumptionRecordSourceType | "ALL" : "ALL";
status.value = ["ALL", "ACTIVE", "REVOKED"].includes(routeStatus) ? routeStatus as ConsumptionRecordStatus | "ALL" : "ALL";
dateFilterType.value = dateOptions.some(item => item.value === routeDateType) ? routeDateType as DateFilterType : "all";
dateRange.value = parseDateRange(dateFilterType.value, getText);
}
async function handleCustomerChange(value: string): Promise<void> {
@@ -178,6 +256,64 @@ async function handleCustomerChange(value: string): Promise<void> {
await reloadRecords();
}
async function clearCustomer(): Promise<void> {
customerId.value = "";
selectedCustomer.value = undefined;
await reloadRecords();
}
async function selectDateFilter(value: DropdownValue): Promise<void> {
if (Array.isArray(value)) {
return;
}
dateFilterType.value = String(value) as DateFilterType;
if (dateFilterType.value !== "custom") {
dateRange.value = buildPresetRange(dateFilterType.value);
await reloadRecords();
}
}
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 (dateRange.value[0] && dateRange.value[1] && dateRange.value[1] < dateRange.value[0]) {
dateRange.value = [dateRange.value[1], dateRange.value[0]];
}
dateFilterType.value = "custom";
await reloadRecords();
}
async function loadSelectedCustomer(): Promise<void> {
selectedCustomer.value = customerId.value ? await CustomerAPI.detail(customerId.value) : undefined;
}
@@ -217,15 +353,83 @@ function handleScanStatusChange(value: string): void {
}
function buildPage(): ConsumptionRecordPage {
const [beginAt, endAt] = buildUnixRange();
return {
customerId: customerId.value || undefined,
sourceType: sourceType.value === "ALL" ? undefined : sourceType.value,
status: status.value === "ALL" ? undefined : status.value,
beginAt,
endAt,
index: page.value,
size: pageSize
};
}
function parseDateRange(type: DateFilterType, getText: (value: unknown) => string): [string, string] {
if (type !== "custom") {
return buildPresetRange(type);
}
return [getText(route.query.beginAt), getText(route.query.endAt)];
}
function buildPresetRange(type: Exclude<DateFilterType, "custom">): [string, string] {
if (type === "all") {
return ["", ""];
}
if (type === "lastWeek") {
const current = new Date();
current.setHours(0, 0, 0, 0);
const day = current.getDay() || 7;
const thisWeekMonday = new Date(current);
thisWeekMonday.setDate(current.getDate() - day + 1);
const lastWeekMonday = new Date(thisWeekMonday);
lastWeekMonday.setDate(thisWeekMonday.getDate() - 7);
const lastWeekSunday = new Date(thisWeekMonday);
lastWeekSunday.setDate(thisWeekMonday.getDate() - 1);
return [`${Time.formatDate(lastWeekMonday)} 00:00`, `${Time.formatDate(lastWeekSunday)} 23:59`];
}
if (type === "thisMonth") {
const current = new Date();
const begin = new Date(current.getFullYear(), current.getMonth(), 1);
return [`${Time.formatDate(begin)} 00:00`, `${Time.formatDate(current)} 23:59`];
}
if (type === "lastMonth") {
const current = new Date();
const begin = new Date(current.getFullYear(), current.getMonth() - 1, 1);
const end = new Date(current.getFullYear(), current.getMonth(), 0);
return [`${Time.formatDate(begin)} 00:00`, `${Time.formatDate(end)} 23:59`];
}
const offsetMap: Record<Exclude<DateFilterType, "all" | "custom" | "lastWeek" | "thisMonth" | "lastMonth">, number> = {
today: 0,
yesterday: 1
};
const date = new Date();
date.setHours(0, 0, 0, 0);
date.setDate(date.getDate() - offsetMap[type]);
return [`${Time.formatDate(date)} 00:00`, `${Time.formatDate(date)} 23:59`];
}
function buildUnixRange(): [number | undefined, number | undefined] {
if (dateFilterType.value === "all") {
return [undefined, undefined];
}
return [
Time.toTimestamp(parseDateTime(dateRange.value[0], false)),
Time.toTimestamp(parseDateTime(dateRange.value[1], true))
];
}
function parseDateTime(value: string, isEnd: boolean): Date {
if (!value) {
return isEnd ? new Date() : new Date(0);
}
const [dateText, timeText = isEnd ? "23:59" : "00:00"] = value.split(" ");
const [year, month, day] = dateText.split("-").map(Number);
const [hour, minute] = timeText.split(":").map(Number);
return new Date(year, month - 1, day, hour || 0, minute || 0, isEnd ? 59 : 0, 0);
}
async function reloadRecords(value?: DropdownValue): Promise<void> {
if (value && Array.isArray(value)) {
return;
@@ -329,6 +533,12 @@ onMounted(async () => {
border-radius: 1rem;
}
.filter-actions {
gap: .25rem;
display: flex;
align-items: center;
}
.menus {
--td-spacer-1: 6px;