From b62e7a5766c8229fe4e020956f5db06bff36773d Mon Sep 17 00:00:00 2001 From: Timi Date: Fri, 25 Sep 2026 22:07:58 +0800 Subject: [PATCH] add sku filter --- .../fulfillment/OrderFulfillmentList.vue | 133 ++++++- .../fulfillment/components/SkuFilterPopup.vue | 367 ++++++++++++++++++ src/router/routes/fulfillmentRoutes.ts | 2 +- src/types/Fulfillment.ts | 1 + src/types/Product.ts | 3 + 5 files changed, 493 insertions(+), 13 deletions(-) create mode 100644 src/pages/fulfillment/components/SkuFilterPopup.vue diff --git a/src/pages/fulfillment/OrderFulfillmentList.vue b/src/pages/fulfillment/OrderFulfillmentList.vue index fb1b85c..be65076 100644 --- a/src/pages/fulfillment/OrderFulfillmentList.vue +++ b/src/pages/fulfillment/OrderFulfillmentList.vue @@ -31,13 +31,18 @@ - + +
- - - - + + + @@ -96,6 +117,12 @@
+ void }>(); const navBarStore = useNavBarStore(); const userStore = useUserStore(); const canCreate = computed(() => userStore.hasPermissions(permissionCodes.orderFulfillment.create)); @@ -172,6 +201,7 @@ const actions = computed(() => [ } ]); type DateFilterType = "today" | "yesterday" | "thisWeek" | "thisMonth" | "lastMonth" | "all" | "custom"; +type SkuFilterOption = { skuId: string; productName: string; skuName: string }; const dateOptions: Array<{ label: string; value: DateFilterType }> = [ { label: "今天", value: "today" }, { label: "昨天", value: "yesterday" }, @@ -183,11 +213,14 @@ const dateOptions: Array<{ label: string; value: DateFilterType }> = [ ]; const customerId = ref(getQueryText(route.query.customerId)); +const selectedSkuIdList = ref(getSkuIdList(route.query.skuIds)); const selectedCustomer = ref(); const salesOrder = ref(); +const skuFilterOptionList = ref([]); +const skuFilterVisible = ref(false); const customerSelectorVisible = ref(false); const customerSearchKeyword = ref(""); -const dateFilterType = ref(parseDateFilterType(getQueryText(route.query.dateType), dateOptions, "today")); +const dateFilterType = ref(parseDateFilterType(getQueryText(route.query.dateType), dateOptions, "all")); const dateRange = ref<[string, string]>(parseDateRange(dateFilterType.value)); const startDraft = ref(""); const endDraft = ref(""); @@ -211,12 +244,27 @@ const statusOptions = [ { label: "已冲正", value: "REVOKED" } ]; const dateFilterLabel = computed(() => dateOptions.find(item => item.value === dateFilterType.value)?.label || "时间范围"); +const selectedSkuOptionList = computed(() => { + const optionMap = new Map(skuFilterOptionList.value.map(option => [option.skuId, option])); + return selectedSkuIdList.value + .map(skuId => optionMap.get(skuId)) + .filter((option): option is SkuFilterOption => !!option); +}); +const skuFilterLabel = computed(() => selectedSkuIdList.value.length ? `SKU (${selectedSkuIdList.value.length})` : "产品 SKU"); const isCustomDate = computed(() => dateFilterType.value === "custom"); function getQueryText(value: unknown): string { return Array.isArray(value) ? "" : typeof value === "string" ? value : ""; } +function getSkuIdList(value: unknown): string[] { + const queryValueList = Array.isArray(value) ? value : [value]; + return [...new Set(queryValueList + .flatMap(item => typeof item === "string" ? item.split(",") : []) + .map(item => item.trim()) + .filter(Boolean))]; +} + function getInitialStatus(value: unknown): "ALL" | GaoFulfillmentStatus { const statusValue = getQueryText(value); if (["WAITING_STOCK", "PENDING", "VERIFIED", "REJECTED", "CANCELLED", "REVOKED"].includes(statusValue)) { @@ -250,6 +298,7 @@ async function fetchList(reset: boolean): Promise { size: pageSize, salesOrderId: getQueryText(route.query.salesOrderId) || undefined, customerId: customerId.value || undefined, + skuIdList: selectedSkuIdList.value.length ? selectedSkuIdList.value : undefined, beginAt, endAt, status: status.value === "ALL" ? undefined : status.value @@ -297,6 +346,29 @@ async function selectStatus(value: DropdownValue): Promise { } } +async function openSkuFilter(): Promise { + skuFilterVisible.value = true; + await nextTick(); + filterMenuRef.value?.collapseMenu(); +} + +function handleSkuFilterOptionsLoaded(optionList: SkuFilterOption[]): void { + skuFilterOptionList.value = optionList; +} + +async function confirmSkuFilter(skuIdList: string[]): Promise { + selectedSkuIdList.value = skuIdList; + await replaceFilterQuery(); + await reloadList(); +} + +function formatSelectedSkuName(productName: string, skuName: string) { + if (productName === skuName) { + return productName; + } + return `${productName}(${skuName})`; +} + async function handleCustomerChange(value: string | string[]): Promise { customerId.value = Array.isArray(value) ? String(value[0] || "") : value; try { @@ -417,6 +489,7 @@ function buildFilterQuery(transitionDepth = getQueryText(route.query.transitionD const query = { salesOrderId: getQueryText(route.query.salesOrderId), customerId: customerId.value, + skuIds: selectedSkuIdList.value.join(","), dateType: dateFilterType.value, beginAt: dateRange.value[0] || "", endAt: dateRange.value[1] || "", @@ -541,7 +614,7 @@ onUnmounted(() => navBarStore.clearRight()); --td-spacer-1: 6px; width: 100%; - border: 1px solid rgba(148, 163, 184, .18); + border: 1px solid var(--td-component-border); overflow: hidden; margin-top: -1rem; border-top: none; @@ -556,6 +629,42 @@ onUnmounted(() => navBarStore.clearRight()); display: flex; flex-direction: column; + .filter-info { + .selected-sku-list { + gap: .4rem; + width: 100%; + display: flex; + min-width: 0; + max-width: 100%; + overflow-x: auto; + justify-content: flex-start; + white-space: nowrap; + touch-action: pan-x; + scrollbar-width: none; + -webkit-overflow-scrolling: touch; + + &::-webkit-scrollbar { + display: none; + } + + :deep(.t-tag) { + flex: none; + } + } + + .sku-filter-cell { + :deep(.t-cell__title) { + flex: 0 0 5rem; + } + + :deep(.t-cell__note) { + flex: 1 1 0; + min-width: 0; + overflow: hidden; + } + } + } + .date-summary { :deep(.t-cell__title) { diff --git a/src/pages/fulfillment/components/SkuFilterPopup.vue b/src/pages/fulfillment/components/SkuFilterPopup.vue new file mode 100644 index 0000000..7d63fdf --- /dev/null +++ b/src/pages/fulfillment/components/SkuFilterPopup.vue @@ -0,0 +1,367 @@ + + + + + diff --git a/src/router/routes/fulfillmentRoutes.ts b/src/router/routes/fulfillmentRoutes.ts index a7fdef7..d5cdbef 100644 --- a/src/router/routes/fulfillmentRoutes.ts +++ b/src/router/routes/fulfillmentRoutes.ts @@ -5,7 +5,7 @@ function meta(title: string, depth = 3): RouteRecordRaw["meta"] { } export default [ - { path: "/management/order-fulfillments", name: "OrderFulfillmentList", component: () => import("@/pages/fulfillment/OrderFulfillmentList.vue"), meta: meta("统一履约") }, + { path: "/management/order-fulfillments", name: "OrderFulfillmentList", component: () => import("@/pages/fulfillment/OrderFulfillmentList.vue"), meta: meta("履约记录") }, { path: "/management/order-fulfillments/create", name: "OrderFulfillmentCreate", component: () => import("@/pages/fulfillment/OrderFulfillmentCreate.vue"), meta: meta("创建履约单", 4) }, { path: "/management/order-fulfillments/:id", name: "OrderFulfillmentDetail", component: () => import("@/pages/fulfillment/OrderFulfillmentDetail.vue"), meta: meta("履约单详情", 4) } ] as RouteRecordRaw[]; diff --git a/src/types/Fulfillment.ts b/src/types/Fulfillment.ts index f2f8785..a07b039 100644 --- a/src/types/Fulfillment.ts +++ b/src/types/Fulfillment.ts @@ -52,6 +52,7 @@ export type FulfillmentPage = { storeId?: string; salesOrderId?: string; customerId?: string; + skuIdList?: string[]; status?: GaoFulfillmentStatus; beginAt?: number; endAt?: number; diff --git a/src/types/Product.ts b/src/types/Product.ts index 9d7a711..f0b8cf7 100644 --- a/src/types/Product.ts +++ b/src/types/Product.ts @@ -82,6 +82,9 @@ export type Product = { categoryId?: string; code: string; name: string; + namePinyinFull?: string; + namePinyinInitial?: string; + namePinyinMixed?: string; productType: ProductType; stockEnabled: boolean; status?: ProductStatus;