470 lines
10 KiB
Vue
470 lines
10 KiB
Vue
<script lang='ts' setup>
|
||
import dayjs from "dayjs";
|
||
import { arrowDownCircleOutline, arrowUpCircleOutline, listOutline, walletOutline } from "ionicons/icons";
|
||
import { client, safeClient } from "@/api";
|
||
|
||
const walletStore = useWalletStore();
|
||
const balanceWallet = await walletStore.getWalletByType("balance");
|
||
const profitWallet = await walletStore.getWalletByType("profit");
|
||
|
||
const [allQuery] = useResetRef<any>({
|
||
pageIndex: 1,
|
||
pageSize: 10,
|
||
});
|
||
const [incomeQuery] = useResetRef<any>({
|
||
entryType: "subscription_unlock",
|
||
pageIndex: 1,
|
||
pageSize: 10,
|
||
});
|
||
const [investmentQuery] = useResetRef<any>({
|
||
entryType: "subscription_purchase",
|
||
pageIndex: 1,
|
||
pageSize: 10,
|
||
});
|
||
|
||
const { data: allRecords, execute: executeAll } = safeClient(() => client.api.ledger.entries.get({ query: { ...allQuery.value } }));
|
||
const { data: incomeRecords, execute: executeIncome } = safeClient(() => client.api.ledger.entries.get({ query: { ...incomeQuery.value } }), { immediate: false });
|
||
const { data: investmentRecords, execute: executeInvestment } = safeClient(() => client.api.ledger.entries.get({ query: { ...investmentQuery.value } }), { immediate: false });
|
||
|
||
// 当前选中的标签
|
||
const selectedTab = ref<"all" | "income" | "investment">("all");
|
||
|
||
// 获取当前标签对应的数据源和执行函数
|
||
const currentData = computed(() => {
|
||
switch (selectedTab.value) {
|
||
case "all":
|
||
return allRecords;
|
||
case "income":
|
||
return incomeRecords;
|
||
case "investment":
|
||
return investmentRecords;
|
||
default:
|
||
return allRecords;
|
||
}
|
||
});
|
||
|
||
const currentExecute = computed(() => {
|
||
switch (selectedTab.value) {
|
||
case "all":
|
||
return executeAll;
|
||
case "income":
|
||
return executeIncome;
|
||
case "investment":
|
||
return executeInvestment;
|
||
default:
|
||
return executeAll;
|
||
}
|
||
});
|
||
|
||
const currentQuery = computed(() => {
|
||
switch (selectedTab.value) {
|
||
case "all":
|
||
return allQuery;
|
||
case "income":
|
||
return incomeQuery;
|
||
case "investment":
|
||
return investmentQuery;
|
||
default:
|
||
return allQuery;
|
||
}
|
||
});
|
||
|
||
// 当前标签的记录列表(API返回的已分页数据)
|
||
const filteredRecords = computed(() => {
|
||
return currentData.value.value?.data || [];
|
||
});
|
||
|
||
// 全部记录数量
|
||
const totalRecords = computed(() => {
|
||
return currentData.value.value?.pagination.total || 0;
|
||
});
|
||
|
||
// 总页数
|
||
const totalPages = computed(() => {
|
||
return Math.ceil(totalRecords.value / currentQuery.value.value.pageSize);
|
||
});
|
||
|
||
// 当前页码
|
||
const currentPage = computed(() => {
|
||
return currentQuery.value.value.pageIndex;
|
||
});
|
||
|
||
function handleTabChange(event: CustomEvent) {
|
||
selectedTab.value = event.detail.value;
|
||
// 切换标签时重置到第一页
|
||
currentQuery.value.value.pageIndex = 1;
|
||
currentExecute.value();
|
||
}
|
||
|
||
async function goToPage(page: number) {
|
||
if (page >= 1 && page <= totalPages.value) {
|
||
currentQuery.value.value.pageIndex = page;
|
||
await currentExecute.value();
|
||
}
|
||
}
|
||
|
||
function prevPage() {
|
||
goToPage(currentPage.value - 1);
|
||
}
|
||
|
||
function nextPage() {
|
||
goToPage(currentPage.value + 1);
|
||
}
|
||
|
||
function getRecordIcon(type: string) {
|
||
if (type === "income" || type === "recharge") {
|
||
return arrowDownCircleOutline;
|
||
}
|
||
return arrowUpCircleOutline;
|
||
}
|
||
|
||
function getRecordColor(type: string) {
|
||
if (type === "income" || type === "recharge") {
|
||
return "text-[#52c41a]";
|
||
}
|
||
return "text-[#f5222d]";
|
||
}
|
||
|
||
function getTypeName(type: string) {
|
||
const names: Record<string, string> = {
|
||
income: "收益",
|
||
investment: "投资",
|
||
withdraw: "提现",
|
||
recharge: "充值",
|
||
};
|
||
return names[type] || type;
|
||
}
|
||
|
||
function formatAmount(amount: string) {
|
||
const num = Number.parseFloat(amount);
|
||
return num >= 0 ? `+${num.toFixed(2)}` : num.toFixed(2);
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<ion-page>
|
||
<ion-header class="ion-no-border">
|
||
<ion-toolbar class="ion-toolbar">
|
||
<ion-buttons slot="start">
|
||
<back-button />
|
||
</ion-buttons>
|
||
<ion-title>资产明细</ion-title>
|
||
</ion-toolbar>
|
||
</ion-header>
|
||
|
||
<ion-content>
|
||
<!-- 资产概览卡片 -->
|
||
<div class="asset-overview">
|
||
<div class="balance-section">
|
||
<div class="balance-item">
|
||
<div class="balance-label">
|
||
<ion-icon :icon="walletOutline" class="label-icon" />
|
||
<span>账户余额</span>
|
||
</div>
|
||
<div class="balance-amount">
|
||
¥{{ Number(balanceWallet?.available)?.toFixed(2) || '0.00' }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 标签切换 -->
|
||
<div class="tabs-container">
|
||
<ion-segment :value="selectedTab" class="tab-segment" @ion-change="handleTabChange">
|
||
<ion-segment-button value="all">
|
||
<ion-label>全部记录</ion-label>
|
||
</ion-segment-button>
|
||
<ion-segment-button value="income">
|
||
<ion-label>收益记录</ion-label>
|
||
</ion-segment-button>
|
||
<ion-segment-button value="investment">
|
||
<ion-label>投资记录</ion-label>
|
||
</ion-segment-button>
|
||
</ion-segment>
|
||
</div>
|
||
|
||
<!-- 记录列表 -->
|
||
<div class="records-list">
|
||
<div v-if="filteredRecords.length === 0" class="empty-state">
|
||
<empty title="暂无记录">
|
||
<template #icon>
|
||
<ion-icon :icon="listOutline" class="empty-icon" />
|
||
</template>
|
||
</empty>
|
||
</div>
|
||
<div v-else class="ion-padding-horizontal">
|
||
<div
|
||
v-for="record in filteredRecords"
|
||
:key="record.id"
|
||
class="record-item"
|
||
>
|
||
<div class="record-left">
|
||
<div class="record-info">
|
||
<div class="record-title">
|
||
{{ record.memo || '无标题' }}
|
||
</div>
|
||
<div class="record-desc">
|
||
{{ record.walletType.name }}
|
||
</div>
|
||
<div class="record-time">
|
||
{{ dayjs(record.createdAt).format('YYYY-MM-DD HH:mm') }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="record-right">
|
||
<div
|
||
class="record-amount"
|
||
>
|
||
{{ formatAmount(record.amount) }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 分页控制 -->
|
||
<div v-if="totalRecords > 0 && totalPages > 1" class="pagination-section">
|
||
<div class="pagination-info">
|
||
共 {{ totalRecords }} 条,第 {{ currentPage }}/{{ totalPages }} 页
|
||
</div>
|
||
<div class="pagination-buttons">
|
||
<ion-button
|
||
fill="outline"
|
||
size="small"
|
||
:disabled="currentPage === 1"
|
||
@click="prevPage"
|
||
>
|
||
上一页
|
||
</ion-button>
|
||
<ion-button
|
||
fill="outline"
|
||
size="small"
|
||
:disabled="currentPage === totalPages"
|
||
@click="nextPage"
|
||
>
|
||
下一页
|
||
</ion-button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</ion-content>
|
||
</ion-page>
|
||
</template>
|
||
|
||
<style lang='css' scoped>
|
||
.asset-overview {
|
||
padding: 24px 20px;
|
||
margin: 0;
|
||
}
|
||
|
||
.balance-section {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.balance-item {
|
||
text-align: center;
|
||
}
|
||
|
||
.balance-label {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 6px;
|
||
font-size: 14px;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.label-icon {
|
||
font-size: 18px;
|
||
}
|
||
|
||
.balance-amount {
|
||
font-size: 36px;
|
||
font-weight: 700;
|
||
letter-spacing: 1px;
|
||
}
|
||
|
||
.stats-section {
|
||
display: flex;
|
||
justify-content: space-around;
|
||
align-items: center;
|
||
padding-top: 16px;
|
||
border-top: 1px solid rgb(223, 223, 223);
|
||
}
|
||
|
||
.stat-item {
|
||
flex: 1;
|
||
text-align: center;
|
||
}
|
||
|
||
.stat-label {
|
||
font-size: 13px;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.stat-value {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.stat-value.income {
|
||
color: #95de64;
|
||
}
|
||
|
||
.stat-value.investment {
|
||
color: #ffccc7;
|
||
}
|
||
|
||
.stat-divider {
|
||
width: 1px;
|
||
height: 30px;
|
||
background: rgb(223, 223, 223);
|
||
}
|
||
|
||
.tabs-container {
|
||
padding: 16px 20px 0;
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 10;
|
||
}
|
||
|
||
.tab-segment {
|
||
margin-bottom: 16px;
|
||
--background: #f5f5f5;
|
||
border-radius: 12px;
|
||
padding: 4px;
|
||
}
|
||
|
||
.tab-segment ion-segment-button {
|
||
--indicator-color: #c41e3a;
|
||
--color: #666;
|
||
--color-checked: white;
|
||
min-height: 36px;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.records-list {
|
||
background: white;
|
||
min-height: 400px;
|
||
padding: 20px 0;
|
||
}
|
||
|
||
.record-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 16px;
|
||
background: #fafafa;
|
||
border-radius: 12px;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.record-left {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
flex: 1;
|
||
}
|
||
|
||
.record-icon {
|
||
font-size: 32px;
|
||
}
|
||
|
||
.record-info {
|
||
flex: 1;
|
||
}
|
||
|
||
.record-title {
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
color: #333;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.record-desc {
|
||
font-size: 13px;
|
||
color: #999;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.record-time {
|
||
font-size: 12px;
|
||
color: #bbb;
|
||
}
|
||
|
||
.record-right {
|
||
text-align: right;
|
||
}
|
||
|
||
.record-amount {
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.record-amount.income {
|
||
color: #52c41a;
|
||
}
|
||
|
||
.record-amount.expense {
|
||
color: #f5222d;
|
||
}
|
||
|
||
.record-type {
|
||
font-size: 12px;
|
||
color: #999;
|
||
}
|
||
|
||
.empty-state {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-height: 300px;
|
||
padding: 40px 20px;
|
||
}
|
||
|
||
.empty-icon {
|
||
font-size: 80px;
|
||
color: #ddd;
|
||
}
|
||
|
||
.pagination-section {
|
||
margin-top: 20px;
|
||
padding-top: 16px;
|
||
border-top: 1px solid #e8e8e8;
|
||
}
|
||
|
||
.pagination-info {
|
||
text-align: center;
|
||
font-size: 13px;
|
||
color: #666;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.pagination-buttons {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 12px;
|
||
}
|
||
|
||
.pagination-buttons ion-button {
|
||
--border-color: #c41e3a;
|
||
--color: #c41e3a;
|
||
--border-radius: 8px;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
min-width: 80px;
|
||
}
|
||
|
||
.pagination-buttons ion-button:not([disabled]):hover {
|
||
--background: rgba(196, 30, 58, 0.05);
|
||
}
|
||
|
||
.pagination-buttons ion-button[disabled] {
|
||
--border-color: #ddd;
|
||
--color: #999;
|
||
opacity: 0.5;
|
||
}
|
||
</style>
|