bindbox-mini/docs/代码重构分析/DESIGN_组件化重构.md

324 lines
8.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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*