Files
flux-web/功能说明.md
2026-02-03 20:29:21 +08:00

22 KiB
Raw Permalink Blame History

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 借款数据结构

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

{
    "phone": "13800138000"
}

响应:

{
    "retcode": 0,
    "retinfo": "发送成功"
}

验证短信验证码

接口: POST /zcore/sms/verify

请求格式: application/json

{
    "phone": "13800138000",
    "code": "123456"
}

响应:

{
    "retcode": 0,
    "retinfo": "验证成功"
}

用户注册/登录

接口: POST /api/partnerh5/login

请求格式: application/x-www-form-urlencoded

bean={"phone":"13800138000","loanamount":50000,"repaymentperiod":12,"loanpurpose":"个人日常消费"}

响应:

{
    "retcode": 0,
    "result": {
        "customerid": "12345",
        "sessionid": "abc123xyz",
        "loginPhone": "13800138000"
    }
}

本地存储:

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 资产选项配置

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 数据映射关系

前端中文 → 后端数字:

VALUE_MAPPING = {
    '有房产': 1, '无房产': 2,
    '有车辆': 1, '无车辆': 2,
    // ... 其他类似
    '上班族': 1, '自由职业': 2, '企业主': 3, '公务员/国企': 4,
    '700以上': 1, '650-700': 2, '600-650': 3, '无': 4
}

4.4 渐进式显示逻辑

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" }
    │
    └─> 自动填充城市字段

省市区数据结构:

PROVINCE_CITY_DATA = {
    '广东省': ['广州市', '深圳市', '珠海市', ...],
    '江西省': ['南昌市', '九江市', ...],
    ...
}

五、表单提交与草稿管理

5.1 草稿自动保存

触发条件:

  • 选择任一资产选项
  • 修改任一基本信息

延迟机制: 2秒防抖

保存接口: POST /api/partnerh5/save_draft

请求数据:

{
    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

请求数据结构:

{
    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: "深圳市"
}

响应数据:

{
    "retcode": 0,
    "retinfo": "提交成功",
    "result": {
        "formdataid": 12345,           // 表单数据ID用于授权流程
        "h5Urls": [                    // 需要授权的H5列表可能为空
            {
                "apicode": "TAOBAA",
                "apiname": "淘宝授权",
                "h5url": "https://..."
            },
            {
                "apicode": "JD",
                "apiname": "京东授权",
                "h5url": "https://..."
            }
        ],
        "redirectUrl": "https://..."   // 最终跳转URLH5直推地址
    }
}

5.3 表单验证规则

字段 验证规则 错误提示
手机号 /^1[3-9]\d{9}$/ "请输入有效的11位手机号"
姓名 /^[\u4e00-\u9fa5]{2,20}/ "请输入2-20位中文姓名"
身份证 18位身份证校验 "请输入有效的身份证号码"
验证码 6位数字 "请输入6位验证码"

六、授权流程AuthFlow

6.1 流程触发条件

表单提交成功且 h5Urls 不为空时触发。

6.2 授权状态枚举

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"}

响应:

{
    "retcode": 0,
    "result": {
        "apicode": "TAOBAO",
        "apiname": "淘宝授权",
        "status": 2,
        "h5url": "https://..."
    }
}

6.5 轮询配置

POLL_INTERVAL = 3000           // 轮询间隔3秒
POLL_TIMEOUT = 10 * 60 * 1000  // 超时时间10分钟
COUNTDOWN_SECONDS = 5          // 完成后倒计时5秒

6.6 Mixed Content 处理

当 HTTPS 页面需要加载 HTTP 的 iframe 时:

if (window.location.protocol === 'https:' && h5url.startsWith('http://')) {
    // 检测到 Mixed Content使用新窗口打开
    window.open(h5url, '_blank');
}

七、页面间数据传递

7.1 URL 参数传递

// 从 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 本地存储传递

// 保存用户会话
UserCache.saveUserSession({
    customerid: "12345",
    sessionid: "abc123",
    loginPhone: "13800138000",
    formData: {
        loanamount: 50000,
        repaymentperiod: 12,
        loanpurpose: "个人日常消费"
    }
});

// 读取用户会话
const session = UserCache.getUserSession();

7.3 表单ID管理

// 生成或获取表单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 请求头规范

// 所有请求自动添加
headers: {
    'Content-Type': 'application/json'  'application/x-www-form-urlencoded',
    'jsessionid': '从 UserCache 获取'  // 用户登录后自动添加
}

8.3 响应格式规范

{
    "retcode": 0,           // 0=成功,其他=失败
    "retinfo": "操作成功",   // 提示信息
    "result": { ... }       // 返回数据(可选)
}

九、配置说明

9.1 API 配置

// src/js/config/api.config.js
BASE_URL: ''                // 空字符串表示同源部署
TIMEOUT: 30000              // 请求超时30秒

9.2 应用配置

// 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 已登录用户处理

// 页面加载时检查登录态
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 表单提交锁机制

// 防止重复提交
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
  • 版权所有: 北京百雅科技有限公司