feat: 更新capp-eden依赖至0.0.21,优化资产详情页面的记录展示和分页控制逻辑
This commit is contained in:
14
pnpm-lock.yaml
generated
14
pnpm-lock.yaml
generated
@@ -52,8 +52,8 @@ catalogs:
|
|||||||
specifier: 8.0.0
|
specifier: 8.0.0
|
||||||
version: 8.0.0
|
version: 8.0.0
|
||||||
'@capp/eden':
|
'@capp/eden':
|
||||||
specifier: http://192.168.1.2:9538/api/capp-eden-0.0.19.tgz
|
specifier: http://192.168.1.2:9538/api/capp-eden-0.0.21.tgz
|
||||||
version: 0.0.19
|
version: 0.0.21
|
||||||
'@cloudflare/workers-types':
|
'@cloudflare/workers-types':
|
||||||
specifier: ^4.20260113.0
|
specifier: ^4.20260113.0
|
||||||
version: 4.20260116.0
|
version: 4.20260116.0
|
||||||
@@ -298,7 +298,7 @@ importers:
|
|||||||
version: 8.0.0(@capacitor/core@8.0.0)
|
version: 8.0.0(@capacitor/core@8.0.0)
|
||||||
'@capp/eden':
|
'@capp/eden':
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: http://192.168.1.2:9538/api/capp-eden-0.0.19.tgz(@elysiajs/eden@1.4.6(elysia@1.4.22(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3)))
|
version: http://192.168.1.2:9538/api/capp-eden-0.0.21.tgz(@elysiajs/eden@1.4.6(elysia@1.4.22(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3)))
|
||||||
'@elysiajs/eden':
|
'@elysiajs/eden':
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 1.4.6(elysia@1.4.22(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3))
|
version: 1.4.6(elysia@1.4.22(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3))
|
||||||
@@ -1182,9 +1182,9 @@ packages:
|
|||||||
'@capacitor/synapse@1.0.4':
|
'@capacitor/synapse@1.0.4':
|
||||||
resolution: {integrity: sha512-/C1FUo8/OkKuAT4nCIu/34ny9siNHr9qtFezu4kxm6GY1wNFxrCFWjfYx5C1tUhVGz3fxBABegupkpjXvjCHrw==}
|
resolution: {integrity: sha512-/C1FUo8/OkKuAT4nCIu/34ny9siNHr9qtFezu4kxm6GY1wNFxrCFWjfYx5C1tUhVGz3fxBABegupkpjXvjCHrw==}
|
||||||
|
|
||||||
'@capp/eden@http://192.168.1.2:9538/api/capp-eden-0.0.19.tgz':
|
'@capp/eden@http://192.168.1.2:9538/api/capp-eden-0.0.21.tgz':
|
||||||
resolution: {tarball: http://192.168.1.2:9538/api/capp-eden-0.0.19.tgz}
|
resolution: {tarball: http://192.168.1.2:9538/api/capp-eden-0.0.21.tgz}
|
||||||
version: 0.0.19
|
version: 0.0.21
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@elysiajs/eden': ^1.4.6
|
'@elysiajs/eden': ^1.4.6
|
||||||
|
|
||||||
@@ -6903,7 +6903,7 @@ snapshots:
|
|||||||
|
|
||||||
'@capacitor/synapse@1.0.4': {}
|
'@capacitor/synapse@1.0.4': {}
|
||||||
|
|
||||||
'@capp/eden@http://192.168.1.2:9538/api/capp-eden-0.0.19.tgz(@elysiajs/eden@1.4.6(elysia@1.4.22(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3)))':
|
'@capp/eden@http://192.168.1.2:9538/api/capp-eden-0.0.21.tgz(@elysiajs/eden@1.4.6(elysia@1.4.22(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3)))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@elysiajs/eden': 1.4.6(elysia@1.4.22(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3))
|
'@elysiajs/eden': 1.4.6(elysia@1.4.22(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3))
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ catalog:
|
|||||||
'@capacitor/keyboard': 8.0.0
|
'@capacitor/keyboard': 8.0.0
|
||||||
'@capacitor/share': ^8.0.0
|
'@capacitor/share': ^8.0.0
|
||||||
'@capacitor/status-bar': 8.0.0
|
'@capacitor/status-bar': 8.0.0
|
||||||
'@capp/eden': http://192.168.1.2:9538/api/capp-eden-0.0.19.tgz
|
'@capp/eden': http://192.168.1.2:9538/api/capp-eden-0.0.21.tgz
|
||||||
'@cloudflare/workers-types': ^4.20260113.0
|
'@cloudflare/workers-types': ^4.20260113.0
|
||||||
'@elysiajs/eden': ^1.4.6
|
'@elysiajs/eden': ^1.4.6
|
||||||
'@faker-js/faker': ^10.2.0
|
'@faker-js/faker': ^10.2.0
|
||||||
|
|||||||
@@ -1,50 +1,115 @@
|
|||||||
<script lang='ts' setup>
|
<script lang='ts' setup>
|
||||||
|
import dayjs from "dayjs";
|
||||||
import { arrowDownCircleOutline, arrowUpCircleOutline, listOutline, walletOutline } from "ionicons/icons";
|
import { arrowDownCircleOutline, arrowUpCircleOutline, listOutline, walletOutline } from "ionicons/icons";
|
||||||
import { client, safeClient } from "@/api";
|
import { client, safeClient } from "@/api";
|
||||||
|
|
||||||
const walletStore = useWalletStore();
|
const walletStore = useWalletStore();
|
||||||
const balanceWallet = await walletStore.getWalletByType("balance");
|
const balanceWallet = await walletStore.getWalletByType("balance");
|
||||||
const profitWallet = await walletStore.getWalletByType("profit");
|
const profitWallet = await walletStore.getWalletByType("profit");
|
||||||
const { data } = await safeClient(client.api.ledger.entries.get());
|
|
||||||
|
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 } }));
|
||||||
|
const { data: investmentRecords, execute: executeInvestment } = safeClient(() => client.api.ledger.entries.get({ query: { ...investmentQuery.value } }));
|
||||||
|
|
||||||
// 当前选中的标签
|
// 当前选中的标签
|
||||||
const selectedTab = ref<"all" | "income" | "investment">("all");
|
const selectedTab = ref<"all" | "income" | "investment">("all");
|
||||||
|
|
||||||
interface TransactionRecord {
|
// 获取当前标签对应的数据源和执行函数
|
||||||
id: number;
|
const currentData = computed(() => {
|
||||||
type: "income" | "investment" | "withdraw" | "recharge";
|
switch (selectedTab.value) {
|
||||||
title: string;
|
case "all":
|
||||||
amount: number;
|
return allRecords;
|
||||||
time: string;
|
case "income":
|
||||||
status: "success" | "pending" | "failed";
|
return incomeRecords;
|
||||||
description?: string;
|
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) {
|
||||||
const filteredRecords = computed(() => {
|
if (page >= 1 && page <= totalPages.value) {
|
||||||
if (selectedTab.value === "all") {
|
currentQuery.value.value.pageIndex = page;
|
||||||
return allRecords.value;
|
await currentExecute.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");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 统计数据
|
function prevPage() {
|
||||||
const totalIncome = computed(() => {
|
goToPage(currentPage.value - 1);
|
||||||
return allRecords.value
|
}
|
||||||
.filter(r => r.amount > 0)
|
|
||||||
.reduce((sum, r) => sum + r.amount, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
const totalInvestment = computed(() => {
|
function nextPage() {
|
||||||
return Math.abs(allRecords.value
|
goToPage(currentPage.value + 1);
|
||||||
.filter(r => r.amount < 0)
|
}
|
||||||
.reduce((sum, r) => sum + r.amount, 0));
|
|
||||||
});
|
|
||||||
|
|
||||||
function getRecordIcon(type: string) {
|
function getRecordIcon(type: string) {
|
||||||
if (type === "income" || type === "recharge") {
|
if (type === "income" || type === "recharge") {
|
||||||
@@ -70,8 +135,9 @@ function getTypeName(type: string) {
|
|||||||
return names[type] || type;
|
return names[type] || type;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatAmount(amount: number) {
|
function formatAmount(amount: string) {
|
||||||
return amount >= 0 ? `+${amount.toFixed(2)}` : amount.toFixed(2);
|
const num = Number.parseFloat(amount);
|
||||||
|
return num >= 0 ? `+${num.toFixed(2)}` : num.toFixed(2);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -100,58 +166,26 @@ function formatAmount(amount: number) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<!-- 标签切换 -->
|
<!-- 标签切换 -->
|
||||||
<div class="tabs-container">
|
<div class="tabs-container">
|
||||||
<div class="tabs">
|
<ion-segment :value="selectedTab" class="tab-segment" @ion-change="handleTabChange">
|
||||||
<div
|
<ion-segment-button value="all">
|
||||||
class="tab"
|
<ion-label>全部记录</ion-label>
|
||||||
:class="{ active: selectedTab === 'all' }"
|
</ion-segment-button>
|
||||||
@click="selectedTab = 'all'"
|
<ion-segment-button value="income">
|
||||||
>
|
<ion-label>收益记录</ion-label>
|
||||||
全部记录
|
</ion-segment-button>
|
||||||
</div>
|
<ion-segment-button value="investment">
|
||||||
<div
|
<ion-label>投资记录</ion-label>
|
||||||
class="tab"
|
</ion-segment-button>
|
||||||
:class="{ active: selectedTab === 'income' }"
|
</ion-segment>
|
||||||
@click="selectedTab = 'income'"
|
|
||||||
>
|
|
||||||
收益记录
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="tab"
|
|
||||||
:class="{ active: selectedTab === 'investment' }"
|
|
||||||
@click="selectedTab = 'investment'"
|
|
||||||
>
|
|
||||||
投资记录
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 记录列表 -->
|
<!-- 记录列表 -->
|
||||||
<div class="records-list">
|
<div class="records-list">
|
||||||
<div v-if="data?.data.length === 0" class="empty-state">
|
<div v-if="filteredRecords.length === 0" class="empty-state">
|
||||||
<empty title="暂无记录">
|
<empty title="暂无记录">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<ion-icon :icon="listOutline" class="empty-icon" />
|
<ion-icon :icon="listOutline" class="empty-icon" />
|
||||||
@@ -160,41 +194,57 @@ function formatAmount(amount: number) {
|
|||||||
</div>
|
</div>
|
||||||
<div v-else class="ion-padding-horizontal">
|
<div v-else class="ion-padding-horizontal">
|
||||||
<div
|
<div
|
||||||
v-for="record in data?.data"
|
v-for="record in filteredRecords"
|
||||||
:key="record.id"
|
:key="record.id"
|
||||||
class="record-item"
|
class="record-item"
|
||||||
>
|
>
|
||||||
<div class="record-left">
|
<div class="record-left">
|
||||||
<ion-icon
|
|
||||||
:icon="getRecordIcon(record.type)"
|
|
||||||
class="record-icon"
|
|
||||||
:class="getRecordColor(record.type)"
|
|
||||||
/>
|
|
||||||
<div class="record-info">
|
<div class="record-info">
|
||||||
<div class="record-title">
|
<div class="record-title">
|
||||||
{{ record.title }}
|
{{ record.memo || '无标题' }}
|
||||||
</div>
|
</div>
|
||||||
<div class="record-desc">
|
<div class="record-desc">
|
||||||
{{ record.description }}
|
{{ record.walletType.name }}
|
||||||
</div>
|
</div>
|
||||||
<div class="record-time">
|
<div class="record-time">
|
||||||
{{ record.time }}
|
{{ dayjs(record.createdAt).format('YYYY-MM-DD HH:mm') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="record-right">
|
<div class="record-right">
|
||||||
<div
|
<div
|
||||||
class="record-amount"
|
class="record-amount"
|
||||||
:class="record.amount >= 0 ? 'income' : 'expense'"
|
|
||||||
>
|
>
|
||||||
{{ formatAmount(record.amount) }}
|
{{ formatAmount(record.amount) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="record-type">
|
|
||||||
{{ getTypeName(record.type) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
</ion-page>
|
</ion-page>
|
||||||
@@ -277,35 +327,20 @@ function formatAmount(amount: number) {
|
|||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs {
|
.tab-segment {
|
||||||
display: flex;
|
margin-bottom: 16px;
|
||||||
gap: 8px;
|
--background: #f5f5f5;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab {
|
.tab-segment ion-segment-button {
|
||||||
flex: 1;
|
--indicator-color: #c41e3a;
|
||||||
text-align: center;
|
--color: #666;
|
||||||
padding: 12px 16px;
|
--color-checked: white;
|
||||||
font-size: 15px;
|
min-height: 36px;
|
||||||
|
font-size: 14px;
|
||||||
font-weight: 500;
|
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 {
|
.records-list {
|
||||||
@@ -393,4 +428,42 @@ function formatAmount(amount: number) {
|
|||||||
font-size: 80px;
|
font-size: 80px;
|
||||||
color: #ddd;
|
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>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user