# 会员注册登录 — 前端技术方案（C 端 · shop-app）

> **依据：** 《会员注册登录技术方案.md》v1.3  
> **关联：** 《商品详情内页前端技术方案》（加购登录引导）、《搜索页前端技术方案》  
> **范围：** 消费者 APP **会员注册、登录、服务协议勾选**；**不** 改后端、**不** 实现短信验证码、忘记密码、资料/地址编辑（另册）。  
> **实现状态：** 页面与 API 已落地；登录 Token 解析已对齐 v1.3 根级响应，待联调。

---

## 1. 技术栈与约定

| 项 | 说明 |
|----|------|
| 框架 | uni-app **Vue 3** + **uview-plus** |
| 请求 | `@/utils/request`；成功 `code=200`；业务体在 `data`，**登录例外**见 §4.2 |
| Token | `config` → `TOKEN_KEY = shop-Admin-Token`；请求头 `Authorization: Bearer {token}` |
| 鉴权机制 | 与平台一致：`TokenService` 签发 JWT，Redis 会话；不足 20 分钟自动续期（后端） |
| 登录账号 | **手机号** 或 **会员名称**（`memberCode`；产品 **「会员 ID」= 会员名称**，**非** 数字 `memberId`） |
| 注册 | **无短信验证码**；`mobile + password + confirmPassword` |
| 样式 | 登录/注册共用 `styles/auth.scss` |

---

## 2. 页面与路由

| 页面 | 路径 | 主包/分包 | 入口 |
|------|------|-----------|------|
| 会员登录 | `pages/login/index` | **主包** | 我的、加购拦截、401 |
| 会员注册 | `subpackage/account/register` | **分包** | 登录页「立即注册」、我的 |

**Query：**

| 页面 | 参数 | 说明 |
|------|------|------|
| login | `account`（可选） | 注册成功或记住账号预填 |
| register | `mobile`（可选） | 从登录页带手机号 |

**路径常量：** `utils/pageRoute.js` → `PAGE_LOGIN`、`PAGE_REGISTER`

---

## 3. 文件清单

| 类型 | 路径 | 说明 |
|------|------|------|
| 登录页 | `shop-app/pages/login/index.vue` | 账号+密码+协议+记住账号 |
| 注册页 | `shop-app/subpackage/account/register.vue` | 手机号注册表单 |
| 协议块 | `shop-app/components/account/AgreementBlock.vue` | 勾选 + 弹窗 **`rich-text`** 渲染 |
| HTML 预处理 | `shop-app/utils/htmlContent.js` | 实体解码、图片 URL、空 `<p>` 清理 |
| API | `shop-app/api/member.js` | register / login / serviceAgreement / profile |
| 状态 | `shop-app/store/user.js` | 登录态、**根级 token** 解析、资料缓存 |
| 协议加载 | `shop-app/utils/memberAgreement.js` | 初始化协议配置 |
| 校验 | `shop-app/utils/memberValidate.js` | 手机号、密码 |
| 登录引导 | `shop-app/utils/apiAuth.js` | `ensureApiToken` → 登录页 |
| 样式 | `shop-app/styles/auth.scss` | 登录/注册卡片样式 |
| 我的 | `shop-app/pages/mine/index.vue` | 登录/注册入口；**我的服务**菜单见《我的服务前端技术方案》 |

> `api/login.js`（`/login`、`/captchaImage`）已 **废弃**，勿再用于 C 端。

---

## 4. 接口封装

**模块：** `shop-app/api/member.js`

| 方法 | HTTP | 路径 | 鉴权 |
|------|------|------|------|
| `memberRegister(data)` | POST | `/api/member/register` | 匿名 |
| `memberLogin(data)` | POST | `/api/member/login` | 匿名 |
| `getMemberServiceAgreement()` | GET | `/api/member/serviceAgreement` | 匿名 |
| `getMemberProfile()` | GET | `/api/member/profile` | Token |

可选（本期未单独封装）：`GET /api/member/serviceAgreement/status` — 仅开关；注册/登录页用完整 `serviceAgreement` 即可。

### 4.1 注册 Body

| 字段 | 必填 | 前端 |
|------|:----:|------|
| mobile | 是 | 11 位手机号校验 |
| password | 是 | 至少 6 位 |
| confirmPassword | 是 | 与 password 一致 |
| memberCode | 否 | 会员名称，空则后端自动生成 |
| agreementAccepted | 条件 | 协议 `enabled` 时须勾选 |

**成功 `data`：** 系统主键 `memberId`（long）；**不能** 作为登录账号，注册成功后跳转登录页用 **手机号** 登录。

### 4.2 登录 Body 与响应（v1.3）

| 字段 | 必填 | 前端 |
|------|:----:|------|
| account | 是 | 手机号或会员名称（产品所称会员 ID） |
| password | 是 | — |
| agreementAccepted | 条件 | `requireAgreementOnLogin` 为 true 时须勾选 |

**成功响应（与平台 `POST /login` 一致，字段在 JSON 根级）：**

```json
{
  "code": 200,
  "msg": "操作成功",
  "token": "eyJhbGciOiJIUzUxMiJ9...",
  "memberId": 1001
}
```

| 字段 | 说明 |
|------|------|
| `token` | 存本地 + 请求头 `Authorization: Bearer {token}` |
| `memberId` | **系统主键**，仅资料展示；**不可** 填入登录框 |

**`store/user.js` 解析：**

```javascript
const token = res.token || (res.data && res.data.token)
const memberId = res.memberId != null ? res.memberId : (res.data && res.data.memberId)
```

> **兼容：** 后端若仍传 `mobile` 且 `account` 为空，由服务端等同 `account=mobile`；前端统一传 `account`。

### 4.3 服务协议 `data` 要点

| 字段 | 用途 |
|------|------|
| enabled | 是否展示协议勾选与正文 |
| registrationOpen | 注册页是否开放 |
| requireAgreementOnLogin | 登录是否须勾选 |
| checkboxLabel / content | 勾选文案、弹窗 HTML |

---

## 5. 页面结构

### 5.1 登录 `pages/login/index`

```text
顶栏品牌区
└── 卡片
    ├── 账号（手机号或会员名称 / 会员 ID）
    ├── 密码
    ├── 记住账号（localStorage: shop_login_account）
    ├── AgreementBlock（协议启用且 requireAgreementOnLogin 时显示）
    ├── 登录按钮 → memberLogin → 读根级 token → fetchUserInfo → switchTab 首页
    └── 立即注册 → subpackage/account/register
```

| 规则 | 实现 |
|------|------|
| RL3 | 单输入框 `account`；文案标明「会员 ID = 会员名称」 |
| RL4 | 错误文案由后端统一返回，前端 Toast `msg` |
| RL6 | `needAgreement` 计算属性控制勾选 |
| RL8 | 不把 `memberId` 当登录账号 |
| 已登录进页 | 有 Token 直接 `switchTab` 首页 |

### 5.2 注册 `subpackage/account/register`

```text
顶栏「会员注册」
└── 卡片
    ├── registrationOpen=false → 展示关闭提示
    └── 开放时：
        ├── 手机号、密码、确认密码
        ├── 会员名称（选填）
        ├── AgreementBlock（协议 enabled 时）
        ├── 注册 → 成功 Toast → 带 mobile 跳转登录页
        └── 去登录
```

| 规则 | 实现 |
|------|------|
| RL1 | **无** 验证码表单项 |
| RL2 | 前端校验两次密码一致 |
| 注册关闭 | 展示 `agreement.message` 或默认文案 |

### 5.3 我的 `pages/mine/index`

| 状态 | UI |
|------|-----|
| 未登录 | 「登录」「注册会员」 |
| 已登录 | 昵称/会员名、手机号、`/api/member/profile` 头像、退出 |

---

## 6. 登录态与跳转

```text
memberLogin 成功
    → 解析 res.token（根级）
    → setToken(token)
    → getMemberProfile() 写入 store
    → 业务请求带 Authorization

ensureApiToken() 无 Token
    → Toast「请先登录」
    → navigateTo PAGE_LOGIN

request 401（需登录接口）
    → fedLogOut + reLaunch 登录页
```

**退出：** 仅本地 `fedLogOut`（C 端无专用 logout 接口；与 v1.3 Token 机制不冲突）。

---

## 7. 业务规则对照

| 编号 | 前端落实 |
|------|----------|
| RL1 | 注册页无验证码 |
| RL2 | 确认密码前端校验 |
| RL3 | 登录 `account` 字段；UI 提示会员 ID = 会员名称 |
| RL4 | 不区分「用户不存在/密码错误」展示 |
| RL6 | `AgreementBlock` + 提交前校验 |
| RL8 | `memberId` 仅 store/资料；登录框不用数字主键 |

---

## 8. 联调检查清单

- [ ] `GET /api/member/serviceAgreement` 返回开关与正文
- [ ] `POST /api/member/register` 成功 → 登录页预填手机号
- [ ] `POST /api/member/login` 返回 **根级** `token`、`memberId`；前端能登录并进首页
- [ ] 手机号 / 会员名称（会员 ID）均可登录
- [ ] 协议未勾选时注册/登录被后端拒绝且 Toast 文案正确
- [ ] 登录后 `GET /api/member/profile` 我的页展示昵称/手机号
- [ ] 商品详情加购无 Token 跳转登录页
- [ ] Token 过期 401 跳转登录且本地 Token 清除

---

## 9. 非本期

| 项 | 说明 |
|----|------|
| 短信验证码 | 不做 |
| 忘记密码 / 换绑手机 | 不做 |
| 以数字 `member_id` 登录 | 不做 |
| 资料编辑 / 地址管理 | 另册；接口已存在 `/profile`、`/address/**` |
| 微信 OAuth | 不做 |

---

## 10. 修订记录

| 版本 | 说明 |
|------|------|
| **v1.0** | 首版：会员登录改造、注册分包页、协议组件、我的页入口 |
| **v1.1** | 对齐后端 v1.3：登录 **根级** `token`/`memberId` 解析；登录文案「会员 ID」；联调清单补充 |

---

*文档版本：v1.1 · 关联《会员注册登录技术方案.md》v1.3*
