feat: 更新 '@capp/eden' 依赖至 0.0.19,添加转账记录页面及相关逻辑,优化提现功能
This commit is contained in:
BIN
src/assets/images/user-banner.jpg
Normal file
BIN
src/assets/images/user-banner.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 129 KiB |
BIN
src/assets/images/user-header.jpg
Normal file
BIN
src/assets/images/user-header.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
@@ -134,6 +134,11 @@ const routes: Array<RouteRecordRaw> = [
|
||||
component: () => import("@/views/transfer/index.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "/transfer/records",
|
||||
component: () => import("@/views/transfer/records.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "/exchange",
|
||||
component: () => import("@/views/exchange/index.vue"),
|
||||
|
||||
@@ -63,6 +63,9 @@ async function handleLogout() {
|
||||
|
||||
<template>
|
||||
<ion-page>
|
||||
<!-- <ion-header class="ion-no-border">
|
||||
<ion-toolbar />
|
||||
</ion-header> -->
|
||||
<ion-content>
|
||||
<!-- 顶部横幅和用户信息 -->
|
||||
<div class="relative -z-1">
|
||||
@@ -191,6 +194,9 @@ async function handleLogout() {
|
||||
</template>
|
||||
|
||||
<style lang='css' scoped>
|
||||
/* ion-toolbar {
|
||||
--background: url("@/assets/images/user-header.jpg");
|
||||
} */
|
||||
.card {
|
||||
background: linear-gradient(180deg, #ffeef1, #ffffff 15%);
|
||||
}
|
||||
|
||||
@@ -197,7 +197,7 @@ async function handleSubmit() {
|
||||
}
|
||||
|
||||
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) {
|
||||
formData.value.documentName = data.value.documentName || "";
|
||||
formData.value.documentNumber = data.value.documentNumber || "";
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { toastController } from "@ionic/vue";
|
||||
import { eyeOffOutline, eyeOutline, keyOutline } from "ionicons/icons";
|
||||
import zod from "zod";
|
||||
import { client, safeClient } from "@/api";
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
@@ -60,14 +61,10 @@ async function handleSubmit() {
|
||||
|
||||
isSubmitting.value = true;
|
||||
try {
|
||||
// TODO: 调用更改交易密码 API
|
||||
// const { data } = await safeClient(client.api.user.payment_password.put({
|
||||
// currentPassword: formData.value.currentPassword,
|
||||
// newPassword: formData.value.newPassword,
|
||||
// }));
|
||||
|
||||
// 模拟 API 调用
|
||||
await new Promise(resolve => setTimeout(resolve, 1500));
|
||||
await safeClient(client.api.user.security["transaction-password"].put({
|
||||
newPassword: formData.value.newPassword,
|
||||
oldPassword: formData.value.currentPassword,
|
||||
}));
|
||||
|
||||
await showToast("交易密码修改成功", "success");
|
||||
router.back();
|
||||
|
||||
@@ -153,8 +153,7 @@ async function handleSubmit() {
|
||||
}
|
||||
|
||||
function goToRecords() {
|
||||
// router.push("/transfer/records");
|
||||
showToast("功能开发中", "warning");
|
||||
router.push("/transfer/records");
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
344
src/views/transfer/records.vue
Normal file
344
src/views/transfer/records.vue
Normal 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>
|
||||
@@ -10,7 +10,7 @@ type Receipt = Treaty.Data<typeof client.api.receipt_method.get>["data"][number]
|
||||
const router = useRouter();
|
||||
const walletStore = useWalletStore();
|
||||
const { wallets } = storeToRefs(walletStore);
|
||||
const filterWallets = computed(() => wallets.value.filter(w => w.walletType.allowWithdraw === true));
|
||||
const filterWallets = ref<Wallet[]>([]);
|
||||
const withdrawAmount = ref("");
|
||||
const quickAmounts = [100, 500, 1000, 2000];
|
||||
const selectedWallet = ref<Wallet | null>(null);
|
||||
@@ -20,8 +20,9 @@ const { data: bankAccounts } = await safeClient(client.api.receipt_method.get())
|
||||
|
||||
onMounted(async () => {
|
||||
await walletStore.syncWallets();
|
||||
if (wallets.value.length > 0) {
|
||||
selectedWallet.value = wallets.value[0];
|
||||
filterWallets.value = wallets.value.filter(w => w.walletType.allowWithdraw === true);
|
||||
if (filterWallets.value.length > 0) {
|
||||
selectedWallet.value = filterWallets.value[0];
|
||||
}
|
||||
});
|
||||
|
||||
@@ -121,12 +122,12 @@ async function handleSubmit() {
|
||||
{
|
||||
text: "确认提现",
|
||||
handler: async () => {
|
||||
// const { error } = await safeClient(client.api.withdraw.post({
|
||||
// walletId: selectedWallet.value.id,
|
||||
// amount,
|
||||
// bankAccountId: selectedBankAccount.value.id,
|
||||
// transactionPassword: transactionPassword.value,
|
||||
// }));
|
||||
await safeClient(client.api.withdraw.post({
|
||||
amount: amount.toString(),
|
||||
walletTypeId: selectedWallet.value!.walletType.id,
|
||||
receiptMethodId: selectedBankAccount.value!.id,
|
||||
transactionPassword: transactionPassword.value,
|
||||
}));
|
||||
|
||||
await showToast("提现申请已提交,请等待审核");
|
||||
withdrawAmount.value = "";
|
||||
|
||||
Reference in New Issue
Block a user