# 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 - **版权所有:** 北京百雅科技有限公司