diff --git a/auto-imports.d.ts b/auto-imports.d.ts index d19b18a..b13f401 100644 --- a/auto-imports.d.ts +++ b/auto-imports.d.ts @@ -41,7 +41,7 @@ declare global { const effectScope: typeof import('vue').effectScope const emailPattern: typeof import('./src/utils/pattern').emailPattern const extendRef: typeof import('@vueuse/core').extendRef - const formatAmount: typeof import('./src/utils/helper').formatAmount + const formatAmountWithSplit: typeof import('./src/utils/helper').formatAmountWithSplit const formatAmountWithUnit: typeof import('./src/utils/helper').formatAmountWithUnit const formatBalance: typeof import('./src/utils/helper').formatBalance const getActivePinia: typeof import('pinia').getActivePinia @@ -405,7 +405,8 @@ declare module 'vue' { readonly effectScope: UnwrapRef readonly emailPattern: UnwrapRef readonly extendRef: UnwrapRef - readonly formatAmount: UnwrapRef + readonly formatAmountWithSplit: UnwrapRef + readonly formatAmountWithUnit: UnwrapRef readonly formatBalance: UnwrapRef readonly getActivePinia: UnwrapRef readonly getCacheRemainingTime: UnwrapRef diff --git a/components.d.ts b/components.d.ts index 889808e..b59e3dc 100644 --- a/components.d.ts +++ b/components.d.ts @@ -39,6 +39,9 @@ declare module 'vue' { IonLabel: typeof import('@ionic/vue')['IonLabel'] IonList: typeof import('@ionic/vue')['IonList'] IonListHeader: typeof import('@ionic/vue')['IonListHeader'] + IonMenu: typeof import('@ionic/vue')['IonMenu'] + IonMenuButton: typeof import('@ionic/vue')['IonMenuButton'] + IonMenuToggle: typeof import('@ionic/vue')['IonMenuToggle'] IonModal: typeof import('@ionic/vue')['IonModal'] IonNote: typeof import('@ionic/vue')['IonNote'] IonPage: typeof import('@ionic/vue')['IonPage'] @@ -97,6 +100,9 @@ declare global { const IonLabel: typeof import('@ionic/vue')['IonLabel'] const IonList: typeof import('@ionic/vue')['IonList'] const IonListHeader: typeof import('@ionic/vue')['IonListHeader'] + const IonMenu: typeof import('@ionic/vue')['IonMenu'] + const IonMenuButton: typeof import('@ionic/vue')['IonMenuButton'] + const IonMenuToggle: typeof import('@ionic/vue')['IonMenuToggle'] const IonModal: typeof import('@ionic/vue')['IonModal'] const IonNote: typeof import('@ionic/vue')['IonNote'] const IonPage: typeof import('@ionic/vue')['IonPage'] diff --git a/package.json b/package.json index c08e56d..b82fc8b 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "@elysiajs/eden": "^1.4.5", "@ionic/vue": "^8.7.11", "@ionic/vue-router": "^8.7.11", - "@riwa/api-types": "http://192.168.1.27:9527/api/riwa-api-types-0.0.64.tgz", + "@riwa/api-types": "http://192.168.1.27:9527/api/riwa-api-types-0.0.67.tgz", "@tailwindcss/vite": "^4.1.18", "@vee-validate/yup": "^4.15.1", "@vueuse/core": "^14.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1f9e665..de29a6f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -57,8 +57,8 @@ importers: specifier: ^8.7.11 version: 8.7.11(@stencil/core@4.39.0)(vue-router@4.6.3(vue@3.5.25(typescript@5.9.3)))(vue@3.5.25(typescript@5.9.3)) '@riwa/api-types': - specifier: http://192.168.1.27:9527/api/riwa-api-types-0.0.64.tgz - version: http://192.168.1.27:9527/api/riwa-api-types-0.0.64.tgz(@elysiajs/eden@1.4.5(elysia@1.4.18(@sinclair/typebox@0.34.41)(exact-mirror@0.2.5(@sinclair/typebox@0.34.41))(file-type@21.1.1)(openapi-types@12.1.3)(typescript@5.9.3))) + specifier: http://192.168.1.27:9527/api/riwa-api-types-0.0.67.tgz + version: http://192.168.1.27:9527/api/riwa-api-types-0.0.67.tgz(@elysiajs/eden@1.4.5(elysia@1.4.18(@sinclair/typebox@0.34.41)(exact-mirror@0.2.5(@sinclair/typebox@0.34.41))(file-type@21.1.1)(openapi-types@12.1.3)(typescript@5.9.3))) '@tailwindcss/vite': specifier: ^4.1.18 version: 4.1.18(vite@7.2.7(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2)) @@ -1401,9 +1401,9 @@ packages: resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - '@riwa/api-types@http://192.168.1.27:9527/api/riwa-api-types-0.0.64.tgz': - resolution: {tarball: http://192.168.1.27:9527/api/riwa-api-types-0.0.64.tgz} - version: 0.0.64 + '@riwa/api-types@http://192.168.1.27:9527/api/riwa-api-types-0.0.67.tgz': + resolution: {tarball: http://192.168.1.27:9527/api/riwa-api-types-0.0.67.tgz} + version: 0.0.67 peerDependencies: '@elysiajs/eden': ^1.4.5 @@ -6644,7 +6644,7 @@ snapshots: '@pkgr/core@0.2.9': {} - '@riwa/api-types@http://192.168.1.27:9527/api/riwa-api-types-0.0.64.tgz(@elysiajs/eden@1.4.5(elysia@1.4.18(@sinclair/typebox@0.34.41)(exact-mirror@0.2.5(@sinclair/typebox@0.34.41))(file-type@21.1.1)(openapi-types@12.1.3)(typescript@5.9.3)))': + '@riwa/api-types@http://192.168.1.27:9527/api/riwa-api-types-0.0.67.tgz(@elysiajs/eden@1.4.5(elysia@1.4.18(@sinclair/typebox@0.34.41)(exact-mirror@0.2.5(@sinclair/typebox@0.34.41))(file-type@21.1.1)(openapi-types@12.1.3)(typescript@5.9.3)))': dependencies: '@elysiajs/eden': 1.4.5(elysia@1.4.18(@sinclair/typebox@0.34.41)(exact-mirror@0.2.5(@sinclair/typebox@0.34.41))(file-type@21.1.1)(openapi-types@12.1.3)(typescript@5.9.3)) diff --git a/src/composables/useTradingView.ts b/src/composables/useTradingView.ts index 7f11135..32759de 100644 --- a/src/composables/useTradingView.ts +++ b/src/composables/useTradingView.ts @@ -1,12 +1,12 @@ import type { Awaitable } from "@vueuse/core"; -import type { ChartOptions, DeepPartial, IChartApi, ISeriesApi, LayoutOptions, OhlcData, SeriesType } from "lightweight-charts"; +import type { ChartOptions, DeepPartial, IChartApi, ISeriesApi, LayoutOptions, OhlcData, SeriesDataItemTypeMap, SeriesType } from "lightweight-charts"; import type { ThemeMode } from "./useTheme"; import { AreaSeries, BarSeries, BaselineSeries, CandlestickSeries, ColorType, createChart, HistogramSeries, LineSeries } from "lightweight-charts"; import { mergeWith } from "lodash-es"; export type Series = "Area" | "Bar" | "Baseline" | "Candlestick" | "Histogram" | "Line"; -export type TData = OhlcData; +export type TData = SeriesDataItemTypeMap[SeriesType]; export type WeightChartOptions = DeepPartial; diff --git a/src/locales/en-US.json b/src/locales/en-US.json index 8e92fca..c88f1a4 100644 --- a/src/locales/en-US.json +++ b/src/locales/en-US.json @@ -241,6 +241,44 @@ "revenueDetails": "Revenue Details" } }, + "income": { + "title": "Total Revenue", + "overview": { + "totalRevenue": "Total Revenue", + "yesterdayRevenue": "Yesterday's Revenue", + "monthRevenue": "This Month", + "pendingRevenue": "Pending Revenue" + }, + "trend": { + "title": "Revenue Trend", + "last7Days": "Last 7 Days", + "last30Days": "Last 30 Days", + "last90Days": "Last 90 Days" + }, + "sources": { + "title": "Revenue Sources", + "dividend": "Dividend", + "appreciation": "Appreciation", + "trade": "Trading" + }, + "records": { + "title": "Revenue Records", + "all": "All", + "recent": "Recent Records", + "viewAll": "View All", + "assetName": "Asset Name", + "type": "Type", + "amount": "Amount", + "date": "Date", + "status": "Status", + "noData": "No revenue records" + }, + "status": { + "completed": "Completed", + "pending": "Pending", + "processing": "Processing" + } + }, "myIssues": { "title": "My Issuance Applications", "search": "Search", @@ -317,7 +355,10 @@ "common": { "failedSendCode": "Failed to send verification code", "uploadFile": "Upload File", - "files": "files" + "files": "files", + "today": "Today", + "yesterday": "Yesterday", + "items": "items" }, "auth": { "login": { diff --git a/src/locales/zh-CN.json b/src/locales/zh-CN.json index 17273a7..1fa6f8c 100644 --- a/src/locales/zh-CN.json +++ b/src/locales/zh-CN.json @@ -247,6 +247,44 @@ "revenueDetails": "收益明细" } }, + "income": { + "title": "总收益", + "overview": { + "totalRevenue": "累计总收益", + "yesterdayRevenue": "昨日收益", + "monthRevenue": "本月收益", + "pendingRevenue": "待确认收益" + }, + "trend": { + "title": "收益趋势", + "last7Days": "最近7天", + "last30Days": "最近30天", + "last90Days": "最近90天" + }, + "sources": { + "title": "收益来源", + "dividend": "分红收益", + "appreciation": "资产增值", + "trade": "交易收益" + }, + "records": { + "title": "收益明细", + "all": "全部", + "recent": "最近记录", + "viewAll": "查看全部", + "assetName": "资产名称", + "type": "类型", + "amount": "金额", + "date": "日期", + "status": "状态", + "noData": "暂无收益记录" + }, + "status": { + "completed": "已完成", + "pending": "待确认", + "processing": "处理中" + } + }, "myIssues": { "title": "我的发行申请", "search": "搜索", @@ -323,7 +361,10 @@ "common": { "failedSendCode": "发送验证码失败", "uploadFile": "上传文件", - "files": "个文件" + "files": "个文件", + "today": "今天", + "yesterday": "昨天", + "items": "项" }, "auth": { "login": { diff --git a/src/mocks/data/income.ts b/src/mocks/data/income.ts new file mode 100644 index 0000000..c399f2a --- /dev/null +++ b/src/mocks/data/income.ts @@ -0,0 +1,129 @@ +import { useMocks } from "../index"; + +const mocks = useMocks(); + +// 总收益 Mock 数据 +mocks.register("income/total", () => { + return { + // 总收益概览 + overview: { + totalRevenue: 125680.50, // 累计总收益 + yesterdayRevenue: 1256.80, // 昨日收益 + monthRevenue: 38450.20, // 本月收益 + pendingRevenue: 5420.30, // 待确认收益 + currency: "USD", + }, + // 收益趋势数据(最近7天) + trend: [ + { date: "2025-12-21", revenue: 1100.50 }, + { date: "2025-12-22", revenue: 1300.80 }, + { date: "2025-12-23", revenue: 1450.20 }, + { date: "2025-12-24", revenue: 1200.60 }, + { date: "2025-12-25", revenue: 1350.90 }, + { date: "2025-12-26", revenue: 1380.40 }, + { date: "2025-12-27", revenue: 1256.80 }, + ], + // 收益来源分布 + sources: [ + { + type: "dividend", // 分红收益 + name: "分红收益", + amount: 85420.30, + percentage: 67.96, + count: 125, + }, + { + type: "appreciation", // 资产增值 + name: "资产增值", + amount: 32150.20, + percentage: 25.59, + count: 45, + }, + { + type: "trade", // 交易收益 + name: "交易收益", + amount: 8110.00, + percentage: 6.45, + count: 89, + }, + ], + // 最近收益明细 + recentRecords: [ + { + id: "1", + type: "dividend", + typeName: "分红收益", + assetName: "纽约曼哈顿中心公寓", + assetCode: "NYC-001", + amount: 520.50, + currency: "USD", + date: "2025-12-27 10:30:00", + status: "completed", // completed, pending, processing + }, + { + id: "2", + type: "appreciation", + typeName: "资产增值", + assetName: "旧金山商业地产", + assetCode: "SF-002", + amount: 320.80, + currency: "USD", + date: "2025-12-27 09:15:00", + status: "completed", + }, + { + id: "3", + type: "trade", + typeName: "交易收益", + assetName: "洛杉矶住宅楼", + assetCode: "LA-003", + amount: 215.50, + currency: "USD", + date: "2025-12-26 16:45:00", + status: "completed", + }, + { + id: "4", + type: "dividend", + typeName: "分红收益", + assetName: "迈阿密海景别墅", + assetCode: "MIA-004", + amount: 680.20, + currency: "USD", + date: "2025-12-26 14:20:00", + status: "pending", + }, + { + id: "5", + type: "appreciation", + typeName: "资产增值", + assetName: "芝加哥写字楼", + assetCode: "CHI-005", + amount: 450.60, + currency: "USD", + date: "2025-12-25 11:30:00", + status: "completed", + }, + ], + }; +}); + +// 收益明细列表(支持筛选) +mocks.register("income/records", () => { + return { + list: Array.from({ length: 20 }, (_, index) => ({ + id: String(index + 1), + type: ["dividend", "appreciation", "trade"][index % 3], + typeName: ["分红收益", "资产增值", "交易收益"][index % 3], + assetName: `资产名称 ${index + 1}`, + assetCode: `ASSET-${String(index + 1).padStart(3, "0")}`, + amount: (Math.random() * 1000 + 100).toFixed(2), + currency: "USD", + date: new Date(Date.now() - index * 86400000).toISOString(), + status: ["completed", "pending", "processing"][index % 3], + })), + total: 100, + page: 1, + pageSize: 20, + }; +}); diff --git a/src/mocks/data/index.ts b/src/mocks/data/index.ts index 1acb4fa..ebbe0d7 100644 --- a/src/mocks/data/index.ts +++ b/src/mocks/data/index.ts @@ -1 +1,2 @@ import "./notify"; +import "./income"; diff --git a/src/router/index.ts b/src/router/index.ts index 8693c33..96e7e06 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -59,6 +59,11 @@ const routes: Array = [ component: () => import("@/views/withdraw/index.vue"), meta: { requiresAuth: true }, }, + { + path: "/income/total", + component: () => import("@/views/income/total/index.vue"), + meta: { requiresAuth: true }, + }, { path: "/wallet/bill", component: () => import("@/views/wallet/bill.vue"), @@ -135,12 +140,12 @@ const routes: Array = [ component: () => import("@/views/trade-settings/my-subscribe/index.vue"), meta: { requiresAuth: true }, }, - // { - // path: ":id", - // props: true, - // component: () => import("@/views/trade-settings/my-subscribe/detail.vue"), - // meta: { requiresAuth: true }, - // }, + { + path: ":id", + props: true, + component: () => import("@/views/trade-settings/my-subscribe/detail.vue"), + meta: { requiresAuth: true }, + }, ], }, { diff --git a/src/theme/variables.css b/src/theme/variables.css index 930fe55..f28dc0d 100644 --- a/src/theme/variables.css +++ b/src/theme/variables.css @@ -23,11 +23,11 @@ html.ion-palette-light { --ion-color-tertiary-tint: #757575; --ion-color-success: #2c810e; - --ion-color-success-rgb: 44,129,14; - --ion-color-success-contrast: #ffffff; - --ion-color-success-contrast-rgb: 255,255,255; - --ion-color-success-shade: #27720c; - --ion-color-success-tint: #418e26; + --ion-color-success-rgb: 44, 129, 14; + --ion-color-success-contrast: #ffffff; + --ion-color-success-contrast-rgb: 255, 255, 255; + --ion-color-success-shade: #27720c; + --ion-color-success-tint: #418e26; --ion-color-warning: #ffc409; --ion-color-warning-rgb: 255, 196, 9; @@ -143,11 +143,11 @@ html.ion-palette-dark { --ion-color-tertiary-tint: #dadada; --ion-color-success: #2c810e; - --ion-color-success-rgb: 44,129,14; - --ion-color-success-contrast: #ffffff; - --ion-color-success-contrast-rgb: 255,255,255; - --ion-color-success-shade: #27720c; - --ion-color-success-tint: #418e26; + --ion-color-success-rgb: 44, 129, 14; + --ion-color-success-contrast: #ffffff; + --ion-color-success-contrast-rgb: 255, 255, 255; + --ion-color-success-shade: #27720c; + --ion-color-success-tint: #418e26; --ion-color-warning: #ffc409; --ion-color-warning-rgb: 255, 196, 9; diff --git a/src/utils/helper.ts b/src/utils/helper.ts index 383361f..712a2c6 100644 --- a/src/utils/helper.ts +++ b/src/utils/helper.ts @@ -15,7 +15,7 @@ export function timeToLocal(originalTime: number) { return Date.UTC(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds()) / 1000; } -export function formatAmount(amount: MaybeRefOrGetter): string { +export function formatAmountWithUnit(amount: MaybeRefOrGetter): string { const value = toValue(amount); if (Number.isNaN(Number(value))) { return "0"; @@ -24,21 +24,37 @@ export function formatAmount(amount: MaybeRefOrGetter= 100000000) { - const yi = (num / 100000000).toFixed(1); + if (num >= 1e8) { + const yi = (num / 1e8).toFixed(1); return yi.endsWith(".0") ? `${Number.parseInt(yi)}亿` : `${yi}亿`; } // 1万到1亿,显示为xx万 - if (num >= 10000) { - const wan = (num / 10000).toFixed(1); + if (num >= 1e4) { + const wan = (num / 1e4).toFixed(1); return wan.endsWith(".0") ? `${Number.parseInt(wan)}万` : `${wan}万`; } return String(num); } + +export function formatAmountWithSplit(amount: MaybeRefOrGetter): string { + const value = toValue(amount); + if (Number.isNaN(Number(value))) { + return "0"; + } + + const num = Number(value); + + return Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }).format(num); +} diff --git a/src/views/income/total/components/overview.vue b/src/views/income/total/components/overview.vue new file mode 100644 index 0000000..3afb0ca --- /dev/null +++ b/src/views/income/total/components/overview.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/src/views/income/total/components/trend.vue b/src/views/income/total/components/trend.vue new file mode 100644 index 0000000..2ba4e05 --- /dev/null +++ b/src/views/income/total/components/trend.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/src/views/income/total/index.vue b/src/views/income/total/index.vue new file mode 100644 index 0000000..206ec6c --- /dev/null +++ b/src/views/income/total/index.vue @@ -0,0 +1,53 @@ + + + diff --git a/src/views/trade-rwa/components/base.vue b/src/views/trade-rwa/components/base.vue index d611abf..745818c 100644 --- a/src/views/trade-rwa/components/base.vue +++ b/src/views/trade-rwa/components/base.vue @@ -33,7 +33,7 @@ const { t } = useI18n();
{{ t('market.tradeRwa.fields.valuation') }}
-
${{ formatAmount(data?.product.estimatedValue) }}
+
${{ formatAmountWithUnit(data?.product.estimatedValue) }}
@@ -41,7 +41,7 @@ const { t } = useI18n();
{{ t('market.tradeRwa.fields.unitPrice') }}
-
${{ formatAmount(data?.unitPrice) }}
+
${{ formatAmountWithUnit(data?.unitPrice) }}
diff --git a/src/views/trade-settings/bank-management/index.vue b/src/views/trade-settings/bank-management/index.vue index d1ee93e..6dfbe4f 100644 --- a/src/views/trade-settings/bank-management/index.vue +++ b/src/views/trade-settings/bank-management/index.vue @@ -126,7 +126,7 @@ onUpdated(() => {

diff --git a/src/views/trade-settings/my-issues/components/base.vue b/src/views/trade-settings/my-issues/components/base.vue index 8d3b464..a422c57 100644 --- a/src/views/trade-settings/my-issues/components/base.vue +++ b/src/views/trade-settings/my-issues/components/base.vue @@ -33,7 +33,7 @@ const { t } = useI18n();
{{ t('market.tradeRwa.fields.valuation') }}
-
${{ formatAmount(data?.estimatedValue) }}
+
${{ formatAmountWithUnit(data?.estimatedValue) }}
diff --git a/src/views/trade-settings/my-subscribe/detail.vue b/src/views/trade-settings/my-subscribe/detail.vue index 38642af..8f9b6d1 100644 --- a/src/views/trade-settings/my-subscribe/detail.vue +++ b/src/views/trade-settings/my-subscribe/detail.vue @@ -28,16 +28,13 @@ async function handleSubscribe(val: number) { await toast.present(); model.value?.$el.dismiss(); } -function gotoEdit() { - router.push({ name: "trade-rwa-edit" }); -}