feat: 添加收益明细类型和相关数据结构,优化收益记录展示逻辑
This commit is contained in:
@@ -90,6 +90,10 @@ export type NewData = Treaty.Data<typeof client.api.news.get>["data"][number];
|
|||||||
|
|
||||||
export type EaringsSummaryData = Treaty.Data<typeof client.api.earnings.summary.post>;
|
export type EaringsSummaryData = Treaty.Data<typeof client.api.earnings.summary.post>;
|
||||||
|
|
||||||
|
export type EaringsDetailData = Treaty.Data<typeof client.api.earnings.details.post>["data"][number];
|
||||||
|
|
||||||
|
export type EaringsDetailBody = TreatyBody<typeof client.api.earnings.details.post>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 应用版本信息
|
* 应用版本信息
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -247,7 +247,18 @@
|
|||||||
"totalRevenue": "Total Revenue",
|
"totalRevenue": "Total Revenue",
|
||||||
"monthlyRevenue": "Monthly Revenue",
|
"monthlyRevenue": "Monthly Revenue",
|
||||||
"pendingRevenue": "Pending Revenue",
|
"pendingRevenue": "Pending Revenue",
|
||||||
"revenueDetails": "Revenue Details"
|
"revenueDetails": "Revenue Details",
|
||||||
|
"types": {
|
||||||
|
"all": "All",
|
||||||
|
"dividend": "Dividend",
|
||||||
|
"staking": "Staking",
|
||||||
|
"new_user_reward": "New User Reward",
|
||||||
|
"referral_reward": "Referral Reward",
|
||||||
|
"trading_fee_rebate": "Trading Fee Rebate",
|
||||||
|
"deposit_rebate": "Deposit Rebate",
|
||||||
|
"deposit_reward": "Deposit Reward",
|
||||||
|
"other": "Other"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"income": {
|
"income": {
|
||||||
|
|||||||
@@ -253,7 +253,18 @@
|
|||||||
"totalRevenue": "总收益",
|
"totalRevenue": "总收益",
|
||||||
"monthlyRevenue": "月度收益",
|
"monthlyRevenue": "月度收益",
|
||||||
"pendingRevenue": "待确认收益",
|
"pendingRevenue": "待确认收益",
|
||||||
"revenueDetails": "收益明细"
|
"revenueDetails": "收益明细",
|
||||||
|
"types": {
|
||||||
|
"all": "全部",
|
||||||
|
"dividend": "分红收益",
|
||||||
|
"staking": "质押收益",
|
||||||
|
"new_user_reward": "新用户奖励",
|
||||||
|
"referral_reward": "推荐奖励",
|
||||||
|
"trading_fee_rebate": "交易返佣",
|
||||||
|
"deposit_rebate": "存款返佣",
|
||||||
|
"deposit_reward": "存款奖励",
|
||||||
|
"other": "其他"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"income": {
|
"income": {
|
||||||
|
|||||||
@@ -337,7 +337,17 @@ const stickyStyle = computed(() => {
|
|||||||
|
|
||||||
/* 导航包装器 */
|
/* 导航包装器 */
|
||||||
.ui-tabs__nav-wrapper {
|
.ui-tabs__nav-wrapper {
|
||||||
@apply relative w-fit mb-4;
|
@apply relative w-full mb-4 overflow-x-auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 隐藏滚动条但保持滚动功能 */
|
||||||
|
.ui-tabs__nav-wrapper::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-tabs__nav-wrapper {
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
scrollbar-width: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sticky 布局 */
|
/* Sticky 布局 */
|
||||||
@@ -365,7 +375,9 @@ const stickyStyle = computed(() => {
|
|||||||
|
|
||||||
/* 导航容器 */
|
/* 导航容器 */
|
||||||
.ui-tabs__nav {
|
.ui-tabs__nav {
|
||||||
@apply relative flex;
|
@apply relative inline-flex;
|
||||||
|
min-width: min-content;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui-tabs__nav--bar,
|
.ui-tabs__nav--bar,
|
||||||
@@ -395,6 +407,7 @@ const stickyStyle = computed(() => {
|
|||||||
.ui-tab {
|
.ui-tab {
|
||||||
@apply relative flex items-center justify-between cursor-pointer transition-all duration-200;
|
@apply relative flex items-center justify-between cursor-pointer transition-all duration-200;
|
||||||
color: var(--ion-color-medium, #6b7280);
|
color: var(--ion-color-medium, #6b7280);
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui-tab:hover:not(.ui-tab--disabled):not(.ui-tab--active) {
|
.ui-tab:hover:not(.ui-tab--disabled):not(.ui-tab--active) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import CryptocurrencyColorNuls from "~icons/cryptocurrency-color/nuls";
|
|||||||
import { client, safeClient } from "@/api";
|
import { client, safeClient } from "@/api";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
const { vibrate } = useHaptics();
|
||||||
|
|
||||||
const holdingsData = ref<HoldingItem[]>([]);
|
const holdingsData = ref<HoldingItem[]>([]);
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
@@ -18,9 +19,11 @@ async function fetchHoldings() {
|
|||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleRefresh(event: RefresherCustomEvent) {
|
function handleRefresh(event: RefresherCustomEvent) {
|
||||||
await fetchHoldings();
|
vibrate();
|
||||||
|
fetchHoldings().finally(() => {
|
||||||
event.target.complete();
|
event.target.complete();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStatusColor(status: string) {
|
function getStatusColor(status: string) {
|
||||||
|
|||||||
10
src/views/revenue/records/enum.ts
Normal file
10
src/views/revenue/records/enum.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
export enum RevenueRecordTypeEnum {
|
||||||
|
dividend = "dividend",
|
||||||
|
staking = "staking",
|
||||||
|
new_user_reward = "new_user_reward",
|
||||||
|
referral_reward = "referral_reward",
|
||||||
|
trading_fee_rebate = "trading_fee_rebate",
|
||||||
|
deposit_rebate = "deposit_rebate",
|
||||||
|
deposit_reward = "deposit_reward",
|
||||||
|
other = "other",
|
||||||
|
}
|
||||||
@@ -1,172 +1,78 @@
|
|||||||
<script lang='ts' setup>
|
<script lang='ts' setup>
|
||||||
import type { RefresherCustomEvent } from "@ionic/vue";
|
import type { InfiniteScrollCustomEvent, RefresherCustomEvent } from "@ionic/vue";
|
||||||
|
import type { EaringsDetailBody, EaringsDetailData } from "@/api/types";
|
||||||
|
import { client, safeClient } from "@/api";
|
||||||
|
import { RevenueRecordTypeEnum } from "./enum";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { vibrate } = useHaptics();
|
const { vibrate } = useHaptics();
|
||||||
|
|
||||||
const loading = ref(true);
|
const isLoading = ref(true);
|
||||||
const selectedType = ref("all");
|
const isFinished = ref(false);
|
||||||
const searchQuery = ref("");
|
const records = ref<Array<EaringsDetailData>>([]);
|
||||||
|
const [query] = useResetRef<EaringsDetailBody>({
|
||||||
// 收益类型选项
|
limit: 20,
|
||||||
const typeOptions = [
|
offset: 0,
|
||||||
{ value: "all", label: "全部" },
|
type: undefined,
|
||||||
{ value: "dividend", label: "分红收益" },
|
|
||||||
{ value: "appreciation", label: "资产增值" },
|
|
||||||
{ value: "trade", label: "交易收益" },
|
|
||||||
];
|
|
||||||
|
|
||||||
// 收益记录数据
|
|
||||||
const allRecords = ref([
|
|
||||||
{
|
|
||||||
id: "1",
|
|
||||||
type: "dividend",
|
|
||||||
typeName: "分红收益",
|
|
||||||
assetName: "纽约曼哈顿中心公寓",
|
|
||||||
assetCode: "NYC-001",
|
|
||||||
amount: 520.50,
|
|
||||||
date: "2025-12-27 10:30",
|
|
||||||
status: "completed",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "2",
|
|
||||||
type: "appreciation",
|
|
||||||
typeName: "资产增值",
|
|
||||||
assetName: "旧金山商业地产",
|
|
||||||
assetCode: "SF-002",
|
|
||||||
amount: 320.80,
|
|
||||||
date: "2025-12-27 09:15",
|
|
||||||
status: "completed",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "3",
|
|
||||||
type: "trade",
|
|
||||||
typeName: "交易收益",
|
|
||||||
assetName: "洛杉矶住宅楼",
|
|
||||||
assetCode: "LA-003",
|
|
||||||
amount: 215.50,
|
|
||||||
date: "2025-12-26 16:45",
|
|
||||||
status: "completed",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "4",
|
|
||||||
type: "dividend",
|
|
||||||
typeName: "分红收益",
|
|
||||||
assetName: "迈阿密海景别墅",
|
|
||||||
assetCode: "MIA-004",
|
|
||||||
amount: 680.20,
|
|
||||||
date: "2025-12-26 14:20",
|
|
||||||
status: "completed",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "5",
|
|
||||||
type: "appreciation",
|
|
||||||
typeName: "资产增值",
|
|
||||||
assetName: "芝加哥写字楼",
|
|
||||||
assetCode: "CHI-005",
|
|
||||||
amount: 450.60,
|
|
||||||
date: "2025-12-25 11:30",
|
|
||||||
status: "completed",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "6",
|
|
||||||
type: "trade",
|
|
||||||
typeName: "交易收益",
|
|
||||||
assetName: "波士顿商业中心",
|
|
||||||
assetCode: "BOS-006",
|
|
||||||
amount: 890.30,
|
|
||||||
date: "2025-12-24 15:20",
|
|
||||||
status: "completed",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "7",
|
|
||||||
type: "dividend",
|
|
||||||
typeName: "分红收益",
|
|
||||||
assetName: "西雅图科技园区",
|
|
||||||
assetCode: "SEA-007",
|
|
||||||
amount: 720.40,
|
|
||||||
date: "2025-12-23 13:10",
|
|
||||||
status: "completed",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "8",
|
|
||||||
type: "appreciation",
|
|
||||||
typeName: "资产增值",
|
|
||||||
assetName: "达拉斯住宅区",
|
|
||||||
assetCode: "DAL-008",
|
|
||||||
amount: 380.90,
|
|
||||||
date: "2025-12-22 10:50",
|
|
||||||
status: "completed",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "9",
|
|
||||||
type: "trade",
|
|
||||||
typeName: "交易收益",
|
|
||||||
assetName: "奥斯汀高端公寓",
|
|
||||||
assetCode: "AUS-009",
|
|
||||||
amount: 1250.20,
|
|
||||||
date: "2025-12-21 16:30",
|
|
||||||
status: "completed",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "10",
|
|
||||||
type: "dividend",
|
|
||||||
typeName: "分红收益",
|
|
||||||
assetName: "休斯顿商业综合体",
|
|
||||||
assetCode: "HOU-010",
|
|
||||||
amount: 580.70,
|
|
||||||
date: "2025-12-20 14:15",
|
|
||||||
status: "completed",
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 筛选后的记录
|
|
||||||
const filteredRecords = computed(() => {
|
|
||||||
let records = allRecords.value;
|
|
||||||
|
|
||||||
// 按类型筛选
|
|
||||||
if (selectedType.value !== "all") {
|
|
||||||
records = records.filter(item => item.type === selectedType.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 按搜索关键词筛选
|
|
||||||
if (searchQuery.value.trim()) {
|
|
||||||
const query = searchQuery.value.toLowerCase();
|
|
||||||
records = records.filter(item =>
|
|
||||||
item.assetName.toLowerCase().includes(query)
|
|
||||||
|| item.assetCode.toLowerCase().includes(query)
|
|
||||||
|| item.typeName.toLowerCase().includes(query),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return records;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
async function loadData() {
|
const typeOptions = computed(() => [
|
||||||
loading.value = true;
|
{ value: undefined, label: t("asset.revenue.types.all") },
|
||||||
useTimeoutFn(() => {
|
{ value: RevenueRecordTypeEnum.dividend, label: t("asset.revenue.types.dividend") },
|
||||||
loading.value = false;
|
{ value: RevenueRecordTypeEnum.staking, label: t("asset.revenue.types.staking") },
|
||||||
}, 800);
|
{ value: RevenueRecordTypeEnum.new_user_reward, label: t("asset.revenue.types.new_user_reward") },
|
||||||
|
{ value: RevenueRecordTypeEnum.referral_reward, label: t("asset.revenue.types.referral_reward") },
|
||||||
|
{ value: RevenueRecordTypeEnum.trading_fee_rebate, label: t("asset.revenue.types.trading_fee_rebate") },
|
||||||
|
{ value: RevenueRecordTypeEnum.deposit_rebate, label: t("asset.revenue.types.deposit_rebate") },
|
||||||
|
{ value: RevenueRecordTypeEnum.deposit_reward, label: t("asset.revenue.types.deposit_reward") },
|
||||||
|
{ value: RevenueRecordTypeEnum.other, label: t("asset.revenue.types.other") },
|
||||||
|
]);
|
||||||
|
|
||||||
|
function resetData() {
|
||||||
|
query.value.offset = 0;
|
||||||
|
records.value = [];
|
||||||
|
isFinished.value = false;
|
||||||
|
}
|
||||||
|
async function fetchData() {
|
||||||
|
isLoading.value = true;
|
||||||
|
const { data } = await safeClient(client.api.earnings.details.post(query.value));
|
||||||
|
records.value = data.value?.data || [];
|
||||||
|
isFinished.value = (data.value?.data.length || 0) < query.value.limit!;
|
||||||
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleRefresh(event: RefresherCustomEvent) {
|
async function handleRefresh(event: RefresherCustomEvent) {
|
||||||
vibrate();
|
vibrate();
|
||||||
useTimeoutFn(() => {
|
resetData();
|
||||||
|
fetchData().finally(() => {
|
||||||
event.target.complete();
|
event.target.complete();
|
||||||
}, 800);
|
});
|
||||||
|
}
|
||||||
|
async function handleInfinite(event: InfiniteScrollCustomEvent) {
|
||||||
|
if (isFinished.value) {
|
||||||
|
event.target.complete();
|
||||||
|
event.target.disabled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
query.value.offset! += query.value.limit!;
|
||||||
|
await fetchData();
|
||||||
|
setTimeout(() => {
|
||||||
|
event.target.complete();
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
function changeType(val) {
|
||||||
|
resetData();
|
||||||
|
if (val === "all") {
|
||||||
|
query.value.type = undefined;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
query.value.type = val as RevenueRecordTypeEnum;
|
||||||
|
}
|
||||||
|
fetchData();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTypeColor(type: string) {
|
onBeforeMount(() => {
|
||||||
const colors: Record<string, string> = {
|
fetchData();
|
||||||
dividend: "success",
|
|
||||||
appreciation: "primary",
|
|
||||||
trade: "warning",
|
|
||||||
};
|
|
||||||
return colors[type] || "medium";
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
loadData();
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -180,25 +86,29 @@ onMounted(() => {
|
|||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content :fullscreen="true">
|
<ion-content :fullscreen="true">
|
||||||
<ion-searchbar v-model="searchQuery" :placeholder="t('myIssues.search')" />
|
|
||||||
|
|
||||||
<ion-refresher slot="fixed" @ion-refresh="handleRefresh($event)">
|
<ion-refresher slot="fixed" @ion-refresh="handleRefresh($event)">
|
||||||
<ion-refresher-content />
|
<ion-refresher-content />
|
||||||
</ion-refresher>
|
</ion-refresher>
|
||||||
|
|
||||||
<div>
|
<ui-tabs
|
||||||
<ui-tabs size="small" class="ion-padding-horizontal">
|
size="small"
|
||||||
|
class="ion-padding-horizontal py-2"
|
||||||
|
:model-value="query.type || 'all'"
|
||||||
|
@update:model-value="changeType"
|
||||||
|
>
|
||||||
<ui-tab-pane
|
<ui-tab-pane
|
||||||
v-for="option in typeOptions"
|
v-for="option in typeOptions"
|
||||||
:key="option.value"
|
:key="option.value"
|
||||||
:name="option.value"
|
:name="option.value || 'all'"
|
||||||
:title="option.label"
|
:title="option.label"
|
||||||
/>
|
/>
|
||||||
</ui-tabs>
|
</ui-tabs>
|
||||||
|
|
||||||
<ion-list>
|
<ui-empty v-if="!isLoading && records.length === 0" />
|
||||||
|
|
||||||
|
<ion-list v-else>
|
||||||
<ion-item
|
<ion-item
|
||||||
v-for="item in filteredRecords"
|
v-for="item in records"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
lines="full"
|
lines="full"
|
||||||
>
|
>
|
||||||
@@ -206,25 +116,22 @@ onMounted(() => {
|
|||||||
<div class="flex justify-between items-start">
|
<div class="flex justify-between items-start">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<div class="flex items-center gap-2 mb-2">
|
<div class="flex items-center gap-2 mb-2">
|
||||||
<ion-badge
|
<ion-badge class="text-xs px-2 py-0.5">
|
||||||
:color="getTypeColor(item.type)"
|
{{ item.type }}
|
||||||
class="text-xs px-2 py-0.5"
|
|
||||||
>
|
|
||||||
{{ item.typeName }}
|
|
||||||
</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">
|
||||||
已完成
|
已完成
|
||||||
</ion-badge>
|
</ion-badge>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-base font-semibold mb-1">
|
<div class="text-base font-semibold mb-1">
|
||||||
{{ item.assetName }}
|
{{ item.relatedAssetName }}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs text-text-400 mb-2">
|
<div class="text-xs text-text-400 mb-2">
|
||||||
{{ item.assetCode }}
|
{{ item.relatedAssetCode }}
|
||||||
</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>{{ item.date }}</span>
|
<span>{{ useDateFormat(item.createdAt, 'YYYY/MM/DD HH:mm') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-right ml-4">
|
<div class="text-right ml-4">
|
||||||
@@ -236,9 +143,19 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
</div>
|
|
||||||
|
<ion-infinite-scroll threshold="100px" @ion-infinite="handleInfinite">
|
||||||
|
<ion-infinite-scroll-content
|
||||||
|
loading-spinner="bubbles"
|
||||||
|
loading-text="加载中..."
|
||||||
|
/>
|
||||||
|
</ion-infinite-scroll>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
</ion-page>
|
</ion-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang='css' scoped></style>
|
<style lang='css' scoped>
|
||||||
|
:deep(.ui-tabs__nav-wrapper) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user