feat: 添加资产明细页面,支持记录筛选和统计功能,集成钱包状态管理
This commit is contained in:
@@ -94,6 +94,11 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
component: () => import("@/views/security/change_payment_password.vue"),
|
component: () => import("@/views/security/change_payment_password.vue"),
|
||||||
meta: { requiresAuth: true },
|
meta: { requiresAuth: true },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/asset_details",
|
||||||
|
component: () => import("@/views/asset_details/index.vue"),
|
||||||
|
meta: { requiresAuth: true },
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
|
|||||||
464
src/views/asset_details/index.vue
Normal file
464
src/views/asset_details/index.vue
Normal file
@@ -0,0 +1,464 @@
|
|||||||
|
<script lang='ts' setup>
|
||||||
|
import { arrowDownCircleOutline, arrowUpCircleOutline, listOutline, walletOutline } from "ionicons/icons";
|
||||||
|
|
||||||
|
const walletStore = useWalletStore();
|
||||||
|
const { balanceWallet } = storeToRefs(walletStore);
|
||||||
|
|
||||||
|
// 当前选中的标签
|
||||||
|
const selectedTab = ref<"all" | "income" | "investment">("all");
|
||||||
|
|
||||||
|
interface TransactionRecord {
|
||||||
|
id: number;
|
||||||
|
type: "income" | "investment" | "withdraw" | "recharge";
|
||||||
|
title: string;
|
||||||
|
amount: number;
|
||||||
|
time: string;
|
||||||
|
status: "success" | "pending" | "failed";
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟数据
|
||||||
|
const allRecords = ref<TransactionRecord[]>([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
type: "income",
|
||||||
|
title: "投资收益",
|
||||||
|
amount: 1250.50,
|
||||||
|
time: "2026-01-18 14:30:20",
|
||||||
|
status: "success",
|
||||||
|
description: "稳健增长基金收益",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
type: "investment",
|
||||||
|
title: "投资支出",
|
||||||
|
amount: -5000.00,
|
||||||
|
time: "2026-01-17 10:15:30",
|
||||||
|
status: "success",
|
||||||
|
description: "购买稳健增长基金",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
type: "withdraw",
|
||||||
|
title: "提现",
|
||||||
|
amount: -1000.00,
|
||||||
|
time: "2026-01-16 16:45:10",
|
||||||
|
status: "success",
|
||||||
|
description: "提现至银行卡",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
type: "recharge",
|
||||||
|
title: "充值",
|
||||||
|
amount: 3000.00,
|
||||||
|
time: "2026-01-15 09:20:00",
|
||||||
|
status: "success",
|
||||||
|
description: "银行卡充值",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
type: "income",
|
||||||
|
title: "投资收益",
|
||||||
|
amount: 890.30,
|
||||||
|
time: "2026-01-14 14:30:20",
|
||||||
|
status: "success",
|
||||||
|
description: "价值投资基金收益",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
type: "investment",
|
||||||
|
title: "投资支出",
|
||||||
|
amount: -3000.00,
|
||||||
|
time: "2026-01-13 11:20:45",
|
||||||
|
status: "success",
|
||||||
|
description: "购买均衡配置基金",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
type: "income",
|
||||||
|
title: "投资收益",
|
||||||
|
amount: 456.80,
|
||||||
|
time: "2026-01-12 14:30:20",
|
||||||
|
status: "success",
|
||||||
|
description: "高收益债券收益",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 筛选后的记录
|
||||||
|
const filteredRecords = computed(() => {
|
||||||
|
if (selectedTab.value === "all") {
|
||||||
|
return allRecords.value;
|
||||||
|
}
|
||||||
|
else if (selectedTab.value === "income") {
|
||||||
|
return allRecords.value.filter(r => r.type === "income" || r.type === "recharge");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return allRecords.value.filter(r => r.type === "investment" || r.type === "withdraw");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 统计数据
|
||||||
|
const totalIncome = computed(() => {
|
||||||
|
return allRecords.value
|
||||||
|
.filter(r => r.amount > 0)
|
||||||
|
.reduce((sum, r) => sum + r.amount, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
const totalInvestment = computed(() => {
|
||||||
|
return Math.abs(allRecords.value
|
||||||
|
.filter(r => r.amount < 0)
|
||||||
|
.reduce((sum, r) => sum + r.amount, 0));
|
||||||
|
});
|
||||||
|
|
||||||
|
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: number) {
|
||||||
|
return amount >= 0 ? `+${amount.toFixed(2)}` : amount.toFixed(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
walletStore.syncBalanceWallet();
|
||||||
|
</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 class="stats-section">
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-label">
|
||||||
|
累计收益
|
||||||
|
</div>
|
||||||
|
<div class="stat-value income">
|
||||||
|
+¥{{ totalIncome.toFixed(2) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-divider" />
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-label">
|
||||||
|
累计投资
|
||||||
|
</div>
|
||||||
|
<div class="stat-value investment">
|
||||||
|
-¥{{ totalInvestment.toFixed(2) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 标签切换 -->
|
||||||
|
<div class="tabs-container">
|
||||||
|
<div class="tabs">
|
||||||
|
<div
|
||||||
|
class="tab"
|
||||||
|
:class="{ active: selectedTab === 'all' }"
|
||||||
|
@click="selectedTab = 'all'"
|
||||||
|
>
|
||||||
|
全部记录
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="tab"
|
||||||
|
:class="{ active: selectedTab === 'income' }"
|
||||||
|
@click="selectedTab = 'income'"
|
||||||
|
>
|
||||||
|
收益记录
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="tab"
|
||||||
|
:class="{ active: selectedTab === 'investment' }"
|
||||||
|
@click="selectedTab = 'investment'"
|
||||||
|
>
|
||||||
|
投资记录
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 记录列表 -->
|
||||||
|
<div class="records-list">
|
||||||
|
<div v-if="filteredRecords.length > 0" class="ion-padding-horizontal">
|
||||||
|
<div
|
||||||
|
v-for="record in filteredRecords"
|
||||||
|
:key="record.id"
|
||||||
|
class="record-item"
|
||||||
|
>
|
||||||
|
<div class="record-left">
|
||||||
|
<ion-icon
|
||||||
|
:icon="getRecordIcon(record.type)"
|
||||||
|
class="record-icon"
|
||||||
|
:class="getRecordColor(record.type)"
|
||||||
|
/>
|
||||||
|
<div class="record-info">
|
||||||
|
<div class="record-title">
|
||||||
|
{{ record.title }}
|
||||||
|
</div>
|
||||||
|
<div class="record-desc">
|
||||||
|
{{ record.description }}
|
||||||
|
</div>
|
||||||
|
<div class="record-time">
|
||||||
|
{{ record.time }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="record-right">
|
||||||
|
<div
|
||||||
|
class="record-amount"
|
||||||
|
:class="record.amount >= 0 ? 'income' : 'expense'"
|
||||||
|
>
|
||||||
|
{{ formatAmount(record.amount) }}
|
||||||
|
</div>
|
||||||
|
<div class="record-type">
|
||||||
|
{{ getTypeName(record.type) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<div v-else class="empty-state">
|
||||||
|
<empty title="暂无记录">
|
||||||
|
<template #icon>
|
||||||
|
<ion-icon :icon="listOutline" class="empty-icon" />
|
||||||
|
</template>
|
||||||
|
</empty>
|
||||||
|
</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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
padding: 12px 16px;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #666;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab.active {
|
||||||
|
color: #c41e3a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab.active::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
bottom: -2px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 2px;
|
||||||
|
background: #c41e3a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.records-list {
|
||||||
|
background: white;
|
||||||
|
min-height: 400px;
|
||||||
|
padding-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user