From 2fbd4c9fd01d83754b91835d017c12df9437c5d9 Mon Sep 17 00:00:00 2001 From: Timi Date: Sun, 20 Sep 2026 15:43:19 +0800 Subject: [PATCH] more filter for sales order --- src/components/filter-popup/FilterPopup.ts | 49 ++ src/components/filter-popup/FilterPopup.vue | 319 ++++++++++++ src/pages/point/PointRedeemSkuList.vue | 2 +- src/pages/salesOrder/SalesOrderList.vue | 453 +++++++++++------- .../components/SalesOrderFilterPopup.vue | 353 ++++++++++++++ .../components/SalesOrderFilterSummary.vue | 250 ++++++++++ src/types/SalesOrder.ts | 4 + src/types/SalesOrderFilter.ts | 79 +++ src/utils/date.ts | 8 + 9 files changed, 1337 insertions(+), 180 deletions(-) create mode 100644 src/components/filter-popup/FilterPopup.ts create mode 100644 src/components/filter-popup/FilterPopup.vue create mode 100644 src/pages/salesOrder/components/SalesOrderFilterPopup.vue create mode 100644 src/pages/salesOrder/components/SalesOrderFilterSummary.vue create mode 100644 src/types/SalesOrderFilter.ts diff --git a/src/components/filter-popup/FilterPopup.ts b/src/components/filter-popup/FilterPopup.ts new file mode 100644 index 0000000..665ae3f --- /dev/null +++ b/src/components/filter-popup/FilterPopup.ts @@ -0,0 +1,49 @@ +/** 过滤项支持的值类型。 */ +export type FilterValue = string | number | boolean; + +/** 一个过滤组的选中值。 */ +export type FilterValueList = FilterValue[]; + +/** 过滤组的选项控件类型。 */ +export type FilterControl = "checkbox" | "radio"; + +/** 过滤弹窗的值模型。 */ +export type FilterModel = Record; + +/** 过滤项配置。 */ +export interface FilterOption { + label: string; + value: FilterValue; + disabled?: boolean; +} + +/** 过滤值变化上下文。 */ +export interface FilterChangeContext { + group: FilterGroup; + groupKey: string; + value: FilterValueList; + previousValue: FilterValueList; + changedValue?: FilterValue; + checked?: boolean; + modelValue: FilterModel; +} + +/** 过滤值变化回调返回值。数组用于替换当前组,对象用于修改一个或多个组。 */ +export type FilterChangeResult = FilterValueList | Partial | void; + +/** 过滤值变化回调。 */ +export type FilterChangeHandler = (context: FilterChangeContext) => FilterChangeResult; + +/** 过滤组配置。 */ +export interface FilterGroup { + key: string; + title: string; + description?: string; + options: FilterOption[]; + control?: FilterControl; + multiple?: boolean; + columns: number; + readonly?: boolean; + readonlyText?: string; + onChange?: FilterChangeHandler; +} diff --git a/src/components/filter-popup/FilterPopup.vue b/src/components/filter-popup/FilterPopup.vue new file mode 100644 index 0000000..e43ed74 --- /dev/null +++ b/src/components/filter-popup/FilterPopup.vue @@ -0,0 +1,319 @@ + + + + + diff --git a/src/pages/point/PointRedeemSkuList.vue b/src/pages/point/PointRedeemSkuList.vue index aa25a9c..f93a993 100644 --- a/src/pages/point/PointRedeemSkuList.vue +++ b/src/pages/point/PointRedeemSkuList.vue @@ -16,7 +16,7 @@ v-for="(item, index) in result.list" :key="item.id" class="item" - :title="item.product?.name || item.sku?.name || '未命名产品'" + :title="item.product?.name + '长名称长名称长名称长名称长名称长名称长名称长名称' || item.sku?.name || '未命名产品'" arrow v-entrance-transition="index * 30" @click="toEdit(item)" diff --git a/src/pages/salesOrder/SalesOrderList.vue b/src/pages/salesOrder/SalesOrderList.vue index 3297cfb..9ac4552 100644 --- a/src/pages/salesOrder/SalesOrderList.vue +++ b/src/pages/salesOrder/SalesOrderList.vue @@ -1,7 +1,7 @@ - - - + + + 筛选 + - - - - -
+ - + diff --git a/src/pages/salesOrder/components/SalesOrderFilterPopup.vue b/src/pages/salesOrder/components/SalesOrderFilterPopup.vue new file mode 100644 index 0000000..2f13cae --- /dev/null +++ b/src/pages/salesOrder/components/SalesOrderFilterPopup.vue @@ -0,0 +1,353 @@ + + + + + diff --git a/src/pages/salesOrder/components/SalesOrderFilterSummary.vue b/src/pages/salesOrder/components/SalesOrderFilterSummary.vue new file mode 100644 index 0000000..b09f135 --- /dev/null +++ b/src/pages/salesOrder/components/SalesOrderFilterSummary.vue @@ -0,0 +1,250 @@ + + + + + diff --git a/src/types/SalesOrder.ts b/src/types/SalesOrder.ts index c27ddb0..8fc23e2 100644 --- a/src/types/SalesOrder.ts +++ b/src/types/SalesOrder.ts @@ -78,9 +78,13 @@ export type SalesOrderPage = { customerId?: string; packageConfigId?: string; orderType?: GaoSalesOrderType; + orderTypeList?: GaoSalesOrderType[]; status?: GaoSalesOrderStatus; + statusList?: GaoSalesOrderStatus[]; paymentStatus?: GaoSalesPaymentStatus; + paymentStatusList?: GaoSalesPaymentStatus[]; refundStatus?: GaoSalesRefundStatus; + refundStatusList?: GaoSalesRefundStatus[]; pendingReceive?: boolean; fulfilledButUnpaid?: boolean; keyword?: string; diff --git a/src/types/SalesOrderFilter.ts b/src/types/SalesOrderFilter.ts new file mode 100644 index 0000000..6120d5d --- /dev/null +++ b/src/types/SalesOrderFilter.ts @@ -0,0 +1,79 @@ +import type { + GaoSalesOrderStatus, + GaoSalesOrderType, + GaoSalesPaymentStatus, + GaoSalesRefundStatus +} from "@/types/SalesOrder"; +import type { DateTimeRange } from "@/utils/date"; + +/** 销售订单时间筛选类型。 */ +export type SalesOrderDateFilterType = "all" | "today" | "yesterday" | "thisWeek" | "thisMonth" | "last30" | "custom"; + +/** 销售订单资金筛选类型,未收清为未收款和部分收款的组合条件。 */ +export type SalesOrderPaymentFilter = GaoSalesPaymentStatus | "PENDING_RECEIVE"; + +/** 销售订单筛选组。 */ +export type SalesOrderFilterGroupKey = "date" | "orderTypeList" | "statusList" | "paymentStatusList" | "refundStatusList"; + +/** 销售订单列表筛选条件。 */ +export type SalesOrderListFilter = { + dateFilterType: SalesOrderDateFilterType; + dateRange: DateTimeRange; + orderTypeList: GaoSalesOrderType[]; + statusList: GaoSalesOrderStatus[]; + paymentStatusList: SalesOrderPaymentFilter[]; + refundStatusList: GaoSalesRefundStatus[]; +}; + +/** 创建销售订单筛选默认值。 */ +export function createDefaultSalesOrderFilter(): SalesOrderListFilter { + return { + dateFilterType: "all", + dateRange: ["", ""], + orderTypeList: ["PACKAGE", "DIRECT_PRODUCT"], + statusList: ["ACTIVE", "FULFILLED"], + paymentStatusList: ["UNPAID", "PARTIALLY_PAID", "PAID"], + refundStatusList: ["NOT_REQUIRED", "PENDING", "REFUNDED"] + }; +} + +/** 复制销售订单筛选条件。 */ +export function cloneSalesOrderFilter(filter: SalesOrderListFilter): SalesOrderListFilter { + return { + dateFilterType: filter.dateFilterType, + dateRange: [...filter.dateRange] as DateTimeRange, + orderTypeList: [...filter.orderTypeList], + statusList: [...filter.statusList], + paymentStatusList: [...filter.paymentStatusList], + refundStatusList: [...filter.refundStatusList] + }; +} + +/** 将本地存储内容归一化为合法筛选条件。 */ +export function normalizeSalesOrderFilter(value: unknown): SalesOrderListFilter { + const defaultFilter = createDefaultSalesOrderFilter(); + if (!value || typeof value !== "object") { + return defaultFilter; + } + + const source = value as Partial; + const dateFilterType = ["all", "today", "yesterday", "thisWeek", "thisMonth", "last30", "custom"].includes(source.dateFilterType || "") + ? source.dateFilterType! + : defaultFilter.dateFilterType; + const dateRange = Array.isArray(source.dateRange) && 2 <= source.dateRange.length + ? [String(source.dateRange[0] || ""), String(source.dateRange[1] || "")] as DateTimeRange + : defaultFilter.dateRange; + + return { + dateFilterType, + dateRange, + orderTypeList: normalizeList(source.orderTypeList, defaultFilter.orderTypeList), + statusList: normalizeList(source.statusList, defaultFilter.statusList), + paymentStatusList: normalizeList(source.paymentStatusList, defaultFilter.paymentStatusList), + refundStatusList: normalizeList(source.refundStatusList, defaultFilter.refundStatusList) + }; +} + +function normalizeList(value: unknown, fallback: T[]): T[] { + return Array.isArray(value) ? value.filter(item => typeof item === "string") as T[] : [...fallback]; +} diff --git a/src/utils/date.ts b/src/utils/date.ts index 616b338..aedcba8 100644 --- a/src/utils/date.ts +++ b/src/utils/date.ts @@ -5,6 +5,7 @@ export type DateFilterPreset = | "all" | "today" | "yesterday" + | "thisWeek" | "lastWeek" | "thisMonth" | "lastMonth" @@ -33,6 +34,13 @@ export function buildPresetDateRange(type: DateFilterPreset, now = new Date()): return buildDayRange(lastWeekMonday, lastWeekSunday); } + if (type === "thisWeek") { + const current = getDateStart(now); + const day = current.getDay() || 7; + const thisWeekMonday = addDays(current, 1 - day); + return buildDayRange(thisWeekMonday, current); + } + if (type === "thisMonth") { const current = new Date(now); const begin = new Date(current.getFullYear(), current.getMonth(), 1);