修改小程序,前端,官网跳转路径

This commit is contained in:
2025-09-28 18:01:25 +08:00
parent e79e5bb086
commit c429672517
102 changed files with 8653 additions and 544 deletions

View File

@@ -0,0 +1,177 @@
# 银行管理系统小程序 - 按钮跳转问题调试指南
## 当前问题状态
用户反馈:点击"新增商品"和"修改信息"按钮没有跳转到表单页面,但控制台有日志输出。
## 已完成的修复
### 1. 事件冲突修复 ✅
- 移除了商品项容器的`bindtap="viewProductDetail"`事件
- 为商品信息区域单独添加点击事件
- 移除了按钮的`catchtap="true"`属性
### 2. 按钮样式优化 ✅
- 增加按钮高度60rpx → 80rpx
- 增加字体大小24rpx → 26rpx
- 添加z-index确保按钮在最上层
- 增加最小宽度和间距
### 3. 调试信息添加 ✅
- 在按钮点击方法中添加console.log
- 添加跳转成功/失败的回调处理
- 添加详细的错误信息显示
## 当前测试方案
### 测试页面创建
创建了一个简化的测试页面 `test-form` 来验证跳转功能:
**文件结构:**
```
pages/business/loan-products/
├── test-form.wxml # 测试页面模板
├── test-form.js # 测试页面逻辑
└── test-form.wxss # 测试页面样式
```
**测试页面功能:**
- 显示传入的参数mode, productId
- 提供返回按钮
- 简单的页面结构,便于调试
### 当前跳转配置
```javascript
// 新增商品按钮 - 跳转到测试页面
wx.navigateTo({
url: '/pages/business/loan-products/test-form?mode=add',
success: (res) => {
console.log('跳转到测试页面成功', res);
},
fail: (err) => {
console.error('跳转到测试页面失败', err);
// 显示详细错误信息
}
});
```
## 测试步骤
### 1. 基础跳转测试
1. 点击"新增商品"按钮
2. 查看控制台日志:
- 应该看到:"点击新增商品按钮"
- 应该看到:"跳转到测试页面成功" 或 "跳转到测试页面失败"
3. 如果跳转成功,应该看到测试页面显示:
- 标题:"测试表单页面"
- 模式:"add"
- 商品ID"无"
### 2. 错误诊断
如果跳转失败,查看错误信息:
- 检查控制台的错误详情
- 查看弹出的错误对话框
- 常见错误类型:
- 页面路径错误
- 页面未注册
- 页面JS语法错误
- 认证检查失败
### 3. 认证问题排查
如果跳转到登录页面,说明认证检查失败:
```javascript
// 在表单页面的onLoad方法中
if (!auth.isAuthenticated()) {
console.log('用户未登录,跳转到登录页面');
wx.reLaunch({
url: '/pages/login/login'
});
return;
}
```
## 下一步计划
### 如果测试页面跳转成功
1. 恢复跳转到真实表单页面
2. 检查表单页面的认证逻辑
3. 确保表单页面能正常加载
### 如果测试页面跳转失败
1. 检查页面注册是否正确
2. 检查文件路径是否正确
3. 检查JS语法是否有错误
4. 尝试跳转到其他已知页面
## 调试命令
### 检查页面注册
```bash
# 查看app.json中的页面列表
grep -A 20 '"pages"' bank_mini_program/app.json
```
### 检查文件存在
```bash
# 检查测试页面文件是否存在
ls -la bank_mini_program/pages/business/loan-products/test-form.*
```
### 检查JS语法
```bash
# 检查JS文件语法
node -c bank_mini_program/pages/business/loan-products/test-form.js
```
## 常见问题解决
### 1. 页面路径错误
- 确保路径以 `/pages/` 开头
- 确保路径与app.json中的注册一致
- 注意大小写敏感
### 2. 页面未注册
- 在app.json的pages数组中添加页面路径
- 确保路径格式正确
### 3. 认证检查失败
- 检查用户是否已登录
- 检查token是否有效
- 考虑临时注释认证检查进行测试
### 4. 页面JS错误
- 检查onLoad方法是否有语法错误
- 检查依赖的模块是否正确导入
- 查看控制台的JavaScript错误
## 文件修改记录
### 已修改文件
1. `pages/business/loan-products/loan-products.js`
- 添加调试日志
- 修改跳转目标为测试页面
- 添加错误处理
2. `pages/business/loan-products/loan-products.wxml`
- 移除事件冲突
- 优化按钮绑定
3. `pages/business/loan-products/loan-products.wxss`
- 优化按钮样式
- 增加点击区域
4. `app.json`
- 注册测试页面
### 新增文件
1. `pages/business/loan-products/test-form.wxml`
2. `pages/business/loan-products/test-form.js`
3. `pages/business/loan-products/test-form.wxss`
## 联系信息
如果问题仍然存在,请提供:
1. 控制台的完整错误日志
2. 点击按钮后的具体表现
3. 是否看到测试页面
4. 任何其他错误信息

View File

@@ -0,0 +1,175 @@
# 银行管理系统小程序 - 按钮点击问题修复总结
## 问题描述
用户反馈在商品管理页面中,点击"新增商品"、"修改信息"、"产品下架"按钮没有反应。
## 问题分析
经过检查发现以下问题:
1. **事件冲突**:商品项容器有`bindtap="viewProductDetail"`事件,会拦截子元素的点击事件
2. **事件绑定问题**:按钮使用了`catchtap="true"`,可能影响事件处理
3. **样式层级问题**:按钮可能被其他元素遮挡
4. **点击区域太小**按钮高度只有60rpx点击区域不够大
## 修复方案
### 1. 移除容器点击事件冲突
**问题**:商品项容器有`bindtap="viewProductDetail"`事件
```xml
<!-- 修复前 -->
<view class="product-item" bindtap="viewProductDetail">
<!-- 按钮在这里 -->
</view>
```
**解决方案**:移除容器点击事件,为具体区域添加点击事件
```xml
<!-- 修复后 -->
<view class="product-item">
<view class="product-header" bindtap="viewProductDetail">
<!-- 商品标题区域可点击 -->
</view>
<view class="product-info" bindtap="viewProductDetail">
<!-- 商品信息区域可点击 -->
</view>
<view class="product-desc" bindtap="viewProductDetail">
<!-- 商品描述区域可点击 -->
</view>
<!-- 操作按钮区域不添加点击事件 -->
</view>
```
### 2. 移除不必要的事件修饰符
**问题**:按钮使用了`catchtap="true"`
```xml
<!-- 修复前 -->
<button bindtap="editProduct" catchtap="true">
```
**解决方案**:移除`catchtap="true"`
```xml
<!-- 修复后 -->
<button bindtap="editProduct">
```
### 3. 优化按钮样式和层级
**问题**:按钮可能被遮挡,点击区域太小
**解决方案**
- 增加按钮高度从60rpx增加到80rpx
- 增加字体大小从24rpx增加到26rpx
- 添加z-index确保按钮在最上层
- 增加最小宽度确保点击区域足够大
```css
.action-btn {
height: 80rpx;
font-size: 26rpx;
position: relative;
z-index: 10;
margin: 0 10rpx;
min-width: 120rpx;
}
```
### 4. 添加调试信息
为了便于调试在按钮点击方法中添加了console.log
```javascript
// 新增商品
addProduct() {
console.log('点击新增商品按钮');
// ...
},
// 编辑商品
editProduct(e) {
console.log('点击编辑商品按钮', e);
const productId = e.currentTarget.dataset.productId;
console.log('商品ID:', productId);
// ...
},
// 删除商品
deleteProduct(e) {
console.log('点击删除商品按钮', e);
const productId = e.currentTarget.dataset.productId;
console.log('商品ID:', productId);
// ...
}
```
## 修复后的效果
1. **新增商品按钮**:点击后跳转到商品表单页面(新增模式)
2. **修改信息按钮**点击后跳转到商品表单页面编辑模式并传递商品ID
3. **产品下架按钮**点击后显示确认对话框确认后调用删除API
## 技术要点
### 1. 事件冒泡处理
- 使用`bindtap`而不是`catchtap`,让事件正常冒泡
- 为需要点击的区域单独添加事件处理
### 2. 样式优化
- 确保按钮有足够的点击区域
- 使用z-index确保按钮在最上层
- 添加适当的间距和边距
### 3. 调试支持
- 添加console.log便于调试
- 在开发者工具中可以看到按钮点击日志
## 测试建议
1. **功能测试**
- 点击"新增商品"按钮,应该跳转到表单页面
- 点击"修改信息"按钮,应该跳转到表单页面并加载商品数据
- 点击"产品下架"按钮,应该显示确认对话框
2. **调试测试**
- 打开微信开发者工具的控制台
- 点击按钮时应该看到相应的日志输出
- 检查是否有JavaScript错误
3. **界面测试**
- 确认按钮可见且可点击
- 确认按钮样式正确
- 确认按钮间距合适
## 文件修改清单
1. `pages/business/loan-products/loan-products.wxml`
- 移除商品项容器的点击事件
- 为商品信息区域添加点击事件
- 移除按钮的`catchtap="true"`属性
2. `pages/business/loan-products/loan-products.js`
- 添加调试日志
- 确保方法定义正确
3. `pages/business/loan-products/loan-products.wxss`
- 增加按钮高度和字体大小
- 添加z-index和最小宽度
- 优化按钮间距
## 注意事项
1. 确保商品数据正确加载,否则按钮可能不会显示
2. 检查API接口是否正常工作
3. 确认页面路由配置正确
4. 在真机上测试,确保触摸事件正常工作
## 更新日志
- **v1.2.1** (2024-01-15)
- 修复按钮点击无反应问题
- 优化按钮样式和点击区域
- 添加调试支持
- 解决事件冲突问题

View File

@@ -0,0 +1,203 @@
# 银行管理系统小程序 - 字段映射修复
## 问题描述
用户反馈:填写了所有带*号的必填字段后,接口仍然报错 `{success: false, message: "请填写所有必填字段"}`
## 问题分析
经过检查发现,前端发送的字段名与后端期望的字段名不匹配:
### 后端期望的必填字段loanProductController.js:162
```javascript
if (!productName || !loanAmount || !loanTerm || !interestRate || !serviceArea || !servicePhone) {
return res.status(400).json({
success: false,
message: '请填写所有必填字段'
});
}
```
### 前端发送的字段名
```javascript
formData: {
productName: '', // ✅ 匹配
productCode: '', // ❌ 后端不需要
productType: 'personal', // ❌ 后端不需要
minLoanAmount: '', // ❌ 后端期望 loanAmount
maxLoanAmount: '', // ❌ 后端期望 loanAmount
minLoanTerm: '', // ❌ 后端期望 loanTerm
maxLoanTerm: '', // ❌ 后端期望 loanTerm
interestRate: '', // ✅ 匹配
serviceArea: '', // ✅ 匹配
servicePhone: '', // ✅ 匹配
riskLevel: 'LOW', // ❌ 后端不需要
requiredDocuments: '', // ❌ 后端不需要
onSaleStatus: true // ❌ 后端不需要
}
```
## 解决方案
在表单提交时添加字段映射,将前端字段名转换为后端期望的格式:
```javascript
// 转换字段名为后端期望的格式
const submitData = {
productName: formData.productName,
productCode: formData.productCode,
productType: formData.productType,
// 后端期望的字段名
loanAmount: formData.maxLoanAmount || formData.minLoanAmount, // 使用最大贷款额度作为贷款金额
loanTerm: formData.maxLoanTerm || formData.minLoanTerm, // 使用最大贷款期限作为贷款期限
minLoanAmount: formData.minLoanAmount,
maxLoanAmount: formData.maxLoanAmount,
minLoanTerm: formData.minLoanTerm,
maxLoanTerm: formData.maxLoanTerm,
interestRate: formData.interestRate,
productDescription: formData.productDescription,
serviceArea: formData.serviceArea,
servicePhone: formData.servicePhone,
riskLevel: formData.riskLevel,
requiredDocuments: formData.requiredDocuments,
onSaleStatus: formData.onSaleStatus
};
```
## 字段映射规则
| 前端字段名 | 后端字段名 | 映射规则 | 说明 |
|-----------|-----------|---------|------|
| productName | productName | 直接映射 | 产品名称 |
| minLoanAmount | minLoanAmount | 直接映射 | 最小贷款额度 |
| maxLoanAmount | maxLoanAmount | 直接映射 | 最大贷款额度 |
| minLoanTerm | minLoanTerm | 直接映射 | 最小贷款期限 |
| maxLoanTerm | maxLoanTerm | 直接映射 | 最大贷款期限 |
| maxLoanAmount | loanAmount | 优先使用最大值 | 后端必填字段 |
| maxLoanTerm | loanTerm | 优先使用最大值 | 后端必填字段 |
| interestRate | interestRate | 直接映射 | 利率 |
| serviceArea | serviceArea | 直接映射 | 服务区域 |
| servicePhone | servicePhone | 直接映射 | 服务电话 |
| productDescription | productDescription | 直接映射 | 产品描述 |
| riskLevel | riskLevel | 直接映射 | 风险等级 |
| requiredDocuments | requiredDocuments | 直接映射 | 所需材料 |
| onSaleStatus | onSaleStatus | 直接映射 | 在售状态 |
## 后端必填字段验证
后端验证的必填字段:
1. **productName** - 产品名称
2. **loanAmount** - 贷款金额使用maxLoanAmount
3. **loanTerm** - 贷款期限使用maxLoanTerm
4. **interestRate** - 利率
5. **serviceArea** - 服务区域
6. **servicePhone** - 服务电话
## 前端必填字段标识
前端标记的必填字段:
1. **产品名称** * - 对应 productName
2. **产品类型** * - 对应 productType后端不需要
3. **最小贷款额度** * - 对应 minLoanAmount
4. **最大贷款额度** * - 对应 maxLoanAmount
5. **利率** * - 对应 interestRate
6. **风险等级** * - 对应 riskLevel后端不需要
## 修复后的效果
### 1. 字段映射
- 前端字段名正确映射到后端期望的格式
- 必填字段验证通过
- 数据成功提交到后端
### 2. 用户体验
- 表单验证更加准确
- 错误提示更加明确
- 提交成功率提升
### 3. 数据一致性
- 前后端字段名统一
- 数据格式标准化
- 减少字段映射错误
## 技术实现
### 1. 字段转换逻辑
```javascript
// 在 submitForm 方法中添加字段映射
const submitData = {
// 直接映射的字段
productName: formData.productName,
interestRate: formData.interestRate,
serviceArea: formData.serviceArea,
servicePhone: formData.servicePhone,
// 需要转换的字段
loanAmount: formData.maxLoanAmount || formData.minLoanAmount,
loanTerm: formData.maxLoanTerm || formData.minLoanTerm,
// 其他字段
minLoanAmount: formData.minLoanAmount,
maxLoanAmount: formData.maxLoanAmount,
minLoanTerm: formData.minLoanTerm,
maxLoanTerm: formData.maxLoanTerm,
productDescription: formData.productDescription,
riskLevel: formData.riskLevel,
requiredDocuments: formData.requiredDocuments,
onSaleStatus: formData.onSaleStatus
};
```
### 2. 验证逻辑
- 保持前端验证不变
- 后端验证通过字段映射解决
- 双重验证确保数据完整性
## 测试建议
### 1. 功能测试
- 测试所有必填字段的提交
- 验证字段映射是否正确
- 检查后端接收的数据格式
### 2. 边界测试
- 测试空值处理
- 测试数值范围验证
- 测试特殊字符处理
### 3. 集成测试
- 测试前后端数据交互
- 验证错误处理机制
- 检查数据持久化
## 文件修改
### 修改的文件
- `pages/business/loan-products/form/form.js`
- 添加字段映射逻辑
- 优化数据提交格式
### 修改的方法
- `submitForm()` - 添加字段映射转换
## 更新日志
- **v1.3.1** (2024-01-15)
- 修复前后端字段名不匹配问题
- 添加字段映射转换逻辑
- 解决"请填写所有必填字段"错误
- 提升表单提交成功率
## 注意事项
1. **字段映射**:确保所有字段都正确映射
2. **数据验证**:保持前后端验证的一致性
3. **错误处理**:提供清晰的错误提示
4. **向后兼容**保持API接口的稳定性
## 后续优化
1. **统一字段名**:考虑统一前后端字段命名规范
2. **自动映射**:开发字段映射工具
3. **文档更新**更新API文档和字段说明
4. **测试覆盖**:增加字段映射的单元测试

View File

@@ -0,0 +1,210 @@
# 银行管理系统小程序字段映射修复说明
## 修复概述
本次修复解决了银行管理系统小程序中三个主要页面的字段显示不全问题:
1. **商品管理页面** - 贷款产品字段显示不全
2. **业务申请页面** - 申请人ID显示问题
3. **业务合同页面** - 合同字段显示不全
## 修复详情
### 1. 商品管理页面修复 (`pages/business/loan-products/`)
#### 问题描述
- 接口返回的字段名与前端使用的字段名不匹配
- 缺少服务区域、服务电话、风险等级等字段显示
#### 修复内容
**JS文件更新** (`loan-products.js`):
- 添加认证检查
- 更新字段映射逻辑,支持接口返回的字段名
- 新增字段:`serviceArea`, `servicePhone`, `riskLevel`, `requiredDocuments`, `supervisionCustomers`, `totalCustomers`
**WXML文件更新** (`loan-products.wxml`):
- 添加服务区域显示
- 添加服务电话显示
- 添加风险等级显示
- 添加监管客户数量显示
- 优化字段显示逻辑,支持空值处理
#### 字段映射对照表
| 接口字段 | 前端字段 | 说明 |
|---------|---------|------|
| `productName` | `name` | 产品名称 |
| `minLoanAmount` | `minAmount` | 最小贷款金额 |
| `maxLoanAmount` | `maxAmount` | 最大贷款金额 |
| `loanTerm` | `term` | 贷款期限 |
| `onSaleStatus` | `status` | 销售状态 |
| `serviceArea` | `serviceArea` | 服务区域 |
| `servicePhone` | `servicePhone` | 服务电话 |
| `riskLevel` | `riskLevel` | 风险等级 |
### 2. 业务申请页面修复 (`pages/business/loan-applications/`)
#### 问题描述
- 申请人ID字段没有正确显示
- 缺少资产类型、申请数量等字段
#### 修复内容
**JS文件更新** (`loan-applications.js`):
- 添加认证检查
- 更新字段映射逻辑
- 新增字段:`applicantId`, `assetType`, `applicationQuantity`, `borrowerIdNumber`
**WXML文件更新** (`loan-applications.wxml`):
- 添加申请人ID显示
- 添加资产类型显示
- 添加申请数量显示
- 添加身份证号显示
- 添加备注显示(条件显示)
#### 字段映射对照表
| 接口字段 | 前端字段 | 说明 |
|---------|---------|------|
| `borrowerName` | `applicantName` | 申请人姓名 |
| `borrowerIdNumber` | `applicantId` | 申请人ID |
| `assetType` | `assetType` | 资产类型 |
| `applicationQuantity` | `applicationQuantity` | 申请数量 |
### 3. 业务合同页面修复 (`pages/business/loan-contracts/`)
#### 问题描述
- 客户姓名字段显示为空
- 缺少合同相关的详细信息字段
#### 修复内容
**JS文件更新** (`loan-contracts.js`):
- 添加认证检查
- 更新类型映射,新增 `livestock_collateral` 类型
- 更新状态映射,新增 `completed`, `pending` 状态
- 更新字段映射逻辑,支持接口返回的字段名
- 新增字段:`customerId`, `applicationNumber`, `assetType`, `purpose`, `productName`, `paidAmount`, `remainingAmount`, `repaymentProgress`
**WXML文件更新** (`loan-contracts.wxml`):
- 添加客户ID显示
- 添加申请单号显示
- 添加资产类型显示
- 添加申请用途显示
- 添加产品名称显示
- 添加已还金额显示
- 添加剩余金额显示
- 添加还款进度显示
- 添加备注显示(条件显示)
- 更新筛选选项,包含新状态
#### 字段映射对照表
| 接口字段 | 前端字段 | 说明 |
|---------|---------|------|
| `borrowerName` | `customerName` | 客户姓名 |
| `borrowerIdNumber` | `customerId` | 客户ID |
| `contractTime` | `signDate` | 签订时间 |
| `maturityTime` | `expiryDate` | 到期时间 |
| `applicationNumber` | `applicationNumber` | 申请单号 |
| `assetType` | `assetType` | 资产类型 |
| `purpose` | `purpose` | 申请用途 |
| `productName` | `productName` | 产品名称 |
| `paidAmount` | `paidAmount` | 已还金额 |
| `remainingAmount` | `remainingAmount` | 剩余金额 |
| `repaymentProgress` | `repaymentProgress` | 还款进度 |
## 技术改进
### 1. 认证检查
所有业务页面都添加了认证检查,确保用户登录后才能访问:
```javascript
onLoad() {
if (!auth.isAuthenticated()) {
wx.reLaunch({
url: '/pages/login/login'
});
return;
}
// 页面逻辑
}
```
### 2. 字段映射策略
采用灵活的字段映射策略,支持多种字段名:
```javascript
customerName: contract.borrowerName || contract.farmerName || contract.customerName
```
### 3. 空值处理
所有字段都添加了空值处理避免显示undefined
```html
<text class="info-value">{{item.customerName || '暂无'}}</text>
```
### 4. 类型和状态映射
扩展了类型和状态映射,支持更多业务场景:
- 新增合同类型:`livestock_collateral` (养殖抵押贷款)
- 新增合同状态:`completed` (已完成), `pending` (待处理)
## 测试验证
### 测试数据示例
**商品数据**:
```javascript
{
id: 4,
productName: "养殖贷款",
interestRate: "2.00",
minLoanAmount: "23.00",
maxLoanAmount: "2323.00",
serviceArea: "1234",
servicePhone: "19872031965",
riskLevel: "LOW"
}
```
**申请数据**:
```javascript
{
id: 1,
borrowerName: "张三",
borrowerIdNumber: "110101199001010001",
assetType: "养殖设备",
applicationQuantity: "1"
}
```
**合同数据**:
```javascript
{
id: 2,
contractNumber: "CON002",
borrowerName: "李四",
borrowerIdNumber: "110101199002020002",
assetType: "养殖设备",
paidAmount: 0,
remainingAmount: 30000,
repaymentProgress: 0
}
```
## 使用说明
1. **重新编译小程序**:修改完成后需要重新编译小程序
2. **清除缓存**:建议清除小程序缓存后重新测试
3. **检查网络**确保API接口正常返回数据
4. **验证登录**:确保用户已正确登录
## 注意事项
1. 所有字段映射都考虑了向后兼容性
2. 空值处理确保界面不会显示undefined
3. 认证检查确保数据安全性
4. 字段映射策略支持多种数据格式
## 更新日志
- **v1.1.0** (2024-01-15)
- 修复商品字段显示不全问题
- 修复业务申请申请人ID显示问题
- 修复业务合同字段显示不全问题
- 添加认证检查
- 优化字段映射逻辑
- 增强空值处理

View File

@@ -0,0 +1,190 @@
# 银行管理系统小程序 - 表单必填字段标识更新
## 更新概述
为银行管理系统小程序的商品管理表单添加了必填字段的*号标识,提升用户体验和表单可用性。
## 更新内容
### 1. 必填字段标识
为以下字段添加了红色*号标识:
#### 基本信息
- **产品名称** * - 必填,用于标识贷款产品
- **产品类型** * - 必填,选择产品分类
#### 贷款信息
- **最小贷款额度** * - 必填,设置贷款下限
- **最大贷款额度** * - 必填,设置贷款上限
- **利率** * - 必填,设置产品利率
#### 服务信息
- **风险等级** * - 必填,评估产品风险
### 2. 样式优化
#### WXML结构优化
```xml
<!-- 优化前 -->
<text class="form-label">产品名称 *</text>
<!-- 优化后 -->
<text class="form-label">
产品名称
<text class="required-asterisk">*</text>
</text>
```
#### CSS样式添加
```css
/* 必填字段的*号样式 */
.required-asterisk {
color: #ff4d4f;
font-weight: bold;
margin-left: 4rpx;
}
```
### 3. 表单验证增强
更新了表单验证逻辑,确保所有标记为必填的字段都进行验证:
```javascript
// 新增的验证规则
if (!formData.productType) {
wx.showToast({
title: '请选择产品类型',
icon: 'none'
});
return false;
}
if (!formData.riskLevel) {
wx.showToast({
title: '请选择风险等级',
icon: 'none'
});
return false;
}
```
## 技术实现
### 1. 字段标识方式
- 使用嵌套的`<text>`标签分离字段名称和*号
- *号使用独立的CSS类`.required-asterisk`
- 红色高亮显示,增强视觉识别度
### 2. 样式特点
- **颜色**`#ff4d4f` (红色)
- **字重**`bold` (粗体)
- **间距**`margin-left: 4rpx` (与字段名保持适当间距)
### 3. 验证逻辑
- 前端实时验证,提升用户体验
- 统一的错误提示格式
- 阻止无效数据提交
## 用户体验提升
### 1. 视觉识别
- 必填字段一目了然
- 红色*号醒目易识别
- 与可选字段形成对比
### 2. 操作指导
- 用户清楚知道哪些字段必须填写
- 减少表单提交失败的情况
- 提升表单完成率
### 3. 错误预防
- 前端验证减少后端错误
- 及时提示用户补充信息
- 降低数据不完整的情况
## 文件修改清单
### 1. 模板文件
- `pages/business/loan-products/form/form.wxml`
- 更新所有必填字段的标签结构
- 添加`required-asterisk`类包装*号
### 2. 样式文件
- `pages/business/loan-products/form/form.wxss`
- 添加`.required-asterisk`样式类
- 定义红色高亮样式
### 3. 逻辑文件
- `pages/business/loan-products/form/form.js`
- 增强`validateForm()`方法
- 添加产品类型和风险等级验证
## 必填字段列表
| 字段名称 | 字段类型 | 验证规则 | 错误提示 |
|---------|---------|---------|---------|
| 产品名称 | 文本输入 | 非空验证 | "请输入产品名称" |
| 产品类型 | 选择器 | 非空验证 | "请选择产品类型" |
| 最小贷款额度 | 数字输入 | 非空+数值验证 | "请输入贷款额度范围" |
| 最大贷款额度 | 数字输入 | 非空+数值验证 | "请输入贷款额度范围" |
| 利率 | 数字输入 | 非空验证 | "请输入利率" |
| 风险等级 | 选择器 | 非空验证 | "请选择风险等级" |
## 验证规则详情
### 1. 基础验证
- 所有必填字段不能为空
- 字符串字段去除首尾空格后验证
### 2. 数值验证
- 贷款额度必须为有效数字
- 最小额度必须小于最大额度
- 利率必须为有效数值
### 3. 选择验证
- 产品类型必须从预设选项中选择
- 风险等级必须从预设选项中选择
## 测试建议
### 1. 功能测试
- 测试所有必填字段的验证
- 测试*号显示是否正确
- 测试错误提示是否准确
### 2. 界面测试
- 检查*号颜色和样式
- 验证在不同设备上的显示效果
- 确认与整体UI风格协调
### 3. 用户体验测试
- 测试表单提交流程
- 验证错误提示的友好性
- 检查操作流程的流畅性
## 后续优化建议
### 1. 动态验证
- 考虑添加实时验证
- 在用户输入时即时提示
### 2. 视觉增强
- 可以为必填字段添加边框高亮
- 考虑添加必填字段说明
### 3. 国际化支持
- 为多语言环境准备
- 考虑不同语言的*号位置
## 更新日志
- **v1.3.0** (2024-01-15)
- 为表单必填字段添加*号标识
- 增强表单验证逻辑
- 优化用户体验
- 提升表单可用性
## 联系信息
如有问题或建议,请及时反馈,我们将持续优化用户体验。

View File

@@ -0,0 +1,276 @@
# 银行管理系统小程序 - 登录登出功能指南
## 功能概述
银行管理系统小程序已完善了登录和登出功能,包括:
- 用户登录验证
- 记住用户名功能
- 自动登录状态检查
- 安全登出
- 全局认证状态管理
## 文件结构
### 核心文件
```
bank_mini_program/
├── utils/
│ └── auth.js # 认证工具类
├── services/
│ ├── apiService.js # API服务
│ └── authService.js # 认证服务
├── pages/
│ ├── login/
│ │ ├── login.js # 登录页面逻辑
│ │ ├── login.wxml # 登录页面结构
│ │ └── login.wxss # 登录页面样式
│ └── profile/
│ ├── profile.js # 个人中心页面逻辑
│ ├── profile.wxml # 个人中心页面结构
│ └── profile.wxss # 个人中心页面样式
└── app.js # 应用入口,包含全局认证管理
```
## 功能详解
### 1. 登录功能
#### 登录页面特性
- **用户名密码输入**:支持用户名和密码登录
- **记住我功能**:可选择记住用户名,下次自动填充
- **快速登录**:提供测试用的快速登录按钮
- **表单验证**:输入验证和错误提示
- **加载状态**:登录过程中的加载提示
#### 登录流程
1. 用户输入用户名和密码
2. 点击登录按钮
3. 调用 `authService.login()` 进行API验证
4. 成功后保存token和用户信息到本地存储
5. 设置全局用户信息
6. 跳转到首页
#### 代码示例
```javascript
// 登录处理
async handleLogin() {
const { username, password } = this.data;
try {
const response = await authService.login(username, password);
if (response && response.success) {
// 保存认证信息
auth.setToken(response.token);
auth.setUser(response.user);
// 设置全局状态
const app = getApp();
if (app && app.login) {
app.login(response.user);
}
// 跳转到首页
wx.reLaunch({
url: '/pages/index/index'
});
}
} catch (error) {
console.error('登录失败:', error);
}
}
```
### 2. 登出功能
#### 登出页面特性
- **确认对话框**:登出前显示确认提示
- **API调用**:调用后端登出接口
- **本地清理**:清除本地存储的认证信息
- **全局状态更新**:更新应用的全局认证状态
#### 登出流程
1. 用户点击退出登录按钮
2. 显示确认对话框
3. 确认后调用 `authService.logout()` API
4. 清除本地存储的token和用户信息
5. 更新全局认证状态
6. 跳转到登录页面
#### 代码示例
```javascript
// 登出处理
async handleLogout() {
try {
// 调用登出API
await authService.logout();
// 清除本地认证信息
auth.clearAuth();
// 更新全局状态
const app = getApp();
if (app && app.logout) {
app.logout();
}
// 跳转到登录页
wx.reLaunch({
url: '/pages/login/login'
});
} catch (error) {
console.error('登出失败:', error);
}
}
```
### 3. 认证状态管理
#### 全局认证检查
所有需要认证的页面都会在 `onLoad``onShow` 时检查认证状态:
```javascript
onLoad() {
// 检查认证状态
if (!auth.isAuthenticated()) {
wx.reLaunch({
url: '/pages/login/login'
});
return;
}
// 继续页面逻辑
this.loadData();
}
```
#### 认证工具类方法
```javascript
// 检查是否已认证
auth.isAuthenticated()
// 获取用户信息
auth.getUser()
// 获取token
auth.getToken()
// 设置用户信息
auth.setUser(userInfo)
// 设置token
auth.setToken(token)
// 清除所有认证信息
auth.clearAuth()
```
### 4. 记住用户名功能
#### 功能说明
- 用户可以选择"记住我"选项
- 记住的用户名会保存到本地存储
- 下次打开登录页面时自动填充用户名
#### 实现代码
```javascript
// 切换记住我
onRememberMeChange(e) {
this.setData({
rememberMe: e.detail.value
});
}
// 保存/清除用户名
if (rememberMe) {
wx.setStorageSync('remembered_username', username);
} else {
wx.removeStorageSync('remembered_username');
}
```
## 使用说明
### 1. 登录
1. 打开小程序
2. 如果未登录,会自动跳转到登录页面
3. 输入用户名和密码
4. 可选择"记住我"选项
5. 点击"登录"按钮
### 2. 登出
1. 进入个人中心页面
2. 点击"退出登录"按钮
3. 确认退出操作
4. 自动跳转到登录页面
### 3. 自动登录检查
- 每次打开需要认证的页面时,系统会自动检查登录状态
- 如果未登录,会自动跳转到登录页面
- 登录后会自动跳转回原页面
## 测试
### 测试文件
运行 `test-auth.js` 可以测试认证功能:
```javascript
// 在微信开发者工具中运行
const auth = require('./utils/auth.js');
// 测试认证状态
console.log('是否已认证:', auth.isAuthenticated());
```
### 测试步骤
1. 打开微信开发者工具
2. 导入银行管理系统小程序项目
3. 在控制台运行测试脚本
4. 测试登录和登出功能
## 注意事项
1. **API接口**确保后端API接口 `/bank/api/auth/login``/bank/api/auth/logout` 正常工作
2. **Token管理**Token会自动添加到API请求头中
3. **错误处理**:所有认证相关的错误都有适当的错误提示
4. **安全性**:敏感信息存储在微信小程序的本地存储中
5. **兼容性**:支持微信小程序的所有版本
## 故障排除
### 常见问题
1. **登录失败**
- 检查网络连接
- 确认用户名密码正确
- 查看控制台错误信息
2. **自动跳转问题**
- 检查页面路径是否正确
- 确认认证状态检查逻辑
3. **记住用户名不工作**
- 检查本地存储权限
- 确认存储键名正确
4. **登出后仍显示已登录**
- 检查 `auth.clearAuth()` 是否被调用
- 确认全局状态是否正确更新
### 调试方法
1. 在微信开发者工具中查看控制台日志
2. 检查本地存储中的数据
3. 使用 `auth.isAuthenticated()` 检查认证状态
4. 查看网络请求是否成功
## 更新日志
- **v1.0.0** (2024-01-15)
- 初始版本
- 实现基础登录登出功能
- 添加记住用户名功能
- 实现全局认证状态管理
- 添加页面级认证检查

View File

@@ -0,0 +1,180 @@
# 银行管理系统小程序 - 页面跳转问题调试报告
## 问题现象
用户反馈:
- 点击"新增商品"按钮可以跳转到测试页面
- 点击"修改信息"按钮跳转到表单页面失败
- 错误信息:`navigateTo:fail page "pages/business/loan-products/form?mode=edit&productId=4" is not found`
## 问题分析
### 1. 错误信息解读
```
navigateTo:fail page "pages/business/loan-products/form?mode=edit&productId=4" is not found
```
这个错误表明:
- 微信小程序无法找到指定的页面路径
- 路径中包含了查询参数但错误信息显示的是完整的URL
- 可能是页面路径格式或注册问题
### 2. 可能的原因
#### A. 页面路径格式问题
- 微信小程序页面路径应该不包含查询参数
- 查询参数应该通过URL参数传递而不是作为路径的一部分
#### B. 页面注册问题
- 页面可能没有在app.json中正确注册
- 页面路径可能与注册的路径不匹配
#### C. 文件结构问题
- 页面文件可能不存在或路径不正确
- 页面文件可能有语法错误
## 当前调试方案
### 1. 分步测试策略
```javascript
// 第一步:测试基础跳转
wx.navigateTo({
url: 'pages/profile/profile', // 已知存在的页面
success: (res) => {
console.log('基础跳转成功');
// 第二步:测试表单页面跳转
wx.navigateTo({
url: 'pages/business/loan-products/form/form?mode=add',
// ...
});
}
});
```
### 2. 路径格式测试
- 测试1`pages/business/loan-products/form/form` (不带斜杠)
- 测试2`/pages/business/loan-products/form/form` (带斜杠)
- 测试3检查app.json中的注册路径
### 3. 页面注册验证
```json
{
"pages": [
"pages/business/loan-products/form/form", // 确认这个路径存在
// ...
]
}
```
## 已完成的修复
### 1. 事件冲突修复 ✅
- 移除了商品项容器的点击事件冲突
- 优化了按钮事件绑定
### 2. 按钮样式优化 ✅
- 增加了按钮高度和点击区域
- 添加了z-index确保按钮可见
### 3. 调试信息添加 ✅
- 添加了详细的成功/失败回调
- 添加了错误信息显示
### 4. 测试页面创建 ✅
- 创建了简化的测试页面
- 验证了基础跳转功能
## 当前测试状态
### ✅ 成功的部分
- 按钮点击事件正常触发
- 可以跳转到测试页面
- 控制台日志正常输出
### ❌ 失败的部分
- 无法跳转到表单页面
- 错误信息显示页面未找到
## 下一步调试计划
### 1. 立即测试
1. 点击"新增商品"按钮
2. 观察是否先跳转到个人中心页面
3. 观察是否自动返回并尝试跳转到表单页面
4. 查看控制台错误信息
### 2. 路径格式验证
如果基础跳转成功但表单页面跳转失败:
- 检查app.json中的页面注册
- 验证文件路径是否正确
- 检查页面文件是否存在
### 3. 页面文件检查
如果路径正确但仍然失败:
- 检查form.js是否有语法错误
- 检查form.wxml是否有语法错误
- 检查form.json配置是否正确
## 可能的解决方案
### 方案1修复页面路径格式
```javascript
// 当前(可能有问题)
url: 'pages/business/loan-products/form/form?mode=add'
// 修复后
url: '/pages/business/loan-products/form/form?mode=add'
```
### 方案2检查页面注册
确保app.json中的路径与跳转路径完全一致
```json
{
"pages": [
"pages/business/loan-products/form/form"
]
}
```
### 方案3简化页面结构
如果问题持续,考虑:
- 将表单页面移到更简单的路径
- 使用不同的页面命名方式
- 检查是否有特殊字符或路径长度问题
## 调试命令
### 检查文件存在
```bash
ls -la bank_mini_program/pages/business/loan-products/form/
```
### 检查页面注册
```bash
grep -A 10 '"pages"' bank_mini_program/app.json
```
### 检查JS语法
```bash
node -c bank_mini_program/pages/business/loan-products/form/form.js
```
## 预期结果
### 成功情况
- 点击"新增商品"按钮后,先跳转到个人中心
- 1秒后自动返回然后跳转到表单页面
- 表单页面正常加载,显示"商品管理"标题
### 失败情况
- 如果个人中心跳转失败:基础路径格式问题
- 如果表单页面跳转失败:页面注册或文件问题
- 如果都失败:更严重的配置问题
## 联系信息
请测试后提供:
1. 控制台的完整日志
2. 是否看到个人中心页面
3. 是否看到表单页面
4. 任何新的错误信息

View File

@@ -0,0 +1,177 @@
# 银行管理系统小程序 - 商品管理功能完成总结
## 功能概述
已成功为银行管理系统小程序的业务管理页面添加了完整的商品管理功能,包括新增、编辑、删除(下架)商品,并连接了后端接口。
## 完成的功能
### 1. 商品列表页面增强 (`pages/business/loan-products/loan-products.*`)
#### 新增功能
- **新增商品按钮**:在筛选栏右侧添加了"新增商品"按钮
- **商品操作按钮**:为每个商品项添加了"修改信息"和"产品下架"按钮
- **删除确认**:删除商品时显示确认对话框
- **认证检查**:添加了登录状态检查
#### 界面改进
- 优化了筛选栏布局,支持新增按钮
- 为每个商品项添加了操作按钮区域
- 改进了按钮样式和交互效果
### 2. 商品表单页面 (`pages/business/loan-products/form/form.*`)
#### 页面功能
- **新增模式**:支持创建新的贷款商品
- **编辑模式**:支持修改现有商品信息
- **表单验证**:完整的输入验证和错误提示
- **数据加载**:编辑模式下自动加载商品详情
#### 表单字段
- **基本信息**:产品名称、产品代码、产品类型
- **贷款信息**:贷款额度范围、贷款期限、利率
- **服务信息**:服务区域、服务电话、风险等级、所需材料、产品描述
- **状态设置**:在售状态开关
#### 技术特性
- **选择器支持**修复了微信小程序picker组件的兼容性问题
- **计算属性**:实现了动态的选择器索引和标签更新
- **响应式设计**:适配不同屏幕尺寸
- **加载状态**:完整的加载提示和错误处理
### 3. API服务扩展 (`services/apiService.js`)
#### 新增接口方法
- `getDetail(id)` - 获取商品详情
- `create(data)` - 创建商品
- `update(id, data)` - 更新商品
- `delete(id)` - 删除商品
- `toggleStatus(id, status)` - 上架/下架商品
### 4. 页面注册 (`app.json`)
- 注册了新的表单页面路径
- 确保页面路由正常工作
## 技术实现细节
### 1. 微信小程序兼容性修复
**问题**WXML中使用了复杂的JavaScript表达式导致编译错误
```javascript
// 错误的写法
value="{{typeOptions.findIndex(item => item.value === formData.productType)}}"
```
**解决方案**:使用计算属性和数据绑定
```javascript
// 正确的写法
data: {
productTypeIndex: 0,
currentProductTypeLabel: '请选择产品类型'
}
```
### 2. 选择器组件优化
**实现方式**
- 在data中维护选择器的索引和显示标签
- 通过`updatePickerIndexes()`方法同步数据
-`onPickerChange`中更新索引和标签
### 3. 表单验证
**验证规则**
- 产品名称为必填项
- 贷款额度范围必须填写且最小值小于最大值
- 利率为必填项
- 支持实时验证和错误提示
### 4. 数据映射
**字段映射策略**
- 支持多种字段名格式
- 向后兼容现有数据结构
- 空值处理和默认值设置
## 用户界面设计
### 1. 商品列表页面
- **新增按钮**:蓝色主题,位于筛选栏右侧
- **操作按钮**:每个商品项底部显示"修改信息"和"产品下架"按钮
- **按钮样式**:圆角设计,悬停效果,颜色区分功能
### 2. 商品表单页面
- **分段布局**:基本信息、贷款信息、服务信息、状态设置
- **表单控件**:输入框、选择器、开关、文本域
- **操作按钮**:底部固定,取消和提交按钮
- **加载状态**:全屏遮罩,加载提示
## 后端接口对接
### 1. 接口路径
- 商品列表:`GET /loan-products`
- 商品详情:`GET /loan-products/:id`
- 创建商品:`POST /loan-products`
- 更新商品:`PUT /loan-products/:id`
- 删除商品:`DELETE /loan-products/:id`
### 2. 数据格式
- 支持完整的商品信息字段
- 包含验证和错误处理
- 统一的响应格式
## 使用说明
### 1. 新增商品
1. 进入商品管理页面
2. 点击右上角"新增商品"按钮
3. 填写商品信息
4. 点击"新增"按钮提交
### 2. 编辑商品
1. 在商品列表中找到要编辑的商品
2. 点击"修改信息"按钮
3. 修改商品信息
4. 点击"修改"按钮保存
### 3. 下架商品
1. 在商品列表中找到要下架的商品
2. 点击"产品下架"按钮
3. 确认下架操作
## 注意事项
1. **认证要求**:所有操作都需要用户登录
2. **数据验证**:表单提交前会进行完整验证
3. **错误处理**:网络错误和业务错误都有相应提示
4. **兼容性**:修复了微信小程序的兼容性问题
5. **用户体验**:提供加载状态和操作反馈
## 文件结构
```
bank_mini_program/
├── pages/business/loan-products/
│ ├── loan-products.js # 商品列表页面逻辑
│ ├── loan-products.wxml # 商品列表页面结构
│ ├── loan-products.wxss # 商品列表页面样式
│ └── form/
│ ├── form.js # 商品表单页面逻辑
│ ├── form.wxml # 商品表单页面结构
│ ├── form.wxss # 商品表单页面样式
│ └── form.json # 商品表单页面配置
├── services/
│ └── apiService.js # API服务已扩展
└── app.json # 页面注册(已更新)
```
## 更新日志
- **v1.2.0** (2024-01-15)
- 新增商品管理功能
- 添加新增、编辑、删除商品功能
- 修复微信小程序兼容性问题
- 完善表单验证和错误处理
- 优化用户界面和交互体验
- 连接后端API接口

View File

@@ -0,0 +1,117 @@
# 银行小程序个人中心菜单更新
## 更新内容
### 1. 新增功能菜单
在修改密码下方添加了两个新的功能菜单:
- **硬件管理** ⚙️
- **人员管理** 👥
### 2. 样式优化
- 移除了渐变紫色背景 (`linear-gradient(135deg, #667eea 0%, #764ba2 100%)`)
- 改为简洁的蓝色背景 (`#1890ff`)
- 保持整体设计风格的一致性
## 修改的文件
### 1. bank_mini_program/pages/profile/profile.wxml
```xml
<!-- 新增的菜单项 -->
<view class="menu-item" bindtap="hardwareManagement">
<view class="menu-icon">⚙️</view>
<view class="menu-text">硬件管理</view>
<view class="menu-arrow">></view>
</view>
<view class="menu-item" bindtap="personnelManagement">
<view class="menu-icon">👥</view>
<view class="menu-text">人员管理</view>
<view class="menu-arrow">></view>
</view>
```
### 2. bank_mini_program/pages/profile/profile.wxss
```css
/* 修改前 */
.profile-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
/* ... */
}
/* 修改后 */
.profile-header {
background: #1890ff;
/* ... */
}
```
### 3. bank_mini_program/pages/profile/profile.js
```javascript
/**
* 硬件管理
*/
hardwareManagement() {
wx.showToast({
title: '硬件管理功能开发中',
icon: 'none'
})
},
/**
* 人员管理
*/
personnelManagement() {
wx.showToast({
title: '人员管理功能开发中',
icon: 'none'
})
}
```
## 功能说明
### 当前状态
- 两个新功能目前显示"功能开发中"提示
- 为后续功能开发预留了接口
### 菜单顺序
1. 修改密码 🔒
2. 硬件管理 ⚙️ (新增)
3. 人员管理 👥 (新增)
4. 关于我们
### 样式特点
- 使用统一的蓝色主题 (`#1890ff`)
- 保持简洁现代的设计风格
- 移除了复杂的渐变效果
- 图标和文字对齐一致
## 后续开发建议
### 硬件管理功能
- 设备列表查看
- 设备状态监控
- 设备配置管理
- 设备维护记录
### 人员管理功能
- 员工信息管理
- 权限分配
- 角色管理
- 操作日志
## 测试验证
1. **菜单显示**:确认新增的两个菜单项正确显示
2. **点击响应**:点击菜单项显示相应的提示信息
3. **样式效果**:确认头部背景色已更改为蓝色
4. **整体布局**:确认菜单项间距和对齐正确
## 更新日志
- **v1.1.0** (2024-01-15)
- 新增硬件管理菜单项
- 新增人员管理菜单项
- 优化头部背景样式,移除渐变效果
- 保持功能开发中的提示状态

View File

@@ -0,0 +1,193 @@
# 银行管理系统 - 用户ID映射问题修复
## 问题描述
用户反馈:填写了所有必填字段后,接口仍然报错 `{success: false, message: "请填写所有必填字段"}`
## 问题分析
经过深入检查发现问题不在前端字段映射而在后端用户ID映射不一致
### 1. 数据库模型要求
```javascript
// LoanProduct.js 模型定义
createdBy: {
type: DataTypes.INTEGER,
allowNull: false, // 必填字段
field: 'created_by',
comment: '创建人ID'
}
```
### 2. 后端控制器不一致
```javascript
// loanContractController.js (正确)
createdBy: req.user?.id
// loanProductController.js (错误)
createdBy: req.user.userId // ❌ 应该是 req.user.id
```
### 3. 认证中间件设置
认证中间件设置的是 `req.user.id`,但贷款商品控制器使用的是 `req.user.userId`
## 解决方案
修复后端控制器中的用户ID映射统一使用 `req.user.id`
### 1. 用户信息检查
```javascript
// 修复前
if (!req.user || !req.user.userId) {
return res.status(401).json({
success: false,
message: '用户信息无效'
});
}
// 修复后
if (!req.user || !req.user.id) {
return res.status(401).json({
success: false,
message: '用户信息无效'
});
}
```
### 2. 创建记录
```javascript
// 修复前
createdBy: req.user.userId,
updatedBy: req.user.userId
// 修复后
createdBy: req.user.id,
updatedBy: req.user.id
```
### 3. 更新记录
```javascript
// 修复前
product.updatedBy = req.user.userId;
// 修复后
product.updatedBy = req.user.id;
```
### 4. 批量更新
```javascript
// 修复前
updatedBy: req.user.userId
// 修复后
updatedBy: req.user.id
```
## 修复的文件
### bank-backend/controllers/loanProductController.js
- 修复用户信息检查逻辑
- 修复创建记录时的用户ID设置
- 修复更新记录时的用户ID设置
- 修复批量更新时的用户ID设置
## 技术细节
### 1. 认证中间件设置
```javascript
// 认证中间件通常设置
req.user = {
id: userId, // ✅ 正确的字段名
username: username,
// ... 其他用户信息
}
```
### 2. 数据库约束
```javascript
// 数据库模型定义
createdBy: {
type: DataTypes.INTEGER,
allowNull: false, // 必填字段不能为null
field: 'created_by'
}
```
### 3. 错误原因
`req.user.userId``undefined` 时:
- 数据库插入失败
- 返回"请填写所有必填字段"错误
- 实际是 `createdBy` 字段为 `null` 导致的
## 修复后的效果
### 1. 数据完整性
- `createdBy` 字段正确设置
- 数据库约束满足
- 记录创建成功
### 2. 错误消除
- 不再出现"请填写所有必填字段"错误
- 表单提交成功
- 数据持久化正常
### 3. 一致性提升
- 前后端字段映射统一
- 用户ID处理一致
- 代码维护性提升
## 验证步骤
### 1. 前端测试
1. 填写所有必填字段
2. 点击提交按钮
3. 检查控制台日志
4. 验证提交成功
### 2. 后端验证
1. 检查 `createdBy` 字段是否正确设置
2. 验证数据库记录创建成功
3. 确认用户ID映射正确
### 3. 数据库检查
```sql
-- 检查创建的记录
SELECT id, product_name, created_by, created_at
FROM loan_products
ORDER BY created_at DESC
LIMIT 5;
```
## 相关文件
### 修改的文件
- `bank-backend/controllers/loanProductController.js`
- 修复用户ID映射问题
- 统一使用 `req.user.id`
### 参考的文件
- `bank-backend/controllers/loanContractController.js`
- 正确的用户ID使用方式
- 作为修复参考
## 更新日志
- **v1.3.2** (2024-01-15)
- 修复后端用户ID映射问题
- 统一使用 `req.user.id` 替代 `req.user.userId`
- 解决"请填写所有必填字段"错误
- 提升数据完整性
## 注意事项
1. **一致性**确保所有控制器使用相同的用户ID字段
2. **测试**:验证修复后的功能正常
3. **文档**更新API文档中的用户ID说明
4. **监控**关注后续的用户ID相关错误
## 后续优化
1. **统一规范**制定用户ID使用规范
2. **中间件优化**:确保认证中间件设置一致
3. **错误处理**改进用户ID缺失的错误提示
4. **测试覆盖**增加用户ID相关的单元测试

View File

@@ -1,21 +1,99 @@
// app.js
const auth = require('./utils/auth.js')
App({
onLaunch() {
// 展示本地存储能力
const logs = wx.getStorageSync('logs') || []
logs.unshift(Date.now())
wx.setStorageSync('logs', logs)
console.log('银行管理系统小程序启动')
// 检查登录状态
this.checkAuthStatus()
// 设置全局错误处理
this.setupErrorHandler()
},
// 登录
wx.login({
success: res => {
// 发送 res.code 到后台换取 openId, sessionKey, unionId
console.log('登录成功', res.code)
onShow() {
console.log('银行管理系统小程序显示')
},
onHide() {
console.log('银行管理系统小程序隐藏')
},
/**
* 检查认证状态
*/
checkAuthStatus() {
try {
const isAuthenticated = auth.isAuthenticated()
console.log('认证状态:', isAuthenticated)
if (isAuthenticated) {
const userInfo = auth.getUser()
this.globalData.userInfo = userInfo
console.log('用户信息:', userInfo)
}
} catch (error) {
console.error('检查认证状态错误:', error)
}
},
/**
* 设置全局错误处理
*/
setupErrorHandler() {
// 监听未处理的Promise拒绝
wx.onUnhandledRejection((res) => {
console.error('未处理的Promise拒绝:', res)
// 如果是认证相关错误,清除认证信息
if (res.reason && res.reason.message && res.reason.message.includes('认证')) {
auth.clearAuth()
this.globalData.userInfo = null
}
})
},
/**
* 全局登录方法
*/
login(userInfo) {
this.globalData.userInfo = userInfo
console.log('全局登录:', userInfo)
},
/**
* 全局登出方法
*/
logout() {
this.globalData.userInfo = null
auth.clearAuth()
console.log('全局登出')
},
/**
* 检查是否需要登录
*/
requireAuth() {
if (!auth.isAuthenticated()) {
wx.showModal({
title: '需要登录',
content: '此功能需要登录后才能使用',
showCancel: false,
success: () => {
wx.reLaunch({
url: '/pages/login/login'
})
}
})
return false
}
return true
},
globalData: {
userInfo: null,
baseUrl: 'http://localhost:5352/api'
baseUrl: 'https://ad.ningmuyun.com/bank/api',
version: '1.0.0'
}
})

View File

@@ -4,10 +4,13 @@
"pages/projects/projects",
"pages/business/business",
"pages/business/loan-products/loan-products",
"pages/business/loan-products/form/form",
"pages/business/loan-products/test-form",
"pages/business/loan-applications/loan-applications",
"pages/business/loan-contracts/loan-contracts",
"pages/business/loan-releases/loan-releases",
"pages/profile/profile",
"pages/profile/change-password",
"pages/login/login",
"pages/dashboard/dashboard",
"pages/customers/customers",

View File

@@ -1,5 +1,6 @@
// pages/business/business.js
const { apiService } = require('../../services/apiService');
const auth = require('../../utils/auth.js');
Page({
data: {
@@ -12,10 +13,26 @@ Page({
},
onLoad() {
// 检查认证状态
if (!auth.isAuthenticated()) {
wx.reLaunch({
url: '/pages/login/login'
});
return;
}
this.loadBusinessStats();
},
onShow() {
// 检查认证状态
if (!auth.isAuthenticated()) {
wx.reLaunch({
url: '/pages/login/login'
});
return;
}
this.loadBusinessStats();
},

View File

@@ -1,5 +1,6 @@
// pages/business/loan-applications/loan-applications.js
const { apiService } = require('../../../services/apiService');
const auth = require('../../../utils/auth.js');
Page({
data: {
@@ -22,10 +23,26 @@ Page({
},
onLoad() {
// 检查认证状态
if (!auth.isAuthenticated()) {
wx.reLaunch({
url: '/pages/login/login'
});
return;
}
this.loadApplications();
},
onShow() {
// 检查认证状态
if (!auth.isAuthenticated()) {
wx.reLaunch({
url: '/pages/login/login'
});
return;
}
this.loadApplications();
},
@@ -46,9 +63,28 @@ Page({
if (response.success) {
const applications = response.data.applications.map(application => ({
...application,
// 字段映射 - 根据接口返回数据调整
applicantName: application.borrowerName || application.farmerName || application.applicantName,
applicantId: application.borrowerIdNumber || application.id,
type: application.type,
typeText: this.data.typeMap[application.type] || application.type,
status: application.status,
statusText: this.data.statusMap[application.status] || application.status,
applicationTime: this.formatDate(application.applicationTime)
amount: application.amount || application.loanAmount,
term: application.term || application.loanTerm,
interestRate: application.interestRate,
applicationTime: this.formatDate(application.applicationTime || application.created_at),
phone: application.phone,
purpose: application.purpose,
applicationNumber: application.applicationNumber,
applicationQuantity: application.applicationQuantity,
assetType: application.assetType,
borrowerIdNumber: application.borrowerIdNumber,
borrowerName: application.borrowerName,
farmerName: application.farmerName,
approvedTime: application.approvedTime,
rejectedTime: application.rejectedTime,
remark: application.remark
}));
this.setData({

View File

@@ -0,0 +1,3 @@
{
"usingComponents": {}
}

View File

@@ -50,6 +50,11 @@
<text class="info-value">{{item.applicantName}}</text>
</view>
<view class="info-row">
<text class="info-label">申请人ID</text>
<text class="info-value">{{item.applicantId}}</text>
</view>
<view class="info-row">
<text class="info-label">申请类型:</text>
<text class="info-value">{{item.typeText}}</text>
@@ -84,6 +89,26 @@
<text class="info-label">申请用途:</text>
<text class="info-value">{{item.purpose}}</text>
</view>
<view class="info-row">
<text class="info-label">资产类型:</text>
<text class="info-value">{{item.assetType || '暂无'}}</text>
</view>
<view class="info-row">
<text class="info-label">申请数量:</text>
<text class="info-value">{{item.applicationQuantity || '1'}}</text>
</view>
<view class="info-row">
<text class="info-label">身份证号:</text>
<text class="info-value">{{item.borrowerIdNumber || '暂无'}}</text>
</view>
<view class="info-row" wx:if="{{item.remark}}">
<text class="info-label">备注:</text>
<text class="info-value">{{item.remark}}</text>
</view>
</view>
</view>
</view>

View File

@@ -1,5 +1,6 @@
// pages/business/loan-contracts/loan-contracts.js
const { apiService } = require('../../../services/apiService');
const auth = require('../../../utils/auth.js');
Page({
data: {
@@ -11,20 +12,39 @@ Page({
'personal': '个人贷款',
'mortgage': '住房贷款',
'business': '企业贷款',
'agricultural': '农业贷款'
'agricultural': '农业贷款',
'livestock_collateral': '养殖抵押贷款'
},
statusMap: {
'active': '生效中',
'expired': '已到期',
'terminated': '已终止'
'terminated': '已终止',
'completed': '已完成',
'pending': '待处理'
}
},
onLoad() {
// 检查认证状态
if (!auth.isAuthenticated()) {
wx.reLaunch({
url: '/pages/login/login'
});
return;
}
this.loadContracts();
},
onShow() {
// 检查认证状态
if (!auth.isAuthenticated()) {
wx.reLaunch({
url: '/pages/login/login'
});
return;
}
this.loadContracts();
},
@@ -45,10 +65,31 @@ Page({
if (response.success) {
const contracts = response.data.contracts.map(contract => ({
...contract,
// 字段映射 - 根据接口返回数据调整
customerName: contract.borrowerName || contract.farmerName || contract.customerName,
customerId: contract.borrowerIdNumber || contract.customerId,
type: contract.type,
typeText: this.data.typeMap[contract.type] || contract.type,
status: contract.status,
statusText: this.data.statusMap[contract.status] || contract.status,
signDate: this.formatDate(contract.signDate),
expiryDate: this.formatDate(contract.expiryDate)
amount: contract.amount,
term: contract.term,
interestRate: contract.interestRate,
signDate: this.formatDate(contract.contractTime || contract.signDate),
expiryDate: this.formatDate(contract.maturityTime || contract.expiryDate),
phone: contract.phone,
contractNumber: contract.contractNumber,
applicationNumber: contract.applicationNumber,
applicationQuantity: contract.applicationQuantity,
assetType: contract.assetType,
purpose: contract.purpose,
productName: contract.productName,
paidAmount: contract.paidAmount,
remainingAmount: contract.remainingAmount,
repaymentProgress: contract.repaymentProgress,
completedTime: contract.completedTime,
disbursementTime: contract.disbursementTime,
remark: contract.remark
}));
this.setData({

View File

@@ -0,0 +1,3 @@
{
"usingComponents": {}
}

View File

@@ -17,7 +17,7 @@
<picker
bindchange="onFilterChange"
value="{{filterStatus}}"
range="{{['全部', '生效中', '已到期', '已终止']}}"
range="{{['全部', '生效中', '已到期', '已终止', '已完成', '待处理']}}"
range-key=""
>
<view class="filter-item">
@@ -47,7 +47,12 @@
<view class="contract-info">
<view class="info-row">
<text class="info-label">客户姓名:</text>
<text class="info-value">{{item.customerName}}</text>
<text class="info-value">{{item.customerName || '暂无'}}</text>
</view>
<view class="info-row">
<text class="info-label">客户ID</text>
<text class="info-value">{{item.customerId || '暂无'}}</text>
</view>
<view class="info-row">
@@ -72,18 +77,58 @@
<view class="info-row">
<text class="info-label">签订时间:</text>
<text class="info-value">{{item.signDate}}</text>
<text class="info-value">{{item.signDate || '暂无'}}</text>
</view>
<view class="info-row">
<text class="info-label">到期时间:</text>
<text class="info-value">{{item.expiryDate}}</text>
<text class="info-value">{{item.expiryDate || '暂无'}}</text>
</view>
<view class="info-row">
<text class="info-label">联系电话:</text>
<text class="info-value">{{item.phone}}</text>
</view>
<view class="info-row">
<text class="info-label">申请单号:</text>
<text class="info-value">{{item.applicationNumber || '暂无'}}</text>
</view>
<view class="info-row">
<text class="info-label">资产类型:</text>
<text class="info-value">{{item.assetType || '暂无'}}</text>
</view>
<view class="info-row">
<text class="info-label">申请用途:</text>
<text class="info-value">{{item.purpose || '暂无'}}</text>
</view>
<view class="info-row">
<text class="info-label">产品名称:</text>
<text class="info-value">{{item.productName || '暂无'}}</text>
</view>
<view class="info-row">
<text class="info-label">已还金额:</text>
<text class="info-value">{{item.paidAmount || 0}}元</text>
</view>
<view class="info-row">
<text class="info-label">剩余金额:</text>
<text class="info-value">{{item.remainingAmount || 0}}元</text>
</view>
<view class="info-row">
<text class="info-label">还款进度:</text>
<text class="info-value">{{item.repaymentProgress || 0}}%</text>
</view>
<view class="info-row" wx:if="{{item.remark}}">
<text class="info-label">备注:</text>
<text class="info-value">{{item.remark}}</text>
</view>
</view>
</view>
</view>

View File

@@ -0,0 +1,316 @@
// pages/business/loan-products/form/form.js
const { apiService } = require('../../../../services/apiService');
const auth = require('../../../../utils/auth.js');
Page({
data: {
mode: 'add', // add 或 edit
productId: null,
loading: false,
formData: {
productName: '',
productCode: '',
productType: 'personal',
minLoanAmount: '',
maxLoanAmount: '',
minLoanTerm: '',
maxLoanTerm: '',
interestRate: '',
productDescription: '',
serviceArea: '',
servicePhone: '',
riskLevel: 'LOW',
requiredDocuments: '',
onSaleStatus: true
},
typeOptions: [
{ value: 'personal', label: '个人贷款' },
{ value: 'mortgage', label: '住房贷款' },
{ value: 'business', label: '企业贷款' },
{ value: 'agricultural', label: '农业贷款' },
{ value: 'livestock_collateral', label: '养殖抵押贷款' }
],
riskLevelOptions: [
{ value: 'LOW', label: '低风险' },
{ value: 'MEDIUM', label: '中风险' },
{ value: 'HIGH', label: '高风险' }
],
// 计算属性
productTypeIndex: 0,
riskLevelIndex: 0,
currentProductTypeLabel: '请选择产品类型',
currentRiskLevelLabel: '请选择风险等级'
},
onLoad(options) {
console.log('表单页面加载', options);
// 检查认证状态
if (!auth.isAuthenticated()) {
console.log('用户未登录,跳转到登录页面');
wx.reLaunch({
url: '/pages/login/login'
});
return;
}
const { mode, productId } = options;
console.log('表单模式:', mode, '商品ID:', productId);
this.setData({
mode: mode || 'add',
productId: productId || null
});
// 初始化计算属性
this.updatePickerIndexes();
if (mode === 'edit' && productId) {
this.loadProductDetail(productId);
}
},
// 更新选择器的索引和标签
updatePickerIndexes() {
const { formData, typeOptions, riskLevelOptions } = this.data;
// 更新产品类型索引
const productTypeIndex = typeOptions.findIndex(item => item.value === formData.productType);
const currentProductTypeLabel = productTypeIndex >= 0 ? typeOptions[productTypeIndex].label : '请选择产品类型';
// 更新风险等级索引
const riskLevelIndex = riskLevelOptions.findIndex(item => item.value === formData.riskLevel);
const currentRiskLevelLabel = riskLevelIndex >= 0 ? riskLevelOptions[riskLevelIndex].label : '请选择风险等级';
this.setData({
productTypeIndex: productTypeIndex >= 0 ? productTypeIndex : 0,
riskLevelIndex: riskLevelIndex >= 0 ? riskLevelIndex : 0,
currentProductTypeLabel,
currentRiskLevelLabel
});
},
// 加载商品详情(编辑模式)
async loadProductDetail(productId) {
try {
this.setData({ loading: true });
const response = await apiService.loanProducts.getDetail(productId);
if (response.success) {
const product = response.data;
this.setData({
formData: {
productName: product.productName || '',
productCode: product.productCode || '',
productType: product.productType || 'personal',
minLoanAmount: product.minLoanAmount || '',
maxLoanAmount: product.maxLoanAmount || '',
minLoanTerm: product.minLoanTerm || '',
maxLoanTerm: product.maxLoanTerm || '',
interestRate: product.interestRate || '',
productDescription: product.productDescription || '',
serviceArea: product.serviceArea || '',
servicePhone: product.servicePhone || '',
riskLevel: product.riskLevel || 'LOW',
requiredDocuments: product.requiredDocuments || '',
onSaleStatus: product.onSaleStatus !== false
}
});
// 更新选择器索引
this.updatePickerIndexes();
} else {
throw new Error(response.message || '获取商品详情失败');
}
} catch (error) {
console.error('加载商品详情失败:', error);
wx.showToast({
title: error.message || '加载失败',
icon: 'error'
});
} finally {
this.setData({ loading: false });
}
},
// 输入处理
onInputChange(e) {
const { field } = e.currentTarget.dataset;
const { value } = e.detail;
this.setData({
[`formData.${field}`]: value
});
},
// 选择器处理
onPickerChange(e) {
const { field } = e.currentTarget.dataset;
const { value } = e.detail;
let selectedValue = '';
let selectedLabel = '';
if (field === 'productType') {
selectedValue = this.data.typeOptions[value].value;
selectedLabel = this.data.typeOptions[value].label;
this.setData({
[`formData.${field}`]: selectedValue,
productTypeIndex: value,
currentProductTypeLabel: selectedLabel
});
} else if (field === 'riskLevel') {
selectedValue = this.data.riskLevelOptions[value].value;
selectedLabel = this.data.riskLevelOptions[value].label;
this.setData({
[`formData.${field}`]: selectedValue,
riskLevelIndex: value,
currentRiskLevelLabel: selectedLabel
});
}
},
// 开关处理
onSwitchChange(e) {
const { field } = e.currentTarget.dataset;
const { value } = e.detail;
this.setData({
[`formData.${field}`]: value
});
},
// 表单验证
validateForm() {
const { formData } = this.data;
if (!formData.productName.trim()) {
wx.showToast({
title: '请输入产品名称',
icon: 'none'
});
return false;
}
if (!formData.productType) {
wx.showToast({
title: '请选择产品类型',
icon: 'none'
});
return false;
}
if (!formData.minLoanAmount || !formData.maxLoanAmount) {
wx.showToast({
title: '请输入贷款额度范围',
icon: 'none'
});
return false;
}
if (parseFloat(formData.minLoanAmount) >= parseFloat(formData.maxLoanAmount)) {
wx.showToast({
title: '最小额度应小于最大额度',
icon: 'none'
});
return false;
}
if (!formData.interestRate) {
wx.showToast({
title: '请输入利率',
icon: 'none'
});
return false;
}
if (!formData.riskLevel) {
wx.showToast({
title: '请选择风险等级',
icon: 'none'
});
return false;
}
return true;
},
// 提交表单
async submitForm() {
if (!this.validateForm()) {
return;
}
try {
this.setData({ loading: true });
const { mode, productId, formData } = this.data;
// 转换字段名为后端期望的格式
const submitData = {
productName: formData.productName,
productCode: formData.productCode,
productType: formData.productType,
// 后端期望的字段名
loanAmount: formData.maxLoanAmount || formData.minLoanAmount, // 使用最大贷款额度作为贷款金额
loanTerm: formData.maxLoanTerm || formData.minLoanTerm, // 使用最大贷款期限作为贷款期限
minLoanAmount: formData.minLoanAmount,
maxLoanAmount: formData.maxLoanAmount,
minLoanTerm: formData.minLoanTerm,
maxLoanTerm: formData.maxLoanTerm,
interestRate: formData.interestRate,
productDescription: formData.productDescription,
serviceArea: formData.serviceArea,
servicePhone: formData.servicePhone,
riskLevel: formData.riskLevel,
requiredDocuments: formData.requiredDocuments,
onSaleStatus: formData.onSaleStatus
};
// 调试信息:打印发送的数据
console.log('发送的数据:', submitData);
console.log('必填字段检查:');
console.log('productName:', submitData.productName, '是否为空:', !submitData.productName);
console.log('loanAmount:', submitData.loanAmount, '是否为空:', !submitData.loanAmount);
console.log('loanTerm:', submitData.loanTerm, '是否为空:', !submitData.loanTerm);
console.log('interestRate:', submitData.interestRate, '是否为空:', !submitData.interestRate);
console.log('serviceArea:', submitData.serviceArea, '是否为空:', !submitData.serviceArea);
console.log('servicePhone:', submitData.servicePhone, '是否为空:', !submitData.servicePhone);
let response;
if (mode === 'add') {
response = await apiService.loanProducts.create(submitData);
} else {
response = await apiService.loanProducts.update(productId, submitData);
}
if (response.success) {
wx.showToast({
title: mode === 'add' ? '新增成功' : '修改成功',
icon: 'success'
});
setTimeout(() => {
wx.navigateBack();
}, 1500);
} else {
throw new Error(response.message || '操作失败');
}
} catch (error) {
console.error('提交失败:', error);
wx.showToast({
title: error.message || '操作失败',
icon: 'error'
});
} finally {
this.setData({ loading: false });
}
},
// 取消
cancel() {
wx.navigateBack();
}
});

View File

@@ -0,0 +1,6 @@
{
"navigationBarTitleText": "商品管理",
"navigationBarBackgroundColor": "#1890ff",
"navigationBarTextStyle": "white",
"backgroundColor": "#f5f5f5"
}

View File

@@ -0,0 +1,228 @@
<!--pages/business/loan-products/form/form.wxml-->
<view class="form-container">
<view class="form-header">
<text class="form-title">{{mode === 'add' ? '新增商品' : '修改信息'}}</text>
</view>
<view class="form-content">
<!-- 基本信息 -->
<view class="form-section">
<view class="section-title">基本信息</view>
<view class="form-item">
<text class="form-label">
产品名称
<text class="required-asterisk">*</text>
</text>
<input
class="form-input"
placeholder="请输入产品名称"
value="{{formData.productName}}"
data-field="productName"
bindinput="onInputChange"
/>
</view>
<view class="form-item">
<text class="form-label">产品代码</text>
<input
class="form-input"
placeholder="请输入产品代码"
value="{{formData.productCode}}"
data-field="productCode"
bindinput="onInputChange"
/>
</view>
<view class="form-item">
<text class="form-label">
产品类型
<text class="required-asterisk">*</text>
</text>
<picker
class="form-picker"
data-field="productType"
bindchange="onPickerChange"
value="{{productTypeIndex}}"
range="{{typeOptions}}"
range-key="label"
>
<view class="picker-text">
{{currentProductTypeLabel}}
</view>
</picker>
</view>
</view>
<!-- 贷款信息 -->
<view class="form-section">
<view class="section-title">贷款信息</view>
<view class="form-item">
<text class="form-label">
最小贷款额度
<text class="required-asterisk">*</text>
</text>
<input
class="form-input"
placeholder="请输入最小贷款额度"
type="number"
value="{{formData.minLoanAmount}}"
data-field="minLoanAmount"
bindinput="onInputChange"
/>
</view>
<view class="form-item">
<text class="form-label">
最大贷款额度
<text class="required-asterisk">*</text>
</text>
<input
class="form-input"
placeholder="请输入最大贷款额度"
type="number"
value="{{formData.maxLoanAmount}}"
data-field="maxLoanAmount"
bindinput="onInputChange"
/>
</view>
<view class="form-item">
<text class="form-label">最小贷款期限</text>
<input
class="form-input"
placeholder="请输入最小贷款期限(月)"
type="number"
value="{{formData.minLoanTerm}}"
data-field="minLoanTerm"
bindinput="onInputChange"
/>
</view>
<view class="form-item">
<text class="form-label">最大贷款期限</text>
<input
class="form-input"
placeholder="请输入最大贷款期限(月)"
type="number"
value="{{formData.maxLoanTerm}}"
data-field="maxLoanTerm"
bindinput="onInputChange"
/>
</view>
<view class="form-item">
<text class="form-label">
利率
<text class="required-asterisk">*</text>
</text>
<input
class="form-input"
placeholder="请输入利率(%"
type="digit"
value="{{formData.interestRate}}"
data-field="interestRate"
bindinput="onInputChange"
/>
</view>
</view>
<!-- 服务信息 -->
<view class="form-section">
<view class="section-title">服务信息</view>
<view class="form-item">
<text class="form-label">服务区域</text>
<input
class="form-input"
placeholder="请输入服务区域"
value="{{formData.serviceArea}}"
data-field="serviceArea"
bindinput="onInputChange"
/>
</view>
<view class="form-item">
<text class="form-label">服务电话</text>
<input
class="form-input"
placeholder="请输入服务电话"
value="{{formData.servicePhone}}"
data-field="servicePhone"
bindinput="onInputChange"
/>
</view>
<view class="form-item">
<text class="form-label">
风险等级
<text class="required-asterisk">*</text>
</text>
<picker
class="form-picker"
data-field="riskLevel"
bindchange="onPickerChange"
value="{{riskLevelIndex}}"
range="{{riskLevelOptions}}"
range-key="label"
>
<view class="picker-text">
{{currentRiskLevelLabel}}
</view>
</picker>
</view>
<view class="form-item">
<text class="form-label">所需材料</text>
<textarea
class="form-textarea"
placeholder="请输入所需材料"
value="{{formData.requiredDocuments}}"
data-field="requiredDocuments"
bindinput="onInputChange"
/>
</view>
<view class="form-item">
<text class="form-label">产品描述</text>
<textarea
class="form-textarea"
placeholder="请输入产品描述"
value="{{formData.productDescription}}"
data-field="productDescription"
bindinput="onInputChange"
/>
</view>
</view>
<!-- 状态设置 -->
<view class="form-section">
<view class="section-title">状态设置</view>
<view class="form-item switch-item">
<text class="form-label">在售状态</text>
<switch
checked="{{formData.onSaleStatus}}"
data-field="onSaleStatus"
bindchange="onSwitchChange"
/>
</view>
</view>
</view>
<!-- 操作按钮 -->
<view class="form-actions">
<button class="action-btn cancel-btn" bindtap="cancel">取消</button>
<button class="action-btn submit-btn" bindtap="submitForm" disabled="{{loading}}">
{{loading ? '提交中...' : (mode === 'add' ? '新增' : '修改')}}
</button>
</view>
<!-- 加载提示 -->
<view class="loading-overlay" wx:if="{{loading}}">
<view class="loading-content">
<text class="loading-text">加载中...</text>
</view>
</view>
</view>

View File

@@ -0,0 +1,193 @@
/* pages/business/loan-products/form/form.wxss */
.form-container {
min-height: 100vh;
background-color: #f5f5f5;
padding-bottom: 120rpx;
}
.form-header {
background-color: #fff;
padding: 30rpx;
border-bottom: 1rpx solid #eee;
}
.form-title {
font-size: 36rpx;
font-weight: 600;
color: #333;
}
.form-content {
padding: 20rpx;
}
.form-section {
background-color: #fff;
margin-bottom: 20rpx;
border-radius: 8rpx;
padding: 30rpx;
}
.section-title {
font-size: 32rpx;
font-weight: 500;
color: #333;
margin-bottom: 30rpx;
padding-bottom: 10rpx;
border-bottom: 2rpx solid #1890ff;
}
.form-item {
margin-bottom: 30rpx;
}
.form-item:last-child {
margin-bottom: 0;
}
.form-label {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 15rpx;
font-weight: 500;
}
/* 必填字段的*号样式 */
.required-asterisk {
color: #ff4d4f;
font-weight: bold;
margin-left: 4rpx;
}
.form-input {
width: 100%;
height: 80rpx;
background-color: #f8f8f8;
border: 2rpx solid #e0e0e0;
border-radius: 8rpx;
padding: 0 20rpx;
font-size: 28rpx;
color: #333;
box-sizing: border-box;
}
.form-input:focus {
border-color: #1890ff;
background-color: #fff;
}
.form-picker {
width: 100%;
height: 80rpx;
background-color: #f8f8f8;
border: 2rpx solid #e0e0e0;
border-radius: 8rpx;
padding: 0 20rpx;
display: flex;
align-items: center;
box-sizing: border-box;
}
.picker-text {
font-size: 28rpx;
color: #333;
flex: 1;
}
.form-textarea {
width: 100%;
min-height: 120rpx;
background-color: #f8f8f8;
border: 2rpx solid #e0e0e0;
border-radius: 8rpx;
padding: 20rpx;
font-size: 28rpx;
color: #333;
box-sizing: border-box;
}
.form-textarea:focus {
border-color: #1890ff;
background-color: #fff;
}
.switch-item {
display: flex;
align-items: center;
justify-content: space-between;
}
.form-actions {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #fff;
padding: 20rpx 30rpx;
border-top: 1rpx solid #eee;
display: flex;
gap: 20rpx;
}
.action-btn {
flex: 1;
height: 80rpx;
border-radius: 8rpx;
font-size: 32rpx;
font-weight: 500;
border: none;
display: flex;
align-items: center;
justify-content: center;
}
.cancel-btn {
background-color: #f5f5f5;
color: #666;
}
.cancel-btn:active {
background-color: #e8e8e8;
}
.submit-btn {
background-color: #1890ff;
color: #fff;
}
.submit-btn:active {
background-color: #40a9ff;
}
.submit-btn[disabled] {
background-color: #ccc;
color: #999;
}
.loading-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.loading-content {
background-color: #fff;
padding: 40rpx 60rpx;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
}
.loading-text {
font-size: 28rpx;
color: #333;
}

View File

@@ -1,5 +1,6 @@
// pages/business/loan-products/loan-products.js
const { apiService } = require('../../../services/apiService');
const auth = require('../../../utils/auth.js');
Page({
data: {
@@ -16,10 +17,26 @@ Page({
},
onLoad() {
// 检查认证状态
if (!auth.isAuthenticated()) {
wx.reLaunch({
url: '/pages/login/login'
});
return;
}
this.loadProducts();
},
onShow() {
// 检查认证状态
if (!auth.isAuthenticated()) {
wx.reLaunch({
url: '/pages/login/login'
});
return;
}
this.loadProducts();
},
@@ -40,8 +57,27 @@ Page({
if (response.success) {
const products = response.data.products.map(product => ({
...product,
statusText: product.status === 'active' ? '在售' : '停售',
typeText: this.data.typeMap[product.type] || product.type
// 字段映射 - 根据接口返回数据调整
name: product.productName || product.name,
code: product.productCode || product.code,
type: product.productType || product.type,
status: product.onSaleStatus ? 'active' : 'inactive',
statusText: product.onSaleStatus ? '在售' : '停售',
typeText: this.data.typeMap[product.productType || product.type] || (product.productType || product.type),
minAmount: product.minLoanAmount || product.minAmount,
maxAmount: product.maxLoanAmount || product.maxAmount,
minTerm: product.minLoanTerm || product.minTerm,
maxTerm: product.maxLoanTerm || product.maxTerm,
interestRate: product.interestRate || product.minInterestRate,
maxInterestRate: product.maxInterestRate || product.interestRate,
description: product.productDescription || product.description,
// 新增字段
serviceArea: product.serviceArea,
servicePhone: product.servicePhone,
riskLevel: product.riskLevel,
requiredDocuments: product.requiredDocuments,
supervisionCustomers: product.supervisionCustomers,
totalCustomers: product.totalCustomers
}));
this.setData({
@@ -127,6 +163,123 @@ Page({
});
},
// 新增商品
addProduct() {
console.log('点击新增商品按钮');
// 使用绝对路径跳转到表单页面
wx.navigateTo({
url: '/pages/business/loan-products/form/form?mode=add',
success: (res) => {
console.log('跳转到表单页面成功', res);
},
fail: (err) => {
console.error('跳转到表单页面失败', err);
console.error('错误详情:', JSON.stringify(err));
// 如果失败,尝试跳转到测试页面
wx.navigateTo({
url: '/pages/business/loan-products/test-form?mode=add',
success: (res) => {
console.log('跳转到测试页面成功', res);
},
fail: (err2) => {
console.error('跳转到测试页面也失败', err2);
wx.showModal({
title: '跳转失败',
content: `表单页面错误: ${err.errMsg}\n测试页面错误: ${err2.errMsg}`,
showCancel: false
});
}
});
}
});
},
// 编辑商品
editProduct(e) {
console.log('点击编辑商品按钮', e);
const productId = e.currentTarget.dataset.productId;
console.log('商品ID:', productId);
// 使用绝对路径跳转到表单页面
wx.navigateTo({
url: `/pages/business/loan-products/form/form?mode=edit&productId=${productId}`,
success: (res) => {
console.log('跳转到编辑页面成功', res);
},
fail: (err) => {
console.error('跳转到编辑页面失败', err);
console.error('错误详情:', JSON.stringify(err));
// 如果失败,尝试跳转到测试页面
wx.navigateTo({
url: `/pages/business/loan-products/test-form?mode=edit&productId=${productId}`,
success: (res) => {
console.log('跳转到测试页面成功', res);
},
fail: (err2) => {
console.error('跳转到测试页面也失败', err2);
wx.showModal({
title: '跳转失败',
content: `表单页面错误: ${err.errMsg}\n测试页面错误: ${err2.errMsg}`,
showCancel: false
});
}
});
}
});
},
// 删除商品(下架)
deleteProduct(e) {
console.log('点击删除商品按钮', e);
const productId = e.currentTarget.dataset.productId;
console.log('商品ID:', productId);
const product = this.data.products.find(p => p.id == productId);
wx.showModal({
title: '确认下架',
content: `确定要下架商品"${product?.name || '该商品'}"吗?`,
success: (res) => {
if (res.confirm) {
this.handleDeleteProduct(productId);
}
}
});
},
// 处理删除商品
async handleDeleteProduct(productId) {
try {
wx.showLoading({
title: '下架中...'
});
const response = await apiService.loanProducts.delete(productId);
if (response.success) {
wx.hideLoading();
wx.showToast({
title: '下架成功',
icon: 'success'
});
// 重新加载商品列表
this.loadProducts();
} else {
throw new Error(response.message || '下架失败');
}
} catch (error) {
console.error('下架商品失败:', error);
wx.hideLoading();
wx.showToast({
title: error.message || '下架失败',
icon: 'error'
});
}
},
// 下拉刷新
onPullDownRefresh() {
this.loadProducts().finally(() => {

View File

@@ -0,0 +1,3 @@
{
"usingComponents": {}
}

View File

@@ -12,7 +12,7 @@
</view>
</view>
<!-- 筛选栏 -->
<!-- 筛选栏和操作按钮 -->
<view class="filter-bar">
<picker
bindchange="onFilterChange"
@@ -26,6 +26,14 @@
<text class="arrow">></text>
</view>
</picker>
<!-- 新增商品按钮 -->
<view class="action-buttons">
<button class="add-btn" bindtap="addProduct">
<text class="btn-icon">+</text>
<text class="btn-text">新增商品</text>
</button>
</view>
</view>
<!-- 商品列表 -->
@@ -35,19 +43,18 @@
wx:for="{{products}}"
wx:key="id"
data-product-id="{{item.id}}"
bindtap="viewProductDetail"
>
<view class="product-header">
<view class="product-header" bindtap="viewProductDetail" data-product-id="{{item.id}}">
<text class="product-name">{{item.name}}</text>
<view class="product-status {{item.status}}">
<text>{{item.statusText}}</text>
</view>
</view>
<view class="product-info">
<view class="product-info" bindtap="viewProductDetail" data-product-id="{{item.id}}">
<view class="info-row">
<text class="info-label">产品代码:</text>
<text class="info-value">{{item.code}}</text>
<text class="info-value">{{item.code || '暂无'}}</text>
</view>
<view class="info-row">
@@ -62,18 +69,50 @@
<view class="info-row">
<text class="info-label">贷款期限:</text>
<text class="info-value">{{item.minTerm}} - {{item.maxTerm}}个月</text>
<text class="info-value">{{item.loanTerm || item.minTerm}}个月</text>
</view>
<view class="info-row">
<text class="info-label">利率范围</text>
<text class="info-value">{{item.interestRate}}% - {{item.maxInterestRate}}%</text>
<text class="info-label">利率:</text>
<text class="info-value">{{item.interestRate}}%</text>
</view>
<view class="info-row">
<text class="info-label">服务区域:</text>
<text class="info-value">{{item.serviceArea || '全国'}}</text>
</view>
<view class="info-row">
<text class="info-label">服务电话:</text>
<text class="info-value">{{item.servicePhone || '暂无'}}</text>
</view>
<view class="info-row">
<text class="info-label">风险等级:</text>
<text class="info-value">{{item.riskLevel || 'LOW'}}</text>
</view>
<view class="info-row">
<text class="info-label">监管客户:</text>
<text class="info-value">{{item.supervisionCustomers}}/{{item.totalCustomers}}</text>
</view>
</view>
<view class="product-desc">
<view class="product-desc" bindtap="viewProductDetail" data-product-id="{{item.id}}">
<text class="desc-text">{{item.description}}</text>
</view>
<!-- 操作按钮 -->
<view class="product-actions">
<button class="action-btn edit-btn" bindtap="editProduct" data-product-id="{{item.id}}">
<text class="btn-icon">✏️</text>
<text class="btn-text">修改信息</text>
</button>
<button class="action-btn delete-btn" bindtap="deleteProduct" data-product-id="{{item.id}}">
<text class="btn-icon">🗑️</text>
<text class="btn-text">产品下架</text>
</button>
</view>
</view>
</view>

View File

@@ -37,15 +37,48 @@
.filter-bar {
background-color: #fff;
border-bottom: 1rpx solid #eee;
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 30rpx;
}
.filter-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 30rpx;
font-size: 28rpx;
color: #333;
flex: 1;
}
.action-buttons {
margin-left: 20rpx;
}
.add-btn {
background-color: #1890ff;
color: #fff;
border: none;
border-radius: 25rpx;
padding: 12rpx 24rpx;
font-size: 24rpx;
display: flex;
align-items: center;
justify-content: center;
min-width: 120rpx;
}
.add-btn:active {
background-color: #40a9ff;
}
.btn-icon {
margin-right: 8rpx;
font-size: 20rpx;
}
.btn-text {
font-size: 24rpx;
}
.filter-value {
@@ -131,6 +164,50 @@
line-height: 1.4;
}
/* 商品操作按钮 */
.product-actions {
display: flex;
gap: 20rpx;
margin-top: 20rpx;
padding: 20rpx 0;
border-top: 1rpx solid #f0f0f0;
position: relative;
z-index: 5;
}
.action-btn {
flex: 1;
height: 80rpx;
border-radius: 8rpx;
font-size: 26rpx;
display: flex;
align-items: center;
justify-content: center;
border: none;
position: relative;
z-index: 10;
margin: 0 10rpx;
min-width: 120rpx;
}
.edit-btn {
background-color: #1890ff;
color: #fff;
}
.edit-btn:active {
background-color: #40a9ff;
}
.delete-btn {
background-color: #ff4d4f;
color: #fff;
}
.delete-btn:active {
background-color: #ff7875;
}
/* 加载状态 */
.loading {
text-align: center;

View File

@@ -0,0 +1,19 @@
// 测试表单页面
Page({
data: {
mode: '',
productId: ''
},
onLoad(options) {
console.log('测试表单页面加载', options);
this.setData({
mode: options.mode || '未知',
productId: options.productId || '无'
});
},
goBack() {
wx.navigateBack();
}
});

View File

@@ -0,0 +1,3 @@
{
"usingComponents": {}
}

View File

@@ -0,0 +1,16 @@
<!--测试表单页面-->
<view class="test-container">
<view class="test-header">
<text class="test-title">测试表单页面</text>
</view>
<view class="test-content">
<text class="test-text">这是一个测试页面</text>
<text class="test-text">模式: {{mode}}</text>
<text class="test-text">商品ID: {{productId}}</text>
</view>
<view class="test-actions">
<button class="test-btn" bindtap="goBack">返回</button>
</view>
</view>

View File

@@ -0,0 +1,46 @@
/* 测试表单页面样式 */
.test-container {
padding: 40rpx;
min-height: 100vh;
background: #f5f5f5;
}
.test-header {
background: #1890ff;
color: #fff;
padding: 40rpx;
border-radius: 16rpx;
margin-bottom: 40rpx;
}
.test-title {
font-size: 32rpx;
font-weight: 600;
}
.test-content {
background: #fff;
padding: 40rpx;
border-radius: 16rpx;
margin-bottom: 40rpx;
}
.test-text {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 20rpx;
}
.test-actions {
text-align: center;
}
.test-btn {
background: #1890ff;
color: #fff;
border: none;
border-radius: 8rpx;
padding: 20rpx 40rpx;
font-size: 28rpx;
}

View File

@@ -0,0 +1,3 @@
{
"usingComponents": {}
}

View File

@@ -65,6 +65,26 @@ Page({
},
onLoad() {
// 检查认证状态
if (!auth.isAuthenticated()) {
wx.reLaunch({
url: '/pages/login/login'
});
return;
}
this.initData()
},
onShow() {
// 检查认证状态
if (!auth.isAuthenticated()) {
wx.reLaunch({
url: '/pages/login/login'
});
return;
}
this.initData()
},

View File

@@ -7,11 +7,25 @@ Page({
username: '',
password: '',
showPassword: false,
loading: false
loading: false,
rememberMe: false
},
onLoad() {
// 检查是否已登录
if (auth.isAuthenticated()) {
wx.reLaunch({
url: '/pages/index/index'
})
return
}
// 尝试从本地存储恢复用户名
this.loadStoredUsername()
},
onShow() {
// 每次显示页面时检查登录状态
if (auth.isAuthenticated()) {
wx.reLaunch({
url: '/pages/index/index'
@@ -19,67 +33,185 @@ Page({
}
},
/**
* 加载存储的用户名
*/
loadStoredUsername() {
try {
const storedUsername = wx.getStorageSync('remembered_username')
if (storedUsername) {
this.setData({
username: storedUsername,
rememberMe: true
})
}
} catch (error) {
console.error('加载存储用户名错误:', error)
}
},
/**
* 用户名输入
*/
onUsernameInput(e) {
this.setData({
username: e.detail.value
username: e.detail.value.trim()
})
},
/**
* 密码输入
*/
onPasswordInput(e) {
this.setData({
password: e.detail.value
})
},
/**
* 切换密码显示
*/
togglePassword() {
this.setData({
showPassword: !this.data.showPassword
})
},
/**
* 切换记住我
*/
onRememberMeChange(e) {
this.setData({
rememberMe: e.detail.value
})
},
/**
* 处理登录
*/
async handleLogin() {
const { username, password, loading } = this.data
const { username, password, loading, rememberMe } = this.data
if (!username.trim() || !password.trim() || loading) return
// 参数验证
if (!username || !password || loading) {
if (!username) {
wx.showToast({
title: '请输入用户名',
icon: 'none'
})
} else if (!password) {
wx.showToast({
title: '请输入密码',
icon: 'none'
})
}
return
}
this.setData({ loading: true })
try {
console.log('开始登录:', username)
// 调用登录服务
const response = await authService.login(username, password)
if (response && response.token) {
if (response && response.success) {
// 保存token
auth.setToken(response.token)
if (response.token) {
auth.setToken(response.token)
}
// 获取用户信息
const userInfo = await authService.getUserInfo()
if (userInfo) {
auth.setUser(userInfo)
// 保存用户信息
if (response.user) {
auth.setUser(response.user)
// 设置全局用户信息
const app = getApp()
if (app && app.login) {
app.login(response.user)
}
} else {
// 如果没有用户信息,尝试获取
try {
const userInfo = await authService.getUserInfo()
if (userInfo) {
auth.setUser(userInfo)
// 设置全局用户信息
const app = getApp()
if (app && app.login) {
app.login(userInfo)
}
}
} catch (error) {
console.warn('获取用户信息失败:', error)
}
}
// 记住用户名
if (rememberMe) {
wx.setStorageSync('remembered_username', username)
} else {
wx.removeStorageSync('remembered_username')
}
// 显示成功提示
wx.showToast({
title: '登录成功',
title: response.message || '登录成功',
icon: 'success',
duration: 2000
})
// 跳转到首页
// 跳转到日检预警页面
setTimeout(() => {
wx.reLaunch({
url: '/pages/index/index'
url: '/pages/warning/warning'
})
}, 1000)
} else {
throw new Error(response.message || '登录失败')
}
} catch (error) {
console.error('登录失败:', error)
// 显示错误提示
wx.showToast({
title: error.message || '登录失败',
title: error.message || '登录失败,请检查用户名和密码',
icon: 'error',
duration: 3000
})
// 如果是认证错误,清除本地存储
if (error.message && error.message.includes('认证')) {
auth.clearAuth()
}
} finally {
this.setData({ loading: false })
}
},
/**
* 快速登录(测试用)
*/
quickLogin() {
this.setData({
username: 'admin',
password: '123456',
rememberMe: true
})
},
/**
* 重置表单
*/
resetForm() {
this.setData({
username: '',
password: '',
showPassword: false,
rememberMe: false
})
}
})

View File

@@ -32,6 +32,17 @@
</view>
</view>
<view class="form-item remember-item">
<label class="remember-label">
<checkbox
checked="{{rememberMe}}"
bindchange="onRememberMeChange"
class="remember-checkbox"
/>
<text class="remember-text">记住我</text>
</label>
</view>
<view class="form-item">
<button
class="login-btn {{(!username || !password || loading) ? 'disabled' : ''}}"
@@ -44,6 +55,9 @@
<view class="login-tips">
<text class="tips-text">默认账号admin / 123456</text>
<view class="quick-login" bindtap="quickLogin">
<text class="quick-login-text">快速登录</text>
</view>
</view>
</view>

View File

@@ -1,7 +1,7 @@
/* pages/login/login.wxss */
.login-container {
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background: #fff;
display: flex;
flex-direction: column;
align-items: center;
@@ -22,19 +22,20 @@
width: 120rpx;
height: 120rpx;
border-radius: 60rpx;
background: #fff;
background: #f0f0f0;
border: 2rpx solid #e0e0e0;
}
.title {
font-size: 48rpx;
font-weight: 600;
color: #fff;
color: #333;
margin-bottom: 16rpx;
}
.subtitle {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.8);
color: #666;
}
.login-form {
@@ -50,8 +51,8 @@
.input {
width: 100%;
height: 88rpx;
background: rgba(255, 255, 255, 0.9);
border: none;
background: #f8f8f8;
border: 2rpx solid #e0e0e0;
border-radius: 44rpx;
padding: 0 40rpx;
font-size: 32rpx;
@@ -94,14 +95,51 @@
color: #999;
}
.remember-item {
margin-bottom: 30rpx;
}
.remember-label {
display: flex;
align-items: center;
justify-content: flex-start;
}
.remember-checkbox {
margin-right: 16rpx;
transform: scale(0.8);
}
.remember-text {
font-size: 28rpx;
color: #666;
}
.login-tips {
text-align: center;
margin-top: 40rpx;
display: flex;
flex-direction: column;
align-items: center;
}
.tips-text {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.8);
color: #999;
margin-bottom: 20rpx;
}
.quick-login {
padding: 16rpx 32rpx;
background: #f0f0f0;
border-radius: 24rpx;
border: 2rpx solid #e0e0e0;
}
.quick-login-text {
font-size: 24rpx;
color: #666;
font-weight: 500;
}
.login-footer {
@@ -112,5 +150,5 @@
.footer-text {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.6);
color: #999;
}

View File

@@ -0,0 +1,203 @@
// pages/profile/change-password.js
const auth = require('../../utils/auth.js')
const authService = require('../../services/authService.js')
Page({
data: {
userInfo: null,
loading: false,
formData: {
oldPassword: '',
newPassword: '',
confirmPassword: ''
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
this.loadUserInfo()
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
// 检查登录状态
if (!auth.isAuthenticated()) {
wx.reLaunch({
url: '/pages/login/login'
})
return
}
this.loadUserInfo()
},
/**
* 加载用户信息
*/
loadUserInfo() {
try {
const userInfo = auth.getUser()
if (userInfo) {
this.setData({
userInfo: userInfo
})
}
} catch (error) {
console.error('加载用户信息错误:', error)
}
},
/**
* 输入框内容变化
*/
onInputChange(e) {
const field = e.currentTarget.dataset.field
const value = e.detail.value
this.setData({
[`formData.${field}`]: value
})
},
/**
* 表单验证
*/
validateForm() {
const { oldPassword, newPassword, confirmPassword } = this.data.formData
if (!oldPassword.trim()) {
wx.showToast({
title: '请输入旧密码',
icon: 'none'
})
return false
}
if (!newPassword.trim()) {
wx.showToast({
title: '请输入新密码',
icon: 'none'
})
return false
}
if (newPassword.length < 6) {
wx.showToast({
title: '新密码至少6位',
icon: 'none'
})
return false
}
if (newPassword === oldPassword) {
wx.showToast({
title: '新密码不能与旧密码相同',
icon: 'none'
})
return false
}
if (!confirmPassword.trim()) {
wx.showToast({
title: '请输入确认密码',
icon: 'none'
})
return false
}
if (newPassword !== confirmPassword) {
wx.showToast({
title: '两次输入的新密码不一致',
icon: 'none'
})
return false
}
return true
},
/**
* 确认修改密码
*/
async handleConfirm() {
if (!this.validateForm()) {
return
}
if (this.data.loading) {
return
}
this.setData({ loading: true })
try {
const { oldPassword, newPassword } = this.data.formData
// 调用修改密码接口
const result = await authService.changePassword({
oldPassword,
newPassword
})
if (result.success) {
wx.showToast({
title: '密码修改成功',
icon: 'success',
duration: 2000
})
// 延迟返回上一页
setTimeout(() => {
wx.navigateBack()
}, 1500)
} else {
throw new Error(result.message || '修改密码失败')
}
} catch (error) {
console.error('修改密码错误:', error)
wx.showToast({
title: error.message || '修改密码失败',
icon: 'error'
})
} finally {
this.setData({ loading: false })
}
},
/**
* 重置表单
*/
handleReset() {
if (this.data.loading) {
return
}
this.setData({
formData: {
oldPassword: '',
newPassword: '',
confirmPassword: ''
}
})
wx.showToast({
title: '已重置',
icon: 'success',
duration: 1000
})
},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
return {
title: '银行管理系统',
path: '/pages/index/index'
}
}
})

View File

@@ -0,0 +1,7 @@
{
"usingComponents": {},
"navigationBarTitleText": "修改密码",
"navigationBarBackgroundColor": "#1890ff",
"navigationBarTextStyle": "white",
"backgroundColor": "#f5f5f5"
}

View File

@@ -0,0 +1,68 @@
<!--pages/profile/change-password.wxml-->
<view class="change-password-container">
<!-- 页面标题 -->
<view class="page-header">
<view class="header-title">修改密码</view>
</view>
<!-- 表单内容 -->
<view class="form-container">
<view class="form-item">
<text class="form-label">用户名</text>
<view class="form-value">{{userInfo.username || '刘超'}}</view>
</view>
<view class="form-item">
<text class="form-label">旧密码</text>
<input
class="form-input"
type="password"
placeholder="请输入旧密码"
value="{{formData.oldPassword}}"
data-field="oldPassword"
bindinput="onInputChange"
/>
</view>
<view class="form-item">
<text class="form-label">新密码</text>
<input
class="form-input"
type="password"
placeholder="请输入新密码"
value="{{formData.newPassword}}"
data-field="newPassword"
bindinput="onInputChange"
/>
</view>
<view class="form-item">
<text class="form-label">确认新密码</text>
<input
class="form-input"
type="password"
placeholder="请输入确认密码"
value="{{formData.confirmPassword}}"
data-field="confirmPassword"
bindinput="onInputChange"
/>
</view>
</view>
<!-- 操作按钮 -->
<view class="button-container">
<button class="btn confirm-btn" bindtap="handleConfirm" disabled="{{loading}}">
{{loading ? '修改中...' : '确认'}}
</button>
<button class="btn reset-btn" bindtap="handleReset" disabled="{{loading}}">
重置
</button>
</view>
</view>
<!-- 加载提示 -->
<view class="loading-overlay" wx:if="{{loading}}">
<view class="loading-content">
<text class="loading-text">修改中...</text>
</view>
</view>

View File

@@ -0,0 +1,141 @@
/* pages/profile/change-password.wxss */
.change-password-container {
min-height: 100vh;
background: #f5f5f5;
padding: 0 0 40rpx 0;
}
/* 页面标题 */
.page-header {
background: #1890ff;
padding: 40rpx 40rpx 30rpx 40rpx;
text-align: center;
}
.header-title {
font-size: 36rpx;
font-weight: 600;
color: #fff;
}
/* 表单容器 */
.form-container {
background: #fff;
margin: 20rpx;
border-radius: 16rpx;
padding: 40rpx;
}
.form-item {
margin-bottom: 40rpx;
display: flex;
flex-direction: column;
}
.form-item:last-child {
margin-bottom: 0;
}
.form-label {
font-size: 28rpx;
color: #333;
margin-bottom: 16rpx;
font-weight: 500;
}
.form-value {
font-size: 32rpx;
color: #666;
padding: 24rpx 0;
border-bottom: 1rpx solid #f0f0f0;
}
.form-input {
font-size: 32rpx;
color: #333;
padding: 24rpx 0;
border-bottom: 1rpx solid #e8e8e8;
background: transparent;
}
.form-input:focus {
border-bottom-color: #1890ff;
}
/* 按钮容器 */
.button-container {
padding: 40rpx;
display: flex;
gap: 24rpx;
}
.btn {
flex: 1;
height: 88rpx;
border-radius: 16rpx;
font-size: 32rpx;
font-weight: 500;
display: flex;
align-items: center;
justify-content: center;
border: none;
position: relative;
}
.confirm-btn {
background: #1890ff;
color: #fff;
}
.confirm-btn:active {
background: #40a9ff;
}
.confirm-btn[disabled] {
background: #d9d9d9;
color: #999;
}
.reset-btn {
background: #fff;
color: #666;
border: 2rpx solid #d9d9d9;
}
.reset-btn:active {
background: #f5f5f5;
}
.reset-btn[disabled] {
background: #f5f5f5;
color: #999;
border-color: #e8e8e8;
}
/* 加载提示 */
.loading-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.loading-content {
background: #fff;
padding: 40rpx 60rpx;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
}
.loading-text {
font-size: 28rpx;
color: #333;
}

View File

@@ -0,0 +1,66 @@
// pages/profile/change-password/change-password.js
Page({
/**
* 页面的初始数据
*/
data: {
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
}
})

View File

@@ -0,0 +1,3 @@
{
"usingComponents": {}
}

View File

@@ -0,0 +1,2 @@
<!--pages/profile/change-password/change-password.wxml-->
<text>pages/profile/change-password/change-password.wxml</text>

View File

@@ -0,0 +1 @@
/* pages/profile/change-password/change-password.wxss */

View File

@@ -1,66 +1,225 @@
// pages/profile/profile.js
const auth = require('../../utils/auth.js')
const authService = require('../../services/authService.js')
Page({
/**
* 页面的初始数据
*/
data: {
userInfo: null,
loading: false
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {
this.loadUserInfo()
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
// 检查登录状态
if (!auth.isAuthenticated()) {
wx.reLaunch({
url: '/pages/login/login'
})
return
}
this.loadUserInfo()
},
/**
* 生命周期函数--监听页面隐藏
* 加载用户信息
*/
onHide() {
loadUserInfo() {
try {
const userInfo = auth.getUser()
if (userInfo) {
this.setData({
userInfo: userInfo
})
} else {
// 如果没有用户信息,尝试获取
this.refreshUserInfo()
}
} catch (error) {
console.error('加载用户信息错误:', error)
}
},
/**
* 生命周期函数--监听页面卸载
* 刷新用户信息
*/
onUnload() {
async refreshUserInfo() {
if (this.data.loading) return
this.setData({ loading: true })
try {
const userInfo = await authService.refreshUserInfo()
this.setData({
userInfo: userInfo
})
} catch (error) {
console.error('刷新用户信息错误:', error)
wx.showToast({
title: '获取用户信息失败',
icon: 'error'
})
} finally {
this.setData({ loading: false })
}
},
/**
* 编辑个人信息
*/
editProfile() {
wx.showToast({
title: '功能开发中',
icon: 'none'
})
},
/**
* 修改密码
*/
changePassword() {
wx.navigateTo({
url: '/pages/profile/change-password'
})
},
/**
* 硬件管理
*/
hardwareManagement() {
wx.showToast({
title: '硬件管理功能开发中',
icon: 'none'
})
},
/**
* 人员管理
*/
personnelManagement() {
wx.showToast({
title: '人员管理功能开发中',
icon: 'none'
})
},
/**
* 关于我们
*/
aboutUs() {
wx.showModal({
title: '关于银行管理系统',
content: '版本1.0.0\n\n银行管理系统小程序提供项目管理和业务处理功能。',
showCancel: false,
confirmText: '确定'
})
},
/**
* 退出登录
*/
logout() {
wx.showModal({
title: '确认退出',
content: '确定要退出登录吗?',
success: (res) => {
if (res.confirm) {
this.handleLogout()
}
}
})
},
/**
* 处理退出登录
*/
async handleLogout() {
try {
// 显示加载提示
wx.showLoading({
title: '退出中...'
})
// 调用登出API
await authService.logout()
// 清除本地认证信息
auth.clearAuth()
// 调用全局登出方法
const app = getApp()
if (app && app.logout) {
app.logout()
}
// 隐藏加载提示
wx.hideLoading()
// 显示成功提示
wx.showToast({
title: '已退出登录',
icon: 'success',
duration: 2000
})
// 跳转到登录页
setTimeout(() => {
wx.reLaunch({
url: '/pages/login/login'
})
}, 1000)
} catch (error) {
console.error('退出登录错误:', error)
// 即使API调用失败也清除本地数据并跳转
auth.clearAuth()
// 调用全局登出方法
const app = getApp()
if (app && app.logout) {
app.logout()
}
wx.hideLoading()
wx.showToast({
title: '已退出登录',
icon: 'success',
duration: 2000
})
setTimeout(() => {
wx.reLaunch({
url: '/pages/login/login'
})
}, 1000)
}
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
this.refreshUserInfo()
wx.stopPullDownRefresh()
},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
return {
title: '银行管理系统',
path: '/pages/index/index'
}
}
})

View File

@@ -1,2 +1,63 @@
<!--pages/profile/profile.wxml-->
<text>pages/profile/profile.wxml</text>
<view class="profile-container">
<!-- 用户信息头部 -->
<view class="profile-header">
<view class="user-avatar">
<image src="/images/avatar.png" class="avatar-img" />
</view>
<view class="user-info">
<view class="username">{{userInfo.username || userInfo.name || '用户'}}</view>
<view class="user-role">{{userInfo.role || '管理员'}}</view>
</view>
<view class="edit-btn" bindtap="editProfile">
<text class="edit-icon">✏️</text>
</view>
</view>
<!-- 功能菜单 -->
<view class="menu-section">
<view class="menu-item" bindtap="changePassword">
<view class="menu-icon">🔒</view>
<view class="menu-text">修改密码</view>
<view class="menu-arrow">></view>
</view>
<view class="menu-item" bindtap="hardwareManagement">
<view class="menu-icon">⚙️</view>
<view class="menu-text">硬件管理</view>
<view class="menu-arrow">></view>
</view>
<view class="menu-item" bindtap="personnelManagement">
<view class="menu-icon">👥</view>
<view class="menu-text">人员管理</view>
<view class="menu-arrow">></view>
</view>
<view class="menu-item" bindtap="aboutUs">
<view class="menu-icon"></view>
<view class="menu-text">关于我们</view>
<view class="menu-arrow">></view>
</view>
</view>
<!-- 退出登录 -->
<view class="logout-section">
<button class="logout-btn" bindtap="logout">
<text class="logout-icon">🚪</text>
<text class="logout-text">退出登录</text>
</button>
</view>
<!-- 版本信息 -->
<view class="version-info">
<text class="version-text">版本 1.0.0</text>
</view>
</view>
<!-- 加载提示 -->
<view class="loading-overlay" wx:if="{{loading}}">
<view class="loading-content">
<text class="loading-text">加载中...</text>
</view>
</view>

View File

@@ -0,0 +1,172 @@
/* pages/profile/profile.wxss */
.profile-container {
min-height: 100vh;
background: #f5f5f5;
padding: 0 0 40rpx 0;
}
/* 用户信息头部 */
.profile-header {
background: #1890ff;
padding: 60rpx 40rpx 40rpx 40rpx;
display: flex;
align-items: center;
position: relative;
}
.user-avatar {
margin-right: 30rpx;
}
.avatar-img {
width: 120rpx;
height: 120rpx;
border-radius: 60rpx;
background: rgba(255, 255, 255, 0.2);
border: 4rpx solid rgba(255, 255, 255, 0.3);
}
.user-info {
flex: 1;
}
.username {
font-size: 36rpx;
font-weight: 600;
color: #fff;
margin-bottom: 8rpx;
}
.user-role {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.8);
}
.edit-btn {
position: absolute;
top: 60rpx;
right: 40rpx;
padding: 20rpx;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
}
.edit-icon {
font-size: 32rpx;
color: #fff;
}
/* 功能菜单 */
.menu-section {
background: #fff;
margin: 20rpx 0;
border-radius: 16rpx;
overflow: hidden;
}
.menu-item {
display: flex;
align-items: center;
padding: 32rpx 40rpx;
border-bottom: 1rpx solid #f0f0f0;
position: relative;
}
.menu-item:last-child {
border-bottom: none;
}
.menu-item:active {
background: #f8f8f8;
}
.menu-icon {
font-size: 36rpx;
margin-right: 24rpx;
width: 40rpx;
text-align: center;
}
.menu-text {
flex: 1;
font-size: 32rpx;
color: #333;
}
.menu-arrow {
font-size: 28rpx;
color: #999;
}
/* 退出登录 */
.logout-section {
margin: 20rpx 0;
padding: 0 0 40rpx 0;
}
.logout-btn {
width: 100%;
height: 88rpx;
background: #fff;
border: 2rpx solid #ff4d4f;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
color: #ff4d4f;
font-weight: 500;
margin: 0 0 0 0;
}
.logout-btn:active {
background: #fff2f0;
}
.logout-icon {
font-size: 32rpx;
margin-right: 16rpx;
}
.logout-text {
font-size: 32rpx;
}
/* 版本信息 */
.version-info {
text-align: center;
padding: 40rpx 0;
}
.version-text {
font-size: 24rpx;
color: #999;
}
/* 加载提示 */
.loading-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.loading-content {
background: #fff;
padding: 40rpx 60rpx;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
}
.loading-text {
font-size: 28rpx;
color: #333;
}

View File

@@ -1,5 +1,6 @@
// pages/projects/projects.js
const { apiService } = require('../../services/apiService');
const auth = require('../../utils/auth.js');
Page({
data: {
@@ -14,10 +15,26 @@ Page({
},
onLoad() {
// 检查认证状态
if (!auth.isAuthenticated()) {
wx.reLaunch({
url: '/pages/login/login'
});
return;
}
this.loadProjects();
},
onShow() {
// 检查认证状态
if (!auth.isAuthenticated()) {
wx.reLaunch({
url: '/pages/login/login'
});
return;
}
this.loadProjects();
},

View File

@@ -1,4 +1,6 @@
// pages/warning/warning.js
const auth = require('../../utils/auth.js');
Page({
data: {
farms: [
@@ -46,10 +48,26 @@ Page({
},
onLoad() {
// 检查认证状态
if (!auth.isAuthenticated()) {
wx.reLaunch({
url: '/pages/login/login'
});
return;
}
console.log('日检预警页面加载');
},
onShow() {
// 检查认证状态
if (!auth.isAuthenticated()) {
wx.reLaunch({
url: '/pages/login/login'
});
return;
}
console.log('日检预警页面显示');
},

View File

@@ -44,7 +44,7 @@
},
"compileType": "miniprogram",
"libVersion": "3.10.1",
"appid": "wx1b9c7cd2d0e0bfd3",
"appid": "wxa634eb5e3f0ab518",
"projectname": "bank-mini-program",
"isGameTourist": false,
"condition": {

View File

@@ -1,5 +1,5 @@
// 银行端小程序API服务层
const API_BASE_URL = 'https://ad.ningmuyun.com';
const API_BASE_URL = 'https://ad.ningmuyun.com/bank/api';
// 获取存储的token
const getToken = () => {
@@ -95,7 +95,7 @@ const apiService = {
// 登录
login: (username, password) => {
return request({
url: '/api/auth/login',
url: '/auth/login',
method: 'POST',
data: { username, password }
});
@@ -104,7 +104,7 @@ const apiService = {
// 获取当前用户信息
getCurrentUser: () => {
return request({
url: '/api/auth/me',
url: '/auth/me',
method: 'GET'
});
},
@@ -112,9 +112,18 @@ const apiService = {
// 登出
logout: () => {
return request({
url: '/api/auth/logout',
url: '/auth/logout',
method: 'POST'
});
},
// 修改密码
changePassword: (passwordData) => {
return request({
url: '/auth/change-password',
method: 'POST',
data: passwordData
});
}
},
@@ -123,7 +132,7 @@ const apiService = {
// 获取项目列表
getList: (params = {}) => {
return request({
url: '/api/projects',
url: '/projects',
method: 'GET',
data: params
});
@@ -132,7 +141,7 @@ const apiService = {
// 获取项目详情
getById: (id) => {
return request({
url: `/api/projects/${id}`,
url: `/projects/${id}`,
method: 'GET'
});
},
@@ -140,7 +149,7 @@ const apiService = {
// 创建项目
create: (data) => {
return request({
url: '/api/projects',
url: '/projects',
method: 'POST',
data
});
@@ -149,7 +158,7 @@ const apiService = {
// 更新项目
update: (id, data) => {
return request({
url: `/api/projects/${id}`,
url: `/projects/${id}`,
method: 'PUT',
data
});
@@ -158,7 +167,7 @@ const apiService = {
// 删除项目
delete: (id) => {
return request({
url: `/api/projects/${id}`,
url: `/projects/${id}`,
method: 'DELETE'
});
},
@@ -166,7 +175,7 @@ const apiService = {
// 获取项目统计
getStats: () => {
return request({
url: '/api/projects/stats',
url: '/projects/stats',
method: 'GET'
});
}
@@ -177,7 +186,7 @@ const apiService = {
// 获取监管任务列表
getList: (params = {}) => {
return request({
url: '/api/supervision-tasks',
url: '/supervision-tasks',
method: 'GET',
data: params
});
@@ -186,7 +195,7 @@ const apiService = {
// 获取监管任务详情
getById: (id) => {
return request({
url: `/api/supervision-tasks/${id}`,
url: `/supervision-tasks/${id}`,
method: 'GET'
});
}
@@ -197,7 +206,7 @@ const apiService = {
// 获取贷款商品列表
getList: (params = {}) => {
return request({
url: '/api/loan-products',
url: '/loan-products',
method: 'GET',
data: params
});
@@ -206,7 +215,7 @@ const apiService = {
// 获取贷款商品详情
getById: (id) => {
return request({
url: `/api/loan-products/${id}`,
url: `/loan-products/${id}`,
method: 'GET'
});
},
@@ -214,9 +223,52 @@ const apiService = {
// 获取贷款商品统计
getStats: () => {
return request({
url: '/api/loan-products/stats',
url: '/loan-products/stats',
method: 'GET'
});
},
// 获取贷款商品详情(别名)
getDetail: (id) => {
return request({
url: `/loan-products/${id}`,
method: 'GET'
});
},
// 创建贷款商品
create: (data) => {
return request({
url: '/loan-products',
method: 'POST',
data: data
});
},
// 更新贷款商品
update: (id, data) => {
return request({
url: `/loan-products/${id}`,
method: 'PUT',
data: data
});
},
// 删除贷款商品(下架)
delete: (id) => {
return request({
url: `/loan-products/${id}`,
method: 'DELETE'
});
},
// 上架/下架商品
toggleStatus: (id, status) => {
return request({
url: `/loan-products/${id}/status`,
method: 'PATCH',
data: { onSaleStatus: status }
});
}
},
@@ -225,7 +277,7 @@ const apiService = {
// 获取贷款申请列表
getList: (params = {}) => {
return request({
url: '/api/loan-applications',
url: '/loan-applications',
method: 'GET',
data: params
});
@@ -234,7 +286,7 @@ const apiService = {
// 获取贷款申请详情
getById: (id) => {
return request({
url: `/api/loan-applications/${id}`,
url: `/loan-applications/${id}`,
method: 'GET'
});
},
@@ -242,7 +294,7 @@ const apiService = {
// 获取申请统计
getStats: () => {
return request({
url: '/api/loan-applications/stats',
url: '/loan-applications/stats',
method: 'GET'
});
}
@@ -253,7 +305,7 @@ const apiService = {
// 获取贷款合同列表
getList: (params = {}) => {
return request({
url: '/api/loan-contracts',
url: '/loan-contracts',
method: 'GET',
data: params
});
@@ -262,7 +314,7 @@ const apiService = {
// 获取贷款合同详情
getById: (id) => {
return request({
url: `/api/loan-contracts/${id}`,
url: `/loan-contracts/${id}`,
method: 'GET'
});
},
@@ -270,7 +322,7 @@ const apiService = {
// 获取合同统计
getStats: () => {
return request({
url: '/api/loan-contracts/stats',
url: '/loan-contracts/stats',
method: 'GET'
});
}
@@ -281,7 +333,7 @@ const apiService = {
// 获取贷款解押列表
getList: (params = {}) => {
return request({
url: '/api/loan-releases',
url: '/loan-releases',
method: 'GET',
data: params
});
@@ -290,7 +342,7 @@ const apiService = {
// 获取贷款解押详情
getById: (id) => {
return request({
url: `/api/loan-releases/${id}`,
url: `/loan-releases/${id}`,
method: 'GET'
});
},
@@ -298,7 +350,7 @@ const apiService = {
// 获取解押统计
getStats: () => {
return request({
url: '/api/loan-releases/stats',
url: '/loan-releases/stats',
method: 'GET'
});
}

View File

@@ -1,19 +1,218 @@
// 认证相关API
const request = require('../utils/request.js')
/**
* 银行端小程序认证服务
* @file authService.js
* @description 处理用户登录、登出、用户信息管理等功能
*/
const { apiService } = require('./apiService.js')
// 认证服务
const authService = {
// 用户登录
login(username, password) {
return request.post('/auth/login', {
username,
password
})
/**
* 用户登录
* @param {string} username - 用户名
* @param {string} password - 密码
* @returns {Promise} 登录结果
*/
async login(username, password) {
try {
console.log('开始登录:', username)
// 参数验证
if (!username || !password) {
throw new Error('用户名和密码不能为空')
}
if (username.length < 3) {
throw new Error('用户名至少3个字符')
}
if (password.length < 6) {
throw new Error('密码至少6个字符')
}
// 调用登录API
const response = await apiService.auth.login(username, password)
console.log('登录响应:', response)
if (response && response.success && response.data) {
return {
success: true,
token: response.data.token,
user: response.data.user,
message: response.message || '登录成功'
}
} else {
throw new Error(response.message || '登录失败')
}
} catch (error) {
console.error('登录错误:', error)
throw error
}
},
// 获取用户信息
getUserInfo() {
return request.get('/auth/userinfo')
/**
* 获取当前用户信息
* @returns {Promise} 用户信息
*/
async getUserInfo() {
try {
const response = await apiService.auth.getCurrentUser()
if (response && response.success && response.data) {
return response.data
} else {
throw new Error(response.message || '获取用户信息失败')
}
} catch (error) {
console.error('获取用户信息错误:', error)
throw error
}
},
/**
* 用户登出
* @returns {Promise} 登出结果
*/
async logout() {
try {
console.log('开始登出')
// 调用登出API
const response = await apiService.auth.logout()
console.log('登出响应:', response)
return {
success: true,
message: '登出成功'
}
} catch (error) {
console.error('登出错误:', error)
// 即使API调用失败也认为登出成功清除本地数据
return {
success: true,
message: '登出成功'
}
}
},
/**
* 刷新用户信息
* @returns {Promise} 用户信息
*/
async refreshUserInfo() {
try {
const userInfo = await this.getUserInfo()
// 更新本地存储的用户信息
wx.setStorageSync('bank_user', userInfo)
return userInfo
} catch (error) {
console.error('刷新用户信息错误:', error)
throw error
}
},
/**
* 检查登录状态
* @returns {boolean} 是否已登录
*/
isLoggedIn() {
const token = wx.getStorageSync('bank_token')
const user = wx.getStorageSync('bank_user')
return !!(token && user)
},
/**
* 获取存储的用户信息
* @returns {Object|null} 用户信息
*/
getStoredUser() {
try {
return wx.getStorageSync('bank_user') || null
} catch (error) {
console.error('获取存储用户信息错误:', error)
return null
}
},
/**
* 获取存储的token
* @returns {string|null} token
*/
getStoredToken() {
try {
return wx.getStorageSync('bank_token') || null
} catch (error) {
console.error('获取存储token错误:', error)
return null
}
},
/**
* 修改密码
* @param {Object} passwordData - 密码数据
* @param {string} passwordData.oldPassword - 旧密码
* @param {string} passwordData.newPassword - 新密码
* @returns {Promise} 修改结果
*/
async changePassword(passwordData) {
try {
console.log('开始修改密码')
const { oldPassword, newPassword } = passwordData
// 参数验证
if (!oldPassword || !newPassword) {
throw new Error('旧密码和新密码不能为空')
}
if (newPassword.length < 6) {
throw new Error('新密码至少6个字符')
}
if (oldPassword === newPassword) {
throw new Error('新密码不能与旧密码相同')
}
// 调用修改密码API
const response = await apiService.auth.changePassword({
oldPassword,
newPassword
})
console.log('修改密码响应:', response)
if (response && response.success) {
return {
success: true,
message: response.message || '密码修改成功'
}
} else {
throw new Error(response.message || '修改密码失败')
}
} catch (error) {
console.error('修改密码错误:', error)
throw error
}
},
/**
* 清除所有认证信息
*/
clearAuth() {
try {
wx.removeStorageSync('bank_token')
wx.removeStorageSync('bank_user')
console.log('认证信息已清除')
} catch (error) {
console.error('清除认证信息错误:', error)
}
}
}
module.exports = authService
module.exports = authService

View File

@@ -1,73 +1,208 @@
/**
* 银行端小程序认证工具
* @file auth.js
* @description 处理认证状态管理、token管理等功能
*/
// 认证工具类
const auth = {
// 设置token
// 存储键名
TOKEN_KEY: 'bank_token',
USER_KEY: 'bank_user',
/**
* 设置token
* @param {string} token - 认证token
*/
setToken(token) {
try {
wx.setStorageSync('bank_token', token)
if (!token) {
console.warn('设置token失败: token为空')
return false
}
wx.setStorageSync(this.TOKEN_KEY, token)
console.log('Token已保存')
return true
} catch (error) {
console.error('设置token失败:', error)
console.error('设置token错误:', error)
return false
}
},
// 获取token
/**
* 获取token
* @returns {string|null} token
*/
getToken() {
try {
return wx.getStorageSync('bank_token') || ''
return wx.getStorageSync(this.TOKEN_KEY) || null
} catch (error) {
console.error('获取token失败:', error)
return ''
}
},
// 清除token
clearToken() {
try {
wx.removeStorageSync('bank_token')
wx.removeStorageSync('bank_user')
return true
} catch (error) {
console.error('清除token失败:', error)
return false
}
},
// 设置用户信息
setUser(user) {
try {
wx.setStorageSync('bank_user', user)
return true
} catch (error) {
console.error('设置用户信息失败:', error)
return false
}
},
// 获取用户信息
getUser() {
try {
return wx.getStorageSync('bank_user') || null
} catch (error) {
console.error('获取用户信息失败:', error)
console.error('获取token错误:', error)
return null
}
},
// 检查是否已登录
/**
* 设置用户信息
* @param {Object} user - 用户信息
*/
setUser(user) {
try {
if (!user) {
console.warn('设置用户信息失败: 用户信息为空')
return false
}
wx.setStorageSync(this.USER_KEY, user)
console.log('用户信息已保存:', user.username || user.name)
return true
} catch (error) {
console.error('设置用户信息错误:', error)
return false
}
},
/**
* 获取用户信息
* @returns {Object|null} 用户信息
*/
getUser() {
try {
return wx.getStorageSync(this.USER_KEY) || null
} catch (error) {
console.error('获取用户信息错误:', error)
return null
}
},
/**
* 检查是否已认证
* @returns {boolean} 是否已认证
*/
isAuthenticated() {
const token = this.getToken()
const user = this.getUser()
return !!(token && user)
},
// 登出
logout() {
this.clearToken()
wx.reLaunch({
url: '/pages/login/login'
})
/**
* 获取用户角色
* @returns {string|null} 用户角色
*/
getUserRole() {
try {
const user = this.getUser()
return user ? user.role : null
} catch (error) {
console.error('获取用户角色错误:', error)
return null
}
},
/**
* 获取用户名
* @returns {string|null} 用户名
*/
getUsername() {
try {
const user = this.getUser()
return user ? (user.username || user.name) : null
} catch (error) {
console.error('获取用户名错误:', error)
return null
}
},
/**
* 清除认证信息
*/
clearAuth() {
try {
wx.removeStorageSync(this.TOKEN_KEY)
wx.removeStorageSync(this.USER_KEY)
console.log('认证信息已清除')
} catch (error) {
console.error('清除认证信息错误:', error)
}
},
/**
* 登出
* @param {boolean} showToast - 是否显示提示
*/
logout(showToast = true) {
try {
// 清除本地认证信息
this.clearAuth()
if (showToast) {
wx.showToast({
title: '已退出登录',
icon: 'success',
duration: 2000
})
}
// 跳转到登录页
setTimeout(() => {
wx.reLaunch({
url: '/pages/login/login'
})
}, showToast ? 1000 : 0)
} catch (error) {
console.error('登出错误:', error)
}
},
/**
* 检查token是否过期
* @returns {boolean} token是否过期
*/
isTokenExpired() {
try {
const token = this.getToken()
if (!token) return true
// 简单的token过期检查实际项目中应该解析JWT token
// 这里可以根据实际需求实现更复杂的过期检查
return false
} catch (error) {
console.error('检查token过期错误:', error)
return true
}
},
/**
* 更新用户信息
* @param {Object} newUserInfo - 新的用户信息
*/
updateUser(newUserInfo) {
try {
if (!newUserInfo) {
console.warn('更新用户信息失败: 新用户信息为空')
return false
}
const currentUser = this.getUser()
if (!currentUser) {
console.warn('更新用户信息失败: 当前用户信息不存在')
return false
}
// 合并用户信息
const updatedUser = { ...currentUser, ...newUserInfo }
this.setUser(updatedUser)
console.log('用户信息已更新')
return true
} catch (error) {
console.error('更新用户信息错误:', error)
return false
}
}
}
module.exports = auth
module.exports = auth