add filter for ProductStockRecordList.vue

This commit is contained in:
Timi
2026-09-05 15:14:14 +08:00
parent eb8f4778a5
commit 52813fcce8
3 changed files with 110 additions and 10 deletions
+104 -7
View File
@@ -1,6 +1,6 @@
<template> <template>
<div v-can="permissionCodes.productStock.read" class="product-stock-record-list"> <div v-can="permissionCodes.productStock.read" class="product-stock-record-list">
<div class="search bg-white"> <div class="filters bg-white">
<t-input <t-input
v-model.trim="keyword" v-model.trim="keyword"
class="keyword" class="keyword"
@@ -9,6 +9,10 @@
clearable clearable
placeholder="搜索产品名称或编码" placeholder="搜索产品名称或编码"
/> />
<t-dropdown-menu class="menus">
<t-dropdown-item v-model="operationType" label="操作类型" :options="operationOptions" @change="selectOperationType" />
<t-dropdown-item v-model="direction" label="出入库" :options="directionOptions" @change="selectDirection" />
</t-dropdown-menu>
</div> </div>
<t-pull-down-refresh <t-pull-down-refresh
v-model="isRefreshing" v-model="isRefreshing"
@@ -34,6 +38,11 @@
<template #title> <template #title>
<div class="title"> <div class="title">
<strong v-text="getStockOperationText(record.operationType)" /> <strong v-text="getStockOperationText(record.operationType)" />
<span
v-if="record.quantity !== undefined && record.quantity !== null && record.quantity !== ''"
class="quantity"
v-text="getStockQuantityText(record)"
/>
</div> </div>
</template> </template>
<template #note> <template #note>
@@ -58,10 +67,11 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { type DropdownValue } from "tdesign-mobile-vue";
import { PassTimeLabel } from "timi-web"; import { PassTimeLabel } from "timi-web";
import ProductAPI from "@/api/ProductAPI"; import ProductAPI from "@/api/ProductAPI";
import { useNavBarStore } from "@/store/navBarStore"; import { useNavBarStore } from "@/store/navBarStore";
import type { StockOperationType, StockRecord } from "@/types/Product"; import type { StockOperationType, StockRecord, StockRecordDirection } from "@/types/Product";
import { getErrorMessage } from "@/utils/error"; import { getErrorMessage } from "@/utils/error";
import { getProductQuantityText, getStockOperationText } from "@/utils/product"; import { getProductQuantityText, getStockOperationText } from "@/utils/product";
import { Message } from "@/utils/message"; import { Message } from "@/utils/message";
@@ -69,6 +79,9 @@ import { permissionCodes } from "@/utils/permission";
defineOptions({ name: "ProductStockRecordList" }); defineOptions({ name: "ProductStockRecordList" });
type OperationFilter = "ALL" | StockOperationType;
type DirectionFilter = "ALL" | StockRecordDirection;
const pageSize = 15; const pageSize = 15;
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
@@ -79,7 +92,9 @@ const total = ref(0);
const loading = ref(false); const loading = ref(false);
const isRefreshing = ref(false); const isRefreshing = ref(false);
const noMore = ref(false); const noMore = ref(false);
const productId = computed(() => String(route.query.productId || "")); const productId = computed(() => getQueryText("productId"));
const operationType = ref<OperationFilter>(getQueryText("operationType") as OperationFilter || "ALL");
const direction = ref<DirectionFilter>(getQueryText("direction") as DirectionFilter || "ALL");
const keyword = ref(getQueryText("keyword")); const keyword = ref(getQueryText("keyword"));
let keywordTimer: number | undefined; let keywordTimer: number | undefined;
let requestToken = 0; let requestToken = 0;
@@ -87,9 +102,27 @@ const emptyText = computed(() => {
if (loading.value) { if (loading.value) {
return "正在加载库存流水"; return "正在加载库存流水";
} }
return keyword.value.trim() ? "未找到匹配的库存流水" : "暂无库存流水"; return keyword.value.trim() || productId.value || operationType.value !== "ALL" || direction.value !== "ALL" ? "未找到匹配的库存流水" : "暂无库存流水";
}); });
const loadStateText = computed(() => loading.value ? "正在加载" : noMore.value ? "已经到底了" : "上拉加载更多"); const loadStateText = computed(() => loading.value ? "正在加载" : noMore.value ? "已经到底了" : "上拉加载更多");
const operationOptions = [
{ label: "全部操作", value: "ALL" },
{ label: "期初入库", value: "INITIAL_IN" },
{ label: "手工入库", value: "MANUAL_IN" },
{ label: "手工出库", value: "MANUAL_OUT" },
{ label: "手工流水冲正", value: "MANUAL_REVOKE" },
{ label: "履约预占", value: "ORDER_RESERVE" },
{ label: "履约释放预占", value: "ORDER_RELEASE" },
{ label: "履约出库", value: "ORDER_OUT" },
{ label: "履约冲正入库", value: "ORDER_REVOKE" },
{ label: "盘盈", value: "STOCKTAKE_GAIN" },
{ label: "盘亏", value: "STOCKTAKE_LOSS" }
];
const directionOptions = [
{ label: "全部流水", value: "ALL" },
{ label: "入库", value: "IN" },
{ label: "出库", value: "OUT" }
];
function getQueryText(key: string): string { function getQueryText(key: string): string {
const value = route.query[key]; const value = route.query[key];
@@ -101,6 +134,7 @@ function getOperationIcon(type?: StockOperationType): string {
INITIAL_IN: "import", INITIAL_IN: "import",
MANUAL_IN: "import", MANUAL_IN: "import",
MANUAL_OUT: "export", MANUAL_OUT: "export",
MANUAL_REVOKE: "rollback",
ORDER_RESERVE: "lock-on", ORDER_RESERVE: "lock-on",
ORDER_RELEASE: "lock-off", ORDER_RELEASE: "lock-off",
ORDER_OUT: "task", ORDER_OUT: "task",
@@ -110,6 +144,14 @@ function getOperationIcon(type?: StockOperationType): string {
} as Record<string, string>)[type || ""] || "history"; } as Record<string, string>)[type || ""] || "history";
} }
function getStockQuantityText(record: StockRecord): string {
if (record.quantity === undefined || record.quantity === null || record.quantity === "") {
return "";
}
const unitText = record.unitSnapshot ? ` ${record.unitSnapshot}` : "";
return `${getProductQuantityText(record.quantity)}${unitText}`;
}
function getDeltaText(value?: number | string): string { function getDeltaText(value?: number | string): string {
const amount = Number(value || 0); const amount = Number(value || 0);
const text = String(value || "0").replace(/^-/, ""); const text = String(value || "0").replace(/^-/, "");
@@ -152,6 +194,8 @@ async function loadMore(force = false): Promise<void> {
const result = await ProductAPI.recordList({ const result = await ProductAPI.recordList({
productId: productId.value || undefined, productId: productId.value || undefined,
keyword: keyword.value.trim() || undefined, keyword: keyword.value.trim() || undefined,
operationType: operationType.value === "ALL" ? undefined : operationType.value,
direction: direction.value === "ALL" ? undefined : direction.value,
index: pageIndex.value, index: pageIndex.value,
size: pageSize size: pageSize
}); });
@@ -170,6 +214,20 @@ async function loadMore(force = false): Promise<void> {
} }
} }
async function selectOperationType(value: DropdownValue): Promise<void> {
if (!Array.isArray(value)) {
operationType.value = String(value) as OperationFilter;
await reload();
}
}
async function selectDirection(value: DropdownValue): Promise<void> {
if (!Array.isArray(value)) {
direction.value = String(value) as DirectionFilter;
await reload();
}
}
async function reload(): Promise<void> { async function reload(): Promise<void> {
pageIndex.value = 0; pageIndex.value = 0;
recordList.value = []; recordList.value = [];
@@ -222,20 +280,53 @@ onUnmounted(() => {
padding: var(--app-nav-height) 0 1rem; padding: var(--app-nav-height) 0 1rem;
flex-direction: column; flex-direction: column;
.search { .filters {
top: var(--app-nav-height); top: var(--app-nav-height);
left: 0;
right: 0;
z-index: 10; z-index: 10;
display: flex;
padding: .75rem 1rem; padding: .75rem 1rem;
position: sticky; position: sticky;
background: var(--td-bg-color-page); background: #F5F5F8;
box-shadow: var(--td-shadow-1); box-shadow: var(--td-shadow-1);
flex-direction: column;
.keyword { .keyword {
--td-input-vertical-padding: .25rem 1rem;
flex: 1;
border: 1px solid var(--td-component-border); border: 1px solid var(--td-component-border);
padding: .5rem 1rem; z-index: 2;
background: var(--td-bg-color-container); background: var(--td-bg-color-container);
border-radius: 1rem; border-radius: 1rem;
} }
.menus {
--td-spacer-1: 6px;
width: 100%;
border: 1px solid rgba(148, 163, 184, .18);
overflow: hidden;
margin-top: -1rem;
background: #fff;
border-top: none;
padding-top: .8rem;
border-radius: 0 0 1rem 1rem;
&:after {
display: none;
}
:deep(.t-dropdown-menu__item) {
min-width: 0;
}
:deep(.t-dropdown-menu__title) {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
} }
.list { .list {
@@ -257,6 +348,12 @@ onUnmounted(() => {
gap: .5rem; gap: .5rem;
display: flex; display: flex;
align-items: center; align-items: center;
.quantity {
color: var(--td-text-color-secondary);
font-size: .875rem;
font-weight: 400;
}
} }
.delta { .delta {
+2 -2
View File
@@ -83,8 +83,8 @@ const menuGroupList = computed<MenuGroup[]>(() => [
title: "交易管理", title: "交易管理",
items: [ items: [
{ icon: "order", path: "/management/sales-orders", title: "销售订单", visible: userStore.hasPermissions(permissionCodes.salesOrder.read) }, { icon: "order", path: "/management/sales-orders", title: "销售订单", visible: userStore.hasPermissions(permissionCodes.salesOrder.read) },
{ icon: "wallet", path: "/management/sales-orders?view=pending-receive", title: "待收款", visible: userStore.hasPermissions(permissionCodes.salesOrder.read) }, { icon: "wallet", path: "/management/sales-orders?pendingReceive=true", title: "待收款", visible: userStore.hasPermissions(permissionCodes.salesOrder.read) },
{ icon: "discount", path: "/management/sales-orders?view=pending-refund", title: "待退款", visible: userStore.hasPermissions(permissionCodes.salesOrder.read) }, { icon: "discount", path: "/management/sales-orders?status=CANCELLED&refundStatus=PENDING", title: "待退款", visible: userStore.hasPermissions(permissionCodes.salesOrder.read) },
{ icon: "wallet", path: "/management/fund-records", title: "资金流水", visible: userStore.hasPermissions(permissionCodes.fundRecord.read) }, { icon: "wallet", path: "/management/fund-records", title: "资金流水", visible: userStore.hasPermissions(permissionCodes.fundRecord.read) },
{ icon: "task", path: "/management/order-fulfillments", title: "统一履约", visible: userStore.hasPermissions(permissionCodes.orderFulfillment.read) } { icon: "task", path: "/management/order-fulfillments", title: "统一履约", visible: userStore.hasPermissions(permissionCodes.orderFulfillment.read) }
] ]
+3
View File
@@ -9,12 +9,14 @@ export type StockOperationType =
| "INITIAL_IN" | "INITIAL_IN"
| "MANUAL_IN" | "MANUAL_IN"
| "MANUAL_OUT" | "MANUAL_OUT"
| "MANUAL_REVOKE"
| "ORDER_RESERVE" | "ORDER_RESERVE"
| "ORDER_RELEASE" | "ORDER_RELEASE"
| "ORDER_OUT" | "ORDER_OUT"
| "ORDER_REVOKE" | "ORDER_REVOKE"
| "STOCKTAKE_GAIN" | "STOCKTAKE_GAIN"
| "STOCKTAKE_LOSS"; | "STOCKTAKE_LOSS";
export type StockRecordDirection = "IN" | "OUT";
export type ProductCategory = { export type ProductCategory = {
storeId?: string; storeId?: string;
@@ -124,6 +126,7 @@ export type StockRecordPage = {
bizType?: StockBizType; bizType?: StockBizType;
bizId?: string; bizId?: string;
operationType?: StockOperationType; operationType?: StockOperationType;
direction?: StockRecordDirection;
beginAt?: number; beginAt?: number;
endAt?: number; endAt?: number;
index: number; index: number;