Files
financial/src/views/asset_details/index.vue

470 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>