add time between
This commit is contained in:
@@ -10,16 +10,35 @@
|
|||||||
@click="customerSelectorVisible = true"
|
@click="customerSelectorVisible = true"
|
||||||
>
|
>
|
||||||
<template #suffix-icon>
|
<template #suffix-icon>
|
||||||
<t-button
|
<div class="filter-actions">
|
||||||
size="extra-small"
|
<t-button
|
||||||
theme="primary"
|
v-if="customerId"
|
||||||
variant="outline"
|
shape="circle"
|
||||||
:content="scannerVisible ? '关闭扫码' : '扫码'"
|
theme="danger"
|
||||||
@click.stop="toggleScanner"
|
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>
|
</template>
|
||||||
</t-input>
|
</t-input>
|
||||||
<t-dropdown-menu class="menus">
|
<t-dropdown-menu class="menus">
|
||||||
|
<t-dropdown-item
|
||||||
|
v-model="dateFilterType"
|
||||||
|
:label="dateFilterLabel"
|
||||||
|
:options="dateOptions"
|
||||||
|
@change="selectDateFilter"
|
||||||
|
/>
|
||||||
<t-dropdown-item
|
<t-dropdown-item
|
||||||
v-model="sourceType"
|
v-model="sourceType"
|
||||||
label="消费来源"
|
label="消费来源"
|
||||||
@@ -51,6 +70,20 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<t-cell-group class="summary" theme="card">
|
<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="`共 ${total} 条`" />
|
||||||
<t-cell title="消费金额合计" :note="`¥${formatPackageAmount(amountTotal)}`" />
|
<t-cell title="消费金额合计" :note="`¥${formatPackageAmount(amountTotal)}`" />
|
||||||
</t-cell-group>
|
</t-cell-group>
|
||||||
@@ -110,12 +143,36 @@
|
|||||||
v-model:visible="customerSelectorVisible"
|
v-model:visible="customerSelectorVisible"
|
||||||
@change="handleCustomerChange"
|
@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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { DropdownValue } from "tdesign-mobile-vue";
|
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 { useUserStore } from "timi-web-api";
|
||||||
import ConsumptionRecordAPI from "@/api/ConsumptionRecordAPI";
|
import ConsumptionRecordAPI from "@/api/ConsumptionRecordAPI";
|
||||||
import CustomerAPI from "@/api/CustomerAPI";
|
import CustomerAPI from "@/api/CustomerAPI";
|
||||||
@@ -137,16 +194,33 @@ import { permissionCodes } from "@/utils/permission";
|
|||||||
|
|
||||||
defineOptions({ name: "ConsumptionRecordList" });
|
defineOptions({ name: "ConsumptionRecordList" });
|
||||||
|
|
||||||
|
type DateFilterType = "today" | "yesterday" | "lastWeek" | "thisMonth" | "lastMonth" | "all" | "custom";
|
||||||
|
|
||||||
const pageSize = 20;
|
const pageSize = 20;
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const navBarStore = useNavBarStore();
|
const navBarStore = useNavBarStore();
|
||||||
const userStore = useUserStore();
|
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 sourceType = ref<ConsumptionRecordSourceType | "ALL">("ALL");
|
||||||
const status = ref<ConsumptionRecordStatus | "ALL">("ALL");
|
const status = ref<ConsumptionRecordStatus | "ALL">("ALL");
|
||||||
const customerId = ref("");
|
const customerId = ref("");
|
||||||
const selectedCustomer = ref<Customer>();
|
const selectedCustomer = ref<Customer>();
|
||||||
const customerSelectorVisible = ref(false);
|
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 scannerVisible = ref(false);
|
||||||
const scanStatus = ref("点击扫码开始识别。");
|
const scanStatus = ref("点击扫码开始识别。");
|
||||||
let latestDecodedText = "";
|
let latestDecodedText = "";
|
||||||
@@ -162,14 +236,18 @@ const statusOptions = [{ label: "全部状态", value: "ALL" }, { label: "有效
|
|||||||
const canReadEventRecord = computed(() => userStore.hasPermissions(permissionCodes.eventRecord.read));
|
const canReadEventRecord = computed(() => userStore.hasPermissions(permissionCodes.eventRecord.read));
|
||||||
const loadStateText = computed(() => isLoading.value ? "正在加载更多" : isNoMore.value ? "没有更多记录" : "上拉加载更多");
|
const loadStateText = computed(() => isLoading.value ? "正在加载更多" : isNoMore.value ? "没有更多记录" : "上拉加载更多");
|
||||||
const customerDisplayText = computed(() => selectedCustomer.value?.name || selectedCustomer.value?.code || "");
|
const customerDisplayText = computed(() => selectedCustomer.value?.name || selectedCustomer.value?.code || "");
|
||||||
|
const dateFilterLabel = computed(() => dateOptions.find(item => item.value === dateFilterType.value)?.label || "全部时间");
|
||||||
|
|
||||||
function applyRouteQuery(): void {
|
function applyRouteQuery(): void {
|
||||||
const getText = (value: unknown): string => Array.isArray(value) ? String(value[0] || "") : String(value || "");
|
const getText = (value: unknown): string => Array.isArray(value) ? String(value[0] || "") : String(value || "");
|
||||||
const routeSourceType = getText(route.query.sourceType);
|
const routeSourceType = getText(route.query.sourceType);
|
||||||
const routeStatus = getText(route.query.status);
|
const routeStatus = getText(route.query.status);
|
||||||
|
const routeDateType = getText(route.query.dateType);
|
||||||
customerId.value = getText(route.query.customerId);
|
customerId.value = getText(route.query.customerId);
|
||||||
sourceType.value = ["ALL", "LEGACY_EVENT", "SALES_ORDER"].includes(routeSourceType) ? routeSourceType as ConsumptionRecordSourceType | "ALL" : "ALL";
|
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";
|
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> {
|
async function handleCustomerChange(value: string): Promise<void> {
|
||||||
@@ -178,6 +256,64 @@ async function handleCustomerChange(value: string): Promise<void> {
|
|||||||
await reloadRecords();
|
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> {
|
async function loadSelectedCustomer(): Promise<void> {
|
||||||
selectedCustomer.value = customerId.value ? await CustomerAPI.detail(customerId.value) : undefined;
|
selectedCustomer.value = customerId.value ? await CustomerAPI.detail(customerId.value) : undefined;
|
||||||
}
|
}
|
||||||
@@ -217,15 +353,83 @@ function handleScanStatusChange(value: string): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildPage(): ConsumptionRecordPage {
|
function buildPage(): ConsumptionRecordPage {
|
||||||
|
const [beginAt, endAt] = buildUnixRange();
|
||||||
return {
|
return {
|
||||||
customerId: customerId.value || undefined,
|
customerId: customerId.value || undefined,
|
||||||
sourceType: sourceType.value === "ALL" ? undefined : sourceType.value,
|
sourceType: sourceType.value === "ALL" ? undefined : sourceType.value,
|
||||||
status: status.value === "ALL" ? undefined : status.value,
|
status: status.value === "ALL" ? undefined : status.value,
|
||||||
|
beginAt,
|
||||||
|
endAt,
|
||||||
index: page.value,
|
index: page.value,
|
||||||
size: pageSize
|
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> {
|
async function reloadRecords(value?: DropdownValue): Promise<void> {
|
||||||
if (value && Array.isArray(value)) {
|
if (value && Array.isArray(value)) {
|
||||||
return;
|
return;
|
||||||
@@ -329,6 +533,12 @@ onMounted(async () => {
|
|||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filter-actions {
|
||||||
|
gap: .25rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.menus {
|
.menus {
|
||||||
--td-spacer-1: 6px;
|
--td-spacer-1: 6px;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user