add time between
This commit is contained in:
@@ -10,6 +10,18 @@
|
||||
@click="customerSelectorVisible = true"
|
||||
>
|
||||
<template #suffix-icon>
|
||||
<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"
|
||||
@@ -17,9 +29,16 @@
|
||||
: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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user