Files
dukang/docs/杜康好客-v3.4.18-门店体验与登录态优化.md
T
2026-08-19 16:20:33 +08:00

129 lines
12 KiB
Markdown
Raw 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.
# 杜康好客 · v3.4.18 门店体验与登录态优化
> **2026-08-17** · mini-user `3.4.18` / h5-partner / h5-shop / API
> 目标:用户小程序端门店电话与套餐图体验优化;城市合伙人「暂停」账号禁止登录;门店端结算页留白对齐与「休息中能否开张核销」交互。
## 范围
| 项 | 交付 |
|----|------|
| A. mini-user 门店座机电话脱敏 | `maskPhone` 统一「中间四位隐藏」;门店详情已调用 |
| B. mini-user 套餐详情图 1/5 + 自动轮播 | `ProductCarousel` detail 变体 autoplay + 右下角 `1/5` 计数 |
| C. 合伙人账号暂停禁止登录(双拦截) | 账号 `status=DISABLED` 或主账号 `bindingStatus=PAUSED` → 登录/发码/微信均拒,提示「该账号已暂停使用」或「该合伙人合作已暂停」 |
| D. 门店端 申请提现 / 筛选栏留白对齐 | `WithdrawPage` 左右内边距统一 `--space-page` |
| E. 门店端 门店休息中 → 是否开启营业 | 门店 PAUSED 时点击首页「扫码核销」即弹「是否开启营业?」,不进入扫码流程 |
| F. mini-user 门店套餐图片自适应完整显示 | `ProductCarousel` 新增 `imageFit="adaptive"`(widthFix + 动态高度),门店套餐详情不再裁剪 |
## A. mini-user 门店座机电话脱敏(中间四位隐藏)
门店对外电话 `store.phone`= `contactPhone ?? loginPhone`,见 `server/.../common/compat/v31-compat.ts``resolveStoreContactPhone`)在门店详情以「电话: {maskPhone(...)}」展示(`apps/mini-user/src/pages/store-detail/index.tsx` ~L393-402)。
- 脱敏规则(座机):`区号 + 本地号前 2 位 + **** + 本地号后 2 位`,隐藏本地号中间四位。
- `0379-12345678``0379-12****78`
- `010-87654321``010-87****21`
- 无分机同理;手机号仍按 `138****8000` 不变。
- 实现点:`apps/mini-user/src/lib/phone.ts``maskPhone` 座机分支(当前保留末 2~4 位)→ 改为保留首 2 + 末 2、中间以 `****` 替代。
- 拨号仍走 `toDialablePhone`(明文),不受脱敏影响。
- 无 Prisma / API 变更;仅前端 `maskPhone` 规则调整。
## B. mini-user 套餐详情图 1/5 计数 + 自动轮播
入口:`apps/mini-user/src/pages/store-package-detail/index.tsx` 渲染
`<ProductCarousel images={imageUrls} variant="detail" previewable />`,图片来自 `GET /stores/:id``packages[index].imageUrls`(最多 20 张,由 `normalizeStorePackageImageUrls` 归一化)。
- 组件:`apps/mini-user/src/components/ProductCarousel.tsx`
- `variant='detail'` 时给 `<Swiper>` 增加 `autoplay` + `interval`(建议 3500ms),仅 `slides.length > 1` 时生效(现有 `circular` 已满足)。
- 右下角叠加分页计数:`${activeIndex + 1}/${slides.length}`(白底圆角胶囊,绝对定位于 `.detail-carousel-wrap` 右下角);保留原居中圆点(dots)或改为以右下计数为主。
- 单图(`slides.length === 1`)不展示计数、不开轮播。
- 图片容器 `.store-package-detail-gallery` 预留右下角定位锚点(如需)。
- 无 API / schema 变更。
## C. 合伙人账号暂停禁止登录(双拦截:账号停用 + 合伙人绑定暂停)
> 两个状态字段、两个枚举,**不要混**:
> | 维度 | 字段 | 枚举 | 取值 |
> |------|------|------|------|
> | 登录账号启用状态 | `partner_account.status` | `AccountStatus` | `ACTIVE` / `DISABLED` |
> | 城市合伙人绑定状态 | `partner_account.bindingStatus` | `CityPartnerStatus` | `ACTIVE` / `PAUSED` |
> 列表(`admin/partners`)里能看到的是 `bindingStatus`;主账号自身 `status` 不在列表返回体(仅子账号 `children[].status` 有)。
- 拦截规则(**主账号、子账号都拦**):
1. 登录账号自身 `status !== 'ACTIVE'``DISABLED`,主账号 / 子账号均适用)→ 抛 `该账号已暂停使用,请联系客服人员`
2. 所属**主账号** `bindingStatus !== 'ACTIVE'``PAUSED`,城市合伙人绑定暂停)→ 抛 `该合伙人合作已暂停,请联系客服人员`
- 子账号的绑定状态以其**父主账号**为准(`resolvePrimaryAccount`)。
- 后端统一闸门:新增 `assertPartnerAccountActive(account)``auth.service.ts` ~L307 后),一次性校验以上两条;由以下入口复用:
- `assertPartnerAccountByPhone`~L320):被 `checkPartnerPhone``POST /partner/auth/phone/check`)、发码预检 `assertSmsSendAllowed`PARTNER_LOGIN / PARTNER_PROXY_ORDER 场景)调用 → 暂停账号在「手机号校验」阶段即被拦截,无法获取短信验证码。
- `loginPartner``POST /partner/auth/login/sms`~L1063 后)。
- `loginPartnerWechat``POST /partner/auth/login/wechat`~L1726 后)。
- 前端 `apps/h5-partner/src/pages/LoginPage.tsx`
- `formatPartnerError`~L69/ `formatWechatError`(~L84):命中「已暂停 / 已停用」分支映射 `该账号已暂停使用,请联系客服人员`;其余未匹配错误(含 `该合伙人合作已暂停…`)原样透出 `return text`
- 枚举现状:`AccountStatus``schema.prisma` ~L244)与 `CityPartnerStatus`(~L203)均为既有,本次**无任何 Prisma 迁移**。
- 统一文案:
- 账号停用:`该账号已暂停使用,请联系客服人员`
- 合伙人绑定暂停:`该合伙人合作已暂停,请联系客服人员`
## D. 门店端 申请提现 / 筛选栏左右留白对齐
页面:`apps/h5-shop/src/pages/WithdrawPage.tsx`(门店管理 / 结算提现)。
- 申请提现按钮 `.shop-withdraw-btn`~L135):当前 `width:100%; margin-top:12px`,置于 `.shop-records-main`(无左右 padding)内,贴边满宽。
- 下方筛选栏 `.shop-records-filters`~L147`position: sticky; top:0`)与 `.shop-records-status-chips`:当前左右 `padding:0`chips 贴屏幕边缘。
- 同页 `.shop-records-summary` / `.shop-records-list` / `.shop-records-list-head` 均使用页面级 token `var(--space-page)` 左右内缩。
- 改动:`.shop-records-filters` 增加 `padding: 0 var(--space-page)`(sticky 背景保留);申请提现按钮区域同样左右内缩 `var(--space-page)`(或其父容器加 `padding: 0 var(--space-page)`),使其与上下组件留白一致。仅样式调整,无逻辑 / API 变更。
## E. 门店端 休息中核销 → 是否开启营业
现状:`PhoneRedeemPage.tsx` / `RedeemConfirmPage.tsx` 拉取 `GET /shop/store``status !== 'OPEN'``storeClosed=true`,核销前拦截并报「门店未营业,无法核销」(服务端 `server/.../modules/redeem/redeem.service.ts``loadOpenStoreAccount` ~L117 亦硬校验「门店未营业」)。
- **前置拦截(主路径 · 门店端首页 `HomePage`**:门店状态非 `OPEN`(PAUSED 临时闭店 / 休息中)时,用户点击首页「扫码核销」按钮**立即**弹确认框「门店目前休息中无法核销,是否开启营业?」,**不进入扫码流程**:
- 确认 → 调用 `PUT /shop/store/status { status: 'OPEN' }`,成功后关闭弹窗并 `loadDashboard()` 刷新门店状态为营业中,用户可再次点击扫码。
- 取消 → 关闭弹窗,维持拦截。
- **确认页兜底(次路径 · `RedeemConfirmPage`)**:若直接带核销码进入确认页且门店仍非 `OPEN`,点击「确认核销」时同样弹「是否开启营业?」,开张后重新拉取预览并继续核销(`doConfirm`)。
- 边界:门店 `auditStatus !== 'APPROVED'``updateShopStatus` 会拒绝开张(抛「门店尚在总部审核中 / 审核未通过」),前端需捕获并提示该错误,不进入误开启。
- 适用页:`HomePage`(门店端首页扫码入口,PAUSED 时点击即弹窗)、`RedeemConfirmPage`(扫码核销确认页兜底)、`PhoneRedeemPage`(手机号核销)。
- 后端无需改动(开张接口与硬校验已存在)。
## 关键接口 / 文件
| 位置 | 说明 |
|------|------|
| `apps/mini-user/src/lib/phone.ts` `maskPhone` | 座机脱敏规则改为「中间四位隐藏」 |
| `apps/mini-user/src/pages/store-detail/index.tsx` | 门店详情电话展示(已用 maskPhone) |
| `apps/mini-user/src/components/ProductCarousel.tsx` | detail 变体 autoplay + 右下 `1/5` 计数 |
| `apps/mini-user/src/pages/store-package-detail/index.tsx` | 套餐详情图渲染 |
| `GET /stores/:id` | 门店 / 套餐数据(无变更) |
| `server/.../modules/iam/auth.service.ts` `loginPartner` / `assertPartnerAccountByPhone` / `assertSmsSendAllowed` | 合伙人登录 / 发码非 ACTIVE 抛「该账号已暂停使用,请联系客服人员」 |
| `apps/h5-partner/src/pages/LoginPage.tsx` `formatPartnerError` / `formatWechatError` | 暂停文案 |
| `apps/h5-shop/src/pages/WithdrawPage.tsx` + `styles.css` `.shop-records-filters` / `.shop-records-status-chips` / `.shop-withdraw-btn` | 左右留白对齐 `--space-page` |
| `apps/h5-shop/src/pages/PhoneRedeemPage.tsx` / `RedeemConfirmPage.tsx` | 休息中弹「是否开启营业?」 |
| `apps/h5-shop/src/pages/StatusPage.tsx` `requestToggle` / `confirmToggle` | 复用开张调用 |
| `PUT /shop/store/status` `GET /shop/store` | 切换营业 / 读取门店(已存在) |
## F. mini-user 门店套餐图片自适应完整显示(不裁剪)
门店套餐详情(`apps/mini-user/src/pages/store-package-detail/index.tsx`)的 `ProductCarousel` 此前用 `variant="detail"` 默认 `imageFit="cover"`= `aspectFill`),配合 `.detail-carousel-wrap` 的固定 `aspect-ratio: 1``overflow:hidden`,非正方形图片被裁切 → 用户反馈「图片没显示全」。
- 新增 `imageFit="adaptive"` 模式(仅作用于门店套餐详情):
- `Image` 改用 `mode="widthFix"`,按图片真实比例缩放、完整显示、不裁剪。
- 组件挂载时实测容器宽度(`Taro.createSelectorQuery().select('.detail-carousel-wrap--adaptive').boundingClientRect`),图片 `onLoad` 拿到自然宽高后按 `容器宽 × (自然高/自然宽)` 计算每张幻灯片渲染高度,赋给 `<Swiper>` 内联 `height`,实现轮播高度自适应(多图比例不一也能逐张适配,带 0.2s 过渡)。
- 未加载前兜底高度为 `容器宽 × 0.75`(约 3:4)。
- 样式:`product-detail.css` 增加 `.detail-carousel-wrap--adaptive`,覆盖基类固定 `aspect-ratio:1``overflow:hidden``aspect-ratio:auto; overflow:visible`),并令 `.detail-carousel-image` 高度为 `auto`
- `autoplay`3.5s/ `1/5` 计数(feature B)在 adaptive 下保持不变。
- 仅门店套餐详情传 `imageFit="adaptive"`;门店头图(`variant="store"``contain`)、商品详情(`cover`)不受影响。
## 验收
- [ ] 门店详情座机显示形如 `0379-12****78`(中间四位隐藏),手机号仍 `138****8000`;拨打为明文。
- [ ] 套餐详情图多张时右下角显示 `1/5` 分页计数,并自动轮播(约 3.5s 切换);单图不计数、不轮播;点击仍可预览。
- [ ] 合伙人**账号** `status=DISABLED`(主账号或子账号)时:短信登录与微信登录均被拦截,提示「该账号已暂停使用,请联系客服人员」,无法进入。
- [ ] 合伙人**绑定** `bindingStatus=PAUSED`(主账号)时:无论用主账号还是其任一子账号登录,均被拦截,提示「该合伙人合作已暂停,请联系客服人员」。
- [ ] 以上拦截在「手机号校验(`phone/check`)」阶段即生效,暂停账号拿不到短信验证码。
- [ ] 门店管理(结算提现)页:申请提现按钮与筛选栏左右留白与其他区块一致(统一 `--space-page`),不再贴边。
- [ ] 门店休息中(PAUSED)时:点击门店端首页「扫码核销」按钮**立即**弹「门店目前休息中无法核销,是否开启营业?」,**不进入扫码流程**;确认开启后门店状态刷新、可再次扫码;取消则维持拦截;未过审门店开张被拒时给出对应提示。
- [ ] 门店套餐详情图片按真实比例完整显示、不再被裁切(轮播高度随图自适应);自动轮播与 `1/5` 计数仍正常。
- [ ] A 仅 `maskPhone` 规则、C/D/E 仅前端样式 / 交互;均无需 Prisma 迁移(C 复用 `DISABLED`)。
## HQ 开发计划
创建版本 `v3.4.18` 并关联本迭代任务;发版前再合并发布(mini-user 升 `3.4.18`)。