ShortPro · Hadiah 页 Vue 3 工程
完整生产级实现:Vue 3 + TypeScript + Vite + Pinia + axios + qrcode 100% 还原 设计稿 移动端 H5 页面,可直接接入后端 API。
🚀 快速启动
# 1. 安装依赖 (推荐 pnpm,npm/yarn 也可)
pnpm install
# 2. 复制环境变量
cp .env.example .env
# 3. 开发模式启动 (默认端口 5173)
pnpm dev
# 4. 构建生产包
pnpm build
# 5. 本地预览构建结果
pnpm preview
无后端时,在 .env 中设置 VITE_USE_MOCK=true,会用 src/mock/hadiah.mock.ts 中的数据。
📂 目录结构
src/
├── api/ # API 封装 (axios + 拦截器 + 错误兜底)
│ ├── http.ts # 全局 axios 实例
│ └── hadiah.ts # Hadiah 页所有 endpoint
├── components/ # 所有 UI 组件 (16 个)
│ ├── PhoneFrame.vue # 手机外框容器
│ ├── StatusBar.vue # 状态栏
│ ├── TopTabs.vue # 顶部 Bonus/Hadiah 切换
│ ├── RankingHero.vue # 顶部 "Daftar Bonus" hero
│ ├── KarnivalBanner.vue # AI karnival banner
│ ├── UserStatsCard.vue # 等级/月奖励/邀请链接卡
│ ├── VoucherStrip.vue # 横滑 voucher 列表
│ ├── QuickActions.vue # 2x2 入口块
│ ├── DukungDramaSection.vue # Dukung Drama 大区(组合体)
│ ├── RecentFeed.vue # 滚动 feed
│ ├── AIInvestCard.vue # AI 投资产品卡
│ ├── DramaSupportCard.vue # Drama 支持卡
│ ├── AccordionItem.vue # 折叠组件 (Aturan/FAQ)
│ ├── BottomNav.vue # 底部 5 项 nav
│ ├── SharePoster.vue # 奢华证书风海报
│ ├── SharePreviewModal.vue # 全屏分享预览
│ ├── QrCode.vue # 真实 QR 码
│ ├── PageSkeleton.vue # 骨架屏
│ └── ErrorRetry.vue # 错误兜底
├── composables/
│ └── useHadiahData.ts # 数据获取 + abort 控制
├── views/
│ └── HadiahView.vue # 主视图,组件聚合 + 操作分发
├── types/index.ts # 全部类型 (API 契约)
├── styles/ # 样式
│ ├── variables.scss # 设计令牌
│ ├── global.scss # 全局 reset + 动画
│ └── share-poster.scss # 海报独立样式
├── utils/
│ ├── format.ts # 格式化(千分位等)
│ └── toast.ts # 轻量 toast
├── mock/hadiah.mock.ts # mock 数据
├── router/index.ts # vue-router
├── App.vue
└── main.ts
🔌 后端接入指南 (重点)
推荐方案:聚合接口
后端最好提供 1 个聚合接口 GET /api/hadiah/page 返回 HadiahPageData 完整结构。这能减少 HTTP 往返,首屏快 ~600ms。
// 类型定义见 src/types/index.ts
interface HadiahPageData {
user: UserStats
vouchers: Voucher[]
aiProducts: AIProduct[]
dramas: DramaSupport[]
feed: SupportFeedItem[]
claim: ClaimPanel
poster: SharePosterData
karnivalPeriod: string
rankingPercent: number
}
备选方案:并行接口
如果后端暂时没法做聚合,前端已写好 getHadiahPageDataParallel() 在 src/api/hadiah.ts,会用 Promise.all 并发拉取 8 个子接口(并发数受控,移动端弱网也不会卡)。
操作类接口
| 方法 | 路径 | 说明 |
|---|---|---|
claimReward |
POST /api/claim |
领取待领奖励 |
redeemCoupon |
POST /api/coupon/redeem |
兑换优惠券 |
confirmInvest |
POST /api/invest/confirm |
确认 AI 投资 |
supportDrama |
POST /api/dramas/:id/support |
手动支持 drama |
trackShare |
POST /api/share/track |
分享上报 |
接入 token
在 src/api/http.ts 的请求拦截器中,搜 TODO[real-auth] 把注释打开:
const token = localStorage.getItem('access_token')
if (token) config.headers.set('Authorization', `Bearer ${token}`)
接入 nginx 反代 (生产)
.env.production 配置:
VITE_API_BASE=/api
VITE_USE_MOCK=false
VITE_REFERRAL_BASE=https://shortpro.com/r
nginx 加上反向代理把 /api/* 转到后端。
⚡ 性能优化清单 (已落地)
- ✅ 路由懒加载 (
HadiahView单独 chunk) - ✅ vendor / qrcode 拆 chunk,主包小
- ✅ 接口聚合优先,减少首屏 RTT
- ✅
AbortController在组件卸载时取消未完成请求 - ✅ Voucher 横滑用
requestAnimationFrame节流,而非高频scroll - ✅ 操作类 API 乐观更新,UI 不等接口返回
- ✅ 骨架屏首屏不闪烁
- ✅ 海报截图态
.frozen关闭所有动画/transition - ✅
prefers-reduced-motion全局适配 - ✅ Bonus/Hadiah 切 tab 不会重发请求
🎨 设计 Token
所有颜色 / 渐变 / 阴影集中在 src/styles/variables.scss,改一个变量全站生效。
字体由 Google Fonts 加载:
- Bricolage Grotesque — display / 大数字
- Plus Jakarta Sans — body
- JetBrains Mono — 代码 / ID
- Playfair Display — 海报 serif
📦 切图说明
本项目采用 零静态图片 设计:
- 所有 icon (奖杯、shield、AI 头像、底部 nav、QR 边框、海报边角等) 都是 inline SVG
- 所有海报、海报、drama 海报背景都是 CSS 渐变 + SVG
- 真实二维码由
qrcode.js运行时生成,内容是referralLink
→ 不需要切图资源,部署后包体更小,任意分辨率不糊。
如果将来想用真实剧海报照片,把 DramaSupportCard.vue 中的 .drama-poster 替换为 <img :src="drama.coverUrl" /> 即可,后端 DramaSupport 类型加 coverUrl: string 字段。
🔍 关键代码标记
代码里有 3 类待对接标记,搜索就能找到:
TODO[real-api]— 真实 API 路径需要后端确认TODO[real-auth]— token 获取/刷新逻辑TODO[router]— 路由跳转 (其它页面)
🛠 浏览器兼容
iOS Safari 14+ / Chrome 90+ / Android WebView 90+。需要兼容更老,加 @vitejs/plugin-legacy。
📜 License
ShortPro Internal — 仅授权 ShortPro 团队使用。