324 lines
8.5 KiB
Markdown
324 lines
8.5 KiB
Markdown
# bindbox-mini 组件化重构设计
|
||
|
||
## 架构目标
|
||
|
||
将三个活动页面(yifanshang/duiduipeng/wuxianshang)共约5079行代码减少至约2500行,消除61%的冗余。
|
||
|
||
---
|
||
|
||
## 架构设计图
|
||
|
||
```mermaid
|
||
graph TB
|
||
subgraph Utils[工具层 utils/]
|
||
A1[activity.js<br>活动相关工具函数]
|
||
A2[format.js<br>格式化工具]
|
||
A3[cache.js<br>缓存管理]
|
||
end
|
||
|
||
subgraph Composables[组合式函数 composables/]
|
||
B1[useActivity.js<br>活动数据管理]
|
||
B2[useIssues.js<br>期数据管理]
|
||
B3[useRewards.js<br>奖励数据管理]
|
||
B4[usePayment.js<br>支付流程]
|
||
end
|
||
|
||
subgraph Components[组件层 components/]
|
||
subgraph Layout[布局组件]
|
||
C1[ActivityPageLayout.vue<br>活动页面框架]
|
||
C2[ActivityHeader.vue<br>头部卡片]
|
||
end
|
||
subgraph Biz[业务组件]
|
||
C3[ActivityTabs.vue<br>Tab切换]
|
||
C4[RewardsPopup.vue<br>奖品弹窗]
|
||
C5[RecordsList.vue<br>购买记录]
|
||
C6[RewardsPreview.vue<br>奖池预览]
|
||
end
|
||
subgraph Existing[已有组件]
|
||
C7[PaymentPopup.vue]
|
||
C8[FlipGrid.vue]
|
||
end
|
||
end
|
||
|
||
subgraph Pages[页面层 pages/activity/]
|
||
D1[yifanshang - 选号+专属业务]
|
||
D2[duiduipeng - 对对碰游戏+专属业务]
|
||
D3[wuxianshang - 多档抽奖+专属业务]
|
||
end
|
||
|
||
Utils --> Composables
|
||
Composables --> Pages
|
||
Components --> Pages
|
||
```
|
||
|
||
---
|
||
|
||
## 详细模块设计
|
||
|
||
### 1. 工具函数层 `utils/`
|
||
|
||
#### `utils/activity.js` - 活动相关工具 [NEW]
|
||
|
||
```javascript
|
||
// 数据标准化
|
||
export function unwrap(list) { /* ... */ }
|
||
export function normalizeIssues(list) { /* ... */ }
|
||
export function normalizeRewards(list) { /* ... */ }
|
||
|
||
// 值判断
|
||
export function truthy(v) { /* ... */ }
|
||
export function detectBoss(i) { /* ... */ }
|
||
export function levelToAlpha(level) { /* ... */ }
|
||
|
||
// 状态转换
|
||
export function statusToText(s) { /* ... */ }
|
||
```
|
||
|
||
#### `utils/format.js` - 格式化工具 [NEW]
|
||
|
||
```javascript
|
||
export function cleanUrl(u) { /* ... */ }
|
||
export function formatPercent(v) { /* ... */ }
|
||
export function formatDateTime(v) { /* ... */ }
|
||
export function formatPrice(cents) { /* ... */ }
|
||
```
|
||
|
||
#### `utils/cache.js` - 缓存管理 [NEW]
|
||
|
||
```javascript
|
||
export function isFresh(ts, ttl = 24 * 60 * 60 * 1000) { /* ... */ }
|
||
export function getRewardCache() { /* ... */ }
|
||
export function setRewardCache(activityId, issueId, value) { /* ... */ }
|
||
```
|
||
|
||
---
|
||
|
||
### 2. 组合式函数层 `composables/`
|
||
|
||
#### `composables/useActivity.js` [NEW]
|
||
|
||
```javascript
|
||
export function useActivity(activityId) {
|
||
const detail = ref({})
|
||
const coverUrl = computed(() => cleanUrl(detail.value?.image || detail.value?.banner || ''))
|
||
const statusText = computed(() => statusToText(detail.value?.status))
|
||
const pricePerDraw = computed(() => (Number(detail.value?.price_draw || 0) / 100))
|
||
|
||
async function fetchDetail() { /* ... */ }
|
||
|
||
return { detail, coverUrl, statusText, pricePerDraw, fetchDetail }
|
||
}
|
||
```
|
||
|
||
#### `composables/useIssues.js` [NEW]
|
||
|
||
```javascript
|
||
export function useIssues(activityId) {
|
||
const issues = ref([])
|
||
const selectedIssueIndex = ref(0)
|
||
const currentIssueId = computed(() => issues.value[selectedIssueIndex.value]?.id || '')
|
||
const currentIssueTitle = computed(() => /* ... */)
|
||
|
||
async function fetchIssues() { /* ... */ }
|
||
function prevIssue() { /* ... */ }
|
||
function nextIssue() { /* ... */ }
|
||
function setSelectedById(id) { /* ... */ }
|
||
|
||
return { issues, selectedIssueIndex, currentIssueId, currentIssueTitle, fetchIssues, prevIssue, nextIssue, setSelectedById }
|
||
}
|
||
```
|
||
|
||
#### `composables/useRewards.js` [NEW]
|
||
|
||
```javascript
|
||
export function useRewards(activityId, currentIssueId) {
|
||
const rewardsMap = ref({})
|
||
const currentIssueRewards = computed(() => rewardsMap.value[currentIssueId.value] || [])
|
||
const rewardGroups = computed(() => /* 按level分组 */)
|
||
|
||
async function fetchRewardsForIssues(issueList) { /* 带缓存 */ }
|
||
|
||
return { rewardsMap, currentIssueRewards, rewardGroups, fetchRewardsForIssues }
|
||
}
|
||
```
|
||
|
||
#### `composables/useRecords.js` [NEW]
|
||
|
||
```javascript
|
||
export function useRecords() {
|
||
const winRecords = ref([])
|
||
|
||
async function fetchWinRecords(activityId, issueId) { /* ... */ }
|
||
|
||
return { winRecords, fetchWinRecords }
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 3. 组件层 `components/`
|
||
|
||
#### `ActivityPageLayout.vue` [NEW] - 页面框架组件
|
||
|
||
Props:
|
||
- `coverUrl: String` - 背景图URL
|
||
|
||
Slots:
|
||
- `header` - 头部卡片区域
|
||
- `content` - 主要内容(tabs等)
|
||
- `footer` - 底部操作栏
|
||
- `modals` - 弹窗区域
|
||
|
||
#### `ActivityHeader.vue` [NEW] - 头部卡片
|
||
|
||
Props:
|
||
- `title: String`
|
||
- `price: Number` (分)
|
||
- `priceUnit: String` - 价格单位(如"/发"、"/次")
|
||
- `coverUrl: String`
|
||
- `tags: Array<String>`
|
||
- `scheduledTime: String` (可选)
|
||
|
||
Events:
|
||
- `@show-rules`
|
||
- `@go-cabinet`
|
||
|
||
#### `ActivityTabs.vue` [NEW] - Tab切换
|
||
|
||
Props:
|
||
- `modelValue: String` - 当前tab ('pool' | 'records')
|
||
- `tabs: Array<{key, label}>`
|
||
|
||
Events:
|
||
- `@update:modelValue`
|
||
|
||
#### `RewardsPreview.vue` [NEW] - 奖池预览
|
||
|
||
Props:
|
||
- `rewards: Array`
|
||
- `grouped: Boolean` - 是否按等级分组显示
|
||
|
||
#### `RewardsPopup.vue` [NEW] - 奖品弹窗
|
||
|
||
Props:
|
||
- `visible: Boolean`
|
||
- `title: String`
|
||
- `rewardGroups: Array` - 按等级分组的奖励
|
||
|
||
Events:
|
||
- `@update:visible`
|
||
|
||
#### `RecordsList.vue` [NEW] - 购买记录列表
|
||
|
||
Props:
|
||
- `records: Array`
|
||
- `emptyText: String`
|
||
|
||
---
|
||
|
||
### 4. 样式层 `styles/`
|
||
|
||
#### `styles/activity-common.scss` [NEW]
|
||
|
||
提取共用样式(约600行):
|
||
- 页面布局:`.page-wrapper`, `.bg-decoration`, `.orb`, `@keyframes float`
|
||
- 背景处理:`.page-bg`, `.bg-image`, `.bg-mask`
|
||
- 入场动画:`.animate-enter`, `.stagger-*`
|
||
- 头部卡片样式(可在ActivityHeader组件内联)
|
||
- 板块容器:`.section-container`, `.section-header`
|
||
- Tabs样式(可在ActivityTabs组件内联)
|
||
- 预览列表:`.preview-scroll`, `.preview-item`
|
||
- 记录列表:`.records-list`, `.record-item`
|
||
- 弹窗样式(可在RewardsPopup组件内联)
|
||
|
||
---
|
||
|
||
## 重构后页面结构示例
|
||
|
||
### yifanshang/index.vue (预计约400行→优化后)
|
||
|
||
```vue
|
||
<template>
|
||
<ActivityPageLayout :cover-url="coverUrl">
|
||
<template #header>
|
||
<ActivityHeader
|
||
:title="detail.name"
|
||
:price="detail.price_draw"
|
||
price-unit="/发"
|
||
:cover-url="coverUrl"
|
||
:tags="['公开透明', '拒绝套路']"
|
||
:scheduled-time="scheduledTimeText"
|
||
@show-rules="showRules"
|
||
@go-cabinet="goCabinet"
|
||
/>
|
||
</template>
|
||
|
||
<template #content>
|
||
<ActivityTabs v-model="tabActive">
|
||
<template #pool>
|
||
<RewardsPreview :rewards="currentIssueRewards" @view-all="openRewardsPopup" />
|
||
</template>
|
||
<template #records>
|
||
<RecordsList :records="winRecords" />
|
||
</template>
|
||
</ActivityTabs>
|
||
|
||
<!-- 一番赏专属:选号组件 -->
|
||
<YifanSelector ... />
|
||
</template>
|
||
|
||
<template #modals>
|
||
<RewardsPopup v-model:visible="rewardsVisible" ... />
|
||
<FlipGrid ref="flipRef" ... />
|
||
</template>
|
||
</ActivityPageLayout>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { useActivity, useIssues, useRewards, useRecords } from '@/composables'
|
||
// 专注于一番赏特有的业务逻辑
|
||
</script>
|
||
```
|
||
|
||
---
|
||
|
||
## 文件变更清单
|
||
|
||
### 新增文件
|
||
|
||
| 文件路径 | 行数估算 | 说明 |
|
||
|----------|---------|------|
|
||
| `utils/activity.js` | ~80 | 活动工具函数 |
|
||
| `utils/format.js` | ~50 | 格式化工具 |
|
||
| `utils/cache.js` | ~40 | 缓存管理 |
|
||
| `composables/useActivity.js` | ~50 | 活动数据composable |
|
||
| `composables/useIssues.js` | ~80 | 期数据composable |
|
||
| `composables/useRewards.js` | ~80 | 奖励数据composable |
|
||
| `composables/useRecords.js` | ~40 | 记录composable |
|
||
| `components/ActivityPageLayout.vue` | ~150 | 页面框架 |
|
||
| `components/ActivityHeader.vue` | ~200 | 头部卡片 |
|
||
| `components/ActivityTabs.vue` | ~100 | Tab切换 |
|
||
| `components/RewardsPreview.vue` | ~120 | 奖池预览 |
|
||
| `components/RewardsPopup.vue` | ~150 | 奖品弹窗 |
|
||
| `components/RecordsList.vue` | ~80 | 记录列表 |
|
||
| **小计** | **~1220** | |
|
||
|
||
### 修改文件
|
||
|
||
| 文件路径 | 原行数 | 预计行数 | 变化 |
|
||
|----------|-------|---------|------|
|
||
| `yifanshang/index.vue` | 1229 | ~400 | -829 |
|
||
| `duiduipeng/index.vue` | 2291 | ~800 | -1491 |
|
||
| `wuxianshang/index.vue` | 1559 | ~500 | -1059 |
|
||
| **小计** | **5079** | **~1700** | **-3379** |
|
||
|
||
### 净变化
|
||
|
||
- 新增:~1220行
|
||
- 删除:~3379行
|
||
- **净减少:~2159行(42%)**
|
||
|
||
---
|
||
|
||
*设计文档创建时间:2025-12-25*
|