xsh_1997 2 veckor sedan
förälder
incheckning
52ee991b53

+ 50 - 17
doc/消费者APP/会员注册登录/会员注册登录前端技术方案.md

@@ -1,9 +1,9 @@
1 1
 # 会员注册登录 — 前端技术方案(C 端 · shop-app)
2 2
 
3
-> **依据:** 《会员注册登录技术方案.md》v1.2  
3
+> **依据:** 《会员注册登录技术方案.md》v1.3  
4 4
 > **关联:** 《商品详情内页前端技术方案》(加购登录引导)、《搜索页前端技术方案》  
5 5
 > **范围:** 消费者 APP **会员注册、登录、服务协议勾选**;**不** 改后端、**不** 实现短信验证码、忘记密码、资料/地址编辑(另册)。  
6
-> **实现状态:** 页面与 API 已落地,待与 `/api/member/**` 联调。
6
+> **实现状态:** 页面与 API 已落地;登录 Token 解析已对齐 v1.3 根级响应,待联调。
7 7
 
8 8
 ---
9 9
 
@@ -12,9 +12,10 @@
12 12
 | 项 | 说明 |
13 13
 |----|------|
14 14
 | 框架 | uni-app **Vue 3** + **uview-plus** |
15
-| 请求 | `@/utils/request`;成功 `code=200`,业务数据在 `data` |
15
+| 请求 | `@/utils/request`;成功 `code=200`;业务体在 `data`,**登录例外**见 §4.2 |
16 16
 | Token | `config` → `TOKEN_KEY = shop-Admin-Token`;请求头 `Authorization: Bearer {token}` |
17
-| 登录账号 | **手机号** 或 **会员名称**(`memberCode`,产品所称「会员 ID」) |
17
+| 鉴权机制 | 与平台一致:`TokenService` 签发 JWT,Redis 会话;不足 20 分钟自动续期(后端) |
18
+| 登录账号 | **手机号** 或 **会员名称**(`memberCode`;产品 **「会员 ID」= 会员名称**,**非** 数字 `memberId`) |
18 19
 | 注册 | **无短信验证码**;`mobile + password + confirmPassword` |
19 20
 | 样式 | 登录/注册共用 `styles/auth.scss` |
20 21
 
@@ -44,10 +45,10 @@
44 45
 |------|------|------|
45 46
 | 登录页 | `shop-app/pages/login/index.vue` | 账号+密码+协议+记住账号 |
46 47
 | 注册页 | `shop-app/subpackage/account/register.vue` | 手机号注册表单 |
47
-| 协议块 | `shop-app/components/account/AgreementBlock.vue` | 勾选 + 弹窗 **`rich-text`** 渲染(同商品详情) |
48
+| 协议块 | `shop-app/components/account/AgreementBlock.vue` | 勾选 + 弹窗 **`rich-text`** 渲染 |
48 49
 | HTML 预处理 | `shop-app/utils/htmlContent.js` | 实体解码、图片 URL、空 `<p>` 清理 |
49 50
 | API | `shop-app/api/member.js` | register / login / serviceAgreement / profile |
50
-| 状态 | `shop-app/store/user.js` | 登录态、资料缓存 |
51
+| 状态 | `shop-app/store/user.js` | 登录态、**根级 token** 解析、资料缓存 |
51 52
 | 协议加载 | `shop-app/utils/memberAgreement.js` | 初始化协议配置 |
52 53
 | 校验 | `shop-app/utils/memberValidate.js` | 手机号、密码 |
53 54
 | 登录引导 | `shop-app/utils/apiAuth.js` | `ensureApiToken` → 登录页 |
@@ -69,6 +70,8 @@
69 70
 | `getMemberServiceAgreement()` | GET | `/api/member/serviceAgreement` | 匿名 |
70 71
 | `getMemberProfile()` | GET | `/api/member/profile` | Token |
71 72
 
73
+可选(本期未单独封装):`GET /api/member/serviceAgreement/status` — 仅开关;注册/登录页用完整 `serviceAgreement` 即可。
74
+
72 75
 ### 4.1 注册 Body
73 76
 
74 77
 | 字段 | 必填 | 前端 |
@@ -79,15 +82,40 @@
79 82
 | memberCode | 否 | 会员名称,空则后端自动生成 |
80 83
 | agreementAccepted | 条件 | 协议 `enabled` 时须勾选 |
81 84
 
82
-### 4.2 登录 Body
85
+**成功 `data`:** 系统主键 `memberId`(long);**不能** 作为登录账号,注册成功后跳转登录页用 **手机号** 登录。
86
+
87
+### 4.2 登录 Body 与响应(v1.3)
83 88
 
84 89
 | 字段 | 必填 | 前端 |
85 90
 |------|:----:|------|
86
-| account | 是 | 手机号或会员名称 |
91
+| account | 是 | 手机号或会员名称(产品所称会员 ID) |
87 92
 | password | 是 | — |
88 93
 | agreementAccepted | 条件 | `requireAgreementOnLogin` 为 true 时须勾选 |
89 94
 
90
-**成功 `data`:** `{ token, memberId }`(`memberId` 为系统主键,**不能**当登录账号)。
95
+**成功响应(与平台 `POST /login` 一致,字段在 JSON 根级):**
96
+
97
+```json
98
+{
99
+  "code": 200,
100
+  "msg": "操作成功",
101
+  "token": "eyJhbGciOiJIUzUxMiJ9...",
102
+  "memberId": 1001
103
+}
104
+```
105
+
106
+| 字段 | 说明 |
107
+|------|------|
108
+| `token` | 存本地 + 请求头 `Authorization: Bearer {token}` |
109
+| `memberId` | **系统主键**,仅资料展示;**不可** 填入登录框 |
110
+
111
+**`store/user.js` 解析:**
112
+
113
+```javascript
114
+const token = res.token || (res.data && res.data.token)
115
+const memberId = res.memberId != null ? res.memberId : (res.data && res.data.memberId)
116
+```
117
+
118
+> **兼容:** 后端若仍传 `mobile` 且 `account` 为空,由服务端等同 `account=mobile`;前端统一传 `account`。
91 119
 
92 120
 ### 4.3 服务协议 `data` 要点
93 121
 
@@ -107,19 +135,20 @@
107 135
 ```text
108 136
 顶栏品牌区
109 137
 └── 卡片
110
-    ├── 账号(手机号或会员名称)
138
+    ├── 账号(手机号或会员名称 / 会员 ID
111 139
     ├── 密码
112 140
     ├── 记住账号(localStorage: shop_login_account)
113 141
     ├── AgreementBlock(协议启用且 requireAgreementOnLogin 时显示)
114
-    ├── 登录按钮 → memberLogin → fetchUserInfo → switchTab 首页
142
+    ├── 登录按钮 → memberLogin → 读根级 token → fetchUserInfo → switchTab 首页
115 143
     └── 立即注册 → subpackage/account/register
116 144
 ```
117 145
 
118 146
 | 规则 | 实现 |
119 147
 |------|------|
120
-| RL3 | 单输入框 `account` |
148
+| RL3 | 单输入框 `account`;文案标明「会员 ID = 会员名称」 |
121 149
 | RL4 | 错误文案由后端统一返回,前端 Toast `msg` |
122 150
 | RL6 | `needAgreement` 计算属性控制勾选 |
151
+| RL8 | 不把 `memberId` 当登录账号 |
123 152
 | 已登录进页 | 有 Token 直接 `switchTab` 首页 |
124 153
 
125 154
 ### 5.2 注册 `subpackage/account/register`
@@ -155,6 +184,7 @@
155 184
 
156 185
 ```text
157 186
 memberLogin 成功
187
+    → 解析 res.token(根级)
158 188
     → setToken(token)
159 189
     → getMemberProfile() 写入 store
160 190
     → 业务请求带 Authorization
@@ -167,7 +197,7 @@ request 401(需登录接口)
167 197
     → fedLogOut + reLaunch 登录页
168 198
 ```
169 199
 
170
-**退出:** 仅本地 `fedLogOut`(后端无 C 端 logout 接口)。
200
+**退出:** 仅本地 `fedLogOut`(C 端无专用 logout 接口;与 v1.3 Token 机制不冲突)。
171 201
 
172 202
 ---
173 203
 
@@ -177,10 +207,10 @@ request 401(需登录接口)
177 207
 |------|----------|
178 208
 | RL1 | 注册页无验证码 |
179 209
 | RL2 | 确认密码前端校验 |
180
-| RL3 | 登录 `account` 字段 |
210
+| RL3 | 登录 `account` 字段;UI 提示会员 ID = 会员名称 |
181 211
 | RL4 | 不区分「用户不存在/密码错误」展示 |
182 212
 | RL6 | `AgreementBlock` + 提交前校验 |
183
-| RL8 | UI 提示「会员名称」;`memberId` 仅展示在资料区(若有) |
213
+| RL8 | `memberId` 仅 store/资料;登录框不用数字主键 |
184 214
 
185 215
 ---
186 216
 
@@ -188,7 +218,8 @@ request 401(需登录接口)
188 218
 
189 219
 - [ ] `GET /api/member/serviceAgreement` 返回开关与正文
190 220
 - [ ] `POST /api/member/register` 成功 → 登录页预填手机号
191
-- [ ] `POST /api/member/login` 手机号 / 会员名称均可登录
221
+- [ ] `POST /api/member/login` 返回 **根级** `token`、`memberId`;前端能登录并进首页
222
+- [ ] 手机号 / 会员名称(会员 ID)均可登录
192 223
 - [ ] 协议未勾选时注册/登录被后端拒绝且 Toast 文案正确
193 224
 - [ ] 登录后 `GET /api/member/profile` 我的页展示昵称/手机号
194 225
 - [ ] 商品详情加购无 Token 跳转登录页
@@ -202,6 +233,7 @@ request 401(需登录接口)
202 233
 |----|------|
203 234
 | 短信验证码 | 不做 |
204 235
 | 忘记密码 / 换绑手机 | 不做 |
236
+| 以数字 `member_id` 登录 | 不做 |
205 237
 | 资料编辑 / 地址管理 | 另册;接口已存在 `/profile`、`/address/**` |
206 238
 | 微信 OAuth | 不做 |
207 239
 
@@ -212,7 +244,8 @@ request 401(需登录接口)
212 244
 | 版本 | 说明 |
213 245
 |------|------|
214 246
 | **v1.0** | 首版:会员登录改造、注册分包页、协议组件、我的页入口 |
247
+| **v1.1** | 对齐后端 v1.3:登录 **根级** `token`/`memberId` 解析;登录文案「会员 ID」;联调清单补充 |
215 248
 
216 249
 ---
217 250
 
218
-*文档版本:v1.0 · 关联《会员注册登录技术方案.md》v1.2*
251
+*文档版本:v1.1 · 关联《会员注册登录技术方案.md》v1.3*

+ 1 - 1
doc/消费者APP/会员注册登录/会员注册登录技术方案.md

@@ -303,7 +303,7 @@ MemberAppServiceImpl.login
303 303
 | `POST /login`(account:手机号/会员名称) | **已实现** |
304 304
 | `BizMemberMapper.selectByMemberCode` | **已实现** |
305 305
 | `POST /sms/send` | **保留代码、非本期** |
306
-| C 端注册/登录 **前端** | **已实现**(见《会员注册登录前端技术方案.md》) |
306
+| C 端注册/登录 **前端** | **已实现**(见《会员注册登录前端技术方案.md》v1.1,含根级 token 解析) |
307 307
 
308 308
 ---
309 309
 

+ 4 - 1
shop-app/api/member.js

@@ -10,7 +10,10 @@ export function memberRegister(data) {
10 10
   })
11 11
 }
12 12
 
13
-/** 会员登录(匿名) */
13
+/**
14
+ * 会员登录(匿名)
15
+ * 成功响应与平台 /login 一致:根级 token、memberId(非 data.token)
16
+ */
14 17
 export function memberLogin(data) {
15 18
   return request({
16 19
     url: '/api/member/login',

+ 7 - 9
shop-app/components/account/AgreementBlock.vue

@@ -78,8 +78,8 @@ function openPopup() {
78 78
 .agreement-card {
79 79
 	margin-top: 24rpx;
80 80
 	// padding: 20rpx 24rpx;
81
-	background: #f5faf6;
82
-	border: 1rpx solid #e8f5e9;
81
+	// background: #f5faf6;
82
+	// border: 1rpx solid #e8f5e9;
83 83
 	border-radius: 12rpx;
84 84
 	box-sizing: border-box;
85 85
 }
@@ -90,16 +90,14 @@ function openPopup() {
90 90
 }
91 91
 
92 92
 .agreement-card__box {
93
-	width: 36rpx;
94
-	height: 36rpx;
95
-	margin-top: 2rpx;
96
-	margin-right: 16rpx;
97
-	border: 2rpx solid #a5d6a7;
98
-	border-radius: 8rpx;
93
+	width: 32rpx;
94
+	height: 32rpx;
95
+	margin-right: 12rpx;
96
+	border: 2rpx solid #c8e6c9;
97
+	border-radius: 6rpx;
99 98
 	display: flex;
100 99
 	align-items: center;
101 100
 	justify-content: center;
102
-	flex-shrink: 0;
103 101
 	background: #fff;
104 102
 }
105 103
 

+ 2 - 2
shop-app/pages/login/index.vue

@@ -10,7 +10,7 @@
10 10
 		<view class="auth-body">
11 11
 			<view class="auth-card">
12 12
 				<text class="auth-card__title">会员登录</text>
13
-				<text class="auth-card__sub">可使用手机号或会员名称登录</text>
13
+				<text class="auth-card__sub">可使用手机号或会员名称(会员 ID)登录</text>
14 14
 
15 15
 				<view class="auth-field">
16 16
 					<view class="auth-field__box">
@@ -19,7 +19,7 @@
19 19
 							v-model="form.account"
20 20
 							class="auth-field__input"
21 21
 							type="text"
22
-							placeholder="手机号或会员名称"
22
+							placeholder="手机号或会员名称(会员 ID)"
23 23
 							placeholder-class="auth-field__placeholder"
24 24
 							confirm-type="next"
25 25
 						/>

+ 6 - 3
shop-app/store/user.js

@@ -30,14 +30,17 @@ export function useUserStore() {
30 30
     const password = payload.password
31 31
     const agreementAccepted = !!payload.agreementAccepted
32 32
     return memberLogin({ account, password, agreementAccepted }).then((res) => {
33
-      const data = res.data || {}
34
-      const token = data.token
33
+      // v1.3:与平台 /login 一致,token、memberId 在响应根级;兼容旧版 data 包裹
34
+      const token = res.token || (res.data && res.data.token)
35
+      const memberId = res.memberId != null && res.memberId !== ''
36
+        ? res.memberId
37
+        : (res.data && res.data.memberId)
35 38
       if (!token) {
36 39
         return Promise.reject(new Error('登录失败,未返回令牌'))
37 40
       }
38 41
       setToken(token)
39 42
       state.token = token
40
-      state.memberId = data.memberId || ''
43
+      state.memberId = memberId != null ? memberId : ''
41 44
       return res
42 45
     })
43 46
   }

+ 1 - 1
shop-app/styles/auth.scss

@@ -1,7 +1,7 @@
1 1
 @import '@/styles/morandi.scss';
2 2
 
3 3
 .auth-page {
4
-	min-height: 100%;
4
+	height: 100vh;
5 5
 	display: flex;
6 6
 	flex-direction: column;
7 7
 	background: $morandi-bg-page;

+ 1 - 1
shop-app/subpackage/account/register.vue

@@ -64,7 +64,7 @@
64 64
 								v-model="form.memberCode"
65 65
 								class="auth-field__input"
66 66
 								type="text"
67
-								placeholder="会员名称(选填,不填自动生成)"
67
+								placeholder="会员名称/会员 ID(选填,不填自动生成)"
68 68
 								placeholder-class="auth-field__placeholder"
69 69
 							/>
70 70
 						</view>