Compare commits
12 Commits
0f90faa595
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c2bfdef8f | |||
| 033aa5dfec | |||
| 4f025ce788 | |||
| cdda12afaa | |||
| 027db84a25 | |||
| 520b9e93bd | |||
| 01d1ce5ba8 | |||
| 8eb09ce6f0 | |||
| 7f87126c53 | |||
| d492c1c42e | |||
| f769373da1 | |||
| d1b2388616 |
16
DEPLOY.md
16
DEPLOY.md
@@ -20,6 +20,19 @@ BASE_URL: 'https://your-api-domain.com', // 改成你的API地址
|
||||
ENABLED: false, // 改成 false
|
||||
```
|
||||
|
||||
### 3. 配置极光域名白名单(如果使用一键登录功能)
|
||||
|
||||
如果使用极光一键登录功能,需要在极光控制台配置域名白名单:
|
||||
|
||||
1. 登录极光控制台:https://www.jiguang.cn/
|
||||
2. 进入应用管理 → 选择应用 → 一键登录配置
|
||||
3. 在"Web端配置"中添加允许的域名(如:`abc.1216.top`)
|
||||
4. 保存配置并等待生效(通常几分钟内)
|
||||
|
||||
**注意**:如果不配置域名白名单,会出现跨域错误,一键登录功能将无法使用。
|
||||
|
||||
详细说明请参考:`docs/cors-issue-fix.md`
|
||||
|
||||
---
|
||||
|
||||
## 🚀 三种部署方式
|
||||
@@ -130,6 +143,9 @@ A: 确认 `static/` 和 `src/` 目录都已上传
|
||||
**Q: 如何清除缓存?**
|
||||
A: 修改 `index.html` 中 CSS/JS 引用,加版本号:`style.css?v=2`
|
||||
|
||||
**Q: 极光一键登录出现跨域错误?**
|
||||
A: 需要在极光控制台配置域名白名单,详细说明请参考 `docs/cors-issue-fix.md`
|
||||
|
||||
---
|
||||
|
||||
## 📞 需要帮助?
|
||||
|
||||
294
agre/与第三方共享个人信息清单.html
Normal file
294
agre/与第三方共享个人信息清单.html
Normal file
@@ -0,0 +1,294 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>与第三方共享个人信息清单</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
line-height: 1.8;
|
||||
padding: 16px;
|
||||
}
|
||||
.agreement-container {
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
max-width: 100%;
|
||||
}
|
||||
.agreement-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
color: #333;
|
||||
}
|
||||
.agreement-content {
|
||||
font-size: 14px;
|
||||
line-height: 1.8;
|
||||
color: #333;
|
||||
}
|
||||
.agreement-content p {
|
||||
margin-bottom: 12px;
|
||||
text-align: justify;
|
||||
}
|
||||
.agreement-content .section-title {
|
||||
font-weight: 600;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
}
|
||||
.agreement-content .section-item {
|
||||
margin-left: 20px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.agreement-content .bold {
|
||||
font-weight: 600;
|
||||
}
|
||||
.agreement-content a {
|
||||
color: #3474fe;
|
||||
text-decoration: none;
|
||||
word-break: break-all;
|
||||
}
|
||||
.agreement-content a:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 20px 0;
|
||||
font-size: 12px;
|
||||
overflow-x: auto;
|
||||
display: block;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
table thead {
|
||||
display: table;
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
}
|
||||
table tbody {
|
||||
display: table;
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
}
|
||||
table tr {
|
||||
display: table-row;
|
||||
}
|
||||
table th,
|
||||
table td {
|
||||
border: 1px solid #323232;
|
||||
padding: 8px 4px;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
word-wrap: break-word;
|
||||
display: table-cell;
|
||||
min-width: 80px;
|
||||
}
|
||||
table th {
|
||||
background-color: #f5f5f5;
|
||||
font-weight: 600;
|
||||
font-size: 11px;
|
||||
}
|
||||
table td {
|
||||
font-size: 11px;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
table {
|
||||
font-size: 10px;
|
||||
}
|
||||
table th,
|
||||
table td {
|
||||
padding: 6px 3px;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
.info-block {
|
||||
margin-bottom: 20px;
|
||||
padding: 12px;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.info-block .label {
|
||||
font-weight: 600;
|
||||
margin-right: 8px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="agreement-container">
|
||||
<div class="agreement-title">与第三方共享个人信息清单</div>
|
||||
<div class="agreement-content">
|
||||
<div class="section-title">第三方SDK列表</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>SDK名称</th>
|
||||
<th>提供者名称</th>
|
||||
<th>沟通反馈渠道</th>
|
||||
<th>隐私政策链接地址</th>
|
||||
<th>SDK基本功能</th>
|
||||
<th>业务需求</th>
|
||||
<th>使用目的</th>
|
||||
<th>收集信息</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>e签宝身份核验认证SDK</td>
|
||||
<td>杭州天谷信息科技有限公司</td>
|
||||
<td>400-087-8198</td>
|
||||
<td><a href="https://www.esign.cn/gw/fwxy/" target="_blank">https://www.esign.cn/gw/fwxy/</a></td>
|
||||
<td>身份证识别、人像检测</td>
|
||||
<td>人像检测、身份认证</td>
|
||||
<td>人像检测、拍摄并识别身份证信息</td>
|
||||
<td>传感器<br>网络信息<br>身份证信息(指姓名、身份证等)及影像件(指照片及视频)<br>地理位置信息</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>百度人脸核验SDK</td>
|
||||
<td>北京百度网讯科技有限公司</td>
|
||||
<td>400-6700-866</td>
|
||||
<td><a href="https://privacy.baidu.com/policy" target="_blank">https://privacy.baidu.com/policy</a></td>
|
||||
<td>身份证识别、人像检测</td>
|
||||
<td>人像检测、身份认证</td>
|
||||
<td>人像检测、拍摄并识别身份证信息</td>
|
||||
<td>传感器<br>网络信息<br>身份证信息(指姓名、身份证等)及影像件(指照片及视频)<br>地理位置信息</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>com.alipay(支付宝|mPaaS|阿里乘车码|芝麻认证|实人认证)SDK</td>
|
||||
<td>蚂蚁集团</td>
|
||||
<td>95188</td>
|
||||
<td><a href="https://render.alipay.com/p/yuyan/180020010001196791/preview.html?agreementId=AG00000174" target="_blank">https://render.alipay.com/p/yuyan/180020010001196791/preview.html?agreementId=AG00000174</a></td>
|
||||
<td>身份证识别、人像检测</td>
|
||||
<td>人像检测、身份认证</td>
|
||||
<td>人像检测、拍摄并识别身份证信息</td>
|
||||
<td>传感器<br>网络信息<br>身份证信息(指姓名、身份证等)及影像件(指照片及视频)<br>地理位置信息</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>极光安全认证SDK</td>
|
||||
<td>深圳市和讯华谷信息技术有限公司</td>
|
||||
<td>0755-83881462</td>
|
||||
<td><a href="https://www.jiguang.cn/license/privacy" target="_blank">https://www.jiguang.cn/license/privacy</a></td>
|
||||
<td>一键登录功能</td>
|
||||
<td>一键登录功能</td>
|
||||
<td>提供一键登录认证服务</td>
|
||||
<td>获取设备信息(包括Android ID、GAID、OAID、UAID、IDFA、ICCID)<br>获取位置信息<br>手机号<br>获取设备读写外部储存<br>网络信息(包括网络类型、运营商信息、IP地址、WIFI状态信息)<br>获取网络连接状态(包括网络类型、运营商名称、基站信息、IP地址、WiFi状态信息、WiFi列表信息、SSID、BSSID)<br>软件列表信息(包括软件列表及软件运行列表信息)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>移动安全联盟OAID</td>
|
||||
<td>天翼数字生活科技有限公司</td>
|
||||
<td>4008-281-189</td>
|
||||
<td><a href="https://e.189.cn/sdk/agreement/detail.do?hidetop=true" target="_blank">https://e.189.cn/sdk/agreement/detail.do?hidetop=true</a></td>
|
||||
<td>获取oaid</td>
|
||||
<td>获取oaid</td>
|
||||
<td>获取oaid</td>
|
||||
<td>设备信息</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Bugly SDK</td>
|
||||
<td>深圳市腾讯计算机系统有限公司</td>
|
||||
<td><a href="https://kf.qq.com/" target="_blank">https://kf.qq.com/</a><br>4006-700-700</td>
|
||||
<td><a href="https://privacy.qq.com/document/priview/fbd2c3f898df4c1c869925dd49d57827" target="_blank">https://privacy.qq.com/document/priview/fbd2c3f898df4c1c869925dd49d57827</a><br><a href="https://static.bugly.qq.com/bugly-sdk-privacy-statement.pdf" target="_blank">https://static.bugly.qq.com/bugly-sdk-privacy-statement.pdf</a></td>
|
||||
<td>实现应用崩溃上报和崩溃统计分析</td>
|
||||
<td>崩溃跟踪</td>
|
||||
<td>为了实现应用崩溃上报和崩溃统计分析</td>
|
||||
<td>日志信息(包括:自定义日志、Logcat日志以及APP崩溃堆栈信息)、AndroidID、联网信息、系统名称、系统版本以及国家码。</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>腾讯浏览服务X5网页引擎SDK</td>
|
||||
<td>深圳市腾讯计算机系统有限公司</td>
|
||||
<td><a href="https://kf.qq.com/" target="_blank">https://kf.qq.com/</a><br>4006-700-700</td>
|
||||
<td><a href="https://x5.tencent.com/docs/privacy.html" target="_blank">https://x5.tencent.com/docs/privacy.html</a></td>
|
||||
<td>Web页面浏览</td>
|
||||
<td>提升用户网页浏览体验</td>
|
||||
<td>为用户提供更好的Webview内核,提供更好的网页浏览服务</td>
|
||||
<td>设备信息(设备型号、操作系统、CPU类型)、应用信息(宿主应用包名,版本号)、Wi-Fi状态和参数、IP地址、AndroidID、精确位置信息(可选)、附近的Wi-Fi(可选)、CellID(可选)、OAID(可选)、BSSID(可选)、SSID(可选)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="section-title">《与第三方共享个人信息清单》:</div>
|
||||
|
||||
<div class="info-block">
|
||||
<p><span class="label">合作商类型:</span>支付服务提供商</p>
|
||||
<p><span class="label">第三方名称:</span>宝付网络科技(上海)有限公司、易宝支付有限公司</p>
|
||||
<p><span class="label">共享信息名称:</span>个人基本资料及个人身份信息(姓名、身份证号、银行卡号、银行卡预留手机号)</p>
|
||||
<p><span class="label">使用目的:</span>为用户提供支付服务</p>
|
||||
<p><span class="label">使用场景:</span>银行卡绑定、扣款、提现</p>
|
||||
<p><span class="label">敏感权限:</span>无</p>
|
||||
<p><span class="label">共享方式:</span>后台接口传输</p>
|
||||
</div>
|
||||
|
||||
<div class="info-block">
|
||||
<p><span class="label">合作商类型:</span>短信服务提供商</p>
|
||||
<p><span class="label">第三方名称:</span>厦门触悟科技有限公司、深圳市智信科技有限公司、深圳市微网信云科技有限公司、深圳市盈富美仙科技有限公司</p>
|
||||
<p><span class="label">共享信息名称:</span>用户姓名、手机号、金额、账单日</p>
|
||||
<p><span class="label">使用目的:</span>将放款、还款等借款相关信息反馈用户</p>
|
||||
<p><span class="label">使用场景:</span>验证码登录注册、借款申请提交成功、助贷产品授信成功/失败、助贷产品放款成功/失败、助贷产品还款提醒、用户借款提醒</p>
|
||||
<p><span class="label">敏感权限:</span>无</p>
|
||||
<p><span class="label">共享方式:</span>后台接口传输</p>
|
||||
</div>
|
||||
|
||||
<div class="info-block">
|
||||
<p><span class="label">合作商类型:</span>审批资信数据查询服务提供商</p>
|
||||
<p><span class="label">第三方名称:</span>百融云创科技股份有限公司、中国信息通信研究院、浙江大数据交易中心有限公司</p>
|
||||
<p><span class="label">共享信息名称:</span>个人基本资料(身份证、姓名、手机号、设备信息、房、车、公积金、常驻城市)</p>
|
||||
<p><span class="label">使用目的:</span>用户资质信息及资产情况评估</p>
|
||||
<p><span class="label">使用场景:</span>贷款申请共享一次</p>
|
||||
<p><span class="label">敏感权限:</span>无</p>
|
||||
<p><span class="label">共享方式:</span>后台接口传输</p>
|
||||
</div>
|
||||
|
||||
<div class="info-block">
|
||||
<p><span class="label">合作商类型:</span>审批资信数据查询服务提供商(纯净版)</p>
|
||||
<p><span class="label">第三方名称:</span>百行征信有限公司、朴道征信有限公司、钱塘征信有限公司及其他依法成立的征信机构等</p>
|
||||
<p><span class="label">共享信息名称:</span>个人基本资料(身份证、姓名、手机号、设备信息)</p>
|
||||
<p><span class="label">使用目的:</span>用户资质信息及资产情况评估</p>
|
||||
<p><span class="label">使用场景:</span>贷款申请共享一次</p>
|
||||
<p><span class="label">敏感权限:</span>无</p>
|
||||
<p><span class="label">共享方式:</span>后台接口传输</p>
|
||||
</div>
|
||||
|
||||
<div class="info-block">
|
||||
<p><span class="label">合作商类型:</span>实名认证/人脸数据服务提供商</p>
|
||||
<p><span class="label">第三方名称:</span>杭州天谷信息科技有限公司</p>
|
||||
<p><span class="label">共享信息名称:</span>个人基本资料(身份证、姓名)、人脸照片</p>
|
||||
<p><span class="label">使用目的:</span>人像检测及身份认证</p>
|
||||
<p><span class="label">使用场景:</span>实名验证时加密共享一次</p>
|
||||
<p><span class="label">敏感权限:</span>无</p>
|
||||
<p><span class="label">共享方式:</span>后台接口传输</p>
|
||||
</div>
|
||||
|
||||
<div class="info-block">
|
||||
<p><span class="label">合作商类型:</span>借款推荐/咨询服务</p>
|
||||
<p><span class="label">第三方名称:</span>借贷咨询推荐服务商</p>
|
||||
<p><span class="label">共享信息名称:</span>个人基本资料(车、房、公积金、社保、芝麻分、职业)、姓名、身份证号、手机号、基本信息(婚姻状况、学历、居住地址、月收入情况)、工作信息(从事行业、职业、工作单位、公司名称、单位地址)、紧急联系人(关系、姓名、手机号)、身份证相关信息、人脸照片及人脸分数、设备信息(IP、经纬度、设备类型)</p>
|
||||
<p><span class="label">使用目的:</span>用于服务方为用户提供相关借贷产品咨询及推荐服务、信贷额度评估</p>
|
||||
<p><span class="label">使用场景:</span>首次申请授予</p>
|
||||
<p><span class="label">敏感权限:</span>无</p>
|
||||
<p><span class="label">共享方式:</span>后台接口传输</p>
|
||||
</div>
|
||||
|
||||
<div class="info-block">
|
||||
<p><span class="label">合作商类型:</span>电子签章服务提供商</p>
|
||||
<p><span class="label">第三方名称:</span>北京中金国信科技有限公司</p>
|
||||
<p><span class="label">共享信息名称:</span>个人基本资料(身份证号码、姓名)</p>
|
||||
<p><span class="label">使用目的:</span>申请电子签章</p>
|
||||
<p><span class="label">使用场景:</span>使用时加密共享一次</p>
|
||||
<p><span class="label">敏感权限:</span>无</p>
|
||||
<p><span class="label">共享方式:</span>后台接口传输</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
105
agre/个人信息共享授权书.html
Normal file
105
agre/个人信息共享授权书.html
Normal file
@@ -0,0 +1,105 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>个人信息共享授权书</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
line-height: 1.8;
|
||||
padding: 16px;
|
||||
}
|
||||
.agreement-container {
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
max-width: 100%;
|
||||
}
|
||||
.agreement-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
color: #333;
|
||||
}
|
||||
.agreement-content {
|
||||
font-size: 14px;
|
||||
line-height: 1.8;
|
||||
color: #333;
|
||||
}
|
||||
.agreement-content p {
|
||||
margin-bottom: 12px;
|
||||
text-align: justify;
|
||||
}
|
||||
.agreement-content .section-title {
|
||||
font-weight: 600;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
color: #333;
|
||||
}
|
||||
.agreement-content .section-item {
|
||||
margin-left: 20px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.agreement-content .bold {
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="agreement-container">
|
||||
<div class="agreement-title">个人信息共享授权书-20260102</div>
|
||||
<div class="agreement-content">
|
||||
<p><span class="bold">重要提示:</span></p>
|
||||
<p>为维护您(以下简称"本人"或"您")的合法权益,请在线签署《个人信息共享授权书》(以下简称"本授权书")前,仔细阅读、充分理解本授权书各条款内容,特别是免除或减轻北京百雅科技有限公司及其关联公司(以下简称"被授权人")责任,或限制您权利的条款。您点击本授权书所在页面按钮并进入下一页面或勾选本授权书即视为您已阅读本授权书,并同意接受本授权书条款的约束,本授权书即完成在线签署并生效。(为了保护您的个人信息,本授权书在向您展示时将部分隐藏您的身份信息,您的身份信息详情以您在本平台账户对应的实名信息为准)</p>
|
||||
|
||||
<div class="section-title">一、定义</div>
|
||||
<p class="section-item">1、个人信息,指您的如下信息:</p>
|
||||
<p class="section-item">(1)个人识别信息,指您的手机号码,以及平台通过向您申请电话状态权限/IDFA权限、位置权限(如涉及)而收集的您的唯一设备识别码(IMEI、IMSI)、IP地址、地理位置。</p>
|
||||
<p class="section-item">(2)个人资质信息,指您的姓名、年龄、房产情况、公积金情况、社保情况、车辆情况、月收入金额范围、保单情况、企业主情况(如涉及)、信用情况。您已经充分认识到您的财产性信息对您而言是私密且重要的信息(本授权书项下的财产性信息包括您的房产情况、公积金情况、社保情况、车辆情况、月收入金额范围、保单情况、企业主情况、信用情况),您已经充分理解并知悉提供和使用财产性信息的风险,这些风险包括但不限于:纳入这些信息对您的信用评级(评分)、信用报告等结果可能产生不利影响,该等信息被被授权人依法共享给第三方后被他人不当利用的风险,因您的信用状况较好而造成您被第三方推销产品或服务等打扰的风险等。</p>
|
||||
<p class="section-item">2、贷款服务咨询机构/咨询顾问,指为您提供贷款咨询服务的第三方机构及咨询顾问(如涉及)。</p>
|
||||
<p class="section-item">3、广告及分析服务类提供商,指对您的产品需求/偏好进行评估,并向您推荐您需要的贷款产品及服务的第三方机构。</p>
|
||||
<p class="section-item">4、业务终止之日,指您明确向被授权人通知终止本授权书的日期或您本次产品需求得到解决的日期。</p>
|
||||
|
||||
<div class="section-title">二、授权内容</div>
|
||||
<p class="section-item">1.在平台为您提供服务过程中,为了便于被授权人向您推荐适合您的贷款产品及贷款咨询服务,您授权被授权人收集并存储您提交的个人信息,并授权被授权人通过短信、电子邮件、电话、咨询等一种或多种渠道向本人推送通知。</p>
|
||||
<p class="section-item">2.为了便于贷款服务咨询机构/咨询顾问根据您的资质情况为您提供贷款咨询服务并根据您的资质情况向您推荐适合您的贷款产品,您授权被授权人将您的手机号码及个人资质信息共享给贷款服务咨询机构/咨询顾问,同时授权贷款服务咨询机构/咨询顾问分析您的个人资质信息,通过给您拨打电话的方式向您推荐适合您的贷款产品。</p>
|
||||
<p class="section-item">3.为了对您的产品需求/偏好进行评估,并向您推荐平台产品及服务,您授权被授权人将您的个人识别信息共享给广告及分析服务类提供商,同时授权广告及分析服务类提供商分析评估您的产品需求/偏好,并将分析评估结果反馈给被授权人或者直接根据分析评估结果向您拨打电话为您推荐产品。</p>
|
||||
<p class="section-item">4.基于贷前风险识别及风险管理目的,为了对您的资质情况进行评估,我们会将您的部分个人基本资料信息共享至相关征信公司。</p>
|
||||
<p class="section-item">5.为了您的支付便利,我们会将您的部分个人基本资料和个人身份信息提供给相关支付合作方。</p>
|
||||
<p class="section-item">6.为了更好地向您提供服务,也为了我们自身的风险防控,我们会将您的部分个人基本资料信息提供给实名认证/人脸数据服务提供商,以对您提供的身份信息进行验证。</p>
|
||||
<p class="section-item">7.因申请电子签章的需要,我们会将您的姓名、身份证号码提供给电子签章的合作方,以确保您同意在确认的协议上使用电子签章。</p>
|
||||
<p class="section-item">我们向合作伙伴及第三方共享您的个人信息的具体情况,详见《与第三方共享个人信息清单》。(详情请点击以下链接 <a href="https://file.grjianr.com/weiqianbao/SDK%26gxgrxxqd.html" target="_blank">https://file.grjianr.com/weiqianbao/SDK%26gxgrxxqd.html</a> )</p>
|
||||
|
||||
<div class="section-title">三、授权期限</div>
|
||||
<p>本授权书自您签署时生效。授权期限自本授权书生效之日起至您与我们的业务终止之日止,但在授权有效期内我们已实施的行为及依其自身性质需在授权终止后实施或需持续实施的行为,不受授权终止的影响。本授权书具有独立法律效力,不因您签署的任何业务合同或协议成立与否、效力状态变化(包括但不限于部分或全部条款无效或被撤销而无效或失效)的影响。</p>
|
||||
|
||||
<div class="section-title">四、免责条款</div>
|
||||
<p>您同意若您与贷款服务咨询机构或咨询顾问或广告及分析服务类提供商发生纠纷,被授权人不承担任何责任,但被授权人可以尽可能的配合您处理。</p>
|
||||
|
||||
<div class="section-title">五、争议解决</div>
|
||||
<p>若本人与被授权人发生任何纠纷或争议,应友好协商解决。协商解决不成的,本人同意将纠纷或争议提交至本授权书签订地(即北京市平谷区)有管辖权的人民法院。</p>
|
||||
|
||||
<div class="section-title">六、其他</div>
|
||||
<p class="section-item">1、本人同意本授权书以数据电文形式订立,本人将不会因此否认本授权书的法律效力。</p>
|
||||
<p class="section-item">2、对于本人授权被授权人收集的个人信息,可通过拨打【4009263889】撤回本人的授权同意,也可以通过注销账户的方式,解除被授权人继续收集本人相关个人信息的全部授权。如因本人解除授权导致被授权人无法提供相应服务的,产生的后果由本人自行承担。</p>
|
||||
<p class="section-item">3、个人信息保护负责人的联系方式【bjbaiya@163.com】。您有任何关于个人信息保护方面的问题,可以通过前述邮箱与我们联系,我们会在15个工作日内及时处理反馈。</p>
|
||||
<p class="section-item">4、本人已知悉本授权书所有内容(特别是加粗字体内容)的意义及由此产生的法律效力,自愿作出上述授权,本授权书一经签署即视为本人同意本授权书内容,并同意承担由此带来的一切法律后果。</p>
|
||||
<p>您的登录账号:【/】</p>
|
||||
<p>授权人: 【】</p>
|
||||
<p>本人身份证号码:【】第三方服务商:e签宝</p>
|
||||
<p>公司名称:杭州天谷信息科技有限公司</p>
|
||||
<p>服务商联系方式:0571-85785223</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
212
agre/用户注册服务协议.html
Normal file
212
agre/用户注册服务协议.html
Normal file
@@ -0,0 +1,212 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>白丫融用户注册服务协议</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
line-height: 1.8;
|
||||
padding: 16px;
|
||||
}
|
||||
.agreement-container {
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
max-width: 100%;
|
||||
}
|
||||
.agreement-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
color: #333;
|
||||
}
|
||||
.agreement-date {
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.agreement-content {
|
||||
font-size: 14px;
|
||||
line-height: 1.8;
|
||||
color: #333;
|
||||
}
|
||||
.agreement-content p {
|
||||
margin-bottom: 12px;
|
||||
text-align: justify;
|
||||
}
|
||||
.agreement-content .section-title {
|
||||
font-weight: 600;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
color: #333;
|
||||
}
|
||||
.agreement-content .section-item {
|
||||
margin-left: 20px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.agreement-content .bold {
|
||||
font-weight: 600;
|
||||
}
|
||||
.agreement-content .list-item {
|
||||
margin-left: 20px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="agreement-container">
|
||||
<div class="agreement-title">白丫融用户注册服务协议</div>
|
||||
<div class="agreement-date">生效日期:2025.5.18</div>
|
||||
<div class="agreement-content">
|
||||
<p>《白丫融用户注册服务协议》包括正文及所有已经发布的或将来可能发布的各类具体规则,各类具体规则为本协议不可分割的一部分,与本协议具有同等法律效力(以下统称"本协议")。本服务协议中白丫融指北京百雅科技有限公司(注册地址:北京市平谷区平谷镇府前西街40号205室),包括白丫融借款APP及相关网站、链接、h5页面,以下简称"我们"或"白丫融"或"本公司"。请您使用我们服务前仔细阅读本协议,您在注册为我们用户过程中通过点击、确认、勾选、电子签章或其他类似任一方式确认即表示您已仔细阅读并明确同意遵守本协议。您承诺在注册之前认真阅读、理解并接受本服务协议的全部内容,如有任何疑问,可向我们咨询。但无论您事实上是否在注册之前认真阅读了本服务协议,一旦您通过移动设备客户端、H5网页等界面点击勾选本服务协议并注册成为白丫融用户,即表示您同意并签署了本服务协议。您确认本服务协议后,本服务协议即产生法律效力。 若您不接受或无法准确理解本服务协议的任何条款,请立即停止注册行为。 如发生纠纷,您不得以未仔细阅读或理解本协议为由进行任何抗辩。</p>
|
||||
<p>我们有权根据法律法规、国家政策的变动、监管部门的要求、监管精神导向、行业协会要求、行业协会建议及白丫融平台运营需要不时修改或变更本服务协议正文及各类具体规则部分或全部(以下称"修订内容"),而无需另行单独通知您。除非另有规定,否则任何修订内容于平台在相关系统板块上发布之时立即生效。若您在修订内容发布后继续使用我们提供的服务的,即表示您已充分阅读、理解并接受修订内容,也将遵循修订内容使用我们提供的服务,同时对您在本协议修改前通过我们进行的交易,您同意按照修订内容进行相应的授权和追认。如果您不同意修订内容的,您应当在履行完毕通过我们与任意主体达成的约定义务的前提下,停止使用我们的服务或注销账号。如果您未就修改后的本服务协议向我们提出明确的异议,则视为您接受我们对本服务协议所做的修改,并应遵照修改后的协议履行应尽义务。白丫融平台保留随时修改或中断服务而不需通知用户的权利,白丫融平台行使修改或中断服务的权利,不需对用户或第三方负责。</p>
|
||||
|
||||
<div class="section-title">协议细则</div>
|
||||
|
||||
<div class="section-title">1.用户注册</div>
|
||||
<p class="section-item">1.1 用户须满足的条件。您必须持有中国大陆有效身份证件(不包括香港特区、澳门特区及台湾地区),在使用白丫融平台服务前,您必须先在白丫融平台上进行注册。白丫融平台只接受持有中华人民共和国有效身份证的18周岁以上的具有完全民事权利和民事行为能力的自然人或者依据中华人民共和国法律依法设立并存续的法人成为白丫融平台用户,如您不符合资格,请勿继续操作注册;如您不符合上述资格条件并已注册为白丫融平台用户,一经发现,我们有权拒绝为您提供服务、立即注销您的账号,并追究您使用我们服务的一切法律责任。</p>
|
||||
<p class="section-item">1.2 您应当使用真实手机号码在白丫融平台进行注册,并设置相应的密码。注册成功后,您将获得一个独立的白丫融平台账号。您的账号及密码由您负责保管,您应当妥善保管您的账号和密码,绑定的手机号、手机验证码等信息。上述信息是您在白丫融平台的唯一身份识别信息,您应妥善保管并正确使用与账户有关的一切信息,不得将账号、密码进行转让、出售、出借、出租、赠与、继承(账户内的相关财产权益可被依法继承)或授权给第三方使用,若您的账户存在上述被第三方使用的情形,您应对第三方在该账户下发生的所有行为负全部责任。因密码被遗忘、被第三方破解,使用的计算机被入侵等原因造成的交易风险均由您自行承担。任何经由您账号发出的一切指令均视为您本人的行为和真实意思表示,该等指令不可撤销,由此产生的一切责任和后果由您本人承担。</p>
|
||||
<p class="section-item">1.3 若您发现有他人冒用或盗用您的账号,或您的手机或其他有关设备丢失时,您需要及时通知我们,我们会根据您的请求采取暂停或停止服务行动,但采取行动需要合理时间,同时我们对采取行动之前已执行的指令免于承担责任。</p>
|
||||
|
||||
<div class="section-title">2.关于我们的服务</div>
|
||||
<p class="section-item">2.1 白丫融平台依托互联网向您提供互联网信息服务、积分服务等服务内容,包括但不限于接受您的借款申请信息并向第三方包括但不限于合作的第三方信息服务方以及金融机构、网络小贷公司、信托公司、消费金融公司、担保公司、保险公司等贷款服务方进行导流、推荐,并就前述服务内容提供技术服务支持、资信初审、签订和查阅合同;将您的信息和/或您的借款申请一次性或多次推荐至一家或多家合作的第三方信息服务方,由您自主选择是否通过前述信息服务方提供的信息服务申请借款;白丫融平台可能会根据您使用白丫融平台服务的情况可能会向您发放积分,并允许您可在白丫融平台及/或指定的其他服务平台中进行积分兑换等。服务内容由我们根据实际情况提供并不时更新、补充或删减,我们将定期或不定期根据您的意愿以电子邮件、短信、电话或站内信等方式为您提供服务信息,并向您提供相应服务。白丫融平台为您的借贷法律关系的达成所提供的服务,不代表明示或暗示我们为借贷法律关系的一方,相关交易的权利义务及交易风险(包括但不限于法律政策风险、信用风险、流动性风险、信息安全风险)由您自行承担。为提高我们服务的效率,我们将在您登录本平台后基于您历史申请借款产品服务的状态为您定向展示您可能需要的产品或服务申请流程,以便于您及时完成相关服务的申请,您可以选择退出相关流程,退出不影响你使用我们的其他服务。</p>
|
||||
<p class="section-item">2.2 您使用白丫融平台或我们合作机构服务前需要进行实名认证、资信初审评估及银行卡绑卡等环节,您需要如实填写和提交认证/评估信息及绑卡信息,并对信息的真实性、合法性、准确性、完整性和有效性承担责任。您需要根据我们的要求,配合向我们提供有关证件及银行卡的原件、复印件、影印件等材料,以便我们核实您提供信息的真实性。因未能完成认证而无法享受我们的服务所造成的损失,我们不承担任何责任。</p>
|
||||
<p class="section-item">2.3 您使用我们的服务应当:</p>
|
||||
<p class="section-item">(1)自行配备上网的所需设备, 包括个人电脑、调制解调器或其他必备上网装置。</p>
|
||||
<p class="section-item">(2)自行负担个人上网所支付的与此服务有关的电话费用、 网络费用。</p>
|
||||
<p class="section-item">2.4 为了更好的向您提供服务,您授权并同意我们可收集、存储、使用、披露和保护您的个人信息(包含但不限于您本人提交的资料、我们自行收集及我们通过第三方收集的您的资料及信息),具体以您向我们确认并授权的《隐私政策》及《个人信息授权书》约定内容执行。</p>
|
||||
<p class="section-item">2.5 为方便您登陆和使用相关产品/服务提供商的服务,您在此授权并委托我们为提供服务之需,可以将您在白丫融平台填写的信息及根据《隐私政策》及《个人信息授权书》所获取的您的相关信息提交给相关金融产品/信息服务提供商等,并不可撤销的授权前述机构为您自动生成相关服务账号代码及初始密码(如有),以完成相关金融产品/服务/信息服务提供平台个人账户的注册。在用白丫融平台账号登陆并使用其他平台提供产品或服务时,除遵守本协议约定外,还应遵守该等平台服务协议的约定、平台公布的规则以及有关正确使用平台服务的说明和操作指引。白丫融平台和其他平台对可能出现的纠纷在法律规定和约定的范围内各自承担责任。</p>
|
||||
<p class="section-item">2.6我们有权根据法律法规及国家政策的变动、监管部门的要求、行业协会的要求及/或运营需要对我们的服务内容进行调整,包括但不限于限制、改变、减少、终止或暂停提供白丫融平台服务的部分功能,或增加新的功能。除非另有其它明示规定,白丫融平台所展示的新项目、新功能、新服务,均受到本协议之规范。</p>
|
||||
<p class="section-item">2.7白丫融向您提供的所有产品和服务,包括北京百雅科技有限公司的产品和服务均适用本服务协议。</p>
|
||||
|
||||
<div class="section-title">3.服务使用规则</div>
|
||||
<p class="section-item">3.1 您在使用白丫融平台服务过程中,必须遵循国家的相关法律法规,不得在白丫融网站、论坛、客户端、H5页面等界面发表不当言论/行为,不得在白丫融平台上发布、复制、上传、散播、分发、存储、创建或以其它方式公开含有下列内容的信息:</p>
|
||||
<p class="section-item">(1)反对宪法所确定的基本原则的;</p>
|
||||
<p class="section-item">(2)危害国家安全,泄露国家秘密,颠覆国家政权,破坏国家统一的;</p>
|
||||
<p class="section-item">(3)损害国家荣誉和利益的;</p>
|
||||
<p class="section-item">(4)煽动民族仇恨、民族歧视,破坏民族团结的;</p>
|
||||
<p class="section-item">(5)破坏国家宗教政策,宣扬邪教和封建迷信的;</p>
|
||||
<p class="section-item">(6)散布谣言,扰乱社会秩序,破坏社会稳定的;</p>
|
||||
<p class="section-item">(7)散布淫秽、色情、赌博、暴力、凶杀、恐怖或者教唆犯罪的、欺诈性的或以其它令人反感的讯息、数据、信息、文本、音乐、声音、照片、图形、代码或其它材料;</p>
|
||||
<p class="section-item">(8)侮辱或者诽谤他人,侵害他人合法权益的;</p>
|
||||
<p class="section-item">(9) 其他违反宪法和法律、行政法规或规章制度的;</p>
|
||||
<p class="section-item">(10)可能侵犯他人的专利、商标、商业秘密、版权或其它知识产权或专有权利的内容;</p>
|
||||
<p class="section-item">(11)假冒任何人或实体或以其它方式歪曲您与任何人或实体之关联性的内容;</p>
|
||||
<p class="section-item">(12)未经请求而擅自提供的促销信息、政治活动、广告或意见征集;</p>
|
||||
<p class="section-item">(13)任何第三方的私人信息,包括但不限于地址、电话号码、电子邮件地址、身份证号以及信用卡卡号;</p>
|
||||
<p class="section-item">(14)病毒、不可靠数据或其它有害的、破坏性的或危害性的文件;</p>
|
||||
<p class="section-item">(15)与内容所在的互动区域的话题不相关的内容;</p>
|
||||
<p class="section-item">(16)依我们的自行判断,足以令人反感的内容,或者限制或妨碍他人使用或享受互动区域或平台的内容,或者可能使我们或我们关联方或其他用户遭致任何类型损害或责任的内容;</p>
|
||||
<p class="section-item">(17)包含法律或行政法规禁止内容的其他内容。</p>
|
||||
<p>如您的某些行为或言论存在不合法、违反有关协议约定、侵犯白丫融平台的合法权益等情形,我们有权基于独立判断直接删除您作出的上述行为或言论,并有权中止、终止、限制您使用白丫融平台服务,而无需通知您,亦无需承担任何责任。</p>
|
||||
<p class="section-item">3.2 您不得利用白丫融平台从事任何违法违规活动,您在此承诺合法使用白丫融平台提供的服务,不会泄露或非法使用基于本服务获得的信息,包括但不限于将信息进行出售、转售或用于任何其它商业目的或任何非商业目的,并将遵守中国现行法律、法规、规章、规范性文件的规定以及白丫融平台的规则、协议等规范。 若您违反上述规定,所产生的一切法律责任和后果由您自行承担。您承担法律责任的形式包括但不限于:对受到侵害方进行赔偿,给我们造成损失的,需赔偿我们的损失。此外,如我们首先承担了因您的行为导致的行政处罚或侵权损害赔偿责任后,您应向我们进行赔偿。我们保留将您违法违规行为及有关信息资料进行公示、计入您的信用档案、按照法律法规的规定提供给有关政府部门或按照有关协议约定提供给第三方的权利,且因此给您的个人信用造成影响或给您造成任何损失的,我们免于承担责任。</p>
|
||||
<p class="section-item">3.3 您不得利用我们提供的服务从事下列危害互联网信息网络安全的活动:</p>
|
||||
<p class="section-item">(1)未经允许,进入互联网信息网络或者使用互联网信息网络资源;</p>
|
||||
<p class="section-item">(2)未经允许,对互联网信息网络功能进行删除、修改或者增加;</p>
|
||||
<p class="section-item">(3)未经允许,对进入互联网信息网络中存储、处理或者传输的数据和应用程序进行删除、修改或者增加;</p>
|
||||
<p class="section-item">(4)故意制作、传播计算机或手机病毒等破坏性程序;</p>
|
||||
<p class="section-item">(5)其他危害互联网信息网络安全的行为。</p>
|
||||
<p class="section-item">3.4 为保护公民信息及交易安全,针对我们有合理理由认为涉嫌存在使用限制情形的用户,我们保留要求用户提交补充相应证明信息或者文件的权利,上述证明信息或者文件包括但不限于身份证明文件、本人影像资料等,对于无理由不予提供的,我们有权采取拒绝向您提供服务、冻结您的账户等处理措施,涉嫌犯罪的,我们保留向司法机关控告的权利。</p>
|
||||
<p class="section-item">3.5 您在使用在白丫融平台上由第三方提供的产品或服务时,除应遵守本服务协议外,还应遵守您与第三方签订的各项协议。若因您使用白丫融平台服务或要求白丫融平台提供特定服务时,我们可能会调用第三方系统或者通过第三方支持您的使用或者访问,使用或者访问的结果由该第三方提供,我们不保证通过第三方系统提供的服务及内容或支持实现的结果的安全性、准确性、有效性及其他不确定的风险,若因此发生的任何争议、纠纷及损失等,均与我们无关,我们亦不承担任何责任。</p>
|
||||
<p class="section-item">3.6 因您违反本服务协议或相关服务条款的规定,导致或产生的任何第三方向我们主张的任何索赔、要求或损失(包括合理的律师费),应由您对第三方进行赔偿并使我们免责。若我们先行进行赔付的,将由您向我们进行赔偿,以使我们免受损害。对此,我们有权视用户行为的性质,采取包括但不限于删除用户发布信息内容、暂停使用许可、终止服务、限制使用、回收您的账号、追究法律责任等措施。对恶意注册白丫融平台账号或利用白丫融平台账号进行违法活动,骚扰、欺骗其他用户,以及其他违反法律规定及违反本协议的行为,我们有权回收其您的账号。同时,我们保留要求您承担相应法律责任的权利且我们会视监管及司法部门的要求,协助调查。</p>
|
||||
<p class="section-item">3.7 您同意:为更加有效、及时的向您提供服务信息,我们将通过包括但不限于站内信、APP推送、手机短信、电子邮件等形式向您发布服务信息及白丫融平台相关信息、商业广告、市场推广活动信息等。</p>
|
||||
<p class="section-item">3.8白丫融平台保留在任何时候为任何理由而不经通知地过滤、移除、筛查或编辑本平台或网站上发布或存储的任何内容的权利,您须自行负责备份和替换在我们发布或存储的任何内容,成本和费用自理。</p>
|
||||
<p class="section-item">3.9您须对自己在使用白丫融平台服务过程中的行为承担法律责任。若您为限制行为能力或无行为能力者,则您的法定监护人应承担相应的法律责任。</p>
|
||||
<p class="section-item">4.0如您的操作影响系统总体稳定性或完整性,白丫融平台将暂停或终止您的操作,直至相关问题得到解决。</p>
|
||||
|
||||
<div class="section-title">4.用户信息与承诺</div>
|
||||
<p class="section-item">4.1 您应如实向我们提供您的个人信息并保证信息真实、准确、完整、合法有效,并承诺对您提供的信息及时进行更新。如您提供的信息不合法、不真实、不准确、不详尽,或您未及时更新信息导致我们无法向您提供服务或服务指向、服务内容发生错误等,由此产生的法律责任和后果由您自行承担,我们亦有权终止您成为我们用户的资格。您在接受我们的服务期间,您本人的姓名、身份证号、手机号码、银行账户等信息如发生变更,您应在信息变更后2日内将相关信息提供给我们,若您未及时进行更新而带来的损失均由您自行承担。一旦我们发现您提供的信息存在虚假、无效、不完整和/或不准确等情形,我们保留因您提供的信息不实而随时中止或终止为您提供服务,并注销您账号的权利,由此所产生的损失由您自行承担,白丫融平台将不承担任何责任。</p>
|
||||
<p class="section-item">4.2 如我们发现您(作为"借款人")为在校学生或您提供的信息存在虚假或欺诈,我们有权在任何时候且无需通知您的情况下取消您的借款申请,并中止或终止您的用户资格,由此导致的一切后果和责任由您自行承担。</p>
|
||||
<p class="section-item">4.3 您在接受白丫融平台服务过程中应当诚实、守信地履行义务,否则,您可能承担相应的不利后果。您不得对白丫融平台的系统和程序采取反向工程手段进行破解,不得对上述系统和程序进行复制、修改、编译、整合或篡改,不得修改或增减白丫融平台系统的任何功能。</p>
|
||||
<p class="section-item">4.4 我们有义务根据行政、司法机关的要求向该等机关提供您的资料及信息。</p>
|
||||
<p class="section-item">4.5 您在接受白丫融平台服务时必须遵守中国法律法规、规章以及政府规范性文件,不得作出违法违规的行为,具体如下:</p>
|
||||
<p class="section-item">(1)发表、传达、传播、储存侵害他人知识产权、人身权、商业秘密等合法权益的内容;</p>
|
||||
<p class="section-item">(2)伪造虚假身份、发布虚假信息等误导、欺骗他人,或违背白丫融平台页面公布之活动规则进行虚假交易;</p>
|
||||
<p class="section-item">(3)进行危害计算机网络安全的行为。包括但不限于在用户发布的信息中含有蓄意毁坏、恶意干扰、秘密 地截取或侵占任何系统、数据或个人信息的任何病毒、伪装破坏程序、电脑蠕虫、定时程序炸弹或其他电脑程序。</p>
|
||||
|
||||
<div class="section-title">5.免责条款</div>
|
||||
<p class="section-item">5.1 白丫融平台是一个开放平台,您将文章或照片等个人资料上传到平台或互联网上,有可能会被其他组织或个人复制、转载、擅改或做其它非法用途,您必须充分意识此类风险的存在。作为网络服务的提供者,我们对您在任何论坛、个人主页或其它互动区域提供的任何陈述、声明或内容均不承担责任。您明确同意使用我们服务所存在的风险或产生的一切后果将完全由您自身承担,我们对上述风险或后果不承担任何责任。</p>
|
||||
<p class="section-item">5.2 您违反本协议、违反道德或法律的,侵犯他人权利(包括但不限于知识产权)的,我们不承担任何责任。同时,我们对任何第三方通过白丫融平台为您提供服务或包含在第三方服务中的任何内容不承担责任。</p>
|
||||
<p class="section-item">5.3 对您、其他用户或任何第三方发布、存储或上传的任何内容或由该等内容导致的任何损失或损害,我们不承担责任。</p>
|
||||
<p class="section-item">5.4 对任何第三方通过我们可能对您造成的任何错误、中伤、诽谤、诬蔑、不作为、谬误、淫秽、色情或亵渎,我们不承担责任。</p>
|
||||
<p class="section-item">5.5 我们不对任何用户及/或任何交易提供任何明示或默示的担保。白丫融平台向用户提供的各种信息及资料仅为参考,用户应依其独立判断做出决策。您据此进行交易的,产生的风险由您自行承担,您无权据此向我们提出任何法律主张。在交易过程中,交易各方发生的纠纷,由纠纷各方自行解决。白丫融平台不能完全保证平台内容的真实性、充分性、及时性、可靠性、完整性和有效性, 并且免除任何由此引起的法律责任。</p>
|
||||
<p class="section-item">5.6 由于互联网本身所具有的不稳定性,我们无法保证服务不会中断。系统因有关状况无法正常运作, 使您无法使用任何我们的服务或使用我们的服务受到任何影响时,我们对您或第三方不负任何责任, 前述状况包括但不限于:</p>
|
||||
<p class="section-item">(1)系统停机维护期间。</p>
|
||||
<p class="section-item">(2)电信设备出现故障不能进行数据传输的。</p>
|
||||
<p class="section-item">(3)由于黑客攻击、网络供应商技术调整或故障、网站升级、银行方面的问题等原因造成的本网站服务中 断或延迟。</p>
|
||||
<p class="section-item">5.7 因任何非白丫融平台原因或因白丫融平台合理控制范围以外的原因(包括但不限于自然灾害(台风、地震、海啸、洪水)、罢工 或骚乱、暴动、战争行为、政府行为、通讯或其他设施故障或严重伤亡事故等)致使白丫融平台服务延迟或未能履约,造成的网络服务中断或其他缺陷,及其他我们不能预见、不能避免、不能克服的客观情况,我们不承担任何责任。</p>
|
||||
<p class="section-item">5.8 因您的过错导致的任何损失由您自行承担,该过错包括但不限于:决策失误、操作不当、遗忘或泄露密码、密码被他人破解、您使用的计算机系统被第三方侵入、您委托他人代理交易时他人恶意或不当操作而造成的损失。</p>
|
||||
<p class="section-item">5.9 我们不保证服务一定能满足您的要求,不保证服务不会中断,也不保证服务的及时性、安全性、准确性。</p>
|
||||
<p class="section-item">5.10 任何情况下,因使用白丫融平台服务而引起或与使用白丫融平台服务有关的而产生的由我们负担的责任总额,无论是基于合同、保证、侵权、一般责任、严格责任或其它理论,均不得超过您因访问或使用白丫融平台服务而向我们支付的任何服务费用(如有)。</p>
|
||||
<p class="section-item">5.11白丫融平台提供免费的借款信息咨询服务,借款过程中遇到的任何预先收费均为诈骗行为,请保持警惕避免损失。</p>
|
||||
<p class="section-item">5.12您根据我们提供的信息服务自主选择并向合作的第三方信息服务方申请借款的,应按其要求提供借款申请材料,您知悉并理解,该等借款申请由合作的第三方信息服务方受理、审核并自主决定借款额度、是否提供借款,与白丫融平台无关。白丫融平台将对第三方进行合理审慎的审核,但该等审核不能保证第三方提供的信息和服务的真实性、有效性和合法性。您应自主选择并确认是否同意按照第三方的相关协议和规则使用第三方提供的服务,并对使用该等服务的后果承担责任。第三方服务内容、产品、广告和其他任何信息均由第三方提供,由您自主判断并承担风险,与白丫融平台无关。</p>
|
||||
|
||||
<div class="section-title">6.知识产权及其它权利</div>
|
||||
<p class="section-item">6.1 白丫融平台在其网站、链接、APP等界面中使用的商标或标记均为我们或其他特别注明的权利人所有。我们在我们网站、APP等中包含、展示的任何文字、图像、软件及其他资料均归我们及其他特别注明的权利人所有,仅供您根据本服务协议个人使用,我们及相应权利人并未通过本服务协议授予您对上述信息和内容的任何权利。未经我们及相应权利人书面同意,您不得以任何形式向任何第三方公开发表、传输、复制或以其他方式使用我们提供的全部或部分信息,不得更改、虚化或删除署名、商标、版权标识和/或其他权利声明。所有这些资料或资料的任何部分仅可作为个人或非商业用途而保存在某台计算机或其他电子设备内,否则,我们及/或权利人将追究您的法律责任。</p>
|
||||
<p class="section-item">6.2 对您在白丫融平台发布或以其它方式传播的内容,您作如下声明和保证:</p>
|
||||
<p class="section-item">(1)对于该等内容,您具有所有权或使用权;</p>
|
||||
<p class="section-item">(2)该等内容是合法的、真实的、准确的、非误导性的;</p>
|
||||
<p class="section-item">(3)使用和发布此等内容或以其它方式传播此等内容不违反本协议,也不侵犯任何人或实体的任何权利或造成对任何人或实体的伤害。</p>
|
||||
<p class="section-item">6.3 您在白丫融平台发布或传播的自有内容或具有使用权的内容,您特此同意如下:</p>
|
||||
<p class="section-item">(1)授予我们使用、复制、修改、改编、翻译、传播、发表此等内容,从此等内容创建派生作品,以及在全世界范围内通过任何媒介(现在已知的或今后发明的)公开展示和表演此等内容的权利;</p>
|
||||
<p class="section-item">(2)授予我们及其关联方和再许可人一项权利,可依他们的选择而使用您发布的有关此等内容而提交的名称;</p>
|
||||
<p class="section-item">(3)授予我们在第三方侵犯您在白丫融平台的权益、或您发布在白丫融平台的内容情况下,依法追究其责任的权利(但这并非我们的义务)。</p>
|
||||
<p class="section-item">6.4 您在白丫融平台公开发布或传播的内容、图片等为非保密信息,我们没有义务将此等信息作为您的保密信息对待。在不限制前述规定的前提下,我们保留以适当的方式使用内容的权利,包括但不限于删除、编辑、更改、不予采纳或拒绝发布。我们无义务就您提交的内容而向您付款。一旦内容已在白丫融平台发布,我们也不保证向您提供对已发布内容进行编辑、删除或作其它修改的机会。</p>
|
||||
<p class="section-item">6.5 如有权利人发现您在白丫融平台发表的内容侵犯其权利,并依相关法律、行政法规的规定向我们发出书面通知的,我们有权在不事先通知您的情况下自行移除相关内容,并依法保留相关数据。您同意不因该种移除行为向我们主张任何赔偿,如我们因此遭受任何损失,您应赔偿我们的损失(包括但不限于赔偿各种费用及律师费)。</p>
|
||||
<p class="section-item">6.6 若您认为您发布指向内容并未侵犯其他方的权利,您可以向我们以书面方式说明被移除内容不侵犯其他方权利的书面通知,该书面通知应包含如下内容:您详细的身份证明、住址、联系方式、您认为被移除内容不侵犯其他方权利的证明、被移除内容在白丫融平台上的位置以及书面通知内容的真实性声明。我们收到该书面通知后,有权决定是否恢复被移除内容。</p>
|
||||
<p class="section-item">6.7 您特此同意,如果您向白丫融平台提供的用于说明被移除内容不侵犯其他方权利的书面通知的陈述失实,您将承担由此造成的全部法律责任,如我们因此遭受任何损失,您应赔偿我们的损失(包括但不限于赔偿各种费用及律师费)。</p>
|
||||
|
||||
<div class="section-title">7、通知</div>
|
||||
<p class="section-item">7.1 您同意我们以下列合理的方式向您送达各类广告、通知及推送:</p>
|
||||
<p class="section-item">(1)白丫融平台公告的文案;</p>
|
||||
<p class="section-item">(2)站内消息、推送消息;</p>
|
||||
<p class="section-item">(3)根据您预留于白丫融平台的联系方式发出的电子邮件、短信、函件等。</p>
|
||||
<p class="section-item">(4)您同意,我们有权在提供服务过程中以各种方式投放各种商业性广告或其他任何类型的商业信息(包括但不限于在白丫融平台的任何页面上投放广告),并且,您同意接收我们、我们的关联公司及我们合作的第三方通过短信或其他方式向您发送的营销或其他相关商业信息。</p>
|
||||
<p>通知内容通过以上任一方式发出之日即视为送达日,您应及时关注我们通过以上方式送达的各类通知。</p>
|
||||
|
||||
<div class="section-title">8.服务变更、中断或终止</div>
|
||||
<p class="section-item">8.1 如因升级的需要而需暂停网络服务、或调整服务内容,我们将尽可能在网站/APP上进行通告。由于您未能及时浏览通告而造成的损失,我们不承担任何责任。</p>
|
||||
<p class="section-item">8.2 您明确同意,我们保留根据实际情况随时调整我们提供的服务内容、种类和形式,或自行决定授权第三方向您提供原本我们提供的服务。因业务调整给您或其他用户造成的损失,我们不承担任何责任。同时,我们保留随时变更、中断或终止为您提供全部或部分服务的权利。</p>
|
||||
<p class="section-item">8.3 发生下列任何一种情形,我们有权单方面中断或终止向您提供服务而无需通知您,且无需对您或第三方承担任何责任:</p>
|
||||
<p class="section-item">(1)您提供的个人信息不真实;</p>
|
||||
<p class="section-item">(2)您违反本协议条款;</p>
|
||||
<p class="section-item">(3)您未经我们书面同意,将白丫融平台用于商业目的;</p>
|
||||
<p class="section-item">(4)您实施任何违反本服务协议的行为。</p>
|
||||
<p class="section-item">8.4 您可随时通知我们终止向您提供服务或直接取消白丫融平台的服务。自您终止或取消服务之日起,白丫融平台不再向您承担任何形式的责任。</p>
|
||||
<p class="section-item">8.5 您使用白丫融平台服务的行为若有任何违反国家法律法规或侵犯任何第三方的合法权益的情形时,我们有权直接删除该等违反规定之信息,并可以暂停或终止向您提供服务。</p>
|
||||
<p class="section-item">8.6 白丫融平台结束对您的服务后,您使用白丫融平台服务的权利马上终止。从此时起,您没有权利,我们也没有义务传送任何未处理的信息或未完成的服务给您或第三方。</p>
|
||||
|
||||
<div class="section-title">9..法律适用及争议解决</div>
|
||||
<p class="section-item">9.1本协议之效力、解释、执行均适用中华人民共和国法律。</p>
|
||||
<p class="section-item">9.2如就本协议内容或其执行发生任何争议,应尽量友好协商解决;协商不成时,任何一方均可向辽宁省大连旅顺口区人民法院提起诉讼。</p>
|
||||
|
||||
<div class="section-title">10.其他</div>
|
||||
<p class="section-item">10.1本协议未尽事宜按照白丫融平台现有及不时发布的各项规则执行。</p>
|
||||
<p class="section-item">10.2本协议中的标题仅为方便而设,不影响对于条款本身的解释。</p>
|
||||
<p class="section-item">10.3本协议采用电子合同方式。您在白丫融平台或我们合作平台注册页面或其他页面通过点击、确认、勾选、电子签章或其他类似任一方式签署的电子合同即视为您本人真实意愿并系为以您本人名义签署的协议,具有法律效力。您应妥善保管自己的密码等用户信息,您通过前述方式订立的电子合同对合同各方具有法律约束力,您不得以用户信息及登陆密码等被盗用或任何其他理由否认已订立的协议的效力或不按照该等协议履行相关义务。为免疑义,如您在白丫融平台签署的相关协议或授权书需使用我们或我们关联公司合作的第三方电子签章认证服务商提供电子签章服务的,则您不可撤销地授权我们为您向合作的电子签章认证服务机构为您申请电子签章及数字证书,您点击确认相关协议或授权的行为即视为您已同意授权我们和我们指定的电子签章认证服务商在相应的协议、授权上加盖您的电子签章。</p>
|
||||
<p>签订地:辽宁省大连市</p>
|
||||
|
||||
<div class="section-title">附件:信用承诺书</div>
|
||||
<div class="section-title">信用承诺书</div>
|
||||
<p>本人承诺并保证:</p>
|
||||
<p class="section-item">1、本人通过白丫融平台或合作的第三方信息服务方申请的借款用于真实且合法的融资项目,且本人在签署本承诺书之前及之后向贷款人及白丫融平台提交的一切文本、图文、个人信息、融资信息(包括但不限于信用信息、资金用途、还款来源)等资料或信息均为真实、准确、合法、有效的,不存在任何虚假信息、遗漏或隐瞒。如提供资料存在虚假不实的,本人将承担相应的法律责任。</p>
|
||||
<p class="section-item">2、不以任何理由和任何形式将获得的借款资金用于任何违法违规活动(包括但不限于赌博、吸毒、贩毒、卖淫嫖娼等),亦不将借款资金用作申请时所填写的借款用途之外的其他任何用途(包括但不限于用于出借、支付购房首付款、赎楼、房地产场外配资等购房融资、房地产开发、进行证券投资或权益投资、场外配资、期货合约、结构化产品及其他衍生品等高风险的投资或从事其他违法、违规交易等)。</p>
|
||||
<p class="section-item">3、借款全部清偿之前,如发生影响或可能影响贷款人权益的重大情形(具体见借款合同的约定)或个人信息变更,本人承诺将在在重大情形发生或信息变更后三日内告知白丫融平台及/或贷款人。</p>
|
||||
<p>本承诺书自本人采用数据电文形式签署,自本人在白丫融平台或我们合作平台注册页面或其他相关页面通过点击确认、勾选、电子签章或其他类似任一方式签署即视为本人的真实意愿,对本人具有法律效力。</p>
|
||||
<p>年 月 日</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
214
agre/隐私政策.html
Normal file
214
agre/隐私政策.html
Normal file
@@ -0,0 +1,214 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>隐私政策</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
line-height: 1.8;
|
||||
padding: 16px;
|
||||
}
|
||||
.agreement-container {
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
max-width: 100%;
|
||||
}
|
||||
.agreement-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
color: #333;
|
||||
}
|
||||
.agreement-content {
|
||||
font-size: 14px;
|
||||
line-height: 1.8;
|
||||
color: #333;
|
||||
}
|
||||
.agreement-content p {
|
||||
margin-bottom: 12px;
|
||||
text-align: justify;
|
||||
}
|
||||
.agreement-content .section-title {
|
||||
font-weight: 600;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
color: #333;
|
||||
}
|
||||
.agreement-content .section-item {
|
||||
margin-left: 20px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.agreement-content .bold {
|
||||
font-weight: 600;
|
||||
}
|
||||
.agreement-content .list-item {
|
||||
margin-left: 20px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="agreement-container">
|
||||
<div class="agreement-title">隐私政策</div>
|
||||
<div class="agreement-content">
|
||||
<p>本服务协议中白丫融指北京百雅科技有限公司(注册地址:北京市平谷区平谷镇府前西街40号205室),包括白丫融APP及相关网站、链接、h5页面,以下简称"我们"或"白丫融"或"本公司"。我们尊重并保护使用我们产品及/或服务之用户(以下简称"用户"或"您")的统一适用的一般性隐私条款,如我们的具体产品和服务单独的隐私政策与本政策有不一致的,以具体产品和服务单独的隐私政策为准。</p>
|
||||
<p>白丫融重视用户个人信息的保护,您在使用白丫融产品和服务时,白丫融可能会收集和使用您的相关信息。白丫融希望通过本政策向您说明白丫融可能会收集的信息、收集方式、收集范围、收集这些信息的用途、白丫融及您如何保护这些信息的安全等。请您在使用白丫融所有产品及服务前,仔细阅读本政策(尤其是加粗的内容)并确定了解我们对您个人信息的处理规则。如您对本隐私协议有任何疑问,可联系我们进行咨询,如您不同意本隐私政策中的任何条款,您应立即停止使用白丫融产品和服务。一旦您选择使用或在白丫融更新本隐私政策后继续使用白丫融产品和服务,视为您同意白丫融按本政策收集、使用、保存和共享您的相关信息。</p>
|
||||
<p>本隐私政策将帮助您了解以下内容:</p>
|
||||
<p class="list-item">1、我们如何收集和使用您的个人信息</p>
|
||||
<p class="list-item">2、我们的APP权限获取说明</p>
|
||||
<p class="list-item">3、我们收集个人信息的用途及使用规则</p>
|
||||
<p class="list-item">4、个人信息的共享、转让及披露</p>
|
||||
<p class="list-item">5、白丫融的信息安全保护</p>
|
||||
<p class="list-item">6、您对个人信息的管理</p>
|
||||
<p class="list-item">7、未成年人信息保护</p>
|
||||
<p class="list-item">8、个人信息存储地点及期限</p>
|
||||
<p class="list-item">9、本政策的适用范围</p>
|
||||
<p class="list-item">10、本政策的修订及法律适用</p>
|
||||
<p class="list-item">11、关键词定义</p>
|
||||
|
||||
<div class="section-title">一、我们如何收集和使用您的个人信息</div>
|
||||
<p class="section-item">1.用户注册和登录功能</p>
|
||||
<p class="section-item">当您选择注册白丫融时,您需要主动向我们提供您本人的手机号码,我们将通过向您提供的手机号码发送验证短信并收集您反馈的验证码的方式进行验证是您本人操作。手机号码和验证码为必要信息,如果您不提供,您将无法完成注册。当您登录时,您需要主动向我们提供您本人的手机号码、验证码或密码,用于登录及进行验证是您本人操作。或者您可以使用"本机号码一键登录"的方式登录。</p>
|
||||
<p class="section-item">2.产品的浏览、匹配服务功能</p>
|
||||
<p class="section-item">为了保障您使用我们的产品或服务时系统的稳定性和安全性,防止您的个人信息被非法获取,更准确地预防欺诈和保护账号安全,当您使用的浏览、匹配服务时,我们需要收集您的设备信息(包括设备名称、设备型号、设备品牌、设备服务商、硬件序列号、软件安装列表、SIM卡序列号、设备Mac地址、Wifi Mac 地址、IMEI、OAID、IMSI、IDFA、DIFV、MAC设备ID、应用进程列表、操作系统及软件版本、设备状态、网络状况)、上网记录、日志信息、IP地址。</p>
|
||||
<p class="section-item">以上部分信息属于敏感信息,如果您拒绝提供该信息,我们将停止对该信息的收集,您可能因此无法使用与该信息相关的服务,但不影响您使用我们提供的其他服务。</p>
|
||||
<p class="section-item">3.实名验证</p>
|
||||
<p class="section-item">根据国家法律法规及监管要求,当您使用我们的产品或服务时,我们可能需要首先认证您身份的真实性,为此您需要提供您的身份基本信息(姓名、身份证号码、性别、民族、身份证签发机关、身份证有效期、身份证地址、身份证正反面照片),以对您进行身份实名认证;您可能需要通过身份基本信息多重交叉验证后方可使用我们的部分服务,若需要通过刷脸进一步确认您提供信息的准确性时,我们还需要您提供脸部图像/视频。我们会将上述信息提交给合法持有您上述信息的第三方身份验证机构,以便对您所提供信息的准确性进行核对。如果您不提供上述信息或您的实名验证未通过,则您无法使用相关服务。</p>
|
||||
<p class="section-item">4.金融产品咨询服务</p>
|
||||
<p class="section-item">您在使用平台提供的贷款咨询服务时,为了向您匹配适合您的贷款咨询服务方,您可以自主选择提供您的姓名、年龄、房产情况、公积金情况、社保情况、车辆情况、月收入金额范围、保单情况、企业主情况(如涉及)、信用情况等。您已经充分认识到您的财产性信息对您而言是私密且重要的信息(包括您的房产情况、公积金情况、社保情况、车辆情况、月收入金额范围、保单情况、企业主情况、信用情况),您已经充分理解并知悉提供和使用财产性信息的风险,这些风险包括但不限于:纳入这些信息对您的信用评级(评分)、信用报告等结果可能产生不利影响。</p>
|
||||
<p>请您注意,在您使用本平台服务过程中,为了保障您的账户安全,同时为了让您更好的了解本平台的服务、参与本平台开展的各项活动,在您突然中断服务使用或长时间停止使用本平台服务或本平台开展的各种优惠活动、我们需要向您进行营销的情况下,我们会通过您留存的手机号码以多种方式向您了解情况或告知活动信息(包括但不限于向您发送信息或向您拨打AI语音电话或由电话客服直接与您联系等)。</p>
|
||||
|
||||
<div class="section-title">二、我们的APP权限获取说明</div>
|
||||
<p>在您使用我们的产品或服务时,我们可能需要向您申请下列与个人信息相关的系统敏感权限:</p>
|
||||
<p class="section-item">1、读取电话状态权限(安卓):当您启动APP后,我们会向您申请获取此权限,目的是为了完成安全风控和服务推荐,而需要获取您的设备信息【包括SIM卡序列号、IMEI(国际移动设备识别码)、IMSI(国际移动用户识别码)、IDFA(广告标识符)、IDFV(开发商标识符)、IP地址、网络状况】。如您选择不开启此权限,我们可能无法准确判您的设备标识码,进而影响您申请部分基于设备信息进行风险评估的贷款产品,但不影响您继续使用我们提供的其他服务。</p>
|
||||
<p class="section-item">2、通讯录权限(安卓/IOS):当您在产品申请期间填写紧急联系人时,我们会向您申请获取此权限,目的是为了方便您直接从通讯录中选择并导入您选定的联系人信息。如您选择不开启此权限,您将无法通过手机通讯录选取联系人信息,但不影响您继续使用我们提供的其他服务。</p>
|
||||
<p class="section-item">3、相机权限(安卓/IOS):当您在服务使用过程中需要进行身份验证、人脸识别时,我们会向您申请获取此权限,目的是:在身份验证的场景下,需要调用您的摄像头进行身份证的拍摄。在需要人脸识别的场景下,则需要调用您的摄像头进行人脸的拍摄。若您选择不开启此权限,您将无法使用与身份验证或人脸识别相关的功能,但不影响您继续使用我们提供的其他服务。</p>
|
||||
<p class="section-item">4、读写外部存储权限(安卓):当您在服务使用过程中需要进行身份验证、人脸识别时,我们会向您申请获取此权限,目的是方便您保存并上传视频或文件或图片(包括您拍摄的身份证图片、人脸图片)通过去标识、加密传输的安全处理方式。若您选择不开启此权限,您将无法使用与身份验证或人脸识别相关的功能,但不影响您继续使用我们提供的其他服务。</p>
|
||||
<p class="section-item">5、打电话权限(安卓):当您在服务使用过程中需要给客服人员打电话时,我们会向您申请获取此权限,目的是为了方便您在给客服人员打电话时预填客服联系电话。如您选择不开启此权限,您将无法在APP中直接拨打电话给客服人员,但不影响您继续使用我们提供的其他服务。</p>
|
||||
<p class="section-item">6、音频录制权限(安卓):当您在服务使用过程中进行人脸识别或视频认证时,我们会向您申请获取此权限,目的是在人脸识别或视频认证的场景下收集您的语音内容。若您不开启此权限,您将无法使用人脸识别或视频认证相关的功能,但不影响您继续使用我们提供的其他服务。</p>
|
||||
<p class="section-item">7、相册权限(IOS):当您保存客服二维码或修改APP头像时,我们会向您申请获取此权限,目的是方便您保存客服微信二维码及更换APP账户头像。如您不开启此权限,您将无法通过点击按钮保存图片以及无法更改头像,但不影响您继续使用我们提供的其他服务。</p>
|
||||
<p class="section-item">8、设备信息(ANDROID_ID):当您登录APP时会获取您的设备信息(包括Android ID、GAID、OAID、UAID、IDFA、ICCID)、设备硬件信息、网络信息、手机号、软件列表信息、位置相关信息等信息,目的是记录埋点数据、为极光SDK提供服务,如您不开启此权限,您将无法正常登录使用相关功能,但不影响您使用我们提供的其他服务。</p>
|
||||
<p class="section-item">9.设备信息权限(Android ID):当您登录APP时会收集您的个人信息,目的是提供登录功能,通过去标识、加密传输的安全处理方式。</p>
|
||||
<p class="section-item">10.剪切板权限(安卓):在您使用本应用服务时,为了实现信息分享、参加相关活动等目的所必需,我们可能会调用剪切板并使用与功能相关的最小必要信息(口令、链接等)。</p>
|
||||
|
||||
<div class="section-title">三、我们收集个人信息的用途及使用规则</div>
|
||||
<p class="section-item">(一)白丫融主要将所收集的信息用作下列用途:</p>
|
||||
<p class="section-item">1.安全保障:在白丫融提供产品和服务时,用于身份验证、客户服务、安全防范、存档和备份用途,确保白丫融向您提供的产品和服务的安全性。</p>
|
||||
<p class="section-item">2.保证为您所提供的搜索、输入、页面浏览、翻译及其他具体产品或服务功能的正常实现。</p>
|
||||
<p class="section-item">3.软件认证或管理软件升级。</p>
|
||||
<p class="section-item">4.根据法律法规在如下情形收集使用您的相关信息,此种情况下无须获得您的授权:</p>
|
||||
<p class="section-item">(1)与国家安全、国防安全有关的;</p>
|
||||
<p class="section-item">(2)与公共安全、公共卫生、重大公共利益有关的;</p>
|
||||
<p class="section-item">(3)与犯罪侦查、起诉、审判和判决执行等有关的;</p>
|
||||
<p class="section-item">(4)出于维护个人信息主体或其他个人的生命、财产等重大合法权益但又很难得到本人同意的;</p>
|
||||
<p class="section-item">(5)用于维护所提供的产品与/或服务的安全稳定运行所必需的,包括发现、处置产品与/或服务的故障等;</p>
|
||||
<p class="section-item">(6)为合法的新闻报道所必需的;</p>
|
||||
<p class="section-item">(7)学术研究机构基于公共利益开展统计或学术研究所必要,且对外提供学术研究或描述的结果时,对结果中所包含的个人信息进行去标识化处理的;</p>
|
||||
<p class="section-item">(8)法律法规规定的其他情形。</p>
|
||||
<p class="section-item">5.经您同意的其他用途。</p>
|
||||
<p class="section-item">(二)对您个人信息使用的规则</p>
|
||||
<p class="section-item">1.白丫融会根据本隐私政策的约定并为实现白丫融的产品与/或服务功能对所收集的信息进行使用。</p>
|
||||
<p class="section-item">2.在收集您的个人信息后,白丫融将通过技术手段对数据进行去标识化处理,去标识化处理的信息将无法识别主体。请您了解并同意,在此情况下白丫融有权使用已经去标识化的信息,并在不透露您个人信息的前提下,有权对用户数据库进行分析并予以商业化的利用。</p>
|
||||
<p class="section-item">3.请您注意,您在使用白丫融的产品与/或服务时所提供的所有个人信息,除非您删除或通过系统设置拒绝白丫融收集,否则将在您使用白丫融的产品与/或服务期间持续授权白丫融使用。在您注销账号时,白丫融将停止使用并删除您的个人信息。</p>
|
||||
<p class="section-item">4.白丫融会对白丫融的产品与/或服务使用情况进行统计,并可能会与公众或第三方共享这些统计信息,以展示白丫融的产品与/或服务的整体使用趋势。但这些统计信息不包含您的任何身份识别信息。</p>
|
||||
<p class="section-item">5.当白丫融展示您的个人信息时,白丫融会采用包括内容替换、匿名处理方式对您的信息进行脱敏,以保护您的信息安全。</p>
|
||||
<p class="section-item">6.当白丫融要将您的个人信息用于本政策未载明的其它用途时,或基于特定目的收集而来的信息用于其他目的时,会事先征求您的同意后再进行使用。</p>
|
||||
<p class="section-item">(三)我们可能向您发送的信息</p>
|
||||
<p class="section-item">1.信息推送</p>
|
||||
<p class="section-item">您在使用我们的服务时,我们可能向您发送电子邮件、短信、资讯或推送通知。您可以按照我们的相关提示,在设备上选择取消订阅。</p>
|
||||
<p class="section-item">2.与服务有关的公告</p>
|
||||
<p class="section-item">我们可能在必要时(包括因系统维护而暂停某一服务时)向您发出与产品或服务有关的公告。您可能无法取消这些与服务有关、性质不属于广告的公告。</p>
|
||||
|
||||
<div class="section-title">四、个人信息的共享、转让及披露</div>
|
||||
<p class="section-item">(一)信息的共享</p>
|
||||
<p class="section-item">1.白丫融不会与白丫融以外的任何公司、组织和个人共享您的个人信息,但以下情况除外:</p>
|
||||
<p class="section-item">(1)事先获得您明确的同意或授权;</p>
|
||||
<p class="section-item">(2)根据适用的法律法规、法律程序的要求、强制性的行政或司法要求所必须的情况下进行提供;</p>
|
||||
<p class="section-item">(3)在法律法规允许的范围内,为维护白丫融或合作伙伴、您或其他白丫融用户或社会公众利益、财产或安全免遭损害而有必要提供;</p>
|
||||
<p class="section-item">(4)只有共享您的信息,才能实现白丫融的产品与/或服务的核心功能或提供您需要的服务;</p>
|
||||
<p class="section-item">(5)应您需求为您处理您与他人的纠纷或争议;</p>
|
||||
<p class="section-item">(6)符合与您签署的相关协议(包括在线签署的电子协议以及相应的平台规则)或其他的法律文件约定所提供;</p>
|
||||
<p class="section-item">(7)基于学术研究而使用;</p>
|
||||
<p class="section-item">(8)基于符合法律法规的社会公共利益而使用。</p>
|
||||
<p>白丫融可能会将您的个人信息与白丫融的关联方共享。但白丫融只会共享必要的个人信息,且受本隐私政策的约束。白丫融的关联方如要改变个人信息的处理目的,将再次征求您的授权同意。</p>
|
||||
<p>仅为事先本政策中声明的目的,我们的某些服务将由我们和第三方签约合作伙伴通过数据接口合作的方式共同提供。我们可能会与签约合作伙伴共享您的某些信息,以提供更好的客户服务和用户体验。我们仅会出于合法、正当、必要、特定、明确的目的共享您的个人信息,并且只会共享提供服务所必须的个人信息。我们的签约合作伙伴无权将共享的个人信息用于与产品或服务无关的其他用途。目前,此类第三方合作商主要包括:软件服务提供商、智能设备提供商和系统服务提供商、产品功能相关的合作商等,具体包括:</p>
|
||||
<p class="section-item">(1)网络服务合作商、智能设备提供商、系统服务提供商:当这些合作商需要与我们联合为您提供服务时,我们会将实现业务所必须的信息与其共享,以向您提供服务。如在本产品接入合作方提供的网络服务时,合作方可能会通过本软件获取您的设备及网络状态信息,以便向您提供当前可用的网络信息。</p>
|
||||
<p class="section-item">(2)与产品功能相关的服务合作商。使用的场景主要包括:第三方账号登录、天气服务,信息推送服务、分享服务、支付服务。</p>
|
||||
<p class="section-item">(3)数据分析、信息推广及广告服务合作伙伴。我们会与该类合作伙伴共享您的设备信息或经过加密后的标签信息,以用于监察广告投放的情况、为用户提供更符合需求的分析结果、实现推广或广告信息的有效触达、优化广告效果。</p>
|
||||
<p class="section-item">(4)实现联合活动的参与。当您选择参加我们及我们的关联方或第三方举办的营销活动时,可能需要您提供姓名、地址、联系方式等个人信息,我们可能会将上述信息与关联方或第三方共享,以保障您在我们与第三方的联合活动中获得体验一致的服务。</p>
|
||||
<p>为了遵守法律、执行或适用白丫融的使用条件和其他协议,或者为了保护白丫融、您或其他白丫融客户的权利及其财产或安全,比如为防止欺诈等违法活动和减少信用风险,而与其他公司和组织交换信息。不过,这并不包括违反本隐私政策中所作的承诺而为获利目的出售、出租、共享或以其它方式披露的个人信息。</p>
|
||||
<p>对白丫融与之共享个人信息的公司、组织和个人,白丫融会要求其遵守保密约定,要求他们按照白丫融的说明、本隐私政策以及其他任何相关的保密和安全措施来处理个人信息。</p>
|
||||
<p>我们向合作伙伴及第三方共享您的个人信息的情形逐项列举,具体详见《与第三方共享个人信息清单》。</p>
|
||||
<p class="section-item">(二)信息的转让</p>
|
||||
<p>白丫融不会将您的个人信息转让给任何公司、组织和个人,但以下情况除外:</p>
|
||||
<p class="section-item">1.事先获得您明确的同意或授权;</p>
|
||||
<p class="section-item">2.根据适用的法律法规、法律程序的要求、强制性的行政或司法要求所必须的情况下进行提供;</p>
|
||||
<p class="section-item">3.符合与您签署的相关协议(包括在线签署的电子协议以及相应的平台规则)或其他的法律文件约定所提供;</p>
|
||||
<p class="section-item">4.在涉及合并、收购、资产转让或类似的交易时,如涉及到个人信息转让,白丫融会要求新的持有您个人信息的公司、组织继续受本隐私政策的约束,否则,白丫融将要求该公司、组织重新向您征求授权同意。</p>
|
||||
<p class="section-item">(三)信息的披露</p>
|
||||
<p>白丫融仅会在以下情况下,且采取符合业界标准的安全防护措施的前提下,才会公开披露您的个人信息:</p>
|
||||
<p class="section-item">1.您使用共享功能的;</p>
|
||||
<p class="section-item">2.根据法律、法规的要求、强制性的行政执法或司法要求所必须提供您个人信息的情况下,白丫融可能会依据所要求的个人信息类型和披露方式公开披露您的个人信息。在符合法律法规的前提下,当白丫融收到上述披露信息的请求时,白丫融会要求必须出具与之相应的法律文件。白丫融对所有的请求都将进行慎重的审查,以确保其具备合法依据,且仅限于执法部门因特定调查目的且有合法权利获取的数据;</p>
|
||||
<p class="section-item">3.在紧急情况下,为了保护您、白丫融及其他用户的合法权益或公共安全及利益的;</p>
|
||||
<p class="section-item">4.符合白丫融相关产品和服务的用户协议或其他类似协议的规定。</p>
|
||||
|
||||
<div class="section-title">五、白丫融的信息安全保护</div>
|
||||
<p class="section-item">1.数据安全技术措施:白丫融会采用符合业界标准的安全防护措施,包括建立合理的制度规范、安全技术来防止您的个人信息遭到未经授权的访问使用、修改、避免数据的损坏或丢失;白丫融将使用安全技术和程序监测、记录网络运行状态、网络安全事件,并采取必要的技术措施保障网络安全,以防信息的丢失、不当使用、未经授权阅览或披露。在某些服务中,白丫融将利用加密技术、匿名化处理等手段来保护您提供的个人信息。但请您理解,由于技术的限制以及可能存在的各种恶意手段,在互联网行业,即便竭尽所能加强安全措施,也不可能始终保证信息百分之百的安全。您需要了解,您接入白丫融的产品和服务所用的系统和通讯网络,有可能因白丫融可控范围外的因素而出现问题。</p>
|
||||
<p class="section-item">2.白丫融仅允许有必要知晓这些信息的员工、合作伙伴访问个人信息,并为此设置了严格的访问权限控制和监控机制。白丫融同时要求可能接触到您个人信息的所有人员履行相应的保密义务。如果未能履行这些义务,可能会被追究法律责任或被中止与白丫融的合作关系。</p>
|
||||
<p class="section-item">3.白丫融会采取一切合理可行的措施,确保无关的个人信息不被收集。白丫融只会在达成本政策所述目的所需的期限内保留您的个人信息,除非需要延长保留期或受到法律的允许</p>
|
||||
<p class="section-item">4.您在使用白丫融的产品和服务时所提供、上传或发布的内容和信息,可能会泄露您的个人信息。您需要谨慎地考虑,是否在使用白丫融的产品和服务时披露相关个人信息。在一些情况下,您可通过白丫融某些产品和服务的隐私设定来控制有权浏览您共享信息的用户范围。</p>
|
||||
<p class="section-item">5.互联网并非绝对安全的环境,而且电子邮件、即时通讯、社交软件等与其他用户的交流方式无法确定是否完全加密,白丫融建议您使用此类工具时请使用复杂密码,并注意保护您的个人信息安全。由于您将用户密码告知他人、与他人共享注册账户或其他因您个人保管不善,由此导致的任何个人资料泄露、丢失、被盗用或被篡改等产生的责任和损失,您应自行承担。</p>
|
||||
<p class="section-item">6.安全事件处理:为应对个人信息泄露、损毁和丢失等可能出现的风险,白丫融建立了专门的应急响应团队,按照公司有关安全事件处置规范要求,针对不同安全事件启动安全预案,进行止损、分析、制定补救措施、联合相关部门进行溯源和打击。同时白丫融将按照法律法规的要求,及时向您告知:安全事件的基本情况和可能的影响、白丫融已采取或将要采取的处置措施、您可自主防范和降低风险的建议、对您的补救措施等。白丫融同时将及时将事件相关情况以邮件、信函、电话、推送通知等方式告知您,难以逐一告知个人信息主体时,白丫融会采取合理、有效的方式发布公告。同时,白丫融还将按照监管部门要求,主动上报个人信息安全事件的处置情况。</p>
|
||||
|
||||
<div class="section-title">六、您对个人信息的管理</div>
|
||||
<p class="section-item">1.您有权访问、注销您的个人信息;但相关信息的删除或修改可能会影响您对相关产品或服务的使用或导致部分功能无法实现。访问、注销的具体路径一般为:进入产品后,点击"我的",可以访问、注销您的账号内信息。</p>
|
||||
<p class="section-item">2.您有权改变您授权同意的范围或撤回您的授权;您可以通过1)删除信息、2)关闭设备功能、3)在具体产品的网站或软件"设置"中进行隐私设置改变您授权白丫融继续收集个人信息的范围或撤回您的授权。您也可以通过注销账户的方式,撤回白丫融继续收集您个人信息的授权。请您理解,每个业务功能需要一些基本的个人信息才能得以完成,当您撤回同意或授权后,白丫融无法继续为您提供撤回同意或授权所对应的服务。但您撤回同意或授权的决定,不会影响此前基于您的授权而开展的个人信息处理。</p>
|
||||
<p class="section-item">3.注销账户:您可以通过【我的→设置→注销账号】,按页面提示内容进行账户注销。您注销上述账户的行为是不可逆行为,并且我们将停止为您提供产品和 / 或服务,不再收集您的个人信息,并依据您的要求删除与您账户相关的个人信息或作匿名化处理,但法律法规或监管机构对用户信息存储时间另有规定的除外。为了保护您或他人的合法权益,我们需要在您提交注销申请时验证您的身份,结合您对白丫融各产品和/ 或服务的使用情况 ( 例如是否存在在途借款、是否存在未决争议及纠纷 ) 判断是否存在损害或影响您合法自主权益的事项后支持您的注销请求。</p>
|
||||
<p class="section-item">4.白丫融将采取适当的技术手段,保证您对于自己的个人资料可进行查询、补充、更正或删除,或通过白丫融相关产品或服务发布的反馈或投诉渠道申请白丫融对相关信息进行补充、更正或删除;白丫融收到您的申请后将按流程(如白丫融可能会要求您提供相关证明,以确认您的身份)予以处理。请您理解,由于技术所限、法律或监管要求,我们可能无法满足您的所有要求,我们会在合理的期限内答复您的请求。</p>
|
||||
<p class="section-item">5.如果您无法访问、更正或删除您的个人信息,或您需要访问、更正或删除您在使用白丫融产品与/或服务时所产生的其他个人信息,或您认为白丫融存在任何违反法律法规或与您关于个人信息的收集或使用的约定,您可通过第九条所列方与我们联系。</p>
|
||||
<p class="section-item">6.个人信息保护负责人的联系方式wsj@grjjr.com。您有任何关于个人信息保护方面的问题,可以通过前述邮箱与我们联系,我们会在法律规定的范围内及时处理反馈。</p>
|
||||
|
||||
<div class="section-title">七、未成年人信息保护</div>
|
||||
<p class="section-item">1.我们根据我国法律法规的要求,保护未成年人的个人信息安全。如果您是18周岁以下的未成年人,您将无法完成我们的实名认证,我们不会对未成年人提供服务,并不会收集未成年人的信息。如果您认为我们无意间收集了未成年人信息,请通过本隐私政策载明的联系方式与我们联系以便我们可以尽快删除这些信息。</p>
|
||||
|
||||
<div class="section-title">八、个人信息存储地点及期限</div>
|
||||
<p class="section-item">1.个人信息存储地点:</p>
|
||||
<p>我们会按照法律法规规定,将境内收集的用户个人信息存储在中华人民共和国境内。</p>
|
||||
<p class="section-item">2.个人信息存储期限:</p>
|
||||
<p>白丫融仅在本政策所述目的所必需的期间和法律法规要求的时限内保留您的个人信息。具体期限详见各产品和服务单独的隐私政策,如具体的产品和服务未具体列明的,一般情况下,除非法律法规有其他要求,存储期限与您账号有效期限一致,如您属于非注册账户的,则存储期限一般为6个月。超过前述存储期限后,我们将予以删除或进行不可恢复的匿名化处理。</p>
|
||||
<p>但在下列情况下,我们可能因需要符合法律要求,更改个人信息的存储时间:</p>
|
||||
<p>为遵守适用的法律法规等有关规定;为遵守法院判决、裁定或其他法律程序的规定;为遵守相关政府机关或法定授权组织的要求;我们有理由确信需要遵守法律法规等有关规定;为执行相关服务协议或本政策、维护社会公共利益,为保护我们的客户、我们或我们的关联公司、其他用户或雇员的人身财产安全或其他合法权益所合理必需的用途。</p>
|
||||
<p>当我们的产品或服务发生停止运营的情形时,我们将采取包括但不限于推送通知、公告等形式通知您,并在合理的期限内删除或匿名化处理您的个人信息。</p>
|
||||
|
||||
<div class="section-title">九、本政策的适用范围</div>
|
||||
<p class="section-item">1.白丫融所有的产品和服务均适用本政策。针对某些特定产品和服务的特定隐私政策,将在相关产品和服务的使用协议或单独的隐私协议中具体说明。该等特定产品和服务的隐私条款构成本政策的一部分。如相关特定产品和服务的隐私条款与本政策有不一致之处,适用该特定产品和服务的隐私条款。</p>
|
||||
<p class="section-item">2.本政策仅适用于白丫融所收集的信息,并不适用于任何第三方提供的产品和服务或第三方的信息使用规则,您通过白丫融的产品和服务而接受的第三方产品和服务(包括任何第三方网站)收集的信息,不适用本政策。如您因第三方产品和服务产生的损失,您可以向第三方主张赔偿。</p>
|
||||
<p class="section-item">3.如您在使用白丫融产品、服务中对本隐私政策有任何问题,您可通过我们相关产品或服务对外公布的官方反馈渠道、个人信息保护负责人的联系方式与我们的具体产品或服务团队联系。</p>
|
||||
|
||||
<div class="section-title">十、本政策的修订及法律适用</div>
|
||||
<p class="section-item">1、白丫融可能适时修订本政策的条款,该等修订构成本政策的一部分。修订后的政策白丫融将会以弹窗及时在网页显著位置或短信等合理方式予以发布。若您继续使用白丫融产品和服务,即表示您同意受经修订的隐私政策的约束。</p>
|
||||
<p class="section-item">2、本政策的解释及争议解决均应适用中华人民共和国法律。与本政策相关的任何纠纷,双方应经友好协商解决;若不能协商解决,您在此同意将争议提交至北京市平谷区人民法院。如果有管辖权的任何法院裁定或判决本政策的任何条款无效,则该条款将从本政策中移除,该条款的无效不影响本政策其余条款的效力。本政策的其余条款将继续执行。</p>
|
||||
<p class="section-item">3、本政策的标题仅为方便阅读而设计,不影响本政策任何条款的含义或解释。</p>
|
||||
|
||||
<div class="section-title">十一、关键词定义</div>
|
||||
<p class="section-item">1.用户或您:指使用白丫融产品与/或服务的用户。</p>
|
||||
<p class="section-item">2.未成年人:指年龄不超过18周岁的未成年人。</p>
|
||||
<p class="section-item">3.去标识化:指通过对个人信息的技术处理,使其在不借助额外信息的情况下,无法识别您的过程。</p>
|
||||
<p class="section-item">4.匿名化:指通过对个人信息的技术处理,使得您无法被识别,且处理后的信息不能被复原的过程。</p>
|
||||
<p class="section-item">5.设备:指可用于访问白丫融产品或服务的装置。</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
619
basic-info.css
619
basic-info.css
@@ -15,7 +15,7 @@ body {
|
||||
.basic-info-container {
|
||||
min-height: 100vh;
|
||||
padding: 16px;
|
||||
padding-bottom: 100px;
|
||||
padding-bottom: calc(130px + env(safe-area-inset-bottom, 0px));
|
||||
}
|
||||
|
||||
/* 顶部卡片 */
|
||||
@@ -547,14 +547,13 @@ body {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
/* 底部按钮 */
|
||||
/* 底部按钮:贴底并预留安全区,避免被系统栏/手势区遮挡 */
|
||||
.button-section {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
padding-bottom: calc(8px + env(safe-area-inset-bottom));
|
||||
padding: 16px 16px calc(16px + env(safe-area-inset-bottom, 0px));
|
||||
background-color: #fff;
|
||||
box-shadow: 0 -2px 4px 0 rgba(0, 0, 0, 0.08);
|
||||
z-index: 100;
|
||||
@@ -590,6 +589,16 @@ body {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.agreement-link {
|
||||
color: #2e6df6;
|
||||
text-decoration: none;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.agreement-link:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
@@ -659,9 +668,9 @@ body {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 16px 16px 0 0;
|
||||
max-height: 70vh;
|
||||
background-color: #fff;
|
||||
border-radius: 20px 20px 0 0;
|
||||
max-height: 80vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
animation: slideUp 0.3s ease;
|
||||
@@ -680,11 +689,319 @@ body {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
padding: 6px 20px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.city-picker-body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 0;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.city-picker-body::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.city-picker-body::-webkit-scrollbar-thumb {
|
||||
background: #e0e0e0;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* ==================== 定位和热门城市区域 ==================== */
|
||||
.city-picker-hot-section {
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 定位区域 */
|
||||
.location-section {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
/* 定位标签 */
|
||||
.location-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 8px 16px;
|
||||
background: #fff;
|
||||
border: 1.5px solid #e5e5e5;
|
||||
border-radius: 18px;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
user-select: none;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.location-badge:hover {
|
||||
border-color: #3474fe;
|
||||
color: #3474fe;
|
||||
background: #E8F0FF;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(52, 116, 254, 0.2);
|
||||
}
|
||||
|
||||
.location-badge:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.location-badge.locating {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.location-badge .location-icon {
|
||||
font-size: 16px;
|
||||
display: inline-block;
|
||||
filter: hue-rotate(0deg) saturate(1.2);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.location-badge .location-text {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* 热门城市区域 */
|
||||
.hot-cities-section {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.section-title::before {
|
||||
content: '';
|
||||
width: 3px;
|
||||
height: 14px;
|
||||
background: #3474fe;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.hot-cities-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* 热门城市标签 */
|
||||
.city-tag {
|
||||
padding: 8px 16px;
|
||||
background: #fff;
|
||||
border: 1.5px solid #e5e5e5;
|
||||
border-radius: 20px;
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
cursor: pointer;
|
||||
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.city-tag:hover {
|
||||
border-color: #3474fe;
|
||||
color: #3474fe;
|
||||
background: #E8F0FF;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(52, 116, 254, 0.2);
|
||||
}
|
||||
|
||||
.city-tag:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.city-tag.active {
|
||||
background: #3474fe;
|
||||
border-color: #3474fe;
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
box-shadow: 0 4px 12px rgba(52, 116, 254, 0.35);
|
||||
}
|
||||
|
||||
/* ==================== 省份城市联动区域 ==================== */
|
||||
.province-city-section {
|
||||
padding: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.province-city-section > .section-title {
|
||||
padding: 12px 16px 10px;
|
||||
margin-bottom: 0;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.province-city-container {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
/* 省份列表 */
|
||||
.province-list-wrapper {
|
||||
width: 100px;
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
background: #f5f5f5;
|
||||
border-right: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
.province-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
max-height: 280px;
|
||||
overflow-y: auto;
|
||||
padding-right: 0;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.province-list::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.province-list::-webkit-scrollbar-thumb {
|
||||
background: #e0e0e0;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.province-item {
|
||||
padding: 12px 10px;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
cursor: pointer;
|
||||
border-radius: 0;
|
||||
transition: all 0.2s ease;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
background: transparent;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.province-item:hover {
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.province-item.active {
|
||||
background: #3474fe;
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 城市网格 */
|
||||
.city-grid-wrapper {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.city-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 10px;
|
||||
max-height: 280px;
|
||||
overflow-y: auto;
|
||||
padding: 12px;
|
||||
align-content: start;
|
||||
}
|
||||
|
||||
.city-grid::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.city-grid::-webkit-scrollbar-thumb {
|
||||
background: #e0e0e0;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.city-item {
|
||||
padding: 10px 6px;
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
background: #fff;
|
||||
border: 1px solid #e5e5e5;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
white-space: normal;
|
||||
word-break: break-all;
|
||||
line-height: 1.3;
|
||||
min-height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.city-item:hover {
|
||||
background: #E8F0FF;
|
||||
border-color: #3474fe;
|
||||
color: #3474fe;
|
||||
}
|
||||
|
||||
.city-item.active {
|
||||
background: #3474fe;
|
||||
border-color: #3474fe;
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 淡入动画 */
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.province-list-wrapper {
|
||||
width: 90px;
|
||||
}
|
||||
|
||||
.city-grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 8px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.hot-cities-grid {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.city-tag {
|
||||
padding: 8px 16px;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.city-picker-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
@@ -703,50 +1020,16 @@ body {
|
||||
}
|
||||
|
||||
.city-picker-confirm {
|
||||
color: #666;
|
||||
font-weight: 400;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.city-picker-confirm.has-selection {
|
||||
color: #3474fe;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.city-picker-body {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.city-picker-column {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.city-picker-column:first-child {
|
||||
border-right: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
.city-picker-item {
|
||||
padding: 12px 16px;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
}
|
||||
|
||||
.city-picker-item:active {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.city-picker-item.active {
|
||||
background-color: #e8f0ff;
|
||||
color: #3474fe;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
body.modal-open {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Toast 提示样式 */
|
||||
.toast {
|
||||
position: fixed;
|
||||
@@ -1075,3 +1358,239 @@ body.modal-open {
|
||||
.back-home-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* ==================== 授权流程样式 ==================== */
|
||||
.auth-flow-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 16px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* 授权进度区域 */
|
||||
.auth-progress-section {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.auth-progress-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.auth-progress-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.auth-progress-count {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.auth-progress-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.auth-progress-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.auth-progress-item.active {
|
||||
background: #e8f0ff;
|
||||
border: 1px solid #3474fe;
|
||||
}
|
||||
|
||||
.auth-progress-item.completed {
|
||||
background: #f0fff4;
|
||||
}
|
||||
|
||||
.auth-progress-item.completed .auth-progress-icon {
|
||||
color: #19be6b;
|
||||
}
|
||||
|
||||
.auth-progress-item.completed .auth-progress-status {
|
||||
color: #19be6b;
|
||||
}
|
||||
|
||||
.auth-progress-item.failed {
|
||||
background: #fff5f5;
|
||||
}
|
||||
|
||||
.auth-progress-item.failed .auth-progress-icon {
|
||||
color: #ff4444;
|
||||
}
|
||||
|
||||
.auth-progress-item.failed .auth-progress-status {
|
||||
color: #ff4444;
|
||||
}
|
||||
|
||||
.auth-progress-item.timeout {
|
||||
background: #fffbe6;
|
||||
}
|
||||
|
||||
.auth-progress-item.timeout .auth-progress-icon {
|
||||
color: #faad14;
|
||||
}
|
||||
|
||||
.auth-progress-item.timeout .auth-progress-status {
|
||||
color: #faad14;
|
||||
}
|
||||
|
||||
.auth-progress-icon {
|
||||
font-size: 18px;
|
||||
width: 24px;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.auth-progress-item.active .auth-progress-icon {
|
||||
color: #3474fe;
|
||||
animation: pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.auth-progress-name {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.auth-progress-status {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.auth-progress-item.active .auth-progress-status {
|
||||
color: #3474fe;
|
||||
}
|
||||
|
||||
/* iframe 区域 */
|
||||
.auth-iframe-section {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.auth-iframe-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 14px 16px;
|
||||
background: linear-gradient(135deg, #3474fe 0%, #5b8def 100%);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.auth-iframe-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.auth-iframe-hint {
|
||||
font-size: 12px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.auth-iframe {
|
||||
width: 100%;
|
||||
height: calc(100vh - 350px);
|
||||
min-height: 400px;
|
||||
border: none;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 授权完成区域 */
|
||||
.auth-complete-section {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 40px 20px;
|
||||
text-align: center;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.auth-complete-icon {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
line-height: 72px;
|
||||
margin: 0 auto 20px;
|
||||
background: linear-gradient(135deg, #19be6b 0%, #47d88a 100%);
|
||||
color: #fff;
|
||||
font-size: 36px;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 4px 12px rgba(25, 190, 107, 0.3);
|
||||
}
|
||||
|
||||
.auth-complete-title {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.auth-complete-desc {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.auth-countdown {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.auth-countdown #countdownSeconds {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #3474fe;
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.auth-redirect-btn {
|
||||
display: inline-block;
|
||||
padding: 14px 48px;
|
||||
background: linear-gradient(135deg, #3474fe 0%, #5b8def 100%);
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
border-radius: 24px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 12px rgba(52, 116, 254, 0.3);
|
||||
}
|
||||
|
||||
.auth-redirect-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 16px rgba(52, 116, 254, 0.4);
|
||||
}
|
||||
|
||||
.auth-redirect-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<div class="asset-header" id="assetHeader">
|
||||
<div class="asset-title">
|
||||
<img class="asset-icon" src="./static/image/zc-pic.png" alt="资产信息">
|
||||
<span>资产信息(<span id="completedCount">0</span>/6)</span>
|
||||
<span>资产信息(<span id="completedCount">0</span>/8)</span>
|
||||
</div>
|
||||
<img class="section-arrow" id="assetArrow" src="./static/image/dropdown-down.png" alt="展开">
|
||||
</div>
|
||||
@@ -59,9 +59,7 @@
|
||||
<span class="agreement-text">我已阅读并同意</span>
|
||||
</label>
|
||||
<span class="agreement-links">
|
||||
《人脸识别验证个人信息使用授权书》
|
||||
《个人信息授权书》
|
||||
《个人信息共享授权书》
|
||||
<a href="./agre/个人信息共享授权书.html" class="agreement-link">《个人信息共享授权书》</a>
|
||||
</span>
|
||||
</div>
|
||||
<button class="submit-btn" id="submitBtn" disabled>下一步</button>
|
||||
@@ -76,16 +74,46 @@
|
||||
<button class="city-picker-btn city-picker-cancel" id="cityCancelBtn">取消</button>
|
||||
<button class="city-picker-btn city-picker-confirm" id="cityConfirmBtn">确认</button>
|
||||
</div>
|
||||
|
||||
<div class="city-picker-body">
|
||||
<div class="city-picker-column" id="provinceColumn">
|
||||
<!-- 定位和热门城市区域 -->
|
||||
<div class="city-picker-hot-section">
|
||||
<!-- 定位标签 -->
|
||||
<div class="location-section" id="locationSection">
|
||||
<!-- 定位标签会通过JS动态添加 -->
|
||||
</div>
|
||||
|
||||
<!-- 热门城市 -->
|
||||
<div class="hot-cities-section">
|
||||
<div class="section-title">热门城市</div>
|
||||
<div class="hot-cities-grid" id="hotCitiesList">
|
||||
<!-- 热门城市标签会通过JS动态添加 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 省份城市联动区域 -->
|
||||
<div class="province-city-section">
|
||||
<div class="section-title">选择地区</div>
|
||||
<div class="province-city-container">
|
||||
<!-- 省份列表 -->
|
||||
<div class="province-list-wrapper">
|
||||
<div class="province-list" id="provinceColumn">
|
||||
<!-- 省份列表会通过JS动态添加 -->
|
||||
</div>
|
||||
<div class="city-picker-column" id="cityColumn">
|
||||
</div>
|
||||
|
||||
<!-- 城市网格 -->
|
||||
<div class="city-grid-wrapper">
|
||||
<div class="city-grid" id="cityColumn">
|
||||
<!-- 城市列表会通过JS动态添加 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toast 提示 -->
|
||||
<div class="toast" id="toast">
|
||||
@@ -101,9 +129,7 @@
|
||||
<div class="agreement-modal-body">
|
||||
<div class="agreement-modal-text">我已阅读并同意</div>
|
||||
<div class="agreement-modal-links">
|
||||
<a href="#" class="agreement-modal-link">《人脸识别验证个人信息使用授权书》</a>
|
||||
<a href="#" class="agreement-modal-link">《个人信息授权书》</a>
|
||||
<a href="#" class="agreement-modal-link">《个人信息共享授权书》</a>
|
||||
<a href="./agre/个人信息共享授权书.html" class="agreement-modal-link">《个人信息共享授权书》</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="agreement-modal-footer">
|
||||
|
||||
131
docs/cors-issue-fix.md
Normal file
131
docs/cors-issue-fix.md
Normal file
@@ -0,0 +1,131 @@
|
||||
# 极光SDK跨域问题解决方案
|
||||
|
||||
## 问题描述
|
||||
|
||||
flux-web 发布后访问极光服务时出现跨域错误。从 `https://abc.1216.top` 发起到 `https://h5.verification.jiguang.cn` 的请求被浏览器阻止。
|
||||
|
||||
## 问题原因
|
||||
|
||||
极光SDK在初始化时会**直接由浏览器**向 `https://h5.verification.jiguang.cn` 发送请求,这是一个跨域请求。
|
||||
|
||||
**重要说明**:
|
||||
- 请求是浏览器直接从H5页面发起到极光服务器的,**不经过你的Nginx服务器**
|
||||
- CORS响应头必须由**被请求的服务器**(极光服务器)返回才有效
|
||||
- 你的Nginx只能控制自己返回的响应头,**无法控制极光服务器的响应头**
|
||||
- 因此,在Nginx中添加CORS响应头对这种情况**完全无效**
|
||||
|
||||
如果极光服务器没有为当前域名配置CORS白名单,浏览器会阻止这个请求。
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 方案一:配置极光控制台域名白名单(**唯一有效方案**)
|
||||
|
||||
这是**唯一有效**的解决方案,因为只有极光服务器可以控制它返回的CORS响应头。
|
||||
|
||||
1. 登录极光控制台:https://www.jiguang.cn/
|
||||
2. 进入应用管理 → 选择应用 → 一键登录配置
|
||||
3. 在"Web端配置"中添加允许的域名:
|
||||
- `abc.1216.top`
|
||||
- `https://abc.1216.top`(如果需要)
|
||||
4. 保存配置并等待生效(通常几分钟内)
|
||||
|
||||
**为什么这是唯一有效方案**:
|
||||
- 浏览器直接请求极光服务器,不经过你的服务器
|
||||
- 只有极光服务器配置了域名白名单后,才会在响应头中添加 `Access-Control-Allow-Origin`
|
||||
- 你的Nginx、后端服务器都无法解决这个问题
|
||||
|
||||
### 方案二:检查SDK初始化配置
|
||||
|
||||
确认 `jverify.service.js` 中的 `domainName` 参数正确设置:
|
||||
|
||||
```javascript
|
||||
window.JVerificationInterface.init({
|
||||
appkey: appId,
|
||||
domainName: window.location.origin, // 应该是 https://abc.1216.top
|
||||
debugMode: debugMode,
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
### 方案三:使用后端代理(不推荐,仅作为备选)
|
||||
|
||||
**注意**:此方案**不推荐**,因为:
|
||||
1. 极光SDK是第三方SDK,其内部请求无法被代理
|
||||
2. SDK初始化时的请求是SDK内部发起的,无法修改
|
||||
3. 即使代理了部分API,SDK的其他功能可能仍然失败
|
||||
|
||||
如果极光控制台确实不支持域名白名单配置(通常都支持),可以尝试:
|
||||
|
||||
1. 在后端添加代理接口(如 `/zcore/jpush/proxy`)
|
||||
2. 修改极光SDK的调用方式(**需要修改SDK源码,不推荐**)
|
||||
3. 后端转发请求到极光服务器
|
||||
|
||||
**强烈建议**:优先使用方案一,在极光控制台配置域名白名单。
|
||||
|
||||
### 方案四:检查网络环境
|
||||
|
||||
1. 确认服务器网络可以访问 `h5.verification.jiguang.cn`
|
||||
2. 检查是否有防火墙或安全组规则阻止了请求
|
||||
3. 确认HTTPS证书配置正确
|
||||
|
||||
## 调试步骤
|
||||
|
||||
1. **检查浏览器控制台**
|
||||
- 打开浏览器开发者工具(F12)
|
||||
- 查看 Network 标签页
|
||||
- 找到对 `h5.verification.jiguang.cn` 的请求
|
||||
- 查看响应头中是否有 `Access-Control-Allow-Origin`
|
||||
|
||||
2. **检查请求详情**
|
||||
- 查看请求的 Referer 是否为 `https://abc.1216.top`
|
||||
- 查看请求的 Origin 是否为 `https://abc.1216.top`
|
||||
- 查看响应状态码和错误信息
|
||||
|
||||
3. **检查极光SDK初始化日志**
|
||||
- 查看浏览器控制台中的 `[JVerifyService]` 日志
|
||||
- 确认 `domainName` 参数的值是否正确
|
||||
- 确认SDK初始化是否成功
|
||||
|
||||
## 常见错误
|
||||
|
||||
### 错误1:CORS policy blocked
|
||||
```
|
||||
Access to XMLHttpRequest at 'https://h5.verification.jiguang.cn/...'
|
||||
from origin 'https://abc.1216.top' has been blocked by CORS policy
|
||||
```
|
||||
**解决方法**:在极光控制台配置域名白名单
|
||||
|
||||
### 错误2:Network Error
|
||||
```
|
||||
Network Error: Failed to fetch
|
||||
```
|
||||
**解决方法**:检查网络连接和防火墙配置
|
||||
|
||||
### 错误3:SDK初始化失败
|
||||
```
|
||||
[JVerifyService] 极光SDK初始化失败
|
||||
```
|
||||
**解决方法**:检查 `domainName` 参数和 AppKey 配置
|
||||
|
||||
## 验证方法
|
||||
|
||||
配置完成后,按以下步骤验证:
|
||||
|
||||
1. 清除浏览器缓存
|
||||
2. 重新访问页面
|
||||
3. 打开浏览器控制台,查看是否有跨域错误
|
||||
4. 检查极光SDK是否成功初始化
|
||||
5. 测试一键登录功能是否正常
|
||||
|
||||
## 相关文件
|
||||
|
||||
- `src/js/services/jverify.service.js` - 极光SDK服务
|
||||
- `src/js/pages/index.page.js` - 主页面(包含一键登录初始化)
|
||||
- `nginx.conf.example` - Nginx配置示例
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 极光控制台的域名白名单配置可能需要几分钟才能生效
|
||||
2. 确保配置的域名与访问域名完全一致(包括协议 http/https)
|
||||
3. 如果使用CDN,可能需要同时配置CDN域名
|
||||
4. 某些浏览器(如Safari)对CORS有更严格的限制
|
||||
14
index.html
14
index.html
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>薇钱包</title>
|
||||
<title>百雅融</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<link rel="stylesheet" href="./src/css/components/one-click-login.css">
|
||||
</head>
|
||||
@@ -87,9 +87,9 @@
|
||||
<span class="checkbox-icon"></span>
|
||||
<span class="checkbox-label">我已阅读并同意</span>
|
||||
</label>
|
||||
<a href="#" class="agreement-link">《个人信息共享授权书》</a>
|
||||
<a href="#" class="agreement-link">《白丫融注册协议》</a>
|
||||
<a href="#" class="agreement-link">《隐私政策》</a>
|
||||
<a href="./agre/个人信息共享授权书.html" class="agreement-link">《个人信息共享授权书》</a>
|
||||
<a href="./agre/用户注册服务协议.html" class="agreement-link">《白丫融注册协议》</a>
|
||||
<a href="./agre/隐私政策.html" class="agreement-link">《隐私政策》</a>
|
||||
</div>
|
||||
|
||||
<!-- 底部声明 -->
|
||||
@@ -198,9 +198,9 @@
|
||||
<div class="agreement-modal-body">
|
||||
<div class="agreement-modal-text">我已阅读并同意</div>
|
||||
<div class="agreement-modal-links">
|
||||
<a href="#" class="agreement-modal-link">《个人信息共享授权书》</a>
|
||||
<a href="#" class="agreement-modal-link">《白丫融注册协议》</a>
|
||||
<a href="#" class="agreement-modal-link">《隐私政策》</a>
|
||||
<a href="./agre/个人信息共享授权书.html" class="agreement-modal-link">《个人信息共享授权书》</a>
|
||||
<a href="./agre/用户注册服务协议.html" class="agreement-modal-link">《白丫融注册协议》</a>
|
||||
<a href="./agre/隐私政策.html" class="agreement-modal-link">《隐私政策》</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="agreement-modal-footer">
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
# flux-web Nginx 配置示例
|
||||
#
|
||||
# 使用方法:
|
||||
# 1. 将下面配置复制到 /etc/nginx/sites-available/flux-web
|
||||
# 2. 修改 server_name(你的域名)和 root(项目路径)
|
||||
# 3. 执行:ln -s /etc/nginx/sites-available/flux-web /etc/nginx/sites-enabled/
|
||||
# 4. 测试:nginx -t
|
||||
# 5. 重载:systemctl reload nginx
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name your-domain.com; # ← 改成你的域名
|
||||
|
||||
root /var/www/flux-web; # ← 改成项目路径
|
||||
index index.html;
|
||||
|
||||
# 静态资源缓存
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
|
||||
expires 30d;
|
||||
}
|
||||
|
||||
# 主路由
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
|
||||
# HTTPS 配置(可选,使用 Let's Encrypt 免费证书)
|
||||
# 先执行:apt install certbot python3-certbot-nginx
|
||||
# 然后执行:certbot --nginx -d your-domain.com
|
||||
|
||||
# 完整 HTTPS 配置示例:
|
||||
#
|
||||
# server {
|
||||
# listen 443 ssl;
|
||||
# server_name your-domain.com;
|
||||
# root /var/www/flux-web;
|
||||
# index index.html;
|
||||
#
|
||||
# ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
|
||||
# ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
|
||||
#
|
||||
# location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
|
||||
# expires 30d;
|
||||
# }
|
||||
#
|
||||
# location / {
|
||||
# try_files $uri $uri/ /index.html;
|
||||
# }
|
||||
# }
|
||||
@@ -5,11 +5,13 @@
|
||||
|
||||
export const API_CONFIG = {
|
||||
// 基础 URL - 开发环境
|
||||
BASE_URL: 'http://localhost:8071',
|
||||
// BASE_URL: 'http://localhost:8071',
|
||||
|
||||
// 生产环境 URL(如需切换,取消注释并注释掉上面的)
|
||||
// BASE_URL: 'https://flux.1216.top',
|
||||
|
||||
BASE_URL: '',
|
||||
|
||||
// API 端点配置
|
||||
ENDPOINTS: {
|
||||
// 短信相关接口(使用 JSON 格式)
|
||||
@@ -20,15 +22,21 @@ export const API_CONFIG = {
|
||||
JPUSH_LOGIN: '/zcore/jpush/login',
|
||||
|
||||
// 客户相关接口(使用 x-www-form-urlencoded 格式)
|
||||
CUSTOMER_REGISTER: '/partnerh5/login',
|
||||
CUSTOMER_REGISTER: '/api/partnerh5/login',
|
||||
|
||||
// 表单相关接口(使用 x-www-form-urlencoded 格式)
|
||||
SUBMIT_FORM: '/partnerh5/submit',
|
||||
SUBMIT_DRAFT_FORM: '/partnerh5/save_draft',
|
||||
GET_DRAFT_FORM: '/partnerh5/get_draft',
|
||||
SUBMIT_FORM: '/api/partnerh5/submit',
|
||||
SUBMIT_DRAFT_FORM: '/api/partnerh5/save_draft',
|
||||
GET_DRAFT_FORM: '/api/partnerh5/get_draft',
|
||||
|
||||
// 授权状态查询接口
|
||||
CHECK_AUTH_STATUS: '/api/partnerh5/check_auth_status',
|
||||
|
||||
// 区域数据接口
|
||||
AREA_LIST: '/partnerh5/area_list',
|
||||
AREA_LIST: '/api/partnerh5/area_list',
|
||||
|
||||
// IP定位接口
|
||||
IP_LOCATION: '/api/partnerh5/ip_location',
|
||||
},
|
||||
|
||||
// 请求超时配置(毫秒)
|
||||
|
||||
@@ -5,12 +5,15 @@
|
||||
|
||||
// ==================== 调试配置 ====================
|
||||
export const DEBUG_CONFIG = {
|
||||
// 调试模式开关
|
||||
ENABLED: true,
|
||||
// 调试模式开关(生产环境设为 false)
|
||||
ENABLED: false,
|
||||
|
||||
// 调试模式下的固定验证码
|
||||
SMS_CODE: '123456',
|
||||
|
||||
// 调试模式下默认的短链代码
|
||||
DEFAULT_SHORTCODE: 'I3fMzX',
|
||||
|
||||
// 是否启用详细日志
|
||||
VERBOSE_LOGGING: true,
|
||||
};
|
||||
@@ -39,7 +42,9 @@ export const ASSET_CONFIG = {
|
||||
'有公积金': 1, '无公积金': 2,
|
||||
'有社保': 1, '无社保': 2,
|
||||
'有信用卡': 1, '无信用卡': 2,
|
||||
'有银行流水': 1, '无银行流水': 2
|
||||
'有银行流水': 1, '无银行流水': 2,
|
||||
'上班族': 1, '自由职业': 2, '企业主': 3, '公务员/国企': 4,
|
||||
'700以上': 1, '650-700': 2, '600-650': 3, '无': 4
|
||||
},
|
||||
|
||||
// 资产选项反向映射:数字值 → 中文
|
||||
@@ -49,7 +54,9 @@ export const ASSET_CONFIG = {
|
||||
fund: { 1: '有公积金', 2: '无公积金' },
|
||||
social: { 1: '有社保', 2: '无社保' },
|
||||
credit: { 1: '有信用卡', 2: '无信用卡' },
|
||||
bank: { 1: '有银行流水', 2: '无银行流水' }
|
||||
bank: { 1: '有银行流水', 2: '无银行流水' },
|
||||
job: { 1: '上班族', 2: '自由职业', 3: '企业主', 4: '公务员/国企' },
|
||||
zhima: { 1: '700以上', 2: '650-700', 3: '600-650', 4: '无' }
|
||||
},
|
||||
|
||||
// 资产项配置列表
|
||||
@@ -59,7 +66,9 @@ export const ASSET_CONFIG = {
|
||||
{ id: 'fund', name: '公积金', options: ['有公积金', '无公积金'] },
|
||||
{ id: 'social', name: '社保', options: ['有社保', '无社保'] },
|
||||
{ id: 'credit', name: '信用卡', options: ['有信用卡', '无信用卡'] },
|
||||
{ id: 'bank', name: '银行流水', options: ['有银行流水', '无银行流水'] }
|
||||
{ id: 'bank', name: '银行流水', options: ['有银行流水', '无银行流水'] },
|
||||
{ id: 'job', name: '职业', options: ['上班族', '自由职业', '企业主', '公务员/国企'] },
|
||||
{ id: 'zhima', name: '芝麻分', options: ['700以上', '650-700', '600-650', '无'] }
|
||||
],
|
||||
|
||||
// 进度金额配置
|
||||
@@ -136,12 +145,6 @@ export const CACHE_CONFIG = {
|
||||
USER_SESSION: 'flux_user_session',
|
||||
FORM_ID: 'flux_form_id',
|
||||
},
|
||||
|
||||
// 测试模式配置
|
||||
TEST_MODE: {
|
||||
ENABLED: true, // 是否启用测试模式
|
||||
DEFAULT_SHORTCODE: 'sRh907', // 测试模式下默认的短链代码
|
||||
},
|
||||
};
|
||||
|
||||
// ==================== 验证规则配置 ====================
|
||||
|
||||
@@ -7,6 +7,17 @@ import { API_CONFIG, DEBUG_CONFIG } from '../config/index.js';
|
||||
import { UserCache } from './user-cache.js';
|
||||
|
||||
export class ApiClient {
|
||||
/**
|
||||
* 拼接请求 URL(BASE_URL 为空时使用当前页同源,便于同站部署)
|
||||
* @param {string} endpoint - 路径,如 /api/partnerh5/area_list
|
||||
* @returns {string} - 完整 URL 或相对路径
|
||||
*/
|
||||
static getRequestUrl(endpoint) {
|
||||
const base = API_CONFIG.BASE_URL;
|
||||
if (base) return base.replace(/\/$/, '') + endpoint;
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建查询参数
|
||||
* @param {Object} params - 参数对象
|
||||
@@ -42,7 +53,7 @@ export class ApiClient {
|
||||
* @returns {Promise<Object>} - 响应数据
|
||||
*/
|
||||
static async post(endpoint, data = {}) {
|
||||
const url = API_CONFIG.BASE_URL + endpoint;
|
||||
const url = this.getRequestUrl(endpoint);
|
||||
const headers = this.getHeaders('application/json');
|
||||
|
||||
try {
|
||||
@@ -75,7 +86,7 @@ export class ApiClient {
|
||||
* @returns {Promise<Object>} - 响应数据
|
||||
*/
|
||||
static async xpost(endpoint, data = {}) {
|
||||
const url = API_CONFIG.BASE_URL + endpoint;
|
||||
const url = this.getRequestUrl(endpoint);
|
||||
const headers = this.getHeaders('application/x-www-form-urlencoded');
|
||||
|
||||
try {
|
||||
@@ -108,9 +119,11 @@ export class ApiClient {
|
||||
* @returns {Promise<Object>} - 响应数据
|
||||
*/
|
||||
static async get(endpoint, params = {}) {
|
||||
const url = new URL(API_CONFIG.BASE_URL + endpoint);
|
||||
const baseUrl = this.getRequestUrl(endpoint);
|
||||
const url = baseUrl.startsWith('http')
|
||||
? new URL(baseUrl)
|
||||
: new URL(baseUrl, window.location.origin);
|
||||
|
||||
// 添加查询参数
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
url.searchParams.append(key, value);
|
||||
});
|
||||
|
||||
103
src/js/core/cache-manager.js
Normal file
103
src/js/core/cache-manager.js
Normal file
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* 通用缓存管理器
|
||||
* 提供 localStorage 缓存、请求去重和过期时间管理
|
||||
*/
|
||||
|
||||
export class CacheManager {
|
||||
// 正在进行的请求缓存(静态)
|
||||
static pendingRequests = new Map();
|
||||
|
||||
/**
|
||||
* 获取缓存或执行请求
|
||||
* @param {string} cacheKey - 缓存键
|
||||
* @param {Function} fetchFn - 获取数据的异步函数
|
||||
* @param {number} duration - 缓存有效期(毫秒),默认 10 分钟
|
||||
* @returns {Promise<any>} - 缓存的数据或新获取的数据
|
||||
*/
|
||||
static async getCachedOrFetch(cacheKey, fetchFn, duration = 10 * 60 * 1000) {
|
||||
// 检查 localStorage 缓存
|
||||
const cached = this._getFromCache(cacheKey, duration);
|
||||
if (cached !== null) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
// 检查是否有正在进行的请求
|
||||
if (this.pendingRequests.has(cacheKey)) {
|
||||
console.log(`[CacheManager] 等待正在进行的请求: ${cacheKey}`);
|
||||
return this.pendingRequests.get(cacheKey);
|
||||
}
|
||||
|
||||
// 创建新请求
|
||||
const requestPromise = this._fetchAndCache(cacheKey, fetchFn, duration);
|
||||
this.pendingRequests.set(cacheKey, requestPromise);
|
||||
|
||||
try {
|
||||
return await requestPromise;
|
||||
} finally {
|
||||
// 请求完成后清除缓存
|
||||
this.pendingRequests.delete(cacheKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 localStorage 获取缓存
|
||||
* @private
|
||||
* @param {string} key - 缓存键
|
||||
* @param {number} duration - 有效期(毫秒)
|
||||
* @returns {any|null} - 缓存的数据,如果不存在或已过期则返回 null
|
||||
*/
|
||||
static _getFromCache(key, duration) {
|
||||
const cached = localStorage.getItem(key);
|
||||
if (cached) {
|
||||
try {
|
||||
const { data, timestamp } = JSON.parse(cached);
|
||||
if (Date.now() - timestamp < duration) {
|
||||
console.log(`[CacheManager] 使用缓存: ${key}`);
|
||||
return data;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(`[CacheManager] 缓存解析失败: ${key}`, e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据并缓存
|
||||
* @private
|
||||
* @param {string} key - 缓存键
|
||||
* @param {Function} fetchFn - 获取数据的异步函数
|
||||
* @param {number} duration - 有效期(毫秒)
|
||||
* @returns {Promise<any>} - 获取的数据
|
||||
*/
|
||||
static async _fetchAndCache(key, fetchFn, duration) {
|
||||
const data = await fetchFn();
|
||||
localStorage.setItem(key, JSON.stringify({
|
||||
data,
|
||||
timestamp: Date.now()
|
||||
}));
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除指定前缀的所有缓存
|
||||
* @param {string} prefix - 缓存键前缀
|
||||
*/
|
||||
static clearCacheByPrefix(prefix) {
|
||||
Object.keys(localStorage)
|
||||
.filter(key => key.startsWith(prefix))
|
||||
.forEach(key => localStorage.removeItem(key));
|
||||
console.log(`[CacheManager] 清除缓存前缀: ${prefix}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除单个缓存
|
||||
* @param {string} key - 缓存键
|
||||
*/
|
||||
static clearCache(key) {
|
||||
localStorage.removeItem(key);
|
||||
console.log(`[CacheManager] 清除缓存: ${key}`);
|
||||
}
|
||||
}
|
||||
|
||||
export default CacheManager;
|
||||
@@ -35,6 +35,8 @@ export class DraftManager {
|
||||
social: ASSET_CONFIG.VALUE_MAPPING[formData.assets.social] || 0,
|
||||
credit: ASSET_CONFIG.VALUE_MAPPING[formData.assets.credit] || 0,
|
||||
bank: ASSET_CONFIG.VALUE_MAPPING[formData.assets.bank] || 0,
|
||||
job: ASSET_CONFIG.VALUE_MAPPING[formData.assets.job] || 0,
|
||||
zhima: ASSET_CONFIG.VALUE_MAPPING[formData.assets.zhima] || 0,
|
||||
realname: formData.basicInfo.name || '',
|
||||
idcard: formData.basicInfo.idCard || '',
|
||||
city: formData.basicInfo.city || '',
|
||||
@@ -74,6 +76,12 @@ export class DraftManager {
|
||||
if (serverData.bank && ASSET_CONFIG.REVERSE_MAPPING.bank[serverData.bank]) {
|
||||
formData.assets.bank = ASSET_CONFIG.REVERSE_MAPPING.bank[serverData.bank];
|
||||
}
|
||||
if (serverData.job && ASSET_CONFIG.REVERSE_MAPPING.job[serverData.job]) {
|
||||
formData.assets.job = ASSET_CONFIG.REVERSE_MAPPING.job[serverData.job];
|
||||
}
|
||||
if (serverData.zhima && ASSET_CONFIG.REVERSE_MAPPING.zhima[serverData.zhima]) {
|
||||
formData.assets.zhima = ASSET_CONFIG.REVERSE_MAPPING.zhima[serverData.zhima];
|
||||
}
|
||||
|
||||
// 转换基本信息
|
||||
if (serverData.realname) {
|
||||
@@ -95,21 +103,23 @@ export class DraftManager {
|
||||
* @private
|
||||
*/
|
||||
static _getShortcode() {
|
||||
// 测试模式下使用默认 shortcode
|
||||
if (CACHE_CONFIG.TEST_MODE.ENABLED) {
|
||||
console.log('[DraftManager] 测试模式,使用默认 shortcode:', CACHE_CONFIG.TEST_MODE.DEFAULT_SHORTCODE);
|
||||
return CACHE_CONFIG.TEST_MODE.DEFAULT_SHORTCODE;
|
||||
}
|
||||
|
||||
// 优先从 URL 获取
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const shortcode = params.get('code') || params.get('shortcode') || '';
|
||||
|
||||
if (!shortcode) {
|
||||
console.warn('[DraftManager] URL 中未找到 code 或 shortcode 参数');
|
||||
console.warn('[DraftManager] 当前 URL:', window.location.href);
|
||||
if (shortcode) {
|
||||
return shortcode;
|
||||
}
|
||||
|
||||
return shortcode;
|
||||
// URL 中没有,调试模式下使用默认 shortcode
|
||||
if (DEBUG_CONFIG.ENABLED && DEBUG_CONFIG.DEFAULT_SHORTCODE) {
|
||||
console.log('[DraftManager] URL 无 shortcode,调试模式使用默认值:', DEBUG_CONFIG.DEFAULT_SHORTCODE);
|
||||
return DEBUG_CONFIG.DEFAULT_SHORTCODE;
|
||||
}
|
||||
|
||||
console.warn('[DraftManager] URL 中未找到 code 或 shortcode 参数');
|
||||
console.warn('[DraftManager] 当前 URL:', window.location.href);
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,17 +6,13 @@
|
||||
import { CACHE_CONFIG } from '../config/index.js';
|
||||
|
||||
export class FormIdGenerator {
|
||||
// 防止递归调用检测
|
||||
static _gettingFormId = false;
|
||||
static _lastFormId = '';
|
||||
|
||||
/**
|
||||
* 生成随机9位数字的表单唯一标识符
|
||||
* @returns {number} - 9位随机数字
|
||||
* @private
|
||||
*/
|
||||
static _generateRandomId() {
|
||||
return Math.floor(Math.random() * 900000000) + 100000000; // 生成100000000-999999999之间的9位数字
|
||||
return Math.floor(Math.random() * 900000000) + 100000000;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -24,17 +20,7 @@ export class FormIdGenerator {
|
||||
* @returns {string} - 表单ID字符串
|
||||
*/
|
||||
static getOrCreate() {
|
||||
// 防止递归调用检测
|
||||
if (this._gettingFormId) {
|
||||
console.error('[FormIdGenerator] 检测到递归调用 getOrCreateFormId', new Error().stack);
|
||||
return this._lastFormId || '';
|
||||
}
|
||||
this._gettingFormId = true;
|
||||
|
||||
try {
|
||||
console.log('[FormIdGenerator] getOrCreate 被调用');
|
||||
let formId = localStorage.getItem(CACHE_CONFIG.KEYS.FORM_ID);
|
||||
console.log('[FormIdGenerator] 从 localStorage 获取的 formId:', formId);
|
||||
|
||||
if (!formId) {
|
||||
formId = this._generateRandomId().toString();
|
||||
@@ -42,14 +28,7 @@ export class FormIdGenerator {
|
||||
console.log('[FormIdGenerator] 生成新的表单ID:', formId);
|
||||
}
|
||||
|
||||
this._lastFormId = formId;
|
||||
return formId;
|
||||
} catch (error) {
|
||||
console.error('[FormIdGenerator] getOrCreate 出错:', error);
|
||||
return '';
|
||||
} finally {
|
||||
this._gettingFormId = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,7 +36,6 @@ export class FormIdGenerator {
|
||||
*/
|
||||
static clear() {
|
||||
localStorage.removeItem(CACHE_CONFIG.KEYS.FORM_ID);
|
||||
this._lastFormId = '';
|
||||
console.log('[FormIdGenerator] 已清除表单ID');
|
||||
}
|
||||
|
||||
@@ -79,7 +57,6 @@ export class FormIdGenerator {
|
||||
return;
|
||||
}
|
||||
localStorage.setItem(CACHE_CONFIG.KEYS.FORM_ID, formId);
|
||||
this._lastFormId = formId;
|
||||
console.log('[FormIdGenerator] 已设置表单ID:', formId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,12 @@
|
||||
import { CityPicker, Modal } from '../ui/index.js';
|
||||
import { Validator, Formatter } from '../utils/index.js';
|
||||
import { DraftManager, FormIdGenerator, UserCache } from '../core/index.js';
|
||||
import { ASSET_CONFIG, BASIC_INFO_CONFIG, PROVINCE_CITY_DATA, CACHE_CONFIG } from '../config/index.js';
|
||||
import { ASSET_CONFIG, BASIC_INFO_CONFIG, PROVINCE_CITY_DATA, CACHE_CONFIG, API_CONFIG } from '../config/index.js';
|
||||
import { showToast } from '../ui/toast.js';
|
||||
import { AreaService } from '../services/area.service.js';
|
||||
import { getLocationService } from '../services/location.service.js';
|
||||
import { AuthFlowService, AUTH_STATUS } from '../services/index.js';
|
||||
import { ApiClient } from '../core/api.js';
|
||||
|
||||
export class BasicInfoPage {
|
||||
constructor() {
|
||||
@@ -24,9 +27,6 @@ export class BasicInfoPage {
|
||||
this.isSavingDraft = false;
|
||||
this.autoSaveTimer = null;
|
||||
|
||||
// 身份证自动填充标记
|
||||
this.lastFilledAreaCode = null;
|
||||
|
||||
// 组件实例
|
||||
this.cityPicker = null;
|
||||
this.agreementModal = null;
|
||||
@@ -70,6 +70,9 @@ export class BasicInfoPage {
|
||||
this.renderForm();
|
||||
this.bindEvents();
|
||||
this.updateProgress();
|
||||
|
||||
// IP定位自动填充城市
|
||||
this.autoFillCityByLocation();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,6 +102,7 @@ export class BasicInfoPage {
|
||||
modalId: 'cityPickerModal',
|
||||
provinceColumnId: 'provinceColumn',
|
||||
cityColumnId: 'cityColumn',
|
||||
hotCitiesId: 'hotCitiesList',
|
||||
cancelBtnId: 'cityCancelBtn',
|
||||
confirmBtnId: 'cityConfirmBtn',
|
||||
onConfirm: (result) => {
|
||||
@@ -329,34 +333,15 @@ export class BasicInfoPage {
|
||||
const input = infoItem.querySelector(`#basic-input-${item.id}`);
|
||||
const errorEl = infoItem.querySelector(`#error-${item.id}`);
|
||||
|
||||
input.addEventListener('input', async () => {
|
||||
input.addEventListener('input', () => {
|
||||
this.basicInfoValues[item.id] = input.value.trim();
|
||||
this.updateBasicInfoProgress();
|
||||
this.checkSubmitButton();
|
||||
|
||||
// 清除错误提示
|
||||
input.classList.remove('error');
|
||||
if (errorEl) {
|
||||
input.classList.remove('error');
|
||||
errorEl.style.display = 'none';
|
||||
}
|
||||
|
||||
// 身份证输入到6位时,自动填充地区
|
||||
if (item.id === 'idCard' && input.value.trim().length >= 6) {
|
||||
const areaCode = Validator.extractAreaCode(input.value);
|
||||
console.log('[BasicInfoPage] 身份证输入,提取地区代码:', areaCode);
|
||||
|
||||
if (areaCode && areaCode !== this.lastFilledAreaCode) {
|
||||
console.log('[BasicInfoPage] 查询地区信息...');
|
||||
const areaInfo = await AreaService.getAreaByCode(areaCode);
|
||||
console.log('[BasicInfoPage] 查询结果:', areaInfo);
|
||||
|
||||
if (areaInfo) {
|
||||
this.handleCityConfirm(areaInfo);
|
||||
this.lastFilledAreaCode = areaCode;
|
||||
console.log('[BasicInfoPage] 地区自动填充成功');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -547,7 +532,7 @@ export class BasicInfoPage {
|
||||
console.error('[BasicInfoPage] 当前 URL:', window.location.href);
|
||||
console.error('[BasicInfoPage] 解决方案:');
|
||||
console.error(' 1. 在 URL 中添加 ?code=your_shortcode');
|
||||
console.error(' 2. 或启用测试模式(CACHE_CONFIG.TEST_MODE.ENABLED = true)');
|
||||
console.error(' 2. 或启用调试模式(DEBUG_CONFIG.ENABLED = true)');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -629,17 +614,12 @@ export class BasicInfoPage {
|
||||
// 清除草稿
|
||||
DraftManager.clearDraft();
|
||||
|
||||
// 提交成功,显示结果
|
||||
if (response.data && response.data.h5Urls) {
|
||||
// 如果有返回的 H5 URL,显示跳转选项
|
||||
// 提交成功:有返回数据则进入成功页(含无 H5 时的成功+5秒倒计时/回首页)
|
||||
if (response.data) {
|
||||
this.showSubmitSuccessDialog(response.data);
|
||||
} else {
|
||||
// 普通成功提示
|
||||
showToast('信息提交成功!');
|
||||
// 延迟跳转或刷新
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 2000);
|
||||
setTimeout(() => window.location.reload(), 2000);
|
||||
}
|
||||
} else {
|
||||
throw new Error(response.message || '提交失败');
|
||||
@@ -655,85 +635,241 @@ export class BasicInfoPage {
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示提交成功页面
|
||||
* @param {Object} data - 返回数据 { formdataid, h5Urls }
|
||||
* 显示提交成功页面(授权流程)
|
||||
* @param {Object} data - 返回数据 { formdataid/formDataId/formid, h5Urls, redirectUrl }
|
||||
*/
|
||||
showSubmitSuccessDialog(data) {
|
||||
const h5Urls = data.h5Urls;
|
||||
const urlEntries = Object.entries(h5Urls);
|
||||
// 兼容不同的字段名
|
||||
const formdataid = data.formdataid || data.formDataId || data.formid || data.id;
|
||||
const h5Urls = data.h5Urls || data.h5urls || [];
|
||||
const redirectUrl = data.redirectUrl || data.redirecturl || '';
|
||||
|
||||
// 隐藏表单内容
|
||||
document.getElementById('assetList').style.display = 'none';
|
||||
document.getElementById('basicInfoSection').style.display = 'none';
|
||||
this.elements.submitBtn.style.display = 'none';
|
||||
console.log('[BasicInfoPage] 授权流程数据:', { formdataid, h5Urls, redirectUrl });
|
||||
|
||||
// 创建成功提示区域
|
||||
const successContainer = document.createElement('div');
|
||||
successContainer.className = 'submit-success-container';
|
||||
successContainer.innerHTML = `
|
||||
<div class="success-header">
|
||||
<div class="success-icon">✅</div>
|
||||
<h2>信息提交成功</h2>
|
||||
<p class="formdata-id">表单ID:${data.formdataid}</p>
|
||||
</div>
|
||||
// 隐藏表单相关的所有内容
|
||||
const topCard = document.querySelector('.top-card');
|
||||
const assetSection = document.querySelector('.asset-section');
|
||||
const basicInfoSection = document.getElementById('basicInfoSection');
|
||||
const bottomSection = document.getElementById('bottomSection');
|
||||
|
||||
${urlEntries.length > 0 ? `
|
||||
<div class="iframe-container">
|
||||
${urlEntries.map(([name, url], index) => `
|
||||
<div class="iframe-wrapper ${index === 0 ? 'active' : ''}" data-index="${index}">
|
||||
<div class="iframe-header">
|
||||
<span class="product-name">${name}</span>
|
||||
<span class="iframe-hint">请在下方完成申请流程</span>
|
||||
</div>
|
||||
<iframe src="${url}" class="product-iframe" frameborder="0"></iframe>
|
||||
</div>
|
||||
`).join('')}
|
||||
if (topCard) topCard.style.display = 'none';
|
||||
if (assetSection) assetSection.style.display = 'none';
|
||||
if (basicInfoSection) basicInfoSection.style.display = 'none';
|
||||
if (bottomSection) bottomSection.style.display = 'none';
|
||||
|
||||
${urlEntries.length > 1 ? `
|
||||
<div class="product-tabs">
|
||||
${urlEntries.map(([name, url], index) => `
|
||||
<button class="product-tab ${index === 0 ? 'active' : ''}" data-index="${index}">
|
||||
${name}
|
||||
</button>
|
||||
`).join('')}
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
` : `
|
||||
<div class="no-urls-message">
|
||||
<p>您的申请已提交成功!</p>
|
||||
<p>我们会尽快处理您的申请。</p>
|
||||
</div>
|
||||
`}
|
||||
// 创建授权流程容器
|
||||
const authContainer = document.createElement('div');
|
||||
authContainer.className = 'auth-flow-container';
|
||||
authContainer.id = 'authFlowContainer';
|
||||
|
||||
<div class="success-footer">
|
||||
<button onclick="window.location.reload()" class="back-home-btn">返回首页</button>
|
||||
// 如果没有 h5Urls,显示成功提示
|
||||
if (!h5Urls || h5Urls.length === 0) {
|
||||
// 有 redirectUrl 跳转到指定地址,否则返回首页
|
||||
const finalUrl = redirectUrl || window.location.href.split('?')[0];
|
||||
const btnText = redirectUrl ? '立即跳转' : '返回首页';
|
||||
|
||||
authContainer.innerHTML = `
|
||||
<div class="auth-complete-section">
|
||||
<div class="auth-complete-icon">✓</div>
|
||||
<div class="auth-complete-title">信息提交成功</div>
|
||||
<div class="auth-complete-desc">您的申请已提交成功!</div>
|
||||
<div class="auth-countdown">
|
||||
<span id="countdownSeconds">5</span> 秒后自动跳转...
|
||||
</div>
|
||||
<button class="auth-redirect-btn" id="redirectNowBtn">${btnText}</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 插入到页面顶部
|
||||
const mainContainer = document.querySelector('.container') || document.body;
|
||||
mainContainer.insertBefore(successContainer, mainContainer.firstChild);
|
||||
mainContainer.insertBefore(authContainer, mainContainer.firstChild);
|
||||
|
||||
// 绑定 Tab 切换事件
|
||||
if (urlEntries.length > 1) {
|
||||
const tabs = successContainer.querySelectorAll('.product-tab');
|
||||
const iframes = successContainer.querySelectorAll('.iframe-wrapper');
|
||||
// 启动 5 秒倒计时
|
||||
this.startFinalCountdown(finalUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
tabs.forEach(tab => {
|
||||
tab.addEventListener('click', () => {
|
||||
const index = parseInt(tab.dataset.index);
|
||||
// 有 h5Urls,显示授权流程界面
|
||||
authContainer.innerHTML = `
|
||||
<div class="auth-progress-section">
|
||||
<div class="auth-progress-header">
|
||||
<span class="auth-progress-title">授权进度</span>
|
||||
<span class="auth-progress-count">(<span id="currentIndex">1</span>/${h5Urls.length})</span>
|
||||
</div>
|
||||
<div class="auth-progress-list" id="authProgressList">
|
||||
${h5Urls.map((item, index) => `
|
||||
<div class="auth-progress-item" data-apicode="${item.apicode}" data-index="${index}">
|
||||
<span class="auth-progress-icon" id="icon-${item.apicode}">○</span>
|
||||
<span class="auth-progress-name">${item.apiname}</span>
|
||||
<span class="auth-progress-status" id="status-${item.apicode}">等待中</span>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
<div class="auth-iframe-section">
|
||||
<div class="auth-iframe-header">
|
||||
<span class="auth-iframe-title" id="currentProductName">${h5Urls[0].apiname}</span>
|
||||
<span class="auth-iframe-hint">请在下方完成授权</span>
|
||||
</div>
|
||||
<iframe src="" class="auth-iframe" id="authIframe" frameborder="0"></iframe>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 更新 Tab 状态
|
||||
tabs.forEach(t => t.classList.remove('active'));
|
||||
tab.classList.add('active');
|
||||
const mainContainer = document.querySelector('.container') || document.body;
|
||||
mainContainer.insertBefore(authContainer, mainContainer.firstChild);
|
||||
|
||||
// 更新 iframe 显示
|
||||
iframes.forEach(iframe => iframe.classList.remove('active'));
|
||||
iframes[index].classList.add('active');
|
||||
// 启动授权流程
|
||||
this.runAuthFlow(formdataid, h5Urls, redirectUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行授权流程
|
||||
* @param {number} formdataid - 表单数据ID
|
||||
* @param {Array} h5Urls - H5 URL 列表
|
||||
* @param {string} redirectUrl - 最终跳转 URL
|
||||
*/
|
||||
async runAuthFlow(formdataid, h5Urls, redirectUrl) {
|
||||
const iframe = document.getElementById('authIframe');
|
||||
const currentIndexEl = document.getElementById('currentIndex');
|
||||
const currentProductNameEl = document.getElementById('currentProductName');
|
||||
|
||||
await AuthFlowService.startAuthFlow(formdataid, h5Urls, {
|
||||
// 开始处理某个产品
|
||||
onStart: (item, index) => {
|
||||
console.log('[BasicInfoPage] 开始处理:', item.apiname);
|
||||
|
||||
// 更新进度显示
|
||||
if (currentIndexEl) {
|
||||
currentIndexEl.textContent = index + 1;
|
||||
}
|
||||
if (currentProductNameEl) {
|
||||
currentProductNameEl.textContent = item.apiname;
|
||||
}
|
||||
|
||||
// 更新进度列表状态
|
||||
const iconEl = document.getElementById(`icon-${item.apicode}`);
|
||||
const statusEl = document.getElementById(`status-${item.apicode}`);
|
||||
if (iconEl) iconEl.textContent = '●';
|
||||
if (statusEl) statusEl.textContent = '授权中...';
|
||||
|
||||
// 高亮当前项
|
||||
document.querySelectorAll('.auth-progress-item').forEach(el => {
|
||||
el.classList.remove('active');
|
||||
});
|
||||
const currentItem = document.querySelector(`.auth-progress-item[data-apicode="${item.apicode}"]`);
|
||||
if (currentItem) {
|
||||
currentItem.classList.add('active');
|
||||
}
|
||||
},
|
||||
|
||||
// iframe 切换
|
||||
onIframeChange: (url) => {
|
||||
console.log('[BasicInfoPage] iframe 切换到:', url);
|
||||
if (iframe && url) {
|
||||
iframe.src = url;
|
||||
}
|
||||
},
|
||||
|
||||
// 状态更新
|
||||
onProgress: (item, index, status) => {
|
||||
console.log('[BasicInfoPage] 状态更新:', item.apiname, status);
|
||||
},
|
||||
|
||||
// 单个完成
|
||||
onComplete: (item, index, result) => {
|
||||
console.log('[BasicInfoPage] 完成:', item.apiname, result);
|
||||
|
||||
const iconEl = document.getElementById(`icon-${item.apicode}`);
|
||||
const statusEl = document.getElementById(`status-${item.apicode}`);
|
||||
const progressItem = document.querySelector(`.auth-progress-item[data-apicode="${item.apicode}"]`);
|
||||
|
||||
if (result.timeout) {
|
||||
// 超时
|
||||
if (iconEl) iconEl.textContent = '○';
|
||||
if (statusEl) statusEl.textContent = '已超时';
|
||||
if (progressItem) progressItem.classList.add('timeout');
|
||||
} else if (result.status === AUTH_STATUS.APPLY_FAIL) {
|
||||
// 失败
|
||||
if (iconEl) iconEl.textContent = '✗';
|
||||
if (statusEl) statusEl.textContent = '失败';
|
||||
if (progressItem) progressItem.classList.add('failed');
|
||||
} else {
|
||||
// 成功
|
||||
if (iconEl) iconEl.textContent = '✓';
|
||||
if (statusEl) statusEl.textContent = '已完成';
|
||||
if (progressItem) progressItem.classList.add('completed');
|
||||
}
|
||||
},
|
||||
|
||||
// 全部完成
|
||||
onAllComplete: () => {
|
||||
console.log('[BasicInfoPage] 全部授权完成');
|
||||
this.showAuthComplete(redirectUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示授权完成界面
|
||||
* @param {string} redirectUrl - 跳转 URL
|
||||
*/
|
||||
showAuthComplete(redirectUrl) {
|
||||
const container = document.getElementById('authFlowContainer');
|
||||
if (!container) return;
|
||||
|
||||
// 隐藏 iframe 区域,显示完成提示
|
||||
const iframeSection = container.querySelector('.auth-iframe-section');
|
||||
if (iframeSection) {
|
||||
iframeSection.style.display = 'none';
|
||||
}
|
||||
|
||||
// 有 redirectUrl 跳转直推页,否则 5 秒后回首页(与无 h5Urls 分支一致)
|
||||
const finalUrl = redirectUrl || window.location.href.split('?')[0];
|
||||
const btnText = redirectUrl ? '立即跳转' : '返回首页';
|
||||
|
||||
const completeSection = document.createElement('div');
|
||||
completeSection.className = 'auth-complete-section';
|
||||
completeSection.innerHTML = `
|
||||
<div class="auth-complete-icon">✓</div>
|
||||
<div class="auth-complete-title">全部授权完成</div>
|
||||
<div class="auth-complete-desc">您的申请已全部提交成功!</div>
|
||||
<div class="auth-countdown">
|
||||
<span id="countdownSeconds">5</span> 秒后自动跳转...
|
||||
</div>
|
||||
<button class="auth-redirect-btn" id="redirectNowBtn">${btnText}</button>
|
||||
`;
|
||||
|
||||
container.appendChild(completeSection);
|
||||
|
||||
this.startFinalCountdown(finalUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动最终倒计时
|
||||
* @param {string} redirectUrl - 跳转 URL
|
||||
*/
|
||||
startFinalCountdown(redirectUrl) {
|
||||
const countdownEl = document.getElementById('countdownSeconds');
|
||||
const redirectBtn = document.getElementById('redirectNowBtn');
|
||||
|
||||
// 立即跳转按钮
|
||||
if (redirectBtn) {
|
||||
redirectBtn.addEventListener('click', () => {
|
||||
AuthFlowService.redirect(redirectUrl);
|
||||
});
|
||||
}
|
||||
|
||||
// 启动倒计时
|
||||
AuthFlowService.startCountdown(
|
||||
AuthFlowService.COUNTDOWN_SECONDS,
|
||||
(remaining) => {
|
||||
if (countdownEl) {
|
||||
countdownEl.textContent = remaining;
|
||||
}
|
||||
},
|
||||
() => {
|
||||
AuthFlowService.redirect(redirectUrl);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -754,6 +890,51 @@ export class BasicInfoPage {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过IP定位自动填充城市
|
||||
*/
|
||||
async autoFillCityByLocation() {
|
||||
try {
|
||||
// 如果已经有城市值,跳过
|
||||
if (this.basicInfoValues.city) {
|
||||
console.log('[BasicInfoPage] 城市已存在,跳过IP定位填充');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[BasicInfoPage] 开始IP定位...');
|
||||
const locationService = getLocationService();
|
||||
const location = await locationService.getLocation();
|
||||
|
||||
if (location) {
|
||||
const { province, city } = location;
|
||||
console.log('[BasicInfoPage] IP定位成功:', province, city);
|
||||
|
||||
// 查找城市代码
|
||||
const cityCode = await AreaService.findCityCode(province, city);
|
||||
|
||||
if (cityCode) {
|
||||
// 自动填充城市
|
||||
this.handleCityConfirm({
|
||||
value: city,
|
||||
province: province,
|
||||
city: city,
|
||||
provinceCode: cityCode.provinceCode,
|
||||
cityCode: cityCode.cityCode
|
||||
});
|
||||
|
||||
console.log('[BasicInfoPage] 城市自动填充成功:', city);
|
||||
} else {
|
||||
console.warn('[BasicInfoPage] 未找到城市代码:', province, city);
|
||||
}
|
||||
} else {
|
||||
console.warn('[BasicInfoPage] IP定位失败: 未获取到位置信息');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[BasicInfoPage] IP定位异常:', error);
|
||||
// 静默失败,不影响用户正常使用
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁页面
|
||||
*/
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { Picker, Modal, OneClickLoginButton } from '../ui/index.js';
|
||||
import { Validator, Formatter } from '../utils/index.js';
|
||||
import { SMSService, AuthService, LoanService } from '../services/index.js';
|
||||
import { AuthFlowService } from '../services/auth-flow.service.js';
|
||||
import { LOAN_CONFIG, PURPOSE_PICKER_CONFIG, TERM_PICKER_CONFIG, ANIMATION_CONFIG } from '../config/index.js';
|
||||
import { UserCache } from '../core/user-cache.js';
|
||||
import { showToast } from '../ui/toast.js';
|
||||
@@ -27,10 +28,11 @@ export class IndexPage {
|
||||
this.oneClickLoginBtn = null; // 一键登录按钮
|
||||
|
||||
// 极光一键登录配置
|
||||
this.jVerifyAppId = '80570da3ef331d9de547b4f1'; // 极光AppKey
|
||||
// 注意:使用一键登录需要在极光控制台配置域名白名单,否则会出现跨域错误
|
||||
this.jVerifyAppId = '80570da3ef331d9de547b4f1';
|
||||
|
||||
// 倒计时定时器
|
||||
this.countdownTimer = null;
|
||||
// 倒计时取消函数
|
||||
this.countdownCancel = null;
|
||||
|
||||
this.init();
|
||||
}
|
||||
@@ -313,24 +315,27 @@ export class IndexPage {
|
||||
* @param {HTMLElement} countdownEl - 倒计时元素
|
||||
*/
|
||||
startCountdown(countdownEl) {
|
||||
let time = 59;
|
||||
countdownEl.textContent = `${time}s`;
|
||||
countdownEl.style.color = '#999';
|
||||
countdownEl.style.cursor = 'default';
|
||||
countdownEl.onclick = null;
|
||||
|
||||
this.countdownTimer = setInterval(() => {
|
||||
time--;
|
||||
if (time > 0) {
|
||||
// 清除之前的倒计时
|
||||
if (this.countdownCancel) {
|
||||
this.countdownCancel();
|
||||
}
|
||||
|
||||
this.countdownCancel = AuthFlowService.startCountdown(
|
||||
59,
|
||||
(time) => {
|
||||
countdownEl.textContent = `${time}s`;
|
||||
} else {
|
||||
},
|
||||
() => {
|
||||
countdownEl.textContent = '重新发送';
|
||||
countdownEl.style.color = '#3474fe';
|
||||
countdownEl.style.cursor = 'pointer';
|
||||
countdownEl.onclick = () => this.resendSMS(countdownEl);
|
||||
clearInterval(this.countdownTimer);
|
||||
}
|
||||
}, 1000);
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -533,8 +538,8 @@ export class IndexPage {
|
||||
* 销毁页面
|
||||
*/
|
||||
destroy() {
|
||||
if (this.countdownTimer) {
|
||||
clearInterval(this.countdownTimer);
|
||||
if (this.countdownCancel) {
|
||||
this.countdownCancel();
|
||||
}
|
||||
|
||||
if (this.purposePicker) {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import { API_CONFIG } from '../config/api.config.js';
|
||||
import { ApiClient } from '../core/api.js';
|
||||
import { CacheManager } from '../core/cache-manager.js';
|
||||
|
||||
const CACHE_KEY_PREFIX = 'area_cache_';
|
||||
const CACHE_DURATION = 10 * 60 * 1000; // 10分钟
|
||||
@@ -19,44 +20,20 @@ export class AreaService {
|
||||
* @returns {Promise<Array>} 区域列表 [{code, name}]
|
||||
*/
|
||||
static async getAreaList(provincecode) {
|
||||
// 检查缓存
|
||||
const cacheKey = `${CACHE_KEY_PREFIX}${provincecode || 'all'}`;
|
||||
const cached = localStorage.getItem(cacheKey);
|
||||
|
||||
if (cached) {
|
||||
const { data, timestamp } = JSON.parse(cached);
|
||||
if (Date.now() - timestamp < CACHE_DURATION) {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
return CacheManager.getCachedOrFetch(cacheKey, async () => {
|
||||
const params = provincecode ? { provincecode } : {};
|
||||
console.log(`[AreaService] 请求区域数据 ${provincecode || 'all'}`);
|
||||
|
||||
try {
|
||||
// 构建请求参数
|
||||
const params = {};
|
||||
if (provincecode) {
|
||||
params.provincecode = provincecode;
|
||||
}
|
||||
|
||||
// 使用 ApiClient 发送请求
|
||||
const result = await ApiClient.get(API_CONFIG.ENDPOINTS.AREA_LIST, params);
|
||||
|
||||
if (result.retcode !== 0) {
|
||||
throw new Error(result.retmsg || '获取区域数据失败');
|
||||
}
|
||||
|
||||
const areaList = result.result || [];
|
||||
|
||||
// 保存到缓存
|
||||
localStorage.setItem(cacheKey, JSON.stringify({
|
||||
data: areaList,
|
||||
timestamp: Date.now()
|
||||
}));
|
||||
|
||||
return areaList;
|
||||
} catch (error) {
|
||||
console.error('[AreaService] 获取区域数据失败:', error);
|
||||
throw error;
|
||||
}
|
||||
return result.result || [];
|
||||
}, CACHE_DURATION);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,56 +57,48 @@ export class AreaService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除缓存
|
||||
* 根据省市名称查找代码
|
||||
* @param {string} provinceName - 省份名称
|
||||
* @param {string} cityName - 城市名称
|
||||
* @returns {Promise<Object|null>} - {provinceCode, cityCode},如果未找到则返回 null
|
||||
*/
|
||||
static clearCache() {
|
||||
Object.keys(localStorage)
|
||||
.filter(key => key.startsWith(CACHE_KEY_PREFIX))
|
||||
.forEach(key => localStorage.removeItem(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据地区代码(身份证前6位)查询地区信息
|
||||
* @param {string} areaCode - 地区代码(6位)
|
||||
* @returns {Promise<Object|null>} - 地区信息 {province, city, district, value},如果未找到则返回 null
|
||||
*/
|
||||
static async getAreaByCode(areaCode) {
|
||||
if (!areaCode || areaCode.length < 6) {
|
||||
static async findCityCode(provinceName, cityName) {
|
||||
if (!provinceName || !cityName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const provincecode = areaCode.substring(0, 2);
|
||||
const provinces = await this.getProvinces();
|
||||
const province = provinces.find(p => p.name === provinceName);
|
||||
|
||||
// 并行查询省列表和该省的市区列表
|
||||
const [provinces, areas] = await Promise.all([
|
||||
this.getProvinces(),
|
||||
this.getAreaList(provincecode)
|
||||
]);
|
||||
if (!province) {
|
||||
console.warn(`[AreaService] 未找到省份: ${provinceName}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 查找省、市、区
|
||||
const province = provinces.find(p => p.code === provincecode);
|
||||
const city = areas.find(a => a.code === areaCode.substring(0, 4));
|
||||
const district = areas.find(a => a.code === areaCode);
|
||||
const areas = await this.getCities(province.code);
|
||||
const city = areas.find(c => c.name === cityName && c.code.length === 4);
|
||||
|
||||
if (!city) {
|
||||
console.warn(`[AreaService] 未找到城市: ${provinceName}-${cityName}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 至少要有省份才返回
|
||||
if (province) {
|
||||
return {
|
||||
province: province.name,
|
||||
city: city ? city.name : '',
|
||||
district: district ? district.name : '',
|
||||
provinceCode: province.code,
|
||||
cityCode: city ? city.code : '',
|
||||
districtCode: district ? district.code : '',
|
||||
value: city ? `${province.name}/${city.name}` : province.name
|
||||
cityCode: city.code
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[AreaService] 查找城市代码失败:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('[AreaService] 根据代码查询地区失败:', error);
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* 清除缓存
|
||||
*/
|
||||
static clearCache() {
|
||||
CacheManager.clearCacheByPrefix(CACHE_KEY_PREFIX);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
231
src/js/services/auth-flow.service.js
Normal file
231
src/js/services/auth-flow.service.js
Normal file
@@ -0,0 +1,231 @@
|
||||
/**
|
||||
* 授权流程服务
|
||||
* 处理多个 H5 授权页面的逐个加载、轮询检测、超时控制和最终跳转
|
||||
*/
|
||||
|
||||
import { ApiClient } from '../core/api.js';
|
||||
import { API_CONFIG } from '../config/index.js';
|
||||
|
||||
// 授权状态常量
|
||||
const AUTH_STATUS = {
|
||||
WAITING: 1, // 等待授权
|
||||
CALLBACK: 2, // 已回调
|
||||
APPLY_OK: 3, // 进件成功
|
||||
APPLY_FAIL: 4 // 进件失败
|
||||
};
|
||||
|
||||
export class AuthFlowService {
|
||||
// 配置常量
|
||||
static POLL_INTERVAL = 3000; // 轮询间隔:3秒
|
||||
static POLL_TIMEOUT = 10 * 60 * 1000; // 超时时间:10分钟
|
||||
static COUNTDOWN_SECONDS = 5; // 倒计时:5秒
|
||||
|
||||
/**
|
||||
* 检查单个 API 的授权状态
|
||||
* @param {number} formdataid - 表单数据ID
|
||||
* @param {string} apicode - API编码
|
||||
* @returns {Promise<Object>} - 状态信息 { apicode, apiname, status, h5url }
|
||||
*/
|
||||
static async checkAuthStatus(formdataid, apicode) {
|
||||
try {
|
||||
const response = await ApiClient.xpost(API_CONFIG.ENDPOINTS.CHECK_AUTH_STATUS, {
|
||||
formdataid,
|
||||
apicode
|
||||
});
|
||||
|
||||
if (response.retcode === 0 && response.result) {
|
||||
return response.result;
|
||||
}
|
||||
|
||||
console.warn('[AuthFlowService] 检查授权状态失败:', response.retinfo);
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('[AuthFlowService] 检查授权状态出错:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 等待授权完成(轮询)
|
||||
* @param {number} formdataid - 表单数据ID
|
||||
* @param {string} apicode - API编码
|
||||
* @param {Function} onStatusChange - 状态变化回调
|
||||
* @returns {Promise<Object>} - { success: boolean, status: number, timeout: boolean }
|
||||
*/
|
||||
static async waitForAuth(formdataid, apicode, onStatusChange) {
|
||||
const startTime = Date.now();
|
||||
|
||||
while (true) {
|
||||
// 检查超时
|
||||
if (Date.now() - startTime > this.POLL_TIMEOUT) {
|
||||
console.log('[AuthFlowService] 轮询超时:', apicode);
|
||||
return { success: false, status: AUTH_STATUS.WAITING, timeout: true };
|
||||
}
|
||||
|
||||
// 查询状态
|
||||
const result = await this.checkAuthStatus(formdataid, apicode);
|
||||
|
||||
if (result) {
|
||||
// 通知状态变化
|
||||
if (onStatusChange) {
|
||||
onStatusChange(result);
|
||||
}
|
||||
|
||||
// 状态 >= 2 表示已回调/成功/失败,可以进入下一个
|
||||
if (result.status >= AUTH_STATUS.CALLBACK) {
|
||||
return {
|
||||
success: result.status === AUTH_STATUS.CALLBACK || result.status === AUTH_STATUS.APPLY_OK,
|
||||
status: result.status,
|
||||
timeout: false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 等待下次轮询
|
||||
await this.sleep(this.POLL_INTERVAL);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行授权流程
|
||||
* @param {number} formdataid - 表单数据ID
|
||||
* @param {Array} h5Urls - H5 URL 列表 [{ apicode, apiname, h5url }]
|
||||
* @param {Object} callbacks - 回调函数集合
|
||||
* - onStart(item, index) - 开始处理某个产品
|
||||
* - onProgress(item, index, status) - 状态更新
|
||||
* - onComplete(item, index, result) - 单个产品完成
|
||||
* - onAllComplete() - 全部完成
|
||||
* - onIframeChange(url) - iframe 需要切换 URL
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async startAuthFlow(formdataid, h5Urls, callbacks = {}) {
|
||||
const { onStart, onProgress, onComplete, onAllComplete, onIframeChange } = callbacks;
|
||||
|
||||
console.log('[AuthFlowService] 开始授权流程, formdataid:', formdataid, 'h5Urls:', h5Urls);
|
||||
|
||||
for (let i = 0; i < h5Urls.length; i++) {
|
||||
const item = h5Urls[i];
|
||||
console.log('[AuthFlowService] 处理第', i + 1, '个产品:', item.apiname);
|
||||
|
||||
// 通知开始处理
|
||||
if (onStart) {
|
||||
onStart(item, i);
|
||||
}
|
||||
|
||||
// 切换 iframe 或新窗口
|
||||
if (onIframeChange && item.h5url) {
|
||||
const targetUrl = item.h5url;
|
||||
|
||||
// 检查是否存在 Mixed Content 问题(HTTPS 页面加载 HTTP 内容)
|
||||
const isMixedContent = window.location.protocol === 'https:' && targetUrl.startsWith('http://');
|
||||
|
||||
if (isMixedContent) {
|
||||
// HTTPS 页面无法在 iframe 中加载 HTTP 内容,改用新窗口打开
|
||||
console.log('[AuthFlowService] 检测到 Mixed Content,使用新窗口打开:', targetUrl);
|
||||
onIframeChange(targetUrl, { useNewWindow: true });
|
||||
} else {
|
||||
onIframeChange(targetUrl);
|
||||
}
|
||||
}
|
||||
|
||||
// 轮询等待授权完成
|
||||
const result = await this.waitForAuth(formdataid, item.apicode, (status) => {
|
||||
if (onProgress) {
|
||||
onProgress(item, i, status);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('[AuthFlowService] 产品授权结果:', item.apiname, result);
|
||||
|
||||
// 通知单个完成
|
||||
if (onComplete) {
|
||||
onComplete(item, i, result);
|
||||
}
|
||||
}
|
||||
|
||||
// 全部完成
|
||||
console.log('[AuthFlowService] 全部授权流程完成');
|
||||
if (onAllComplete) {
|
||||
onAllComplete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始倒计时
|
||||
* @param {number} seconds - 倒计时秒数
|
||||
* @param {Function} onTick - 每秒回调,参数为剩余秒数
|
||||
* @param {Function} onComplete - 倒计时完成回调
|
||||
* @returns {Function} - 取消函数
|
||||
*/
|
||||
static startCountdown(seconds, onTick, onComplete) {
|
||||
let remaining = seconds;
|
||||
let cancelled = false;
|
||||
|
||||
const tick = () => {
|
||||
if (cancelled) return;
|
||||
|
||||
if (onTick) {
|
||||
onTick(remaining);
|
||||
}
|
||||
|
||||
if (remaining <= 0) {
|
||||
if (onComplete) {
|
||||
onComplete();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
remaining--;
|
||||
setTimeout(tick, 1000);
|
||||
};
|
||||
|
||||
tick();
|
||||
|
||||
// 返回取消函数
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转到目标页面
|
||||
* @param {string} url - 目标 URL
|
||||
*/
|
||||
static redirect(url) {
|
||||
if (url) {
|
||||
window.location.href = url;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 辅助函数:延时
|
||||
* @param {number} ms - 毫秒
|
||||
* @returns {Promise}
|
||||
*/
|
||||
static sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取状态文本
|
||||
* @param {number} status - 状态码
|
||||
* @returns {string}
|
||||
*/
|
||||
static getStatusText(status) {
|
||||
switch (status) {
|
||||
case AUTH_STATUS.WAITING:
|
||||
return '等待授权';
|
||||
case AUTH_STATUS.CALLBACK:
|
||||
return '已完成';
|
||||
case AUTH_STATUS.APPLY_OK:
|
||||
return '授权成功';
|
||||
case AUTH_STATUS.APPLY_FAIL:
|
||||
return '授权失败';
|
||||
default:
|
||||
return '未知状态';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导出状态常量
|
||||
export { AUTH_STATUS };
|
||||
@@ -11,32 +11,40 @@ export class AuthService {
|
||||
/**
|
||||
* 客户注册或登录
|
||||
* @param {string} phone - 手机号
|
||||
* @param {Object} loanData - 借款数据
|
||||
* @param {number} loanData.loanamount - 借款金额
|
||||
* @param {number} loanData.repaymentperiod - 还款期数
|
||||
* @param {string} loanData.loanpurpose - 借款用途
|
||||
* @param {Object} loanData - 借款数据(支持 amount/period/purpose 或 loanamount/repaymentperiod/loanpurpose)
|
||||
* @param {number} [loanData.amount] - 借款金额(与 loanamount 二选一)
|
||||
* @param {number} [loanData.period] - 还款期数(与 repaymentperiod 二选一)
|
||||
* @param {string} [loanData.purpose] - 借款用途(与 loanpurpose 二选一)
|
||||
* @returns {Promise<{success: boolean, message: string, data: Object}>} - 注册/登录结果
|
||||
*/
|
||||
static async registerOrLogin(phone, loanData) {
|
||||
try {
|
||||
const response = await ApiClient.xpost(API_CONFIG.ENDPOINTS.CUSTOMER_REGISTER, {
|
||||
const loanamount = Number(loanData?.loanamount ?? loanData?.amount ?? 0) || 0;
|
||||
const repaymentperiod = Number(loanData?.repaymentperiod ?? loanData?.period ?? 0) || 0;
|
||||
const loanpurpose = (typeof (loanData?.loanpurpose ?? loanData?.purpose) === 'string')
|
||||
? (loanData.loanpurpose ?? loanData.purpose).trim()
|
||||
: '';
|
||||
|
||||
const payload = {
|
||||
mobile: phone,
|
||||
loanamount: loanData.loanamount,
|
||||
repaymentperiod: loanData.repaymentperiod,
|
||||
loanpurpose: loanData.loanpurpose
|
||||
});
|
||||
loanamount,
|
||||
repaymentperiod,
|
||||
loanpurpose
|
||||
};
|
||||
|
||||
const response = await ApiClient.xpost(API_CONFIG.ENDPOINTS.CUSTOMER_REGISTER, payload);
|
||||
|
||||
if (response.retcode === 0) {
|
||||
console.log('[AuthService] 后端返回数据:', response.result);
|
||||
|
||||
// 保存用户登录状态到缓存
|
||||
// 保存用户登录状态到缓存(formData 使用与接口一致的字段名,供 basic-info 恢复用)
|
||||
const userData = {
|
||||
...response.result,
|
||||
customerid: response.result.customerid || response.result.customerId,
|
||||
loginPhone: phone,
|
||||
sessionid: response.result.sessionid || response.result.sessionId,
|
||||
mobile: response.result.mobile,
|
||||
formData: loanData
|
||||
formData: { loanamount, repaymentperiod, loanpurpose }
|
||||
};
|
||||
|
||||
console.log('[AuthService] 保存用户数据:', userData);
|
||||
|
||||
@@ -7,3 +7,4 @@ export { AuthService } from './auth.service.js';
|
||||
export { LoanService } from './loan.service.js';
|
||||
export { FormService } from './form.service.js';
|
||||
export { JVerifyService, getJVerifyService } from './jverify.service.js';
|
||||
export { AuthFlowService, AUTH_STATUS } from './auth-flow.service.js';
|
||||
|
||||
@@ -336,10 +336,10 @@ export class JVerifyService {
|
||||
// 根据当前页面主题设置UI
|
||||
const uiConfig = {
|
||||
// Logo设置(尺寸建议:弹窗模式 60x60)
|
||||
logo: 'https://via.placeholder.com/60x60/667eea/ffffff?text=薇', // 替换为实际logo
|
||||
logo: 'https://via.placeholder.com/60x60/667eea/ffffff?text=百雅融', // 替换为实际logo
|
||||
|
||||
// 应用名称(最多15个字符)
|
||||
appName: '薇钱包',
|
||||
appName: '百雅融',
|
||||
|
||||
// 登录按钮颜色(与自定义按钮保持一致)
|
||||
loginBtnColor: '#667eea',
|
||||
|
||||
124
src/js/services/location.service.js
Normal file
124
src/js/services/location.service.js
Normal file
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* 定位服务
|
||||
* 提供全局 IP 定位功能,避免重复请求
|
||||
*/
|
||||
|
||||
import { API_CONFIG } from '../config/api.config.js';
|
||||
import { ApiClient } from '../core/api.js';
|
||||
|
||||
/**
|
||||
* 定位服务(单例模式)
|
||||
*/
|
||||
export class LocationService {
|
||||
static instance = null;
|
||||
|
||||
// 定位缓存
|
||||
locationCache = null;
|
||||
|
||||
// 正在定位的 Promise
|
||||
locatingPromise = null;
|
||||
|
||||
// 缓存有效期(10分钟)
|
||||
CACHE_DURATION = 10 * 60 * 1000;
|
||||
|
||||
/**
|
||||
* 获取单例实例
|
||||
*/
|
||||
static getInstance() {
|
||||
if (!this.instance) {
|
||||
this.instance = new LocationService();
|
||||
}
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前定位(带缓存)
|
||||
* @returns {Promise<Object>} 定位结果 {province, city, code}
|
||||
*/
|
||||
async getLocation() {
|
||||
// 如果有缓存且未过期,直接返回
|
||||
if (this.locationCache) {
|
||||
const { data, timestamp } = this.locationCache;
|
||||
if (Date.now() - timestamp < this.CACHE_DURATION) {
|
||||
console.log('[LocationService] 使用缓存定位:', data);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果正在定位,返回同一个 Promise
|
||||
if (this.locatingPromise) {
|
||||
console.log('[LocationService] 定位进行中,等待结果...');
|
||||
return this.locatingPromise;
|
||||
}
|
||||
|
||||
// 开始定位
|
||||
this.locatingPromise = this.doLocation();
|
||||
|
||||
try {
|
||||
const result = await this.locatingPromise;
|
||||
|
||||
// 缓存结果
|
||||
this.locationCache = {
|
||||
data: result,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
return result;
|
||||
} finally {
|
||||
// 清除定位 Promise
|
||||
this.locatingPromise = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行 IP 定位
|
||||
* @private
|
||||
*/
|
||||
async doLocation() {
|
||||
try {
|
||||
console.log('[LocationService] 开始 IP 定位...');
|
||||
const result = await ApiClient.get(API_CONFIG.ENDPOINTS.IP_LOCATION);
|
||||
|
||||
if (result.retcode === 0 && result.result) {
|
||||
const { province, city } = result.result;
|
||||
|
||||
if (province && city) {
|
||||
console.log('[LocationService] IP 定位成功:', province, city);
|
||||
return {
|
||||
province,
|
||||
city,
|
||||
code: null // 稍后可以添加城市代码
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('定位失败:未获取到省市信息');
|
||||
} catch (error) {
|
||||
console.error('[LocationService] IP 定位失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除缓存
|
||||
*/
|
||||
clearCache() {
|
||||
this.locationCache = null;
|
||||
console.log('[LocationService] 缓存已清除');
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置定位结果(手动设置)
|
||||
* @param {Object} location 定位结果 {province, city, code}
|
||||
*/
|
||||
setLocation(location) {
|
||||
this.locationCache = {
|
||||
data: location,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
console.log('[LocationService] 手动设置定位:', location);
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例获取函数
|
||||
export const getLocationService = () => LocationService.getInstance();
|
||||
@@ -1,9 +1,31 @@
|
||||
/**
|
||||
* 城市选择器组件
|
||||
* 提供省份和城市的联动选择功能
|
||||
* 提供省份和城市的联动选择功能,支持热门城市快速选择和定位
|
||||
*/
|
||||
|
||||
import {AreaService} from '../services/area.service.js';
|
||||
import {getLocationService} from '../services/location.service.js';
|
||||
import {API_CONFIG} from '../config/api.config.js';
|
||||
import {ApiClient} from '../core/api.js';
|
||||
|
||||
// 热门城市列表(12个核心城市)
|
||||
const HOT_CITIES = [
|
||||
{name: '北京市', province: '北京市', code: '1100'},
|
||||
{name: '上海市', province: '上海市', code: '3100'},
|
||||
{name: '广州市', province: '广东省', code: '4401'},
|
||||
{name: '深圳市', province: '广东省', code: '4403'},
|
||||
{name: '成都市', province: '四川省', code: '5101'},
|
||||
{name: '杭州市', province: '浙江省', code: '3301'},
|
||||
{name: '重庆市', province: '重庆市', code: '5000'},
|
||||
{name: '武汉市', province: '湖北省', code: '4201'},
|
||||
{name: '西安市', province: '陕西省', code: '6101'},
|
||||
{name: '南京市', province: '江苏省', code: '3201'},
|
||||
{name: '苏州市', province: '江苏省', code: '3205'},
|
||||
{name: '天津市', province: '天津市', code: '1200'}
|
||||
];
|
||||
|
||||
// 直辖市列表(省和市同名)
|
||||
const MUNICIPALITIES = ['北京市', '上海市', '天津市', '重庆市'];
|
||||
|
||||
export class CityPicker {
|
||||
/**
|
||||
@@ -12,6 +34,7 @@ export class CityPicker {
|
||||
* @param {string} options.modalId - 模态框元素ID
|
||||
* @param {string} options.provinceColumnId - 省份列元素ID
|
||||
* @param {string} options.cityColumnId - 城市列元素ID
|
||||
* @param {string} options.hotCitiesId - 热门城市区域元素ID
|
||||
* @param {string} options.cancelBtnId - 取消按钮ID
|
||||
* @param {string} options.confirmBtnId - 确认按钮ID
|
||||
* @param {Function} options.onConfirm - 确认回调
|
||||
@@ -20,6 +43,7 @@ export class CityPicker {
|
||||
this.modal = document.getElementById(options.modalId);
|
||||
this.provinceColumn = document.getElementById(options.provinceColumnId);
|
||||
this.cityColumn = document.getElementById(options.cityColumnId);
|
||||
this.hotCitiesArea = document.getElementById(options.hotCitiesId);
|
||||
this.cancelBtn = document.getElementById(options.cancelBtnId);
|
||||
this.confirmBtn = document.getElementById(options.confirmBtnId);
|
||||
this.onConfirm = options.onConfirm || null;
|
||||
@@ -37,6 +61,11 @@ export class CityPicker {
|
||||
// 数据缓存
|
||||
this.provinces = [];
|
||||
this.cities = [];
|
||||
this.hotCities = HOT_CITIES;
|
||||
|
||||
// 定位相关
|
||||
this.currentLocationCity = null; // 当前定位城市
|
||||
this.isLocating = false; // 是否正在定位
|
||||
|
||||
if (!this.modal) {
|
||||
console.error(`[CityPicker] 找不到模态框元素: ${options.modalId}`);
|
||||
@@ -68,6 +97,154 @@ export class CityPicker {
|
||||
|
||||
// 预加载省份数据
|
||||
this.loadProvinces();
|
||||
|
||||
// 获取定位
|
||||
this.getCurrentLocation();
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染定位标签
|
||||
*/
|
||||
renderLocationBadge() {
|
||||
const locationSection = document.getElementById('locationSection');
|
||||
if (!locationSection) return;
|
||||
|
||||
locationSection.innerHTML = '';
|
||||
|
||||
if (this.currentLocationCity) {
|
||||
// 已定位,显示当前城市
|
||||
const badge = document.createElement('div');
|
||||
badge.className = 'location-badge';
|
||||
badge.innerHTML = `
|
||||
<span class="location-icon">📍</span>
|
||||
<span class="location-text">${this.currentLocationCity.name}</span>
|
||||
`;
|
||||
|
||||
badge.addEventListener('click', async () => {
|
||||
await this.selectHotCity(this.currentLocationCity);
|
||||
});
|
||||
|
||||
locationSection.appendChild(badge);
|
||||
} else if (this.isLocating) {
|
||||
// 正在定位
|
||||
const badge = document.createElement('div');
|
||||
badge.className = 'location-badge locating';
|
||||
badge.innerHTML = `
|
||||
<span class="location-icon">⌛</span>
|
||||
<span class="location-text">定位中...</span>
|
||||
`;
|
||||
locationSection.appendChild(badge);
|
||||
} else {
|
||||
// 未定位,显示定位按钮
|
||||
const badge = document.createElement('div');
|
||||
badge.className = 'location-badge';
|
||||
badge.innerHTML = `
|
||||
<span class="location-icon">📍</span>
|
||||
<span class="location-text">获取定位</span>
|
||||
`;
|
||||
|
||||
badge.addEventListener('click', () => {
|
||||
this.getCurrentLocation();
|
||||
});
|
||||
|
||||
locationSection.appendChild(badge);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染热门城市区域
|
||||
*/
|
||||
renderHotCities() {
|
||||
// 渲染定位标签
|
||||
this.renderLocationBadge();
|
||||
|
||||
if (!this.hotCitiesArea) return;
|
||||
|
||||
this.hotCitiesArea.innerHTML = '';
|
||||
|
||||
// 渲染热门城市列表
|
||||
this.hotCities.forEach(city => {
|
||||
const cityTag = document.createElement('div');
|
||||
cityTag.className = 'city-tag';
|
||||
cityTag.textContent = city.name;
|
||||
cityTag.dataset.cityName = city.name;
|
||||
cityTag.dataset.provinceName = city.province;
|
||||
cityTag.dataset.cityCode = city.code;
|
||||
cityTag.dataset.provinceCode = city.code.substring(0, 2) + '00';
|
||||
|
||||
// 添加淡入动画
|
||||
cityTag.style.animationDelay = `${Math.floor(Math.random() * 100)}ms`;
|
||||
|
||||
// 单击选中热门城市
|
||||
cityTag.addEventListener('click', async () => {
|
||||
await this.selectHotCity(city);
|
||||
});
|
||||
|
||||
// 双击直接确认选择
|
||||
cityTag.addEventListener('dblclick', async () => {
|
||||
await this.selectHotCity(city);
|
||||
this.confirmSelection();
|
||||
});
|
||||
|
||||
this.hotCitiesArea.appendChild(cityTag);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择热门城市
|
||||
* @param {Object} city - 热门城市对象
|
||||
*/
|
||||
async selectHotCity(city) {
|
||||
const isMunicipality = MUNICIPALITIES.includes(city.province);
|
||||
|
||||
this.tempSelectedProvince = city.province;
|
||||
this.tempSelectedCity = city.name;
|
||||
this.tempSelectedProvinceCode = city.provinceCode || city.code.substring(0, 2) + '00';
|
||||
this.tempSelectedCityCode = city.code;
|
||||
|
||||
// 更新热门城市选中状态
|
||||
const tags = this.hotCitiesArea.querySelectorAll('.city-tag');
|
||||
tags.forEach(tag => {
|
||||
tag.classList.toggle('active', tag.dataset.cityCode === city.code);
|
||||
});
|
||||
|
||||
// 同步更新省份和城市的选中状态
|
||||
if (this.provinces.length === 0) {
|
||||
await this.loadProvinces();
|
||||
}
|
||||
|
||||
// 选中对应的省份
|
||||
const province = this.provinces.find(p => p.name === city.province || p.code === this.tempSelectedProvinceCode);
|
||||
if (province) {
|
||||
this.tempSelectedProvince = province.name;
|
||||
this.tempSelectedProvinceCode = province.code;
|
||||
|
||||
// 更新省份列表选中状态
|
||||
const provinceItems = this.provinceColumn.querySelectorAll('.province-item');
|
||||
provinceItems.forEach(item => {
|
||||
item.classList.toggle('active', item.dataset.provinceCode === province.code);
|
||||
});
|
||||
|
||||
// 加载并渲染城市列表
|
||||
await this.loadCities(province.code);
|
||||
|
||||
// 选中对应的城市
|
||||
const cityItems = this.cityColumn.querySelectorAll('.city-item');
|
||||
if (isMunicipality) {
|
||||
// 直辖市:自动选中唯一选项(省份名称)
|
||||
if (cityItems.length > 0) {
|
||||
cityItems[0].classList.add('active');
|
||||
}
|
||||
} else {
|
||||
// 普通城市:选中对应的城市
|
||||
cityItems.forEach(item => {
|
||||
item.classList.toggle('active', item.dataset.cityCode === city.code);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 更新确认按钮状态
|
||||
this.updateConfirmButtonState();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,13 +267,16 @@ export class CityPicker {
|
||||
|
||||
this.provinceColumn.innerHTML = '';
|
||||
|
||||
this.provinces.forEach(province => {
|
||||
this.provinces.forEach((province, index) => {
|
||||
const provinceItem = document.createElement('div');
|
||||
provinceItem.className = 'city-picker-item';
|
||||
provinceItem.className = 'province-item';
|
||||
provinceItem.textContent = province.name;
|
||||
provinceItem.dataset.province = province.name;
|
||||
provinceItem.dataset.provinceCode = province.code;
|
||||
|
||||
// 添加淡入动画
|
||||
provinceItem.style.animation = `fadeIn 0.3s ease ${index * 20}ms backwards`;
|
||||
|
||||
provinceItem.addEventListener('click', () => {
|
||||
this.selectProvince(province.name, province.code);
|
||||
});
|
||||
@@ -115,13 +295,20 @@ export class CityPicker {
|
||||
this.tempSelectedProvinceCode = provinceCode;
|
||||
|
||||
// 更新省份选中状态
|
||||
const items = this.provinceColumn.querySelectorAll('.city-picker-item');
|
||||
const items = this.provinceColumn.querySelectorAll('.province-item');
|
||||
items.forEach(item => {
|
||||
item.classList.toggle('active', item.dataset.provinceCode === provinceCode);
|
||||
});
|
||||
|
||||
// 清除城市选中状态(切换省份时)
|
||||
this.tempSelectedCity = '';
|
||||
this.tempSelectedCityCode = '';
|
||||
|
||||
// 加载并渲染城市列表
|
||||
await this.loadCities(provinceCode);
|
||||
|
||||
// 更新确认按钮状态
|
||||
this.updateConfirmButtonState();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -146,20 +333,48 @@ export class CityPicker {
|
||||
|
||||
this.cityColumn.innerHTML = '';
|
||||
|
||||
// 只显示市(4位),不显示区(6位)
|
||||
const cities = this.cities.filter(area => area.code.length === 4);
|
||||
// 判断是否为直辖市
|
||||
const isMunicipality = MUNICIPALITIES.includes(this.tempSelectedProvince);
|
||||
|
||||
cities.forEach(city => {
|
||||
let cities;
|
||||
if (isMunicipality) {
|
||||
// 直辖市:直接显示省份名称作为唯一的城市选项
|
||||
// 这样用户选择后返回的就是"北京市"、"上海市"等
|
||||
cities = [{
|
||||
name: this.tempSelectedProvince,
|
||||
code: this.tempSelectedProvinceCode
|
||||
}];
|
||||
} else {
|
||||
// 普通省份:显示市级数据(4位代码),排除"市辖区"
|
||||
cities = this.cities.filter(area =>
|
||||
area.code.length === 4 &&
|
||||
area.name !== '市辖区'
|
||||
);
|
||||
}
|
||||
|
||||
cities.forEach((city, index) => {
|
||||
const cityItem = document.createElement('div');
|
||||
cityItem.className = 'city-picker-item';
|
||||
cityItem.className = 'city-item';
|
||||
cityItem.textContent = city.name;
|
||||
cityItem.dataset.city = city.name;
|
||||
cityItem.dataset.cityCode = city.code;
|
||||
// 添加 title 属性,鼠标悬停时显示完整城市名称
|
||||
cityItem.title = city.name;
|
||||
|
||||
// 添加淡入动画
|
||||
cityItem.style.animation = `fadeIn 0.3s ease ${index * 15}ms backwards`;
|
||||
|
||||
// 单击选中城市
|
||||
cityItem.addEventListener('click', () => {
|
||||
this.selectCity(city.name, city.code);
|
||||
});
|
||||
|
||||
// 双击直接确认选择
|
||||
cityItem.addEventListener('dblclick', () => {
|
||||
this.selectCity(city.name, city.code);
|
||||
this.confirmSelection();
|
||||
});
|
||||
|
||||
this.cityColumn.appendChild(cityItem);
|
||||
});
|
||||
|
||||
@@ -168,12 +383,11 @@ export class CityPicker {
|
||||
const existingCity = cities.find(c => c.code === this.tempSelectedCityCode);
|
||||
if (existingCity) {
|
||||
this.selectCity(existingCity.name, existingCity.code);
|
||||
} else if (cities.length > 0) {
|
||||
// 默认选择第一个
|
||||
this.selectCity(cities[0].name, cities[0].code);
|
||||
}
|
||||
} else if (cities.length > 0) {
|
||||
// 默认选择第一个
|
||||
}
|
||||
|
||||
// 对于直辖市,自动选中唯一的选项
|
||||
if (isMunicipality && cities.length === 1 && !this.tempSelectedCityCode) {
|
||||
this.selectCity(cities[0].name, cities[0].code);
|
||||
}
|
||||
}
|
||||
@@ -188,35 +402,59 @@ export class CityPicker {
|
||||
this.tempSelectedCityCode = cityCode;
|
||||
|
||||
// 更新城市选中状态
|
||||
const items = this.cityColumn.querySelectorAll('.city-picker-item');
|
||||
const items = this.cityColumn.querySelectorAll('.city-item');
|
||||
items.forEach(item => {
|
||||
item.classList.toggle('active', item.dataset.cityCode === cityCode);
|
||||
});
|
||||
|
||||
// 更新热门城市选中状态
|
||||
this.updateHotCitiesSelection();
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开选择器
|
||||
* @param {string} currentValue - 当前值(格式:"省/市")
|
||||
* @param {string} currentValue - 当前值(格式:"市名" 或 "省/市")
|
||||
*/
|
||||
async open(currentValue = '') {
|
||||
// 解析当前值,并查找对应的代码
|
||||
if (currentValue) {
|
||||
const parts = currentValue.split('/');
|
||||
if (parts.length === 2) {
|
||||
const provinceName = parts[0];
|
||||
const cityName = parts[1];
|
||||
let cityName = '';
|
||||
let provinceName = '';
|
||||
|
||||
// 支持两种格式:纯市名 "北京市" 或 省市格式 "北京市/北京市"
|
||||
if (currentValue.includes('/')) {
|
||||
const parts = currentValue.split('/');
|
||||
provinceName = parts[0];
|
||||
cityName = parts[1];
|
||||
} else {
|
||||
cityName = currentValue;
|
||||
}
|
||||
|
||||
// 先在热门城市中查找
|
||||
const hotCity = this.hotCities.find(c => c.name === cityName);
|
||||
if (hotCity) {
|
||||
this.tempSelectedProvince = hotCity.province;
|
||||
this.tempSelectedCity = hotCity.name;
|
||||
this.tempSelectedProvinceCode = hotCity.code.substring(0, 2) + '00';
|
||||
this.tempSelectedCityCode = hotCity.code;
|
||||
} else {
|
||||
// 查找省份代码
|
||||
if (this.provinces.length === 0) {
|
||||
await this.loadProvinces();
|
||||
}
|
||||
|
||||
// 如果有省份名,直接查找
|
||||
if (provinceName) {
|
||||
const province = this.provinces.find(p => p.name === provinceName);
|
||||
if (province) {
|
||||
this.tempSelectedProvince = provinceName;
|
||||
this.tempSelectedProvinceCode = province.code;
|
||||
}
|
||||
}
|
||||
|
||||
// 加载城市数据并查找城市代码
|
||||
await this.loadCities(province.code);
|
||||
// 如果有省代码,加载城市数据并查找城市代码
|
||||
if (this.tempSelectedProvinceCode) {
|
||||
await this.loadCities(this.tempSelectedProvinceCode);
|
||||
const city = this.cities.find(c => c.name === cityName && c.code.length === 4);
|
||||
if (city) {
|
||||
this.tempSelectedCity = cityName;
|
||||
@@ -238,16 +476,51 @@ export class CityPicker {
|
||||
}
|
||||
|
||||
// 渲染列表
|
||||
this.renderLocationBadge(); // 渲染定位标签
|
||||
this.renderHotCities();
|
||||
this.renderProvinceList();
|
||||
|
||||
// 更新热门城市选中状态
|
||||
this.updateHotCitiesSelection();
|
||||
|
||||
if (this.tempSelectedProvinceCode) {
|
||||
await this.selectProvince(this.tempSelectedProvince, this.tempSelectedProvinceCode);
|
||||
}
|
||||
|
||||
// 更新确认按钮状态
|
||||
this.updateConfirmButtonState();
|
||||
|
||||
// 显示模态框
|
||||
document.body.classList.add('modal-open');
|
||||
this.modal.classList.add('show');
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新热门城市选中状态
|
||||
*/
|
||||
updateHotCitiesSelection() {
|
||||
if (!this.hotCitiesArea) return;
|
||||
|
||||
const tags = this.hotCitiesArea.querySelectorAll('.city-tag');
|
||||
tags.forEach(tag => {
|
||||
const isSelected = this.tempSelectedCityCode && tag.dataset.cityCode === this.tempSelectedCityCode;
|
||||
tag.classList.toggle('active', isSelected);
|
||||
});
|
||||
|
||||
// 更新确认按钮状态
|
||||
this.updateConfirmButtonState();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新确认按钮状态
|
||||
*/
|
||||
updateConfirmButtonState() {
|
||||
if (this.confirmBtn) {
|
||||
const hasSelection = this.tempSelectedProvince && this.tempSelectedCity;
|
||||
this.confirmBtn.classList.toggle('has-selection', hasSelection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭选择器
|
||||
*/
|
||||
@@ -274,7 +547,8 @@ export class CityPicker {
|
||||
this.selectedProvinceCode = this.tempSelectedProvinceCode;
|
||||
this.selectedCityCode = this.tempSelectedCityCode;
|
||||
|
||||
const cityValue = `${this.tempSelectedProvince}/${this.tempSelectedCity}`;
|
||||
// 只返回市名,如:北京市、武汉市、曲靖市
|
||||
const cityValue = this.tempSelectedCity;
|
||||
|
||||
if (this.onConfirm) {
|
||||
this.onConfirm({
|
||||
@@ -292,18 +566,38 @@ export class CityPicker {
|
||||
|
||||
/**
|
||||
* 设置选中值
|
||||
* @param {string} value - 值(格式:"省/市")
|
||||
* @param {string} value - 值(格式:"市名" 或 "省/市")
|
||||
*/
|
||||
setValue(value) {
|
||||
if (!value) return;
|
||||
|
||||
// 支持两种格式:纯市名或省/市格式
|
||||
if (value.includes('/')) {
|
||||
const parts = value.split('/');
|
||||
if (parts.length === 2) {
|
||||
this.selectedProvince = parts[0];
|
||||
this.selectedCity = parts[1];
|
||||
this.tempSelectedProvince = parts[0];
|
||||
this.tempSelectedCity = parts[1];
|
||||
// 代码需要通过查找获取,这里暂时留空
|
||||
}
|
||||
} else {
|
||||
// 纯市名,从热门城市中查找省份信息
|
||||
const hotCity = this.hotCities.find(c => c.name === value);
|
||||
if (hotCity) {
|
||||
const provinceCode = hotCity.code.substring(0, 2) + '00';
|
||||
this.selectedProvince = hotCity.province;
|
||||
this.selectedCity = hotCity.name;
|
||||
this.selectedProvinceCode = provinceCode;
|
||||
this.selectedCityCode = hotCity.code;
|
||||
this.tempSelectedProvince = hotCity.province;
|
||||
this.tempSelectedCity = hotCity.name;
|
||||
this.tempSelectedProvinceCode = provinceCode;
|
||||
this.tempSelectedCityCode = hotCity.code;
|
||||
} else {
|
||||
// 如果不在热门城市中,只记录城市名
|
||||
this.selectedCity = value;
|
||||
this.tempSelectedCity = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,7 +611,7 @@ export class CityPicker {
|
||||
city: this.selectedCity,
|
||||
provinceCode: this.selectedProvinceCode,
|
||||
cityCode: this.selectedCityCode,
|
||||
value: `${this.selectedProvince}/${this.selectedCity}`
|
||||
value: this.selectedCity // 只返回市名
|
||||
};
|
||||
}
|
||||
|
||||
@@ -344,4 +638,69 @@ export class CityPicker {
|
||||
this.provinceColumn = null;
|
||||
this.cityColumn = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前定位(使用IP定位)
|
||||
*/
|
||||
async getCurrentLocation() {
|
||||
// 如果已经定位过,不再重复定位
|
||||
if (this.currentLocationCity) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果正在定位,避免重复请求
|
||||
if (this.isLocating) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isLocating = true;
|
||||
|
||||
// 如果定位区域已渲染,更新状态显示"定位中"
|
||||
const locationSection = document.getElementById('locationSection');
|
||||
if (locationSection) {
|
||||
this.renderLocationBadge();
|
||||
}
|
||||
|
||||
try {
|
||||
// 使用全局定位服务
|
||||
const locationService = getLocationService();
|
||||
const location = await locationService.getLocation();
|
||||
|
||||
if (location) {
|
||||
const {province, city} = location;
|
||||
console.log('[CityPicker] IP定位成功:', province, city);
|
||||
|
||||
// 查找城市代码
|
||||
const cityCode = await AreaService.findCityCode(province, city);
|
||||
|
||||
if (cityCode) {
|
||||
this.currentLocationCity = {
|
||||
name: city,
|
||||
province: province,
|
||||
code: cityCode.cityCode,
|
||||
provinceCode: cityCode.provinceCode
|
||||
};
|
||||
console.log('[CityPicker] 定位成功:', this.currentLocationCity.name);
|
||||
} else {
|
||||
// 如果找不到代码,返回基本信息(代码为空)
|
||||
this.currentLocationCity = {
|
||||
name: city,
|
||||
province: province,
|
||||
code: '',
|
||||
provinceCode: ''
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[CityPicker] IP定位失败:', error);
|
||||
// 定位失败时不显示错误,静默处理
|
||||
} finally {
|
||||
this.isLocating = false;
|
||||
// 更新UI显示
|
||||
const locationSection = document.getElementById('locationSection');
|
||||
if (locationSection) {
|
||||
this.renderLocationBadge();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,34 +4,15 @@
|
||||
*/
|
||||
|
||||
import { getJVerifyService } from '../services/index.js';
|
||||
import { showToast } from './toast.js';
|
||||
import { API_CONFIG } from '../config/api.config.js';
|
||||
|
||||
/**
|
||||
* 简单的 Toast 提示工具
|
||||
* @param {string} message - 提示消息
|
||||
* @param {number} duration - 持续时间(毫秒)
|
||||
*/
|
||||
function showToast(message, duration = 2000) {
|
||||
// 移除现有的 toast
|
||||
const existingToast = document.querySelector('.one-click-toast');
|
||||
if (existingToast) {
|
||||
existingToast.remove();
|
||||
}
|
||||
|
||||
// 创建新的 toast
|
||||
const toast = document.createElement('div');
|
||||
toast.className = 'one-click-toast';
|
||||
toast.textContent = message;
|
||||
document.body.appendChild(toast);
|
||||
|
||||
// 触发动画
|
||||
setTimeout(() => toast.classList.add('show'), 10);
|
||||
|
||||
// 自动移除
|
||||
setTimeout(() => {
|
||||
toast.classList.remove('show');
|
||||
setTimeout(() => toast.remove(), 300);
|
||||
}, duration);
|
||||
}
|
||||
// 极光一键登录错误码
|
||||
const JVERIFY_ERROR_CODES = {
|
||||
AUTH_FAILED: '2002', // 运营商授权失败
|
||||
TIMEOUT: '2006', // 登录超时
|
||||
NOT_SUPPORTED: 'not support' // 当前环境不支持
|
||||
};
|
||||
|
||||
export class OneClickLoginButton {
|
||||
/**
|
||||
@@ -178,7 +159,7 @@ export class OneClickLoginButton {
|
||||
this.setButtonState('verifying');
|
||||
|
||||
// 调用后端验证
|
||||
const response = await fetch('/zcore/jpush/login', {
|
||||
const response = await fetch(API_CONFIG.ENDPOINTS.JPUSH_LOGIN, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
@@ -206,11 +187,11 @@ export class OneClickLoginButton {
|
||||
// 判断错误类型,提供友好提示
|
||||
let message = '登录失败,请使用短信验证码登录';
|
||||
|
||||
if (error.message && error.message.includes('2002')) {
|
||||
if (error.message && error.message.includes(JVERIFY_ERROR_CODES.AUTH_FAILED)) {
|
||||
message = '运营商授权失败,请使用短信验证码登录';
|
||||
} else if (error.message && error.message.includes('2006')) {
|
||||
} else if (error.message && error.message.includes(JVERIFY_ERROR_CODES.TIMEOUT)) {
|
||||
message = '登录超时,请重试或使用短信验证码登录';
|
||||
} else if (error.message && error.message.includes('not support')) {
|
||||
} else if (error.message && error.message.includes(JVERIFY_ERROR_CODES.NOT_SUPPORTED)) {
|
||||
message = '当前环境不支持一键登录,请使用短信验证码登录';
|
||||
}
|
||||
|
||||
|
||||
@@ -96,26 +96,6 @@ export class Validator {
|
||||
return idCard[17].toUpperCase() === checkCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从身份证号提取地区代码
|
||||
* @param {string} idCard - 身份证号
|
||||
* @returns {string|null} - 地区代码(前6位),如果无效则返回 null
|
||||
*/
|
||||
static extractAreaCode(idCard) {
|
||||
if (!idCard) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const idCardStr = idCard.trim();
|
||||
|
||||
// 身份证号前6位是地区代码
|
||||
if (idCardStr.length >= 6) {
|
||||
return idCardStr.substring(0, 6);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证短信验证码
|
||||
* @param {string} code - 验证码
|
||||
|
||||
BIN
static/image.zip
Normal file
BIN
static/image.zip
Normal file
Binary file not shown.
791
功能说明.md
Normal file
791
功能说明.md
Normal file
@@ -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
|
||||
- **版权所有:** 北京百雅科技有限公司
|
||||
Reference in New Issue
Block a user