diff --git a/功能说明.md b/功能说明.md new file mode 100644 index 0000000..d4aef58 --- /dev/null +++ b/功能说明.md @@ -0,0 +1,791 @@ +# flux-web 功能说明文档 + +## 项目简介 + +flux-web 是薇钱包 H5 前端项目,提供用户借款申请、信息填写和授权流程的完整业务功能。 + +--- + +## 一、业务流程总览 + +### 1.1 完整用户旅程 + +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ index.html │───>│ basic-info.html │───>│ 授权流程 │ +│ 借款申请页面 │ │ 基本信息填写 │ │ (可选) │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ +``` + +### 1.2 核心业务流程图 + +``` +用户进入页面 + │ + ├─> 输入/修改借款金额 + ├─> 选择还款期数 + ├─> 选择借款用途 + │ + ├─> [已有登录态?] ──是──> 显示脱敏手机号 + │ │ + │ └─> 勾选协议 → 跳转基本信息页 + │ + └─> 否 ──> 选择登录方式 + │ + ├─> 方式A: 极光一键登录 + │ └─> 成功 → 注册/登录 → 跳转 + │ └─> 失败 → 降级到方式B + │ + └─> 方式B: 短信验证码登录 + ├─> 输入手机号 + ├─> 发送验证码 (60s倒计时) + ├─> 验证码校验 + └─> 注册/登录 → 跳转 +``` + +--- + +## 二、index.html - 借款申请页面 + +### 2.1 页面元素与交互 + +| 元素 | DOM ID | 交互行为 | 数据流向 | +|------|--------|----------|----------| +| 借款金额输入 | `loanAmount` | input 事件触发 | `loanData.amount` | +| 全部借出按钮 | `maxLoan` | 点击设为最大值 | `loanData.amount = 200000` | +| 还款期数 | `repaymentTerm` | 点击打开选择器 | `loanData.period` | +| 还款计划 | `repaymentPlan` | 自动计算更新 | `calculateRepaymentPlan()` | +| 借款用途 | `loanPurpose` | 点击打开选择器 | `loanData.purpose` | +| 手机号输入 | `phoneNumber` | 输入 + 验证 | → 验证码弹窗 | +| 一键登录 | `oneClickLoginWrapper` | 极光SDK | → 成功后自动填手机号 | +| 立即申请按钮 | `applyBtn` | 点击触发申请 | → 验证码/跳转 | +| 协议勾选 | `agreementCheck` | 必选 | 未勾选弹窗提示 | + +### 2.2 借款数据结构 + +```javascript +loanData = { + amount: 50000, // 借款金额 (1000-200000) + period: 12, // 还款期数 (3,6,9,12,18,24,36) + purpose: '个人日常消费' // 借款用途 +} +``` + +### 2.3 还款计划计算逻辑 + +**接口:** `LoanService.calculateRepaymentPlan(amount, period, interestRate)` + +**计算公式:** +``` +月均本金 = 借款金额 / 期数 +月利息 = 借款金额 × (年化利率 / 100) / 12 +首期还款 = 月均本金 + 月利息 +首期日期 = 当前日期 + 30天 +``` + +**年化利率范围:** 10.8% - 24%(单利) + +**显示格式:** `首期02月05日 应还 4916.67元` + +--- + +## 三、用户认证流程 + +### 3.1 极光一键登录 + +**初始化条件:** +- 配置了 `jVerifyAppId` +- 用户未登录 + +**流程:** +``` +加载极光SDK + │ + ├─> 成功获取手机号 + │ └─> AuthService.registerOrLogin(phone, loanData) + │ └─> 成功 → 跳转基本信息页 + │ └─> 失败 → 显示手机号输入框 + │ + └─> 失败/不支持 + └─> 自动降级到短信验证码登录 +``` + +**关键接口:** `AuthService.registerOrLogin(phone, loanData)` + +### 3.2 短信验证码登录 + +**流程图:** +``` +输入手机号 + │ + ├─> 验证手机号格式 + │ └─> 无效 → 显示错误提示 + │ + ├─> 有效 → 点击"立即申请" + │ │ + │ ├─> 勾选协议? + │ │ └─> 否 → 弹协议确认窗 + │ │ + │ └─> 是 → 打开验证码弹窗 + │ │ + │ ├─> 发送验证码 + │ │ ├─> SMSService.send(phone) + │ │ ├─> 成功 → 启动60s倒计时 + │ │ └─> 失败 → 显示错误,可重新发送 + │ │ + │ ├─> 输入验证码 + │ │ └─> SMSService.verify(phone, code) + │ │ └─> 成功 → 注册/登录 + │ │ └─> 失败 → 显示错误 + │ │ + │ └─> 验证通过 + │ └─> 跳转基本信息页 +``` + +**倒计时逻辑:** +- 60秒倒计时 +- 倒计时结束显示"重新发送" +- 可点击重新发送 + +### 3.3 接口数据说明 + +#### 发送短信验证码 + +**接口:** `POST /zcore/sms/send` + +**请求格式:** `application/json` + +```json +{ + "phone": "13800138000" +} +``` + +**响应:** +```json +{ + "retcode": 0, + "retinfo": "发送成功" +} +``` + +#### 验证短信验证码 + +**接口:** `POST /zcore/sms/verify` + +**请求格式:** `application/json` + +```json +{ + "phone": "13800138000", + "code": "123456" +} +``` + +**响应:** +```json +{ + "retcode": 0, + "retinfo": "验证成功" +} +``` + +#### 用户注册/登录 + +**接口:** `POST /api/partnerh5/login` + +**请求格式:** `application/x-www-form-urlencoded` + +``` +bean={"phone":"13800138000","loanamount":50000,"repaymentperiod":12,"loanpurpose":"个人日常消费"} +``` + +**响应:** +```json +{ + "retcode": 0, + "result": { + "customerid": "12345", + "sessionid": "abc123xyz", + "loginPhone": "13800138000" + } +} +``` + +**本地存储:** +```javascript +UserCache.saveUserSession({ + customerid: "12345", + sessionid: "abc123xyz", + loginPhone: "13800138000", + formData: { loanamount: 50000, ... } +}) +``` + +**后续请求自动添加请求头:** +``` +jsessionid: abc123xyz +``` + +--- + +## 四、basic-info.html - 基本信息填写页面 + +### 4.1 页面结构 + +``` +┌─────────────────────────────────┐ +│ 顶部进度卡片 │ +│ - 预期额度: 35000 → 50000 │ +│ - 进度条: 0% → 100% │ +│ - 已完成: 0/8 │ +└─────────────────────────────────┘ + │ + ├─> 选择一项,进度+12.5% + │ 预期额度增加1875元 + │ +┌─────────────────────────────────┐ +│ 资产信息区域 (渐进式显示) │ +│ 1. 房产: 有房产/无房产 │ +│ 2. 车辆: 有车辆/无车辆 │ +│ 3. 公积金: 有/无 │ +│ 4. 社保: 有/无 │ +│ 5. 信用卡: 有/无 │ +│ 6. 银行流水: 有/无 │ +│ 7. 职业: 4个选项 │ +│ 8. 芝麻分: 4个选项 │ +└─────────────────────────────────┘ + │ + └─> 8项全部完成后 + ↓ +┌─────────────────────────────────┐ +│ 基本信息区域 (自动展开) │ +│ 1. 真实姓名 (输入) │ +│ 2. 身份证号 (输入) │ +│ 3. 所属城市 (选择器) │ +└─────────────────────────────────┘ + │ + ↓ +┌─────────────────────────────────┐ +│ 提交按钮 (基本信息完成时可用) │ +└─────────────────────────────────┘ +``` + +### 4.2 资产选项配置 + +```javascript +ASSET_CONFIG.ITEMS = [ + { id: 'house', name: '房产', options: ['有房产', '无房产'] }, + { id: 'car', name: '车辆', options: ['有车辆', '无车辆'] }, + { id: 'fund', name: '公积金', options: ['有公积金', '无公积金'] }, + { id: 'social', name: '社保', options: ['有社保', '无社保'] }, + { id: 'credit', name: '信用卡', options: ['有信用卡', '无信用卡'] }, + { id: 'bank', name: '银行流水', options: ['有银行流水', '无银行流水'] }, + { id: 'job', name: '职业', options: ['上班族', '自由职业', '企业主', '公务员/国企'] }, + { id: 'zhima', name: '芝麻分', options: ['700以上', '650-700', '600-650', '无'] } +] +``` + +### 4.3 数据映射关系 + +**前端中文 → 后端数字:** +```javascript +VALUE_MAPPING = { + '有房产': 1, '无房产': 2, + '有车辆': 1, '无车辆': 2, + // ... 其他类似 + '上班族': 1, '自由职业': 2, '企业主': 3, '公务员/国企': 4, + '700以上': 1, '650-700': 2, '600-650': 3, '无': 4 +} +``` + +### 4.4 渐进式显示逻辑 + +```javascript +selectAssetOption(itemId, value) { + // 1. 保存选择 + this.selectedValues[itemId] = value; + + // 2. 更新进度 + const completed = Object.keys(this.selectedValues).length; + const progress = (completed / 8) * 100; + const money = 35000 + (50000 - 35000) * (progress / 100); + + // 3. 检查是否是当前步骤 + const currentIndex = ASSET_CONFIG.ITEMS.findIndex(item => item.id === itemId); + if (currentIndex === this.currentStep && this.currentStep < 7) { + // 延迟400ms后显示下一项 + setTimeout(() => this.revealNextItem(), 400); + } + + // 4. 自动保存草稿(2秒后) + this.autoSaveDraft(); +} +``` + +### 4.5 城市选择逻辑 + +**IP定位自动填充:** +``` +页面加载 + │ + ├─> 调用 IP定位接口 + │ └─> /api/partnerh5/ip_location + │ └─> 返回 { province: "广东省", city: "深圳市" } + │ + ├─> 查询城市代码 + │ └─> AreaService.findCityCode(province, city) + │ └─> 返回 { provinceCode: "440000", cityCode: "440300" } + │ + └─> 自动填充城市字段 +``` + +**省市区数据结构:** +```javascript +PROVINCE_CITY_DATA = { + '广东省': ['广州市', '深圳市', '珠海市', ...], + '江西省': ['南昌市', '九江市', ...], + ... +} +``` + +--- + +## 五、表单提交与草稿管理 + +### 5.1 草稿自动保存 + +**触发条件:** +- 选择任一资产选项 +- 修改任一基本信息 + +**延迟机制:** 2秒防抖 + +**保存接口:** `POST /api/partnerh5/save_draft` + +**请求数据:** +```javascript +{ + shortcode: "I3fMzX", // URL参数或默认值 + formid: "uuid-xxx", // 唯一表单ID + draftstatus: 1, // 1=草稿,0=正式提交 + + // 资产信息(映射为数字) + house: 1, car: 2, fund: 1, ..., + + // 基本信息 + name: "张三", + idCard: "110101199001011234", + city: "深圳市" +} +``` + +### 5.2 表单正式提交 + +**提交接口:** `POST /api/partnerh5/submit` + +**请求数据结构:** +```javascript +{ + shortcode: "I3fMzX", + formid: "uuid-xxx", + draftstatus: 0, // 0=正式提交 + + // 资产信息 + house: 1, car: 2, fund: 1, social: 1, + credit: 1, bank: 1, job: 1, zhima: 2, + + // 基本信息 + name: "张三", + idCard: "110101199001011234", + city: "深圳市" +} +``` + +**响应数据:** +```json +{ + "retcode": 0, + "retinfo": "提交成功", + "result": { + "formdataid": 12345, // 表单数据ID(用于授权流程) + "h5Urls": [ // 需要授权的H5列表(可能为空) + { + "apicode": "TAOBAA", + "apiname": "淘宝授权", + "h5url": "https://..." + }, + { + "apicode": "JD", + "apiname": "京东授权", + "h5url": "https://..." + } + ], + "redirectUrl": "https://..." // 最终跳转URL(H5直推地址) + } +} +``` + +### 5.3 表单验证规则 + +| 字段 | 验证规则 | 错误提示 | +|------|----------|----------| +| 手机号 | `/^1[3-9]\d{9}$/` | "请输入有效的11位手机号" | +| 姓名 | `/^[\u4e00-\u9fa5]{2,20}/` | "请输入2-20位中文姓名" | +| 身份证 | 18位身份证校验 | "请输入有效的身份证号码" | +| 验证码 | 6位数字 | "请输入6位验证码" | + +--- + +## 六、授权流程(AuthFlow) + +### 6.1 流程触发条件 + +表单提交成功且 `h5Urls` 不为空时触发。 + +### 6.2 授权状态枚举 + +```javascript +AUTH_STATUS = { + WAITING: 1, // 等待授权 + CALLBACK: 2, // 已回调 + APPLY_OK: 3, // 进件成功 + APPLY_FAIL: 4 // 进件失败 +} +``` + +### 6.3 授权流程图 + +``` +表单提交成功,返回 h5Urls + │ + ↓ +┌─────────────────────────────────┐ +│ 显示授权进度列表 │ +│ - 当前产品: 1/3 │ +│ - 状态: ● 淘宝授权 授权中... │ +│ - iframe 加载 h5url │ +└─────────────────────────────────┘ + │ + ↓ +开始轮询授权状态 + │ + ├─> 每3秒查询一次 + │ └─> POST /api/partnerh5/check_auth_status + │ └─> { formdataid: 12345, apicode: "TAOBAO" } + │ + ├─> 状态判断 + │ ├─> status = 1 (WAITING) → 继续轮询 + │ ├─> status = 2 (CALLBACK) → 成功,下一个 + │ ├─> status = 3 (APPLY_OK) → 成功,下一个 + │ └─> status = 4 (APPLY_FAIL) → 失败,下一个 + │ + ├─> 超时判断 + │ └─> 超过10分钟 → 标记超时,下一个 + │ + └─> 全部完成 + ↓ + ┌─────────────────────────────────┐ + │ 显示完成页面 │ + │ - 全部授权完成 ✓ │ + │ - 5秒后自动跳转... │ + │ - [立即跳转] 按钮 │ + └─────────────────────────────────┘ + │ + ├─> 有 redirectUrl → 跳转到指定页面 + └─> 无 redirectUrl → 返回首页 +``` + +### 6.4 授权状态查询接口 + +**接口:** `POST /api/partnerh5/check_auth_status` + +**请求格式:** `application/x-www-form-urlencoded` + +``` +bean={"formdataid":12345,"apicode":"TAOBAO"} +``` + +**响应:** +```json +{ + "retcode": 0, + "result": { + "apicode": "TAOBAO", + "apiname": "淘宝授权", + "status": 2, + "h5url": "https://..." + } +} +``` + +### 6.5 轮询配置 + +```javascript +POLL_INTERVAL = 3000 // 轮询间隔:3秒 +POLL_TIMEOUT = 10 * 60 * 1000 // 超时时间:10分钟 +COUNTDOWN_SECONDS = 5 // 完成后倒计时:5秒 +``` + +### 6.6 Mixed Content 处理 + +当 HTTPS 页面需要加载 HTTP 的 iframe 时: + +```javascript +if (window.location.protocol === 'https:' && h5url.startsWith('http://')) { + // 检测到 Mixed Content,使用新窗口打开 + window.open(h5url, '_blank'); +} +``` + +--- + +## 七、页面间数据传递 + +### 7.1 URL 参数传递 + +```javascript +// 从 index.html 跳转到 basic-info.html,保留当前页的查询串 +window.location.href = 'basic-info.html' + window.location.search; + +// 跳转后 URL 参数保持不变,例如: +// basic-info.html?shortcode=I3fMzX&channel=toutiao +// 其中 shortcode 为短链编码(与后端 shortcode 对应),;channel 等其它参数一并保留。 +``` + +### 7.2 本地存储传递 + +```javascript +// 保存用户会话 +UserCache.saveUserSession({ + customerid: "12345", + sessionid: "abc123", + loginPhone: "13800138000", + formData: { + loanamount: 50000, + repaymentperiod: 12, + loanpurpose: "个人日常消费" + } +}); + +// 读取用户会话 +const session = UserCache.getUserSession(); +``` + +### 7.3 表单ID管理 + +```javascript +// 生成或获取表单ID +const formId = FormIdGenerator.getOrCreate(); // uuid + +// 提交成功后清除 +FormIdGenerator.clear(); +``` + +--- + +## 八、API 接口汇总 + +### 8.1 接口列表 + +| 接口 | 方法 | 用途 | Content-Type | +|------|------|------|--------------| +| /zcore/sms/send | POST | 发送短信验证码 | application/json | +| /zcore/sms/verify | POST | 验证短信验证码 | application/json | +| /zcore/jpush/login | POST | 极光一键登录 | application/json | +| /api/partnerh5/login | POST | 用户注册/登录 | x-www-form-urlencoded | +| /api/partnerh5/submit | POST | 提交表单 | x-www-form-urlencoded | +| /api/partnerh5/save_draft | POST | 保存草稿 | x-www-form-urlencoded | +| /api/partnerh5/get_draft | POST | 获取草稿 | x-www-form-urlencoded | +| /api/partnerh5/check_auth_status | POST | 检查授权状态 | x-www-form-urlencoded | +| /api/partnerh5/area_list | GET | 获取区域列表 | - | +| /api/partnerh5/ip_location | GET | IP定位 | - | + +### 8.2 请求头规范 + +```javascript +// 所有请求自动添加 +headers: { + 'Content-Type': 'application/json' 或 'application/x-www-form-urlencoded', + 'jsessionid': '从 UserCache 获取' // 用户登录后自动添加 +} +``` + +### 8.3 响应格式规范 + +```json +{ + "retcode": 0, // 0=成功,其他=失败 + "retinfo": "操作成功", // 提示信息 + "result": { ... } // 返回数据(可选) +} +``` + +--- + +## 九、配置说明 + +### 9.1 API 配置 + +```javascript +// src/js/config/api.config.js +BASE_URL: '' // 空字符串表示同源部署 +TIMEOUT: 30000 // 请求超时30秒 +``` + +### 9.2 应用配置 + +```javascript +// src/js/config/app.config.js +DEBUG_CONFIG: { + ENABLED: false, // 生产环境设为 false + SMS_CODE: '123456', // 调试模式固定验证码 + DEFAULT_SHORTCODE: 'I3fMzX', // 默认短链代码 + VERBOSE_LOGGING: true // 详细日志 +} + +LOAN_CONFIG: { + AMOUNT: { MIN: 1000, MAX: 200000, DEFAULT: 50000 }, + INTEREST_RATE: { MIN: 10.8, MAX: 24 }, + PERIOD_OPTIONS: [3, 6, 9, 12, 18, 24, 36] +} + +ASSET_CONFIG: { + PROGRESS_MONEY: { + INITIAL: 35000, // 初始预期额度 + FINAL: 50000 // 最终预期额度 + } +} +``` + +--- + +## 十、关键业务逻辑 + +### 10.1 一键登录降级机制 + +``` +极光一键登录初始化 + │ + ├─> 成功获取手机号 + │ └─> 调用注册接口 + │ ├─> 成功 → 跳转 + │ └─> 失败 → 显示手机号输入框 + │ + └─> 失败/不支持 + └─> 隐藏一键登录按钮 + └─> 显示手机号输入框 + └─> 显示"立即申请"按钮 +``` + +### 10.2 已登录用户处理 + +```javascript +// 页面加载时检查登录态 +restoreUserData() { + const session = UserCache.getUserSession(); + + if (session && session.loginPhone) { + // 显示脱敏手机号 + this.elements.phoneNumber.value = Formatter.maskPhone(session.loginPhone); + this.elements.phoneNumber.readOnly = true; + + // 恢复表单数据 + if (session.formData) { + this.elements.loanAmount.value = session.formData.loanamount; + // ... + } + } +} +``` + +### 10.3 协议确认逻辑 + +``` +点击"立即申请" + │ + ├─> 已登录 + │ └─> 勾选协议? ──否──> 弹协议确认窗 + │ └─> 是 → 跳转基本信息页 + │ + └─> 未登录 + └─> 验证手机号 + └─> 勾选协议? ──否──> 弹协议确认窗 + └─> 是 → 显示验证码弹窗 +``` + +### 10.4 表单提交锁机制 + +```javascript +// 防止重复提交 +this.isSubmitting = false; + +async handleSubmit() { + if (this.isSubmitting) return; // 已在提交中 + + this.elements.submitBtn.disabled = true; + this.elements.submitBtn.textContent = '提交中...'; + this.isSubmitting = true; + + try { + const response = await DraftManager.submitForm(...); + if (response.success) { + // 处理成功 + } else { + throw new Error(response.message); + } + } catch (error) { + // 恢复按钮状态 + this.elements.submitBtn.disabled = false; + this.elements.submitBtn.textContent = '下一步'; + this.isSubmitting = false; + } +} +``` + +--- + +## 十一、错误处理 + +### 11.1 短信发送失败 + +``` +发送短信 + │ + └─> 失败 + ├─> 显示错误信息 + ├─> 倒计时显示"重新发送" + └─> 可点击重新发送 +``` + +### 11.2 验证码错误 + +``` +输入验证码点击确定 + │ + └─> 验证失败 + ├─> 显示错误提示 + ├─> 输入框标红 + ├─> 保持弹窗打开 + └─> 可重新输入 +``` + +### 11.3 表单提交失败 + +``` +提交表单 + │ + └─> 失败 + ├─> Toast 提示错误信息 + ├─> 恢复提交按钮 + ├─> 不清除表单数据 + └─> 可重新提交 +``` + +--- + +## 十二、版本信息 + +- **当前版本:** 2.0.0 +- **最后更新:** 2025-01-21 +- **版权所有:** 北京百雅科技有限公司