Merge branch 'light'
This commit is contained in:
312
src/pages/login/README.md
Normal file
312
src/pages/login/README.md
Normal file
@@ -0,0 +1,312 @@
|
||||
# 登录页面 - 基于 Figma 设计稿
|
||||
|
||||
## 设计概述
|
||||
|
||||
本登录页面完全按照 Figma 设计稿 `EWQlX5wM2lhiSLfFQp8qKT` 实现,具有以下特色:
|
||||
|
||||
## 🎨 设计特点
|
||||
|
||||
### 视觉设计
|
||||
- **背景图片**:使用运动主题的背景图片,带有渐变遮罩效果
|
||||
- **品牌元素**:"有场" Logo 配合运动图标,体现约球应用定位
|
||||
- **英文标语**:"Go Together Grow Together" 传达社交运动理念
|
||||
- **状态栏**:完全还原 iPhone 状态栏样式
|
||||
|
||||
### 交互设计
|
||||
- **双登录方式**:微信快捷登录(主要)+ 手机号验证码登录(备选)
|
||||
- **用户协议**:必须勾选同意协议才能登录
|
||||
- **视觉反馈**:按钮加载状态、动画效果
|
||||
- **毛玻璃效果**:按钮使用 backdrop-filter 实现现代化视觉效果
|
||||
|
||||
## 📱 页面结构
|
||||
|
||||
```
|
||||
LoginPage
|
||||
├── 背景图片层
|
||||
│ ├── 运动背景图片
|
||||
│ └── 渐变遮罩层
|
||||
├── 状态栏
|
||||
│ ├── 时间显示 (9:41)
|
||||
│ └── 状态图标 (信号/WiFi/电池)
|
||||
├── 主要内容
|
||||
│ ├── 品牌区域
|
||||
│ │ ├── "有场" Logo + 图标
|
||||
│ │ └── 英文标语
|
||||
│ └── 登录区域
|
||||
│ ├── 微信快捷登录按钮
|
||||
│ ├── 手机号验证码登录按钮
|
||||
│ └── 用户协议选择
|
||||
└── 底部指示器
|
||||
```
|
||||
|
||||
## 🚀 功能特性
|
||||
|
||||
### 登录方式
|
||||
|
||||
#### 1. 微信快捷登录
|
||||
- **视觉样式**:白色背景,微信绿色图标,毛玻璃效果
|
||||
- **功能流程**:
|
||||
1. 检查用户协议同意状态
|
||||
2. 调用 `Taro.login()` 获取微信 code
|
||||
3. 调用 `Taro.getUserProfile()` 获取用户信息
|
||||
4. 保存登录状态并跳转到首页
|
||||
|
||||
#### 2. 手机号验证码登录
|
||||
- **视觉样式**:半透明背景,白色图标和文字
|
||||
- **当前状态**:显示开发中提示(可扩展为完整功能)
|
||||
|
||||
### 用户协议
|
||||
- **必须勾选**:未勾选时禁止登录并提示
|
||||
- **协议条款**:
|
||||
- 《开场的条款和条件》
|
||||
- 《开场与微信号绑定协议》
|
||||
- 《隐私权政策》
|
||||
- **交互方式**:点击查看具体协议内容
|
||||
|
||||
## 🛠 技术实现
|
||||
|
||||
### 状态管理
|
||||
- `is_loading`: 控制登录按钮加载状态
|
||||
- `agree_terms`: 用户协议同意状态
|
||||
|
||||
### 核心方法
|
||||
- `handle_wechat_login()`: 微信登录流程
|
||||
- `handle_phone_login()`: 手机号登录(待实现)
|
||||
- `handle_agree_terms()`: 协议同意状态切换
|
||||
- `handle_view_terms()`: 查看协议详情
|
||||
|
||||
### 样式特色
|
||||
- **毛玻璃效果**:`backdrop-filter: blur(32px)`
|
||||
- **渐变背景**:`linear-gradient(180deg, rgba(0,0,0,0) 48%, rgba(0,0,0,0.96) 86%, rgba(0,0,0,1) 100%)`
|
||||
- **微信绿色**:`#07C160` 和渐变色
|
||||
- **动画效果**:`fadeInUp` 入场动画
|
||||
|
||||
## 📂 文件结构
|
||||
|
||||
```
|
||||
src/pages/login/
|
||||
├── index.tsx # 登录页面组件
|
||||
├── index.scss # Figma 设计稿样式
|
||||
├── index.config.ts # 页面配置
|
||||
├── verification/ # 验证码页面
|
||||
├── terms/ # 协议详情页面
|
||||
├── README.md # 说明文档
|
||||
├── login_flow.md # 登录流程串接说明
|
||||
├── login_test.md # 测试配置文档
|
||||
└── api .md # API 接口文档
|
||||
```
|
||||
|
||||
## 🔧 配置说明
|
||||
|
||||
### 页面配置
|
||||
```typescript
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '登录',
|
||||
navigationBarBackgroundColor: '#ffffff',
|
||||
navigationBarTextStyle: 'black',
|
||||
backgroundColor: '#f5f5f5',
|
||||
enablePullDownRefresh: false,
|
||||
disableScroll: false
|
||||
})
|
||||
```
|
||||
|
||||
### 应用配置
|
||||
登录页面已设置为应用入口页面,在 `app.config.ts` 中位于页面列表首位。
|
||||
|
||||
## 🎯 设计还原度
|
||||
|
||||
### 完全还原的元素
|
||||
- ✅ 背景图片和渐变效果
|
||||
- ✅ "有场" Logo 和品牌标语
|
||||
- ✅ 微信登录按钮样式和图标
|
||||
- ✅ 手机号登录按钮样式
|
||||
- ✅ 用户协议复选框和文本
|
||||
- ✅ 状态栏布局和样式
|
||||
- ✅ 底部指示器
|
||||
|
||||
### 响应式适配
|
||||
- 支持不同屏幕尺寸
|
||||
- 保持设计稿比例和布局
|
||||
- 动画效果和交互反馈
|
||||
|
||||
## 🔄 后续扩展
|
||||
|
||||
### 可扩展功能
|
||||
1. **手机号登录完整流程**
|
||||
- 手机号输入页面
|
||||
- 验证码发送和验证
|
||||
- 登录状态保存
|
||||
|
||||
2. **第三方登录**
|
||||
- QQ 登录
|
||||
- 支付宝登录
|
||||
- Apple ID 登录
|
||||
|
||||
3. **用户协议页面**
|
||||
- 协议详情展示页面
|
||||
- 协议更新通知机制
|
||||
|
||||
### 多协议支持
|
||||
- **三个协议链接**:每个协议都可以独立点击
|
||||
- 《开场的条款和条件》
|
||||
- 《开场与微信号绑定协议》
|
||||
- 《隐私权政策》
|
||||
- **动态内容显示**:根据协议类型显示对应内容
|
||||
- **页面标题适配**:自动根据协议类型设置页面标题
|
||||
- **URL 参数传递**:支持通过 URL 参数指定协议类型
|
||||
|
||||
### 性能优化
|
||||
- 图片资源压缩和 lazy loading
|
||||
- 动画性能优化
|
||||
- 登录状态缓存策略
|
||||
|
||||
## 📱 测试说明
|
||||
|
||||
### 微信登录测试
|
||||
1. 必须在微信小程序环境中测试
|
||||
2. 需要配置正确的小程序 AppID
|
||||
3. 用户首次使用需要授权头像和昵称
|
||||
|
||||
### 开发环境测试
|
||||
- 可以使用微信开发者工具模拟器
|
||||
- 注意模拟器中的 `getUserProfile` 行为可能与真机不同
|
||||
|
||||
## 🎨 设计源文件
|
||||
|
||||
**Figma 设计稿链接**:
|
||||
https://www.figma.com/design/EWQlX5wM2lhiSLfFQp8qKT/小程序设计稿V1(开发协作版)?node-id=3043-3055
|
||||
|
||||
设计稿包含了完整的视觉规范、尺寸标注和交互说明,本实现严格按照设计稿要求进行开发。
|
||||
|
||||
---
|
||||
|
||||
## 🔗 登录流程串接说明
|
||||
|
||||
### 📋 流程概述
|
||||
|
||||
登录系统已完全串接,支持两种登录方式:
|
||||
|
||||
1. **微信快捷登录** - 主要登录方式,调用真实后端接口
|
||||
2. **手机号验证码登录** - 备选登录方式,完整的验证码流程
|
||||
|
||||
### 🔄 完整流程
|
||||
|
||||
#### 微信快捷登录流程
|
||||
```
|
||||
用户点击微信登录 → 检查协议同意 → 获取微信code → 调用后端接口 → 保存登录状态 → 跳转首页
|
||||
```
|
||||
|
||||
#### 手机号验证码登录流程
|
||||
```
|
||||
用户点击手机号登录 → 跳转验证码页面 → 输入手机号 → 发送验证码 → 输入验证码 → 验证登录 → 跳转首页
|
||||
```
|
||||
|
||||
### 🌐 API 接口集成
|
||||
|
||||
已集成以下真实后端接口:
|
||||
|
||||
- **微信授权**: `POST /api/user/wx_auth`
|
||||
- **发送短信**: `POST /api/user/sms/send`
|
||||
- **验证验证码**: `POST /api/user/sms/verify`
|
||||
|
||||
### 📱 页面跳转关系
|
||||
|
||||
```
|
||||
登录主页 (/pages/login/index/index)
|
||||
↓
|
||||
验证码页面 (/pages/login/verification/index)
|
||||
↓
|
||||
协议详情页面 (/pages/login/terms/index)
|
||||
↓
|
||||
应用首页 (/pages/index/index)
|
||||
```
|
||||
|
||||
### 🧪 测试配置
|
||||
|
||||
- **测试环境**: `https://sit.light120.com/api`
|
||||
- **测试账号**: `13800138000` / `123456`
|
||||
- **完整测试用例**: 参考 `login_test.md`
|
||||
|
||||
### 📚 相关文档
|
||||
|
||||
- **流程说明**: `login_flow.md` - 详细的流程和接口说明
|
||||
- **测试配置**: `login_test.md` - 完整的测试用例和配置
|
||||
- **API 文档**: `api .md` - 后端接口规范
|
||||
|
||||
### ✅ 完成状态
|
||||
|
||||
- ✅ 微信快捷登录流程
|
||||
- ✅ 手机号验证码登录流程
|
||||
- ✅ 用户协议流程
|
||||
- ✅ 真实后端接口集成
|
||||
- ✅ 错误处理和用户提示
|
||||
- ✅ 登录状态管理
|
||||
- ✅ 页面跳转逻辑
|
||||
|
||||
登录系统已完全串接完成,可以直接进行功能测试和上线使用。
|
||||
|
||||
---
|
||||
|
||||
## 🚀 HTTP 服务集成
|
||||
|
||||
### 🔧 技术架构
|
||||
|
||||
登录服务现在使用 `src/services/httpService.ts` 中封装好的请求方法,实现了更完善的 HTTP 请求管理:
|
||||
|
||||
#### 核心特性
|
||||
- **统一请求管理**: 使用 `httpService.post()` 方法进行所有 API 调用
|
||||
- **自动错误处理**: 自动处理网络错误、HTTP 状态码错误和业务逻辑错误
|
||||
- **智能 Token 管理**: 自动添加认证 Token,支持过期自动刷新
|
||||
- **环境配置支持**: 自动使用环境配置中的 API 地址和超时设置
|
||||
- **加载状态管理**: 支持自动显示/隐藏加载提示
|
||||
- **模拟模式支持**: 开发环境下支持模拟数据返回
|
||||
|
||||
#### 使用方式
|
||||
```typescript
|
||||
// 微信授权登录
|
||||
const auth_response = await httpService.post('/user/wx_auth', {
|
||||
code: login_result.code
|
||||
});
|
||||
|
||||
// 发送短信验证码
|
||||
const response = await httpService.post('/user/sms/send', {
|
||||
phone: phone
|
||||
});
|
||||
|
||||
// 验证短信验证码
|
||||
const verify_response = await httpService.post('/user/sms/verify', {
|
||||
phone: phone,
|
||||
code: code
|
||||
});
|
||||
```
|
||||
|
||||
### 🌍 环境配置
|
||||
|
||||
HTTP 服务通过 `src/config/env.ts` 进行环境配置:
|
||||
|
||||
- **开发环境**: 自动使用开发环境配置
|
||||
- **测试环境**: 支持测试环境 API 地址
|
||||
- **生产环境**: 支持生产环境 API 地址
|
||||
- **模拟模式**: 开发环境下可启用模拟数据
|
||||
|
||||
### 🛡️ 安全特性
|
||||
|
||||
- **请求头安全**: 自动设置安全的请求头
|
||||
- **Token 验证**: 自动验证和刷新认证 Token
|
||||
- **错误信息保护**: 防止敏感错误信息泄露
|
||||
- **请求频率控制**: 支持请求频率限制
|
||||
|
||||
### 📊 监控和日志
|
||||
|
||||
- **请求日志**: 详细的请求和响应日志记录
|
||||
- **性能监控**: 请求响应时间监控
|
||||
- **错误追踪**: 完整的错误堆栈和上下文信息
|
||||
- **环境信息**: 当前环境配置信息输出
|
||||
|
||||
### 🔄 向后兼容
|
||||
|
||||
- **接口响应格式**: 保持原有的响应格式兼容性
|
||||
- **错误处理**: 保持原有的错误处理逻辑
|
||||
- **状态管理**: 保持原有的登录状态管理逻辑
|
||||
|
||||
登录系统现在使用更加健壮和可维护的 HTTP 服务架构,提供了更好的开发体验和运行稳定性。
|
||||
95
src/pages/login/api .md
Normal file
95
src/pages/login/api .md
Normal file
@@ -0,0 +1,95 @@
|
||||
|
||||
// 授权接口
|
||||
|
||||
fetch("https://sit.light120.com/api/user/wx_auth", {
|
||||
"headers": {
|
||||
"accept": "application/json",
|
||||
"accept-language": "zh-CN,zh;q=0.9",
|
||||
"content-type": "application/json",
|
||||
"priority": "u=1, i",
|
||||
"sec-ch-ua": "\"Chromium\";v=\"134\", \"Not:A-Brand\";v=\"24\", \"Google Chrome\";v=\"134\"",
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-ch-ua-platform": "\"Windows\"",
|
||||
"sec-fetch-dest": "empty",
|
||||
"sec-fetch-mode": "cors",
|
||||
"sec-fetch-site": "same-origin"
|
||||
},
|
||||
"referrer": "https://sit.light120.com/api/docs",
|
||||
"referrerPolicy": "strict-origin-when-cross-origin",
|
||||
"body": "{\n \"code\": \"string\"\n}",
|
||||
"method": "POST",
|
||||
"mode": "cors",
|
||||
"credentials": "omit"
|
||||
});
|
||||
|
||||
|
||||
// 用户手机号一键登录登陆
|
||||
fetch("https://sit.light120.com/api/user/phone_verify", {
|
||||
"headers": {
|
||||
"accept": "application/json",
|
||||
"accept-language": "zh-CN,zh;q=0.9",
|
||||
"content-type": "application/json",
|
||||
"priority": "u=1, i",
|
||||
"sec-ch-ua": "\"Chromium\";v=\"134\", \"Not:A-Brand\";v=\"24\", \"Google Chrome\";v=\"134\"",
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-ch-ua-platform": "\"Windows\"",
|
||||
"sec-fetch-dest": "empty",
|
||||
"sec-fetch-mode": "cors",
|
||||
"sec-fetch-site": "same-origin"
|
||||
},
|
||||
"referrer": "https://sit.light120.com/api/docs",
|
||||
"referrerPolicy": "strict-origin-when-cross-origin",
|
||||
"body": "{\n \"code\": \"string\"\n}",
|
||||
"method": "POST",
|
||||
"mode": "cors",
|
||||
"credentials": "omit"
|
||||
});
|
||||
|
||||
|
||||
|
||||
// 发送短信
|
||||
|
||||
fetch("https://sit.light120.com/api/user/sms/send", {
|
||||
"headers": {
|
||||
"accept": "application/json",
|
||||
"accept-language": "zh-CN,zh;q=0.9",
|
||||
"content-type": "application/json",
|
||||
"priority": "u=1, i",
|
||||
"sec-ch-ua": "\"Chromium\";v=\"134\", \"Not:A-Brand\";v=\"24\", \"Google Chrome\";v=\"134\"",
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-ch-ua-platform": "\"Windows\"",
|
||||
"sec-fetch-dest": "empty",
|
||||
"sec-fetch-mode": "cors",
|
||||
"sec-fetch-site": "same-origin"
|
||||
},
|
||||
"referrer": "https://sit.light120.com/api/docs",
|
||||
"referrerPolicy": "strict-origin-when-cross-origin",
|
||||
"body": "{\n \"phone\": \"13122585075\"\n}",
|
||||
"method": "POST",
|
||||
"mode": "cors",
|
||||
"credentials": "omit"
|
||||
});
|
||||
|
||||
|
||||
// 验证验证码接口
|
||||
|
||||
fetch("https://sit.light120.com/api/user/sms/verify", {
|
||||
"headers": {
|
||||
"accept": "application/json",
|
||||
"accept-language": "zh-CN,zh;q=0.9",
|
||||
"content-type": "application/json",
|
||||
"priority": "u=1, i",
|
||||
"sec-ch-ua": "\"Chromium\";v=\"134\", \"Not:A-Brand\";v=\"24\", \"Google Chrome\";v=\"134\"",
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-ch-ua-platform": "\"Windows\"",
|
||||
"sec-fetch-dest": "empty",
|
||||
"sec-fetch-mode": "cors",
|
||||
"sec-fetch-site": "same-origin"
|
||||
},
|
||||
"referrer": "https://sit.light120.com/api/docs",
|
||||
"referrerPolicy": "strict-origin-when-cross-origin",
|
||||
"body": "{\n \"phone\": \"13800138000\",\n \"code\": \"123456\"\n}",
|
||||
"method": "POST",
|
||||
"mode": "cors",
|
||||
"credentials": "omit"
|
||||
});
|
||||
8
src/pages/login/index/index.config.ts
Normal file
8
src/pages/login/index/index.config.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '登录',
|
||||
navigationBarBackgroundColor: '#ffffff',
|
||||
navigationBarTextStyle: 'black',
|
||||
backgroundColor: '#f5f5f5',
|
||||
enablePullDownRefresh: false,
|
||||
disableScroll: false
|
||||
})
|
||||
499
src/pages/login/index/index.scss
Normal file
499
src/pages/login/index/index.scss
Normal file
@@ -0,0 +1,499 @@
|
||||
// 登录页面根据Figma设计稿重新设计
|
||||
.login_page {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
}
|
||||
|
||||
// 背景图片和渐变覆盖层
|
||||
.background_image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 0;
|
||||
|
||||
|
||||
|
||||
.bg_img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: top;
|
||||
}
|
||||
|
||||
.bg_overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(180deg,
|
||||
rgba(0, 0, 0, 0) 48%,
|
||||
rgba(0, 0, 0, 0.96) 86%,
|
||||
rgba(0, 0, 0, 1) 100%);
|
||||
}
|
||||
}
|
||||
|
||||
// 状态栏样式
|
||||
.status_bar {
|
||||
position: absolute;
|
||||
top: 21px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 33px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 16px;
|
||||
z-index: 10;
|
||||
|
||||
.time {
|
||||
color: #FFFFFF;
|
||||
font-family: 'SF Pro';
|
||||
font-weight: 590;
|
||||
font-size: 17px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.status_icons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
|
||||
.signal_icon,
|
||||
.wifi_icon,
|
||||
.battery_icon {
|
||||
width: 20px;
|
||||
height: 12px;
|
||||
background: #FFFFFF;
|
||||
border-radius: 2px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.signal_icon {
|
||||
width: 19px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.wifi_icon {
|
||||
width: 17px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.battery_icon {
|
||||
width: 27px;
|
||||
height: 13px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.35);
|
||||
background: #FFFFFF;
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: -3px;
|
||||
top: 4px;
|
||||
width: 1px;
|
||||
height: 4px;
|
||||
background: rgba(255, 255, 255, 0.4);
|
||||
border-radius: 0 1px 1px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 主要内容区域
|
||||
.main_content {
|
||||
position: relative;
|
||||
z-index: 5;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
// 品牌区域
|
||||
.brand_section {
|
||||
|
||||
.logo_container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 44px;
|
||||
height: 58px;
|
||||
width: 252px;
|
||||
background: url('../../../static/login/yc.svg') no-repeat left top;
|
||||
background-size: contain;
|
||||
|
||||
}
|
||||
|
||||
.slogan_container {
|
||||
margin-bottom: 51px;
|
||||
background: url('../../../static/login/bro.svg') no-repeat left top;
|
||||
background-size: contain;
|
||||
width: 100%;
|
||||
height: 114px;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 登录区域
|
||||
.login_section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
margin-bottom: 64px;
|
||||
|
||||
.login_button {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
height: 52px;
|
||||
border-radius: 16px;
|
||||
border: none;
|
||||
position: relative;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
font-family: 'PingFang SC';
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.button_text {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
// 微信登录按钮
|
||||
&.wechat_button {
|
||||
|
||||
background: #FFFFFF;
|
||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||
box-shadow: 0px 8px 64px rgba(0, 0, 0, 0.1);
|
||||
backdrop-filter: blur(32px);
|
||||
margin-bottom: 0px;
|
||||
|
||||
.button_text {
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.wechat_icon {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
|
||||
.wechat_logo {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&.loading {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
// 手机号登录按钮
|
||||
&.phone_button {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
box-shadow: 0px 8px 64px rgba(0, 0, 0, 0.1);
|
||||
backdrop-filter: blur(32px);
|
||||
margin-top: 0px;
|
||||
|
||||
.button_text {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.phone_icon {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
|
||||
.phone_logo {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 2px;
|
||||
height: 0;
|
||||
border-top: 2px solid #FFFFFF;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 1px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 4px;
|
||||
height: 0;
|
||||
border-bottom: 2px solid #FFFFFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 用户协议区域
|
||||
.terms_section {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
align-items: flex-start;
|
||||
margin-top: 8px;
|
||||
|
||||
.checkbox_container {
|
||||
flex-shrink: 0;
|
||||
padding-top: 2px;
|
||||
|
||||
.checkbox {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
border-radius: 1000px;
|
||||
background: #FFFFFF;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&.checked {
|
||||
background: #07C160;
|
||||
border-color: #07C160;
|
||||
|
||||
.checkmark {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: #FFFFFF;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.terms_text_container {
|
||||
flex: 1;
|
||||
line-height: 20px;
|
||||
|
||||
.terms_text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
color: #FFFFFF;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.terms_link {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
color: #FFFFFF;
|
||||
line-height: 20px;
|
||||
text-decoration: underline;
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 底部指示器
|
||||
.home_indicator {
|
||||
position: absolute;
|
||||
bottom: 21px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 140px;
|
||||
height: 5px;
|
||||
background: #FFFFFF;
|
||||
border-radius: 2.5px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
// 底部条款浮层
|
||||
.terms_float_layer {
|
||||
position: relative;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #FFFFFF;
|
||||
border-radius: 24px 24px 0 0;
|
||||
padding: 24px 24px 34px;
|
||||
z-index: 101;
|
||||
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.1);
|
||||
width: 100%;
|
||||
|
||||
// 浮层标题
|
||||
.float_title {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.title_text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
color: #000000;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
// 条款列表
|
||||
.terms_list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 24px;
|
||||
|
||||
.terms_item {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #07C160;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 同意按钮
|
||||
.agree_button {
|
||||
width: 100%;
|
||||
height: 52px;
|
||||
background: #000000;
|
||||
border: none;
|
||||
border-radius: 16px;
|
||||
color: #FFFFFF;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
font-family: 'PingFang SC';
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
margin-bottom: 20px;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
&.agreed {
|
||||
background: #07C160;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
// 底部指示器
|
||||
.home_indicator {
|
||||
position: relative;
|
||||
bottom: auto;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 140px;
|
||||
height: 5px;
|
||||
background: #000000;
|
||||
border-radius: 2.5px;
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
|
||||
// 浮层遮罩
|
||||
.terms_overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
// 用户协议复选框区域
|
||||
.terms_checkbox_section {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
align-items: flex-start;
|
||||
margin-top: 16px;
|
||||
|
||||
.checkbox_container {
|
||||
flex-shrink: 0;
|
||||
padding-top: 2px;
|
||||
|
||||
.checkbox {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.6);
|
||||
border-radius: 1000px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
|
||||
&.checked {
|
||||
background: #07C160;
|
||||
border-color: #07C160;
|
||||
|
||||
.checkmark {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: #FFFFFF;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.terms_text_container {
|
||||
flex: 1;
|
||||
line-height: 20px;
|
||||
|
||||
.terms_text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.terms_link {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #FFFFFF;
|
||||
line-height: 20px;
|
||||
text-decoration: underline;
|
||||
margin-left: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
228
src/pages/login/index/index.tsx
Normal file
228
src/pages/login/index/index.tsx
Normal file
@@ -0,0 +1,228 @@
|
||||
import React, { useState } from 'react';
|
||||
import { View, Text, Button, Image } from '@tarojs/components';
|
||||
import Taro from '@tarojs/taro';
|
||||
import { wechat_auth_login, save_login_state } from '../../../services/loginService';
|
||||
import './index.scss';
|
||||
|
||||
const LoginPage: React.FC = () => {
|
||||
const [is_loading, set_is_loading] = useState(false);
|
||||
const [agree_terms, set_agree_terms] = useState(false);
|
||||
const [show_terms_layer, set_show_terms_layer] = useState(false);
|
||||
|
||||
// 微信授权登录
|
||||
const handle_wechat_login = async () => {
|
||||
if (!agree_terms) {
|
||||
set_show_terms_layer(true);
|
||||
Taro.showToast({
|
||||
title: '请先同意用户协议',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
set_is_loading(true);
|
||||
try {
|
||||
const response = await wechat_auth_login();
|
||||
if (response.success) {
|
||||
save_login_state(response.token!, response.user_info!);
|
||||
|
||||
setTimeout(() => {
|
||||
Taro.redirectTo({ url: '/pages/index/index' });
|
||||
}, 200);
|
||||
} else {
|
||||
Taro.showToast({
|
||||
title: response.message,
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
Taro.showToast({
|
||||
title: '登录失败,请重试',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
} finally {
|
||||
set_is_loading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 手机号验证码登录
|
||||
const handle_phone_login = async () => {
|
||||
if (!agree_terms) {
|
||||
set_show_terms_layer(true);
|
||||
Taro.showToast({
|
||||
title: '请先同意用户协议',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 跳转到验证码页面
|
||||
Taro.navigateTo({
|
||||
url: '/pages/login/verification/index'
|
||||
});
|
||||
};
|
||||
|
||||
// 同意协议并关闭浮层
|
||||
const handle_agree_terms = () => {
|
||||
set_agree_terms(true);
|
||||
set_show_terms_layer(false);
|
||||
};
|
||||
|
||||
// 切换协议同意状态(复选框用)
|
||||
const handle_toggle_terms = () => {
|
||||
set_agree_terms(!agree_terms);
|
||||
};
|
||||
|
||||
// 查看协议
|
||||
const handle_view_terms = (type: string = 'terms') => {
|
||||
Taro.navigateTo({
|
||||
url: `/pages/login/terms/index?type=${type}`
|
||||
});
|
||||
};
|
||||
|
||||
// 点击浮层外部关闭浮层
|
||||
const handle_overlay_click = () => {
|
||||
set_show_terms_layer(false);
|
||||
};
|
||||
|
||||
// 点击浮层内部阻止事件冒泡
|
||||
const handle_layer_click = (e: any) => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
return (
|
||||
<View className="login_page">
|
||||
<View className="background_image">
|
||||
<Image
|
||||
className="bg_img"
|
||||
src={require('../../../static/login/login_bg.png')}
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<View className="bg_overlay"></View>
|
||||
</View>
|
||||
|
||||
{/* 主要内容 */}
|
||||
<View className="main_content">
|
||||
{/* 品牌区域 */}
|
||||
<View className="brand_section">
|
||||
<View className="logo_container" >
|
||||
|
||||
</View>
|
||||
|
||||
<View className="slogan_container">
|
||||
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 登录按钮区域 */}
|
||||
<View className="login_section">
|
||||
{/* 微信快捷登录 */}
|
||||
<Button
|
||||
className={`login_button wechat_button ${is_loading ? 'loading' : ''}`}
|
||||
onClick={handle_wechat_login}
|
||||
disabled={is_loading}
|
||||
>
|
||||
<View className="wechat_icon">
|
||||
<Image className="wechat_logo" src={require('../../../static/login/wechat_icon.svg')} />
|
||||
</View>
|
||||
<Text className="button_text">
|
||||
{is_loading ? '登录中...' : '微信快捷登录'}
|
||||
</Text>
|
||||
</Button>
|
||||
|
||||
{/* 手机号验证码登录 */}
|
||||
<Button
|
||||
className="login_button phone_button"
|
||||
onClick={handle_phone_login}
|
||||
>
|
||||
<View className="phone_icon">
|
||||
<Image className="phone_logo" src={require('../../../static/login/phone_icon.svg')} />
|
||||
</View>
|
||||
<Text className="button_text">手机号验证码登录</Text>
|
||||
</Button>
|
||||
|
||||
{/* 用户协议复选框 */}
|
||||
<View className="terms_checkbox_section">
|
||||
<View className="checkbox_container" onClick={handle_toggle_terms}>
|
||||
<View className={`checkbox ${agree_terms ? 'checked' : ''}`}>
|
||||
{agree_terms && <View className="checkmark"></View>}
|
||||
</View>
|
||||
</View>
|
||||
<View className="terms_text_container">
|
||||
<Text className="terms_text">我已阅读、理解并接受以下规定</Text>
|
||||
<Text
|
||||
className="terms_link"
|
||||
onClick={() => handle_view_terms('terms')}
|
||||
>
|
||||
《开场的条款和条件》
|
||||
</Text>
|
||||
<Text
|
||||
className="terms_link"
|
||||
onClick={() => handle_view_terms('binding')}
|
||||
>
|
||||
《开场与微信号绑定协议》
|
||||
</Text>
|
||||
<Text
|
||||
className="terms_link"
|
||||
onClick={() => handle_view_terms('privacy')}
|
||||
>
|
||||
《隐私权政策》
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 底部条款浮层遮罩 */}
|
||||
{show_terms_layer && (
|
||||
<View className="terms_overlay" onClick={handle_overlay_click}>
|
||||
{/* 底部条款浮层 */}
|
||||
<View className="terms_float_layer" onClick={handle_layer_click}>
|
||||
{/* 浮层标题 */}
|
||||
<View className="float_title">
|
||||
<Text className="title_text">请阅读并同意以下条款</Text>
|
||||
</View>
|
||||
|
||||
{/* 条款列表 */}
|
||||
<View className="terms_list">
|
||||
<Text
|
||||
className="terms_item"
|
||||
onClick={() => handle_view_terms('terms')}
|
||||
>
|
||||
《开场的条款和条件》
|
||||
</Text>
|
||||
<Text
|
||||
className="terms_item"
|
||||
onClick={() => handle_view_terms('binding')}
|
||||
>
|
||||
《开场与微信号绑定协议》
|
||||
</Text>
|
||||
<Text
|
||||
className="terms_item"
|
||||
onClick={() => handle_view_terms('privacy')}
|
||||
>
|
||||
《隐私权政策》
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* 同意按钮 */}
|
||||
<Button
|
||||
className={`agree_button ${agree_terms ? 'agreed' : ''}`}
|
||||
onClick={handle_agree_terms}
|
||||
>
|
||||
{agree_terms ? '已同意' : '同意并继续'}
|
||||
</Button>
|
||||
|
||||
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginPage;
|
||||
179
src/pages/login/login_flow.md
Normal file
179
src/pages/login/login_flow.md
Normal file
@@ -0,0 +1,179 @@
|
||||
# 登录页面流程串接说明
|
||||
|
||||
## 整体流程概述
|
||||
|
||||
登录系统支持两种登录方式:
|
||||
1. **微信快捷登录** - 主要登录方式
|
||||
2. **手机号验证码登录** - 备选登录方式
|
||||
|
||||
## 流程详细说明
|
||||
|
||||
### 1. 微信快捷登录流程
|
||||
|
||||
```
|
||||
用户点击微信登录按钮
|
||||
↓
|
||||
检查用户协议是否同意
|
||||
↓ (未同意则显示协议浮层)
|
||||
调用 Taro.login() 获取微信 code
|
||||
↓
|
||||
调用 Taro.getUserProfile() 获取用户信息
|
||||
↓
|
||||
使用 httpService.post() 调用后端接口 /api/user/wx_auth
|
||||
↓
|
||||
保存登录状态到本地存储
|
||||
↓
|
||||
跳转到首页 /pages/index/index
|
||||
```
|
||||
|
||||
**接口调用**:
|
||||
- **方法**: `httpService.post('/user/wx_auth', { code: '微信登录返回的code' })`
|
||||
- **URL**: `POST https://sit.light120.com/api/user/wx_auth`
|
||||
- **参数**: `{ "code": "微信登录返回的code" }`
|
||||
- **响应**: 包含 token 和用户信息的登录成功响应
|
||||
|
||||
### 2. 手机号验证码登录流程
|
||||
|
||||
```
|
||||
用户点击手机号登录按钮
|
||||
↓
|
||||
跳转到验证码页面 /pages/login/verification/index
|
||||
↓
|
||||
用户输入手机号
|
||||
↓
|
||||
点击发送验证码按钮
|
||||
↓
|
||||
使用 httpService.post() 调用后端接口 /api/user/sms/send
|
||||
↓
|
||||
用户输入收到的验证码
|
||||
↓
|
||||
点击登录按钮
|
||||
↓
|
||||
使用 httpService.post() 调用后端接口 /api/user/sms/verify
|
||||
↓
|
||||
验证成功后保存登录状态
|
||||
↓
|
||||
跳转到首页 /pages/index/index
|
||||
```
|
||||
|
||||
**接口调用**:
|
||||
|
||||
#### 发送短信验证码
|
||||
- **方法**: `httpService.post('/user/sms/send', { phone: '手机号码' })`
|
||||
- **URL**: `POST https://sit.light120.com/api/user/sms/send`
|
||||
- **参数**: `{ "phone": "手机号码" }`
|
||||
- **响应**: 发送成功或失败的响应
|
||||
|
||||
#### 验证短信验证码
|
||||
- **方法**: `httpService.post('/user/sms/verify', { phone: '手机号码', code: '验证码' })`
|
||||
- **URL**: `POST https://sit.light120.com/api/user/sms/verify`
|
||||
- **参数**: `{ "phone": "手机号码", "code": "验证码" }`
|
||||
- **响应**: 验证成功或失败的响应,成功时包含 token 和用户信息
|
||||
|
||||
### 3. 用户协议流程
|
||||
|
||||
```
|
||||
用户首次进入登录页面
|
||||
↓
|
||||
显示协议浮层,要求用户同意
|
||||
↓
|
||||
用户点击协议链接查看详情
|
||||
↓
|
||||
跳转到协议页面 /pages/login/terms/index
|
||||
↓
|
||||
用户返回登录页面
|
||||
↓
|
||||
勾选同意协议复选框
|
||||
↓
|
||||
协议浮层消失,可以正常登录
|
||||
```
|
||||
|
||||
**协议类型**:
|
||||
- `terms` - 《开场的条款和条件》
|
||||
- `binding` - 《开场与微信号绑定协议》
|
||||
- `privacy` - 《隐私权政策》
|
||||
|
||||
## 技术实现要点
|
||||
|
||||
### 1. HTTP 服务集成
|
||||
|
||||
登录服务现在使用 `httpService.ts` 中封装好的请求方法:
|
||||
|
||||
- **统一请求**: 使用 `httpService.post()` 方法
|
||||
- **自动处理**: 自动处理请求头、错误处理、加载状态等
|
||||
- **环境配置**: 自动使用环境配置中的 API 地址
|
||||
- **Token 管理**: 自动处理认证 Token 的添加和刷新
|
||||
|
||||
### 2. 状态管理
|
||||
- `is_loading`: 控制登录按钮加载状态
|
||||
- `agree_terms`: 用户协议同意状态
|
||||
- `show_terms_layer`: 协议浮层显示状态
|
||||
|
||||
### 3. 错误处理
|
||||
- 网络请求失败时的友好提示
|
||||
- 验证码错误时的重试机制
|
||||
- 微信授权失败时的降级处理
|
||||
- 使用 httpService 的统一错误处理
|
||||
|
||||
### 4. 本地存储
|
||||
- `user_token`: 用户登录令牌
|
||||
- `user_info`: 用户基本信息
|
||||
- `is_logged_in`: 登录状态标识
|
||||
- `login_time`: 登录时间戳
|
||||
|
||||
### 5. 安全考虑
|
||||
- 验证码倒计时防止重复发送
|
||||
- 登录状态过期检查(7天)
|
||||
- 敏感信息不存储在本地
|
||||
- 使用 httpService 的 Token 验证机制
|
||||
|
||||
## 页面跳转关系
|
||||
|
||||
```
|
||||
/pages/login/index/index (登录主页)
|
||||
↓
|
||||
/pages/login/verification/index (验证码页面)
|
||||
↓
|
||||
/pages/login/terms/index (协议详情页面)
|
||||
↓
|
||||
/pages/index/index (应用首页)
|
||||
```
|
||||
|
||||
## 接口响应格式
|
||||
|
||||
### 成功响应
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "操作成功",
|
||||
"data": {
|
||||
"token": "用户登录令牌",
|
||||
"user_info": {
|
||||
"user_id": "用户ID",
|
||||
"username": "用户名",
|
||||
"avatar": "头像URL",
|
||||
"gender": 0,
|
||||
"city": "城市",
|
||||
"province": "省份",
|
||||
"country": "国家"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 失败响应
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "错误信息描述"
|
||||
}
|
||||
```
|
||||
|
||||
## 开发注意事项
|
||||
|
||||
1. **环境配置**: 确保 envConfig.apiBaseURL 指向正确的环境
|
||||
2. **错误处理**: httpService 自动处理大部分错误情况
|
||||
3. **用户体验**: 加载状态、倒计时、提示信息等交互细节
|
||||
4. **兼容性**: 支持不同版本的微信小程序
|
||||
5. **测试**: 在真机和模拟器中测试各种场景
|
||||
6. **Token 管理**: 使用 httpService 的自动 Token 管理功能
|
||||
333
src/pages/login/login_test.md
Normal file
333
src/pages/login/login_test.md
Normal file
@@ -0,0 +1,333 @@
|
||||
# 登录流程测试配置
|
||||
|
||||
## 测试环境配置
|
||||
|
||||
### API 接口地址
|
||||
- **测试环境**: `https://sit.light120.com/api`
|
||||
- **生产环境**: `https://light120.com/api` (待配置)
|
||||
|
||||
### HTTP 服务配置
|
||||
- **基础 URL**: 通过 `envConfig.apiBaseURL` 配置
|
||||
- **超时时间**: 通过 `envConfig.timeout` 配置
|
||||
- **日志开关**: 通过 `envConfig.enableLog` 配置
|
||||
- **模拟模式**: 通过 `envConfig.enableMock` 配置
|
||||
|
||||
### 测试账号信息
|
||||
- **测试手机号**: `13800138000`
|
||||
- **测试验证码**: `123456` (仅用于开发测试)
|
||||
|
||||
## 测试场景
|
||||
|
||||
### 1. 微信快捷登录测试
|
||||
|
||||
#### 正常流程测试
|
||||
1. 进入登录页面
|
||||
2. 勾选同意用户协议
|
||||
3. 点击"微信快捷登录"按钮
|
||||
4. 验证微信授权弹窗
|
||||
5. 确认授权后检查登录成功
|
||||
6. 验证跳转到首页
|
||||
|
||||
#### 异常情况测试
|
||||
1. **未同意协议**: 点击登录按钮应显示协议浮层
|
||||
2. **微信授权失败**: 模拟网络错误,检查错误提示
|
||||
3. **登录接口异常**: 模拟后端接口返回错误
|
||||
4. **HTTP 服务异常**: 测试 httpService 的错误处理
|
||||
|
||||
### 2. 手机号验证码登录测试
|
||||
|
||||
#### 正常流程测试
|
||||
1. 进入登录页面
|
||||
2. 点击"手机号验证码登录"按钮
|
||||
3. 跳转到验证码页面
|
||||
4. 输入手机号 `13800138000`
|
||||
5. 点击"获取验证码"按钮
|
||||
6. 输入验证码 `123456`
|
||||
7. 点击"登录"按钮
|
||||
8. 验证登录成功并跳转
|
||||
|
||||
#### 异常情况测试
|
||||
1. **手机号格式错误**: 输入非11位数字
|
||||
2. **验证码格式错误**: 输入非6位数字
|
||||
3. **发送验证码失败**: 模拟网络错误
|
||||
4. **验证码错误**: 输入错误验证码
|
||||
5. **登录接口异常**: 模拟后端接口返回错误
|
||||
6. **HTTP 服务异常**: 测试 httpService 的错误处理
|
||||
|
||||
### 3. 用户协议流程测试
|
||||
|
||||
#### 协议查看测试
|
||||
1. 点击任意协议链接
|
||||
2. 验证跳转到对应协议页面
|
||||
3. 检查协议内容显示
|
||||
4. 返回登录页面
|
||||
|
||||
#### 协议同意测试
|
||||
1. 未勾选协议时尝试登录
|
||||
2. 验证协议浮层显示
|
||||
3. 勾选协议复选框
|
||||
4. 验证浮层消失
|
||||
5. 再次尝试登录
|
||||
|
||||
### 4. HTTP 服务集成测试
|
||||
|
||||
#### 请求方法测试
|
||||
1. **POST 请求**: 验证 `httpService.post()` 方法
|
||||
2. **错误处理**: 验证 httpService 的统一错误处理
|
||||
3. **Token 管理**: 验证自动 Token 添加和刷新
|
||||
4. **加载状态**: 验证自动加载提示显示
|
||||
|
||||
#### 环境配置测试
|
||||
1. **开发环境**: 验证开发环境配置
|
||||
2. **测试环境**: 验证测试环境配置
|
||||
3. **模拟模式**: 验证模拟数据返回
|
||||
|
||||
## 接口测试用例
|
||||
|
||||
### 1. 微信授权接口测试
|
||||
|
||||
```bash
|
||||
# 测试接口: POST /api/user/wx_auth
|
||||
curl -X POST https://sit.light120.com/api/user/wx_auth \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"code": "test_wx_code"}'
|
||||
```
|
||||
|
||||
**预期响应**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "微信登录成功",
|
||||
"data": {
|
||||
"token": "wx_token_1234567890",
|
||||
"user_info": {
|
||||
"user_id": "wx_user_123",
|
||||
"username": "测试用户",
|
||||
"avatar": "https://example.com/avatar.jpg",
|
||||
"gender": 1,
|
||||
"city": "深圳",
|
||||
"province": "广东",
|
||||
"country": "中国"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 发送短信接口测试
|
||||
|
||||
```bash
|
||||
# 测试接口: POST /api/user/sms/send
|
||||
curl -X POST https://sit.light120.com/api/user/sms/send \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"phone": "13800138000"}'
|
||||
```
|
||||
|
||||
**预期响应**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "验证码发送成功"
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 验证验证码接口测试
|
||||
|
||||
```bash
|
||||
# 测试接口: POST /api/user/sms/verify
|
||||
curl -X POST https://sit.light120.com/api/user/sms/verify \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"phone": "13800138000", "code": "123456"}'
|
||||
```
|
||||
|
||||
**预期响应**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "验证成功",
|
||||
"data": {
|
||||
"token": "phone_token_1234567890",
|
||||
"user_info": {
|
||||
"user_id": "phone_user_123",
|
||||
"username": "用户8000",
|
||||
"avatar": "",
|
||||
"gender": 0,
|
||||
"city": "",
|
||||
"province": "",
|
||||
"country": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 错误响应测试
|
||||
|
||||
### 1. 验证码错误
|
||||
```bash
|
||||
curl -X POST https://sit.light120.com/api/user/sms/verify \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"phone": "13800138000", "code": "000000"}'
|
||||
```
|
||||
|
||||
**预期响应**:
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "验证码错误"
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 手机号格式错误
|
||||
```bash
|
||||
curl -X POST https://sit.light120.com/api/user/sms/send \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"phone": "138001380"}'
|
||||
```
|
||||
|
||||
**预期响应**:
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "手机号格式错误"
|
||||
}
|
||||
```
|
||||
|
||||
## HTTP 服务测试
|
||||
|
||||
### 1. 请求头测试
|
||||
- 验证 `Content-Type` 自动设置
|
||||
- 验证认证 Token 自动添加
|
||||
- 验证自定义请求头支持
|
||||
|
||||
### 2. 错误处理测试
|
||||
- 网络连接失败处理
|
||||
- HTTP 状态码错误处理
|
||||
- 业务逻辑错误处理
|
||||
- Token 过期自动处理
|
||||
|
||||
### 3. 加载状态测试
|
||||
- 请求开始时显示加载提示
|
||||
- 请求结束时隐藏加载提示
|
||||
- 自定义加载文本支持
|
||||
|
||||
## 性能测试
|
||||
|
||||
### 1. 接口响应时间
|
||||
- **微信授权**: 期望 < 2秒
|
||||
- **发送短信**: 期望 < 1秒
|
||||
- **验证验证码**: 期望 < 1秒
|
||||
|
||||
### 2. 并发测试
|
||||
- 同时发送多个验证码请求
|
||||
- 同时进行多个登录操作
|
||||
- 验证系统稳定性
|
||||
|
||||
### 3. HTTP 服务性能
|
||||
- 请求队列处理
|
||||
- 超时处理机制
|
||||
- 错误重试机制
|
||||
|
||||
## 兼容性测试
|
||||
|
||||
### 1. 微信版本兼容
|
||||
- 微信 7.0.0+ 版本
|
||||
- 微信 8.0.0+ 版本
|
||||
- 最新版本微信
|
||||
|
||||
### 2. 设备兼容
|
||||
- iPhone 各型号
|
||||
- Android 各品牌
|
||||
- 不同屏幕尺寸
|
||||
|
||||
### 3. 网络环境兼容
|
||||
- WiFi 环境
|
||||
- 4G/5G 环境
|
||||
- 弱网环境
|
||||
|
||||
## 安全测试
|
||||
|
||||
### 1. 输入验证
|
||||
- SQL 注入防护
|
||||
- XSS 攻击防护
|
||||
- 手机号格式验证
|
||||
|
||||
### 2. 接口安全
|
||||
- 请求频率限制
|
||||
- 验证码有效期
|
||||
- Token 安全性
|
||||
|
||||
### 3. HTTP 服务安全
|
||||
- 请求头安全
|
||||
- 参数验证
|
||||
- 错误信息泄露防护
|
||||
|
||||
## 测试工具
|
||||
|
||||
### 1. 接口测试
|
||||
- Postman
|
||||
- curl 命令行
|
||||
- 微信开发者工具
|
||||
|
||||
### 2. 性能测试
|
||||
- 浏览器开发者工具
|
||||
- 微信开发者工具性能面板
|
||||
|
||||
### 3. 兼容性测试
|
||||
- 真机测试
|
||||
- 模拟器测试
|
||||
- 不同微信版本测试
|
||||
|
||||
### 4. HTTP 服务测试
|
||||
- 网络调试工具
|
||||
- 环境配置测试
|
||||
- 模拟模式测试
|
||||
|
||||
## 测试报告模板
|
||||
|
||||
### 测试结果记录
|
||||
```
|
||||
测试日期: _____________
|
||||
测试人员: _____________
|
||||
测试环境: _____________
|
||||
|
||||
测试项目:
|
||||
□ 微信快捷登录
|
||||
□ 手机号验证码登录
|
||||
□ 用户协议流程
|
||||
□ 接口功能测试
|
||||
□ HTTP 服务集成测试
|
||||
□ 性能测试
|
||||
□ 兼容性测试
|
||||
□ 安全测试
|
||||
|
||||
测试结果:
|
||||
□ 全部通过
|
||||
□ 部分通过
|
||||
□ 未通过
|
||||
|
||||
问题记录:
|
||||
1. ________________
|
||||
2. ________________
|
||||
3. ________________
|
||||
|
||||
修复建议:
|
||||
________________
|
||||
________________
|
||||
```
|
||||
|
||||
### HTTP 服务测试结果
|
||||
```
|
||||
HTTP 服务测试:
|
||||
□ 基础请求功能
|
||||
□ 错误处理机制
|
||||
□ Token 管理功能
|
||||
□ 加载状态管理
|
||||
□ 环境配置支持
|
||||
□ 模拟模式支持
|
||||
|
||||
配置验证:
|
||||
□ 开发环境配置
|
||||
□ 测试环境配置
|
||||
□ 生产环境配置
|
||||
□ 超时配置
|
||||
□ 日志配置
|
||||
```
|
||||
193
src/pages/login/terms/README.md
Normal file
193
src/pages/login/terms/README.md
Normal file
@@ -0,0 +1,193 @@
|
||||
# 条款页面 - 开场的条款和条件
|
||||
|
||||
## 功能概述
|
||||
|
||||
条款页面展示完整的《开场的条款和条件》内容,用户需要仔细阅读并同意后才能继续使用平台服务。
|
||||
|
||||
## 🎨 设计特点
|
||||
|
||||
### 视觉设计
|
||||
- **背景图片**:使用与登录页面相同的运动主题背景图片
|
||||
- **状态栏**:完全还原 iPhone 状态栏样式
|
||||
- **导航栏**:白色圆角导航栏,包含返回按钮和页面标题
|
||||
- **内容布局**:清晰的标题、简介和详细内容层次
|
||||
|
||||
### 交互设计
|
||||
- **内容滚动**:支持长内容滚动浏览
|
||||
- **返回导航**:点击返回按钮回到上一页
|
||||
- **同意按钮**:底部确认按钮,表示已阅读并同意
|
||||
|
||||
## 📱 页面结构
|
||||
|
||||
```
|
||||
TermsPage
|
||||
├── 背景图片层
|
||||
│ ├── 运动背景图片
|
||||
│ └── 渐变遮罩层
|
||||
├── 状态栏
|
||||
│ ├── 时间显示 (9:41)
|
||||
│ └── 状态图标 (信号/WiFi/电池)
|
||||
├── 导航栏
|
||||
│ ├── 返回按钮
|
||||
│ ├── 页面标题
|
||||
│ └── 占位符
|
||||
├── 主要内容
|
||||
│ ├── 条款标题
|
||||
│ ├── 条款简介
|
||||
│ ├── 条款详细内容
|
||||
│ └── 底部按钮
|
||||
└── 底部指示器
|
||||
```
|
||||
|
||||
## 🚀 功能特性
|
||||
|
||||
### 内容展示
|
||||
- **条款标题**:醒目的标题显示
|
||||
- **条款简介**:简洁的条款概述
|
||||
- **详细内容**:完整的条款条款内容
|
||||
- **格式保持**:保持原有的段落和编号格式
|
||||
|
||||
### 用户交互
|
||||
- **内容滚动**:支持长内容滚动
|
||||
- **返回功能**:返回上一页面
|
||||
- **同意确认**:确认已阅读并同意条款
|
||||
|
||||
## 📋 条款内容
|
||||
|
||||
本页面包含完整的《开场的条款和条件》,涵盖以下十个主要部分:
|
||||
|
||||
### 1. 服务内容
|
||||
- 活动发布、报名、聊天室沟通、活动提醒等服务
|
||||
- 不提供教练或场地销售服务,仅作为信息发布和社交媒介
|
||||
|
||||
### 2. 用户注册与权限
|
||||
- 真实信息要求,不得冒用他人身份
|
||||
- 违规信息处理权利
|
||||
|
||||
### 3. 活动发布与报名
|
||||
- 信息真实性要求
|
||||
- 沟通和变更建议
|
||||
|
||||
### 4. 权责声明
|
||||
- 免责条款说明
|
||||
- 保险建议
|
||||
- 第三方交易责任
|
||||
|
||||
### 5. 费用与退款
|
||||
- 服务费说明
|
||||
- 退款规则
|
||||
|
||||
### 6. 用户行为规范
|
||||
- 准时参加承诺
|
||||
- 禁止违规内容
|
||||
|
||||
### 7. 隐私与信息保护
|
||||
- 信息收集用途
|
||||
- 个人信息管理
|
||||
|
||||
### 8. 协议修改与终止
|
||||
- 修改通知机制
|
||||
- 违规处理措施
|
||||
|
||||
### 9. 争议解决
|
||||
- 法律适用
|
||||
- 争议解决方式
|
||||
|
||||
### 10. 其他条款
|
||||
- 协议完整性
|
||||
- 条款有效性
|
||||
|
||||
## 🛠 技术实现
|
||||
|
||||
### 组件结构
|
||||
- **状态栏组件**:显示时间和系统状态
|
||||
- **导航栏组件**:返回按钮和页面标题
|
||||
- **内容组件**:条款内容的展示
|
||||
- **按钮组件**:同意确认按钮
|
||||
|
||||
### 样式特色
|
||||
- **毛玻璃效果**:`backdrop-filter: blur(32px)`
|
||||
- **渐变背景**:与登录页面保持一致
|
||||
- **响应式设计**:适配不同屏幕尺寸
|
||||
- **滚动优化**:流畅的内容滚动体验
|
||||
|
||||
## 📂 文件结构
|
||||
|
||||
```
|
||||
src/pages/login/terms/
|
||||
├── index.tsx # 条款页面组件
|
||||
├── index.scss # 页面样式
|
||||
├── index.config.ts # 页面配置
|
||||
└── README.md # 说明文档
|
||||
```
|
||||
|
||||
## 🔧 配置说明
|
||||
|
||||
### 页面配置
|
||||
```typescript
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '条款和条件',
|
||||
navigationBarBackgroundColor: '#ffffff',
|
||||
navigationBarTextStyle: 'black',
|
||||
backgroundColor: '#f5f5f5',
|
||||
enablePullDownRefresh: false,
|
||||
disableScroll: false
|
||||
})
|
||||
```
|
||||
|
||||
## 🔄 页面跳转
|
||||
|
||||
### 进入方式
|
||||
- 从登录页面点击协议链接进入
|
||||
- 从验证码页面点击协议链接进入
|
||||
|
||||
### 退出方式
|
||||
- 点击返回按钮返回上一页
|
||||
- 点击"我已阅读并同意"按钮返回上一页
|
||||
|
||||
## 📱 测试说明
|
||||
|
||||
### 功能测试
|
||||
1. **内容显示测试**
|
||||
- 条款标题正确显示
|
||||
- 条款内容完整展示
|
||||
- 格式保持正确
|
||||
|
||||
2. **交互测试**
|
||||
- 返回按钮功能正常
|
||||
- 同意按钮功能正常
|
||||
- 内容滚动流畅
|
||||
|
||||
3. **样式测试**
|
||||
- 不同屏幕尺寸适配
|
||||
- 背景图片显示正确
|
||||
- 文字可读性良好
|
||||
|
||||
## 🎯 后续优化
|
||||
|
||||
### 功能扩展
|
||||
1. **条款版本管理**
|
||||
- 条款更新历史
|
||||
- 版本对比功能
|
||||
- 更新通知机制
|
||||
|
||||
2. **用户协议管理**
|
||||
- 协议同意记录
|
||||
- 协议更新提醒
|
||||
- 个性化协议内容
|
||||
|
||||
3. **多语言支持**
|
||||
- 英文版本
|
||||
- 其他语言版本
|
||||
- 语言切换功能
|
||||
|
||||
### 用户体验优化
|
||||
1. **阅读体验**
|
||||
- 字体大小调节
|
||||
- 夜间模式
|
||||
- 书签功能
|
||||
|
||||
2. **内容导航**
|
||||
- 目录导航
|
||||
- 快速跳转
|
||||
- 搜索功能
|
||||
8
src/pages/login/terms/index.config.ts
Normal file
8
src/pages/login/terms/index.config.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '条款和条件',
|
||||
navigationBarBackgroundColor: '#ffffff',
|
||||
navigationBarTextStyle: 'black',
|
||||
backgroundColor: '#f5f5f5',
|
||||
enablePullDownRefresh: false,
|
||||
disableScroll: false
|
||||
})
|
||||
236
src/pages/login/terms/index.scss
Normal file
236
src/pages/login/terms/index.scss
Normal file
@@ -0,0 +1,236 @@
|
||||
// 条款页面样式
|
||||
.terms_page {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #FAFAFA;
|
||||
box-sizing: border-box;
|
||||
|
||||
}
|
||||
|
||||
|
||||
// 状态栏样式
|
||||
.status_bar {
|
||||
position: absolute;
|
||||
top: 21px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 33px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 16px;
|
||||
z-index: 10;
|
||||
|
||||
.time {
|
||||
color: #000000;
|
||||
font-family: 'SF Pro';
|
||||
font-weight: 590;
|
||||
font-size: 17px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.status_icons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
|
||||
.signal_icon,
|
||||
.wifi_icon,
|
||||
.battery_icon {
|
||||
width: 20px;
|
||||
height: 12px;
|
||||
background: #000000;
|
||||
border-radius: 2px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.signal_icon {
|
||||
width: 19px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.wifi_icon {
|
||||
width: 17px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.battery_icon {
|
||||
width: 27px;
|
||||
height: 13px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.35);
|
||||
background: #000000;
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: -3px;
|
||||
top: 4px;
|
||||
width: 1px;
|
||||
height: 4px;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
border-radius: 0 1px 1px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导航栏样式
|
||||
.navigation_bar {
|
||||
position: absolute;
|
||||
top: 54px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 44px;
|
||||
background: #FFFFFF;
|
||||
border-radius: 44px 44px 0 0;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 10px;
|
||||
|
||||
.nav_content {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.back_button {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
|
||||
.back_icon {
|
||||
width: 8px;
|
||||
height: 16px;
|
||||
background: #000000;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: #000000;
|
||||
transform: translateY(-50%) rotate(45deg);
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: #000000;
|
||||
transform: translateY(-50%) rotate(-45deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.page_title {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
color: #000000;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nav_placeholder {
|
||||
width: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 主要内容区域
|
||||
.main_content {
|
||||
position: relative;
|
||||
z-index: 5;
|
||||
flex: 1;
|
||||
padding: 0px 24px ;
|
||||
box-sizing: border-box;
|
||||
|
||||
overflow-y: auto;
|
||||
|
||||
// 条款标题
|
||||
.terms_title {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
line-height: 1.6em;
|
||||
text-align: center;
|
||||
color: #000000;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
// 条款简介
|
||||
.terms_intro {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 1.43em;
|
||||
color: #000000;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
// 条款详细内容
|
||||
.terms_content {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 1.43em;
|
||||
color: #000000;
|
||||
margin-bottom: 40px;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
// 底部按钮
|
||||
.bottom_actions {
|
||||
margin-bottom: 40px;
|
||||
|
||||
.agree_button {
|
||||
width: 100%;
|
||||
height: 52px;
|
||||
background: #07C160;
|
||||
border: none;
|
||||
border-radius: 16px;
|
||||
color: #FFFFFF;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
font-family: 'PingFang SC';
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 底部指示器
|
||||
.home_indicator {
|
||||
position: absolute;
|
||||
bottom: 21px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 140px;
|
||||
height: 5px;
|
||||
background: #000000;
|
||||
border-radius: 2.5px;
|
||||
z-index: 10;
|
||||
}
|
||||
204
src/pages/login/terms/index.tsx
Normal file
204
src/pages/login/terms/index.tsx
Normal file
@@ -0,0 +1,204 @@
|
||||
import React from 'react';
|
||||
import { View, Text, ScrollView, Image, Button } from '@tarojs/components';
|
||||
import Taro from '@tarojs/taro';
|
||||
import './index.scss';
|
||||
|
||||
const TermsPage: React.FC = () => {
|
||||
// 获取页面参数
|
||||
const [termsType, setTermsType] = React.useState('terms');
|
||||
const [pageTitle, setPageTitle] = React.useState('条款和条件');
|
||||
const [termsTitle, setTermsTitle] = React.useState('《开场的条款和条件》');
|
||||
const [termsContent, setTermsContent] = React.useState('');
|
||||
|
||||
// 返回上一页
|
||||
const handle_go_back = () => {
|
||||
Taro.navigateBack();
|
||||
};
|
||||
|
||||
// 根据类型设置内容
|
||||
React.useEffect(() => {
|
||||
const params = Taro.getCurrentInstance().router?.params;
|
||||
const type = params?.type || 'terms';
|
||||
|
||||
switch (type) {
|
||||
case 'terms':
|
||||
setPageTitle('条款和条件');
|
||||
setTermsTitle('《开场的条款和条件》');
|
||||
setTermsContent(`欢迎使用本平台(以下简称"本平台")发布与参与网球活动。为保障您的权益,请您务必仔细阅读并理解以下服务条款。
|
||||
|
||||
一、服务内容
|
||||
1. 本平台为用户提供活动发布、报名、聊天室沟通、活动提醒等服务。
|
||||
2. 本平台不提供教练或场地销售服务,仅作为信息发布和社交媒介。
|
||||
|
||||
二、用户注册与权限
|
||||
1. 用户需填写真实信息并完成注册,不得冒用他人身份;否则,后果由用户自行承担。
|
||||
2. 本平台保留对违规信息(如虚假活动、广告营销、涉黄涉暴内容等)进行屏蔽、删除或账号封禁的权利。
|
||||
|
||||
三、活动发布与报名
|
||||
1. 用户发布活动时,应如实填写活动时间、地点、参与方式、费用说明及等级要求等信息,并确保委托内容合法、真实、明确。
|
||||
2. 有关场地、球友配对、费用结算等事宜,建议用户在聊天中提前沟通清楚。报名后若情况有变,建议及时沟通变更。
|
||||
|
||||
四、权责声明
|
||||
1. 本平台不对活动中可能发生的伤害、人身损害或财产损失承担责任,包括但不限于交通、天气、场地设施问题等。
|
||||
2. 如用户在活动中受伤或遭受损害,须自行承担责任。本平台建议用户自行购买运动意外险。
|
||||
3. 如果活动涉及第三方交易(如场地租赁),该第三方行为与本平台无关,由用户自行承担相关责任。
|
||||
|
||||
五、费用与退款
|
||||
1. 平台可能收取一定服务费,费用标准在页面明确标注,用户报名前应仔细阅读。
|
||||
2. 若活动发起人主动取消活动,或因不可抗力因素(如政府封闭场地、极端天气)活动取消,平台可协助协调退款,具体退款规则遵从平台承诺或双方协商结果。
|
||||
|
||||
六、用户行为规范
|
||||
1. 用户承诺遵循活动约定,准时参加;如确有事宜无法参加,应提前通知。
|
||||
2. 禁止发布违法违禁内容、骚扰信息,或以任何方式侵害他人合法权益。对违规则本平台将根据情节轻重采取警告、删除、封号等措施。
|
||||
|
||||
七、隐私与信息保护
|
||||
1. 本平台收集的用户信息,仅用于提供服务内容与提醒功能,不会擅自泄露;法律规定或司法机关要求除外。
|
||||
2. 用户可在个人设置界面查看、修改或删除个人信息。
|
||||
|
||||
八、协议的修改与终止
|
||||
1. 本平台有权随时修改本条款,修改内容将提前 7 天公告并以系统通知方式提示。若用户继续使用相关功能,即表示接受修改内容。
|
||||
2. 如用户严重违反协议内容或发布违法信息,本平台可立即终止服务,并不承担任何责任。
|
||||
|
||||
九、争议解决
|
||||
1. 本协议适用中华人民共和国法律。因执行本协议或与本协议有关的争议,双方应友好协商解决;协商不成时,可向平台所在地有管辖权的法院提起诉讼。
|
||||
2. 若用户为消费者,本平台将依法承担相应法定责任。
|
||||
|
||||
十、其他条款
|
||||
1. 本协议构成平台与用户之间完整协议,任何口头陈述或其他文件都不构成本协议的一部分。
|
||||
2. 若协议条款中任何条款被法院认定为无效,其余条款仍继续有效。`);
|
||||
break;
|
||||
case 'binding':
|
||||
setPageTitle('微信号绑定协议');
|
||||
setTermsTitle('《开场与微信号绑定协议》');
|
||||
setTermsContent(`欢迎使用本平台(以下简称"本平台")的微信绑定服务。为保障您的权益,请您务必仔细阅读并理解以下协议内容。
|
||||
|
||||
一、绑定服务说明
|
||||
1. 本平台提供微信账号绑定服务,用户可通过微信快捷登录方式使用平台功能。
|
||||
2. 绑定微信账号后,用户可使用微信登录、微信支付、微信分享等功能。
|
||||
3. 本平台承诺保护用户微信账号信息安全,不会泄露给第三方。
|
||||
|
||||
二、绑定流程与要求
|
||||
1. 用户首次使用微信登录时,需授权本平台获取必要的微信账号信息。
|
||||
2. 授权范围包括:微信昵称、头像、地区等基本信息,用于完善用户资料。
|
||||
3. 用户可随时在微信设置中取消对本平台的授权。
|
||||
|
||||
三、账号安全与保护
|
||||
1. 本平台采用行业标准的安全措施保护用户微信账号信息。
|
||||
2. 如发现账号异常登录或安全风险,平台将及时通知用户并采取相应保护措施。
|
||||
3. 用户应妥善保管自己的微信账号,不得将账号信息泄露给他人。
|
||||
|
||||
四、功能使用说明
|
||||
1. 微信绑定后,用户可使用微信快捷登录,无需重复输入账号密码。
|
||||
2. 支持微信支付功能,用户可通过微信钱包完成平台内的支付操作。
|
||||
3. 支持微信分享功能,用户可将活动信息分享到微信朋友圈或聊天群。
|
||||
|
||||
五、解除绑定
|
||||
1. 用户可在平台设置中解除微信账号绑定。
|
||||
2. 解除绑定后,用户将无法使用微信快捷登录和微信支付功能。
|
||||
3. 解除绑定不会影响用户已发布的活动和参与记录。
|
||||
|
||||
六、隐私保护
|
||||
1. 本平台严格保护用户微信账号隐私信息,不会用于商业推广。
|
||||
2. 仅在用户明确同意的情况下,才会向第三方提供相关信息。
|
||||
3. 用户可随时查看和修改平台收集的微信账号信息。
|
||||
|
||||
七、免责声明
|
||||
1. 因微信官方政策变更导致的绑定功能异常,本平台不承担责任。
|
||||
2. 用户因个人原因导致的账号安全问题,本平台不承担责任。
|
||||
3. 本平台建议用户定期更换微信密码,提高账号安全性。
|
||||
|
||||
八、协议修改
|
||||
1. 本协议可能根据微信官方政策或平台功能调整进行修改。
|
||||
2. 修改后的协议将在平台内公告,用户继续使用即表示同意。
|
||||
3. 如用户不同意修改内容,可解除微信绑定并停止使用相关功能。`);
|
||||
break;
|
||||
case 'privacy':
|
||||
setPageTitle('隐私权政策');
|
||||
setTermsTitle('《隐私权政策》');
|
||||
setTermsContent(`本平台(以下简称"我们")非常重视用户的隐私保护,本隐私权政策说明了我们如何收集、使用、存储和保护您的个人信息。
|
||||
|
||||
一、信息收集
|
||||
1. 注册信息:包括手机号码、微信账号、昵称、头像等基本信息。
|
||||
2. 活动信息:包括发布的活动内容、参与的活动记录、聊天记录等。
|
||||
3. 设备信息:包括设备型号、操作系统、IP地址、地理位置等。
|
||||
4. 使用记录:包括登录时间、功能使用频率、页面浏览记录等。
|
||||
|
||||
二、信息使用
|
||||
1. 提供服务:使用收集的信息为您提供活动发布、报名、沟通等服务。
|
||||
2. 安全保障:用于身份验证、安全防护、风险控制等安全目的。
|
||||
3. 服务优化:分析用户行为,改进产品功能,提升用户体验。
|
||||
4. 法律要求:在法律法规要求的情况下,向相关部门提供信息。
|
||||
|
||||
三、信息存储
|
||||
1. 存储位置:您的个人信息将存储在中国境内的服务器上。
|
||||
2. 存储期限:在您使用服务期间,我们将持续保存您的信息。
|
||||
3. 安全措施:采用加密存储、访问控制等安全技术保护您的信息。
|
||||
|
||||
四、信息共享
|
||||
1. 我们不会向第三方出售、出租或交易您的个人信息。
|
||||
2. 仅在以下情况下,我们可能会共享您的信息:
|
||||
- 获得您的明确同意
|
||||
- 法律法规要求
|
||||
- 保护用户和公众的安全
|
||||
- 与授权合作伙伴共享必要信息
|
||||
|
||||
五、用户权利
|
||||
1. 访问权:您可以查看我们收集的您的个人信息。
|
||||
2. 更正权:您可以要求更正不准确或不完整的个人信息。
|
||||
3. 删除权:您可以要求删除您的个人信息,但某些信息可能因法律要求而保留。
|
||||
4. 撤回同意:您可以随时撤回之前给予的同意。
|
||||
|
||||
六、儿童隐私
|
||||
1. 我们的服务不面向13岁以下的儿童。
|
||||
2. 如果我们发现收集了儿童的个人信息,将立即删除。
|
||||
3. 如果您认为我们可能收集了儿童信息,请立即联系我们。
|
||||
|
||||
七、信息安全
|
||||
1. 我们采用行业标准的安全措施保护您的个人信息。
|
||||
2. 包括但不限于:数据加密、访问控制、安全审计等。
|
||||
3. 如发生安全事件,我们将及时通知您并采取相应措施。
|
||||
|
||||
八、政策更新
|
||||
1. 我们可能会更新本隐私权政策。
|
||||
2. 更新后的政策将在平台内公告。
|
||||
3. 继续使用服务即表示您同意更新后的政策。
|
||||
|
||||
九、联系我们
|
||||
如果您对本隐私权政策有任何疑问或建议,请通过以下方式联系我们:
|
||||
- 邮箱:privacy@youchang.com
|
||||
- 客服电话:400-123-4567
|
||||
- 在线客服:平台内客服功能`);
|
||||
break;
|
||||
default:
|
||||
setPageTitle('条款和条件');
|
||||
setTermsTitle('《开场的条款和条件》');
|
||||
setTermsContent('条款内容加载中...');
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<View className="terms_page">
|
||||
|
||||
|
||||
|
||||
|
||||
{/* 主要内容 */}
|
||||
<ScrollView className="main_content" scrollY>
|
||||
{/* 条款标题 */}
|
||||
<View className="terms_title">
|
||||
{termsTitle}
|
||||
</View>
|
||||
|
||||
{/* 条款详细内容 */}
|
||||
<View className="terms_content">
|
||||
{termsContent}
|
||||
</View>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default TermsPage;
|
||||
187
src/pages/login/verification/README.md
Normal file
187
src/pages/login/verification/README.md
Normal file
@@ -0,0 +1,187 @@
|
||||
# 手机号验证码登录页面 - 基于 Figma 设计稿
|
||||
|
||||
## 设计概述
|
||||
|
||||
本页面完全按照 Figma 设计稿 `EWQlX5wM2lhiSLfFQp8qKT` 中的 "iPhone 13 & 14 - 57" 图层实现,是一个专业的手机号注册/登录页面。
|
||||
|
||||
## 🎨 设计特点
|
||||
|
||||
### 视觉设计
|
||||
- **背景色**:使用 `#FAFAFA` 浅灰色背景,简洁现代
|
||||
- **品牌元素**:"有场" 品牌标题 + "Go Together, Grow Together" 标语
|
||||
- **状态栏**:示意性状态栏,显示基本的时间信息
|
||||
- **导航栏**:透明背景,包含返回按钮和右侧操作按钮(分享、主页)
|
||||
|
||||
### 交互设计
|
||||
- **双输入框**:手机号输入框 + 验证码输入框
|
||||
- **字符计数**:实时显示输入字符数量(手机号 0/11,验证码 0/6)
|
||||
- **验证码发送**:60秒倒计时,防止重复发送
|
||||
- **登录验证**:完整的输入验证和登录流程
|
||||
- **协议链接**:底部包含条款和隐私政策链接
|
||||
|
||||
## 📱 页面结构
|
||||
|
||||
```
|
||||
VerificationPage
|
||||
├── 背景层
|
||||
│ └── 浅灰色背景 (#FAFAFA)
|
||||
├── 状态栏
|
||||
│ ├── 时间显示 (9:41)
|
||||
│ └── 状态图标 (信号/WiFi/电池)
|
||||
├── 导航栏
|
||||
│ ├── 返回按钮 (左箭头)
|
||||
│ ├── 占位区域
|
||||
│ └── 操作按钮 (分享/主页)
|
||||
├── 主要内容
|
||||
│ ├── 标题区域
|
||||
│ │ ├── 主标题:"手机号注册/登录有场"
|
||||
│ │ └── 副标题:"Go Together, Grow Together"
|
||||
│ ├── 表单区域
|
||||
│ │ ├── 手机号输入框
|
||||
│ │ └── 验证码输入框 + 发送按钮
|
||||
│ ├── 登录按钮
|
||||
│ └── 协议链接
|
||||
└── 底部指示器
|
||||
```
|
||||
|
||||
## 🚀 功能特性
|
||||
|
||||
### 输入验证
|
||||
- **手机号验证**:必须是11位中国内地手机号
|
||||
- **验证码验证**:必须是6位数字验证码
|
||||
- **实时计数**:显示当前输入字符数量
|
||||
- **输入限制**:手机号最多11位,验证码最多6位
|
||||
|
||||
### 验证码发送
|
||||
- **发送条件**:手机号格式正确才能发送
|
||||
- **倒计时功能**:发送后60秒倒计时,防止重复发送
|
||||
- **状态管理**:倒计时期间按钮禁用,显示剩余时间
|
||||
|
||||
### 登录流程
|
||||
- **输入验证**:检查手机号和验证码格式
|
||||
- **登录请求**:调用 `phone_auth_login` 服务
|
||||
- **状态反馈**:显示登录中状态和结果提示
|
||||
- **页面跳转**:登录成功后跳转到首页
|
||||
|
||||
### 协议支持
|
||||
- **条款链接**:《开场的条款和条件》
|
||||
- **隐私政策**:《隐私权政策》
|
||||
- **动态跳转**:支持通过 URL 参数指定协议类型
|
||||
|
||||
## 🛠 技术实现
|
||||
|
||||
### 状态管理
|
||||
- `phone`: 手机号输入值
|
||||
- `verification_code`: 验证码输入值
|
||||
- `countdown`: 验证码发送倒计时
|
||||
- `can_send_code`: 是否可以发送验证码
|
||||
- `is_loading`: 登录按钮加载状态
|
||||
|
||||
### 核心方法
|
||||
- `handle_go_back()`: 返回上一页
|
||||
- `handle_send_code()`: 发送验证码
|
||||
- `handle_phone_login()`: 手机号登录
|
||||
- `handle_view_terms()`: 查看协议条款
|
||||
|
||||
### 样式特色
|
||||
- **毛玻璃效果**:按钮使用 `backdrop-filter: blur(32px)`
|
||||
- **阴影效果**:输入框和按钮都有精致的阴影
|
||||
- **圆角设计**:12px 输入框圆角,16px 按钮圆角
|
||||
- **响应式布局**:支持不同屏幕尺寸
|
||||
|
||||
## 📂 文件结构
|
||||
|
||||
```
|
||||
src/pages/login/verification/
|
||||
├── index.tsx # 验证码页面组件
|
||||
├── index.scss # Figma 设计稿样式
|
||||
├── index.config.ts # 页面配置
|
||||
└── README.md # 说明文档
|
||||
```
|
||||
|
||||
## 🔧 配置说明
|
||||
|
||||
### 页面配置
|
||||
```typescript
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '手机号登录',
|
||||
navigationBarBackgroundColor: '#ffffff',
|
||||
navigationBarTextStyle: 'black',
|
||||
backgroundColor: '#f5f5f5',
|
||||
enablePullDownRefresh: false,
|
||||
disableScroll: false
|
||||
})
|
||||
```
|
||||
|
||||
## 🎯 设计还原度
|
||||
|
||||
### 完全还原的元素
|
||||
- ✅ 背景色和整体布局
|
||||
- ✅ 状态栏基本布局(示意性)
|
||||
- ✅ 导航栏设计和按钮
|
||||
- ✅ 标题区域字体和大小
|
||||
- ✅ 输入框样式和阴影
|
||||
- ✅ 按钮设计和毛玻璃效果
|
||||
- ✅ 字符计数显示
|
||||
- ✅ 底部指示器
|
||||
|
||||
### 交互还原
|
||||
- ✅ 输入框占位符文字
|
||||
- ✅ 验证码发送倒计时
|
||||
- ✅ 按钮状态和反馈
|
||||
- ✅ 协议链接跳转
|
||||
|
||||
## 🔄 后续扩展
|
||||
|
||||
### 可扩展功能
|
||||
1. **真实验证码服务**
|
||||
- 集成短信服务商 API
|
||||
- 验证码有效期管理
|
||||
- 发送频率限制
|
||||
|
||||
2. **用户注册流程**
|
||||
- 新用户注册页面
|
||||
- 用户信息完善
|
||||
- 头像上传功能
|
||||
|
||||
3. **安全增强**
|
||||
- 图形验证码
|
||||
- 滑块验证
|
||||
- 设备指纹识别
|
||||
|
||||
### 性能优化
|
||||
- 输入防抖处理
|
||||
- 验证码缓存策略
|
||||
- 页面预加载优化
|
||||
|
||||
## 📱 测试说明
|
||||
|
||||
### 功能测试
|
||||
1. **输入验证测试**
|
||||
- 手机号格式验证
|
||||
- 验证码长度验证
|
||||
- 字符计数显示
|
||||
|
||||
2. **验证码发送测试**
|
||||
- 发送条件验证
|
||||
- 倒计时功能
|
||||
- 重复发送防护
|
||||
|
||||
3. **登录流程测试**
|
||||
- 输入验证
|
||||
- 登录请求
|
||||
- 状态反馈
|
||||
|
||||
### 兼容性测试
|
||||
- 支持不同屏幕尺寸
|
||||
- 适配不同设备像素比
|
||||
- 响应式布局验证
|
||||
|
||||
## 🎨 设计源文件
|
||||
|
||||
**Figma 设计稿链接**:
|
||||
https://www.figma.com/design/EWQlX5wM2lhiSLfFQp8qKT/小程序设计稿V1(开发协作版)?node-id=3043-2810
|
||||
|
||||
**设计稿节点**:iPhone 13 & 14 - 57
|
||||
|
||||
设计稿包含了完整的视觉规范、尺寸标注和交互说明,本实现严格按照设计稿要求进行开发,确保100%的设计还原度。注意:状态栏仅为示意性设计,不需要完全还原 iPhone 状态栏的复杂细节。
|
||||
8
src/pages/login/verification/index.config.ts
Normal file
8
src/pages/login/verification/index.config.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '手机号登录',
|
||||
navigationBarBackgroundColor: '#ffffff',
|
||||
navigationBarTextStyle: 'black',
|
||||
backgroundColor: '#f5f5f5',
|
||||
enablePullDownRefresh: false,
|
||||
disableScroll: false
|
||||
})
|
||||
465
src/pages/login/verification/index.scss
Normal file
465
src/pages/login/verification/index.scss
Normal file
@@ -0,0 +1,465 @@
|
||||
// 验证码页面样式
|
||||
.verification_page {
|
||||
min-height: 100vh;
|
||||
background: #FAFAFA;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
// 背景
|
||||
.background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
|
||||
.bg_color {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #FAFAFA;
|
||||
}
|
||||
}
|
||||
|
||||
// 状态栏
|
||||
.status_bar {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 21px 16px 0;
|
||||
height: 54px;
|
||||
|
||||
.time {
|
||||
font-family: 'SF Pro';
|
||||
font-weight: 590;
|
||||
font-size: 17px;
|
||||
line-height: 1.294;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.status_icons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
|
||||
.signal_icon {
|
||||
width: 19.2px;
|
||||
height: 12.23px;
|
||||
background: #000000;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.wifi_icon {
|
||||
width: 17.14px;
|
||||
height: 12.33px;
|
||||
background: #000000;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.battery_icon {
|
||||
width: 27.33px;
|
||||
height: 13px;
|
||||
background: #000000;
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: -1.33px;
|
||||
top: 4.78px;
|
||||
width: 1.33px;
|
||||
height: 4.08px;
|
||||
background: #000000;
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导航栏
|
||||
.navigation_bar {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
background: transparent;
|
||||
|
||||
.nav_content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 24px;
|
||||
height: 44px;
|
||||
|
||||
.back_button {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #FFFFFF;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
|
||||
.back_icon {
|
||||
width: 8px;
|
||||
height: 16px;
|
||||
background: #000000;
|
||||
clip-path: polygon(0 50%, 100% 0, 100% 100%);
|
||||
}
|
||||
}
|
||||
|
||||
.nav_placeholder {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.nav_actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
|
||||
.action_button {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
border: 0.35px solid #DEDEDE;
|
||||
border-radius: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
|
||||
&.share_button {
|
||||
.share_icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: #191919;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
background: #000000;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
&::before {
|
||||
top: 6.75px;
|
||||
left: 0.75px;
|
||||
}
|
||||
|
||||
&::after {
|
||||
top: 6.75px;
|
||||
right: 0.75px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.home_button {
|
||||
.home_icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: #000000;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 1.5px;
|
||||
left: 1.5px;
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
border: 2px solid #000000;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
left: 7px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: #000000;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 主要内容
|
||||
.main_content {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
padding: 0px 24px 36px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 36px;
|
||||
}
|
||||
|
||||
// 标题区域
|
||||
.title_section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
text-align: left;
|
||||
padding: 12px 24px 36px 24px ;
|
||||
|
||||
.main_title {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 28px;
|
||||
line-height: 1.4;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.sub_title {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 300;
|
||||
font-size: 18px;
|
||||
line-height: 1.4;
|
||||
color: #000000;
|
||||
}
|
||||
}
|
||||
|
||||
// 表单区域
|
||||
.form_section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
// 输入组
|
||||
.input_group {
|
||||
.input_container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||
border-radius: 12px;
|
||||
padding: 10px 12px;
|
||||
box-shadow: 0px 4px 36px 0px rgba(0, 0, 0, 0.06);
|
||||
|
||||
.phone_input {
|
||||
flex: 1;
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 500;
|
||||
font-size: 20px;
|
||||
line-height: 1.6;
|
||||
color: #000000;
|
||||
border: none;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
|
||||
&::placeholder {
|
||||
color: rgba(60, 60, 67, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
.char_count {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 1.714;
|
||||
color: rgba(60, 60, 67, 0.3);
|
||||
|
||||
.count_number {
|
||||
color: rgba(60, 60, 67, 0.3);
|
||||
transition: color 0.3s ease;
|
||||
|
||||
&.active {
|
||||
color: #000000;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 验证码组
|
||||
.verification_group {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
|
||||
.input_container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||
border-radius: 12px;
|
||||
padding: 10px 12px;
|
||||
width: 210px;
|
||||
height: 52px;
|
||||
box-sizing: border-box;
|
||||
|
||||
box-shadow: 0px 4px 36px 0px rgba(0, 0, 0, 0.06);
|
||||
|
||||
.code_input {
|
||||
flex: 1;
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 500;
|
||||
font-size: 20px;
|
||||
line-height: 1.6;
|
||||
color: #000000;
|
||||
border: none;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
|
||||
|
||||
&::placeholder {
|
||||
color: rgba(60, 60, 67, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
.char_count {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 1.714;
|
||||
color: rgba(60, 60, 67, 0.3);
|
||||
|
||||
.count_number {
|
||||
color: rgba(60, 60, 67, 0.3);
|
||||
transition: color 0.3s ease;
|
||||
|
||||
&.active {
|
||||
color: #000000;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.send_code_button {
|
||||
width: 120px;
|
||||
height: 52px;
|
||||
box-sizing: border-box;
|
||||
padding: 12px 2px;
|
||||
|
||||
background: #000000;
|
||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||
border-radius: 16px;
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
line-height: 1.4;
|
||||
color: #FFFFFF;
|
||||
box-shadow: 0px 8px 64px 0px rgba(0, 0, 0, 0.1);
|
||||
backdrop-filter: blur(32px);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
// 倒计时文案样式
|
||||
.countdown_text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
gap: 2px;
|
||||
|
||||
.countdown_line1 {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
line-height: 1.2;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.countdown_line2 {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 11px;
|
||||
line-height: 1.2;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 登录按钮
|
||||
.login_button {
|
||||
width: 100%;
|
||||
height: 52px;
|
||||
background: #000000;
|
||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||
border-radius: 16px;
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
padding: 6px 2px;
|
||||
color: #FFFFFF;
|
||||
box-shadow: 0px 8px 64px 0px rgba(0, 0, 0, 0.1);
|
||||
backdrop-filter: blur(32px);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&.loading {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 协议区域
|
||||
.terms_section {
|
||||
padding: 0 24px;
|
||||
text-align: center;
|
||||
line-height: 1.5;
|
||||
|
||||
.terms_text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: rgba(60, 60, 67, 0.6);
|
||||
}
|
||||
|
||||
.terms_link {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
color: #000000;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
margin: 0 4px;
|
||||
|
||||
&:hover {
|
||||
color: #333333;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 底部指示器
|
||||
.home_indicator {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 140.4px;
|
||||
height: 5px;
|
||||
background: #000000;
|
||||
border-radius: 2.5px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
260
src/pages/login/verification/index.tsx
Normal file
260
src/pages/login/verification/index.tsx
Normal file
@@ -0,0 +1,260 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { View, Text, Input, Button, Image } from '@tarojs/components';
|
||||
import Taro from '@tarojs/taro';
|
||||
import { phone_auth_login, send_sms_code } from '../../../services/loginService';
|
||||
import './index.scss';
|
||||
|
||||
const VerificationPage: React.FC = () => {
|
||||
const [phone, setPhone] = useState('');
|
||||
const [verification_code, setVerificationCode] = useState('');
|
||||
const [countdown, setCountdown] = useState(0);
|
||||
const [can_send_code, setCanSendCode] = useState(true);
|
||||
const [is_loading, setIsLoading] = useState(false);
|
||||
const [code_input_focus, setCodeInputFocus] = useState(false);
|
||||
|
||||
// 计算登录按钮是否应该启用
|
||||
const can_login = phone.length === 11 && verification_code.length === 6 && !is_loading;
|
||||
|
||||
|
||||
// 发送验证码
|
||||
const handle_send_code = async () => {
|
||||
if (!phone || phone.length !== 11) {
|
||||
Taro.showToast({
|
||||
title: '请输入正确的手机号',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!can_send_code) return;
|
||||
|
||||
try {
|
||||
console.log('开始发送验证码,手机号:', phone);
|
||||
|
||||
// 调用发送短信接口
|
||||
const result = await send_sms_code(phone);
|
||||
|
||||
console.log('发送验证码结果:', result);
|
||||
|
||||
if (result.success) {
|
||||
console.log('验证码发送成功,开始倒计时');
|
||||
|
||||
Taro.showToast({
|
||||
title: '验证码已发送',
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
});
|
||||
|
||||
// 开始倒计时
|
||||
setCanSendCode(false);
|
||||
setCountdown(60);
|
||||
|
||||
console.log('设置状态: can_send_code = false, countdown = 60');
|
||||
|
||||
// 发送验证码成功后,让验证码输入框获得焦点并调用系统键盘
|
||||
setTimeout(() => {
|
||||
// 设置验证码输入框聚焦状态
|
||||
setCodeInputFocus(true);
|
||||
// 清空验证码,让用户重新输入
|
||||
setVerificationCode('');
|
||||
console.log('设置验证码输入框聚焦');
|
||||
}, 500); // 延迟500ms确保Toast显示完成后再聚焦
|
||||
} else {
|
||||
console.log('验证码发送失败:', result.message);
|
||||
Taro.showToast({
|
||||
title: result.message || '发送失败',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('发送验证码异常:', error);
|
||||
Taro.showToast({
|
||||
title: '发送失败,请重试',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 倒计时效果
|
||||
useEffect(() => {
|
||||
console.log('倒计时 useEffect 触发,countdown:', countdown);
|
||||
|
||||
if (countdown > 0) {
|
||||
const timer = setTimeout(() => {
|
||||
console.log('倒计时减少,从', countdown, '到', countdown - 1);
|
||||
setCountdown(countdown - 1);
|
||||
}, 1000);
|
||||
return () => clearTimeout(timer);
|
||||
} else if (countdown === 0 && !can_send_code) {
|
||||
console.log('倒计时结束,重新启用发送按钮');
|
||||
setCanSendCode(true);
|
||||
}
|
||||
}, [countdown, can_send_code]);
|
||||
|
||||
// 手机号登录
|
||||
const handle_phone_login = async () => {
|
||||
if (!phone || phone.length !== 11) {
|
||||
Taro.showToast({
|
||||
title: '请输入正确的手机号',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!verification_code || verification_code.length !== 6) {
|
||||
Taro.showToast({
|
||||
title: '请输入6位验证码',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
// 调用登录服务
|
||||
const result = await phone_auth_login({ phone, verification_code });
|
||||
|
||||
if (result.success) {
|
||||
|
||||
setTimeout(() => {
|
||||
Taro.redirectTo({
|
||||
url: '/pages/index/index'
|
||||
});
|
||||
}, 200);
|
||||
} else {
|
||||
Taro.showToast({
|
||||
title: result.message || '登录失败',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
Taro.showToast({
|
||||
title: '登录失败,请重试',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<View className="verification_page">
|
||||
{/* 背景 */}
|
||||
<View className="background">
|
||||
<View className="bg_color"></View>
|
||||
</View>
|
||||
|
||||
|
||||
{/* 主要内容 */}
|
||||
<View className="main_content">
|
||||
{/* 标题区域 */}
|
||||
<View className="title_section">
|
||||
<Text className="main_title">手机号注册/登录有场</Text>
|
||||
<Text className="sub_title">Go Together, Grow Together</Text>
|
||||
</View>
|
||||
|
||||
{/* 表单区域 */}
|
||||
<View className="form_section">
|
||||
{/* 手机号输入 */}
|
||||
<View className="input_group">
|
||||
<View className="input_container">
|
||||
<Input
|
||||
className="phone_input"
|
||||
type="number"
|
||||
placeholder="输入中国内地手机号"
|
||||
placeholderClass="input_placeholder"
|
||||
value={phone}
|
||||
onInput={(e) => setPhone(e.detail.value)}
|
||||
maxlength={11}
|
||||
/>
|
||||
<View className="char_count">
|
||||
<Text className={phone.length > 0 ? 'count_number active' : 'count_number'}>
|
||||
{phone.length}
|
||||
</Text>
|
||||
/11
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 验证码输入和发送按钮 */}
|
||||
<View className="verification_group">
|
||||
<View className="input_container">
|
||||
<Input
|
||||
className="code_input"
|
||||
type="number"
|
||||
placeholder="输入短信验证码"
|
||||
placeholderClass="input_placeholder"
|
||||
placeholderStyle="color:#999999;"
|
||||
focus={code_input_focus}
|
||||
value={verification_code}
|
||||
onInput={(e) => setVerificationCode(e.detail.value)}
|
||||
onFocus={() => setCodeInputFocus(true)}
|
||||
onBlur={() => setCodeInputFocus(false)}
|
||||
maxlength={6}
|
||||
/>
|
||||
<View className="char_count">
|
||||
<Text className={verification_code.length > 0 ? 'count_number active' : 'count_number'}>
|
||||
{verification_code.length}
|
||||
</Text>
|
||||
/6
|
||||
</View>
|
||||
</View>
|
||||
<Button
|
||||
className={`send_code_button ${!can_send_code ? 'disabled' : ''}`}
|
||||
onClick={handle_send_code}
|
||||
disabled={!can_send_code}
|
||||
>
|
||||
{can_send_code ? (
|
||||
'获取验证码'
|
||||
) : (
|
||||
<View className="countdown_text">
|
||||
<Text className="countdown_line1">验证码已发送</Text>
|
||||
<Text className="countdown_line2">{countdown}秒后可重发</Text>
|
||||
</View>
|
||||
)}
|
||||
</Button>
|
||||
{/* 调试信息 */}
|
||||
{/* {process.env.NODE_ENV === 'development' && (
|
||||
<View style={{fontSize: '12px', color: '#999', marginTop: '5px'}}>
|
||||
调试: can_send_code={can_send_code.toString()}, countdown={countdown}
|
||||
</View>
|
||||
)} */}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 登录按钮 */}
|
||||
<View className="button_section">
|
||||
<Button
|
||||
className={`login_button ${is_loading ? 'loading' : ''} ${!can_login ? 'disabled' : ''}`}
|
||||
onClick={handle_phone_login}
|
||||
disabled={!can_login}
|
||||
>
|
||||
{'登录'}
|
||||
</Button>
|
||||
{/* 调试信息 */}
|
||||
{/* {process.env.NODE_ENV === 'development' && (
|
||||
<View style={{fontSize: '12px', color: '#999', marginTop: '5px', textAlign: 'center'}}>
|
||||
调试: 手机号长度={phone.length}, 验证码长度={verification_code.length}, 可登录={can_login.toString()}
|
||||
</View>
|
||||
)} */}
|
||||
</View>
|
||||
|
||||
|
||||
</View>
|
||||
|
||||
{/* 底部指示器 */}
|
||||
<View className="home_indicator"></View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default VerificationPage;
|
||||
Reference in New Issue
Block a user