新增:活动模块
This commit is contained in:
parent
07291abcf7
commit
0d523ee5b8
9315
package-lock.json
generated
9315
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -25,7 +25,7 @@ const currentRoute = computed(() => route.path)
|
|||||||
|
|
||||||
const showBottomNav = computed(() => {
|
const showBottomNav = computed(() => {
|
||||||
if (!display.smAndDown.value) return false
|
if (!display.smAndDown.value) return false
|
||||||
return currentRoute.value === '/' || currentRoute.value === '/search' || currentRoute.value === '/profile'
|
return currentRoute.value === '/' || currentRoute.value === '/search' || currentRoute.value === '/earn-activity' || currentRoute.value === '/profile'
|
||||||
})
|
})
|
||||||
const mineTargetPath = computed(() =>
|
const mineTargetPath = computed(() =>
|
||||||
userStore.isLoggedIn ? '/profile' : '/login',
|
userStore.isLoggedIn ? '/profile' : '/login',
|
||||||
@ -35,6 +35,7 @@ const bottomNavValue = computed({
|
|||||||
const p = currentRoute.value
|
const p = currentRoute.value
|
||||||
if (p.startsWith('/profile') || p === '/login') return '/profile'
|
if (p.startsWith('/profile') || p === '/login') return '/profile'
|
||||||
if (p.startsWith('/search')) return '/search'
|
if (p.startsWith('/search')) return '/search'
|
||||||
|
if (p.startsWith('/earn-activity')) return '/earn-activity'
|
||||||
return '/'
|
return '/'
|
||||||
},
|
},
|
||||||
set(v: string) {
|
set(v: string) {
|
||||||
@ -177,6 +178,10 @@ watch(
|
|||||||
<v-icon size="24">mdi-magnify</v-icon>
|
<v-icon size="24">mdi-magnify</v-icon>
|
||||||
<span>{{ t('nav.search') }}</span>
|
<span>{{ t('nav.search') }}</span>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
<v-btn value="/earn-activity" :ripple="false">
|
||||||
|
<v-icon size="24">mdi-lock-outline</v-icon>
|
||||||
|
<span>{{ t('nav.activity') }}</span>
|
||||||
|
</v-btn>
|
||||||
<v-btn value="/profile" :ripple="false">
|
<v-btn value="/profile" :ripple="false">
|
||||||
<v-icon size="24">mdi-account-outline</v-icon>
|
<v-icon size="24">mdi-account-outline</v-icon>
|
||||||
<span>{{ t('nav.mine') }}</span>
|
<span>{{ t('nav.mine') }}</span>
|
||||||
|
|||||||
26
src/api/earnActivity.ts
Normal file
26
src/api/earnActivity.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { get } from './request'
|
||||||
|
import type { ApiResponse } from './types'
|
||||||
|
|
||||||
|
/** 赚钱活动项(polymarket.PmEarnActivity) */
|
||||||
|
export interface PmEarnActivityItem {
|
||||||
|
ID: number
|
||||||
|
typeId: number | null
|
||||||
|
seconds: number | null
|
||||||
|
moreProfitRate: number | null
|
||||||
|
minAmount: number | null
|
||||||
|
[key: string]: unknown // 保留其他可能存在的后端字段
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetPmEarnActivityPublicResponse extends ApiResponse<PmEarnActivityItem[]> {
|
||||||
|
code: number
|
||||||
|
data?: PmEarnActivityItem[]
|
||||||
|
msg: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /pmEarnActivity/getPmEarnActivityPublic
|
||||||
|
* 获取公开赚钱活动配置列表,无需鉴权
|
||||||
|
*/
|
||||||
|
export async function getPmEarnActivityPublic(): Promise<GetPmEarnActivityPublicResponse> {
|
||||||
|
return get<GetPmEarnActivityPublicResponse>('/pmEarnActivity/getPmEarnActivityPublic')
|
||||||
|
}
|
||||||
@ -2,6 +2,7 @@
|
|||||||
"nav": {
|
"nav": {
|
||||||
"home": "Home",
|
"home": "Home",
|
||||||
"search": "Search",
|
"search": "Search",
|
||||||
|
"activity": "Activity",
|
||||||
"mine": "Mine"
|
"mine": "Mine"
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
@ -431,6 +432,19 @@
|
|||||||
"connectingWallet": "Connecting wallet…",
|
"connectingWallet": "Connecting wallet…",
|
||||||
"walletMismatch": "Switch your browser wallet to the same account you used to sign in."
|
"walletMismatch": "Switch your browser wallet to the same account you used to sign in."
|
||||||
},
|
},
|
||||||
|
"earnActivity": {
|
||||||
|
"title": "Earn Liquidity",
|
||||||
|
"subtitle": "Participate to earn extra returns",
|
||||||
|
"duration": "Duration",
|
||||||
|
"unlimited": "Unlimited",
|
||||||
|
"extraProfitRate": "Extra Profit Rate",
|
||||||
|
"notice": "This page is for viewing only. Please use API for locking operations.",
|
||||||
|
"tiersTitle": "Profit Tiers",
|
||||||
|
"minAmount": "Min Amount (USDC)",
|
||||||
|
"tierLevel": "Tier {n}",
|
||||||
|
"expectedProfit": "Expected Profit",
|
||||||
|
"days": "{n} Days"
|
||||||
|
},
|
||||||
"locale": {
|
"locale": {
|
||||||
"zh": "简体中文",
|
"zh": "简体中文",
|
||||||
"en": "English",
|
"en": "English",
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
"nav": {
|
"nav": {
|
||||||
"home": "ホーム",
|
"home": "ホーム",
|
||||||
"search": "検索",
|
"search": "検索",
|
||||||
|
"activity": "アクティビティ",
|
||||||
"mine": "マイ"
|
"mine": "マイ"
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
@ -431,6 +432,19 @@
|
|||||||
"connectingWallet": "ウォレットに接続しています…",
|
"connectingWallet": "ウォレットに接続しています…",
|
||||||
"walletMismatch": "ログイン時に使用したアカウントにブラウザのウォレットを切り替えてください。"
|
"walletMismatch": "ログイン時に使用したアカウントにブラウザのウォレットを切り替えてください。"
|
||||||
},
|
},
|
||||||
|
"earnActivity": {
|
||||||
|
"title": "流動性を稼いでロック",
|
||||||
|
"subtitle": "キャンペーンに参加して追加收益を獲得",
|
||||||
|
"duration": "期間",
|
||||||
|
"unlimited": "無制限",
|
||||||
|
"extraProfitRate": "追加利益率",
|
||||||
|
"notice": "このペンは表示のみを目的としています。ロック操作はAPIを通じて行ってください。",
|
||||||
|
"tiersTitle": "利益ティア",
|
||||||
|
"minAmount": "最小金額 (USDC)",
|
||||||
|
"tierLevel": "ティア {n}",
|
||||||
|
"expectedProfit": "予想利益",
|
||||||
|
"days": "{n} 日"
|
||||||
|
},
|
||||||
"locale": {
|
"locale": {
|
||||||
"zh": "简体中文",
|
"zh": "简体中文",
|
||||||
"en": "English",
|
"en": "English",
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
"nav": {
|
"nav": {
|
||||||
"home": "홈",
|
"home": "홈",
|
||||||
"search": "검색",
|
"search": "검색",
|
||||||
|
"activity": "활동",
|
||||||
"mine": "마이"
|
"mine": "마이"
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
@ -431,6 +432,19 @@
|
|||||||
"connectingWallet": "지갑 연결 중…",
|
"connectingWallet": "지갑 연결 중…",
|
||||||
"walletMismatch": "로그인에 사용한 계정과 동일한 계정으로 브라우저 지갑을 전환해 주세요."
|
"walletMismatch": "로그인에 사용한 계정과 동일한 계정으로 브라우저 지갑을 전환해 주세요."
|
||||||
},
|
},
|
||||||
|
"earnActivity": {
|
||||||
|
"title": "유동성 획득 잠금",
|
||||||
|
"subtitle": "이벤트에 참여하여 추가 수익 획득",
|
||||||
|
"duration": "기간",
|
||||||
|
"unlimited": "무제한",
|
||||||
|
"extraProfitRate": "추가 이익률",
|
||||||
|
"notice": "이 페이지는 조회 전용입니다. 잠금 작업은 API를 통해 진행해 주세요.",
|
||||||
|
"tiersTitle": "수익 티어",
|
||||||
|
"minAmount": "최소 금액 (USDC)",
|
||||||
|
"tierLevel": "티어 {n}",
|
||||||
|
"expectedProfit": "예상 수익",
|
||||||
|
"days": "{n}일"
|
||||||
|
},
|
||||||
"locale": {
|
"locale": {
|
||||||
"zh": "简体中文",
|
"zh": "简体中文",
|
||||||
"en": "English",
|
"en": "English",
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
"nav": {
|
"nav": {
|
||||||
"home": "首页",
|
"home": "首页",
|
||||||
"search": "搜索",
|
"search": "搜索",
|
||||||
|
"activity": "活动",
|
||||||
"mine": "我的"
|
"mine": "我的"
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
@ -443,6 +444,19 @@
|
|||||||
"connectingWallet": "正在连接钱包…",
|
"connectingWallet": "正在连接钱包…",
|
||||||
"walletMismatch": "请在浏览器钱包中切换到您登录时使用的账户。"
|
"walletMismatch": "请在浏览器钱包中切换到您登录时使用的账户。"
|
||||||
},
|
},
|
||||||
|
"earnActivity": {
|
||||||
|
"title": "锁仓赚取流动性",
|
||||||
|
"subtitle": "参与活动获得额外收益",
|
||||||
|
"duration": "活动时长",
|
||||||
|
"unlimited": "不限时",
|
||||||
|
"extraProfitRate": "额外利润比例",
|
||||||
|
"notice": "此页面仅供查看,锁仓操作请通过 API 进行。",
|
||||||
|
"tiersTitle": "收益挡位",
|
||||||
|
"minAmount": "最小金额 (USDC)",
|
||||||
|
"tierLevel": "挡位 {n}",
|
||||||
|
"expectedProfit": "预期利润",
|
||||||
|
"days": "{n} 天"
|
||||||
|
},
|
||||||
"locale": {
|
"locale": {
|
||||||
"zh": "简体中文",
|
"zh": "简体中文",
|
||||||
"en": "English",
|
"en": "English",
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
"nav": {
|
"nav": {
|
||||||
"home": "首頁",
|
"home": "首頁",
|
||||||
"search": "搜尋",
|
"search": "搜尋",
|
||||||
|
"activity": "活動",
|
||||||
"mine": "我的"
|
"mine": "我的"
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
@ -431,6 +432,19 @@
|
|||||||
"connectingWallet": "正在連接錢包…",
|
"connectingWallet": "正在連接錢包…",
|
||||||
"walletMismatch": "請在瀏覽器錢包中切換至您登入時使用的帳戶。"
|
"walletMismatch": "請在瀏覽器錢包中切換至您登入時使用的帳戶。"
|
||||||
},
|
},
|
||||||
|
"earnActivity": {
|
||||||
|
"title": "鎖倉賺取流動性",
|
||||||
|
"subtitle": "參與活動獲得額外收益",
|
||||||
|
"duration": "活動時長",
|
||||||
|
"unlimited": "不限時",
|
||||||
|
"extraProfitRate": "額外利潤比例",
|
||||||
|
"notice": "此頁面僅供查看,鎖倉操作請透過 API 進行。",
|
||||||
|
"tiersTitle": "收益檔位",
|
||||||
|
"minAmount": "最小金額 (USDC)",
|
||||||
|
"tierLevel": "檔位 {n}",
|
||||||
|
"expectedProfit": "預期利潤",
|
||||||
|
"days": "{n} 天"
|
||||||
|
},
|
||||||
"locale": {
|
"locale": {
|
||||||
"zh": "繁體中文",
|
"zh": "繁體中文",
|
||||||
"en": "English",
|
"en": "English",
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import Search from '../views/Search.vue'
|
|||||||
import Profile from '../views/Profile.vue'
|
import Profile from '../views/Profile.vue'
|
||||||
import MemberCenter from '../views/MemberCenter.vue'
|
import MemberCenter from '../views/MemberCenter.vue'
|
||||||
import ApiKey from '../views/ApiKey.vue'
|
import ApiKey from '../views/ApiKey.vue'
|
||||||
|
import EarnActivity from '../views/EarnActivity.vue'
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
@ -64,6 +65,11 @@ const router = createRouter({
|
|||||||
name: 'api-key',
|
name: 'api-key',
|
||||||
component: ApiKey,
|
component: ApiKey,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/earn-activity',
|
||||||
|
name: 'earn-activity',
|
||||||
|
component: EarnActivity,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
scrollBehavior(to, from, savedPosition) {
|
scrollBehavior(to, from, savedPosition) {
|
||||||
const el = document.querySelector('[data-main-scroll]')
|
const el = document.querySelector('[data-main-scroll]')
|
||||||
|
|||||||
236
src/views/EarnActivity.vue
Normal file
236
src/views/EarnActivity.vue
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
<template>
|
||||||
|
<div class="earn-activity-page">
|
||||||
|
<div class="earn-activity-screen">
|
||||||
|
<section class="card header-card">
|
||||||
|
<div class="header-icon">
|
||||||
|
<v-icon size="48" color="#5b5bd6">mdi-lock-outline</v-icon>
|
||||||
|
</div>
|
||||||
|
<h1 class="header-title">{{ t('earnActivity.title') }}</h1>
|
||||||
|
<p class="header-sub">{{ t('earnActivity.subtitle') }}</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="card tiers-card" v-if="activityList.length > 0">
|
||||||
|
<h2 class="tiers-title">{{ t('earnActivity.tiersTitle') }}</h2>
|
||||||
|
<div class="tier-list">
|
||||||
|
<div class="tier-item" v-for="(item, index) in activityList" :key="item.ID">
|
||||||
|
<div class="tier-header">
|
||||||
|
<h3>{{ t('earnActivity.tierLevel', { n: index + 1 }) }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="tier-content">
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="info-label">{{ t('earnActivity.minAmount') }}</span>
|
||||||
|
<span class="info-value">{{ item.minAmount ? item.minAmount.toLocaleString() : '--' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-divider"></div>
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="info-label">{{ t('earnActivity.expectedProfit') }}</span>
|
||||||
|
<span class="info-value highlight">{{ formatRate(item.moreProfitRate) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-divider"></div>
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="info-label">{{ t('earnActivity.duration') }}</span>
|
||||||
|
<span class="info-value">{{ formatDuration(item.seconds) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="card notice-card">
|
||||||
|
<div class="notice-icon">
|
||||||
|
<v-icon size="20" color="#ca8a04">mdi-information-outline</v-icon>
|
||||||
|
</div>
|
||||||
|
<p class="notice-text">{{ t('earnActivity.notice') }}</p>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { getPmEarnActivityPublic } from '@/api/earnActivity'
|
||||||
|
import type { PmEarnActivityItem } from '@/api/earnActivity'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const activityList = ref<PmEarnActivityItem[]>([])
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
const formatRate = (rate: number | null) => {
|
||||||
|
if (!rate) return '--'
|
||||||
|
return `${(rate * 100).toFixed(0)}%`
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatDuration = (seconds: number | null) => {
|
||||||
|
if (!seconds) return t('earnActivity.unlimited')
|
||||||
|
const days = Math.round(seconds / 86400)
|
||||||
|
return t('earnActivity.days', { n: days })
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await getPmEarnActivityPublic()
|
||||||
|
if (res.code === 0 && res.data) {
|
||||||
|
activityList.value = res.data
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch earn activity list:', error)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.earn-activity-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #f5f5f5;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.earn-activity-screen {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 480px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-icon {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #f0f0ff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-title {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #111827;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-sub {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #6b7280;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tiers-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tiers-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #111827;
|
||||||
|
margin: 0;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid #f3f4f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tier-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tier-item {
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tier-header {
|
||||||
|
background: #f9fafb;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tier-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tier-content {
|
||||||
|
padding: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value.highlight {
|
||||||
|
color: #5b5bd6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-divider {
|
||||||
|
height: 1px;
|
||||||
|
background: #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
background: #fffbeb;
|
||||||
|
border-color: #fde68a;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-text {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #92400e;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
x
Reference in New Issue
Block a user