# 会员注册登录 — 前端技术方案(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、空 `
` 清理 | | 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*