170 lines
5.9 KiB
Vue
170 lines
5.9 KiB
Vue
<script lang='ts' setup>
|
||
import type { Treaty } from "@elysiajs/eden";
|
||
import type { InfiniteScrollCustomEvent } from "@ionic/vue";
|
||
import type { TreatyQuery } from "@/api/types";
|
||
import { calendarOutline, cardOutline, timeOutline, trendingUpOutline } from "ionicons/icons";
|
||
import { client, safeClient } from "@/api";
|
||
|
||
type Product = Treaty.Data<typeof client.api.subscription.products.get>["data"][number];
|
||
type ProductQuery = TreatyQuery<typeof client.api.subscription.products.get>;
|
||
|
||
const [query] = useResetRef<ProductQuery>({
|
||
offset: 0,
|
||
limit: 10,
|
||
});
|
||
const data = ref<Product[]>([]);
|
||
const isFinished = ref(false);
|
||
|
||
async function fetchData() {
|
||
const { data: responseData } = await safeClient(client.api.subscription.products.get({ query: { ...query.value } }));
|
||
data.value.push(...(responseData.value?.data || []));
|
||
isFinished.value = responseData.value?.pagination.hasNextPage === false;
|
||
}
|
||
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 handleProductClick(product: any) {
|
||
console.log("查看产品:", product.name);
|
||
// TODO: 跳转到产品详情
|
||
}
|
||
|
||
function handleSubscribe(product: any, event: Event) {
|
||
event.stopPropagation();
|
||
console.log("申购产品:", product.name);
|
||
// TODO: 实现申购功能
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<ion-page>
|
||
<ion-content>
|
||
<img src="@/assets/images/product-banner.jpg" class="h-50 w-full object-cover" alt="服务页横幅">
|
||
|
||
<!-- 基金产品列表 -->
|
||
<section class="mb-5 -mt-5 ion-padding-horizontal">
|
||
<div class="flex justify-between items-center mb-4">
|
||
<div class="flex justify-between items-center mb-4">
|
||
<div class="flex items-center gap-2">
|
||
<img src="@/assets/images/icon.png" class="size-7">
|
||
<div class="text-xl font-bold text-[#1a1a1a]">
|
||
基金产品
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<empty v-if="data.length === 0" message="暂无基金产品信息" />
|
||
<div v-else class="flex flex-col gap-4">
|
||
<div
|
||
v-for="product in data"
|
||
:key="product.id"
|
||
class="bg-white rounded-2xl overflow-hidden shadow-sm cursor-pointer transition-all active:translate-y-0.5 active:shadow-sm flex"
|
||
@click="handleProductClick(product)"
|
||
>
|
||
<!-- 左侧缩略图 -->
|
||
<div class="relative w-28 h-36 shrink-0 overflow-hidden bg-linear-to-br from-[#f5f5f5] to-[#e8e8e8]">
|
||
<img :src="product.image" :alt="product.name" class="w-full h-full object-cover">
|
||
<!-- <div
|
||
class="fund-tag absolute top-2 left-2 text-white px-2 py-0.5 rounded-lg text-xs font-semibold shadow-lg"
|
||
>
|
||
{{ product.tag }}
|
||
</div> -->
|
||
</div>
|
||
|
||
<!-- 右侧信息 -->
|
||
<div class="flex-1 p-4 flex flex-col justify-between">
|
||
<div>
|
||
<h4 class="text-base font-bold text-[#1a1a1a] mb-1 leading-snug">
|
||
{{ product.name }}
|
||
</h4>
|
||
<div class="flex items-center gap-1 text-xs text-[#999] mb-2">
|
||
<ion-icon :icon="timeOutline" class="text-sm" />
|
||
<span>申购截止:{{ product.subscribeEndAt }}</span>
|
||
</div>
|
||
|
||
<!-- 产品信息 -->
|
||
<div class="grid grid-cols-3 gap-2 mb-2">
|
||
<div class="flex flex-col">
|
||
<span class="text-xs text-[#999]">当前价格</span>
|
||
<span class="text-lg font-bold text-[#c41e3a]">¥{{ product.price }}</span>
|
||
</div>
|
||
<div class="flex flex-col">
|
||
<span class="text-xs text-[#999]">到期收益</span>
|
||
<span class="text-lg font-bold text-[#52c41a] flex items-center gap-0.5">
|
||
<ion-icon :icon="trendingUpOutline" class="text-xs" />
|
||
{{ product.maturityYield }}
|
||
</span>
|
||
</div>
|
||
<div class="flex flex-col">
|
||
<span class="text-xs text-[#999]">产品周期</span>
|
||
<span class="text-lg font-bold text-[#1a1a1a] flex items-center gap-0.5">
|
||
<ion-icon :icon="calendarOutline" class="text-xs" />
|
||
{{ product.cycleDays }}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 申购按钮 -->
|
||
<div class="flex items-center gap-2 mt-2">
|
||
<ion-button
|
||
size="small"
|
||
class="subscribe-btn flex-1"
|
||
@click="handleSubscribe(product, $event)"
|
||
>
|
||
<div class="flex-center gap-2">
|
||
<ion-icon slot="start" :icon="cardOutline" />
|
||
<span>我要申购</span>
|
||
</div>
|
||
</ion-button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</ion-content>
|
||
</ion-page>
|
||
</template>
|
||
|
||
<style lang='css' scoped>
|
||
.header-toolbar {
|
||
--background: #c32120;
|
||
--color: #fff;
|
||
}
|
||
|
||
.header-bg {
|
||
background: linear-gradient(180deg, #c32120 0%, transparent 100%);
|
||
}
|
||
|
||
.fund-tag {
|
||
backdrop-filter: blur(4px);
|
||
}
|
||
|
||
.subscribe-btn {
|
||
--background: linear-gradient(135deg, #1778ac 0%, #265166 100%);
|
||
--background-activated: linear-gradient(135deg, #1778ac 0%, #265166 100%);
|
||
--border-radius: 12px;
|
||
--padding-start: 16px;
|
||
--padding-end: 16px;
|
||
--box-shadow: 0 2px 8px rgba(30, 124, 196, 0.3);
|
||
font-weight: 600;
|
||
font-size: 13px;
|
||
height: 32px;
|
||
text-transform: none;
|
||
}
|
||
|
||
.subscribe-btn::part(native) {
|
||
font-weight: 600;
|
||
}
|
||
</style>
|