feat: internationalize various components and views with i18n translations

This commit is contained in:
2026-01-14 04:24:44 +07:00
parent 0308c56555
commit 8b70c6a3e9
42 changed files with 251 additions and 210 deletions

View File

@@ -33,7 +33,7 @@ function validate(value: string) {
async function onSubmit() { async function onSubmit() {
const loading = await loadingController.create({ const loading = await loadingController.create({
message: "提交中...", message: t("recharge.fiat.submitting"),
}); });
await loading.present(); await loading.present();
await safeClient(client.api.deposit.fiat.post(form.value)).finally(async () => { await safeClient(client.api.deposit.fiat.post(form.value)).finally(async () => {

View File

@@ -7,6 +7,7 @@ import html2canvas from "html2canvas";
import MaterialSymbolsDownload from "~icons/material-symbols/download"; import MaterialSymbolsDownload from "~icons/material-symbols/download";
import MaterialSymbolsUpload from "~icons/material-symbols/upload"; import MaterialSymbolsUpload from "~icons/material-symbols/upload";
const { t } = useI18n();
const userStore = useUserStore(); const userStore = useUserStore();
const { userProfile } = storeToRefs(userStore); const { userProfile } = storeToRefs(userStore);
const url = computed(() => { const url = computed(() => {
@@ -63,7 +64,7 @@ async function captureQRCode(): Promise<string> {
async function handleShare() { async function handleShare() {
const loading = await loadingController.create({ const loading = await loadingController.create({
message: "准备分享...", message: t("onchainAddress.preparing"),
}); });
await loading.present(); await loading.present();
@@ -78,8 +79,8 @@ async function handleShare() {
const file = new File([blob], "qrcode.png", { type: "image/png" }); const file = new File([blob], "qrcode.png", { type: "image/png" });
await share({ await share({
title: "我的转账二维码", title: t("onchainAddress.shareTitle"),
text: `我的 ID: ${userProfile.value?.uid}`, text: `${t("onchainAddress.myId")}: ${userProfile.value?.uid}`,
files: [file], files: [file],
}); });
} }
@@ -87,7 +88,7 @@ async function handleShare() {
// 不支持则提示复制链接 // 不支持则提示复制链接
await navigator.clipboard.writeText(url.value); await navigator.clipboard.writeText(url.value);
const toast = await toastController.create({ const toast = await toastController.create({
message: "链接已复制到剪贴板", message: t("onchainAddress.linkCopied"),
duration: 2000, duration: 2000,
color: "success", color: "success",
}); });
@@ -107,17 +108,17 @@ async function handleShare() {
}); });
await Share.share({ await Share.share({
title: "我的转账二维码", title: t("onchainAddress.shareTitle"),
text: `我的 ID: ${userProfile.value?.uid}`, text: `${t("onchainAddress.myId")}: ${userProfile.value?.uid}`,
url: result.uri, url: result.uri,
dialogTitle: "分享二维码", dialogTitle: t("onchainAddress.shareDialogTitle"),
}); });
} }
} }
catch (error) { catch (error) {
console.error("Share error:", error); console.error("Share error:", error);
const toast = await toastController.create({ const toast = await toastController.create({
message: "分享失败", message: t("onchainAddress.shareFailed"),
duration: 2000, duration: 2000,
color: "danger", color: "danger",
}); });
@@ -133,7 +134,7 @@ async function handleShare() {
*/ */
async function handleDownload() { async function handleDownload() {
const loading = await loadingController.create({ const loading = await loadingController.create({
message: "正在保存...", message: t("onchainAddress.saving"),
}); });
await loading.present(); await loading.present();
@@ -159,7 +160,7 @@ async function handleDownload() {
}); });
const toast = await toastController.create({ const toast = await toastController.create({
message: "二维码已保存到文件", message: t("onchainAddress.saveSuccess"),
duration: 2000, duration: 2000,
color: "success", color: "success",
}); });
@@ -169,7 +170,7 @@ async function handleDownload() {
catch (error) { catch (error) {
console.error("Download error:", error); console.error("Download error:", error);
const toast = await toastController.create({ const toast = await toastController.create({
message: "保存失败", message: t("onchainAddress.saveFailed"),
duration: 2000, duration: 2000,
color: "danger", color: "danger",
}); });
@@ -188,7 +189,7 @@ async function handleDownload() {
<ion-buttons slot="start"> <ion-buttons slot="start">
<ui-back-button /> <ui-back-button />
</ion-buttons> </ion-buttons>
<ion-title>转账二维码</ion-title> <ion-title>{{ t("onchainAddress.title") }}</ion-title>
</IonToolbar> </IonToolbar>
</IonHeader> </IonHeader>
<IonContent :fullscreen="true" class="ion-padding"> <IonContent :fullscreen="true" class="ion-padding">
@@ -210,7 +211,7 @@ async function handleDownload() {
<div class="mt-4"> <div class="mt-4">
<ion-text color="secondary"> <ion-text color="secondary">
<div class="text-sm text-text-400 font-semibold"> <div class="text-sm text-text-400 font-semibold">
我的 ID {{ t("onchainAddress.myId") }}
</div> </div>
</ion-text> </ion-text>
<ion-text> <ion-text>

View File

@@ -6,6 +6,7 @@ import IconParkOutlineDownload from "~icons/icon-park-outline/download";
import IconParkOutlinePhone from "~icons/icon-park-outline/phone"; import IconParkOutlinePhone from "~icons/icon-park-outline/phone";
import IconParkOutlineShare from "~icons/icon-park-outline/share"; import IconParkOutlineShare from "~icons/icon-park-outline/share";
const { t } = useI18n();
const { canInstall, isInstalled, install, isIOS, isIOSSafari } = usePWAInstall(); const { canInstall, isInstalled, install, isIOS, isIOSSafari } = usePWAInstall();
const platform = usePlatform(); const platform = usePlatform();
@@ -39,9 +40,9 @@ const shouldShowDownloadButton = computed(() => {
async function handleInstall() { async function handleInstall() {
if (isIOSSafari) { if (isIOSSafari) {
const alert = await alertController.create({ const alert = await alertController.create({
header: "iOS 安装指引", header: t("pwa.download.iosInstallHeader"),
message: "请点击浏览器底部的分享按钮,然后选择\"添加到主屏幕\"", message: t("pwa.download.iosInstallMessage"),
buttons: ["知道了"], buttons: [t("pwa.download.iosInstallButton")],
}); });
await alert.present(); await alert.present();
return; return;
@@ -55,17 +56,17 @@ async function handleInstall() {
localStorage.setItem("pwa-was-installed", "true"); localStorage.setItem("pwa-was-installed", "true");
const alert = await alertController.create({ const alert = await alertController.create({
header: "安装成功", header: t("pwa.download.installSuccessHeader"),
message: "应用已成功安装到您的设备", message: t("pwa.download.installSuccessMessage"),
buttons: ["确定"], buttons: [t("pwa.download.installSuccessButton")],
}); });
await alert.present(); await alert.present();
} }
else if (result.outcome === "ios-instruction") { else if (result.outcome === "ios-instruction") {
const alert = await alertController.create({ const alert = await alertController.create({
header: "iOS 安装指引", header: t("pwa.download.iosInstallHeader"),
message: "请点击浏览器底部的分享按钮,然后选择\"添加到主屏幕\"", message: t("pwa.download.iosInstallMessage"),
buttons: ["知道了"], buttons: [t("pwa.download.iosInstallButton")],
}); });
await alert.present(); await alert.present();
} }
@@ -90,7 +91,7 @@ watch(isInstalled, (installed) => {
<ion-page> <ion-page>
<ion-header class="ion-no-border"> <ion-header class="ion-no-border">
<ion-toolbar class="ion-toolbar"> <ion-toolbar class="ion-toolbar">
<ion-title>下载应用</ion-title> <ion-title>{{ t("pwa.download.title") }}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
@@ -103,7 +104,7 @@ watch(isInstalled, (installed) => {
{{ appName }} {{ appName }}
</h1> </h1>
<p class="text-text-500 text-center text-sm"> <p class="text-text-500 text-center text-sm">
随时随地管理您的数字资产 {{ t("pwa.download.subtitle") }}
</p> </p>
</div> </div>
@@ -112,10 +113,10 @@ watch(isInstalled, (installed) => {
<div class="bg-success/10 border border-success/20 rounded-2xl p-6 text-center"> <div class="bg-success/10 border border-success/20 rounded-2xl p-6 text-center">
<IconParkOutlineCheckOne class="text-5xl text-success mx-auto mb-4" /> <IconParkOutlineCheckOne class="text-5xl text-success mx-auto mb-4" />
<h2 class="text-lg font-semibold text-success mb-2"> <h2 class="text-lg font-semibold text-success mb-2">
应用已安装 {{ t("pwa.download.installed") }}
</h2> </h2>
<p class="text-sm text-text-500"> <p class="text-sm text-text-500">
您可以在主屏幕找到应用图标 {{ t("pwa.download.installedDesc") }}
</p> </p>
</div> </div>
</div> </div>
@@ -126,7 +127,7 @@ watch(isInstalled, (installed) => {
<div class="flex items-center mb-4"> <div class="flex items-center mb-4">
<IconParkOutlinePhone class="text-2xl text-primary mr-3" /> <IconParkOutlinePhone class="text-2xl text-primary mr-3" />
<h2 class="text-lg font-semibold"> <h2 class="text-lg font-semibold">
iOS 安装步骤 {{ t("pwa.download.iosInstallTitle") }}
</h2> </h2>
</div> </div>
@@ -138,9 +139,9 @@ watch(isInstalled, (installed) => {
</div> </div>
<div class="flex-1"> <div class="flex-1">
<p class="text-sm"> <p class="text-sm">
点击 Safari 底部的 {{ t("pwa.download.iosStep1") }}
<IconParkOutlineShare class="inline-block text-primary mx-1" /> <IconParkOutlineShare class="inline-block text-primary mx-1" />
<strong>分享</strong> 按钮 <strong>{{ t("pwa.download.iosStep1Button") }}</strong> 按钮
</p> </p>
</div> </div>
</div> </div>
@@ -152,8 +153,8 @@ watch(isInstalled, (installed) => {
</div> </div>
<div class="flex-1"> <div class="flex-1">
<p class="text-sm"> <p class="text-sm">
在弹出的菜单中向下滚动找到 {{ t("pwa.download.iosStep2") }}
<strong>"添加到主屏幕"</strong> <strong>"{{ t("pwa.download.iosStep2Option") }}"</strong>
</p> </p>
</div> </div>
</div> </div>
@@ -165,7 +166,7 @@ watch(isInstalled, (installed) => {
</div> </div>
<div class="flex-1"> <div class="flex-1">
<p class="text-sm"> <p class="text-sm">
点击 <strong>"添加"</strong> 完成安装 {{ t("pwa.download.iosStep3") }} <strong>"{{ t("pwa.download.iosStep3Button") }}"</strong> {{ t("pwa.download.iosStep3Complete") }}
</p> </p>
</div> </div>
</div> </div>
@@ -176,7 +177,7 @@ watch(isInstalled, (installed) => {
<div class="bg-warning/10 border border-warning/20 rounded-xl p-4 text-sm text-text-600"> <div class="bg-warning/10 border border-warning/20 rounded-xl p-4 text-sm text-text-600">
<p class="flex-center"> <p class="flex-center">
<span class="mr-2">💡</span> <span class="mr-2">💡</span>
<span>请使用 Safari 浏览器进行安装</span> <span>{{ t("pwa.download.iosTip") }}</span>
</p> </p>
</div> </div>
</div> </div>
@@ -186,10 +187,10 @@ watch(isInstalled, (installed) => {
<div class="bg-background-secondary rounded-2xl p-6 mb-6 text-center"> <div class="bg-background-secondary rounded-2xl p-6 mb-6 text-center">
<IconParkOutlineDownload class="text-5xl text-primary mx-auto mb-4" /> <IconParkOutlineDownload class="text-5xl text-primary mx-auto mb-4" />
<h2 class="text-xl font-semibold mb-2"> <h2 class="text-xl font-semibold mb-2">
{{ wasUninstalled ? '重新安装应用' : '安装到设备' }} {{ wasUninstalled ? t("pwa.download.reinstallButton") : t("pwa.download.installButton") }}
</h2> </h2>
<p class="text-sm text-text-500 mb-6"> <p class="text-sm text-text-500 mb-6">
{{ wasUninstalled ? '快速重新安装应用到您的设备' : '一键安装,无需下载,即刻使用' }} {{ wasUninstalled ? t("pwa.download.reinstallDesc") : t("pwa.download.installDesc") }}
</p> </p>
<ion-button <ion-button
@@ -201,7 +202,7 @@ watch(isInstalled, (installed) => {
> >
<IconParkOutlineDownload v-if="!isLoading" class="mr-2" /> <IconParkOutlineDownload v-if="!isLoading" class="mr-2" />
<ion-spinner v-if="isLoading" name="crescent" class="mr-2" /> <ion-spinner v-if="isLoading" name="crescent" class="mr-2" />
{{ isLoading ? '安装中...' : wasUninstalled ? '重新安装' : '立即安装' }} {{ isLoading ? t("pwa.download.installing") : wasUninstalled ? t("pwa.download.reinstall") : t("pwa.download.install") }}
</ion-button> </ion-button>
</div> </div>
@@ -211,19 +212,19 @@ watch(isInstalled, (installed) => {
<div class="w-8 h-8 rounded-full bg-success/10 flex items-center justify-center mr-3"> <div class="w-8 h-8 rounded-full bg-success/10 flex items-center justify-center mr-3">
<IconParkOutlineCheckOne class="text-success" /> <IconParkOutlineCheckOne class="text-success" />
</div> </div>
<span class="text-text-600">无需应用商店快速安装</span> <span class="text-text-600">{{ t("pwa.download.advantage1") }}</span>
</div> </div>
<div class="flex items-center text-sm"> <div class="flex items-center text-sm">
<div class="w-8 h-8 rounded-full bg-success/10 flex items-center justify-center mr-3"> <div class="w-8 h-8 rounded-full bg-success/10 flex items-center justify-center mr-3">
<IconParkOutlineCheckOne class="text-success" /> <IconParkOutlineCheckOne class="text-success" />
</div> </div>
<span class="text-text-600">占用空间小运行流畅</span> <span class="text-text-600">{{ t("pwa.download.advantage2") }}</span>
</div> </div>
<div class="flex items-center text-sm"> <div class="flex items-center text-sm">
<div class="w-8 h-8 rounded-full bg-success/10 flex items-center justify-center mr-3"> <div class="w-8 h-8 rounded-full bg-success/10 flex items-center justify-center mr-3">
<IconParkOutlineCheckOne class="text-success" /> <IconParkOutlineCheckOne class="text-success" />
</div> </div>
<span class="text-text-600">自动更新始终最新版本</span> <span class="text-text-600">{{ t("pwa.download.advantage3") }}</span>
</div> </div>
</div> </div>
</div> </div>
@@ -233,10 +234,10 @@ watch(isInstalled, (installed) => {
<div class="bg-background-secondary rounded-2xl p-6 text-center"> <div class="bg-background-secondary rounded-2xl p-6 text-center">
<IconParkOutlineCheckOne class="text-5xl text-success mx-auto mb-4" /> <IconParkOutlineCheckOne class="text-5xl text-success mx-auto mb-4" />
<h2 class="text-lg font-semibold mb-2"> <h2 class="text-lg font-semibold mb-2">
您正在使用原生应用 {{ t("pwa.download.nativeAppTitle") }}
</h2> </h2>
<p class="text-sm text-text-500"> <p class="text-sm text-text-500">
已经是最新版本无需下载 {{ t("pwa.download.nativeAppDesc") }}
</p> </p>
</div> </div>
</div> </div>
@@ -245,10 +246,10 @@ watch(isInstalled, (installed) => {
<div v-else class="w-full max-w-md"> <div v-else class="w-full max-w-md">
<div class="bg-warning/10 border border-warning/20 rounded-2xl p-6 text-center"> <div class="bg-warning/10 border border-warning/20 rounded-2xl p-6 text-center">
<p class="text-sm text-text-600"> <p class="text-sm text-text-600">
当前浏览器暂不支持应用安装 {{ t("pwa.download.notSupportedTitle") }}
</p> </p>
<p class="text-xs text-text-500 mt-2"> <p class="text-xs text-text-500 mt-2">
建议使用 ChromeSafari Edge 浏览器 {{ t("pwa.download.notSupportedDesc") }}
</p> </p>
</div> </div>
</div> </div>

View File

@@ -4,19 +4,21 @@ import type { EaringsSummaryData } from "@/api/types";
defineProps<{ defineProps<{
data: EaringsSummaryData | null; data: EaringsSummaryData | null;
}>(); }>();
const { t } = useI18n();
</script> </script>
<template> <template>
<div class="bg-text-900 rounded-xl p-7"> <div class="bg-text-900 rounded-xl p-7">
<div> <div>
<div class="text-sm mb-2"> <div class="text-sm mb-2">
本月总收益 {{ t("revenue.monthly.monthTotalRevenue") }}
</div> </div>
<div class="text-4xl font-bold"> <div class="text-4xl font-bold">
{{ formatAmountWithSplit(data?.realized.thisMonth) }} {{ formatAmountWithSplit(data?.realized.thisMonth) }}
</div> </div>
<div class="flex items-center gap-2 text-xs mt-1"> <div class="flex items-center gap-2 text-xs mt-1">
<div>昨日收益</div> <div>{{ t("revenue.monthly.yesterdayRevenue") }}</div>
<div>+{{ formatAmountWithSplit(data?.realized.yesterday) }}</div> <div>+{{ formatAmountWithSplit(data?.realized.yesterday) }}</div>
</div> </div>
</div> </div>
@@ -27,7 +29,7 @@ defineProps<{
{{ formatAmountWithSplit(data?.realized.lastMonth) }} {{ formatAmountWithSplit(data?.realized.lastMonth) }}
</div> </div>
<div class="text-xs"> <div class="text-xs">
上月收益 {{ t("revenue.monthly.lastMonthRevenue") }}
</div> </div>
</div> </div>
<div class="h-auto w-px bg-text-800" /> <div class="h-auto w-px bg-text-800" />
@@ -36,7 +38,7 @@ defineProps<{
{{ formatAmountWithSplit(data?.unrealized.total) }} {{ formatAmountWithSplit(data?.unrealized.total) }}
</div> </div>
<div class="text-xs"> <div class="text-xs">
累计总收益 {{ t("revenue.monthly.totalRevenue") }}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,6 +1,8 @@
<script lang='ts' setup> <script lang='ts' setup>
import { client, safeClient } from "@/api"; import { client, safeClient } from "@/api";
const { t } = useI18n();
const { data } = await safeClient(client.api.earnings.details.post({ const { data } = await safeClient(client.api.earnings.details.post({
limit: 6, limit: 6,
offset: 0, offset: 0,
@@ -10,9 +12,9 @@ const { data } = await safeClient(client.api.earnings.details.post({
<template> <template>
<ion-list> <ion-list>
<ion-list-header> <ion-list-header>
<h4>本月记录</h4> <h4>{{ t("revenue.monthly.monthRecords") }}</h4>
<ion-button v-show="data?.data.length" class="text-sm"> <ion-button v-show="data?.data.length" class="text-sm">
查看全部 {{ t("revenue.monthly.viewAll") }}
</ion-button> </ion-button>
</ion-list-header> </ion-list-header>
<ui-empty v-if="!data || data.data.length === 0" class="mt-5" /> <ui-empty v-if="!data || data.data.length === 0" class="mt-5" />

View File

@@ -2,6 +2,8 @@
import dayjs from "dayjs"; import dayjs from "dayjs";
import { client, safeClient } from "@/api"; import { client, safeClient } from "@/api";
const { t } = useI18n();
const now = dayjs(); const now = dayjs();
const { data } = safeClient(client.api.earnings.type_summary.post({ const { data } = safeClient(client.api.earnings.type_summary.post({
@@ -12,7 +14,7 @@ const { data } = safeClient(client.api.earnings.type_summary.post({
<template> <template>
<div> <div>
<h4>本月收益来源</h4> <h4>{{ t("revenue.monthly.monthSource") }}</h4>
<ui-empty v-if="data?.data.length === 0" /> <ui-empty v-if="data?.data.length === 0" />
<template v-else> <template v-else>
<div v-for="item in data?.data" :key="item.type" class="card"> <div v-for="item in data?.data" :key="item.type" class="card">
@@ -31,7 +33,7 @@ const { data } = safeClient(client.api.earnings.type_summary.post({
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div class="w-2 h-2 " /> <div class="w-2 h-2 " />
<div class="text-xs"> <div class="text-xs">
{{ item.count }} {{ item.count }}{{ t("revenue.monthly.itemsCount") }}
</div> </div>
</div> </div>
<div class="text-xs text-text-200"> <div class="text-xs text-text-200">

View File

@@ -2,6 +2,8 @@
import dayjs from "dayjs"; import dayjs from "dayjs";
import { client, safeClient } from "@/api"; import { client, safeClient } from "@/api";
const { t } = useI18n();
const tradingViewInst = useTemplateRef<HTMLDivElement>("tradingViewInst"); const tradingViewInst = useTemplateRef<HTMLDivElement>("tradingViewInst");
const now = dayjs(); const now = dayjs();
@@ -31,7 +33,7 @@ useTradingView(tradingViewInst, {
<template> <template>
<div> <div>
<h4>本月收益趋势</h4> <h4>{{ t("revenue.monthly.monthTrend") }}</h4>
<ui-empty v-if="!data || data.data.length === 0" class="mt-5" /> <ui-empty v-if="!data || data.data.length === 0" class="mt-5" />
<div v-else ref="tradingViewInst" /> <div v-else ref="tradingViewInst" />
</div> </div>

View File

@@ -19,7 +19,7 @@ function getStatusColor(status?: string) {
} }
function getStatusText(status?: string) { function getStatusText(status?: string) {
return status === "pending" ? "待确认" : "处理中"; return status === "pending" ? t("revenue.pending.statusPending") : t("revenue.pending.statusProcessing");
} }
</script> </script>
@@ -41,7 +41,7 @@ function getStatusText(status?: string) {
<!-- 待确认总金额卡片 --> <!-- 待确认总金额卡片 -->
<div class="bg-text-900 rounded-xl p-6 mb-4"> <div class="bg-text-900 rounded-xl p-6 mb-4">
<div class="text-sm text-text-400 mb-2"> <div class="text-sm text-text-400 mb-2">
待确认总金额 {{ t("revenue.pending.totalPending") }}
</div> </div>
<div class="flex items-end gap-2"> <div class="flex items-end gap-2">
<div class="text-3xl font-bold"> <div class="text-3xl font-bold">
@@ -53,14 +53,14 @@ function getStatusText(status?: string) {
</div> </div>
<div class="flex items-center gap-2 mt-3 text-xs text-text-400"> <div class="flex items-center gap-2 mt-3 text-xs text-text-400">
<i-ic-baseline-info class="text-base" /> <i-ic-baseline-info class="text-base" />
<span>收益将在预计日期后1-3个工作日内到账</span> <span>{{ t("revenue.pending.accountTip") }}</span>
</div> </div>
</div> </div>
<!-- 待确认收益列表 --> <!-- 待确认收益列表 -->
<div class="mb-3"> <div class="mb-3">
<div class="text-base font-medium mb-3"> <div class="text-base font-medium mb-3">
待确认明细 {{ t("revenue.pending.detailTitle") }}
</div> </div>
</div> </div>
@@ -97,7 +97,7 @@ function getStatusText(status?: string) {
</div> </div>
<div class="flex items-center gap-1.5 text-xs text-text-400"> <div class="flex items-center gap-1.5 text-xs text-text-400">
<i-ic-outline-access-time class="text-sm" /> <i-ic-outline-access-time class="text-sm" />
<span>预计到账{{ useDateFormat(item.expectedAt, 'YY/MM/DD') }}</span> <span>{{ t("revenue.pending.expectedDate") }}{{ useDateFormat(item.expectedAt, 'YY/MM/DD') }}</span>
</div> </div>
</div> </div>
</ion-item> </ion-item>
@@ -110,12 +110,12 @@ function getStatusText(status?: string) {
<!-- <i-ic-baseline-lightbulb-outline class="text-yellow-500 text-lg mt-0.5 flex-shrink-0" /> --> <!-- <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="text-xs text-text-400 leading-relaxed">
<div class="font-medium mb-2"> <div class="font-medium mb-2">
待确认收益说明 {{ t("revenue.pending.noteTitle") }}
</div> </div>
<div class="space-y-1"> <div class="space-y-1">
<div> 分红收益预计在分红日后2-3个工作日到账</div> <div>{{ t("revenue.pending.dividendNote") }}</div>
<div> 资产增值预计在结算日后1-2个工作日到账</div> <div>{{ t("revenue.pending.appreciationNote") }}</div>
<div> 交易收益预计在交易完成后1个工作日到账</div> <div>{{ t("revenue.pending.tradeNote") }}</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -120,7 +120,7 @@ onBeforeMount(() => {
{{ item.type }} {{ item.type }}
</ion-badge> </ion-badge>
<ion-badge color="success" class="text-xs px-2 py-0.5"> <ion-badge color="success" class="text-xs px-2 py-0.5">
已完成 {{ t("revenue.records.completed") }}
</ion-badge> </ion-badge>
</div> </div>
<div class="text-base font-semibold mb-1"> <div class="text-base font-semibold mb-1">
@@ -147,7 +147,7 @@ onBeforeMount(() => {
<ion-infinite-scroll threshold="100px" @ion-infinite="handleInfinite"> <ion-infinite-scroll threshold="100px" @ion-infinite="handleInfinite">
<ion-infinite-scroll-content <ion-infinite-scroll-content
loading-spinner="bubbles" loading-spinner="bubbles"
loading-text="加载中..." :loading-text="t('revenue.records.loading')"
/> />
</ion-infinite-scroll> </ion-infinite-scroll>
</ion-content> </ion-content>

View File

@@ -4,19 +4,21 @@ import type { EaringsSummaryData } from "@/api/types";
defineProps<{ defineProps<{
data: EaringsSummaryData | null; data: EaringsSummaryData | null;
}>(); }>();
const { t } = useI18n();
</script> </script>
<template> <template>
<div class="bg-text-900 rounded-xl p-7"> <div class="bg-text-900 rounded-xl p-7">
<div> <div>
<div class="text-sm mb-2"> <div class="text-sm mb-2">
累计总收益 {{ t("revenue.total.totalRevenue") }}
</div> </div>
<div class="text-4xl font-bold"> <div class="text-4xl font-bold">
{{ formatAmountWithSplit(data?.realized.total) }} {{ formatAmountWithSplit(data?.realized.total) }}
</div> </div>
<div class="flex items-center gap-2 text-xs mt-1"> <div class="flex items-center gap-2 text-xs mt-1">
<div>昨日收益</div> <div>{{ t("revenue.total.yesterdayRevenue") }}</div>
<div>+{{ formatAmountWithSplit(data?.realized.yesterday) }}</div> <div>+{{ formatAmountWithSplit(data?.realized.yesterday) }}</div>
</div> </div>
</div> </div>
@@ -27,7 +29,7 @@ defineProps<{
{{ formatAmountWithSplit(data?.realized.thisMonth) }} {{ formatAmountWithSplit(data?.realized.thisMonth) }}
</div> </div>
<div class="text-xs"> <div class="text-xs">
本月收益 {{ t("revenue.total.monthRevenue") }}
</div> </div>
</div> </div>
<div class="h-auto w-px bg-text-800" /> <div class="h-auto w-px bg-text-800" />
@@ -36,7 +38,7 @@ defineProps<{
{{ formatAmountWithSplit(data?.unrealized.total) }} {{ formatAmountWithSplit(data?.unrealized.total) }}
</div> </div>
<div class="text-xs"> <div class="text-xs">
待确认收益 {{ t("revenue.total.pendingRevenue") }}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,6 +1,8 @@
<script lang='ts' setup> <script lang='ts' setup>
import { client, safeClient } from "@/api"; import { client, safeClient } from "@/api";
const { t } = useI18n();
const { data } = await safeClient(client.api.earnings.details.post({ const { data } = await safeClient(client.api.earnings.details.post({
limit: 6, limit: 6,
offset: 0, offset: 0,
@@ -10,9 +12,9 @@ const { data } = await safeClient(client.api.earnings.details.post({
<template> <template>
<ion-list> <ion-list>
<ion-list-header> <ion-list-header>
<h4>最近记录</h4> <h4>{{ t("revenue.total.recentRecords") }}</h4>
<ion-button v-show="data?.data.length" class="text-sm"> <ion-button v-show="data?.data.length" class="text-sm">
查看全部 {{ t("revenue.total.viewAll") }}
</ion-button> </ion-button>
</ion-list-header> </ion-list-header>
<ui-empty v-if="!data || data.data.length === 0" class="mt-5" /> <ui-empty v-if="!data || data.data.length === 0" class="mt-5" />

View File

@@ -1,12 +1,14 @@
<script lang='ts' setup> <script lang='ts' setup>
import { client, safeClient } from "@/api"; import { client, safeClient } from "@/api";
const { t } = useI18n();
const { data } = safeClient(client.api.earnings.type_summary.post()); const { data } = safeClient(client.api.earnings.type_summary.post());
</script> </script>
<template> <template>
<div> <div>
<h4>收益来源</h4> <h4>{{ t("revenue.total.revenueSource") }}</h4>
<ui-empty v-if="data?.data.length === 0" /> <ui-empty v-if="data?.data.length === 0" />
<template v-else> <template v-else>
<div v-for="item in data?.data" :key="item.type" class="card"> <div v-for="item in data?.data" :key="item.type" class="card">
@@ -25,7 +27,7 @@ const { data } = safeClient(client.api.earnings.type_summary.post());
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div class="w-2 h-2 " /> <div class="w-2 h-2 " />
<div class="text-xs"> <div class="text-xs">
{{ item.count }} {{ item.count }}{{ t("revenue.total.itemsCount") }}
</div> </div>
</div> </div>
<div class="text-xs text-text-200"> <div class="text-xs text-text-200">

View File

@@ -2,6 +2,8 @@
import dayjs from "dayjs"; import dayjs from "dayjs";
import { client, safeClient } from "@/api"; import { client, safeClient } from "@/api";
const { t } = useI18n();
const tradingViewInst = useTemplateRef<HTMLDivElement>("tradingViewInst"); const tradingViewInst = useTemplateRef<HTMLDivElement>("tradingViewInst");
const now = dayjs(); const now = dayjs();
@@ -31,7 +33,7 @@ useTradingView(tradingViewInst, {
<template> <template>
<div> <div>
<h4>收益趋势</h4> <h4>{{ t("revenue.total.revenueTrend") }}</h4>
<ui-empty v-if="!data || data.data.length === 0" class="mt-5" /> <ui-empty v-if="!data || data.data.length === 0" class="mt-5" />
<div v-else ref="tradingViewInst" /> <div v-else ref="tradingViewInst" />
</div> </div>

View File

@@ -1,12 +1,13 @@
<script lang='ts' setup> <script lang='ts' setup>
import { toastController } from "@ionic/vue"; import { toastController } from "@ionic/vue";
const { t } = useI18n();
const router = useRouter(); const router = useRouter();
const scanner = useTemplateRef<InstanceType<typeof QrScanner>>("scanner"); const scanner = useTemplateRef<InstanceType<typeof QrScanner>>("scanner");
async function handleSuccess(value: string) { async function handleSuccess(value: string) {
const toast = await toastController.create({ const toast = await toastController.create({
message: "扫描成功", message: t("scanQr.scanSuccess"),
duration: 1000, duration: 1000,
position: "bottom", position: "bottom",
color: "success", color: "success",
@@ -21,7 +22,7 @@ async function handleSuccess(value: string) {
function handleError(error: Error) { function handleError(error: Error) {
toastController.create({ toastController.create({
message: `扫描失败: ${error.message}`, message: `${t("scanQr.scanFailed")}${error.message}`,
duration: 2000, duration: 2000,
position: "bottom", position: "bottom",
color: "danger", color: "danger",

View File

@@ -40,7 +40,7 @@ async function handleSubmit(values: GenericObject) {
router.back(); router.back();
} }
catch (error) { catch (error) {
console.error("添加银行卡失败:", error); console.error(t("tradeSettings.bankManagement.addError"), error);
} }
} }
function handleBankChange(event: any) { function handleBankChange(event: any) {

View File

@@ -70,15 +70,15 @@ async function handleSetDefault(card: any) {
// 删除银行卡 // 删除银行卡
async function handleDeleteCard(card: any) { async function handleDeleteCard(card: any) {
const alert = await alertController.create({ const alert = await alertController.create({
header: "删除银行卡", header: t("tradeSettings.bankManagement.deleteTitle"),
message: `确定要删除 ${card.bankName} (${card.accountName}) 吗?此操作无法撤销。`, message: `${t("common.confirmDelete")} ${card.bankName} (${card.accountName}) ${t("tradeSettings.bankManagement.deleteMessage")}`,
buttons: [ buttons: [
{ {
text: "取消", text: t("tradeSettings.bankManagement.cancel"),
role: "cancel", role: "cancel",
}, },
{ {
text: "删除", text: t("tradeSettings.bankManagement.delete"),
role: "destructive", role: "destructive",
handler: async () => { handler: async () => {
await safeClient(() => client.api.bank_account({ id: card.id }).delete()); await safeClient(() => client.api.bank_account({ id: card.id }).delete());

View File

@@ -4,6 +4,8 @@ import type { MyIssueRwaData } from "@/api/types";
const props = defineProps<{ const props = defineProps<{
data: MyIssueRwaData | null; data: MyIssueRwaData | null;
}>(); }>();
const { t } = useI18n();
</script> </script>
<template> <template>
@@ -11,7 +13,7 @@ const props = defineProps<{
<!-- document --> <!-- document -->
<div class="mt-5"> <div class="mt-5">
<div class="font-semibold"> <div class="font-semibold">
相关文档 {{ t('tradeSettings.myIssues.relatedDocs') }}
</div> </div>
<div class="text-xs mt-2"> <div class="text-xs mt-2">
{{ data?.proofDocumentIds }} {{ data?.proofDocumentIds }}

View File

@@ -41,7 +41,7 @@ const { t } = useI18n();
<!-- Rwa status --> <!-- Rwa status -->
<div class="mt-5"> <div class="mt-5">
<div class="font-semibold"> <div class="font-semibold">
资产状态 {{ t('tradeSettings.myIssues.assetStatus') }}
</div> </div>
<div class="text-xs mt-2"> <div class="text-xs mt-2">
{{ data?.status }} {{ data?.status }}
@@ -51,7 +51,7 @@ const { t } = useI18n();
<!-- Rwa status history --> <!-- Rwa status history -->
<div class="mt-5"> <div class="mt-5">
<div class="font-semibold"> <div class="font-semibold">
状态历史 {{ t('tradeSettings.myIssues.statusHistory') }}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -64,7 +64,7 @@ function gotoEdit() {
<div class="flex items-center my-3" @click="gotoEdit"> <div class="flex items-center my-3" @click="gotoEdit">
<IcSharpEditNote class="inline-block text-xl mr-1 text-text-500" /> <IcSharpEditNote class="inline-block text-xl mr-1 text-text-500" />
<div class="text-xs"> <div class="text-xs">
编辑资产 {{ t('tradeSettings.myIssues.editAsset') }}
</div> </div>
</div> </div>
</div> </div>
@@ -73,7 +73,8 @@ function gotoEdit() {
<ui-tab-pane name="overview" :title="t('market.tradeRwa.tabs.overview')"> <ui-tab-pane name="overview" :title="t('market.tradeRwa.tabs.overview')">
<RwaBase :data="data" /> <RwaBase :data="data" />
</ui-tab-pane> </ui-tab-pane>
<ui-tab-pane name="about" title="相关文档"> <ui-tab-pane name="about" :title="t('tradeSettings.myIssues.relatedDocs')">
,
<RwaAbout :data="data" /> <RwaAbout :data="data" />
</ui-tab-pane> </ui-tab-pane>
</ui-tabs> </ui-tabs>

View File

@@ -19,21 +19,21 @@ function gotoMySubscribe(id: string) {
<ion-grid> <ion-grid>
<ion-row class="ion-align-items-center text-xs text-text-500"> <ion-row class="ion-align-items-center text-xs text-text-500">
<ion-col size="6"> <ion-col size="6">
<div>名称/编号</div> <div>{{ t('tradeSettings.mySubscribe.nameOrNumber') }}</div>
</ion-col> </ion-col>
<ion-col> <ion-col>
<div class="text-right"> <div class="text-right">
单价 {{ t('tradeSettings.mySubscribe.unitPrice') }}
</div> </div>
</ion-col> </ion-col>
<ion-col size="2"> <ion-col size="2">
<div class="text-right"> <div class="text-right">
状态 {{ t('tradeSettings.mySubscribe.status') }}
</div> </div>
</ion-col> </ion-col>
<ion-col size="3"> <ion-col size="3">
<div class="text-right"> <div class="text-right">
总金额 {{ t('tradeSettings.mySubscribe.totalAmount') }}
</div> </div>
</ion-col> </ion-col>
</ion-row> </ion-row>

View File

@@ -41,7 +41,7 @@ const { data } = safeClient(client.api.rwa.subscription({ orderId: props.id }).g
<!-- 申购状态 --> <!-- 申购状态 -->
<div class="bg-faint rounded-2xl p-4"> <div class="bg-faint rounded-2xl p-4">
<div class="text-sm text-text-500 mb-2"> <div class="text-sm text-text-500 mb-2">
申购状态 {{ t('tradeSettings.mySubscribe.subscribeStatus') }}
</div> </div>
<div class="text-lg font-semibold"> <div class="text-lg font-semibold">
{{ t(`trade.subscribeStatus.${data?.status}`) }} {{ t(`trade.subscribeStatus.${data?.status}`) }}
@@ -51,17 +51,17 @@ const { data } = safeClient(client.api.rwa.subscription({ orderId: props.id }).g
<!-- 申购信息详情 --> <!-- 申购信息详情 -->
<div class="bg-faint rounded-2xl p-4 space-y-4"> <div class="bg-faint rounded-2xl p-4 space-y-4">
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<span class="text-sm text-text-500">申购数量</span> <span class="text-sm text-text-500">{{ t('tradeSettings.mySubscribe.subscribeQuantity') }}</span>
<span class="text-base font-semibold">{{ data?.quantity }}</span> <span class="text-base font-semibold">{{ data?.quantity }}</span>
</div> </div>
<ui-divider /> <ui-divider />
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<span class="text-sm text-text-500">单价</span> <span class="text-sm text-text-500">{{ t('tradeSettings.mySubscribe.unitPrice') }}</span>
<span class="text-base font-semibold">${{ Number(data?.unitPrice).toFixed(2) }}</span> <span class="text-base font-semibold">${{ Number(data?.unitPrice).toFixed(2) }}</span>
</div> </div>
<ui-divider /> <ui-divider />
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<span class="text-sm text-text-500">总金额</span> <span class="text-sm text-text-500">{{ t('tradeSettings.mySubscribe.totalAmount') }}</span>
<span class="text-lg font-bold text-primary">${{ Number(data?.totalAmount).toFixed(2) }}</span> <span class="text-lg font-bold text-primary">${{ Number(data?.totalAmount).toFixed(2) }}</span>
</div> </div>
</div> </div>
@@ -69,20 +69,20 @@ const { data } = safeClient(client.api.rwa.subscription({ orderId: props.id }).g
<!-- 产品信息 --> <!-- 产品信息 -->
<div class="bg-faint rounded-2xl p-4 space-y-4"> <div class="bg-faint rounded-2xl p-4 space-y-4">
<div class="text-base font-semibold mb-3"> <div class="text-base font-semibold mb-3">
产品信息 {{ t('tradeSettings.mySubscribe.productInfo') }}
</div> </div>
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<span class="text-sm text-text-500">产品类别</span> <span class="text-sm text-text-500">{{ t('tradeSettings.mySubscribe.productCategory') }}</span>
<span class="text-sm">{{ data?.edition.product.categoryId }}</span> <span class="text-sm">{{ data?.edition.product.categoryId }}</span>
</div> </div>
<ui-divider /> <ui-divider />
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<span class="text-sm text-text-500">产品编码</span> <span class="text-sm text-text-500">{{ t('tradeSettings.mySubscribe.productCode') }}</span>
<span class="text-sm">{{ data?.edition.product.code }}</span> <span class="text-sm">{{ data?.edition.product.code }}</span>
</div> </div>
<ui-divider /> <ui-divider />
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<span class="text-sm text-text-500">产品名称</span> <span class="text-sm text-text-500">{{ t('tradeSettings.mySubscribe.productName') }}</span>
<span class="text-sm">{{ data?.edition.product.name }}</span> <span class="text-sm">{{ data?.edition.product.name }}</span>
</div> </div>
</div> </div>
@@ -90,10 +90,10 @@ const { data } = safeClient(client.api.rwa.subscription({ orderId: props.id }).g
<!-- 发行期信息 --> <!-- 发行期信息 -->
<div v-if="data?.edition" class="bg-faint rounded-2xl p-4 space-y-4"> <div v-if="data?.edition" class="bg-faint rounded-2xl p-4 space-y-4">
<div class="text-base font-semibold mb-3"> <div class="text-base font-semibold mb-3">
发行期信息 {{ t('tradeSettings.mySubscribe.periodInfo') }}
</div> </div>
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<span class="text-sm text-text-500">发行期编号</span> <span class="text-sm text-text-500">{{ t('tradeSettings.mySubscribe.periodNumber') }}</span>
<span class="text-sm">{{ data?.edition.id }}</span> <span class="text-sm">{{ data?.edition.id }}</span>
</div> </div>
</div> </div>
@@ -101,15 +101,15 @@ const { data } = safeClient(client.api.rwa.subscription({ orderId: props.id }).g
<!-- 时间信息 --> <!-- 时间信息 -->
<div class="bg-faint rounded-2xl p-4 space-y-4"> <div class="bg-faint rounded-2xl p-4 space-y-4">
<div class="text-base font-semibold mb-3"> <div class="text-base font-semibold mb-3">
时间信息 {{ t('tradeSettings.mySubscribe.timeInfo') }}
</div> </div>
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<span class="text-sm text-text-500">申购时间</span> <span class="text-sm text-text-500">{{ t('tradeSettings.mySubscribe.subscribeTime') }}</span>
<span class="text-sm">{{ new Date(data?.createdAt!).toLocaleString('zh-CN') }}</span> <span class="text-sm">{{ new Date(data?.createdAt!).toLocaleString('zh-CN') }}</span>
</div> </div>
<ui-divider /> <ui-divider />
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<span class="text-sm text-text-500">更新时间</span> <span class="text-sm text-text-500">{{ t('tradeSettings.mySubscribe.updateTime') }}</span>
<span class="text-sm">{{ new Date(data?.updatedAt!).toLocaleString('zh-CN') }}</span> <span class="text-sm">{{ new Date(data?.updatedAt!).toLocaleString('zh-CN') }}</span>
</div> </div>
</div> </div>

View File

@@ -5,6 +5,8 @@ import { client, safeClient } from "@/api";
import Category from "./components/category.vue"; import Category from "./components/category.vue";
import MySubscribeList from "./components/my-subscribe-list.vue"; import MySubscribeList from "./components/my-subscribe-list.vue";
const { t } = useI18n();
const [query] = useResetRef<MySubscribeRwaBody>({ const [query] = useResetRef<MySubscribeRwaBody>({
categoryId: "", categoryId: "",
limit: 20, limit: 20,
@@ -61,7 +63,7 @@ onBeforeMount(() => {
<ion-header> <ion-header>
<ion-toolbar class="ion-toolbar"> <ion-toolbar class="ion-toolbar">
<ui-back-button slot="start" /> <ui-back-button slot="start" />
<ion-title>我的申购</ion-title> <ion-title>{{ t('tradeSettings.mySubscribe.title') }}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
@@ -76,7 +78,7 @@ onBeforeMount(() => {
<ion-infinite-scroll threshold="100px" @ion-infinite="handleInfinite"> <ion-infinite-scroll threshold="100px" @ion-infinite="handleInfinite">
<ion-infinite-scroll-content <ion-infinite-scroll-content
loading-spinner="bubbles" loading-spinner="bubbles"
loading-text="加载中..." :loading-text="t('tradeSettings.mySubscribe.loading')"
/> />
</ion-infinite-scroll> </ion-infinite-scroll>
</ion-content> </ion-content>

View File

@@ -12,7 +12,7 @@ const { t } = useI18n();
<div> <div>
<div> <div>
<div class="font-semibold"> <div class="font-semibold">
关于 {{ t("tradeTokenized.about.about") }}
</div> </div>
<div class="text-xs mt-2"> <div class="text-xs mt-2">
{{ data?.product?.description || t('market.tradeRwa.noDescription') }} {{ data?.product?.description || t('market.tradeRwa.noDescription') }}
@@ -22,7 +22,7 @@ const { t } = useI18n();
<!-- document --> <!-- document -->
<div class="mt-5"> <div class="mt-5">
<div class="font-semibold mb-4"> <div class="font-semibold mb-4">
相关文档 {{ t("tradeTokenized.about.relatedDocs") }}
</div> </div>
<ui-file-preview :file-ids="props.data?.product?.proofDocumentIds || []" /> <ui-file-preview :file-ids="props.data?.product?.proofDocumentIds || []" />

View File

@@ -42,12 +42,12 @@ useTradingView(tradingViewInst, {
<div class="mt-5"> <div class="mt-5">
<div class="font-semibold"> <div class="font-semibold">
代币信息 {{ t("tradeTokenized.base.tokenInfo") }}
</div> </div>
<div class="item"> <div class="item">
<div class="label"> <div class="label">
代币符号 {{ t("tradeTokenized.base.tokenSymbol") }}
</div> </div>
<div class="value"> <div class="value">
{{ data?.assetCode }} {{ data?.assetCode }}
@@ -55,7 +55,7 @@ useTradingView(tradingViewInst, {
</div> </div>
<div class="item"> <div class="item">
<div class="label"> <div class="label">
关联资产 {{ t("tradeTokenized.base.relatedAsset") }}
</div> </div>
<div class="value"> <div class="value">
{{ data?.product?.name }} {{ data?.product?.name }}
@@ -63,7 +63,7 @@ useTradingView(tradingViewInst, {
</div> </div>
<div class="item"> <div class="item">
<div class="label"> <div class="label">
总供应量 {{ t("tradeTokenized.base.totalSupply") }}
</div> </div>
<div class="value"> <div class="value">
{{ Number(data?.totalSupply).toString() }} {{ Number(data?.totalSupply).toString() }}
@@ -71,7 +71,7 @@ useTradingView(tradingViewInst, {
</div> </div>
<div class="item"> <div class="item">
<div class="label"> <div class="label">
市值 {{ t("tradeTokenized.base.marketCap") }}
</div> </div>
<div class="value"> <div class="value">
${{ Number(data?.product?.estimatedValue).toString() }} ${{ Number(data?.product?.estimatedValue).toString() }}
@@ -79,7 +79,7 @@ useTradingView(tradingViewInst, {
</div> </div>
<div class="item"> <div class="item">
<div class="label"> <div class="label">
24小时成交量 {{ t("tradeTokenized.base.volume24h") }}
</div> </div>
<div class="value"> <div class="value">
$500000 $500000
@@ -87,7 +87,7 @@ useTradingView(tradingViewInst, {
</div> </div>
<div class="item"> <div class="item">
<div class="label"> <div class="label">
创建时间 {{ t("tradeTokenized.base.createTime") }}
</div> </div>
<div class="value"> <div class="value">
{{ useDateFormat(data?.createdAt, 'YYYY/MM/DD') }} {{ useDateFormat(data?.createdAt, 'YYYY/MM/DD') }}
@@ -97,18 +97,18 @@ useTradingView(tradingViewInst, {
<div class="mt-5 space-y-4"> <div class="mt-5 space-y-4">
<div class="font-semibold"> <div class="font-semibold">
市场数据 {{ t("tradeTokenized.base.marketData") }}
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<Icon icon="hugeicons:trade-up" class="text-success" /> <Icon icon="hugeicons:trade-up" class="text-success" />
<div class="text-sm"> <div class="text-sm">
价格变动 {{ t("tradeTokenized.base.priceChange") }}
</div> </div>
</div> </div>
<div class="item"> <div class="item">
<div class="label"> <div class="label">
24小时 {{ t("tradeTokenized.base.hours24") }}
</div> </div>
<div class="value"> <div class="value">
{{ tickerData?.changePercent || '-' }}% {{ tickerData?.changePercent || '-' }}%
@@ -116,7 +116,7 @@ useTradingView(tradingViewInst, {
</div> </div>
<div class="item"> <div class="item">
<div class="label"> <div class="label">
7 {{ t("tradeTokenized.base.days7") }}
</div> </div>
<div class="value"> <div class="value">
-% -%
@@ -124,7 +124,7 @@ useTradingView(tradingViewInst, {
</div> </div>
<div class="item"> <div class="item">
<div class="label"> <div class="label">
30 {{ t("tradeTokenized.base.days30") }}
</div> </div>
<div class="value"> <div class="value">
-% -%

View File

@@ -10,6 +10,8 @@ const props = defineProps<{
data: TradableData | null; data: TradableData | null;
}>(); }>();
const { t } = useI18n();
const tradingviewOptions: Partial<ChartingLibraryWidgetOptions> = { const tradingviewOptions: Partial<ChartingLibraryWidgetOptions> = {
disabled_features: [ disabled_features: [
"create_volume_indicator_by_default", "create_volume_indicator_by_default",
@@ -79,7 +81,7 @@ const priceChangeClass = computed(() => {
<div class="grid grid-cols-3 gap-3 text-sm"> <div class="grid grid-cols-3 gap-3 text-sm">
<div> <div>
<div class="text-text-500 text-xs mb-1"> <div class="text-text-500 text-xs mb-1">
24h {{ t("tradeTokenized.market.high24h") }}
</div> </div>
<div class="font-medium"> <div class="font-medium">
{{ formatPrice(tickerData?.high) }} {{ formatPrice(tickerData?.high) }}
@@ -87,7 +89,7 @@ const priceChangeClass = computed(() => {
</div> </div>
<div> <div>
<div class="text-text-500 text-xs mb-1"> <div class="text-text-500 text-xs mb-1">
24h {{ t("tradeTokenized.market.low24h") }}
</div> </div>
<div class="font-medium"> <div class="font-medium">
{{ formatPrice(tickerData?.low) }} {{ formatPrice(tickerData?.low) }}
@@ -95,7 +97,7 @@ const priceChangeClass = computed(() => {
</div> </div>
<div> <div>
<div class="text-text-500 text-xs mb-1"> <div class="text-text-500 text-xs mb-1">
24h {{ t("tradeTokenized.market.volume24h") }}
</div> </div>
<div class="font-medium"> <div class="font-medium">
{{ formatVolume(tickerData?.baseVolume) }} {{ formatVolume(tickerData?.baseVolume) }}
@@ -107,7 +109,7 @@ const priceChangeClass = computed(() => {
<TradingViewChart v-if="symbol" ref="tradingViewInst" class="my-5" height="300px" :symbol="symbol" :options="tradingviewOptions" /> <TradingViewChart v-if="symbol" ref="tradingViewInst" class="my-5" height="300px" :symbol="symbol" :options="tradingviewOptions" />
<ui-tabs size="small" class="my-3"> <ui-tabs size="small" class="my-3">
<ui-tab-pane name="order_book" title="订单表"> <ui-tab-pane name="order_book" :title="t('tradeTokenized.market.orderBookTab')">
<OrderBook :symbol="symbol" /> <OrderBook :symbol="symbol" />
</ui-tab-pane> </ui-tab-pane>
</ui-tabs> </ui-tabs>

View File

@@ -14,6 +14,7 @@ interface Item {
const props = defineProps<{ symbol: string }>(); const props = defineProps<{ symbol: string }>();
const { t } = useI18n();
const data = ref<Item>({ const data = ref<Item>({
asks: [], asks: [],
bids: [], bids: [],
@@ -108,10 +109,10 @@ onUnmounted(() => {
<!-- 标题 --> <!-- 标题 -->
<div class="grid grid-cols-2 gap-2 border-b border-(--ion-border-color) px-3 py-2 text-xs text-(--ion-color-medium)"> <div class="grid grid-cols-2 gap-2 border-b border-(--ion-border-color) px-3 py-2 text-xs text-(--ion-color-medium)">
<div class="text-left"> <div class="text-left">
价格 {{ t("tradeTokenized.orderBook.price") }}
</div> </div>
<div class="text-right"> <div class="text-right">
数量 {{ t("tradeTokenized.orderBook.quantity") }}
</div> </div>
</div> </div>
@@ -146,10 +147,10 @@ onUnmounted(() => {
<!-- 标题 --> <!-- 标题 -->
<div class="grid grid-cols-2 gap-2 border-b border-(--ion-border-color) px-3 py-2 text-xs text-(--ion-color-medium)"> <div class="grid grid-cols-2 gap-2 border-b border-(--ion-border-color) px-3 py-2 text-xs text-(--ion-color-medium)">
<div class="text-left"> <div class="text-left">
价格 {{ t("tradeTokenized.orderBook.price") }}
</div> </div>
<div class="text-right"> <div class="text-right">
数量 {{ t("tradeTokenized.orderBook.quantity") }}
</div> </div>
</div> </div>

View File

@@ -56,10 +56,10 @@ function gotoTrade(mode: TradeTypeEnum) {
<ui-tab-pane name="overview" :title="t('market.tradeRwa.tabs.overview')" lazy> <ui-tab-pane name="overview" :title="t('market.tradeRwa.tabs.overview')" lazy>
<Base :data="data" /> <Base :data="data" />
</ui-tab-pane> </ui-tab-pane>
<ui-tab-pane name="market" title="行情" lazy> <ui-tab-pane name="market" :title="t('tradeTokenized.index.marketTab')" lazy>
<Market :data="data" /> <Market :data="data" />
</ui-tab-pane> </ui-tab-pane>
<ui-tab-pane name="about" title="相关文档" lazy> <ui-tab-pane name="about" :title="t('tradeTokenized.index.aboutTab')" lazy>
<About :data="data" /> <About :data="data" />
</ui-tab-pane> </ui-tab-pane>
</ui-tabs> </ui-tabs>
@@ -69,10 +69,10 @@ function gotoTrade(mode: TradeTypeEnum) {
<ion-toolbar class="ion-padding-horizontal ion-toolbar"> <ion-toolbar class="ion-padding-horizontal ion-toolbar">
<div class="flex justify-center my-2 gap-5"> <div class="flex justify-center my-2 gap-5">
<ion-button shape="round" expand="block" color="success" @click="gotoTrade(TradeTypeEnum.BUY)"> <ion-button shape="round" expand="block" color="success" @click="gotoTrade(TradeTypeEnum.BUY)">
买入 {{ t("tradeTokenized.index.buy") }}
</ion-button> </ion-button>
<ion-button shape="round" expand="block" color="danger" @click="gotoTrade(TradeTypeEnum.SELL)"> <ion-button shape="round" expand="block" color="danger" @click="gotoTrade(TradeTypeEnum.SELL)">
卖出 {{ t("tradeTokenized.index.sell") }}
</ion-button> </ion-button>
</div> </div>
</ion-toolbar> </ion-toolbar>

View File

@@ -112,7 +112,7 @@ onUnmounted(() => {
<ion-icon slot="icon-only" :icon="arrowBackOutline" /> <ion-icon slot="icon-only" :icon="arrowBackOutline" />
</ion-button> </ion-button>
</ion-buttons> </ion-buttons>
<ion-title>用户设置</ion-title> <ion-title>{{ t('userSettings.index.email') }}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>

View File

@@ -4,21 +4,22 @@ import { arrowBackOutline } from "ionicons/icons";
import TdesignCopy from "~icons/tdesign/copy"; import TdesignCopy from "~icons/tdesign/copy";
import { authClient } from "@/auth"; import { authClient } from "@/auth";
const { t } = useI18n();
const router = useRouter(); const router = useRouter();
const userStore = useUserStore(); const userStore = useUserStore();
const { userProfile } = storeToRefs(userStore); const { userProfile } = storeToRefs(userStore);
async function handleSignOut() { async function handleSignOut() {
const alert = await alertController.create({ const alert = await alertController.create({
header: "Sign Out", header: t("userSettings.index.signOut"),
message: "Are you sure you want to sign out?", message: t("userSettings.index.signOutConfirm"),
buttons: [ buttons: [
{ {
text: "Cancel", text: t("userSettings.index.cancel"),
role: "cancel", role: "cancel",
}, },
{ {
text: "Sign Out", text: t("userSettings.index.signOut"),
role: "destructive", role: "destructive",
handler: async () => { handler: async () => {
userStore.signOut(); userStore.signOut();
@@ -39,7 +40,7 @@ function handleCopyUid() {
writeText(userProfile.value.uid); writeText(userProfile.value.uid);
toastController toastController
.create({ .create({
message: "UID copied to clipboard", message: t("userSettings.index.uidCopied"),
duration: 2000, duration: 2000,
position: "bottom", position: "bottom",
}) })
@@ -53,7 +54,7 @@ function handleCopyUid() {
<ion-header class="ion-no-border"> <ion-header class="ion-no-border">
<ion-toolbar class="ion-toolbar"> <ion-toolbar class="ion-toolbar">
<ui-back-button slot="start" /> <ui-back-button slot="start" />
<ion-title>用户设置</ion-title> <ion-title>{{ t('userSettings.index.title') }}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
@@ -97,7 +98,7 @@ function handleCopyUid() {
<div class="flex justify-between w-full items-center"> <div class="flex justify-between w-full items-center">
<div class="flex-center space-x-2"> <div class="flex-center space-x-2">
<div class="text-sm font-semibold"> <div class="text-sm font-semibold">
昵称 {{ t('userSettings.index.nickname') }}
</div> </div>
</div> </div>
<div class="end"> <div class="end">
@@ -110,7 +111,7 @@ function handleCopyUid() {
<div class="flex justify-between w-full items-center"> <div class="flex justify-between w-full items-center">
<div class="flex-center space-x-2"> <div class="flex-center space-x-2">
<div class="text-sm font-semibold"> <div class="text-sm font-semibold">
邮箱 {{ t('userSettings.index.email') }}
</div> </div>
</div> </div>
<div class="end"> <div class="end">
@@ -133,7 +134,7 @@ function handleCopyUid() {
<!-- Action Buttons --> <!-- Action Buttons -->
<div class="mt-10"> <div class="mt-10">
<ion-button expand="block" @click="handleSignOut"> <ion-button expand="block" @click="handleSignOut">
Sign Out {{ t('userSettings.index.signOut') }}
</ion-button> </ion-button>
</div> </div>
</ion-content> </ion-content>

View File

@@ -2,6 +2,7 @@
import { toastController } from "@ionic/vue"; import { toastController } from "@ionic/vue";
import { client, safeClient } from "@/api"; import { client, safeClient } from "@/api";
const { t } = useI18n();
const userStore = useUserStore(); const userStore = useUserStore();
const { userProfile } = storeToRefs(userStore); const { userProfile } = storeToRefs(userStore);
const nickname = ref(userProfile.value?.nickname || ""); const nickname = ref(userProfile.value?.nickname || "");
@@ -10,7 +11,7 @@ const { updateProfile } = useUserStore();
async function handleSave() { async function handleSave() {
if (!usernamePattern.test(nickname.value)) { if (!usernamePattern.test(nickname.value)) {
const toast = await toastController.create({ const toast = await toastController.create({
message: "昵称格式不正确", message: t("userSettings.nickname.invalidFormat"),
duration: 2000, duration: 2000,
position: "bottom", position: "bottom",
color: "danger", color: "danger",
@@ -24,7 +25,7 @@ async function handleSave() {
if (data) { if (data) {
updateProfile(); updateProfile();
const toast = await toastController.create({ const toast = await toastController.create({
message: "昵称更新成功", message: t("userSettings.nickname.updateSuccess"),
duration: 2000, duration: 2000,
position: "bottom", position: "bottom",
color: "success", color: "success",
@@ -39,7 +40,7 @@ async function handleSave() {
<ion-header> <ion-header>
<ion-toolbar class="ion-toolbar"> <ion-toolbar class="ion-toolbar">
<ui-back-button slot="start" /> <ui-back-button slot="start" />
<ion-title>昵称设置</ion-title> <ion-title>{{ t('userSettings.nickname.title') }}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
@@ -47,16 +48,16 @@ async function handleSave() {
<div class="space-y-3"> <div class="space-y-3">
<ui-input <ui-input
v-model="nickname" v-model="nickname"
placeholder="请输入昵称" :placeholder="t('userSettings.nickname.placeholder')"
class="w-full" class="w-full"
/> />
<div class="text-xs text-gray-500"> <div class="text-xs text-gray-500">
仅支持字母数字下划线长度 3-20 个字符 {{ t('userSettings.nickname.hint') }}
</div> </div>
<ion-button expand="block" class="mt-5" @click="handleSave"> <ion-button expand="block" class="mt-5" @click="handleSave">
保存 {{ t('userSettings.nickname.save') }}
</ion-button> </ion-button>
</div> </div>
</ion-content> </ion-content>

View File

@@ -3,6 +3,7 @@ import { toastController } from "@ionic/vue";
import { safeClient } from "@/api"; import { safeClient } from "@/api";
import { authClient } from "@/auth"; import { authClient } from "@/auth";
const { t } = useI18n();
const userStore = useUserStore(); const userStore = useUserStore();
const { userProfile } = storeToRefs(userStore); const { userProfile } = storeToRefs(userStore);
const username = ref(userProfile.value?.user?.username || ""); const username = ref(userProfile.value?.user?.username || "");
@@ -11,7 +12,7 @@ const { updateProfile } = useUserStore();
async function handleSave() { async function handleSave() {
if (!usernamePattern.test(username.value)) { if (!usernamePattern.test(username.value)) {
const toast = await toastController.create({ const toast = await toastController.create({
message: "用户名格式不正确", message: t("userSettings.username.invalidFormat"),
duration: 2000, duration: 2000,
position: "bottom", position: "bottom",
color: "danger", color: "danger",
@@ -25,7 +26,7 @@ async function handleSave() {
if (data) { if (data) {
updateProfile(); updateProfile();
const toast = await toastController.create({ const toast = await toastController.create({
message: "用户名更新成功", message: t("userSettings.username.updateSuccess"),
duration: 2000, duration: 2000,
position: "bottom", position: "bottom",
color: "success", color: "success",
@@ -40,7 +41,7 @@ async function handleSave() {
<ion-header> <ion-header>
<ion-toolbar class="ion-toolbar"> <ion-toolbar class="ion-toolbar">
<ui-back-button slot="start" /> <ui-back-button slot="start" />
<ion-title>用户设置</ion-title> <ion-title>{{ t('userSettings.username.title') }}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
@@ -48,16 +49,16 @@ async function handleSave() {
<div class="space-y-3"> <div class="space-y-3">
<ui-input <ui-input
v-model="username" v-model="username"
placeholder="请输入用户名" :placeholder="t('userSettings.username.placeholder')"
class="w-full" class="w-full"
/> />
<div class="text-xs text-gray-500"> <div class="text-xs text-gray-500">
仅支持字母数字下划线长度 3-20 个字符 {{ t('userSettings.username.hint') }}
</div> </div>
<ion-button expand="block" class="mt-5" @click="handleSave"> <ion-button expand="block" class="mt-5" @click="handleSave">
保存 {{ t('userSettings.username.save') }}
</ion-button> </ion-button>
</div> </div>
</ion-content> </ion-content>

View File

@@ -5,6 +5,7 @@ import IconParkOutlineTransaction from "~icons/icon-park-outline/transaction";
import { client, safeClient } from "@/api"; import { client, safeClient } from "@/api";
const props = defineProps<{ code: string; type?: "funding" | "trading" }>(); const props = defineProps<{ code: string; type?: "funding" | "trading" }>();
const { t } = useI18n();
const [query, resetQuery] = useResetRef<AssetRecordBody>({ const [query, resetQuery] = useResetRef<AssetRecordBody>({
limit: 20, limit: 20,
offset: 0, offset: 0,
@@ -74,7 +75,7 @@ onBeforeMount(() => {
</ion-refresher> </ion-refresher>
<div class="ion-padding-horizontal text-md font-semibold my-4"> <div class="ion-padding-horizontal text-md font-semibold my-4">
资产记录 {{ t('wallet.assetRecord.title') }}
</div> </div>
<!-- 资产记录列表 --> <!-- 资产记录列表 -->
@@ -108,7 +109,7 @@ onBeforeMount(() => {
<div class="flex items-center justify-between text-xs"> <div class="flex items-center justify-between text-xs">
<div class="flex items-center space-x-6"> <div class="flex items-center space-x-6">
<div> <div>
<span class="text-text-500">余额</span> <span class="text-text-500">{{ t('wallet.assetRecord.balance') }}</span>
<span class="ml-2 font-medium">{{ Number(item.availableAfter).toFixed(2) }}</span> <span class="ml-2 font-medium">{{ Number(item.availableAfter).toFixed(2) }}</span>
</div> </div>
</div> </div>
@@ -116,7 +117,7 @@ onBeforeMount(() => {
<div class="pt-3"> <div class="pt-3">
<div class="text-xs text-text-500"> <div class="text-xs text-text-500">
<span>备注</span> <span>{{ t('wallet.assetRecord.memo') }}</span>
<span>{{ item.memo }}</span> <span>{{ item.memo }}</span>
</div> </div>
</div> </div>
@@ -131,14 +132,14 @@ onBeforeMount(() => {
<div v-else class="flex flex-col items-center justify-center py-20 text-text-500"> <div v-else class="flex flex-col items-center justify-center py-20 text-text-500">
<IconParkOutlineTransaction class="text-6xl mb-4 opacity-30" /> <IconParkOutlineTransaction class="text-6xl mb-4 opacity-30" />
<div class="text-sm"> <div class="text-sm">
暂无资产记录 {{ t('wallet.assetRecord.empty') }}
</div> </div>
</div> </div>
<ion-infinite-scroll threshold="100px" @ion-infinite="handleInfinite"> <ion-infinite-scroll threshold="100px" @ion-infinite="handleInfinite">
<ion-infinite-scroll-content <ion-infinite-scroll-content
loading-spinner="bubbles" loading-spinner="bubbles"
loading-text="加载更多中..." :loading-text="t('wallet.assetRecord.loadingMore')"
/> />
</ion-infinite-scroll> </ion-infinite-scroll>
</ion-content> </ion-content>

View File

@@ -2,6 +2,7 @@
import Deposit from "./components/deposit.vue"; import Deposit from "./components/deposit.vue";
import Withdraw from "./components/withdraw.vue"; import Withdraw from "./components/withdraw.vue";
const { t } = useI18n();
const activeTab = ref("deposit"); const activeTab = ref("deposit");
</script> </script>
@@ -10,12 +11,12 @@ const activeTab = ref("deposit");
<ion-header class="ion-no-border"> <ion-header class="ion-no-border">
<ion-toolbar class="ion-toolbar"> <ion-toolbar class="ion-toolbar">
<ui-back-button slot="start" text="" /> <ui-back-button slot="start" text="" />
<ion-title>账单</ion-title> <ion-title>{{ t('wallet.bill.title') }}</ion-title>
</ion-toolbar> </ion-toolbar>
<ion-toolbar class="ion-toolbar"> <ion-toolbar class="ion-toolbar">
<ui-tabs v-model="activeTab" size="small" class="px-2"> <ui-tabs v-model="activeTab" size="small" class="px-2">
<ui-tab-pane name="deposit" title="充值记录" /> <ui-tab-pane name="deposit" :title="t('wallet.bill.depositTab')" />
<ui-tab-pane name="withdraw" title="提现记录" /> <ui-tab-pane name="withdraw" :title="t('wallet.bill.withdrawTab')" />
</ui-tabs> </ui-tabs>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>

View File

@@ -7,6 +7,7 @@ import CryptocurrencyColorNuls from "~icons/cryptocurrency-color/nuls";
import { client, safeClient } from "@/api"; import { client, safeClient } from "@/api";
import { DepositTypeEnum } from "@/api/enum"; import { DepositTypeEnum } from "@/api/enum";
const { t } = useI18n();
const [query] = useResetRef<UserDepositOrderBody>({ const [query] = useResetRef<UserDepositOrderBody>({
limit: 20, limit: 20,
offset: 0, offset: 0,
@@ -49,14 +50,14 @@ async function handleInfinite(event: InfiniteScrollCustomEvent) {
} }
async function handleCancel(id: string) { async function handleCancel(id: string) {
const alert = await alertController.create({ const alert = await alertController.create({
header: "确认取消充值?", header: t("wallet.deposit.confirmCancel"),
buttons: [ buttons: [
{ {
text: "取消", text: t("wallet.deposit.cancel"),
role: "cancel", role: "cancel",
}, },
{ {
text: "确认取消", text: t("wallet.deposit.confirmCancelButton"),
role: "destructive", role: "destructive",
handler: async () => { handler: async () => {
await safeClient(client.api.deposit.orders({ orderId: id }).cancel.post()); await safeClient(client.api.deposit.orders({ orderId: id }).cancel.post());
@@ -102,7 +103,7 @@ onBeforeMount(() => {
<ion-row class="ion-align-items-center"> <ion-row class="ion-align-items-center">
<ion-col size="3" class="flex flex-col"> <ion-col size="3" class="flex flex-col">
<div class="text-xs text-text-500"> <div class="text-xs text-text-500">
金额 {{ t('wallet.deposit.amount') }}
</div> </div>
<div class="text-sm font-bold"> <div class="text-sm font-bold">
{{ Number(item.amount).toFixed(2) }} {{ Number(item.amount).toFixed(2) }}
@@ -110,7 +111,7 @@ onBeforeMount(() => {
</ion-col> </ion-col>
<ion-col size="3" class="flex flex-col"> <ion-col size="3" class="flex flex-col">
<div class="text-xs text-text-500"> <div class="text-xs text-text-500">
充值方式 {{ t('wallet.deposit.depositMethod') }}
</div> </div>
<div class="text-xs font-bold"> <div class="text-xs font-bold">
{{ item.depositType }} {{ item.depositType }}
@@ -118,7 +119,7 @@ onBeforeMount(() => {
</ion-col> </ion-col>
<ion-col size="6" class="flex flex-col"> <ion-col size="6" class="flex flex-col">
<div class="text-xs text-text-500 text-right"> <div class="text-xs text-text-500 text-right">
创建时间 {{ t('wallet.deposit.createdAt') }}
</div> </div>
<div class="text-xs font-bold text-right"> <div class="text-xs font-bold text-right">
{{ useDateFormat(item.createdAt, 'MM/DD HH:mm') }} {{ useDateFormat(item.createdAt, 'MM/DD HH:mm') }}
@@ -129,7 +130,7 @@ onBeforeMount(() => {
<ion-row class="ion-align-items-center"> <ion-row class="ion-align-items-center">
<ion-col size="6" class="flex flex-col"> <ion-col size="6" class="flex flex-col">
<div class="text-xs text-text-500"> <div class="text-xs text-text-500">
订单号 {{ t('wallet.deposit.orderNo') }}
</div> </div>
<div class="text-xs font-bold"> <div class="text-xs font-bold">
{{ item.orderNo }} {{ item.orderNo }}
@@ -137,7 +138,7 @@ onBeforeMount(() => {
</ion-col> </ion-col>
<ion-col size="6" class="text-right"> <ion-col size="6" class="text-right">
<ion-button v-if="item.status === 'pending'" size="small" fill="outline" color="success" @click.stop="handleCancel(item.id)"> <ion-button v-if="item.status === 'pending'" size="small" fill="outline" color="success" @click.stop="handleCancel(item.id)">
取消充值 {{ t('wallet.deposit.cancelDeposit') }}
</ion-button> </ion-button>
</ion-col> </ion-col>
</ion-row> </ion-row>

View File

@@ -5,6 +5,7 @@ import { alertController } from "@ionic/vue";
import CryptocurrencyColorNuls from "~icons/cryptocurrency-color/nuls"; import CryptocurrencyColorNuls from "~icons/cryptocurrency-color/nuls";
import { client, safeClient } from "@/api"; import { client, safeClient } from "@/api";
const { t } = useI18n();
const [query] = useResetRef<UserWithdrawOrderBody>({ const [query] = useResetRef<UserWithdrawOrderBody>({
limit: 20, limit: 20,
offset: 0, offset: 0,
@@ -46,14 +47,14 @@ async function handleInfinite(event: InfiniteScrollCustomEvent) {
} }
async function handleCancel(id: string) { async function handleCancel(id: string) {
const alert = await alertController.create({ const alert = await alertController.create({
header: "确认取消提现?", header: t("wallet.withdrawDetail.confirmCancel"),
buttons: [ buttons: [
{ {
text: "取消", text: t("wallet.withdrawDetail.cancel"),
role: "cancel", role: "cancel",
}, },
{ {
text: "确认取消", text: t("wallet.withdrawDetail.confirmCancelButton"),
role: "destructive", role: "destructive",
handler: async () => { handler: async () => {
await safeClient(client.api.withdraw({ orderId: id }).delete()); await safeClient(client.api.withdraw({ orderId: id }).delete());
@@ -98,7 +99,7 @@ onBeforeMount(() => {
<ion-row class="ion-align-items-center"> <ion-row class="ion-align-items-center">
<ion-col size="3" class="flex flex-col"> <ion-col size="3" class="flex flex-col">
<div class="text-xs text-text-500"> <div class="text-xs text-text-500">
金额 {{ t('wallet.withdrawDetail.amount') }}
</div> </div>
<div class="text-sm font-bold"> <div class="text-sm font-bold">
{{ Number(item.amount).toFixed(2) }} {{ Number(item.amount).toFixed(2) }}
@@ -106,7 +107,7 @@ onBeforeMount(() => {
</ion-col> </ion-col>
<ion-col size="3" class="flex flex-col"> <ion-col size="3" class="flex flex-col">
<div class="text-xs text-text-500"> <div class="text-xs text-text-500">
实际到账 {{ t('wallet.withdrawDetail.actualAmount') }}
</div> </div>
<div class="text-sm font-bold"> <div class="text-sm font-bold">
{{ Number(item.actualAmount).toFixed(2) }} {{ Number(item.actualAmount).toFixed(2) }}
@@ -114,7 +115,7 @@ onBeforeMount(() => {
</ion-col> </ion-col>
<ion-col size="6" class="flex flex-col"> <ion-col size="6" class="flex flex-col">
<div class="text-xs text-text-500 text-right"> <div class="text-xs text-text-500 text-right">
创建时间 {{ t('wallet.withdrawDetail.createdAt') }}
</div> </div>
<div class="text-xs font-bold text-right"> <div class="text-xs font-bold text-right">
{{ useDateFormat(item.createdAt, 'MM/DD HH:mm') }} {{ useDateFormat(item.createdAt, 'MM/DD HH:mm') }}
@@ -124,7 +125,7 @@ onBeforeMount(() => {
<ion-row class="ion-align-items-center"> <ion-row class="ion-align-items-center">
<ion-col size="3" class="flex flex-col"> <ion-col size="3" class="flex flex-col">
<div class="text-xs text-text-500"> <div class="text-xs text-text-500">
手续费 {{ t('wallet.withdrawDetail.fee') }}
</div> </div>
<div class="text-sm font-bold"> <div class="text-sm font-bold">
{{ Number(item.fee).toFixed(2) }} {{ Number(item.fee).toFixed(2) }}
@@ -132,7 +133,7 @@ onBeforeMount(() => {
</ion-col> </ion-col>
<ion-col size="3" class="flex flex-col"> <ion-col size="3" class="flex flex-col">
<div class="text-xs text-text-500"> <div class="text-xs text-text-500">
提现方式 {{ t('wallet.withdrawDetail.withdrawMethod') }}
</div> </div>
<div class="text-xs font-bold"> <div class="text-xs font-bold">
<span>{{ item.withdrawMethod }}</span> <span>{{ item.withdrawMethod }}</span>
@@ -141,7 +142,7 @@ onBeforeMount(() => {
</ion-col> </ion-col>
<ion-col size="6" class="text-right"> <ion-col size="6" class="text-right">
<ion-button v-if="item.status === 'pending'" size="small" fill="outline" color="success" @click.stop="handleCancel(item.id)"> <ion-button v-if="item.status === 'pending'" size="small" fill="outline" color="success" @click.stop="handleCancel(item.id)">
取消提现 {{ t('wallet.withdrawDetail.cancelWithdraw') }}
</ion-button> </ion-button>
</ion-col> </ion-col>
</ion-row> </ion-row>

View File

@@ -1,6 +1,7 @@
<script lang='ts' setup> <script lang='ts' setup>
import { eyeOffOutline, eyeOutline } from "ionicons/icons"; import { eyeOffOutline, eyeOutline } from "ionicons/icons";
const { t } = useI18n();
const walletStore = useWalletStore(); const walletStore = useWalletStore();
const { fundingBalances, totalAssetValue } = storeToRefs(walletStore); const { fundingBalances, totalAssetValue } = storeToRefs(walletStore);
const fundingBalanceVisible = useStorage("funding-balances-visible", true); const fundingBalanceVisible = useStorage("funding-balances-visible", true);
@@ -15,14 +16,14 @@ onMounted(() => {
<ion-header class="ion-no-border"> <ion-header class="ion-no-border">
<ion-toolbar class="ion-toolbar"> <ion-toolbar class="ion-toolbar">
<ui-back-button slot="start" /> <ui-back-button slot="start" />
<ion-title>资金账户</ion-title> <ion-title>{{ t('wallet.funding.title') }}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content :fullscreen="true"> <ion-content :fullscreen="true">
<div class="flex flex-col gap-1 ion-padding border-b border-text-900 mb-2"> <div class="flex flex-col gap-1 ion-padding border-b border-text-900 mb-2">
<div class="text-sm text-gray-500 flex items-center gap-2" @click="fundingBalanceVisible = !fundingBalanceVisible"> <div class="text-sm text-gray-500 flex items-center gap-2" @click="fundingBalanceVisible = !fundingBalanceVisible">
<div class="text-md"> <div class="text-md">
总资产估值 {{ t('wallet.funding.totalAssets') }}
</div> </div>
<ion-icon :icon="fundingBalanceVisible ? eyeOffOutline : eyeOutline" /> <ion-icon :icon="fundingBalanceVisible ? eyeOffOutline : eyeOutline" />
</div> </div>
@@ -37,7 +38,7 @@ onMounted(() => {
</div> </div>
<div class="ion-padding-horizontal text-md font-semibold my-4"> <div class="ion-padding-horizontal text-md font-semibold my-4">
资产 {{ t('wallet.funding.assets') }}
</div> </div>
<ion-list lines="none" class="space-y-5 mt-2!"> <ion-list lines="none" class="space-y-5 mt-2!">
@@ -50,10 +51,10 @@ onMounted(() => {
{{ asset.assetCode }} {{ asset.assetCode }}
</div> </div>
<div class="text-xs text-text-500 font-semibold"> <div class="text-xs text-text-500 font-semibold">
总共: ${{ Number(asset.total).toFixed(2) }} {{ t('wallet.funding.total') }}: ${{ Number(asset.total).toFixed(2) }}
</div> </div>
<div class="text-xs text-text-500 font-semibold"> <div class="text-xs text-text-500 font-semibold">
冻结: ${{ Number(asset.frozen).toFixed(2) }} {{ t('wallet.funding.frozen') }}: ${{ Number(asset.frozen).toFixed(2) }}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,6 +1,7 @@
<script lang='ts' setup> <script lang='ts' setup>
import { eyeOffOutline, eyeOutline } from "ionicons/icons"; import { eyeOffOutline, eyeOutline } from "ionicons/icons";
const { t } = useI18n();
const walletStore = useWalletStore(); const walletStore = useWalletStore();
const { tradingBalances, totalAssetValue } = storeToRefs(walletStore); const { tradingBalances, totalAssetValue } = storeToRefs(walletStore);
const tradingBalanceVisible = useStorage("trading-balances-visible", true); const tradingBalanceVisible = useStorage("trading-balances-visible", true);
@@ -15,7 +16,7 @@ onMounted(() => {
<ion-header class="ion-no-border"> <ion-header class="ion-no-border">
<ion-toolbar class="ion-toolbar"> <ion-toolbar class="ion-toolbar">
<ui-back-button slot="start" /> <ui-back-button slot="start" />
<ion-title>交易账户</ion-title> <ion-title>{{ t('wallet.trading.title') }}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
@@ -23,7 +24,7 @@ onMounted(() => {
<div class="flex flex-col gap-1 ion-padding border-b border-text-900 mb-2"> <div class="flex flex-col gap-1 ion-padding border-b border-text-900 mb-2">
<div class="text-sm text-gray-500 flex items-center gap-2" @click="tradingBalanceVisible = !tradingBalanceVisible"> <div class="text-sm text-gray-500 flex items-center gap-2" @click="tradingBalanceVisible = !tradingBalanceVisible">
<div class="text-md"> <div class="text-md">
总资产估值 {{ t('wallet.trading.totalAssets') }}
</div> </div>
<ion-icon :icon="tradingBalanceVisible ? eyeOffOutline : eyeOutline" /> <ion-icon :icon="tradingBalanceVisible ? eyeOffOutline : eyeOutline" />
</div> </div>
@@ -38,7 +39,7 @@ onMounted(() => {
</div> </div>
<div class="ion-padding-horizontal text-md font-semibold my-4"> <div class="ion-padding-horizontal text-md font-semibold my-4">
资产 {{ t('wallet.trading.assets') }}
</div> </div>
<ion-list lines="none" class="space-y-5 mt-2!"> <ion-list lines="none" class="space-y-5 mt-2!">
@@ -51,10 +52,10 @@ onMounted(() => {
{{ asset.assetCode }} {{ asset.assetCode }}
</div> </div>
<div class="text-xs text-text-500 font-semibold"> <div class="text-xs text-text-500 font-semibold">
总共: ${{ Number(asset.total).toFixed(2) }} {{ t('wallet.trading.total') }}: ${{ Number(asset.total).toFixed(2) }}
</div> </div>
<div class="text-xs text-text-500 font-semibold"> <div class="text-xs text-text-500 font-semibold">
冻结: ${{ Number(asset.frozen).toFixed(2) }} {{ t('wallet.trading.frozen') }}: ${{ Number(asset.frozen).toFixed(2) }}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -13,6 +13,7 @@ const props = defineProps<{
id: string; id: string;
}>(); }>();
const { t } = useI18n();
const router = useRouter(); const router = useRouter();
const walletStore = useWalletStore(); const walletStore = useWalletStore();
const { fundingBalances } = storeToRefs(walletStore); const { fundingBalances } = storeToRefs(walletStore);
@@ -40,11 +41,11 @@ const availableBalance = computed(() => {
// 验证规则 // 验证规则
const schema = computed(() => toTypedSchema(z.object({ const schema = computed(() => toTypedSchema(z.object({
assetCode: z.string().min(1, "请选择币种"), assetCode: z.string().min(1, t("wallet.transferToUser.selectCurrencyRequired")),
amount: z amount: z
.number({ required_error: "请输入转账金额", invalid_type_error: "请输入有效的数字" }) .number({ required_error: t("wallet.transferToUser.enterAmountRequired"), invalid_type_error: t("wallet.transferToUser.enterValidNumber") })
.positive("转账金额必须大于0") .positive(t("wallet.transferToUser.amountMustBePositive"))
.refine(value => value <= Number(availableBalance.value), `可用余额不足,当前余额:${availableBalance.value}`), .refine(value => value <= Number(availableBalance.value), t("wallet.transferToUser.insufficientBalance", { balance: availableBalance.value })),
memo: z.string().optional(), memo: z.string().optional(),
}))); })));
@@ -72,7 +73,7 @@ function setMaxAmount() {
// 获取目标用户信息 // 获取目标用户信息
async function fetchTargetUser() { async function fetchTargetUser() {
const loading = await loadingController.create({ const loading = await loadingController.create({
message: "加载用户信息...", message: t("wallet.transferToUser.loadingUserInfo"),
}); });
await loading.present(); await loading.present();
@@ -85,7 +86,7 @@ async function fetchTargetUser() {
} }
else { else {
const toast = await toastController.create({ const toast = await toastController.create({
message: "用户不存在或已注销, 即将返回上一页", message: t("wallet.transferToUser.userNotFound"),
duration: 2000, duration: 2000,
color: "warning", color: "warning",
}); });
@@ -99,7 +100,7 @@ async function fetchTargetUser() {
// 提交转账 // 提交转账
async function onSubmit(values: GenericObject) { async function onSubmit(values: GenericObject) {
const loading = await loadingController.create({ const loading = await loadingController.create({
message: "转账中...", message: t("wallet.transferToUser.transferring"),
}); });
await loading.present(); await loading.present();
@@ -114,7 +115,7 @@ async function onSubmit(values: GenericObject) {
if (!error.value) { if (!error.value) {
const toast = await toastController.create({ const toast = await toastController.create({
message: "转账成功", message: t("wallet.transferToUser.transferSuccess"),
duration: 2000, duration: 2000,
position: "bottom", position: "bottom",
color: "success", color: "success",
@@ -143,7 +144,7 @@ onMounted(() => {
<ion-buttons slot="start"> <ion-buttons slot="start">
<ion-back-button default-href="/layout/user" /> <ion-back-button default-href="/layout/user" />
</ion-buttons> </ion-buttons>
<ion-title>转账给用户</ion-title> <ion-title>{{ t('wallet.transferToUser.title') }}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content :fullscreen="true" class="ion-padding"> <ion-content :fullscreen="true" class="ion-padding">
@@ -174,7 +175,7 @@ onMounted(() => {
<Field name="assetCode"> <Field name="assetCode">
<template #default="{ value }"> <template #default="{ value }">
<ion-label class="block text-sm font-medium mb-2"> <ion-label class="block text-sm font-medium mb-2">
选择币种 {{ t('wallet.transferToUser.selectCurrency') }}
</ion-label> </ion-label>
<div <div
class="flex items-center justify-between bg-text-900 rounded-2xl p-4 cursor-pointer" class="flex items-center justify-between bg-text-900 rounded-2xl p-4 cursor-pointer"
@@ -198,8 +199,8 @@ onMounted(() => {
<div class="relative"> <div class="relative">
<ui-input-label <ui-input-label
v-bind="field" v-bind="field"
label="转账金额" :label="t('wallet.transferToUser.amount')"
placeholder="请输入转账金额" :placeholder="t('wallet.transferToUser.enterAmount')"
type="number" type="number"
inputmode="decimal" inputmode="decimal"
/> />
@@ -209,7 +210,7 @@ onMounted(() => {
class="absolute right-0 top-8 text-sm font-semibold" class="absolute right-0 top-8 text-sm font-semibold"
@click="setMaxAmount" @click="setMaxAmount"
> >
全部 {{ t('wallet.transferToUser.all') }}
</ion-button> </ion-button>
</div> </div>
</template> </template>
@@ -219,7 +220,7 @@ onMounted(() => {
<!-- 可用余额 --> <!-- 可用余额 -->
<div class="flex items-center justify-between px-1"> <div class="flex items-center justify-between px-1">
<span class="text-sm text-text-400">可用余额</span> <span class="text-sm text-text-400">{{ t('wallet.transferToUser.availableBalance') }}</span>
<span class="text-sm font-medium">{{ Number(availableBalance).toFixed(2) }}</span> <span class="text-sm font-medium">{{ Number(availableBalance).toFixed(2) }}</span>
</div> </div>
@@ -229,8 +230,8 @@ onMounted(() => {
<template #default="{ field }"> <template #default="{ field }">
<ui-input-label <ui-input-label
v-bind="field" v-bind="field"
label="备注(可选)" :label="t('wallet.transferToUser.memo')"
placeholder="请输入备注信息" :placeholder="t('wallet.transferToUser.enterMemo')"
type="text" type="text"
/> />
</template> </template>
@@ -245,7 +246,7 @@ onMounted(() => {
shape="round" shape="round"
class="mt-6 h-12 font-semibold" class="mt-6 h-12 font-semibold"
> >
确认转账 {{ t('wallet.transferToUser.confirmTransfer') }}
</ion-button> </ion-button>
</div> </div>
</Form> </Form>

View File

@@ -168,7 +168,7 @@ function getAccountTypeName(type: AccountType) {
<Field name="assetCode"> <Field name="assetCode">
<template #default="{ value }"> <template #default="{ value }">
<ion-label class="block text-sm font-medium mb-2"> <ion-label class="block text-sm font-medium mb-2">
选择币种 {{ t('wallet.transfer.selectCurrency') }}
</ion-label> </ion-label>
<div <div
class="flex items-center justify-between bg-faint rounded-2xl p-4 cursor-pointer" class="flex items-center justify-between bg-faint rounded-2xl p-4 cursor-pointer"
@@ -239,7 +239,7 @@ function getAccountTypeName(type: AccountType) {
class="absolute right-0 top-10.5 text-sm font-semibold z-10" class="absolute right-0 top-10.5 text-sm font-semibold z-10"
@click="setMaxAmount" @click="setMaxAmount"
> >
全部 {{ t('wallet.transfer.all') }}
</ion-button> </ion-button>
</div> </div>
</template> </template>

View File

@@ -6,6 +6,8 @@ const emit = defineEmits<{
select: [id: BankAccountsData]; select: [id: BankAccountsData];
}>(); }>();
const { t } = useI18n();
const walletStore = useWalletStore(); const walletStore = useWalletStore();
const { bankAccounts } = storeToRefs(walletStore); const { bankAccounts } = storeToRefs(walletStore);
@@ -18,7 +20,7 @@ function handleSelect(item: BankAccountsData) {
<template> <template>
<ion-header class="ion-no-border"> <ion-header class="ion-no-border">
<ion-toolbar class="ion-toolbar"> <ion-toolbar class="ion-toolbar">
<ion-title>选择银行账户</ion-title> <ion-title>{{ t("wallet.selectBankAccount.title") }}</ion-title>
</ion-toolbar> </ion-toolbar>
<ion-content> <ion-content>

View File

@@ -5,6 +5,8 @@ const emit = defineEmits<{
select: [code: string]; select: [code: string];
}>(); }>();
const { t } = useI18n();
const walletStore = useWalletStore(); const walletStore = useWalletStore();
const { fundingBalances } = storeToRefs(walletStore); const { fundingBalances } = storeToRefs(walletStore);
@@ -21,7 +23,7 @@ onMounted(() => {
<template> <template>
<ion-header class="ion-no-border"> <ion-header class="ion-no-border">
<ion-toolbar class="ion-toolbar"> <ion-toolbar class="ion-toolbar">
<ion-title>选择货币</ion-title> <ion-title>{{ t("wallet.selectCurrency.title") }}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>

View File

@@ -67,7 +67,7 @@ async function openSelectBankAccount() {
async function onSubmit(values: GenericObject) { async function onSubmit(values: GenericObject) {
const loading = await loadingController.create({ const loading = await loadingController.create({
message: "提交中...", message: t("withdraw.submitting"),
}); });
await loading.present(); await loading.present();
@@ -108,7 +108,7 @@ async function onSubmit(values: GenericObject) {
<Field name="assetCode"> <Field name="assetCode">
<template #default="{ value }"> <template #default="{ value }">
<ion-label class="block text-sm font-medium mb-2"> <ion-label class="block text-sm font-medium mb-2">
选择币种 {{ t("withdraw.selectCurrency") }}
</ion-label> </ion-label>
<div <div
class="flex items-center justify-between bg-faint rounded-2xl p-4 cursor-pointer" class="flex items-center justify-between bg-faint rounded-2xl p-4 cursor-pointer"