Files
TakeoutSaaS.TenantUI/apps/web-antd/src/views/finance/transaction/components/TransactionTableCard.vue

178 lines
4.3 KiB
Vue

<script setup lang="ts">
/**
* 文件职责:交易流水列表表格与本页汇总行。
*/
import type { TablePaginationConfig, TableProps } from 'ant-design-vue';
import type { FinanceTransactionListItemDto } from '#/api/finance';
import { h } from 'vue';
import { Button, Table, Tag } from 'ant-design-vue';
import {
formatCurrency,
formatSignedAmount,
resolveAmountToneClass,
resolveTransactionTypeTagColor,
} from '../composables/transaction-page/helpers';
interface PaginationState {
page: number;
pageSize: number;
total: number;
}
interface Props {
loading: boolean;
pageIncomeAmount: number;
pageRefundAmount: number;
pagination: PaginationState;
rows: FinanceTransactionListItemDto[];
}
const props = defineProps<Props>();
const emit = defineEmits<{
(event: 'detail', transactionId: string): void;
(event: 'orderDetail', orderNo: string): void;
(event: 'pageChange', page: number, pageSize: number): void;
}>();
const columns: TableProps['columns'] = [
{
title: '流水号',
dataIndex: 'transactionNo',
width: 180,
customRender: ({ record }) =>
h(
Button,
{
type: 'link',
class: 'ft-link-action ft-mono',
onClick: () => emit('detail', String(record.transactionId ?? '')),
},
() => String(record.transactionNo ?? '--'),
),
},
{
title: '关联订单',
dataIndex: 'orderNo',
width: 170,
customRender: ({ record }) => {
const orderNo = String(record.orderNo ?? '').trim();
if (!orderNo) {
return h('span', { class: 'ft-mono' }, '--');
}
return h(
Button,
{
type: 'link',
class: 'ft-link-action ft-mono',
onClick: () => emit('orderDetail', orderNo),
},
() => `#${orderNo}`,
);
},
},
{
title: '类型',
dataIndex: 'typeText',
width: 120,
customRender: ({ record }) =>
h(
Tag,
{ color: resolveTransactionTypeTagColor(String(record.type ?? '')) },
() => String(record.typeText ?? '--'),
),
},
{
title: '渠道',
dataIndex: 'channel',
width: 90,
},
{
title: '支付方式',
dataIndex: 'paymentMethod',
width: 110,
},
{
title: '金额',
dataIndex: 'amount',
width: 140,
align: 'right',
customRender: ({ record }) => {
const amount = Number(record.amount || 0);
return h(
'span',
{
class: `ft-amount ${resolveAmountToneClass(amount, Boolean(record.isIncome))}`,
},
formatSignedAmount(amount, Boolean(record.isIncome)),
);
},
},
{
title: '交易时间',
dataIndex: 'occurredAt',
width: 180,
customRender: ({ text }) => h('span', { class: 'ft-time' }, String(text)),
},
{
title: '备注',
dataIndex: 'remark',
ellipsis: true,
customRender: ({ text }) =>
h(
'span',
{
class: 'ft-remark',
title: String(text ?? ''),
},
String(text || '--'),
),
},
];
function handleTableChange(next: TablePaginationConfig) {
emit('pageChange', Number(next.current || 1), Number(next.pageSize || 20));
}
</script>
<template>
<div class="ft-table-card">
<Table
row-key="transactionId"
:columns="columns"
:data-source="props.rows"
:loading="props.loading"
:pagination="{
current: props.pagination.page,
pageSize: props.pagination.pageSize,
total: props.pagination.total,
showSizeChanger: true,
pageSizeOptions: ['20', '50', '100'],
showTotal: (total: number) => `共 ${total} 条`,
}"
@change="handleTableChange"
>
<template #summary>
<Table.Summary.Row class="ft-summary-row">
<Table.Summary.Cell :index="0" :col-span="5" align="right">
本页合计
</Table.Summary.Cell>
<Table.Summary.Cell :index="5" align="right">
<span class="ft-summary-text">
收入 {{ formatCurrency(props.pageIncomeAmount) }} / 退款
{{ formatCurrency(props.pageRefundAmount) }}
</span>
</Table.Summary.Cell>
<Table.Summary.Cell :index="6" />
<Table.Summary.Cell :index="7" />
</Table.Summary.Row>
</template>
</Table>
</div>
</template>