feat: 添加待确认收益和收益记录页面,更新路由和导航功能

This commit is contained in:
2025-12-27 21:01:12 +07:00
parent 13122c91e1
commit c6c3676a35
5 changed files with 524 additions and 2 deletions

View File

@@ -0,0 +1,201 @@
<script lang='ts' setup>
import type { RefresherCustomEvent } from "@ionic/vue";
const { t } = useI18n();
const { vibrate } = useHaptics();
const loading = ref(true);
// 待确认收益数据
const pendingItems = ref([
{
id: "1",
type: "dividend",
typeName: "分红收益",
assetName: "迈阿密海景别墅",
assetCode: "MIA-004",
amount: 680.20,
expectedDate: "2025-12-28",
status: "pending",
},
{
id: "2",
type: "appreciation",
typeName: "资产增值",
assetName: "波士顿商业中心",
assetCode: "BOS-006",
amount: 1250.50,
expectedDate: "2025-12-29",
status: "pending",
},
{
id: "3",
type: "trade",
typeName: "交易收益",
assetName: "西雅图科技园区",
assetCode: "SEA-007",
amount: 890.30,
expectedDate: "2025-12-30",
status: "processing",
},
{
id: "4",
type: "dividend",
typeName: "分红收益",
assetName: "达拉斯住宅区",
assetCode: "DAL-008",
amount: 520.80,
expectedDate: "2025-12-30",
status: "pending",
},
{
id: "5",
type: "appreciation",
typeName: "资产增值",
assetName: "奥斯汀高端公寓",
assetCode: "AUS-009",
amount: 1580.20,
expectedDate: "2026-01-02",
status: "processing",
},
]);
const totalPending = computed(() =>
pendingItems.value.reduce((sum, item) => sum + item.amount, 0),
);
async function loadData() {
loading.value = true;
useTimeoutFn(() => {
loading.value = false;
}, 800);
}
async function handleRefresh(event: RefresherCustomEvent) {
vibrate();
useTimeoutFn(() => {
event.target.complete();
}, 800);
}
function getStatusColor(status: string) {
return status === "pending" ? "warning" : "medium";
}
function getStatusText(status: string) {
return status === "pending" ? "待确认" : "处理中";
}
onMounted(() => {
loadData();
});
</script>
<template>
<ion-page>
<ion-header class="ion-no-border">
<ion-toolbar class="ui-toolbar">
<ui-back-button slot="start" />
<ion-title>{{ t("asset.revenue.pendingRevenue") }}</ion-title>
</ion-toolbar>
</ion-header>
<ion-content :fullscreen="true" class="ion-padding">
<ion-refresher slot="fixed" @ion-refresh="handleRefresh($event)">
<ion-refresher-content />
</ion-refresher>
<div class="container">
<!-- 待确认总金额卡片 -->
<div class="bg-text-900 rounded-xl p-6 mb-4">
<div class="text-sm text-text-400 mb-2">
待确认总金额
</div>
<div class="flex items-end gap-2">
<div class="text-3xl font-bold">
{{ formatAmountWithSplit(totalPending) }}
</div>
<div class="text-sm text-text-400 mb-1">
USDT
</div>
</div>
<div class="flex items-center gap-2 mt-3 text-xs text-text-400">
<i-ic-baseline-info class="text-base" />
<span>收益将在预计日期后1-3个工作日内到账</span>
</div>
</div>
<!-- 待确认收益列表 -->
<div class="mb-3">
<div class="text-base font-medium mb-3">
待确认明细
</div>
</div>
<ion-list class="rounded-xl overflow-hidden">
<ion-item v-for="item in pendingItems" :key="item.id" lines="full">
<div class="w-full py-4">
<div class="flex justify-between items-start mb-3">
<div class="flex flex-col gap-1.5">
<div class="flex items-center gap-2">
<div class="text-sm font-medium">
{{ item.typeName }}
</div>
<ion-badge
:color="getStatusColor(item.status)"
class="text-xs px-2 py-0.5"
>
{{ getStatusText(item.status) }}
</ion-badge>
</div>
<div class="text-base font-semibold">
{{ item.assetName }}
</div>
<div class="text-xs text-text-400">
{{ item.assetCode }}
</div>
</div>
<div class="text-right">
<div class="text-lg font-bold">
+{{ formatAmountWithSplit(item.amount) }}
</div>
</div>
</div>
<div class="flex items-center gap-1.5 text-xs text-text-400">
<i-ic-outline-access-time class="text-sm" />
<span>预计到账{{ item.expectedDate }}</span>
</div>
</div>
</ion-item>
</ion-list>
<!-- 说明信息 -->
<div class="mt-6 p-4 bg-text-950 rounded-xl">
<div class="flex items-start gap-2">
<!-- <i-ic-baseline-lightbulb-outline class="text-yellow-500 text-lg mt-0.5 flex-shrink-0" /> -->
<div class="text-xs text-text-400 leading-relaxed">
<div class="font-medium mb-2">
待确认收益说明
</div>
<div class="space-y-1">
<div> 分红收益预计在分红日后2-3个工作日到账</div>
<div> 资产增值预计在结算日后1-2个工作日到账</div>
<div> 交易收益预计在交易完成后1个工作日到账</div>
</div>
</div>
</div>
</div>
</div>
</ion-content>
</ion-page>
</template>
<style lang='css' scoped>
.container {
--title-font-size: 16px;
}
ion-list {
background: transparent;
}
</style>

View File

@@ -0,0 +1,287 @@
<script lang='ts' setup>
import type { RefresherCustomEvent } from "@ionic/vue";
import { searchOutline } from "ionicons/icons";
const { t } = useI18n();
const { vibrate } = useHaptics();
const loading = ref(true);
const selectedType = ref("all");
const searchQuery = ref("");
// 收益类型选项
const typeOptions = [
{ value: "all", label: "全部" },
{ value: "dividend", label: "分红收益" },
{ value: "appreciation", label: "资产增值" },
{ value: "trade", label: "交易收益" },
];
// 收益记录数据
const allRecords = ref([
{
id: "1",
type: "dividend",
typeName: "分红收益",
assetName: "纽约曼哈顿中心公寓",
assetCode: "NYC-001",
amount: 520.50,
date: "2025-12-27 10:30",
status: "completed",
},
{
id: "2",
type: "appreciation",
typeName: "资产增值",
assetName: "旧金山商业地产",
assetCode: "SF-002",
amount: 320.80,
date: "2025-12-27 09:15",
status: "completed",
},
{
id: "3",
type: "trade",
typeName: "交易收益",
assetName: "洛杉矶住宅楼",
assetCode: "LA-003",
amount: 215.50,
date: "2025-12-26 16:45",
status: "completed",
},
{
id: "4",
type: "dividend",
typeName: "分红收益",
assetName: "迈阿密海景别墅",
assetCode: "MIA-004",
amount: 680.20,
date: "2025-12-26 14:20",
status: "completed",
},
{
id: "5",
type: "appreciation",
typeName: "资产增值",
assetName: "芝加哥写字楼",
assetCode: "CHI-005",
amount: 450.60,
date: "2025-12-25 11:30",
status: "completed",
},
{
id: "6",
type: "trade",
typeName: "交易收益",
assetName: "波士顿商业中心",
assetCode: "BOS-006",
amount: 890.30,
date: "2025-12-24 15:20",
status: "completed",
},
{
id: "7",
type: "dividend",
typeName: "分红收益",
assetName: "西雅图科技园区",
assetCode: "SEA-007",
amount: 720.40,
date: "2025-12-23 13:10",
status: "completed",
},
{
id: "8",
type: "appreciation",
typeName: "资产增值",
assetName: "达拉斯住宅区",
assetCode: "DAL-008",
amount: 380.90,
date: "2025-12-22 10:50",
status: "completed",
},
{
id: "9",
type: "trade",
typeName: "交易收益",
assetName: "奥斯汀高端公寓",
assetCode: "AUS-009",
amount: 1250.20,
date: "2025-12-21 16:30",
status: "completed",
},
{
id: "10",
type: "dividend",
typeName: "分红收益",
assetName: "休斯顿商业综合体",
assetCode: "HOU-010",
amount: 580.70,
date: "2025-12-20 14:15",
status: "completed",
},
]);
// 筛选后的记录
const filteredRecords = computed(() => {
let records = allRecords.value;
// 按类型筛选
if (selectedType.value !== "all") {
records = records.filter(item => item.type === selectedType.value);
}
// 按搜索关键词筛选
if (searchQuery.value.trim()) {
const query = searchQuery.value.toLowerCase();
records = records.filter(item =>
item.assetName.toLowerCase().includes(query)
|| item.assetCode.toLowerCase().includes(query)
|| item.typeName.toLowerCase().includes(query),
);
}
return records;
});
async function loadData() {
loading.value = true;
useTimeoutFn(() => {
loading.value = false;
}, 800);
}
async function handleRefresh(event: RefresherCustomEvent) {
vibrate();
useTimeoutFn(() => {
event.target.complete();
}, 800);
}
function getTypeColor(type: string) {
const colors: Record<string, string> = {
dividend: "success",
appreciation: "primary",
trade: "warning",
};
return colors[type] || "medium";
}
onMounted(() => {
loadData();
});
</script>
<template>
<ion-page>
<ion-header class="ion-no-border">
<ion-toolbar class="ui-toolbar">
<ui-back-button slot="start" />
<ion-title>{{ t("asset.revenue.revenueDetails") }}</ion-title>
</ion-toolbar>
<ion-searchbar
v-model="searchQuery"
:placeholder="t('myIssues.search')"
/>
</ion-header>
<ion-content :fullscreen="true" class="ion-padding-horizontal">
<ion-refresher slot="fixed" @ion-refresh="handleRefresh($event)">
<ion-refresher-content />
</ion-refresher>
<!-- 类型筛选 -->
<div class="flex gap-2 mb-4 overflow-x-auto pb-2">
<ui-tabs size="small">
<ui-tab-pane
v-for="option in typeOptions"
:key="option.value"
:name="option.value"
:title="option.label"
/>
</ui-tabs>
</div>
<!-- 收益记录列表 -->
<ion-list v-if="filteredRecords.length > 0" class="rounded-xl overflow-hidden">
<ion-item
v-for="item in filteredRecords"
:key="item.id"
lines="full"
class="record-item"
>
<div class="w-full py-3">
<div class="flex justify-between items-start">
<div class="flex-1">
<div class="flex items-center gap-2 mb-2">
<ion-badge
:color="getTypeColor(item.type)"
class="text-xs px-2 py-0.5"
>
{{ item.typeName }}
</ion-badge>
<ion-badge color="success" class="text-xs px-2 py-0.5">
已完成
</ion-badge>
</div>
<div class="text-base font-semibold mb-1">
{{ item.assetName }}
</div>
<div class="text-xs text-text-400 mb-2">
{{ item.assetCode }}
</div>
<div class="flex items-center gap-1.5 text-xs text-text-400">
<i-ic-outline-access-time class="text-sm" />
<span>{{ item.date }}</span>
</div>
</div>
<div class="text-right ml-4">
<div class="text-lg font-bold text-success">
+{{ formatAmountWithSplit(item.amount) }}
</div>
</div>
</div>
</div>
</ion-item>
</ion-list>
<!-- 空状态 -->
<div v-else class="flex flex-col items-center justify-center py-20">
<i-ic-baseline-receipt-long class="text-6xl text-text-600 mb-4" />
<div class="text-text-400">
{{ t("income.records.noData") }}
</div>
</div>
</ion-content>
</ion-page>
</template>
<style lang='css' scoped>
.container {
--title-font-size: 16px;
}
ion-list {
background: transparent;
}
.custom-searchbar {
--background: var(--ion-color-step-100);
--border-radius: 12px;
--box-shadow: none;
padding: 0;
}
ion-chip {
cursor: pointer;
user-select: none;
transition: all 0.2s;
}
ion-chip:active {
opacity: 0.7;
}
.record-item {
--inner-padding-end: 0;
}
</style>

View File

@@ -11,6 +11,14 @@ function navigateToTotal() {
function navigateToMonthly() {
router.push("/revenue/monthly");
}
function navigateToPending() {
router.push("/revenue/pending");
}
function navigateToRecords() {
router.push("/revenue/records");
}
</script>
<template>
@@ -37,13 +45,19 @@ function navigateToMonthly() {
{{ t("asset.revenue.monthlyRevenue") }}
</div>
</div>
<div class="col-span-1 flex flex-col items-center gap-2 cursor-pointer transition-opacity active:opacity-70">
<div
class="col-span-1 flex flex-col items-center gap-2 cursor-pointer transition-opacity active:opacity-70"
@click="navigateToPending"
>
<ion-icon :icon="timeOutline" class="text-2xl text-primary" />
<div class="text-xs">
{{ t("asset.revenue.pendingRevenue") }}
</div>
</div>
<div class="col-span-1 flex flex-col items-center gap-2 cursor-pointer transition-opacity active:opacity-70">
<div
class="col-span-1 flex flex-col items-center gap-2 cursor-pointer transition-opacity active:opacity-70"
@click="navigateToRecords"
>
<ion-icon :icon="listOutline" class="text-2xl text-primary" />
<div class="text-xs">
{{ t("asset.revenue.revenueDetails") }}