docs: 商业化四支柱客户端设计(会员中心/CPS 入口/广告激励)
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
# 商业化四支柱设计(客户端)· slogan-app
|
||||
|
||||
> **目标:** 以「个人形象设计」为主流程,把四支柱收入入口从「方案/单品」里长出来:VIP 会员充值、穿山甲广告激励、线下门店引流(美团联盟)、线上商品(京东/淘宝 CPS)。不做泛化场景广场。
|
||||
> **核心原则:** 客户端零硬编码业务配置;所有商业化入口以「接口可用」为开关 —— 后端未配置 key 时接口报错/返回空 → App 自动隐藏对应入口,主流程(生成方案 → 查看)不受影响。
|
||||
|
||||
## 1. 信息架构改造
|
||||
|
||||
```
|
||||
改造前:/commercial 孤立 tab(会员占位卡 + 门店列表)
|
||||
改造后:
|
||||
├─ /commercial 会员中心(P0) 会员状态卡 · 套餐 · 广告激励 · 合作门店 · 最近优惠
|
||||
├─ /plan-viewer 方案页(P0) 发型卡「做同款发型」· 穿衣清单「买同款/到店试穿」· 场合「延伸优惠」
|
||||
├─ /wardrobe 衣橱(P1) 长按「找升级款」
|
||||
└─ 全局(P2) 开屏广告 / 信息流广告位(效果图页底部)
|
||||
```
|
||||
|
||||
**开关原则**:每个商业化入口包一层 `commercialGate`(统一查询后端配置/捕获接口错误),未开通 → 按钮不渲染。启动时不做额外网络调用,**入口可见性由首次打开该页面的接口结果决定**(零新增请求)。
|
||||
|
||||
## 2. 支柱 A:会员中心(/commercial 重构)
|
||||
|
||||
### 2.1 页面结构
|
||||
|
||||
```
|
||||
/ commercial(ConsumerStatefulWidget,保留现有门店列表与类型筛选)
|
||||
├─ 会员状态卡:头像/会员名 · is_vip · expire_at(倒计时)· 权益 chips(无限效果图/优先AI/返现1.5x/门店折扣)
|
||||
│ ├─ 未开通 →「立即开通」按钮 → 打开套餐 bottom sheet
|
||||
│ └─ 已开通 →「会员码」按钮(store_discount 到店出示,P1)
|
||||
├─ 广告激励卡(非会员时显示):
|
||||
│ ├─ 看视频 +1 效果图(每日 2 次,剩余次数显示)
|
||||
│ └─ 看视频 1 天体验会员(每日 1 次)
|
||||
├─ 合作门店列表(现有 /partner-store/list 品牌合作门店保留,含「会员价」角标 P1;
|
||||
│ 美团联盟到店券走 /cps/product/list,两条链路互不替换)
|
||||
└─ 最近优惠(/cps/my/recent,P1,未开通则隐藏整卡)
|
||||
```
|
||||
|
||||
### 2.2 支付时序(App 侧)
|
||||
|
||||
```
|
||||
点击套餐 → POST /member/order/create {plan_id} → 返回 {order_no, pay_url}
|
||||
→ 打开 PayWebViewPage(内嵌 webview_flutter,iOS 用 WKWebView)
|
||||
→ WebView 加载 pay_url,监听 url 变化(payment 完成跳转)
|
||||
→ 同时 Timer 每 2s GET /member/order/status?order_no=... 轮询(上限 60s)
|
||||
→ status=paid → 关闭 WebView → 刷新 memberProvider → 成功 toast
|
||||
→ 超时 → 提示「支付结果确认中,请稍后在会员中心查看」(状态以服务端为准)
|
||||
```
|
||||
|
||||
- 支付页依赖 `webview_flutter`(P0 引入,国内 App 常规做法);若平台编译受限,降级方案:`url_launcher` 唤起系统浏览器支付,返回 App 后仍走轮询(**P0 默认 url_launcher 方案,webview_flutter 留 P1**,减小依赖风险)
|
||||
|
||||
### 2.3 状态与 Provider
|
||||
|
||||
| Provider | 类型 | 数据 | 接口 |
|
||||
|---|---|---|---|
|
||||
| `memberProvider` | AsyncNotifier | isVip, expireAt, planName, benefits[] | GET /member/status |
|
||||
| `memberPlanProvider` | FutureProvider | 套餐列表 | GET /member/plan/list |
|
||||
| `orderCreateProvider` | Notifier.family(planId) | orderNo, payUrl | POST /member/order/create |
|
||||
| `orderStatusProvider` | FutureProvider.family(orderNo) | status | GET /member/order/status |
|
||||
|
||||
- `memberProvider` 缓存登录态期间;`refresh()` 在支付成功、广告领奖后调用
|
||||
- 权益 chips 文案从套餐 `features` JSON 解析 → 本地文案 map(`effect_unlimited→无限效果图` 等)
|
||||
|
||||
## 3. 支柱 B:广告激励(lib/core/ads/ 新建)
|
||||
|
||||
### 3.1 抽象(供应商隔离)
|
||||
|
||||
```dart
|
||||
// lib/core/ads/ads_provider.dart
|
||||
abstract class AdsService {
|
||||
bool get enabled; // appid 未配置 → false,App 隐藏广告入口
|
||||
Future<bool> showRewarded(); // 激励视频,返回是否完整观看
|
||||
}
|
||||
|
||||
// lib/core/ads/pangle_ads_service.dart —— 穿山甲实现(P1 接入 SDK,P0 仅接口 + mock)
|
||||
// P0:MockAdsService —— 本地模拟 3 秒「播放」返回 true,保证主链路可开发可测
|
||||
```
|
||||
|
||||
- **初始化**:`AdsConfig`(AppConfig 常量:pangleAppId 默认空)→ `adsServiceProvider` 单例
|
||||
- **降级**:appid 空 / SDK 初始化失败 → `enabled=false` → 会员中心激励卡、广告位全部不渲染
|
||||
|
||||
### 3.2 激励流程(服务端防刷,客户端只展示)
|
||||
|
||||
```
|
||||
点击「看视频」→ adsService.showRewarded()
|
||||
→ 完整观看 → POST /ad/reward/claim {ad_type: effect_extra | vip_trial}
|
||||
→ 成功 → 展示奖励弹窗(+1 效果图 / 1 天体验会员)→ refresh 会员卡
|
||||
→ 失败(限频/未配置)→ 隐藏式错误(后端返回「今日次数已用完」→ 入口变灰)
|
||||
```
|
||||
|
||||
- 剩余次数展示:`/ad/reward/claim` 响应带回 `{reward: {ad_type, remaining_today}}`,App 本地缓存当日显示;或 P0 简单化 —— 仅在领取失败时提示次数用完
|
||||
- **效果图配额联动**:后端限额 = 基础 3 + 当日额外次数;App 端文案统一显示「今日剩余 X 次」(P1 后端在生成接口响应中带 `remaining` 字段,P0 保持现状提示)
|
||||
|
||||
### 3.3 广告位(P2,本期只留占位)
|
||||
|
||||
- 开屏广告:`/home` 进入时加载(P2)
|
||||
- 信息流广告:`/plan-effect` 效果图 GridView 底部插一条(P2)
|
||||
|
||||
## 4. 支柱 C/D:方案驱动 CPS 入口
|
||||
|
||||
### 4.1 方案页(/plan-viewer)三处入口
|
||||
|
||||
| 位置 | 按钮 | scene | 请求 | 跳转 |
|
||||
|---|---|---|---|---|
|
||||
| 发型卡尾部 | 「做同款发型」 | haircut | GET /cps/plan/recommend {plan_id, scene: haircut} | /cps-product-list(美团丽人/理发券) |
|
||||
| 穿衣清单每项 trailing | 「买同款」 | item_buy | 京东搜索单品名 | /cps-product-list(电商商品) |
|
||||
| 穿衣清单每项 trailing | 「到店试穿」 | item_upgrade | 美团服装类目 | /cps-product-list(门店券) |
|
||||
| 场合卡(P1,detail 有 occasion 字段后) | 「延伸优惠」 | occasion | 映射表推荐 | /cps-product-list |
|
||||
|
||||
- **可见性**:推荐接口返回空列表 / 接口报错(CPS 未开通)→ 该按钮隐藏;发型卡无发型名(默认发型)→ 隐藏「做同款」
|
||||
- **交互**:推荐返回列表 → push `/cps-product-list?source=&category_code=&city=`(带标题「做同款发型 · 丽人」);列表点击 → POST /cps/product/link {product_id, scene, plan_id} → 打开 deeplink
|
||||
- **打开方式**:`url_launcher`(系统浏览器,携 pid 转链 URL;电商商品优先深链 App,P1 增强)
|
||||
|
||||
### 4.2 衣橱升级款(/wardrobe 长按菜单加一项)
|
||||
|
||||
```
|
||||
长按衣物卡 → 菜单:删除 / 找升级款
|
||||
「找升级款」→ GET /cps/wardrobe/upgrade {item_id} → /cps-product-list(标题「升级款 · 上衣」)
|
||||
```
|
||||
|
||||
### 4.3 商品列表页 /cps-product-list
|
||||
|
||||
```
|
||||
AppBar 标题(由入口传入)+ 分类 chips(/cps/category/list,P1)
|
||||
商品卡:封面图(AppConfig.resolveUrl)· 名称 · 价格(分→元)· 店铺 · 佣金标
|
||||
上滑加载更多(page 分页,has_more 判定)
|
||||
点击 → POST /cps/product/link → deeplink → url_launcher 打开
|
||||
```
|
||||
|
||||
### 4.4 Provider
|
||||
|
||||
| Provider | 类型 | 接口 |
|
||||
|---|---|---|
|
||||
| `cpsRecommendProvider` | FutureProvider.family((planId, scene)) | GET /cps/plan/recommend |
|
||||
| `cpsProductProvider` | AsyncNotifierProvider.family((source, categoryCode, city)) | GET /cps/product/list 分页 |
|
||||
| `cpsUpgradeProvider` | FutureProvider.family(itemId) | GET /cps/wardrobe/upgrade |
|
||||
| `cpsLinkAction` | Notifier | POST /cps/product/link |
|
||||
|
||||
## 5. 会员权益在客户端的呈现
|
||||
|
||||
| 权益 | 客户端表现 |
|
||||
|---|---|
|
||||
| effect_unlimited | 效果图页配额提示隐藏(「每日限 3 次」文案在 isVip 时不显示) |
|
||||
| ai_priority | 生成页状态文案「VIP 优先排队」(MVP 仅文案) |
|
||||
| cps_commission_x15 | 商品卡佣金标签「返现加成 1.5x」(VIP 用户) |
|
||||
| store_discount | 门店列表「会员价」角标 + 会员码页(P1) |
|
||||
|
||||
## 6. 配置(AppConfig 常量,均为本地编译期配置)
|
||||
|
||||
```dart
|
||||
// lib/core/config/app_config.dart 追加
|
||||
static const String pangleAppId = ''; // 穿山甲 AppId,空 = 广告功能关闭
|
||||
static const bool cpsEnabled = true; // 兜底开关;最终以接口结果为准
|
||||
```
|
||||
|
||||
- 商业化开关**最终以接口为准**(后端 config 未配置 → 接口错误 → App 隐藏入口),本地常量只控制「广告 SDK 是否初始化」
|
||||
|
||||
## 7. API 映射与错误处理
|
||||
|
||||
| 后端接口 | 客户端方法 | 错误处理 |
|
||||
|---|---|---|
|
||||
| GET /member/status | memberProvider.build | 401 → 重登;其他 → 默认非会员 |
|
||||
| GET /member/plan/list | memberPlanProvider | 错误 → 套餐区显示「暂未开通」 |
|
||||
| POST /member/order/create | 下单动作 | 错误 → toast「支付未开通」 |
|
||||
| GET /member/order/status | 轮询 | 404/错误 → 结束轮询提示稍后查看 |
|
||||
| POST /member/order/notify | -(后端回调,客户端不参与) | - |
|
||||
| POST /ad/reward/claim | 领奖动作 | 错误 → toast 后端 message(限频等) |
|
||||
| GET /cps/plan/recommend | cpsRecommendProvider | 空/错误 → 入口隐藏 |
|
||||
| GET /cps/product/list | cpsProductProvider | 空/错误 → 列表空态「暂未开放」 |
|
||||
| POST /cps/product/link | cpsLinkAction | 错误 → toast「跳转失败」 |
|
||||
| GET /cps/my/recent | recentProvider | 错误 → 整卡隐藏 |
|
||||
|
||||
- 所有新接口走现有 `apiClientProvider`(Dio 封装,自动带 token),无需改动网络层
|
||||
|
||||
## 8. 路由与依赖变更
|
||||
|
||||
```
|
||||
/lib/main.dart 新增 route:
|
||||
/cps-product-list(extra: CpsListArgs{title, source, categoryCode, city})
|
||||
/pay-webview(extra: PayWebviewArgs{orderNo, payUrl},P1 webview_flutter)
|
||||
依赖(P0):url_launcher(打开 deeplink / 系统浏览器支付)
|
||||
依赖(P1):webview_flutter(内嵌收银台)、穿山甲 SDK(pangle 插件)
|
||||
```
|
||||
|
||||
- 穿山甲 SDK Flutter 插件社区维护不稳定 → **P1 先验证 iOS/Android 编译,若插件不可用则改为原生 module 接入(P2)**;P0 用 MockAdsService 保证业务链路先闭环
|
||||
|
||||
## 9. 分期与对齐
|
||||
|
||||
| 分期 | 客户端内容 | 依赖 |
|
||||
|---|---|---|
|
||||
| **P0** | /commercial 重构会员中心(状态/套餐/下单/轮询)+ MockAds + 广告激励入口 + 方案页「做同款发型/买同款/到店试穿」+ /cps-product-list + url_launcher + cps 入口隐藏逻辑 | 后端 P0(会员+广告接口) |
|
||||
| **P1** | 场合卡「延伸优惠」+ 衣橱「找升级款」+ 最近优惠 + 会员码/门店折扣角标 + webview_flutter 内嵌收银台 + 穿山甲 SDK 接入(替换 Mock) | 后端 P1(CPS 引擎) |
|
||||
| **P2** | 开屏/信息流广告位 + 穿山甲插件不可用时的原生 module 兜底 + 收益/返现展示 | 后端 P2 |
|
||||
|
||||
## 10. 合规(客户端侧)
|
||||
|
||||
- **iOS 充值**:App Store 虚拟商品政策风险 → iOS 端隐藏会员套餐充值入口(`Platform.isIOS` 判断),保留广告激励 + 门店引流;「会员价」到店核销不受影响
|
||||
- **广告**:隐私政策文案补充穿山甲 SDK 信息收集披露;提供「个性化广告关闭」设置项(穿山甲 SDK 提供,P1)
|
||||
- **跳转**:CPS 转链一律走联盟 deeplink,不在 App 内二次改链
|
||||
|
||||
## 11. 开发规范约束(沿用 slogan-app 现有规范)
|
||||
|
||||
- Riverpod 3:AsyncNotifier/Notifier/FutureProvider.family;provider 文件放 `lib/features/<feature>/<feature>_provider.dart`
|
||||
- 新页面组件放 `lib/features/commercial/`(会员中心)、`lib/features/cps/`(商品列表);广告抽象放 `lib/core/ads/`
|
||||
- 图片 URL 一律 `AppConfig.resolveUrl()`;价格字段分 → 元转换写死规则(`(fen / 100).toStringAsFixed(0)`)
|
||||
- 所有「隐藏入口」逻辑集中在入口组件内一行判定,不扩散到业务逻辑
|
||||
- 新页面必配 empty/error/loading 三态(复用 shared/widgets)
|
||||
Reference in New Issue
Block a user