From 1e141d3ce0b3c96110472349f27c48c0859ce8f8 Mon Sep 17 00:00:00 2001 From: MSuMshk <2039814060@qq.com> Date: Wed, 4 Mar 2026 11:03:37 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E8=B4=A2=E5=8A=A1?= =?UTF-8?q?=E4=BA=A4=E6=98=93=E6=B5=81=E6=B0=B4=E6=A8=A1=E5=9D=97=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E4=B8=8E=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web-antd/src/api/finance/index.ts | 159 ++++++++++++ .../components/RelatedOrderDrawer.vue | 181 ++++++++++++++ .../components/TransactionDetailDrawer.vue | 206 +++++++++++++++ .../components/TransactionExportModal.vue | 127 ++++++++++ .../components/TransactionFilterBar.vue | 186 ++++++++++++++ .../components/TransactionStatsBar.vue | 50 ++++ .../components/TransactionTableCard.vue | 177 +++++++++++++ .../composables/transaction-page/constants.ts | 89 +++++++ .../transaction-page/data-actions.ts | 126 ++++++++++ .../transaction-page/drawer-actions.ts | 78 ++++++ .../transaction-page/export-actions.ts | 67 +++++ .../transaction-page/filter-actions.ts | 89 +++++++ .../composables/transaction-page/helpers.ts | 197 +++++++++++++++ .../composables/useFinanceTransactionPage.ts | 236 ++++++++++++++++++ .../src/views/finance/transaction/index.vue | 127 ++++++++++ .../finance/transaction/styles/base.less | 18 ++ .../finance/transaction/styles/drawer.less | 193 ++++++++++++++ .../finance/transaction/styles/index.less | 9 + .../finance/transaction/styles/layout.less | 134 ++++++++++ .../finance/transaction/styles/modal.less | 51 ++++ .../transaction/styles/responsive.less | 59 +++++ .../finance/transaction/styles/table.less | 74 ++++++ .../src/views/finance/transaction/types.ts | 70 ++++++ 23 files changed, 2703 insertions(+) create mode 100644 apps/web-antd/src/api/finance/index.ts create mode 100644 apps/web-antd/src/views/finance/transaction/components/RelatedOrderDrawer.vue create mode 100644 apps/web-antd/src/views/finance/transaction/components/TransactionDetailDrawer.vue create mode 100644 apps/web-antd/src/views/finance/transaction/components/TransactionExportModal.vue create mode 100644 apps/web-antd/src/views/finance/transaction/components/TransactionFilterBar.vue create mode 100644 apps/web-antd/src/views/finance/transaction/components/TransactionStatsBar.vue create mode 100644 apps/web-antd/src/views/finance/transaction/components/TransactionTableCard.vue create mode 100644 apps/web-antd/src/views/finance/transaction/composables/transaction-page/constants.ts create mode 100644 apps/web-antd/src/views/finance/transaction/composables/transaction-page/data-actions.ts create mode 100644 apps/web-antd/src/views/finance/transaction/composables/transaction-page/drawer-actions.ts create mode 100644 apps/web-antd/src/views/finance/transaction/composables/transaction-page/export-actions.ts create mode 100644 apps/web-antd/src/views/finance/transaction/composables/transaction-page/filter-actions.ts create mode 100644 apps/web-antd/src/views/finance/transaction/composables/transaction-page/helpers.ts create mode 100644 apps/web-antd/src/views/finance/transaction/composables/useFinanceTransactionPage.ts create mode 100644 apps/web-antd/src/views/finance/transaction/index.vue create mode 100644 apps/web-antd/src/views/finance/transaction/styles/base.less create mode 100644 apps/web-antd/src/views/finance/transaction/styles/drawer.less create mode 100644 apps/web-antd/src/views/finance/transaction/styles/index.less create mode 100644 apps/web-antd/src/views/finance/transaction/styles/layout.less create mode 100644 apps/web-antd/src/views/finance/transaction/styles/modal.less create mode 100644 apps/web-antd/src/views/finance/transaction/styles/responsive.less create mode 100644 apps/web-antd/src/views/finance/transaction/styles/table.less create mode 100644 apps/web-antd/src/views/finance/transaction/types.ts diff --git a/apps/web-antd/src/api/finance/index.ts b/apps/web-antd/src/api/finance/index.ts new file mode 100644 index 0000000..44f9611 --- /dev/null +++ b/apps/web-antd/src/api/finance/index.ts @@ -0,0 +1,159 @@ +/** + * 文件职责:财务中心交易流水 API 契约与请求封装。 + */ +import { requestClient } from '#/api/request'; + +/** 交易类型筛选值。 */ +export type FinanceTransactionTypeFilter = + | 'all' + | 'income' + | 'point_redeem' + | 'refund' + | 'stored_card_recharge'; + +/** 交易渠道筛选值。 */ +export type FinanceTransactionChannelFilter = + | 'all' + | 'delivery' + | 'dine_in' + | 'pickup'; + +/** 交易支付方式筛选值。 */ +export type FinanceTransactionPaymentFilter = + | 'alipay' + | 'all' + | 'balance' + | 'card' + | 'cash' + | 'wechat'; + +/** 交易流水筛选参数。 */ +export interface FinanceTransactionFilterQuery { + channel?: FinanceTransactionChannelFilter; + endDate?: string; + keyword?: string; + paymentMethod?: FinanceTransactionPaymentFilter; + startDate?: string; + storeId: string; + type?: FinanceTransactionTypeFilter; +} + +/** 交易流水列表查询参数。 */ +export interface FinanceTransactionListQuery extends FinanceTransactionFilterQuery { + page: number; + pageSize: number; +} + +/** 交易流水列表行。 */ +export interface FinanceTransactionListItemDto { + amount: number; + channel: string; + isIncome: boolean; + occurredAt: string; + orderNo?: string; + paymentMethod: string; + remark: string; + transactionId: string; + transactionNo: string; + type: string; + typeText: string; +} + +/** 交易流水列表结果。 */ +export interface FinanceTransactionListResultDto { + items: FinanceTransactionListItemDto[]; + page: number; + pageIncomeAmount: number; + pageRefundAmount: number; + pageSize: number; + total: number; +} + +/** 交易流水统计结果。 */ +export interface FinanceTransactionStatsDto { + totalCount: number; + totalIncome: number; + totalRefund: number; +} + +/** 交易流水详情。 */ +export interface FinanceTransactionDetailDto { + amount: number; + arrivedAmount?: number; + channel: string; + customerName: string; + customerPhone: string; + giftAmount?: number; + memberMobileMasked?: string; + memberName?: string; + occurredAt: string; + orderNo?: string; + paymentMethod: string; + pointBalanceAfterChange?: number; + pointChangeAmount?: number; + rechargeAmount?: number; + refundNo?: string; + refundReason?: string; + remark: string; + storeId: string; + transactionId: string; + transactionNo: string; + type: string; + typeText: string; +} + +/** 交易流水导出结果。 */ +export interface FinanceTransactionExportDto { + fileContentBase64: string; + fileName: string; + totalCount: number; +} + +/** 查询交易流水列表。 */ +export async function getFinanceTransactionListApi( + params: FinanceTransactionListQuery, +) { + return requestClient.get( + '/finance/transaction/list', + { + params, + }, + ); +} + +/** 查询交易流水统计。 */ +export async function getFinanceTransactionStatsApi( + params: FinanceTransactionFilterQuery, +) { + return requestClient.get( + '/finance/transaction/stats', + { + params, + }, + ); +} + +/** 查询交易流水详情。 */ +export async function getFinanceTransactionDetailApi(params: { + storeId: string; + transactionId: string; +}) { + return requestClient.get( + '/finance/transaction/detail', + { + params, + }, + ); +} + +/** 导出交易流水 CSV。 */ +export async function exportFinanceTransactionCsvApi( + params: FinanceTransactionFilterQuery, +) { + return requestClient.get( + '/finance/transaction/export', + { + params, + }, + ); +} diff --git a/apps/web-antd/src/views/finance/transaction/components/RelatedOrderDrawer.vue b/apps/web-antd/src/views/finance/transaction/components/RelatedOrderDrawer.vue new file mode 100644 index 0000000..97eb2c3 --- /dev/null +++ b/apps/web-antd/src/views/finance/transaction/components/RelatedOrderDrawer.vue @@ -0,0 +1,181 @@ + + + diff --git a/apps/web-antd/src/views/finance/transaction/components/TransactionDetailDrawer.vue b/apps/web-antd/src/views/finance/transaction/components/TransactionDetailDrawer.vue new file mode 100644 index 0000000..c6d1a15 --- /dev/null +++ b/apps/web-antd/src/views/finance/transaction/components/TransactionDetailDrawer.vue @@ -0,0 +1,206 @@ + + + diff --git a/apps/web-antd/src/views/finance/transaction/components/TransactionExportModal.vue b/apps/web-antd/src/views/finance/transaction/components/TransactionExportModal.vue new file mode 100644 index 0000000..b63b179 --- /dev/null +++ b/apps/web-antd/src/views/finance/transaction/components/TransactionExportModal.vue @@ -0,0 +1,127 @@ + + + diff --git a/apps/web-antd/src/views/finance/transaction/components/TransactionFilterBar.vue b/apps/web-antd/src/views/finance/transaction/components/TransactionFilterBar.vue new file mode 100644 index 0000000..d1aa384 --- /dev/null +++ b/apps/web-antd/src/views/finance/transaction/components/TransactionFilterBar.vue @@ -0,0 +1,186 @@ + + + diff --git a/apps/web-antd/src/views/finance/transaction/components/TransactionStatsBar.vue b/apps/web-antd/src/views/finance/transaction/components/TransactionStatsBar.vue new file mode 100644 index 0000000..5eb0930 --- /dev/null +++ b/apps/web-antd/src/views/finance/transaction/components/TransactionStatsBar.vue @@ -0,0 +1,50 @@ + + + diff --git a/apps/web-antd/src/views/finance/transaction/components/TransactionTableCard.vue b/apps/web-antd/src/views/finance/transaction/components/TransactionTableCard.vue new file mode 100644 index 0000000..37795fb --- /dev/null +++ b/apps/web-antd/src/views/finance/transaction/components/TransactionTableCard.vue @@ -0,0 +1,177 @@ + + + diff --git a/apps/web-antd/src/views/finance/transaction/composables/transaction-page/constants.ts b/apps/web-antd/src/views/finance/transaction/composables/transaction-page/constants.ts new file mode 100644 index 0000000..23c61a8 --- /dev/null +++ b/apps/web-antd/src/views/finance/transaction/composables/transaction-page/constants.ts @@ -0,0 +1,89 @@ +import type { + FinanceTransactionFilterState, + FinanceTransactionPageSummaryState, + OptionItem, + QuickRangeOption, +} from '../../types'; + +/** + * 文件职责:交易流水页面常量与默认状态定义。 + */ +import type { + FinanceTransactionChannelFilter, + FinanceTransactionPaymentFilter, + FinanceTransactionStatsDto, + FinanceTransactionTypeFilter, +} from '#/api/finance'; + +import { getTodayDateString } from './helpers'; + +/** 交易流水查看权限。 */ +export const FINANCE_TRANSACTION_VIEW_PERMISSION = + 'tenant:finance:transaction:view'; + +/** 交易流水导出权限。 */ +export const FINANCE_TRANSACTION_EXPORT_PERMISSION = + 'tenant:finance:transaction:export'; + +/** 交易类型筛选项。 */ +export const TRANSACTION_TYPE_OPTIONS: OptionItem[] = [ + { label: '全部类型', value: 'all' }, + { label: '收入', value: 'income' }, + { label: '退款', value: 'refund' }, + { label: '储值充值', value: 'stored_card_recharge' }, + { label: '积分抵扣', value: 'point_redeem' }, +]; + +/** 交易渠道筛选项。 */ +export const TRANSACTION_CHANNEL_OPTIONS: OptionItem[] = [ + { label: '全部渠道', value: 'all' }, + { label: '外卖', value: 'delivery' }, + { label: '自提', value: 'pickup' }, + { label: '堂食', value: 'dine_in' }, +]; + +/** 支付方式筛选项。 */ +export const TRANSACTION_PAYMENT_OPTIONS: OptionItem[] = [ + { label: '全部支付方式', value: 'all' }, + { label: '微信', value: 'wechat' }, + { label: '支付宝', value: 'alipay' }, + { label: '现金', value: 'cash' }, + { label: '刷卡', value: 'card' }, + { label: '储值余额', value: 'balance' }, +]; + +/** 快捷日期选项。 */ +export const QUICK_RANGE_OPTIONS: QuickRangeOption[] = [ + { label: '今天', value: 'today' }, + { label: '昨天', value: 'yesterday' }, + { label: '近7天', value: '7d' }, + { label: '近30天', value: '30d' }, + { label: '本月', value: 'month' }, +]; + +/** 默认筛选状态。 */ +export function createDefaultFilters(): FinanceTransactionFilterState { + const today = getTodayDateString(); + return { + type: 'all' as FinanceTransactionTypeFilter, + channel: 'all' as FinanceTransactionChannelFilter, + paymentMethod: 'all' as FinanceTransactionPaymentFilter, + keyword: '', + startDate: today, + endDate: today, + quickRange: 'today', + }; +} + +/** 默认统计数据。 */ +export const DEFAULT_STATS: FinanceTransactionStatsDto = { + totalIncome: 0, + totalRefund: 0, + totalCount: 0, +}; + +/** 默认本页汇总数据。 */ +export const DEFAULT_PAGE_SUMMARY: FinanceTransactionPageSummaryState = { + pageIncomeAmount: 0, + pageRefundAmount: 0, +}; diff --git a/apps/web-antd/src/views/finance/transaction/composables/transaction-page/data-actions.ts b/apps/web-antd/src/views/finance/transaction/composables/transaction-page/data-actions.ts new file mode 100644 index 0000000..b7d0ce7 --- /dev/null +++ b/apps/web-antd/src/views/finance/transaction/composables/transaction-page/data-actions.ts @@ -0,0 +1,126 @@ +import type { + FinanceTransactionFilterState, + FinanceTransactionPageSummaryState, + FinanceTransactionPaginationState, +} from '../../types'; + +/** + * 文件职责:交易流水页面数据加载动作。 + */ +import type { + FinanceTransactionListItemDto, + FinanceTransactionStatsDto, +} from '#/api/finance'; +import type { StoreListItemDto } from '#/api/store'; + +import { + getFinanceTransactionListApi, + getFinanceTransactionStatsApi, +} from '#/api/finance'; +import { getStoreListApi } from '#/api/store'; + +import { buildFilterQueryPayload, buildListQueryPayload } from './helpers'; + +interface DataActionOptions { + filters: FinanceTransactionFilterState; + isListLoading: { value: boolean }; + isStatsLoading: { value: boolean }; + isStoreLoading: { value: boolean }; + pageSummary: FinanceTransactionPageSummaryState; + pagination: FinanceTransactionPaginationState; + rows: { value: FinanceTransactionListItemDto[] }; + selectedStoreId: { value: string }; + stats: FinanceTransactionStatsDto; + stores: { value: StoreListItemDto[] }; +} + +/** 创建数据相关动作。 */ +export function createDataActions(options: DataActionOptions) { + function resetStats() { + options.stats.totalIncome = 0; + options.stats.totalRefund = 0; + options.stats.totalCount = 0; + } + + function resetPageSummary() { + options.pageSummary.pageIncomeAmount = 0; + options.pageSummary.pageRefundAmount = 0; + } + + function clearPageData() { + options.rows.value = []; + options.pagination.total = 0; + resetPageSummary(); + resetStats(); + } + + async function loadStores() { + options.isStoreLoading.value = true; + try { + const result = await getStoreListApi({ page: 1, pageSize: 200 }); + options.stores.value = result.items; + + if (result.items.length === 0) { + options.selectedStoreId.value = ''; + clearPageData(); + return; + } + + const matched = result.items.some( + (item) => item.id === options.selectedStoreId.value, + ); + if (!matched) { + options.selectedStoreId.value = result.items[0]?.id ?? ''; + } + } finally { + options.isStoreLoading.value = false; + } + } + + async function loadPageData() { + if (!options.selectedStoreId.value) { + clearPageData(); + return; + } + + const listPayload = buildListQueryPayload( + options.selectedStoreId.value, + options.filters, + options.pagination.page, + options.pagination.pageSize, + ); + const filterPayload = buildFilterQueryPayload( + options.selectedStoreId.value, + options.filters, + ); + + options.isListLoading.value = true; + options.isStatsLoading.value = true; + try { + const [listResult, statsResult] = await Promise.all([ + getFinanceTransactionListApi(listPayload), + getFinanceTransactionStatsApi(filterPayload), + ]); + + options.rows.value = listResult.items; + options.pagination.total = listResult.total; + options.pagination.page = listResult.page; + options.pagination.pageSize = listResult.pageSize; + options.pageSummary.pageIncomeAmount = listResult.pageIncomeAmount; + options.pageSummary.pageRefundAmount = listResult.pageRefundAmount; + + options.stats.totalIncome = statsResult.totalIncome; + options.stats.totalRefund = statsResult.totalRefund; + options.stats.totalCount = statsResult.totalCount; + } finally { + options.isListLoading.value = false; + options.isStatsLoading.value = false; + } + } + + return { + clearPageData, + loadPageData, + loadStores, + }; +} diff --git a/apps/web-antd/src/views/finance/transaction/composables/transaction-page/drawer-actions.ts b/apps/web-antd/src/views/finance/transaction/composables/transaction-page/drawer-actions.ts new file mode 100644 index 0000000..e9a35c3 --- /dev/null +++ b/apps/web-antd/src/views/finance/transaction/composables/transaction-page/drawer-actions.ts @@ -0,0 +1,78 @@ +/** + * 文件职责:交易流水详情与关联订单抽屉动作。 + */ +import type { FinanceTransactionDetailDto } from '#/api/finance'; +import type { OrderAllDetailDto } from '#/api/order'; + +import { getFinanceTransactionDetailApi } from '#/api/finance'; +import { getOrderAllDetailApi } from '#/api/order'; + +interface DrawerActionOptions { + isRelatedOrderDrawerOpen: { value: boolean }; + isRelatedOrderLoading: { value: boolean }; + isTransactionDetailLoading: { value: boolean }; + isTransactionDrawerOpen: { value: boolean }; + relatedOrderDetail: { value: null | OrderAllDetailDto }; + selectedStoreId: { value: string }; + transactionDetail: { value: FinanceTransactionDetailDto | null }; +} + +/** 创建抽屉动作。 */ +export function createDrawerActions(options: DrawerActionOptions) { + function setTransactionDrawerOpen(value: boolean) { + options.isTransactionDrawerOpen.value = value; + if (!value) { + options.transactionDetail.value = null; + } + } + + function setRelatedOrderDrawerOpen(value: boolean) { + options.isRelatedOrderDrawerOpen.value = value; + if (!value) { + options.relatedOrderDetail.value = null; + } + } + + async function openTransactionDetail(transactionId: string) { + if (!options.selectedStoreId.value || !transactionId) { + return; + } + + options.isTransactionDrawerOpen.value = true; + options.transactionDetail.value = null; + options.isTransactionDetailLoading.value = true; + try { + options.transactionDetail.value = await getFinanceTransactionDetailApi({ + storeId: options.selectedStoreId.value, + transactionId, + }); + } finally { + options.isTransactionDetailLoading.value = false; + } + } + + async function openRelatedOrder(orderNo: string) { + if (!options.selectedStoreId.value || !orderNo) { + return; + } + + options.isRelatedOrderDrawerOpen.value = true; + options.relatedOrderDetail.value = null; + options.isRelatedOrderLoading.value = true; + try { + options.relatedOrderDetail.value = await getOrderAllDetailApi({ + storeId: options.selectedStoreId.value, + orderNo, + }); + } finally { + options.isRelatedOrderLoading.value = false; + } + } + + return { + openRelatedOrder, + openTransactionDetail, + setRelatedOrderDrawerOpen, + setTransactionDrawerOpen, + }; +} diff --git a/apps/web-antd/src/views/finance/transaction/composables/transaction-page/export-actions.ts b/apps/web-antd/src/views/finance/transaction/composables/transaction-page/export-actions.ts new file mode 100644 index 0000000..d0a4453 --- /dev/null +++ b/apps/web-antd/src/views/finance/transaction/composables/transaction-page/export-actions.ts @@ -0,0 +1,67 @@ +import type { FinanceTransactionFilterState } from '../../types'; + +/** + * 文件职责:交易流水导出弹窗与导出动作。 + */ +import { message } from 'ant-design-vue'; + +import { exportFinanceTransactionCsvApi } from '#/api/finance'; + +import { + buildFilterQueryPayload, + downloadBase64File, + isDateRangeInvalid, +} from './helpers'; + +interface ExportActionOptions { + canExport: { value: boolean }; + filters: FinanceTransactionFilterState; + isExportModalOpen: { value: boolean }; + isExporting: { value: boolean }; + selectedStoreId: { value: string }; +} + +/** 创建导出动作。 */ +export function createExportActions(options: ExportActionOptions) { + function setExportModalOpen(value: boolean) { + options.isExportModalOpen.value = value; + } + + function openExportModal() { + if (!options.canExport.value || !options.selectedStoreId.value) { + return; + } + options.isExportModalOpen.value = true; + } + + async function handleConfirmExport() { + if (!options.canExport.value || !options.selectedStoreId.value) { + return; + } + + if (isDateRangeInvalid(options.filters)) { + message.warning('开始日期不能晚于结束日期'); + return; + } + + options.isExporting.value = true; + try { + const payload = buildFilterQueryPayload( + options.selectedStoreId.value, + options.filters, + ); + const result = await exportFinanceTransactionCsvApi(payload); + downloadBase64File(result.fileName, result.fileContentBase64); + message.success(`导出成功,共 ${result.totalCount} 条记录`); + setExportModalOpen(false); + } finally { + options.isExporting.value = false; + } + } + + return { + handleConfirmExport, + openExportModal, + setExportModalOpen, + }; +} diff --git a/apps/web-antd/src/views/finance/transaction/composables/transaction-page/filter-actions.ts b/apps/web-antd/src/views/finance/transaction/composables/transaction-page/filter-actions.ts new file mode 100644 index 0000000..6e4815f --- /dev/null +++ b/apps/web-antd/src/views/finance/transaction/composables/transaction-page/filter-actions.ts @@ -0,0 +1,89 @@ +import type { + FinanceTransactionFilterState, + FinanceTransactionPaginationState, + QuickDateRangeKey, +} from '../../types'; + +/** + * 文件职责:交易流水页面筛选与分页行为。 + */ +import { message } from 'ant-design-vue'; + +import { isDateRangeInvalid, resolveQuickRangeDateRange } from './helpers'; + +interface FilterActionOptions { + filters: FinanceTransactionFilterState; + loadPageData: () => Promise; + pagination: FinanceTransactionPaginationState; +} + +/** 创建筛选行为。 */ +export function createFilterActions(options: FilterActionOptions) { + function setType(value: string) { + options.filters.type = (value || + 'all') as FinanceTransactionFilterState['type']; + } + + function setChannel(value: string) { + options.filters.channel = (value || + 'all') as FinanceTransactionFilterState['channel']; + } + + function setPaymentMethod(value: string) { + options.filters.paymentMethod = (value || + 'all') as FinanceTransactionFilterState['paymentMethod']; + } + + function setQuickRange(value: QuickDateRangeKey) { + options.filters.quickRange = value; + if (!value) { + return; + } + + const { startDate, endDate } = resolveQuickRangeDateRange(value); + options.filters.startDate = startDate; + options.filters.endDate = endDate; + } + + function setStartDate(value: string) { + options.filters.startDate = value; + options.filters.quickRange = ''; + } + + function setEndDate(value: string) { + options.filters.endDate = value; + options.filters.quickRange = ''; + } + + function setKeyword(value: string) { + options.filters.keyword = value; + } + + async function handleSearch() { + if (isDateRangeInvalid(options.filters)) { + message.warning('开始日期不能晚于结束日期'); + return; + } + + options.pagination.page = 1; + await options.loadPageData(); + } + + async function handlePageChange(page: number, pageSize: number) { + options.pagination.page = page; + options.pagination.pageSize = pageSize; + await options.loadPageData(); + } + + return { + handlePageChange, + handleSearch, + setChannel, + setEndDate, + setKeyword, + setPaymentMethod, + setQuickRange, + setStartDate, + setType, + }; +} diff --git a/apps/web-antd/src/views/finance/transaction/composables/transaction-page/helpers.ts b/apps/web-antd/src/views/finance/transaction/composables/transaction-page/helpers.ts new file mode 100644 index 0000000..429ea50 --- /dev/null +++ b/apps/web-antd/src/views/finance/transaction/composables/transaction-page/helpers.ts @@ -0,0 +1,197 @@ +import type { + FinanceTransactionFilterState, + FinanceTransactionListQueryPayload, + FinanceTransactionQueryPayload, + QuickDateRangeKey, +} from '../../types'; + +/** + * 文件职责:交易流水页面纯函数与数据转换工具。 + */ +import type { + FinanceTransactionChannelFilter, + FinanceTransactionPaymentFilter, + FinanceTransactionTypeFilter, +} from '#/api/finance'; + +function formatDate(date: Date) { + const year = date.getFullYear(); + const month = `${date.getMonth() + 1}`.padStart(2, '0'); + const day = `${date.getDate()}`.padStart(2, '0'); + return `${year}-${month}-${day}`; +} + +function toDateOnly(date: Date) { + return new Date(date.getFullYear(), date.getMonth(), date.getDate()); +} + +function shiftDate(date: Date, dayOffset: number) { + const next = new Date(date); + next.setDate(next.getDate() + dayOffset); + return next; +} + +function normalizeType( + value: FinanceTransactionTypeFilter, +): FinanceTransactionTypeFilter | undefined { + return value === 'all' ? undefined : value; +} + +function normalizeChannel( + value: FinanceTransactionChannelFilter, +): FinanceTransactionChannelFilter | undefined { + return value === 'all' ? undefined : value; +} + +function normalizePayment( + value: FinanceTransactionPaymentFilter, +): FinanceTransactionPaymentFilter | undefined { + return value === 'all' ? undefined : value; +} + +/** 获取今天日期字符串(yyyy-MM-dd)。 */ +export function getTodayDateString() { + return formatDate(new Date()); +} + +/** 根据快捷日期键计算筛选起止日期。 */ +export function resolveQuickRangeDateRange(value: QuickDateRangeKey) { + const today = toDateOnly(new Date()); + let start = today; + let end = today; + + switch (value) { + case '7d': { + start = shiftDate(today, -6); + + break; + } + case '30d': { + start = shiftDate(today, -29); + + break; + } + case 'month': { + start = new Date(today.getFullYear(), today.getMonth(), 1); + + break; + } + case 'yesterday': { + start = shiftDate(today, -1); + end = start; + + break; + } + // No default + } + + return { + startDate: formatDate(start), + endDate: formatDate(end), + }; +} + +/** 构建交易流水筛选请求。 */ +export function buildFilterQueryPayload( + storeId: string, + filters: FinanceTransactionFilterState, +): FinanceTransactionQueryPayload { + return { + storeId, + startDate: filters.startDate || undefined, + endDate: filters.endDate || undefined, + type: normalizeType(filters.type), + channel: normalizeChannel(filters.channel), + paymentMethod: normalizePayment(filters.paymentMethod), + keyword: filters.keyword.trim() || undefined, + }; +} + +/** 构建交易流水列表请求。 */ +export function buildListQueryPayload( + storeId: string, + filters: FinanceTransactionFilterState, + page: number, + pageSize: number, +): FinanceTransactionListQueryPayload { + return { + ...buildFilterQueryPayload(storeId, filters), + page, + pageSize, + }; +} + +/** 判断日期范围是否合法。 */ +export function isDateRangeInvalid(filters: FinanceTransactionFilterState) { + if (!filters.startDate || !filters.endDate) { + return false; + } + return filters.startDate > filters.endDate; +} + +/** 货币格式化(人民币)。 */ +export function formatCurrency(value: number) { + return new Intl.NumberFormat('zh-CN', { + style: 'currency', + currency: 'CNY', + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }).format(Number.isFinite(value) ? value : 0); +} + +/** 交易金额(带符号)格式化。 */ +export function formatSignedAmount(value: number, isIncome: boolean) { + const normalized = Number.isFinite(value) ? value : 0; + if (normalized === 0) { + return formatCurrency(0); + } + + if (normalized > 0 || isIncome) { + return `+${formatCurrency(Math.abs(normalized))}`; + } + + return `-${formatCurrency(Math.abs(normalized))}`; +} + +/** 金额视觉色调。 */ +export function resolveAmountToneClass(value: number, isIncome: boolean) { + if (value > 0 || isIncome) { + return 'income'; + } + if (value < 0) { + return 'expense'; + } + return 'neutral'; +} + +/** 交易类型标签颜色。 */ +export function resolveTransactionTypeTagColor(type: string) { + if (type === 'income') return 'green'; + if (type === 'refund') return 'red'; + if (type === 'stored_card_recharge') return 'blue'; + if (type === 'point_redeem') return 'orange'; + return 'default'; +} + +function decodeBase64ToBlob(base64: string) { + const binary = atob(base64); + const bytes = new Uint8Array(binary.length); + for (let index = 0; index < binary.length; index += 1) { + bytes[index] = binary.codePointAt(index) ?? 0; + } + return new Blob([bytes], { type: 'text/csv;charset=utf-8;' }); +} + +/** 下载 Base64 编码文件。 */ +export function downloadBase64File( + fileName: string, + fileContentBase64: string, +) { + const blob = decodeBase64ToBlob(fileContentBase64); + const url = URL.createObjectURL(blob); + const anchor = document.createElement('a'); + anchor.href = url; + anchor.download = fileName; + anchor.click(); + URL.revokeObjectURL(url); +} diff --git a/apps/web-antd/src/views/finance/transaction/composables/useFinanceTransactionPage.ts b/apps/web-antd/src/views/finance/transaction/composables/useFinanceTransactionPage.ts new file mode 100644 index 0000000..c51aadd --- /dev/null +++ b/apps/web-antd/src/views/finance/transaction/composables/useFinanceTransactionPage.ts @@ -0,0 +1,236 @@ +/** + * 文件职责:交易流水页面状态与动作编排。 + */ +import type { + FinanceTransactionDetailDto, + FinanceTransactionListItemDto, + FinanceTransactionStatsDto, +} from '#/api/finance'; +import type { OrderAllDetailDto } from '#/api/order'; +import type { StoreListItemDto } from '#/api/store'; + +import { computed, onActivated, onMounted, reactive, ref, watch } from 'vue'; + +import { useAccessStore } from '@vben/stores'; + +import { + createDefaultFilters, + DEFAULT_PAGE_SUMMARY, + DEFAULT_STATS, + FINANCE_TRANSACTION_EXPORT_PERMISSION, + FINANCE_TRANSACTION_VIEW_PERMISSION, +} from './transaction-page/constants'; +import { createDataActions } from './transaction-page/data-actions'; +import { createDrawerActions } from './transaction-page/drawer-actions'; +import { createExportActions } from './transaction-page/export-actions'; +import { createFilterActions } from './transaction-page/filter-actions'; + +/** 创建交易流水页面组合状态。 */ +export function useFinanceTransactionPage() { + const accessStore = useAccessStore(); + + const stores = ref([]); + const selectedStoreId = ref(''); + const isStoreLoading = ref(false); + + const filters = reactive(createDefaultFilters()); + const rows = ref([]); + const pagination = reactive({ + page: 1, + pageSize: 20, + total: 0, + }); + const pageSummary = reactive({ ...DEFAULT_PAGE_SUMMARY }); + + const stats = reactive({ ...DEFAULT_STATS }); + const isListLoading = ref(false); + const isStatsLoading = ref(false); + + const transactionDetail = ref(null); + const isTransactionDrawerOpen = ref(false); + const isTransactionDetailLoading = ref(false); + + const relatedOrderDetail = ref(null); + const isRelatedOrderDrawerOpen = ref(false); + const isRelatedOrderLoading = ref(false); + + const isExportModalOpen = ref(false); + const isExporting = ref(false); + + const storeOptions = computed(() => + stores.value.map((item) => ({ + label: item.name, + value: item.id, + })), + ); + + const selectedStoreName = computed( + () => + storeOptions.value.find((item) => item.value === selectedStoreId.value) + ?.label ?? '--', + ); + + const accessCodeSet = computed( + () => new Set((accessStore.accessCodes ?? []).map(String)), + ); + + const canView = computed(() => + accessCodeSet.value.has(FINANCE_TRANSACTION_VIEW_PERMISSION), + ); + + const canExport = computed(() => + accessCodeSet.value.has(FINANCE_TRANSACTION_EXPORT_PERMISSION), + ); + + const { clearPageData, loadPageData, loadStores } = createDataActions({ + stores, + selectedStoreId, + filters, + rows, + pagination, + pageSummary, + stats, + isStoreLoading, + isListLoading, + isStatsLoading, + }); + + const { + handlePageChange, + handleSearch, + setChannel, + setEndDate, + setKeyword, + setPaymentMethod, + setQuickRange, + setStartDate, + setType, + } = createFilterActions({ + filters, + pagination, + loadPageData, + }); + + const { + openRelatedOrder, + openTransactionDetail, + setRelatedOrderDrawerOpen, + setTransactionDrawerOpen, + } = createDrawerActions({ + selectedStoreId, + transactionDetail, + isTransactionDrawerOpen, + isTransactionDetailLoading, + relatedOrderDetail, + isRelatedOrderDrawerOpen, + isRelatedOrderLoading, + }); + + const { handleConfirmExport, openExportModal, setExportModalOpen } = + createExportActions({ + canExport, + selectedStoreId, + filters, + isExportModalOpen, + isExporting, + }); + + function setSelectedStoreId(value: string) { + selectedStoreId.value = value; + } + + function clearByPermission() { + stores.value = []; + selectedStoreId.value = ''; + clearPageData(); + setTransactionDrawerOpen(false); + setRelatedOrderDrawerOpen(false); + setExportModalOpen(false); + } + + watch(selectedStoreId, async (storeId) => { + setTransactionDrawerOpen(false); + setRelatedOrderDrawerOpen(false); + + if (!storeId) { + clearPageData(); + return; + } + + pagination.page = 1; + await loadPageData(); + }); + + watch(canView, async (value, oldValue) => { + if (value === oldValue) { + return; + } + + if (!value) { + clearByPermission(); + return; + } + + await loadStores(); + }); + + onMounted(async () => { + if (!canView.value) { + clearByPermission(); + return; + } + + await loadStores(); + }); + + onActivated(() => { + if (!canView.value) { + return; + } + + if (stores.value.length === 0 || !selectedStoreId.value) { + void loadStores(); + } + }); + + return { + canExport, + canView, + filters, + handleConfirmExport, + handlePageChange, + handleSearch, + isExportModalOpen, + isExporting, + isListLoading, + isRelatedOrderDrawerOpen, + isRelatedOrderLoading, + isStatsLoading, + isStoreLoading, + isTransactionDetailLoading, + isTransactionDrawerOpen, + openExportModal, + openRelatedOrder, + openTransactionDetail, + pageSummary, + pagination, + relatedOrderDetail, + rows, + selectedStoreId, + selectedStoreName, + setChannel, + setEndDate, + setExportModalOpen, + setKeyword, + setPaymentMethod, + setQuickRange, + setRelatedOrderDrawerOpen, + setSelectedStoreId, + setStartDate, + setTransactionDrawerOpen, + setType, + stats, + storeOptions, + transactionDetail, + }; +} diff --git a/apps/web-antd/src/views/finance/transaction/index.vue b/apps/web-antd/src/views/finance/transaction/index.vue new file mode 100644 index 0000000..df9ee6d --- /dev/null +++ b/apps/web-antd/src/views/finance/transaction/index.vue @@ -0,0 +1,127 @@ + + + + + diff --git a/apps/web-antd/src/views/finance/transaction/styles/base.less b/apps/web-antd/src/views/finance/transaction/styles/base.less new file mode 100644 index 0000000..00f96ca --- /dev/null +++ b/apps/web-antd/src/views/finance/transaction/styles/base.less @@ -0,0 +1,18 @@ +/** + * 文件职责:交易流水页面基础容器样式。 + */ +.page-finance-transaction { + .ant-card { + border-radius: 10px; + } +} + +.ft-page { + display: flex; + flex-direction: column; + gap: 12px; +} + +.ft-mono { + font-family: ui-monospace, sfmono-regular, menlo, consolas, monospace; +} diff --git a/apps/web-antd/src/views/finance/transaction/styles/drawer.less b/apps/web-antd/src/views/finance/transaction/styles/drawer.less new file mode 100644 index 0000000..c6500a5 --- /dev/null +++ b/apps/web-antd/src/views/finance/transaction/styles/drawer.less @@ -0,0 +1,193 @@ +/** + * 文件职责:交易详情与关联订单抽屉样式。 + */ +.page-finance-transaction { + .ant-drawer { + .ant-drawer-header { + padding: 14px 18px; + border-bottom: 1px solid #f0f0f0; + } + + .ant-drawer-body { + padding: 16px 20px; + } + + .ant-drawer-footer { + padding: 12px 20px; + border-top: 1px solid #f0f0f0; + } + } +} + +.ft-section { + margin-bottom: 20px; + + .ft-section-title { + padding-left: 10px; + margin-bottom: 14px; + font-size: 14px; + font-weight: 600; + color: rgb(0 0 0 / 88%); + border-left: 3px solid #1677ff; + } +} + +.ft-info-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px 20px; + font-size: 13px; + + .label { + color: rgb(0 0 0 / 45%); + } + + .full { + grid-column: 1 / -1; + } +} + +.ft-order-link { + padding-inline: 0; +} + +.ft-detail-amount { + font-weight: 600; + color: rgb(0 0 0 / 88%); + + &.income { + color: #52c41a; + } + + &.expense { + color: #ff4d4f; + } +} + +.ft-detail-table-wrap { + overflow-x: auto; +} + +.ft-detail-table { + width: 100%; + min-width: 520px; + font-size: 13px; + border-collapse: collapse; + + th { + padding: 8px 10px; + font-size: 12px; + font-weight: 500; + color: rgb(0 0 0 / 45%); + text-align: left; + background: #fafafa; + border-bottom: 1px solid #f0f0f0; + + &.right { + text-align: right; + } + } + + td { + padding: 8px 10px; + color: rgb(0 0 0 / 88%); + border-bottom: 1px solid #f5f5f5; + + &.right { + text-align: right; + } + } +} + +.ft-amount-summary { + display: flex; + flex-direction: column; + gap: 6px; + padding: 12px 10px 0; + font-size: 13px; + + > div { + display: flex; + justify-content: space-between; + color: rgb(0 0 0 / 65%); + } + + .discount { + color: #ff4d4f; + } + + .total { + padding-top: 8px; + margin-top: 4px; + font-weight: 600; + color: rgb(0 0 0 / 88%); + border-top: 1px solid #f0f0f0; + + span:last-child { + font-size: 15px; + color: #1677ff; + } + } +} + +.ft-timeline { + position: relative; + padding-left: 22px; + + .ft-timeline-item { + position: relative; + display: flex; + gap: 8px; + align-items: center; + padding-bottom: 18px; + font-size: 13px; + + &::before { + position: absolute; + top: 5px; + left: -22px; + width: 10px; + height: 10px; + content: ''; + background: #1677ff; + border: 2px solid #d6e4ff; + border-radius: 50%; + } + + &::after { + position: absolute; + top: 17px; + left: -18px; + width: 2px; + height: calc(100% - 12px); + content: ''; + background: #e8e8e8; + } + + &:last-child { + padding-bottom: 0; + + &::after { + display: none; + } + } + + .text { + font-weight: 500; + color: rgb(0 0 0 / 88%); + } + + .time { + color: rgb(0 0 0 / 45%); + } + } +} + +.ft-remark-box { + padding: 10px 14px; + font-size: 13px; + color: rgb(0 0 0 / 65%); + background: #fafafa; + border: 1px solid #f0f0f0; + border-radius: 8px; +} diff --git a/apps/web-antd/src/views/finance/transaction/styles/index.less b/apps/web-antd/src/views/finance/transaction/styles/index.less new file mode 100644 index 0000000..ee1dbe6 --- /dev/null +++ b/apps/web-antd/src/views/finance/transaction/styles/index.less @@ -0,0 +1,9 @@ +/** + * 文件职责:交易流水页面样式聚合入口。 + */ +@import './base.less'; +@import './layout.less'; +@import './table.less'; +@import './drawer.less'; +@import './modal.less'; +@import './responsive.less'; diff --git a/apps/web-antd/src/views/finance/transaction/styles/layout.less b/apps/web-antd/src/views/finance/transaction/styles/layout.less new file mode 100644 index 0000000..3f36f03 --- /dev/null +++ b/apps/web-antd/src/views/finance/transaction/styles/layout.less @@ -0,0 +1,134 @@ +/** + * 文件职责:交易流水页面布局与筛选区域样式。 + */ +.ft-stats { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 12px; +} + +.ft-stat-card { + padding: 18px 20px; + background: #fff; + border: 1px solid #f0f0f0; + border-radius: 10px; + box-shadow: 0 2px 8px rgb(15 23 42 / 6%); + transition: all 0.2s ease; + + &:hover { + box-shadow: 0 6px 14px rgb(15 23 42 / 10%); + transform: translateY(-1px); + } +} + +.ft-stat-label { + display: flex; + gap: 6px; + align-items: center; + margin-bottom: 6px; + font-size: 13px; + color: rgb(0 0 0 / 45%); +} + +.ft-stat-icon { + width: 16px; + height: 16px; +} + +.ft-stat-value { + font-size: 24px; + font-weight: 700; + line-height: 1.2; + color: rgb(0 0 0 / 88%); + + &.is-green { + color: #52c41a; + } + + &.is-red { + color: #ff4d4f; + } +} + +.ft-toolbar { + display: flex; + flex-wrap: wrap; + gap: 10px; + align-items: center; + padding: 14px 18px; + background: #fff; + border: 1px solid #f0f0f0; + border-radius: 10px; + box-shadow: 0 2px 8px rgb(15 23 42 / 6%); + + .ft-store-select { + width: 220px; + } + + .ft-date-input { + width: 145px; + } + + .ft-type-select { + width: 120px; + } + + .ft-channel-select { + width: 110px; + } + + .ft-payment-select { + width: 130px; + } + + .ft-search { + width: 190px; + } + + .ft-date-sep { + font-size: 13px; + line-height: 32px; + color: rgb(0 0 0 / 45%); + } + + .ft-toolbar-right { + margin-left: auto; + } + + .ft-search-icon { + width: 14px; + height: 14px; + color: rgb(0 0 0 / 45%); + } + + .ft-export-btn { + display: inline-flex; + gap: 4px; + align-items: center; + height: 32px; + } + + .ant-select-selector, + .ant-input, + .ant-input-affix-wrapper { + height: 32px; + font-size: 13px; + } + + .ant-input-affix-wrapper .ant-input { + height: 100%; + } +} + +.ft-quick-dates { + display: flex; + gap: 4px; + + .ant-btn { + min-width: 56px; + height: 28px; + padding: 0 10px; + font-size: 12px; + border-radius: 4px; + } +} diff --git a/apps/web-antd/src/views/finance/transaction/styles/modal.less b/apps/web-antd/src/views/finance/transaction/styles/modal.less new file mode 100644 index 0000000..13b4477 --- /dev/null +++ b/apps/web-antd/src/views/finance/transaction/styles/modal.less @@ -0,0 +1,51 @@ +/** + * 文件职责:交易流水导出弹窗样式。 + */ +.ft-export-modal { + display: flex; + flex-direction: column; + gap: 12px; +} + +.ft-export-desc { + margin: 0; + font-size: 13px; + color: rgb(0 0 0 / 65%); +} + +.ft-export-list { + display: flex; + flex-direction: column; + gap: 8px; + padding: 12px 14px; + background: #fafafa; + border: 1px solid #f0f0f0; + border-radius: 8px; +} + +.ft-export-line { + display: flex; + gap: 16px; + justify-content: space-between; + font-size: 13px; + + .ft-export-label { + color: rgb(0 0 0 / 45%); + } + + .ft-export-value { + color: rgb(0 0 0 / 88%); + text-align: right; + } + + &.is-total { + padding-top: 8px; + margin-top: 4px; + font-weight: 600; + border-top: 1px solid #f0f0f0; + + .ft-export-value { + color: #1677ff; + } + } +} diff --git a/apps/web-antd/src/views/finance/transaction/styles/responsive.less b/apps/web-antd/src/views/finance/transaction/styles/responsive.less new file mode 100644 index 0000000..581d0c0 --- /dev/null +++ b/apps/web-antd/src/views/finance/transaction/styles/responsive.less @@ -0,0 +1,59 @@ +/** + * 文件职责:交易流水页面响应式样式。 + */ +@media (max-width: 1600px) { + .ft-stats { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (max-width: 768px) { + .ft-stats { + grid-template-columns: 1fr; + } + + .ft-toolbar { + padding: 14px 12px; + + .ft-store-select, + .ft-date-input, + .ft-type-select, + .ft-channel-select, + .ft-payment-select, + .ft-search { + width: 100%; + } + + .ft-date-sep { + display: none; + } + + .ft-quick-dates { + flex-wrap: wrap; + width: 100%; + + .ant-btn { + flex: 1; + min-width: 68px; + } + } + + .ft-toolbar-right { + width: 100%; + margin-left: 0; + } + + .ft-export-btn { + justify-content: center; + width: 100%; + } + } + + .ft-info-grid { + grid-template-columns: 1fr; + + .full { + grid-column: auto; + } + } +} diff --git a/apps/web-antd/src/views/finance/transaction/styles/table.less b/apps/web-antd/src/views/finance/transaction/styles/table.less new file mode 100644 index 0000000..b0b6f94 --- /dev/null +++ b/apps/web-antd/src/views/finance/transaction/styles/table.less @@ -0,0 +1,74 @@ +/** + * 文件职责:交易流水表格区域样式。 + */ +.ft-table-card { + overflow: hidden; + background: #fff; + border: 1px solid #f0f0f0; + border-radius: 10px; + + .ant-table-wrapper { + .ant-table-thead > tr > th { + font-size: 13px; + white-space: nowrap; + } + + .ant-table-tbody > tr > td { + font-size: 13px; + vertical-align: middle; + } + } + + .ant-pagination { + margin: 14px 16px; + } +} + +.ft-link-action { + padding-inline: 0; +} + +.ft-amount { + font-weight: 600; + white-space: nowrap; + + &.income { + color: #52c41a; + } + + &.expense { + color: #ff4d4f; + } + + &.neutral { + color: rgb(0 0 0 / 88%); + } +} + +.ft-time { + font-size: 12px; + color: rgb(0 0 0 / 45%); + white-space: nowrap; +} + +.ft-remark { + display: inline-block; + max-width: 140px; + overflow: hidden; + text-overflow: ellipsis; + font-size: 12px; + color: rgb(0 0 0 / 45%); + white-space: nowrap; +} + +.ft-summary-row > td { + font-size: 13px; + font-weight: 600; + color: rgb(0 0 0 / 88%); + background: #f8f9fb !important; + border-top: 2px solid #e5e7eb; +} + +.ft-summary-text { + white-space: nowrap; +} diff --git a/apps/web-antd/src/views/finance/transaction/types.ts b/apps/web-antd/src/views/finance/transaction/types.ts new file mode 100644 index 0000000..a3b417b --- /dev/null +++ b/apps/web-antd/src/views/finance/transaction/types.ts @@ -0,0 +1,70 @@ +/** + * 文件职责:交易流水页面本地状态类型定义。 + */ +import type { + FinanceTransactionChannelFilter, + FinanceTransactionPaymentFilter, + FinanceTransactionTypeFilter, +} from '#/api/finance'; + +/** 快捷日期范围键。 */ +export type QuickDateRangeKey = + | '7d' + | '30d' + | '' + | 'month' + | 'today' + | 'yesterday'; + +/** 交易流水筛选状态。 */ +export interface FinanceTransactionFilterState { + channel: FinanceTransactionChannelFilter; + endDate: string; + keyword: string; + paymentMethod: FinanceTransactionPaymentFilter; + quickRange: QuickDateRangeKey; + startDate: string; + type: FinanceTransactionTypeFilter; +} + +/** 交易流水分页状态。 */ +export interface FinanceTransactionPaginationState { + page: number; + pageSize: number; + total: number; +} + +/** 交易流水本页汇总状态。 */ +export interface FinanceTransactionPageSummaryState { + pageIncomeAmount: number; + pageRefundAmount: number; +} + +/** 通用选项项。 */ +export interface OptionItem { + label: string; + value: string; +} + +/** 快捷日期选项。 */ +export interface QuickRangeOption { + label: string; + value: QuickDateRangeKey; +} + +/** 交易流水筛选请求负载。 */ +export interface FinanceTransactionQueryPayload { + channel?: FinanceTransactionChannelFilter; + endDate?: string; + keyword?: string; + paymentMethod?: FinanceTransactionPaymentFilter; + startDate?: string; + storeId: string; + type?: FinanceTransactionTypeFilter; +} + +/** 交易流水列表请求负载。 */ +export interface FinanceTransactionListQueryPayload extends FinanceTransactionQueryPayload { + page: number; + pageSize: number; +}