feat: 更新 '@capp/eden' 依赖至 0.0.19,添加转账记录页面及相关逻辑,优化提现功能

This commit is contained in:
2026-01-19 03:19:33 +07:00
parent 8f7fa41cb8
commit a884a90283
12 changed files with 384 additions and 28 deletions

4
components.d.ts vendored
View File

@@ -31,6 +31,8 @@ declare module 'vue' {
IonLabel: typeof import('@ionic/vue')['IonLabel'] IonLabel: typeof import('@ionic/vue')['IonLabel']
IonList: typeof import('@ionic/vue')['IonList'] IonList: typeof import('@ionic/vue')['IonList']
IonPage: typeof import('@ionic/vue')['IonPage'] IonPage: typeof import('@ionic/vue')['IonPage']
IonRefresher: typeof import('@ionic/vue')['IonRefresher']
IonRefresherContent: typeof import('@ionic/vue')['IonRefresherContent']
IonRouterOutlet: typeof import('@ionic/vue')['IonRouterOutlet'] IonRouterOutlet: typeof import('@ionic/vue')['IonRouterOutlet']
IonSegment: typeof import('@ionic/vue')['IonSegment'] IonSegment: typeof import('@ionic/vue')['IonSegment']
IonSegmentButton: typeof import('@ionic/vue')['IonSegmentButton'] IonSegmentButton: typeof import('@ionic/vue')['IonSegmentButton']
@@ -71,6 +73,8 @@ declare global {
const IonLabel: typeof import('@ionic/vue')['IonLabel'] const IonLabel: typeof import('@ionic/vue')['IonLabel']
const IonList: typeof import('@ionic/vue')['IonList'] const IonList: typeof import('@ionic/vue')['IonList']
const IonPage: typeof import('@ionic/vue')['IonPage'] const IonPage: typeof import('@ionic/vue')['IonPage']
const IonRefresher: typeof import('@ionic/vue')['IonRefresher']
const IonRefresherContent: typeof import('@ionic/vue')['IonRefresherContent']
const IonRouterOutlet: typeof import('@ionic/vue')['IonRouterOutlet'] const IonRouterOutlet: typeof import('@ionic/vue')['IonRouterOutlet']
const IonSegment: typeof import('@ionic/vue')['IonSegment'] const IonSegment: typeof import('@ionic/vue')['IonSegment']
const IonSegmentButton: typeof import('@ionic/vue')['IonSegmentButton'] const IonSegmentButton: typeof import('@ionic/vue')['IonSegmentButton']

14
pnpm-lock.yaml generated
View File

@@ -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.15.tgz specifier: http://192.168.1.2:9538/api/capp-eden-0.0.19.tgz
version: 0.0.15 version: 0.0.19
'@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.15.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.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)))
'@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.15.tgz': '@capp/eden@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.15.tgz} resolution: {tarball: http://192.168.1.2:9538/api/capp-eden-0.0.19.tgz}
version: 0.0.15 version: 0.0.19
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.15.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.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)))':
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))

View File

@@ -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.15.tgz '@capp/eden': http://192.168.1.2:9538/api/capp-eden-0.0.19.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -134,6 +134,11 @@ const routes: Array<RouteRecordRaw> = [
component: () => import("@/views/transfer/index.vue"), component: () => import("@/views/transfer/index.vue"),
meta: { requiresAuth: true }, meta: { requiresAuth: true },
}, },
{
path: "/transfer/records",
component: () => import("@/views/transfer/records.vue"),
meta: { requiresAuth: true },
},
{ {
path: "/exchange", path: "/exchange",
component: () => import("@/views/exchange/index.vue"), component: () => import("@/views/exchange/index.vue"),

View File

@@ -63,6 +63,9 @@ async function handleLogout() {
<template> <template>
<ion-page> <ion-page>
<!-- <ion-header class="ion-no-border">
<ion-toolbar />
</ion-header> -->
<ion-content> <ion-content>
<!-- 顶部横幅和用户信息 --> <!-- 顶部横幅和用户信息 -->
<div class="relative -z-1"> <div class="relative -z-1">
@@ -191,6 +194,9 @@ async function handleLogout() {
</template> </template>
<style lang='css' scoped> <style lang='css' scoped>
/* ion-toolbar {
--background: url("@/assets/images/user-header.jpg");
} */
.card { .card {
background: linear-gradient(180deg, #ffeef1, #ffffff 15%); background: linear-gradient(180deg, #ffeef1, #ffffff 15%);
} }

View File

@@ -197,7 +197,7 @@ async function handleSubmit() {
} }
onMounted(async () => { onMounted(async () => {
const { data } = await safeClient(client.api.kyc({ kycMethod: "id_card" }).get()); const { data } = await safeClient(client.api.kyc({ kycMethod: "id_card" }).get(), { silent: true });
if (data.value) { if (data.value) {
formData.value.documentName = data.value.documentName || ""; formData.value.documentName = data.value.documentName || "";
formData.value.documentNumber = data.value.documentNumber || ""; formData.value.documentNumber = data.value.documentNumber || "";

View File

@@ -2,6 +2,7 @@
import { toastController } from "@ionic/vue"; import { toastController } from "@ionic/vue";
import { eyeOffOutline, eyeOutline, keyOutline } from "ionicons/icons"; import { eyeOffOutline, eyeOutline, keyOutline } from "ionicons/icons";
import zod from "zod"; import zod from "zod";
import { client, safeClient } from "@/api";
const router = useRouter(); const router = useRouter();
@@ -60,14 +61,10 @@ async function handleSubmit() {
isSubmitting.value = true; isSubmitting.value = true;
try { try {
// TODO: 调用更改交易密码 API await safeClient(client.api.user.security["transaction-password"].put({
// const { data } = await safeClient(client.api.user.payment_password.put({ newPassword: formData.value.newPassword,
// currentPassword: formData.value.currentPassword, oldPassword: formData.value.currentPassword,
// newPassword: formData.value.newPassword, }));
// }));
// 模拟 API 调用
await new Promise(resolve => setTimeout(resolve, 1500));
await showToast("交易密码修改成功", "success"); await showToast("交易密码修改成功", "success");
router.back(); router.back();

View File

@@ -153,8 +153,7 @@ async function handleSubmit() {
} }
function goToRecords() { function goToRecords() {
// router.push("/transfer/records"); router.push("/transfer/records");
showToast("功能开发中", "warning");
} }
</script> </script>

View File

@@ -0,0 +1,344 @@
<script lang='ts' setup>
import type { Treaty } from "@elysiajs/eden";
import type { RefresherCustomEvent } from "@ionic/vue";
import { arrowDownOutline, arrowUpOutline, checkmarkCircleOutline, closeCircleOutline, timeOutline } from "ionicons/icons";
import { client, safeClient } from "@/api";
type TransferRecord = Treaty.Data<typeof client.api.transfer.get>["data"][number];
const router = useRouter();
const records = ref<TransferRecord[]>([]);
const loading = ref(false);
const hasMore = ref(true);
const page = ref(1);
const pageSize = 20;
onMounted(async () => {
await loadRecords();
});
// 加载记录
async function loadRecords(isRefresh = false) {
if (loading.value)
return;
if (isRefresh) {
page.value = 1;
hasMore.value = true;
records.value = [];
}
loading.value = true;
try {
const { data } = await safeClient(client.api.transfer.get({
query: {
pageIndex: page.value,
pageSize,
},
}));
if (isRefresh) {
records.value = data.value?.data || [];
}
else {
records.value.push(...(data.value?.data || []));
}
hasMore.value = data.value?.pagination.hasNextPage || false;
page.value++;
}
catch (error) {
console.error("加载转账记录失败:", error);
}
finally {
loading.value = false;
}
}
// 加载更多
async function loadMore(event: any) {
if (!hasMore.value) {
event.target.complete();
return;
}
await loadRecords();
event.target.complete();
}
// 下拉刷新
async function handleRefresh(event: RefresherCustomEvent) {
await loadRecords(true);
event.target.complete();
}
// 格式化金额
function formatAmount(amount: string) {
return Number(amount).toLocaleString("zh-CN", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
}
// 格式化时间
function formatTime(time: Date | string) {
const date = new Date(time);
const now = new Date();
const diff = now.getTime() - date.getTime();
// 今天
if (diff < 24 * 60 * 60 * 1000 && now.getDate() === date.getDate()) {
return `今天 ${date.toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" })}`;
}
// 昨天
const yesterday = new Date(now);
yesterday.setDate(yesterday.getDate() - 1);
if (yesterday.getDate() === date.getDate()) {
return `昨天 ${date.toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" })}`;
}
// 其他
return date.toLocaleString("zh-CN", {
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
});
}
// 查看详情
function viewDetail(record: TransferRecord) {
// router.push(`/transfer/detail/${record.id}`);
console.log("查看详情:", record);
}
</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>
<ion-refresher slot="fixed" @ion-refresh="handleRefresh">
<ion-refresher-content />
</ion-refresher>
<!-- 记录列表 -->
<div v-if="records.length > 0" class="records-list">
<div
v-for="record in records"
:key="record.id"
class="record-item"
@click="viewDetail(record)"
>
<div class="record-info">
<div class="record-desc">
<span>{{ record.sourceWalletTypeId }}</span>
<span class="time">{{ formatTime(record.createdAt) }}</span>
</div>
<div v-if="record.orderNo" class="record-remark">
订单号{{ record.orderNo }}
</div>
</div>
<!-- 右侧金额 -->
<div class="record-amount">
¥{{ formatAmount(record.amount) }}
</div>
</div>
</div>
<!-- 空状态 -->
<div v-else-if="!loading" class="empty-state">
<empty title="暂无转账记录" />
</div>
<!-- 加载更多 -->
<ion-infinite-scroll
v-if="hasMore"
threshold="100px"
@ion-infinite="loadMore"
>
<ion-infinite-scroll-content loading-text="加载中..." />
</ion-infinite-scroll>
<!-- 底部提示 -->
<div v-if="records.length > 0 && !hasMore" class="load-end">
- 没有更多了 -
</div>
</ion-content>
</ion-page>
</template>
<style lang='css' scoped>
.filter-tabs {
display: flex;
gap: 12px;
padding: 16px 20px;
background: white;
border-bottom: 1px solid #f0f0f0;
}
.filter-tab {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
padding: 8px 12px;
background: #f5f5f5;
border: 2px solid transparent;
border-radius: 20px;
font-size: 14px;
font-weight: 600;
color: #666;
cursor: pointer;
transition: all 0.3s ease;
}
.filter-tab.active {
background: #fff5f5;
border-color: var(--ion-color-primary);
color: var(--ion-color-primary);
}
.filter-tab:active {
transform: scale(0.98);
}
.tab-icon {
font-size: 16px;
}
.records-list {
padding: 12px 0;
}
.record-item {
display: flex;
align-items: center;
gap: 12px;
padding: 16px 20px;
background: white;
border-bottom: 1px solid #f5f5f5;
cursor: pointer;
transition: background 0.2s ease;
}
.record-item:active {
background: #fafafa;
}
.record-icon {
width: 44px;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
font-size: 20px;
color: white;
}
.record-icon.transfer_out {
background: linear-gradient(135deg, #ff7875 0%, #ff4d4f 100%);
}
.record-icon.transfer_in {
background: linear-gradient(135deg, #95de64 0%, #52c41a 100%);
}
.record-info {
flex: 1;
min-width: 0;
}
.record-title {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 6px;
}
.title-text {
font-size: 15px;
font-weight: 600;
color: #333;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.status-badge {
display: flex;
align-items: center;
gap: 4px;
font-size: 12px;
font-weight: 600;
white-space: nowrap;
}
.status-icon {
font-size: 14px;
}
.record-desc {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
font-size: 13px;
color: #999;
}
.time {
font-size: 12px;
white-space: nowrap;
}
.record-remark {
margin-top: 6px;
padding: 6px 10px;
background: #f9f9f9;
border-radius: 6px;
font-size: 12px;
color: #666;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.record-amount {
font-size: 18px;
font-weight: 700;
white-space: nowrap;
}
.record-amount.transfer_out {
color: #ff4d4f;
}
.record-amount.transfer_in {
color: #52c41a;
}
.empty-state {
padding: 100px 20px;
}
.load-end {
padding: 20px;
text-align: center;
font-size: 12px;
color: #999;
}
</style>

View File

@@ -10,7 +10,7 @@ type Receipt = Treaty.Data<typeof client.api.receipt_method.get>["data"][number]
const router = useRouter(); const router = useRouter();
const walletStore = useWalletStore(); const walletStore = useWalletStore();
const { wallets } = storeToRefs(walletStore); const { wallets } = storeToRefs(walletStore);
const filterWallets = computed(() => wallets.value.filter(w => w.walletType.allowWithdraw === true)); const filterWallets = ref<Wallet[]>([]);
const withdrawAmount = ref(""); const withdrawAmount = ref("");
const quickAmounts = [100, 500, 1000, 2000]; const quickAmounts = [100, 500, 1000, 2000];
const selectedWallet = ref<Wallet | null>(null); const selectedWallet = ref<Wallet | null>(null);
@@ -20,8 +20,9 @@ const { data: bankAccounts } = await safeClient(client.api.receipt_method.get())
onMounted(async () => { onMounted(async () => {
await walletStore.syncWallets(); await walletStore.syncWallets();
if (wallets.value.length > 0) { filterWallets.value = wallets.value.filter(w => w.walletType.allowWithdraw === true);
selectedWallet.value = wallets.value[0]; if (filterWallets.value.length > 0) {
selectedWallet.value = filterWallets.value[0];
} }
}); });
@@ -121,12 +122,12 @@ async function handleSubmit() {
{ {
text: "确认提现", text: "确认提现",
handler: async () => { handler: async () => {
// const { error } = await safeClient(client.api.withdraw.post({ await safeClient(client.api.withdraw.post({
// walletId: selectedWallet.value.id, amount: amount.toString(),
// amount, walletTypeId: selectedWallet.value!.walletType.id,
// bankAccountId: selectedBankAccount.value.id, receiptMethodId: selectedBankAccount.value!.id,
// transactionPassword: transactionPassword.value, transactionPassword: transactionPassword.value,
// })); }));
await showToast("提现申请已提交,请等待审核"); await showToast("提现申请已提交,请等待审核");
withdrawAmount.value = ""; withdrawAmount.value = "";