修改政府端前端,银行端小程序和后端接口
This commit is contained in:
@@ -1,186 +0,0 @@
|
||||
# 前后端集成实现计划
|
||||
|
||||
## 前端页面分析
|
||||
|
||||
根据前端页面分析,需要实现以下模块的后端API接口:
|
||||
|
||||
### 1. 用户管理模块 (Users.vue)
|
||||
**功能需求**:
|
||||
- 用户列表查询(分页、搜索、筛选)
|
||||
- 用户创建、编辑、删除
|
||||
- 用户状态管理(启用/禁用)
|
||||
- 用户角色管理
|
||||
- 密码重置
|
||||
|
||||
**后端API接口**:
|
||||
- `GET /api/users` - 获取用户列表
|
||||
- `POST /api/users` - 创建用户
|
||||
- `GET /api/users/:id` - 获取用户详情
|
||||
- `PUT /api/users/:id` - 更新用户
|
||||
- `DELETE /api/users/:id` - 删除用户
|
||||
- `PUT /api/users/:id/status` - 更新用户状态
|
||||
- `POST /api/users/:id/reset-password` - 重置密码
|
||||
|
||||
### 2. 账户管理模块 (Accounts.vue)
|
||||
**功能需求**:
|
||||
- 账户列表查询(分页、搜索、筛选)
|
||||
- 账户创建、编辑、删除
|
||||
- 账户状态管理
|
||||
- 账户类型管理
|
||||
- 余额管理
|
||||
|
||||
**后端API接口**:
|
||||
- `GET /api/accounts` - 获取账户列表
|
||||
- `POST /api/accounts` - 创建账户
|
||||
- `GET /api/accounts/:id` - 获取账户详情
|
||||
- `PUT /api/accounts/:id` - 更新账户
|
||||
- `DELETE /api/accounts/:id` - 删除账户
|
||||
- `PUT /api/accounts/:id/status` - 更新账户状态
|
||||
- `POST /api/accounts/:id/deposit` - 存款
|
||||
- `POST /api/accounts/:id/withdraw` - 取款
|
||||
|
||||
### 3. 交易管理模块 (Transactions.vue)
|
||||
**功能需求**:
|
||||
- 交易记录查询(分页、搜索、筛选)
|
||||
- 交易详情查看
|
||||
- 交易统计
|
||||
- 交易撤销
|
||||
|
||||
**后端API接口**:
|
||||
- `GET /api/transactions` - 获取交易列表
|
||||
- `GET /api/transactions/:id` - 获取交易详情
|
||||
- `POST /api/transactions` - 创建交易
|
||||
- `PUT /api/transactions/:id/cancel` - 撤销交易
|
||||
- `GET /api/transactions/statistics` - 获取交易统计
|
||||
|
||||
### 4. 贷款管理模块
|
||||
#### 4.1 贷款商品 (LoanProducts.vue)
|
||||
**功能需求**:
|
||||
- 贷款产品列表查询
|
||||
- 产品创建、编辑、删除
|
||||
- 产品状态管理
|
||||
- 产品配置管理
|
||||
|
||||
**后端API接口**:
|
||||
- `GET /api/loan-products` - 获取贷款产品列表
|
||||
- `POST /api/loan-products` - 创建贷款产品
|
||||
- `GET /api/loan-products/:id` - 获取产品详情
|
||||
- `PUT /api/loan-products/:id` - 更新产品
|
||||
- `DELETE /api/loan-products/:id` - 删除产品
|
||||
- `PUT /api/loan-products/:id/status` - 更新产品状态
|
||||
|
||||
#### 4.2 贷款申请 (LoanApplications.vue)
|
||||
**功能需求**:
|
||||
- 贷款申请列表查询
|
||||
- 申请详情查看
|
||||
- 申请审批流程
|
||||
- 申请状态管理
|
||||
|
||||
**后端API接口**:
|
||||
- `GET /api/loan-applications` - 获取申请列表
|
||||
- `POST /api/loan-applications` - 提交申请
|
||||
- `GET /api/loan-applications/:id` - 获取申请详情
|
||||
- `PUT /api/loan-applications/:id` - 更新申请
|
||||
- `PUT /api/loan-applications/:id/approve` - 审批申请
|
||||
- `PUT /api/loan-applications/:id/reject` - 拒绝申请
|
||||
|
||||
#### 4.3 贷款合同 (LoanContracts.vue)
|
||||
**功能需求**:
|
||||
- 合同列表查询
|
||||
- 合同详情查看
|
||||
- 合同生成和管理
|
||||
- 合同状态管理
|
||||
|
||||
**后端API接口**:
|
||||
- `GET /api/loan-contracts` - 获取合同列表
|
||||
- `POST /api/loan-contracts` - 创建合同
|
||||
- `GET /api/loan-contracts/:id` - 获取合同详情
|
||||
- `PUT /api/loan-contracts/:id` - 更新合同
|
||||
- `PUT /api/loan-contracts/:id/status` - 更新合同状态
|
||||
|
||||
#### 4.4 贷款解押 (LoanRelease.vue)
|
||||
**功能需求**:
|
||||
- 解押申请列表
|
||||
- 解押申请处理
|
||||
- 解押状态管理
|
||||
|
||||
**后端API接口**:
|
||||
- `GET /api/loan-releases` - 获取解押申请列表
|
||||
- `POST /api/loan-releases` - 提交解押申请
|
||||
- `GET /api/loan-releases/:id` - 获取申请详情
|
||||
- `PUT /api/loan-releases/:id` - 更新申请
|
||||
- `PUT /api/loan-releases/:id/approve` - 审批解押
|
||||
|
||||
### 5. 报表统计模块 (Reports.vue)
|
||||
**功能需求**:
|
||||
- 交易报表生成
|
||||
- 用户报表生成
|
||||
- 账户报表生成
|
||||
- 报表导出功能
|
||||
|
||||
**后端API接口**:
|
||||
- `GET /api/reports/transactions` - 获取交易报表
|
||||
- `GET /api/reports/users` - 获取用户报表
|
||||
- `GET /api/reports/accounts` - 获取账户报表
|
||||
- `GET /api/reports/export/:type` - 导出报表
|
||||
|
||||
### 6. 员工管理模块 (EmployeeManagement.vue)
|
||||
**功能需求**:
|
||||
- 员工列表管理
|
||||
- 员工信息维护
|
||||
- 员工统计
|
||||
|
||||
**后端API接口**:
|
||||
- `GET /api/employees` - 获取员工列表
|
||||
- `POST /api/employees` - 创建员工
|
||||
- `GET /api/employees/:id` - 获取员工详情
|
||||
- `PUT /api/employees/:id` - 更新员工
|
||||
- `DELETE /api/employees/:id` - 删除员工
|
||||
- `GET /api/employees/statistics` - 获取员工统计
|
||||
|
||||
### 7. 系统管理模块
|
||||
#### 7.1 系统设置 (Settings.vue)
|
||||
**功能需求**:
|
||||
- 系统参数配置
|
||||
- 系统日志查看
|
||||
- 系统备份恢复
|
||||
|
||||
**后端API接口**:
|
||||
- `GET /api/settings` - 获取系统设置
|
||||
- `PUT /api/settings` - 更新系统设置
|
||||
- `GET /api/logs` - 获取系统日志
|
||||
- `POST /api/backup` - 创建备份
|
||||
- `POST /api/restore` - 恢复备份
|
||||
|
||||
#### 7.2 个人中心 (Profile.vue)
|
||||
**功能需求**:
|
||||
- 个人信息查看和修改
|
||||
- 密码修改
|
||||
- 登录日志
|
||||
|
||||
**后端API接口**:
|
||||
- `GET /api/profile` - 获取个人信息
|
||||
- `PUT /api/profile` - 更新个人信息
|
||||
- `PUT /api/profile/password` - 修改密码
|
||||
- `GET /api/profile/login-logs` - 获取登录日志
|
||||
|
||||
## 实现步骤
|
||||
|
||||
1. **完善现有API接口** - 用户管理、账户管理、交易管理
|
||||
2. **实现贷款管理API** - 贷款产品、申请、合同、解押
|
||||
3. **实现报表统计API** - 各种报表生成和导出
|
||||
4. **实现员工管理API** - 员工信息管理
|
||||
5. **实现系统管理API** - 设置、日志、备份
|
||||
6. **更新前端API调用** - 确保前端正确调用所有接口
|
||||
7. **测试和验证** - 端到端功能测试
|
||||
|
||||
## 数据库模型扩展
|
||||
|
||||
需要创建以下新的数据模型:
|
||||
- LoanProduct (贷款产品)
|
||||
- LoanApplication (贷款申请)
|
||||
- LoanContract (贷款合同)
|
||||
- LoanRelease (贷款解押)
|
||||
- Employee (员工)
|
||||
- SystemLog (系统日志)
|
||||
- SystemSetting (系统设置)
|
||||
@@ -1,119 +0,0 @@
|
||||
# 银行后端端口号修改总结
|
||||
|
||||
## 修改概述
|
||||
|
||||
将银行后端系统的端口号从5350修改为5351,确保所有相关配置文件都已同步更新。
|
||||
|
||||
## 修改的文件
|
||||
|
||||
### 1. 后端配置文件
|
||||
|
||||
#### ✅ `bank-backend/server.js`
|
||||
- **修改内容**: 端口号配置已经是5351
|
||||
- **代码位置**: 第30行 `const PORT = process.env.PORT || 5351;`
|
||||
- **状态**: 无需修改,已正确配置
|
||||
|
||||
#### ✅ `bank-backend/env.example`
|
||||
- **修改内容**: 环境变量示例文件中的端口号已经是5351
|
||||
- **代码位置**: 第2行 `PORT=5351`
|
||||
- **状态**: 无需修改,已正确配置
|
||||
|
||||
### 2. 前端配置文件
|
||||
|
||||
#### ✅ `bank-frontend/src/config/env.js`
|
||||
- **修改内容**: API基础URL配置已经是5351
|
||||
- **代码位置**: 第15行和第21行 `baseUrl: getEnvVar('VITE_API_BASE_URL', 'http://localhost:5351')`
|
||||
- **状态**: 无需修改,已正确配置
|
||||
|
||||
#### ✅ `bank-frontend/env.example`
|
||||
- **修改内容**: 更新环境变量示例文件中的API基础URL
|
||||
- **修改前**: `VITE_API_BASE_URL=http://localhost:5350`
|
||||
- **修改后**: `VITE_API_BASE_URL=http://localhost:5351`
|
||||
- **状态**: ✅ 已修改
|
||||
|
||||
#### ✅ `bank-frontend/vite.config.js`
|
||||
- **修改内容**: 更新Vite代理配置中的默认目标端口
|
||||
- **修改前**: `target: env.VITE_API_BASE_URL || 'http://localhost:5350'`
|
||||
- **修改后**: `target: env.VITE_API_BASE_URL || 'http://localhost:5351'`
|
||||
- **状态**: ✅ 已修改
|
||||
|
||||
#### ✅ `bank-frontend/README.md`
|
||||
- **修改内容**: 更新文档中的API基础URL示例
|
||||
- **修改前**: `VITE_API_BASE_URL=http://localhost:5350`
|
||||
- **修改后**: `VITE_API_BASE_URL=http://localhost:5351`
|
||||
- **状态**: ✅ 已修改
|
||||
|
||||
### 3. 小程序配置文件
|
||||
|
||||
#### ✅ `bank_mini_program/src/config/api.js`
|
||||
- **修改内容**: 更新银行小程序的API基础URL
|
||||
- **修改前**: `BASE_URL: 'http://localhost:5350'`
|
||||
- **修改后**: `BASE_URL: 'http://localhost:5351'`
|
||||
- **状态**: ✅ 已修改
|
||||
|
||||
## 端口号配置总结
|
||||
|
||||
### 后端服务
|
||||
- **服务器端口**: 5351
|
||||
- **配置文件**: `bank-backend/server.js`
|
||||
- **环境变量**: `PORT=5351`
|
||||
|
||||
### 前端服务
|
||||
- **前端端口**: 5300 (保持不变)
|
||||
- **API代理目标**: http://localhost:5351
|
||||
- **环境变量**: `VITE_API_BASE_URL=http://localhost:5351`
|
||||
|
||||
### 小程序服务
|
||||
- **API基础URL**: http://localhost:5351
|
||||
- **配置文件**: `bank_mini_program/src/config/api.js`
|
||||
|
||||
## 验证方法
|
||||
|
||||
### 1. 启动后端服务
|
||||
```bash
|
||||
cd bank-backend
|
||||
npm start
|
||||
```
|
||||
预期输出:
|
||||
```
|
||||
🚀 银行管理后台服务器启动成功
|
||||
📡 服务地址: http://localhost:5351
|
||||
📚 API文档: http://localhost:5351/api-docs
|
||||
🏥 健康检查: http://localhost:5351/health
|
||||
```
|
||||
|
||||
### 2. 启动前端服务
|
||||
```bash
|
||||
cd bank-frontend
|
||||
npm run dev
|
||||
```
|
||||
预期输出:
|
||||
```
|
||||
Local: http://localhost:5300/
|
||||
Network: http://192.168.x.x:5300/
|
||||
```
|
||||
|
||||
### 3. 测试API连接
|
||||
访问以下URL验证服务是否正常运行:
|
||||
- 后端健康检查: http://localhost:5351/health
|
||||
- 后端API文档: http://localhost:5351/api-docs
|
||||
- 前端应用: http://localhost:5300
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **环境变量**: 如果使用了自定义的 `.env` 文件,请确保其中的 `VITE_API_BASE_URL` 设置为 `http://localhost:5351`
|
||||
|
||||
2. **代理配置**: 前端开发服务器会自动将 `/api` 请求代理到后端5351端口
|
||||
|
||||
3. **生产环境**: 生产环境部署时,需要相应更新生产环境的API基础URL配置
|
||||
|
||||
4. **数据库连接**: 端口号修改不影响数据库连接,数据库配置保持不变
|
||||
|
||||
## 修改完成状态
|
||||
|
||||
✅ **所有相关配置文件已成功更新为5351端口**
|
||||
✅ **前后端服务配置已同步**
|
||||
✅ **小程序API配置已更新**
|
||||
✅ **文档已更新**
|
||||
|
||||
现在银行后端系统将在5351端口运行,所有相关服务都已正确配置。
|
||||
@@ -1,283 +0,0 @@
|
||||
# 银行管理后台系统
|
||||
|
||||
一个基于 Node.js 和 Express 的现代化银行管理后台系统,提供完整的用户管理、账户管理、交易管理等功能。
|
||||
|
||||
## 🚀 功能特性
|
||||
|
||||
### 核心功能
|
||||
- **用户管理**: 用户注册、登录、权限管理
|
||||
- **账户管理**: 账户创建、状态管理、余额查询
|
||||
- **交易管理**: 存款、取款、转账、交易记录查询
|
||||
- **权限控制**: 基于角色的访问控制(RBAC)
|
||||
- **安全防护**: JWT认证、密码加密、请求限流
|
||||
|
||||
### 技术特性
|
||||
- **RESTful API**: 标准化的API设计
|
||||
- **数据库ORM**: Sequelize ORM支持
|
||||
- **API文档**: Swagger自动生成文档
|
||||
- **日志系统**: Winston日志管理
|
||||
- **安全中间件**: 多层安全防护
|
||||
- **错误处理**: 完善的错误处理机制
|
||||
|
||||
## 🛠 技术栈
|
||||
|
||||
- **运行环境**: Node.js 16+
|
||||
- **Web框架**: Express.js 4.18+
|
||||
- **数据库**: MySQL 8.0+
|
||||
- **ORM**: Sequelize 6.35+
|
||||
- **认证**: JWT (jsonwebtoken)
|
||||
- **密码加密**: bcryptjs
|
||||
- **API文档**: Swagger
|
||||
- **日志**: Winston
|
||||
- **安全**: Helmet, CORS, Rate Limiting
|
||||
|
||||
## 📦 安装部署
|
||||
|
||||
### 环境要求
|
||||
- Node.js 16.0+
|
||||
- MySQL 8.0+
|
||||
- npm 8.0+
|
||||
|
||||
### 安装步骤
|
||||
|
||||
1. **克隆项目**
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd bank-backend
|
||||
```
|
||||
|
||||
2. **安装依赖**
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
3. **环境配置**
|
||||
```bash
|
||||
cp env.example .env
|
||||
# 编辑 .env 文件,配置数据库连接等信息
|
||||
```
|
||||
|
||||
4. **数据库初始化**
|
||||
手动或脚本方式,避免自动建表:
|
||||
```powershell
|
||||
# PowerShell(推荐,自动生成管理员bcrypt哈希)
|
||||
cd scripts
|
||||
./setup-bank-db.ps1 -AdminPlain 'Admin123456'
|
||||
```
|
||||
或在数据库手工执行:
|
||||
```sql
|
||||
-- 1) 执行建表
|
||||
-- scripts/create-bank-schema.sql
|
||||
-- 2) 执行测试数据(将 REPLACE_ADMIN_BCRYPT 替换为真实 bcrypt 哈希)
|
||||
-- scripts/seed-bank-demo.sql
|
||||
```
|
||||
|
||||
5. **启动服务**
|
||||
```bash
|
||||
# 开发环境
|
||||
npm run dev
|
||||
|
||||
# 生产环境
|
||||
npm start
|
||||
```
|
||||
|
||||
## ⚙️ 环境配置
|
||||
|
||||
创建 `.env` 文件并配置以下环境变量:
|
||||
|
||||
```env
|
||||
# 服务器配置
|
||||
PORT=5351
|
||||
NODE_ENV=development
|
||||
|
||||
# 数据库配置
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_NAME=bank_management
|
||||
DB_USER=root
|
||||
DB_PASSWORD=your_password
|
||||
DB_DIALECT=mysql
|
||||
|
||||
# JWT配置
|
||||
JWT_SECRET=your_jwt_secret_key_here
|
||||
JWT_EXPIRES_IN=24h
|
||||
|
||||
# 安全配置
|
||||
BCRYPT_ROUNDS=10
|
||||
RATE_LIMIT_WINDOW_MS=900000
|
||||
RATE_LIMIT_MAX_REQUESTS=100
|
||||
|
||||
# 银行系统配置
|
||||
BANK_CODE=001
|
||||
BANK_NAME=示例银行
|
||||
CURRENCY=CNY
|
||||
TIMEZONE=Asia/Shanghai
|
||||
```
|
||||
|
||||
## 📚 API文档
|
||||
|
||||
启动服务后,访问以下地址查看API文档:
|
||||
- 开发环境: http://localhost:5351/api-docs
|
||||
- 生产环境: https://your-domain.com/api-docs
|
||||
|
||||
## 🗄️ 数据库设计
|
||||
|
||||
### 主要数据表
|
||||
|
||||
#### 用户表 (users)
|
||||
- 用户基本信息
|
||||
- 身份认证信息
|
||||
- 角色关联
|
||||
|
||||
#### 角色表 (roles)
|
||||
- 角色定义
|
||||
- 权限级别
|
||||
- 系统角色标识
|
||||
|
||||
#### 账户表 (accounts)
|
||||
- 账户基本信息
|
||||
- 余额管理
|
||||
- 账户状态
|
||||
|
||||
#### 交易记录表 (transactions)
|
||||
- 交易详情
|
||||
- 余额变化
|
||||
- 交易状态
|
||||
|
||||
## 🔐 安全特性
|
||||
|
||||
### 认证与授权
|
||||
- JWT令牌认证
|
||||
- 基于角色的权限控制
|
||||
- 会话超时管理
|
||||
- 登录失败锁定
|
||||
|
||||
### 数据安全
|
||||
- 密码bcrypt加密
|
||||
- SQL注入防护
|
||||
- XSS攻击防护
|
||||
- 请求频率限制
|
||||
|
||||
### 传输安全
|
||||
- HTTPS支持
|
||||
- CORS配置
|
||||
- 安全头部设置
|
||||
- 输入数据验证
|
||||
|
||||
## 📊 系统监控
|
||||
|
||||
### 健康检查
|
||||
```bash
|
||||
curl http://localhost:5351/health
|
||||
```
|
||||
|
||||
### 日志查看
|
||||
```bash
|
||||
# 查看错误日志
|
||||
tail -f logs/error.log
|
||||
|
||||
# 查看所有日志
|
||||
tail -f logs/combined.log
|
||||
```
|
||||
|
||||
## 🧪 测试
|
||||
|
||||
```bash
|
||||
# 运行测试
|
||||
npm test
|
||||
|
||||
# 测试覆盖率
|
||||
npm run test:coverage
|
||||
|
||||
# 监听模式
|
||||
npm run test:watch
|
||||
```
|
||||
|
||||
## 📝 开发指南
|
||||
|
||||
### 项目结构
|
||||
```
|
||||
bank-backend/
|
||||
├── config/ # 配置文件
|
||||
├── controllers/ # 控制器
|
||||
├── models/ # 数据模型
|
||||
├── routes/ # 路由定义
|
||||
├── middleware/ # 中间件
|
||||
├── utils/ # 工具类
|
||||
├── services/ # 业务服务
|
||||
├── migrations/ # 数据库迁移
|
||||
├── seeds/ # 种子数据
|
||||
├── logs/ # 日志文件
|
||||
├── uploads/ # 上传文件
|
||||
└── scripts/ # 脚本文件
|
||||
```
|
||||
|
||||
### 代码规范
|
||||
- 使用ESLint进行代码检查
|
||||
- 遵循RESTful API设计规范
|
||||
- 统一的错误处理格式
|
||||
- 完整的API文档注释
|
||||
|
||||
### 开发命令
|
||||
```bash
|
||||
# 代码检查
|
||||
npm run lint
|
||||
|
||||
# 代码修复
|
||||
npm run lint:fix
|
||||
|
||||
# 数据库连接测试
|
||||
npm run test-connection
|
||||
|
||||
# 清理临时文件
|
||||
npm run clean
|
||||
```
|
||||
|
||||
## 🚀 部署指南
|
||||
|
||||
### Docker部署
|
||||
```bash
|
||||
# 构建镜像
|
||||
docker build -t bank-backend .
|
||||
|
||||
# 运行容器
|
||||
docker run -p 5351:5351 bank-backend
|
||||
```
|
||||
|
||||
### PM2部署
|
||||
```bash
|
||||
# 安装PM2
|
||||
npm install -g pm2
|
||||
|
||||
# 启动应用
|
||||
pm2 start server.js --name bank-backend
|
||||
|
||||
# 查看状态
|
||||
pm2 status
|
||||
```
|
||||
|
||||
## 🤝 贡献指南
|
||||
|
||||
1. Fork 项目
|
||||
2. 创建功能分支 (`git checkout -b feature/AmazingFeature`)
|
||||
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
|
||||
4. 推送到分支 (`git push origin feature/AmazingFeature`)
|
||||
5. 打开 Pull Request
|
||||
|
||||
## 📄 许可证
|
||||
|
||||
本项目采用 MIT 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情。
|
||||
|
||||
## 📞 联系方式
|
||||
|
||||
- 项目维护者: 银行开发团队
|
||||
- 邮箱: dev@bank.com
|
||||
- 项目地址: https://github.com/bank-management/bank-backend
|
||||
|
||||
## 🙏 致谢
|
||||
|
||||
感谢所有为这个项目做出贡献的开发者和开源社区。
|
||||
|
||||
---
|
||||
|
||||
**注意**: 这是一个演示项目,请勿在生产环境中使用默认的密码和密钥。
|
||||
@@ -1,164 +0,0 @@
|
||||
# 监管任务前后端集成完成总结
|
||||
|
||||
## 🎯 项目概述
|
||||
已成功完成银行端监管任务管理系统的前后端集成,实现了完整的CRUD功能,使用真实的后端API接口替代了前端模拟数据。
|
||||
|
||||
## ✅ 已完成的功能
|
||||
|
||||
### 后端功能
|
||||
1. **数据库模型** (`models/SupervisionTask.js`)
|
||||
- 完整的监管任务数据模型
|
||||
- 支持所有必要字段:申请单号、合同编号、客户信息、监管信息等
|
||||
- 与用户模型的关联关系
|
||||
|
||||
2. **数据库迁移** (`migrations/20241220000003-create-supervision-tasks.js`)
|
||||
- 创建监管任务表结构
|
||||
- 添加必要的索引优化查询性能
|
||||
|
||||
3. **API控制器** (`controllers/supervisionTaskController.js`)
|
||||
- 获取监管任务列表(支持分页、搜索、筛选)
|
||||
- 获取监管任务详情
|
||||
- 创建监管任务(完整的数据验证)
|
||||
- 更新监管任务
|
||||
- 删除监管任务
|
||||
- 获取监管任务统计
|
||||
- 批量更新状态
|
||||
- 批量删除
|
||||
|
||||
4. **API路由** (`routes/supervisionTasks.js`)
|
||||
- 完整的RESTful API路由配置
|
||||
- 统一的认证中间件保护
|
||||
|
||||
5. **测试数据** (`scripts/seed-supervision-tasks.js`)
|
||||
- 8条完整的测试数据
|
||||
- 涵盖不同状态和类型的监管任务
|
||||
|
||||
### 前端功能
|
||||
1. **API集成** (`src/utils/api.js`)
|
||||
- 完整的监管任务API方法
|
||||
- 统一的错误处理和认证
|
||||
|
||||
2. **页面改造** (`src/views/SupervisionTasks.vue`)
|
||||
- 移除模拟数据,使用真实API
|
||||
- 实现完整的CRUD操作
|
||||
- 添加搜索和筛选功能
|
||||
- 分页功能
|
||||
- 错误处理和用户反馈
|
||||
|
||||
## 📊 数据字段说明
|
||||
|
||||
### 核心字段
|
||||
- **申请单号** (`applicationNumber`): 唯一标识,最大50字符
|
||||
- **放款合同编号** (`contractNumber`): 唯一标识,最大50字符
|
||||
- **产品名称** (`productName`): 最大100字符
|
||||
- **客户姓名** (`customerName`): 最大50字符
|
||||
- **证件类型** (`idType`): 枚举值(身份证、护照、其他)
|
||||
- **证件号码** (`idNumber`): 最大50字符
|
||||
- **养殖生资种类** (`assetType`): 枚举值(牛、羊、猪、家禽、其他)
|
||||
- **监管生资数量** (`assetQuantity`): 非负整数
|
||||
- **监管状态** (`supervisionStatus`): 枚举值(待监管、监管中、已完成、已暂停)
|
||||
|
||||
### 时间字段
|
||||
- **任务导入时间** (`importTime`): 自动生成
|
||||
- **监管起始时间** (`startTime`): 必填
|
||||
- **监管结束时间** (`endTime`): 必填
|
||||
|
||||
### 扩展字段
|
||||
- **贷款金额** (`loanAmount`): 精确到分
|
||||
- **利率** (`interestRate`): 精确到万分位
|
||||
- **贷款期限** (`loanTerm`): 月数
|
||||
- **监管员姓名** (`supervisorName`): 最大50字符
|
||||
- **监管员电话** (`supervisorPhone`): 最大20字符
|
||||
- **养殖场地址** (`farmAddress`): 最大200字符
|
||||
- **备注** (`remarks`): 文本字段
|
||||
|
||||
## 🔧 API接口列表
|
||||
|
||||
### 基础CRUD
|
||||
- `GET /api/supervision-tasks` - 获取监管任务列表
|
||||
- `GET /api/supervision-tasks/:id` - 获取监管任务详情
|
||||
- `POST /api/supervision-tasks` - 创建监管任务
|
||||
- `PUT /api/supervision-tasks/:id` - 更新监管任务
|
||||
- `DELETE /api/supervision-tasks/:id` - 删除监管任务
|
||||
|
||||
### 统计和批量操作
|
||||
- `GET /api/supervision-tasks/stats` - 获取监管任务统计
|
||||
- `PUT /api/supervision-tasks/batch/status` - 批量更新状态
|
||||
- `DELETE /api/supervision-tasks/batch` - 批量删除
|
||||
|
||||
### 查询参数
|
||||
- `page`: 页码(默认1)
|
||||
- `limit`: 每页数量(默认10)
|
||||
- `search`: 搜索关键词(申请单号、合同编号、客户姓名、产品名称)
|
||||
- `supervisionStatus`: 状态筛选
|
||||
- `dateRange`: 日期范围筛选
|
||||
- `sortBy`: 排序字段
|
||||
- `sortOrder`: 排序方向
|
||||
|
||||
## 🚀 使用方法
|
||||
|
||||
### 1. 启动后端服务
|
||||
```bash
|
||||
cd bank-backend
|
||||
npm start
|
||||
```
|
||||
|
||||
### 2. 启动前端服务
|
||||
```bash
|
||||
cd bank-frontend
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### 3. 访问页面
|
||||
- 监管任务页面: `http://localhost:5300/supervision-tasks`
|
||||
- API测试页面: `http://localhost:5300/test-supervision-tasks.html`
|
||||
|
||||
### 4. 功能测试
|
||||
1. 登录系统(admin/Admin123456)
|
||||
2. 查看监管任务列表
|
||||
3. 使用搜索和筛选功能
|
||||
4. 创建新的监管任务
|
||||
5. 编辑和删除任务
|
||||
|
||||
## 📝 数据验证规则
|
||||
|
||||
### 必填字段
|
||||
- 申请单号、放款合同编号、产品名称、客户姓名、证件号码、监管起始时间、监管结束时间
|
||||
|
||||
### 唯一性约束
|
||||
- 申请单号、放款合同编号必须唯一
|
||||
|
||||
### 数据格式
|
||||
- 日期格式:YYYY-MM-DD
|
||||
- 金额格式:精确到分
|
||||
- 利率格式:0-1之间的小数
|
||||
|
||||
### 业务规则
|
||||
- 监管结束时间必须晚于开始时间
|
||||
- 监管生资数量不能为负数
|
||||
- 贷款金额不能为负数
|
||||
|
||||
## 🔍 测试数据
|
||||
|
||||
系统已预置8条测试数据,涵盖:
|
||||
- 不同监管状态(待监管、监管中、已完成、已暂停)
|
||||
- 不同养殖生资种类(牛、羊、猪、家禽、其他)
|
||||
- 不同证件类型(身份证、护照)
|
||||
- 不同贷款金额和期限
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
- [监管任务API文档](docs/SUPERVISION_TASKS_API.md)
|
||||
- [项目API文档](docs/PROJECT_API.md)
|
||||
- [前端API集成指南](API_INTEGRATION_COMPLETE.md)
|
||||
|
||||
## 🎉 总结
|
||||
|
||||
监管任务管理系统已完全集成,提供了:
|
||||
- ✅ 完整的后端API服务
|
||||
- ✅ 功能丰富的前端界面
|
||||
- ✅ 真实的数据存储和查询
|
||||
- ✅ 完善的错误处理
|
||||
- ✅ 用户友好的交互体验
|
||||
|
||||
系统现在可以正常使用,支持监管任务的完整生命周期管理。
|
||||
210
bank-backend/add-loan-release-test-data.js
Normal file
210
bank-backend/add-loan-release-test-data.js
Normal file
@@ -0,0 +1,210 @@
|
||||
/**
|
||||
* 添加贷款解押测试数据
|
||||
* @file add-loan-release-test-data.js
|
||||
* @description 为贷款解押功能添加测试数据
|
||||
*/
|
||||
const { LoanRelease, LoanReleaseHistory, User } = require('./models');
|
||||
|
||||
async function addTestData() {
|
||||
try {
|
||||
console.log('🔄 开始添加贷款解押测试数据...\n');
|
||||
|
||||
// 获取管理员用户
|
||||
const adminUser = await User.findOne({ where: { username: 'admin' } });
|
||||
if (!adminUser) {
|
||||
console.log('❌ 未找到管理员用户,请先创建用户');
|
||||
return;
|
||||
}
|
||||
|
||||
// 清空现有数据
|
||||
await LoanReleaseHistory.destroy({ where: {} });
|
||||
await LoanRelease.destroy({ where: {} });
|
||||
console.log('🗑️ 清空现有测试数据');
|
||||
|
||||
// 创建解押申请测试数据
|
||||
const releases = [
|
||||
{
|
||||
application_number: '20240227145555918',
|
||||
contract_id: null,
|
||||
customer_name: '刘超',
|
||||
customer_phone: '13800138000',
|
||||
customer_id_card: '511123199001013017',
|
||||
farmer_name: '刘超',
|
||||
product_name: '中国工商银行扎旗支行"畜禽活体抵押"',
|
||||
collateral_type: 'livestock',
|
||||
collateral_description: '优质肉牛',
|
||||
collateral_value: 150000.00,
|
||||
loan_amount: 100000.00,
|
||||
release_amount: 10000.00,
|
||||
release_quantity: '10头',
|
||||
application_reason: '需要资金周转,申请部分解押',
|
||||
status: 'released',
|
||||
application_date: new Date('2024-02-27 14:55:55'),
|
||||
process_date: new Date('2024-02-27 15:30:00'),
|
||||
complete_date: new Date('2024-02-27 16:00:00'),
|
||||
processor_id: adminUser.id,
|
||||
processor_name: adminUser.real_name || adminUser.username,
|
||||
process_comment: '审核通过,同意解押',
|
||||
remark: '客户信用良好'
|
||||
},
|
||||
{
|
||||
application_number: '20240226113416302',
|
||||
contract_id: null,
|
||||
customer_name: '李四',
|
||||
customer_phone: '13900139000',
|
||||
customer_id_card: '511123199002020002',
|
||||
farmer_name: '李四',
|
||||
product_name: '惠农贷',
|
||||
collateral_type: 'livestock',
|
||||
collateral_description: '优质奶牛',
|
||||
collateral_value: 200000.00,
|
||||
loan_amount: 150000.00,
|
||||
release_amount: 0.00,
|
||||
release_quantity: '10头',
|
||||
application_reason: '申请全部解押',
|
||||
status: 'released',
|
||||
application_date: new Date('2024-02-26 11:34:16'),
|
||||
process_date: new Date('2024-02-26 12:00:00'),
|
||||
complete_date: new Date('2024-02-26 12:30:00'),
|
||||
processor_id: adminUser.id,
|
||||
processor_name: adminUser.real_name || adminUser.username,
|
||||
process_comment: '审核通过,同意解押',
|
||||
remark: '客户按时还款'
|
||||
},
|
||||
{
|
||||
application_number: '20240223140542290',
|
||||
contract_id: null,
|
||||
customer_name: '张洪彬',
|
||||
customer_phone: '13700137000',
|
||||
customer_id_card: '511123199003030003',
|
||||
farmer_name: '张洪彬',
|
||||
product_name: '惠农贷',
|
||||
collateral_type: 'livestock',
|
||||
collateral_description: '优质肉牛',
|
||||
collateral_value: 500000.00,
|
||||
loan_amount: 300000.00,
|
||||
release_amount: 1000000.00,
|
||||
release_quantity: '10头',
|
||||
application_reason: '扩大养殖规模,申请解押',
|
||||
status: 'released',
|
||||
application_date: new Date('2024-02-23 14:05:42'),
|
||||
process_date: new Date('2024-02-23 15:00:00'),
|
||||
complete_date: new Date('2024-02-23 15:30:00'),
|
||||
processor_id: adminUser.id,
|
||||
processor_name: adminUser.real_name || adminUser.username,
|
||||
process_comment: '审核通过,同意解押',
|
||||
remark: '客户经营状况良好'
|
||||
},
|
||||
{
|
||||
application_number: '20231131890123456',
|
||||
contract_id: null,
|
||||
customer_name: '田小平',
|
||||
customer_phone: '13600136000',
|
||||
customer_id_card: '150123199001013140',
|
||||
farmer_name: '田小平',
|
||||
product_name: '中国工商银行扎旗支行"畜禽活体抵押"',
|
||||
collateral_type: 'livestock',
|
||||
collateral_description: '优质肉牛',
|
||||
collateral_value: 600000.00,
|
||||
loan_amount: 400000.00,
|
||||
release_amount: 420000.00,
|
||||
release_quantity: '30头',
|
||||
application_reason: '需要资金购买饲料',
|
||||
status: 'released',
|
||||
application_date: new Date('2023-11-31 08:30:12'),
|
||||
process_date: new Date('2023-11-31 10:00:00'),
|
||||
complete_date: new Date('2023-11-31 10:30:00'),
|
||||
processor_id: adminUser.id,
|
||||
processor_name: adminUser.real_name || adminUser.username,
|
||||
process_comment: '审核通过,同意解押',
|
||||
remark: '客户信用记录良好'
|
||||
},
|
||||
{
|
||||
application_number: '20231131789012345',
|
||||
contract_id: null,
|
||||
customer_name: '杜宝民',
|
||||
customer_phone: '13500135000',
|
||||
customer_id_card: '150123199002020723',
|
||||
farmer_name: '杜宝民',
|
||||
product_name: '中国农业银行扎旗支行"畜禽活体抵押"',
|
||||
collateral_type: 'livestock',
|
||||
collateral_description: '优质奶牛',
|
||||
collateral_value: 700000.00,
|
||||
loan_amount: 500000.00,
|
||||
release_amount: 420000.00,
|
||||
release_quantity: '30头',
|
||||
application_reason: '设备更新需要资金',
|
||||
status: 'released',
|
||||
application_date: new Date('2023-11-31 07:30:01'),
|
||||
process_date: new Date('2023-11-31 09:00:00'),
|
||||
complete_date: new Date('2023-11-31 09:30:00'),
|
||||
processor_id: adminUser.id,
|
||||
processor_name: adminUser.real_name || adminUser.username,
|
||||
process_comment: '审核通过,同意解押',
|
||||
remark: '客户还款能力强'
|
||||
},
|
||||
{
|
||||
application_number: '20231131901234567',
|
||||
contract_id: null,
|
||||
customer_name: '满良',
|
||||
customer_phone: '13400134000',
|
||||
customer_id_card: '150123199003030514',
|
||||
farmer_name: '满良',
|
||||
product_name: '中国农业银行扎旗支行"畜禽活体抵押"',
|
||||
collateral_type: 'livestock',
|
||||
collateral_description: '优质肉牛',
|
||||
collateral_value: 800000.00,
|
||||
loan_amount: 600000.00,
|
||||
release_amount: 530000.00,
|
||||
release_quantity: '38头',
|
||||
application_reason: '扩大养殖规模',
|
||||
status: 'released',
|
||||
application_date: new Date('2023-11-31 09:01:23'),
|
||||
process_date: new Date('2023-11-31 11:00:00'),
|
||||
complete_date: new Date('2023-11-31 11:30:00'),
|
||||
processor_id: adminUser.id,
|
||||
processor_name: adminUser.real_name || adminUser.username,
|
||||
process_comment: '审核通过,同意解押',
|
||||
remark: '客户经营稳定'
|
||||
}
|
||||
];
|
||||
|
||||
// 批量创建解押申请
|
||||
const createdReleases = await LoanRelease.bulkCreate(releases);
|
||||
console.log(`✅ 创建了 ${createdReleases.length} 条解押申请记录`);
|
||||
|
||||
// 为每个解押申请创建历史记录
|
||||
for (let i = 0; i < createdReleases.length; i++) {
|
||||
const release = createdReleases[i];
|
||||
|
||||
// 提交申请历史
|
||||
await LoanReleaseHistory.create({
|
||||
release_id: release.id,
|
||||
action: 'apply',
|
||||
operator: release.customer_name,
|
||||
comment: '提交解押申请'
|
||||
});
|
||||
|
||||
// 处理完成历史
|
||||
await LoanReleaseHistory.create({
|
||||
release_id: release.id,
|
||||
action: 'complete',
|
||||
operator: release.processor_name,
|
||||
operator_id: release.processor_id,
|
||||
comment: '解押手续办理完成',
|
||||
previous_status: 'pending',
|
||||
new_status: 'released'
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`✅ 创建了 ${createdReleases.length * 2} 条历史记录`);
|
||||
|
||||
console.log('\n🎉 贷款解押测试数据添加完成!');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 添加测试数据失败:', error.message);
|
||||
console.error('错误详情:', error);
|
||||
}
|
||||
}
|
||||
|
||||
addTestData();
|
||||
@@ -1,32 +0,0 @@
|
||||
const { sequelize } = require('./config/database');
|
||||
|
||||
async function checkData() {
|
||||
try {
|
||||
console.log('检查各表数据统计...\n');
|
||||
|
||||
const tables = [
|
||||
'supervision_tasks',
|
||||
'projects',
|
||||
'installation_tasks',
|
||||
'completed_supervisions',
|
||||
'loan_applications',
|
||||
'loan_contracts'
|
||||
];
|
||||
|
||||
for (const table of tables) {
|
||||
const result = await sequelize.query(
|
||||
`SELECT COUNT(*) as count FROM ${table}`,
|
||||
{ type: sequelize.QueryTypes.SELECT }
|
||||
);
|
||||
console.log(`${table}: ${result[0].count} 条记录`);
|
||||
}
|
||||
|
||||
console.log('\n检查完成!');
|
||||
} catch (error) {
|
||||
console.error('错误:', error.message);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
checkData();
|
||||
@@ -1,31 +0,0 @@
|
||||
const { LoanProduct } = require('./models');
|
||||
|
||||
async function checkData() {
|
||||
try {
|
||||
console.log('检查数据库中的贷款商品数据...');
|
||||
|
||||
const count = await LoanProduct.count();
|
||||
console.log(`数据库中共有 ${count} 条贷款商品数据`);
|
||||
|
||||
if (count > 0) {
|
||||
const products = await LoanProduct.findAll({
|
||||
limit: 3,
|
||||
attributes: ['id', 'productName', 'loanAmount', 'loanTerm', 'interestRate', 'onSaleStatus']
|
||||
});
|
||||
|
||||
console.log('前3条数据:');
|
||||
products.forEach((product, index) => {
|
||||
console.log(`${index + 1}. ID: ${product.id}, 名称: ${product.productName}, 额度: ${product.loanAmount}`);
|
||||
});
|
||||
} else {
|
||||
console.log('数据库中没有贷款商品数据,需要添加测试数据');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('检查数据失败:', error);
|
||||
} finally {
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
checkData();
|
||||
@@ -1,36 +0,0 @@
|
||||
const { LoanProduct } = require('./models');
|
||||
|
||||
async function checkLoanProducts() {
|
||||
try {
|
||||
console.log('查询数据库中的贷款商品数据...');
|
||||
|
||||
const products = await LoanProduct.findAll({
|
||||
attributes: ['id', 'productName', 'loanAmount', 'loanTerm', 'interestRate', 'serviceArea', 'servicePhone', 'totalCustomers', 'supervisionCustomers', 'completedCustomers', 'onSaleStatus', 'createdAt']
|
||||
});
|
||||
|
||||
console.log(`找到 ${products.length} 条贷款商品数据:`);
|
||||
|
||||
products.forEach((product, index) => {
|
||||
console.log(`\n--- 产品 ${index + 1} ---`);
|
||||
console.log(`ID: ${product.id}`);
|
||||
console.log(`产品名称: ${product.productName}`);
|
||||
console.log(`贷款额度: ${product.loanAmount}`);
|
||||
console.log(`贷款周期: ${product.loanTerm}`);
|
||||
console.log(`贷款利率: ${product.interestRate}`);
|
||||
console.log(`服务区域: ${product.serviceArea}`);
|
||||
console.log(`服务电话: ${product.servicePhone}`);
|
||||
console.log(`总客户数: ${product.totalCustomers}`);
|
||||
console.log(`监管中客户: ${product.supervisionCustomers}`);
|
||||
console.log(`已结项客户: ${product.completedCustomers}`);
|
||||
console.log(`在售状态: ${product.onSaleStatus}`);
|
||||
console.log(`创建时间: ${product.createdAt}`);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('查询失败:', error);
|
||||
} finally {
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
checkLoanProducts();
|
||||
@@ -10,8 +10,8 @@ const options = {
|
||||
openapi: '3.0.0',
|
||||
info: {
|
||||
title: '银行管理后台API',
|
||||
version: '1.0.0',
|
||||
description: '银行管理后台系统API文档',
|
||||
version: '1.1.0',
|
||||
description: '银行管理后台系统API文档,包含贷款解押模块',
|
||||
contact: {
|
||||
name: '银行开发团队',
|
||||
email: 'dev@bank.com'
|
||||
@@ -104,6 +104,196 @@ const options = {
|
||||
example: 10
|
||||
}
|
||||
}
|
||||
},
|
||||
LoanRelease: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'integer',
|
||||
example: 1
|
||||
},
|
||||
applicationNumber: {
|
||||
type: 'string',
|
||||
example: '20240227145555918'
|
||||
},
|
||||
productName: {
|
||||
type: 'string',
|
||||
example: '中国工商银行扎旗支行"畜禽活体抵押"'
|
||||
},
|
||||
farmerName: {
|
||||
type: 'string',
|
||||
example: '刘超'
|
||||
},
|
||||
applicantName: {
|
||||
type: 'string',
|
||||
example: '刘超'
|
||||
},
|
||||
applicantIdNumber: {
|
||||
type: 'string',
|
||||
example: '511123199001013017'
|
||||
},
|
||||
applicantPhone: {
|
||||
type: 'string',
|
||||
example: '13800138000'
|
||||
},
|
||||
assetType: {
|
||||
type: 'string',
|
||||
example: '牛'
|
||||
},
|
||||
releaseQuantity: {
|
||||
type: 'string',
|
||||
example: '10头'
|
||||
},
|
||||
releaseAmount: {
|
||||
type: 'number',
|
||||
example: 10000
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
enum: ['pending', 'processing', 'approved', 'rejected', 'completed', 'released'],
|
||||
example: 'released'
|
||||
},
|
||||
applicationTime: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
example: '2024-02-27T06:55:55.000Z'
|
||||
},
|
||||
processTime: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
example: '2024-02-27T07:30:00.000Z'
|
||||
},
|
||||
completeTime: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
example: '2024-02-27T08:00:00.000Z'
|
||||
},
|
||||
processorName: {
|
||||
type: 'string',
|
||||
example: '系统管理员'
|
||||
},
|
||||
processComment: {
|
||||
type: 'string',
|
||||
example: '审核通过,同意解押'
|
||||
},
|
||||
rejectionReason: {
|
||||
type: 'string',
|
||||
example: null
|
||||
},
|
||||
remark: {
|
||||
type: 'string',
|
||||
example: '客户信用良好'
|
||||
}
|
||||
}
|
||||
},
|
||||
LoanReleaseHistory: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'integer',
|
||||
example: 1
|
||||
},
|
||||
action: {
|
||||
type: 'string',
|
||||
enum: ['apply', 'process', 'approve', 'reject', 'complete', 'release'],
|
||||
example: 'apply'
|
||||
},
|
||||
operator: {
|
||||
type: 'string',
|
||||
example: '刘超'
|
||||
},
|
||||
time: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
example: '2025-09-26T03:38:51.000Z'
|
||||
},
|
||||
comment: {
|
||||
type: 'string',
|
||||
example: '提交解押申请'
|
||||
}
|
||||
}
|
||||
},
|
||||
LoanReleaseCreateRequest: {
|
||||
type: 'object',
|
||||
required: ['customer_name', 'customer_phone', 'customer_id_card', 'collateral_type', 'release_amount'],
|
||||
properties: {
|
||||
contract_id: {
|
||||
type: 'integer',
|
||||
example: 1
|
||||
},
|
||||
customer_name: {
|
||||
type: 'string',
|
||||
example: '测试用户'
|
||||
},
|
||||
customer_phone: {
|
||||
type: 'string',
|
||||
example: '13800138000'
|
||||
},
|
||||
customer_id_card: {
|
||||
type: 'string',
|
||||
example: '511123199001010001'
|
||||
},
|
||||
farmer_name: {
|
||||
type: 'string',
|
||||
example: '测试用户'
|
||||
},
|
||||
product_name: {
|
||||
type: 'string',
|
||||
example: '测试产品'
|
||||
},
|
||||
collateral_type: {
|
||||
type: 'string',
|
||||
enum: ['house', 'car', 'land', 'equipment', 'livestock'],
|
||||
example: 'livestock'
|
||||
},
|
||||
collateral_description: {
|
||||
type: 'string',
|
||||
example: '测试抵押物'
|
||||
},
|
||||
collateral_value: {
|
||||
type: 'number',
|
||||
example: 100000
|
||||
},
|
||||
loan_amount: {
|
||||
type: 'number',
|
||||
example: 80000
|
||||
},
|
||||
release_amount: {
|
||||
type: 'number',
|
||||
example: 50000
|
||||
},
|
||||
release_quantity: {
|
||||
type: 'string',
|
||||
example: '5头'
|
||||
},
|
||||
application_reason: {
|
||||
type: 'string',
|
||||
example: '测试解押申请'
|
||||
},
|
||||
remark: {
|
||||
type: 'string',
|
||||
example: '测试备注'
|
||||
}
|
||||
}
|
||||
},
|
||||
LoanReleaseProcessRequest: {
|
||||
type: 'object',
|
||||
required: ['action', 'comment'],
|
||||
properties: {
|
||||
action: {
|
||||
type: 'string',
|
||||
enum: ['approve', 'reject'],
|
||||
example: 'approve'
|
||||
},
|
||||
comment: {
|
||||
type: 'string',
|
||||
example: '审核通过'
|
||||
},
|
||||
remark: {
|
||||
type: 'string',
|
||||
example: '测试处理'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {
|
||||
@@ -191,6 +381,14 @@ const options = {
|
||||
}
|
||||
],
|
||||
tags: [
|
||||
{
|
||||
name: 'Authentication',
|
||||
description: '认证相关接口'
|
||||
},
|
||||
{
|
||||
name: 'Dashboard',
|
||||
description: '仪表盘相关接口'
|
||||
},
|
||||
{
|
||||
name: 'Users',
|
||||
description: '用户管理相关接口'
|
||||
@@ -202,6 +400,46 @@ const options = {
|
||||
{
|
||||
name: 'Transactions',
|
||||
description: '交易管理相关接口'
|
||||
},
|
||||
{
|
||||
name: 'Loan Products',
|
||||
description: '贷款产品管理相关接口'
|
||||
},
|
||||
{
|
||||
name: 'Loan Applications',
|
||||
description: '贷款申请管理相关接口'
|
||||
},
|
||||
{
|
||||
name: 'Loan Contracts',
|
||||
description: '贷款合同管理相关接口'
|
||||
},
|
||||
{
|
||||
name: 'Loan Release',
|
||||
description: '贷款解押管理相关接口'
|
||||
},
|
||||
{
|
||||
name: 'Employees',
|
||||
description: '员工管理相关接口'
|
||||
},
|
||||
{
|
||||
name: 'Projects',
|
||||
description: '项目管理相关接口'
|
||||
},
|
||||
{
|
||||
name: 'Supervision Tasks',
|
||||
description: '监管任务管理相关接口'
|
||||
},
|
||||
{
|
||||
name: 'Installation Tasks',
|
||||
description: '安装任务管理相关接口'
|
||||
},
|
||||
{
|
||||
name: 'Completed Supervisions',
|
||||
description: '完成监管管理相关接口'
|
||||
},
|
||||
{
|
||||
name: 'Reports',
|
||||
description: '报表管理相关接口'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -38,6 +38,9 @@ const getApplications = async (req, res) => {
|
||||
} else if (searchField === 'applicationNumber') {
|
||||
// 数据库中实际没有applicationNumber字段,使用id作为替代
|
||||
where.id = { [Op.like]: `%${searchValue}%` };
|
||||
} else if (searchField === 'productName') {
|
||||
// 产品名称搜索暂时不处理,因为数据库中没有这个字段
|
||||
// 可以在前端过滤
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,17 +73,27 @@ const getApplications = async (req, res) => {
|
||||
limit
|
||||
});
|
||||
|
||||
// 格式化数据
|
||||
// 格式化数据 - 匹配前端期望的字段
|
||||
const applications = rows.map(app => ({
|
||||
id: app.id,
|
||||
customer_name: app.customer_name,
|
||||
customer_phone: app.customer_phone,
|
||||
customer_id_card: app.customer_id_card,
|
||||
loan_amount: parseFloat(app.loan_amount),
|
||||
loan_term: app.loan_term,
|
||||
interest_rate: parseFloat(app.interest_rate),
|
||||
application_date: app.application_date,
|
||||
applicationNumber: `APP-${String(app.id).padStart(6, '0')}`, // 生成申请单号
|
||||
productName: '养殖贷款', // 默认产品名称
|
||||
farmerName: app.customer_name || '未知养殖户',
|
||||
borrowerName: app.customer_name || '未知借款人',
|
||||
borrowerIdNumber: app.customer_id_card || '',
|
||||
assetType: '养殖设备', // 默认生资种类
|
||||
applicationQuantity: '1', // 默认申请数量
|
||||
amount: parseFloat(app.loan_amount),
|
||||
status: app.status,
|
||||
type: 'personal', // 默认类型
|
||||
term: app.loan_term,
|
||||
interestRate: parseFloat(app.interest_rate),
|
||||
phone: app.customer_phone || '',
|
||||
purpose: '养殖经营', // 默认用途
|
||||
remark: '', // 默认备注
|
||||
applicationTime: app.application_date,
|
||||
approvedTime: null,
|
||||
rejectedTime: null,
|
||||
created_at: app.created_at,
|
||||
updated_at: app.updated_at
|
||||
}));
|
||||
@@ -221,7 +234,7 @@ const auditApplication = async (req, res) => {
|
||||
|
||||
const { id } = req.params;
|
||||
const { action, comment } = req.body;
|
||||
const userId = req.user?.id;
|
||||
const userId = req.user?.userId;
|
||||
|
||||
// 获取申请信息
|
||||
const application = await LoanApplication.findByPk(id);
|
||||
@@ -246,13 +259,15 @@ const auditApplication = async (req, res) => {
|
||||
// 根据审核动作更新状态
|
||||
if (action === 'approve') {
|
||||
newStatus = 'approved';
|
||||
application.approvedTime = new Date();
|
||||
application.approvedBy = userId;
|
||||
// 注意:LoanApplication模型中没有这些字段,暂时注释掉
|
||||
// application.approvedTime = new Date();
|
||||
// application.approvedBy = userId;
|
||||
} else if (action === 'reject') {
|
||||
newStatus = 'rejected';
|
||||
application.rejectedTime = new Date();
|
||||
application.rejectedBy = userId;
|
||||
application.rejectionReason = comment;
|
||||
// 注意:LoanApplication模型中没有这些字段,暂时注释掉
|
||||
// application.rejectedTime = new Date();
|
||||
// application.rejectedBy = userId;
|
||||
// application.rejectionReason = comment;
|
||||
}
|
||||
|
||||
// 更新申请状态
|
||||
@@ -283,9 +298,12 @@ const auditApplication = async (req, res) => {
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('审核贷款申请失败:', error);
|
||||
console.error('错误详情:', error.message);
|
||||
console.error('错误堆栈:', error.stack);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '审核贷款申请失败'
|
||||
message: '审核贷款申请失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -40,6 +40,13 @@ const getContracts = async (req, res) => {
|
||||
} else if (searchField === 'applicationNumber') {
|
||||
// 数据库中实际没有applicationNumber字段,使用id作为替代
|
||||
where.id = { [Op.like]: `%${searchValue}%` };
|
||||
} else if (searchField === 'borrowerName') {
|
||||
where.customer_name = { [Op.like]: `%${searchValue}%` };
|
||||
} else if (searchField === 'farmerName') {
|
||||
where.customer_name = { [Op.like]: `%${searchValue}%` };
|
||||
} else if (searchField === 'productName') {
|
||||
// 产品名称搜索暂时不处理,因为数据库中没有这个字段
|
||||
// 可以在前端过滤
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,18 +79,32 @@ const getContracts = async (req, res) => {
|
||||
limit
|
||||
});
|
||||
|
||||
// 格式化数据
|
||||
// 格式化数据 - 匹配前端期望的字段
|
||||
const contracts = rows.map(contract => ({
|
||||
id: contract.id,
|
||||
contract_number: contract.contract_number,
|
||||
customer_name: contract.customer_name,
|
||||
customer_phone: contract.customer_phone,
|
||||
customer_id_card: contract.customer_id_card,
|
||||
loan_amount: parseFloat(contract.loan_amount),
|
||||
loan_term: contract.loan_term,
|
||||
interest_rate: parseFloat(contract.interest_rate),
|
||||
contract_date: contract.contract_date,
|
||||
contractNumber: contract.contract_number,
|
||||
applicationNumber: `APP-${String(contract.id).padStart(6, '0')}`, // 生成申请单号
|
||||
productName: '养殖贷款', // 默认产品名称
|
||||
farmerName: contract.customer_name || '未知养殖户',
|
||||
borrowerName: contract.customer_name || '未知借款人',
|
||||
borrowerIdNumber: contract.customer_id_card || '',
|
||||
assetType: '养殖设备', // 默认生资种类
|
||||
applicationQuantity: '1', // 默认申请数量
|
||||
amount: parseFloat(contract.loan_amount),
|
||||
paidAmount: 0, // 默认已还款金额
|
||||
status: contract.status,
|
||||
type: 'livestock_collateral', // 默认类型
|
||||
term: contract.loan_term,
|
||||
interestRate: parseFloat(contract.interest_rate),
|
||||
phone: contract.customer_phone || '',
|
||||
purpose: '养殖经营', // 默认用途
|
||||
remark: '', // 默认备注
|
||||
contractTime: contract.contract_date,
|
||||
disbursementTime: null,
|
||||
maturityTime: null,
|
||||
completedTime: null,
|
||||
remainingAmount: parseFloat(contract.loan_amount), // 剩余金额等于贷款金额
|
||||
repaymentProgress: 0, // 默认还款进度
|
||||
created_at: contract.created_at,
|
||||
updated_at: contract.updated_at
|
||||
}));
|
||||
|
||||
@@ -201,7 +201,7 @@ const createLoanProduct = async (req, res) => {
|
||||
}
|
||||
|
||||
// 检查用户信息
|
||||
if (!req.user || !req.user.id) {
|
||||
if (!req.user || !req.user.userId) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '用户信息无效'
|
||||
@@ -222,8 +222,8 @@ const createLoanProduct = async (req, res) => {
|
||||
riskLevel,
|
||||
minLoanAmount: minLoanAmount ? parseFloat(minLoanAmount) : null,
|
||||
maxLoanAmount: maxLoanAmount ? parseFloat(maxLoanAmount) : null,
|
||||
createdBy: req.user.id,
|
||||
updatedBy: req.user.id
|
||||
createdBy: req.user.userId,
|
||||
updatedBy: req.user.userId
|
||||
});
|
||||
|
||||
// 获取创建后的完整信息
|
||||
@@ -313,7 +313,7 @@ const updateLoanProduct = async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
product.updatedBy = req.user.id;
|
||||
product.updatedBy = req.user.userId;
|
||||
await product.save();
|
||||
|
||||
// 获取更新后的完整信息
|
||||
@@ -440,7 +440,7 @@ const batchUpdateStatus = async (req, res) => {
|
||||
await LoanProduct.update(
|
||||
{
|
||||
onSaleStatus,
|
||||
updatedBy: req.user.id
|
||||
updatedBy: req.user.userId
|
||||
},
|
||||
{
|
||||
where: { id: { [Op.in]: ids } }
|
||||
|
||||
545
bank-backend/controllers/loanReleaseController.js
Normal file
545
bank-backend/controllers/loanReleaseController.js
Normal file
@@ -0,0 +1,545 @@
|
||||
/**
|
||||
* 贷款解押控制器
|
||||
* @file loanReleaseController.js
|
||||
* @description 银行系统贷款解押相关接口控制器
|
||||
*/
|
||||
const { LoanRelease, LoanReleaseHistory, User } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
/**
|
||||
* 获取解押申请列表
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
const getReleases = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
pageSize = 10,
|
||||
searchField = 'application_number',
|
||||
searchValue = '',
|
||||
status = '',
|
||||
sortField = 'created_at',
|
||||
sortOrder = 'DESC'
|
||||
} = req.query;
|
||||
|
||||
// 构建查询条件
|
||||
const whereCondition = {};
|
||||
|
||||
// 搜索条件
|
||||
if (searchValue) {
|
||||
switch (searchField) {
|
||||
case 'application_number':
|
||||
whereCondition.application_number = {
|
||||
[Op.like]: `%${searchValue}%`
|
||||
};
|
||||
break;
|
||||
case 'customer_name':
|
||||
whereCondition[Op.or] = [
|
||||
{ customer_name: { [Op.like]: `%${searchValue}%` } },
|
||||
{ farmer_name: { [Op.like]: `%${searchValue}%` } }
|
||||
];
|
||||
break;
|
||||
case 'product_name':
|
||||
whereCondition.product_name = {
|
||||
[Op.like]: `%${searchValue}%`
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 状态筛选
|
||||
if (status) {
|
||||
whereCondition.status = status;
|
||||
}
|
||||
|
||||
// 分页参数
|
||||
const offset = (page - 1) * pageSize;
|
||||
const limit = parseInt(pageSize);
|
||||
|
||||
// 查询数据
|
||||
const { count, rows } = await LoanRelease.findAndCountAll({
|
||||
where: whereCondition,
|
||||
order: [[sortField, sortOrder.toUpperCase()]],
|
||||
offset,
|
||||
limit,
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'processor',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// 格式化返回数据
|
||||
const releases = rows.map(release => ({
|
||||
id: release.id,
|
||||
applicationNumber: release.application_number,
|
||||
productName: release.product_name || '养殖贷款',
|
||||
farmerName: release.farmer_name || release.customer_name,
|
||||
applicantName: release.customer_name,
|
||||
applicantIdNumber: release.customer_id_card,
|
||||
applicantPhone: release.customer_phone,
|
||||
assetType: release.collateral_type === 'livestock' ? '牛' : '其他',
|
||||
releaseQuantity: release.release_quantity || '1头',
|
||||
releaseAmount: parseFloat(release.release_amount),
|
||||
status: release.status,
|
||||
applicationTime: release.application_date,
|
||||
processTime: release.process_date,
|
||||
completeTime: release.complete_date,
|
||||
processorName: release.processor?.real_name || release.processor_name,
|
||||
processComment: release.process_comment,
|
||||
rejectionReason: release.rejection_reason,
|
||||
remark: release.remark,
|
||||
created_at: release.created_at,
|
||||
updated_at: release.updated_at
|
||||
}));
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
releases,
|
||||
pagination: {
|
||||
current: parseInt(page),
|
||||
pageSize: limit,
|
||||
total: count,
|
||||
totalPages: Math.ceil(count / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取解押申请列表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取解押申请列表失败'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取解押申请详情
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
const getReleaseDetail = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const release = await LoanRelease.findByPk(id, {
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'processor',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!release) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '解押申请不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 获取历史记录
|
||||
const history = await LoanReleaseHistory.findAll({
|
||||
where: { release_id: id },
|
||||
order: [['operation_time', 'ASC']]
|
||||
});
|
||||
|
||||
const releaseDetail = {
|
||||
id: release.id,
|
||||
releaseNumber: release.application_number,
|
||||
customerName: release.customer_name,
|
||||
contractNumber: `CONTRACT-${String(release.contract_id || release.id).padStart(6, '0')}`,
|
||||
status: release.status,
|
||||
collateralType: release.collateral_type,
|
||||
collateralDescription: release.collateral_description,
|
||||
loanAmount: parseFloat(release.loan_amount || 0),
|
||||
collateralValue: parseFloat(release.collateral_value || 0),
|
||||
applicationTime: release.application_date,
|
||||
processTime: release.process_date,
|
||||
completeTime: release.complete_date,
|
||||
phone: release.customer_phone,
|
||||
idCard: release.customer_id_card,
|
||||
reason: release.application_reason,
|
||||
remark: release.remark,
|
||||
history: history.map(h => ({
|
||||
id: h.id,
|
||||
action: h.action,
|
||||
operator: h.operator,
|
||||
time: h.operation_time,
|
||||
comment: h.comment
|
||||
}))
|
||||
};
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: releaseDetail
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取解押申请详情失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取解押申请详情失败'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建解押申请
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
const createRelease = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
contract_id,
|
||||
customer_name,
|
||||
customer_phone,
|
||||
customer_id_card,
|
||||
farmer_name,
|
||||
product_name,
|
||||
collateral_type,
|
||||
collateral_description,
|
||||
collateral_value,
|
||||
loan_amount,
|
||||
release_amount,
|
||||
release_quantity,
|
||||
application_reason,
|
||||
remark
|
||||
} = req.body;
|
||||
|
||||
// 生成申请单号
|
||||
const applicationNumber = `REL-${Date.now()}`;
|
||||
|
||||
const release = await LoanRelease.create({
|
||||
application_number: applicationNumber,
|
||||
contract_id,
|
||||
customer_name,
|
||||
customer_phone,
|
||||
customer_id_card,
|
||||
farmer_name,
|
||||
product_name,
|
||||
collateral_type,
|
||||
collateral_description,
|
||||
collateral_value,
|
||||
loan_amount,
|
||||
release_amount,
|
||||
release_quantity,
|
||||
application_reason,
|
||||
remark,
|
||||
status: 'pending'
|
||||
});
|
||||
|
||||
// 创建历史记录
|
||||
await LoanReleaseHistory.create({
|
||||
release_id: release.id,
|
||||
action: 'apply',
|
||||
operator: customer_name,
|
||||
comment: '提交解押申请'
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '解押申请创建成功',
|
||||
data: {
|
||||
id: release.id,
|
||||
applicationNumber: release.application_number
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('创建解押申请失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '创建解押申请失败'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新解押申请
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
const updateRelease = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const updateData = req.body;
|
||||
|
||||
const release = await LoanRelease.findByPk(id);
|
||||
if (!release) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '解押申请不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 更新数据
|
||||
await release.update(updateData);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '解押申请更新成功'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('更新解押申请失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '更新解押申请失败'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理解押申请
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
const processRelease = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { action, comment, remark } = req.body;
|
||||
|
||||
if (!req.user || !req.user.userId) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '用户信息无效'
|
||||
});
|
||||
}
|
||||
|
||||
const release = await LoanRelease.findByPk(id);
|
||||
if (!release) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '解押申请不存在'
|
||||
});
|
||||
}
|
||||
|
||||
if (release.status !== 'pending') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '该申请已完成处理,无法重复操作'
|
||||
});
|
||||
}
|
||||
|
||||
const previousStatus = release.status;
|
||||
let newStatus = release.status;
|
||||
|
||||
// 根据处理动作更新状态
|
||||
if (action === 'approve') {
|
||||
newStatus = 'approved';
|
||||
} else if (action === 'reject') {
|
||||
newStatus = 'rejected';
|
||||
}
|
||||
|
||||
// 更新申请状态
|
||||
release.status = newStatus;
|
||||
release.process_date = new Date();
|
||||
release.processor_id = req.user.userId;
|
||||
release.processor_name = req.user.username;
|
||||
release.process_comment = comment;
|
||||
release.remark = remark;
|
||||
|
||||
if (action === 'reject') {
|
||||
release.rejection_reason = comment;
|
||||
}
|
||||
|
||||
await release.save();
|
||||
|
||||
// 创建历史记录
|
||||
await LoanReleaseHistory.create({
|
||||
release_id: release.id,
|
||||
action: action,
|
||||
operator: req.user.username,
|
||||
operator_id: req.user.userId,
|
||||
comment: comment,
|
||||
previous_status: previousStatus,
|
||||
new_status: newStatus
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `解押申请已${action === 'approve' ? '通过' : '拒绝'}`,
|
||||
data: {
|
||||
id: release.id,
|
||||
status: newStatus,
|
||||
action,
|
||||
comment
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('处理解押申请失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '处理解押申请失败'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 完成解押
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
const completeRelease = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { comment } = req.body;
|
||||
|
||||
if (!req.user || !req.user.userId) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '用户信息无效'
|
||||
});
|
||||
}
|
||||
|
||||
const release = await LoanRelease.findByPk(id);
|
||||
if (!release) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '解押申请不存在'
|
||||
});
|
||||
}
|
||||
|
||||
if (release.status !== 'approved') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '只有已通过的解押申请才能完成解押'
|
||||
});
|
||||
}
|
||||
|
||||
const previousStatus = release.status;
|
||||
|
||||
// 更新状态为已完成
|
||||
release.status = 'completed';
|
||||
release.complete_date = new Date();
|
||||
await release.save();
|
||||
|
||||
// 创建历史记录
|
||||
await LoanReleaseHistory.create({
|
||||
release_id: release.id,
|
||||
action: 'complete',
|
||||
operator: req.user.username,
|
||||
operator_id: req.user.userId,
|
||||
comment: comment || '解押手续办理完成',
|
||||
previous_status: previousStatus,
|
||||
new_status: 'completed'
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '解押完成',
|
||||
data: {
|
||||
id: release.id,
|
||||
status: 'completed'
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('完成解押失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '完成解押失败'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除解押申请
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
const deleteRelease = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const release = await LoanRelease.findByPk(id);
|
||||
if (!release) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '解押申请不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 只有待处理状态的申请才能删除
|
||||
if (release.status !== 'pending') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '只有待处理状态的申请才能删除'
|
||||
});
|
||||
}
|
||||
|
||||
await release.destroy();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '解押申请删除成功'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('删除解押申请失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '删除解押申请失败'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取解押统计信息
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
const getReleaseStats = async (req, res) => {
|
||||
try {
|
||||
const stats = await LoanRelease.findAll({
|
||||
attributes: [
|
||||
'status',
|
||||
[require('sequelize').fn('COUNT', '*'), 'count']
|
||||
],
|
||||
group: ['status']
|
||||
});
|
||||
|
||||
const totalCount = await LoanRelease.count();
|
||||
const pendingCount = await LoanRelease.count({ where: { status: 'pending' } });
|
||||
const processingCount = await LoanRelease.count({ where: { status: 'processing' } });
|
||||
const completedCount = await LoanRelease.count({ where: { status: 'completed' } });
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
total: totalCount,
|
||||
pending: pendingCount,
|
||||
processing: processingCount,
|
||||
completed: completedCount,
|
||||
statusStats: stats
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取解押统计信息失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取解押统计信息失败'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getReleases,
|
||||
getReleaseDetail,
|
||||
createRelease,
|
||||
updateRelease,
|
||||
processRelease,
|
||||
completeRelease,
|
||||
deleteRelease,
|
||||
getReleaseStats
|
||||
};
|
||||
@@ -1,56 +0,0 @@
|
||||
const { User } = require('./models')
|
||||
const bcrypt = require('bcryptjs')
|
||||
|
||||
async function createAdminUser() {
|
||||
try {
|
||||
console.log('开始创建管理员用户...')
|
||||
|
||||
// 检查是否已存在管理员用户
|
||||
const existingAdmin = await User.findOne({
|
||||
where: { username: 'admin' }
|
||||
})
|
||||
|
||||
if (existingAdmin) {
|
||||
console.log('管理员用户已存在,更新密码...')
|
||||
const hashedPassword = await bcrypt.hash('admin123', 10)
|
||||
await existingAdmin.update({
|
||||
password: hashedPassword,
|
||||
status: 'active'
|
||||
})
|
||||
console.log('✅ 管理员用户密码已更新')
|
||||
} else {
|
||||
console.log('创建新的管理员用户...')
|
||||
const hashedPassword = await bcrypt.hash('admin123', 10)
|
||||
|
||||
await User.create({
|
||||
username: 'admin',
|
||||
password: hashedPassword,
|
||||
real_name: '系统管理员',
|
||||
email: 'admin@bank.com',
|
||||
phone: '13800138000',
|
||||
id_card: '110101199001010001',
|
||||
status: 'active',
|
||||
role_id: 1
|
||||
})
|
||||
console.log('✅ 管理员用户创建成功')
|
||||
}
|
||||
|
||||
console.log('管理员用户信息:')
|
||||
console.log('用户名: admin')
|
||||
console.log('密码: admin123')
|
||||
console.log('状态: active')
|
||||
|
||||
} catch (error) {
|
||||
console.error('创建管理员用户失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
createAdminUser()
|
||||
.then(() => {
|
||||
console.log('管理员用户设置完成')
|
||||
process.exit(0)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('脚本执行失败:', error)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -1,43 +0,0 @@
|
||||
const { sequelize } = require('./config/database');
|
||||
|
||||
async function createAuditRecordsTable() {
|
||||
try {
|
||||
console.log('创建bank_audit_records表...');
|
||||
|
||||
await sequelize.query(`
|
||||
CREATE TABLE IF NOT EXISTS bank_audit_records (
|
||||
id INT(11) NOT NULL AUTO_INCREMENT,
|
||||
applicationId INT(11) NOT NULL,
|
||||
action VARCHAR(50) NOT NULL,
|
||||
auditor VARCHAR(100) NOT NULL,
|
||||
auditorId INT(11) NOT NULL,
|
||||
comment TEXT,
|
||||
auditTime DATETIME NOT NULL,
|
||||
previousStatus VARCHAR(50),
|
||||
newStatus VARCHAR(50),
|
||||
createdAt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updatedAt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_application_id (applicationId),
|
||||
KEY idx_auditor_id (auditorId),
|
||||
KEY idx_audit_time (auditTime)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
`, { type: sequelize.QueryTypes.RAW });
|
||||
|
||||
console.log('✅ bank_audit_records表创建成功');
|
||||
|
||||
// 标记迁移为完成
|
||||
await sequelize.query(`
|
||||
INSERT IGNORE INTO SequelizeMeta (name) VALUES ('20241220000008-create-audit-records.js')
|
||||
`, { type: sequelize.QueryTypes.RAW });
|
||||
|
||||
console.log('✅ 迁移记录已标记为完成');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 创建表失败:', error.message);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
createAuditRecordsTable();
|
||||
@@ -1,70 +0,0 @@
|
||||
const http = require('http');
|
||||
|
||||
async function testAccountsAPI() {
|
||||
try {
|
||||
// 先登录获取token
|
||||
const loginResult = await makeRequest({
|
||||
hostname: 'localhost',
|
||||
port: 5351,
|
||||
path: '/api/auth/login',
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
}, {
|
||||
username: 'admin',
|
||||
password: 'Admin123456'
|
||||
});
|
||||
|
||||
if (!loginResult.data.success) {
|
||||
console.log('登录失败:', loginResult.data.message);
|
||||
return;
|
||||
}
|
||||
|
||||
const token = loginResult.data.data.token;
|
||||
console.log('登录成功,token:', token.substring(0, 20) + '...');
|
||||
|
||||
// 测试账户API
|
||||
const accountsResult = await makeRequest({
|
||||
hostname: 'localhost',
|
||||
port: 5351,
|
||||
path: '/api/accounts?page=1&pageSize=10',
|
||||
method: 'GET',
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
});
|
||||
|
||||
console.log('账户API响应状态:', accountsResult.status);
|
||||
console.log('账户API响应数据:', JSON.stringify(accountsResult.data, null, 2));
|
||||
|
||||
} catch (error) {
|
||||
console.error('测试失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function makeRequest(options, data = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = http.request(options, (res) => {
|
||||
let body = '';
|
||||
res.on('data', (chunk) => {
|
||||
body += chunk;
|
||||
});
|
||||
res.on('end', () => {
|
||||
try {
|
||||
const result = JSON.parse(body);
|
||||
resolve({ status: res.statusCode, data: result });
|
||||
} catch (error) {
|
||||
resolve({ status: res.statusCode, data: body });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (error) => {
|
||||
reject(error);
|
||||
});
|
||||
|
||||
if (data) {
|
||||
req.write(JSON.stringify(data));
|
||||
}
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
testAccountsAPI();
|
||||
@@ -1,131 +0,0 @@
|
||||
const { User, Role } = require('./models');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
async function debugAuthDetailed() {
|
||||
try {
|
||||
console.log('=== 详细调试认证逻辑 ===\n');
|
||||
|
||||
// 1. 检查数据库连接
|
||||
console.log('1. 检查数据库连接...');
|
||||
const user = await User.findOne({ where: { username: 'admin' } });
|
||||
if (!user) {
|
||||
console.log('❌ 未找到admin用户');
|
||||
return;
|
||||
}
|
||||
console.log('✅ 数据库连接正常,找到admin用户\n');
|
||||
|
||||
// 2. 检查用户基本信息
|
||||
console.log('2. 检查用户基本信息...');
|
||||
console.log('用户名:', user.username);
|
||||
console.log('状态:', user.status);
|
||||
console.log('登录尝试次数:', user.login_attempts);
|
||||
console.log('锁定时间:', user.locked_until);
|
||||
console.log('密码哈希:', user.password);
|
||||
console.log('');
|
||||
|
||||
// 3. 检查用户角色关联
|
||||
console.log('3. 检查用户角色关联...');
|
||||
const userWithRole = await User.findOne({
|
||||
where: { username: 'admin' },
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role'
|
||||
}]
|
||||
});
|
||||
|
||||
if (userWithRole) {
|
||||
console.log('✅ 用户角色关联正常');
|
||||
console.log('角色:', userWithRole.role ? userWithRole.role.name : '无角色');
|
||||
} else {
|
||||
console.log('❌ 用户角色关联失败');
|
||||
}
|
||||
console.log('');
|
||||
|
||||
// 4. 测试密码验证
|
||||
console.log('4. 测试密码验证...');
|
||||
const testPassword = 'Admin123456';
|
||||
console.log('测试密码:', testPassword);
|
||||
|
||||
// 直接使用bcrypt比较
|
||||
const directTest = await bcrypt.compare(testPassword, user.password);
|
||||
console.log('直接bcrypt验证:', directTest);
|
||||
|
||||
// 使用模型方法验证
|
||||
const modelTest = await user.validPassword(testPassword);
|
||||
console.log('模型验证:', modelTest);
|
||||
|
||||
if (!directTest) {
|
||||
console.log('❌ 密码不匹配,重新生成密码...');
|
||||
const newHash = await bcrypt.hash(testPassword, 10);
|
||||
console.log('新哈希:', newHash);
|
||||
|
||||
await user.update({
|
||||
password: newHash,
|
||||
status: 'active',
|
||||
login_attempts: 0,
|
||||
locked_until: null
|
||||
});
|
||||
|
||||
console.log('✅ 密码已更新');
|
||||
|
||||
// 重新加载用户数据
|
||||
await user.reload();
|
||||
|
||||
// 再次验证
|
||||
const finalTest = await bcrypt.compare(testPassword, user.password);
|
||||
console.log('最终验证:', finalTest);
|
||||
|
||||
if (finalTest) {
|
||||
console.log('🎉 密码修复成功!');
|
||||
} else {
|
||||
console.log('❌ 密码修复失败');
|
||||
}
|
||||
} else {
|
||||
console.log('✅ 密码验证成功');
|
||||
}
|
||||
console.log('');
|
||||
|
||||
// 5. 模拟完整的登录流程
|
||||
console.log('5. 模拟完整的登录流程...');
|
||||
const loginUser = await User.findOne({
|
||||
where: { username: 'admin' },
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role'
|
||||
}]
|
||||
});
|
||||
|
||||
if (loginUser) {
|
||||
console.log('✅ 用户查找成功');
|
||||
console.log('用户状态:', loginUser.status);
|
||||
|
||||
if (loginUser.status !== 'active') {
|
||||
console.log('❌ 用户状态不是active:', loginUser.status);
|
||||
} else {
|
||||
console.log('✅ 用户状态正常');
|
||||
}
|
||||
|
||||
const passwordValid = await loginUser.validPassword(testPassword);
|
||||
console.log('密码验证结果:', passwordValid);
|
||||
|
||||
if (passwordValid) {
|
||||
console.log('🎉 完整登录流程验证成功!');
|
||||
console.log('用户名: admin');
|
||||
console.log('密码: Admin123456');
|
||||
console.log('状态: active');
|
||||
} else {
|
||||
console.log('❌ 密码验证失败');
|
||||
}
|
||||
} else {
|
||||
console.log('❌ 用户查找失败');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('调试失败:', error.message);
|
||||
console.error('错误堆栈:', error.stack);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
debugAuthDetailed();
|
||||
@@ -1,73 +0,0 @@
|
||||
const { User } = require('./models');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
async function debugLogin() {
|
||||
try {
|
||||
console.log('=== 调试登录问题 ===');
|
||||
|
||||
// 查找用户
|
||||
const user = await User.findOne({ where: { username: 'admin' } });
|
||||
if (!user) {
|
||||
console.log('❌ 未找到admin用户');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('✅ 找到admin用户');
|
||||
console.log('用户名:', user.username);
|
||||
console.log('状态:', user.status);
|
||||
console.log('登录尝试次数:', user.login_attempts);
|
||||
console.log('锁定时间:', user.locked_until);
|
||||
console.log('密码哈希:', user.password);
|
||||
|
||||
// 测试密码验证
|
||||
const testPassword = 'Admin123456';
|
||||
console.log('\n=== 测试密码验证 ===');
|
||||
console.log('测试密码:', testPassword);
|
||||
|
||||
// 直接使用bcrypt比较
|
||||
const directTest = await bcrypt.compare(testPassword, user.password);
|
||||
console.log('直接bcrypt验证:', directTest);
|
||||
|
||||
// 使用模型方法验证
|
||||
const modelTest = await user.validPassword(testPassword);
|
||||
console.log('模型验证:', modelTest);
|
||||
|
||||
if (directTest && modelTest) {
|
||||
console.log('✅ 密码验证成功!');
|
||||
} else {
|
||||
console.log('❌ 密码验证失败');
|
||||
|
||||
// 重新生成密码
|
||||
console.log('\n=== 重新生成密码 ===');
|
||||
const newHash = await bcrypt.hash(testPassword, 10);
|
||||
console.log('新哈希:', newHash);
|
||||
|
||||
await user.update({
|
||||
password: newHash,
|
||||
status: 'active',
|
||||
login_attempts: 0,
|
||||
locked_until: null
|
||||
});
|
||||
|
||||
console.log('✅ 密码已更新');
|
||||
|
||||
// 再次验证
|
||||
const finalTest = await bcrypt.compare(testPassword, newHash);
|
||||
console.log('最终验证:', finalTest);
|
||||
|
||||
if (finalTest) {
|
||||
console.log('🎉 密码修复成功!');
|
||||
console.log('用户名: admin');
|
||||
console.log('密码: Admin123456');
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('调试失败:', error.message);
|
||||
console.error('错误堆栈:', error.stack);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
debugLogin();
|
||||
@@ -1,83 +0,0 @@
|
||||
const { User } = require('./models');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
async function debugThisPassword() {
|
||||
try {
|
||||
console.log('=== 调试this.password问题 ===\n');
|
||||
|
||||
// 1. 获取用户
|
||||
const user = await User.findOne({ where: { username: 'admin' } });
|
||||
if (!user) {
|
||||
console.log('❌ 未找到admin用户');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('1. 用户基本信息:');
|
||||
console.log('用户名:', user.username);
|
||||
console.log('this.password类型:', typeof user.password);
|
||||
console.log('this.password值:', user.password);
|
||||
console.log('this.password长度:', user.password ? user.password.length : 0);
|
||||
console.log('');
|
||||
|
||||
// 2. 测试密码
|
||||
const testPassword = 'Admin123456';
|
||||
console.log('2. 测试密码:', testPassword);
|
||||
console.log('');
|
||||
|
||||
// 3. 直接使用bcrypt比较
|
||||
console.log('3. 直接使用bcrypt比较:');
|
||||
const directTest = await bcrypt.compare(testPassword, user.password);
|
||||
console.log('直接bcrypt验证结果:', directTest);
|
||||
console.log('');
|
||||
|
||||
// 4. 检查this.password是否为空或undefined
|
||||
console.log('4. 检查this.password状态:');
|
||||
console.log('this.password === null:', user.password === null);
|
||||
console.log('this.password === undefined:', user.password === undefined);
|
||||
console.log('this.password === "":', user.password === "");
|
||||
console.log('this.password存在:', !!user.password);
|
||||
console.log('');
|
||||
|
||||
// 5. 如果this.password有问题,重新设置
|
||||
if (!user.password || user.password === '') {
|
||||
console.log('5. this.password有问题,重新设置...');
|
||||
const newHash = await bcrypt.hash(testPassword, 10);
|
||||
console.log('新生成的哈希:', newHash);
|
||||
|
||||
await user.update({ password: newHash });
|
||||
await user.reload();
|
||||
|
||||
console.log('更新后的this.password:', user.password);
|
||||
console.log('更新后的this.password类型:', typeof user.password);
|
||||
console.log('');
|
||||
}
|
||||
|
||||
// 6. 再次测试
|
||||
console.log('6. 再次测试密码验证:');
|
||||
const finalTest = await user.validPassword(testPassword);
|
||||
console.log('最终验证结果:', finalTest);
|
||||
|
||||
// 7. 手动测试bcrypt
|
||||
console.log('7. 手动测试bcrypt:');
|
||||
const manualTest = await bcrypt.compare(testPassword, user.password);
|
||||
console.log('手动bcrypt验证结果:', manualTest);
|
||||
|
||||
if (finalTest && manualTest) {
|
||||
console.log('🎉 密码验证成功!');
|
||||
} else {
|
||||
console.log('❌ 密码验证失败');
|
||||
console.log('可能的原因:');
|
||||
console.log('- this.password为空或undefined');
|
||||
console.log('- 数据库更新失败');
|
||||
console.log('- Sequelize模型问题');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('调试失败:', error.message);
|
||||
console.error('错误堆栈:', error.stack);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
debugThisPassword();
|
||||
@@ -797,17 +797,248 @@
|
||||
- **接口地址**: `POST /api/employees/export`
|
||||
- **功能描述**: 导出员工数据
|
||||
|
||||
## 13. 个人中心模块 (Profile)
|
||||
## 13. 贷款解押模块 (Loan Release)
|
||||
|
||||
### 13.1 获取个人信息
|
||||
### 13.1 获取解押申请列表
|
||||
- **接口地址**: `GET /api/loan-releases`
|
||||
- **功能描述**: 获取贷款解押申请列表,支持分页和搜索
|
||||
- **请求参数**:
|
||||
- `page` (integer, optional): 页码,默认1
|
||||
- `pageSize` (integer, optional): 每页数量,默认10
|
||||
- `searchField` (string, optional): 搜索字段 (application_number, customer_name, product_name)
|
||||
- `searchValue` (string, optional): 搜索值
|
||||
- `status` (string, optional): 解押状态筛选
|
||||
- `sortField` (string, optional): 排序字段
|
||||
- `sortOrder` (string, optional): 排序方向
|
||||
- **响应数据**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"releases": [
|
||||
{
|
||||
"id": 1,
|
||||
"applicationNumber": "20240227145555918",
|
||||
"productName": "中国工商银行扎旗支行\"畜禽活体抵押\"",
|
||||
"farmerName": "刘超",
|
||||
"applicantName": "刘超",
|
||||
"applicantIdNumber": "511123199001013017",
|
||||
"applicantPhone": "13800138000",
|
||||
"assetType": "牛",
|
||||
"releaseQuantity": "10头",
|
||||
"releaseAmount": 10000,
|
||||
"status": "released",
|
||||
"applicationTime": "2024-02-27T06:55:55.000Z",
|
||||
"processTime": "2024-02-27T07:30:00.000Z",
|
||||
"completeTime": "2024-02-27T08:00:00.000Z",
|
||||
"processorName": "系统管理员",
|
||||
"processComment": "审核通过,同意解押",
|
||||
"rejectionReason": null,
|
||||
"remark": "客户信用良好",
|
||||
"created_at": "2025-09-26T03:38:50.000Z",
|
||||
"updated_at": "2025-09-26T03:38:50.000Z"
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"current": 1,
|
||||
"pageSize": 10,
|
||||
"total": 6,
|
||||
"totalPages": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 13.2 获取解押申请详情
|
||||
- **接口地址**: `GET /api/loan-releases/:id`
|
||||
- **功能描述**: 获取指定解押申请的详细信息
|
||||
- **路径参数**:
|
||||
- `id` (integer, required): 解押申请ID
|
||||
- **响应数据**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": 1,
|
||||
"releaseNumber": "20240227145555918",
|
||||
"customerName": "刘超",
|
||||
"contractNumber": "CONTRACT-000007",
|
||||
"status": "released",
|
||||
"collateralType": "livestock",
|
||||
"collateralDescription": "优质肉牛",
|
||||
"loanAmount": 100000,
|
||||
"collateralValue": 150000,
|
||||
"applicationTime": "2024-02-27T06:55:55.000Z",
|
||||
"processTime": "2024-02-27T07:30:00.000Z",
|
||||
"completeTime": "2024-02-27T08:00:00.000Z",
|
||||
"phone": "13800138000",
|
||||
"idCard": "511123199001013017",
|
||||
"reason": "需要资金周转,申请部分解押",
|
||||
"remark": "客户信用良好",
|
||||
"history": [
|
||||
{
|
||||
"id": 1,
|
||||
"action": "apply",
|
||||
"operator": "刘超",
|
||||
"time": "2025-09-26T03:38:51.000Z",
|
||||
"comment": "提交解押申请"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"action": "complete",
|
||||
"operator": "系统管理员",
|
||||
"time": "2025-09-26T03:38:51.000Z",
|
||||
"comment": "解押手续办理完成"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 13.3 创建解押申请
|
||||
- **接口地址**: `POST /api/loan-releases`
|
||||
- **功能描述**: 创建新的解押申请
|
||||
- **请求参数**:
|
||||
```json
|
||||
{
|
||||
"contract_id": 1,
|
||||
"customer_name": "测试用户",
|
||||
"customer_phone": "13800138000",
|
||||
"customer_id_card": "511123199001010001",
|
||||
"farmer_name": "测试用户",
|
||||
"product_name": "测试产品",
|
||||
"collateral_type": "livestock",
|
||||
"collateral_description": "测试抵押物",
|
||||
"collateral_value": 100000,
|
||||
"loan_amount": 80000,
|
||||
"release_amount": 50000,
|
||||
"release_quantity": "5头",
|
||||
"application_reason": "测试解押申请",
|
||||
"remark": "测试备注"
|
||||
}
|
||||
```
|
||||
- **响应数据**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "解押申请创建成功",
|
||||
"data": {
|
||||
"id": 13,
|
||||
"applicationNumber": "REL-1758868323652"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 13.4 更新解押申请
|
||||
- **接口地址**: `PUT /api/loan-releases/:id`
|
||||
- **功能描述**: 更新指定解押申请信息
|
||||
- **路径参数**:
|
||||
- `id` (integer, required): 解押申请ID
|
||||
- **请求参数**: 同创建解押申请参数
|
||||
- **响应数据**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "解押申请更新成功"
|
||||
}
|
||||
```
|
||||
|
||||
### 13.5 处理解押申请
|
||||
- **接口地址**: `POST /api/loan-releases/:id/process`
|
||||
- **功能描述**: 处理解押申请(通过/拒绝)
|
||||
- **路径参数**:
|
||||
- `id` (integer, required): 解押申请ID
|
||||
- **请求参数**:
|
||||
```json
|
||||
{
|
||||
"action": "approve",
|
||||
"comment": "审核通过",
|
||||
"remark": "测试处理"
|
||||
}
|
||||
```
|
||||
- **响应数据**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "解押申请已通过",
|
||||
"data": {
|
||||
"id": 13,
|
||||
"status": "approved",
|
||||
"action": "approve",
|
||||
"comment": "审核通过"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 13.6 完成解押
|
||||
- **接口地址**: `POST /api/loan-releases/:id/complete`
|
||||
- **功能描述**: 完成解押手续
|
||||
- **路径参数**:
|
||||
- `id` (integer, required): 解押申请ID
|
||||
- **请求参数**:
|
||||
```json
|
||||
{
|
||||
"comment": "解押手续办理完成"
|
||||
}
|
||||
```
|
||||
- **响应数据**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "解押完成",
|
||||
"data": {
|
||||
"id": 13,
|
||||
"status": "completed"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 13.7 删除解押申请
|
||||
- **接口地址**: `DELETE /api/loan-releases/:id`
|
||||
- **功能描述**: 删除指定解押申请(仅限待处理状态)
|
||||
- **路径参数**:
|
||||
- `id` (integer, required): 解押申请ID
|
||||
- **响应数据**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "解押申请删除成功"
|
||||
}
|
||||
```
|
||||
|
||||
### 13.8 获取解押统计信息
|
||||
- **接口地址**: `GET /api/loan-releases/stats`
|
||||
- **功能描述**: 获取解押申请统计信息
|
||||
- **响应数据**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"total": 6,
|
||||
"pending": 0,
|
||||
"processing": 0,
|
||||
"completed": 6,
|
||||
"statusStats": [
|
||||
{
|
||||
"status": "released",
|
||||
"count": 6
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 14. 个人中心模块 (Profile)
|
||||
|
||||
### 14.1 获取个人信息
|
||||
- **接口地址**: `GET /api/profile`
|
||||
- **功能描述**: 获取当前用户个人信息
|
||||
|
||||
### 13.2 更新个人信息
|
||||
### 14.2 更新个人信息
|
||||
- **接口地址**: `PUT /api/profile`
|
||||
- **功能描述**: 更新个人信息
|
||||
|
||||
### 13.3 修改密码
|
||||
### 14.3 修改密码
|
||||
- **接口地址**: `POST /api/profile/change-password`
|
||||
- **功能描述**: 修改用户密码
|
||||
- **请求参数**:
|
||||
@@ -818,48 +1049,48 @@
|
||||
}
|
||||
```
|
||||
|
||||
### 13.4 更新头像
|
||||
### 14.4 更新头像
|
||||
- **接口地址**: `POST /api/profile/avatar`
|
||||
- **功能描述**: 更新用户头像
|
||||
- **请求类型**: `multipart/form-data`
|
||||
|
||||
### 13.5 获取通知列表
|
||||
### 14.5 获取通知列表
|
||||
- **接口地址**: `GET /api/profile/notifications`
|
||||
- **功能描述**: 获取用户通知列表
|
||||
|
||||
### 13.6 标记通知已读
|
||||
### 14.6 标记通知已读
|
||||
- **接口地址**: `PUT /api/profile/notifications/:id/read`
|
||||
- **功能描述**: 标记通知为已读
|
||||
|
||||
## 14. 系统设置模块 (Settings)
|
||||
## 15. 系统设置模块 (Settings)
|
||||
|
||||
### 14.1 获取系统设置
|
||||
### 15.1 获取系统设置
|
||||
- **接口地址**: `GET /api/settings`
|
||||
- **功能描述**: 获取系统配置设置
|
||||
|
||||
### 14.2 更新基本设置
|
||||
### 15.2 更新基本设置
|
||||
- **接口地址**: `PUT /api/settings/basic`
|
||||
- **功能描述**: 更新系统基本设置
|
||||
|
||||
### 14.3 更新安全设置
|
||||
### 15.3 更新安全设置
|
||||
- **接口地址**: `PUT /api/settings/security`
|
||||
- **功能描述**: 更新安全相关设置
|
||||
|
||||
### 14.4 获取系统日志
|
||||
### 15.4 获取系统日志
|
||||
- **接口地址**: `GET /api/settings/logs`
|
||||
- **功能描述**: 获取系统操作日志
|
||||
|
||||
### 14.5 备份系统数据
|
||||
### 15.5 备份系统数据
|
||||
- **接口地址**: `POST /api/settings/backup`
|
||||
- **功能描述**: 备份系统数据
|
||||
|
||||
### 14.6 恢复系统数据
|
||||
### 15.6 恢复系统数据
|
||||
- **接口地址**: `POST /api/settings/restore`
|
||||
- **功能描述**: 恢复系统数据
|
||||
|
||||
## 15. 文件上传模块 (File Upload)
|
||||
## 16. 文件上传模块 (File Upload)
|
||||
|
||||
### 15.1 上传文件
|
||||
### 16.1 上传文件
|
||||
- **接口地址**: `POST /api/upload`
|
||||
- **功能描述**: 上传文件
|
||||
- **请求类型**: `multipart/form-data`
|
||||
@@ -867,13 +1098,13 @@
|
||||
- `file`: 文件
|
||||
- `type`: 文件类型 (avatar, document, image)
|
||||
|
||||
### 15.2 删除文件
|
||||
### 16.2 删除文件
|
||||
- **接口地址**: `DELETE /api/upload/:id`
|
||||
- **功能描述**: 删除已上传的文件
|
||||
|
||||
## 16. 健康检查模块 (Health Check)
|
||||
## 17. 健康检查模块 (Health Check)
|
||||
|
||||
### 16.1 系统健康检查
|
||||
### 17.1 系统健康检查
|
||||
- **接口地址**: `GET /api/health`
|
||||
- **功能描述**: 检查系统健康状态
|
||||
- **响应数据**:
|
||||
@@ -956,7 +1187,7 @@ Authorization: Bearer <your-jwt-token>
|
||||
|
||||
---
|
||||
|
||||
**文档版本**: v1.0.0
|
||||
**最后更新**: 2024-01-18
|
||||
**文档版本**: v1.1.0
|
||||
**最后更新**: 2025-09-26
|
||||
**维护人员**: 开发团队
|
||||
|
||||
272
bank-backend/docs/API_OVERVIEW.md
Normal file
272
bank-backend/docs/API_OVERVIEW.md
Normal file
@@ -0,0 +1,272 @@
|
||||
# 银行管理系统API接口总览
|
||||
|
||||
## 概述
|
||||
|
||||
本文档提供了银行管理系统所有API接口的快速参考,包括接口地址、请求方法、功能描述等信息。
|
||||
|
||||
## 基础信息
|
||||
|
||||
- **基础URL**: `http://localhost:5351`
|
||||
- **API版本**: v1.0.0
|
||||
- **认证方式**: JWT Token
|
||||
- **数据格式**: JSON
|
||||
|
||||
## 接口列表
|
||||
|
||||
### 1. 认证模块 (Authentication)
|
||||
|
||||
| 接口地址 | 请求方法 | 功能描述 | 认证要求 |
|
||||
|---------|---------|---------|---------|
|
||||
| `/api/auth/login` | POST | 用户登录 | 否 |
|
||||
| `/api/auth/logout` | POST | 用户登出 | 是 |
|
||||
| `/api/auth/refresh` | POST | 刷新令牌 | 是 |
|
||||
| `/api/auth/me` | GET | 获取当前用户信息 | 是 |
|
||||
| `/api/auth/change-password` | POST | 修改密码 | 是 |
|
||||
|
||||
### 2. 仪表盘模块 (Dashboard)
|
||||
|
||||
| 接口地址 | 请求方法 | 功能描述 | 认证要求 |
|
||||
|---------|---------|---------|---------|
|
||||
| `/api/dashboard` | GET | 获取仪表盘统计数据 | 是 |
|
||||
| `/api/dashboard/charts` | GET | 获取图表数据 | 是 |
|
||||
| `/api/dashboard/recent-transactions` | GET | 获取最近交易记录 | 是 |
|
||||
|
||||
### 3. 用户管理模块 (Users)
|
||||
|
||||
| 接口地址 | 请求方法 | 功能描述 | 认证要求 |
|
||||
|---------|---------|---------|---------|
|
||||
| `/api/users` | GET | 获取用户列表 | 是 |
|
||||
| `/api/users/:id` | GET | 获取用户详情 | 是 |
|
||||
| `/api/users` | POST | 创建用户 | 是 |
|
||||
| `/api/users/:id` | PUT | 更新用户 | 是 |
|
||||
| `/api/users/:id` | DELETE | 删除用户 | 是 |
|
||||
| `/api/users/stats` | GET | 获取用户统计 | 是 |
|
||||
|
||||
### 4. 账户管理模块 (Accounts)
|
||||
|
||||
| 接口地址 | 请求方法 | 功能描述 | 认证要求 |
|
||||
|---------|---------|---------|---------|
|
||||
| `/api/accounts` | GET | 获取账户列表 | 是 |
|
||||
| `/api/accounts/:id` | GET | 获取账户详情 | 是 |
|
||||
| `/api/accounts` | POST | 创建账户 | 是 |
|
||||
| `/api/accounts/:id` | PUT | 更新账户 | 是 |
|
||||
| `/api/accounts/:id` | DELETE | 删除账户 | 是 |
|
||||
| `/api/accounts/:id/deposit` | POST | 存款 | 是 |
|
||||
| `/api/accounts/:id/withdraw` | POST | 取款 | 是 |
|
||||
|
||||
### 5. 交易记录模块 (Transactions)
|
||||
|
||||
| 接口地址 | 请求方法 | 功能描述 | 认证要求 |
|
||||
|---------|---------|---------|---------|
|
||||
| `/api/transactions` | GET | 获取交易记录列表 | 是 |
|
||||
| `/api/transactions/:id` | GET | 获取交易详情 | 是 |
|
||||
| `/api/transactions` | POST | 创建交易记录 | 是 |
|
||||
| `/api/transactions/stats` | GET | 获取交易统计 | 是 |
|
||||
|
||||
### 6. 贷款产品模块 (Loan Products)
|
||||
|
||||
| 接口地址 | 请求方法 | 功能描述 | 认证要求 |
|
||||
|---------|---------|---------|---------|
|
||||
| `/api/loan-products` | GET | 获取贷款产品列表 | 是 |
|
||||
| `/api/loan-products/:id` | GET | 获取产品详情 | 是 |
|
||||
| `/api/loan-products` | POST | 创建产品 | 是 |
|
||||
| `/api/loan-products/:id` | PUT | 更新产品 | 是 |
|
||||
| `/api/loan-products/:id` | DELETE | 删除产品 | 是 |
|
||||
| `/api/loan-products/stats` | GET | 获取产品统计 | 是 |
|
||||
| `/api/loan-products/batch/status` | PUT | 批量更新状态 | 是 |
|
||||
| `/api/loan-products/batch/delete` | DELETE | 批量删除 | 是 |
|
||||
|
||||
### 7. 贷款申请模块 (Loan Applications)
|
||||
|
||||
| 接口地址 | 请求方法 | 功能描述 | 认证要求 |
|
||||
|---------|---------|---------|---------|
|
||||
| `/api/loan-applications` | GET | 获取申请列表 | 是 |
|
||||
| `/api/loan-applications/:id` | GET | 获取申请详情 | 是 |
|
||||
| `/api/loan-applications/:id/audit` | POST | 审核申请 | 是 |
|
||||
| `/api/loan-applications/stats` | GET | 获取申请统计 | 是 |
|
||||
| `/api/loan-applications/batch/status` | PUT | 批量更新状态 | 是 |
|
||||
|
||||
### 8. 贷款合同模块 (Loan Contracts)
|
||||
|
||||
| 接口地址 | 请求方法 | 功能描述 | 认证要求 |
|
||||
|---------|---------|---------|---------|
|
||||
| `/api/loan-contracts` | GET | 获取合同列表 | 是 |
|
||||
| `/api/loan-contracts/:id` | GET | 获取合同详情 | 是 |
|
||||
| `/api/loan-contracts` | POST | 创建合同 | 是 |
|
||||
| `/api/loan-contracts/:id` | PUT | 更新合同 | 是 |
|
||||
| `/api/loan-contracts/:id` | DELETE | 删除合同 | 是 |
|
||||
| `/api/loan-contracts/stats` | GET | 获取合同统计 | 是 |
|
||||
| `/api/loan-contracts/batch/status` | PUT | 批量更新状态 | 是 |
|
||||
|
||||
### 9. 贷款解押模块 (Loan Release) - 新增
|
||||
|
||||
| 接口地址 | 请求方法 | 功能描述 | 认证要求 |
|
||||
|---------|---------|---------|---------|
|
||||
| `/api/loan-releases` | GET | 获取解押申请列表 | 是 |
|
||||
| `/api/loan-releases/:id` | GET | 获取解押申请详情 | 是 |
|
||||
| `/api/loan-releases` | POST | 创建解押申请 | 是 |
|
||||
| `/api/loan-releases/:id` | PUT | 更新解押申请 | 是 |
|
||||
| `/api/loan-releases/:id/process` | POST | 处理解押申请 | 是 |
|
||||
| `/api/loan-releases/:id/complete` | POST | 完成解押 | 是 |
|
||||
| `/api/loan-releases/:id` | DELETE | 删除解押申请 | 是 |
|
||||
| `/api/loan-releases/stats` | GET | 获取解押统计 | 是 |
|
||||
|
||||
### 10. 员工管理模块 (Employees)
|
||||
|
||||
| 接口地址 | 请求方法 | 功能描述 | 认证要求 |
|
||||
|---------|---------|---------|---------|
|
||||
| `/api/employees` | GET | 获取员工列表 | 是 |
|
||||
| `/api/employees/:id` | GET | 获取员工详情 | 是 |
|
||||
| `/api/employees` | POST | 创建员工 | 是 |
|
||||
| `/api/employees/:id` | PUT | 更新员工 | 是 |
|
||||
| `/api/employees/:id` | DELETE | 删除员工 | 是 |
|
||||
| `/api/employees/stats` | GET | 获取员工统计 | 是 |
|
||||
| `/api/employees/export` | POST | 导出员工数据 | 是 |
|
||||
|
||||
### 11. 项目管理模块 (Projects)
|
||||
|
||||
| 接口地址 | 请求方法 | 功能描述 | 认证要求 |
|
||||
|---------|---------|---------|---------|
|
||||
| `/api/projects` | GET | 获取项目列表 | 是 |
|
||||
| `/api/projects/:id` | GET | 获取项目详情 | 是 |
|
||||
| `/api/projects` | POST | 创建项目 | 是 |
|
||||
| `/api/projects/:id` | PUT | 更新项目 | 是 |
|
||||
| `/api/projects/:id` | DELETE | 删除项目 | 是 |
|
||||
| `/api/projects/stats` | GET | 获取项目统计 | 是 |
|
||||
|
||||
### 12. 监管任务模块 (Supervision Tasks)
|
||||
|
||||
| 接口地址 | 请求方法 | 功能描述 | 认证要求 |
|
||||
|---------|---------|---------|---------|
|
||||
| `/api/supervision-tasks` | GET | 获取监管任务列表 | 是 |
|
||||
| `/api/supervision-tasks/:id` | GET | 获取任务详情 | 是 |
|
||||
| `/api/supervision-tasks` | POST | 创建监管任务 | 是 |
|
||||
| `/api/supervision-tasks/:id` | PUT | 更新任务 | 是 |
|
||||
| `/api/supervision-tasks/:id` | DELETE | 删除任务 | 是 |
|
||||
| `/api/supervision-tasks/stats` | GET | 获取任务统计 | 是 |
|
||||
|
||||
### 13. 安装任务模块 (Installation Tasks)
|
||||
|
||||
| 接口地址 | 请求方法 | 功能描述 | 认证要求 |
|
||||
|---------|---------|---------|---------|
|
||||
| `/api/installation-tasks` | GET | 获取安装任务列表 | 是 |
|
||||
| `/api/installation-tasks/:id` | GET | 获取任务详情 | 是 |
|
||||
| `/api/installation-tasks` | POST | 创建安装任务 | 是 |
|
||||
| `/api/installation-tasks/:id` | PUT | 更新任务 | 是 |
|
||||
| `/api/installation-tasks/:id` | DELETE | 删除任务 | 是 |
|
||||
| `/api/installation-tasks/stats` | GET | 获取任务统计 | 是 |
|
||||
|
||||
### 14. 完成监管模块 (Completed Supervisions)
|
||||
|
||||
| 接口地址 | 请求方法 | 功能描述 | 认证要求 |
|
||||
|---------|---------|---------|---------|
|
||||
| `/api/completed-supervisions` | GET | 获取完成监管列表 | 是 |
|
||||
| `/api/completed-supervisions/:id` | GET | 获取完成监管详情 | 是 |
|
||||
| `/api/completed-supervisions` | POST | 创建完成监管记录 | 是 |
|
||||
| `/api/completed-supervisions/:id` | PUT | 更新完成监管记录 | 是 |
|
||||
| `/api/completed-supervisions/:id` | DELETE | 删除完成监管记录 | 是 |
|
||||
| `/api/completed-supervisions/stats` | GET | 获取完成监管统计 | 是 |
|
||||
| `/api/completed-supervisions/batch/delete` | DELETE | 批量删除 | 是 |
|
||||
|
||||
### 15. 报表模块 (Reports)
|
||||
|
||||
| 接口地址 | 请求方法 | 功能描述 | 认证要求 |
|
||||
|---------|---------|---------|---------|
|
||||
| `/api/reports` | GET | 获取报表列表 | 是 |
|
||||
| `/api/reports/:id` | GET | 获取报表详情 | 是 |
|
||||
| `/api/reports` | POST | 创建报表 | 是 |
|
||||
| `/api/reports/:id` | PUT | 更新报表 | 是 |
|
||||
| `/api/reports/:id` | DELETE | 删除报表 | 是 |
|
||||
| `/api/reports/export` | POST | 导出报表 | 是 |
|
||||
|
||||
## 通用参数说明
|
||||
|
||||
### 分页参数
|
||||
- `page`: 页码,从1开始,默认1
|
||||
- `pageSize`: 每页数量,默认10,最大100
|
||||
|
||||
### 搜索参数
|
||||
- `searchField`: 搜索字段
|
||||
- `searchValue`: 搜索值
|
||||
- `startDate`: 开始日期 (YYYY-MM-DD)
|
||||
- `endDate`: 结束日期 (YYYY-MM-DD)
|
||||
|
||||
### 排序参数
|
||||
- `sortField`: 排序字段
|
||||
- `sortOrder`: 排序方向 (asc/desc)
|
||||
|
||||
### 筛选参数
|
||||
- `status`: 状态筛选
|
||||
- `type`: 类型筛选
|
||||
- `category`: 分类筛选
|
||||
|
||||
## 响应格式
|
||||
|
||||
### 成功响应
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "操作成功",
|
||||
"data": {},
|
||||
"timestamp": "2025-09-26T10:30:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
### 错误响应
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "错误信息",
|
||||
"error": "ERROR_CODE",
|
||||
"timestamp": "2025-09-26T10:30:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
### 分页响应
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"items": [],
|
||||
"pagination": {
|
||||
"current": 1,
|
||||
"pageSize": 10,
|
||||
"total": 100,
|
||||
"totalPages": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 认证说明
|
||||
|
||||
所有需要认证的接口都需要在请求头中携带JWT令牌:
|
||||
|
||||
```bash
|
||||
Authorization: Bearer <your-jwt-token>
|
||||
```
|
||||
|
||||
## 错误码说明
|
||||
|
||||
| 错误码 | 说明 |
|
||||
|--------|------|
|
||||
| 400 | 请求参数错误 |
|
||||
| 401 | 未授权访问 |
|
||||
| 403 | 禁止访问 |
|
||||
| 404 | 资源不存在 |
|
||||
| 409 | 资源冲突 |
|
||||
| 422 | 数据验证失败 |
|
||||
| 500 | 服务器内部错误 |
|
||||
|
||||
## 限流说明
|
||||
|
||||
- 普通接口: 100次/分钟
|
||||
- 登录接口: 10次/分钟
|
||||
- 文件上传: 20次/分钟
|
||||
- 数据导出: 5次/分钟
|
||||
|
||||
---
|
||||
|
||||
**文档版本**: v1.1.0
|
||||
**最后更新**: 2025-09-26
|
||||
**维护人员**: 开发团队
|
||||
476
bank-backend/docs/LOAN_RELEASE_API.md
Normal file
476
bank-backend/docs/LOAN_RELEASE_API.md
Normal file
@@ -0,0 +1,476 @@
|
||||
# 贷款解押模块API接口文档
|
||||
|
||||
## 概述
|
||||
|
||||
本文档详细描述了银行管理系统贷款解押模块的所有API接口,包括接口地址、请求方法、参数说明、响应格式等信息。
|
||||
|
||||
## 基础信息
|
||||
|
||||
- **基础URL**: `http://localhost:5351`
|
||||
- **模块路径**: `/api/loan-releases`
|
||||
- **认证方式**: JWT Token
|
||||
- **数据格式**: JSON
|
||||
- **字符编码**: UTF-8
|
||||
|
||||
## 通用响应格式
|
||||
|
||||
### 成功响应
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "操作成功",
|
||||
"data": {},
|
||||
"timestamp": "2025-09-26T10:30:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
### 错误响应
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "错误信息",
|
||||
"error": "ERROR_CODE",
|
||||
"timestamp": "2025-09-26T10:30:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
## 1. 获取解押申请列表
|
||||
|
||||
- **接口地址**: `GET /api/loan-releases`
|
||||
- **功能描述**: 获取贷款解押申请列表,支持分页和搜索
|
||||
- **认证要求**: 需要JWT Token
|
||||
- **请求参数**:
|
||||
- `page` (integer, optional): 页码,默认1
|
||||
- `pageSize` (integer, optional): 每页数量,默认10,最大100
|
||||
- `searchField` (string, optional): 搜索字段,可选值:
|
||||
- `application_number`: 申请单号
|
||||
- `customer_name`: 客户姓名
|
||||
- `product_name`: 产品名称
|
||||
- `searchValue` (string, optional): 搜索值
|
||||
- `status` (string, optional): 解押状态筛选,可选值:
|
||||
- `pending`: 待处理
|
||||
- `processing`: 处理中
|
||||
- `approved`: 已通过
|
||||
- `rejected`: 已拒绝
|
||||
- `completed`: 已完成
|
||||
- `released`: 已解押
|
||||
- `sortField` (string, optional): 排序字段,默认created_at
|
||||
- `sortOrder` (string, optional): 排序方向,可选值:asc, desc,默认desc
|
||||
|
||||
- **请求示例**:
|
||||
```bash
|
||||
GET /api/loan-releases?page=1&pageSize=10&searchField=application_number&searchValue=20240227&status=pending
|
||||
```
|
||||
|
||||
- **响应数据**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"releases": [
|
||||
{
|
||||
"id": 1,
|
||||
"applicationNumber": "20240227145555918",
|
||||
"productName": "中国工商银行扎旗支行\"畜禽活体抵押\"",
|
||||
"farmerName": "刘超",
|
||||
"applicantName": "刘超",
|
||||
"applicantIdNumber": "511123199001013017",
|
||||
"applicantPhone": "13800138000",
|
||||
"assetType": "牛",
|
||||
"releaseQuantity": "10头",
|
||||
"releaseAmount": 10000,
|
||||
"status": "released",
|
||||
"applicationTime": "2024-02-27T06:55:55.000Z",
|
||||
"processTime": "2024-02-27T07:30:00.000Z",
|
||||
"completeTime": "2024-02-27T08:00:00.000Z",
|
||||
"processorName": "系统管理员",
|
||||
"processComment": "审核通过,同意解押",
|
||||
"rejectionReason": null,
|
||||
"remark": "客户信用良好",
|
||||
"created_at": "2025-09-26T03:38:50.000Z",
|
||||
"updated_at": "2025-09-26T03:38:50.000Z"
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"current": 1,
|
||||
"pageSize": 10,
|
||||
"total": 6,
|
||||
"totalPages": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 获取解押申请详情
|
||||
|
||||
- **接口地址**: `GET /api/loan-releases/:id`
|
||||
- **功能描述**: 获取指定解押申请的详细信息
|
||||
- **认证要求**: 需要JWT Token
|
||||
- **路径参数**:
|
||||
- `id` (integer, required): 解押申请ID
|
||||
|
||||
- **请求示例**:
|
||||
```bash
|
||||
GET /api/loan-releases/1
|
||||
```
|
||||
|
||||
- **响应数据**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": 1,
|
||||
"releaseNumber": "20240227145555918",
|
||||
"customerName": "刘超",
|
||||
"contractNumber": "CONTRACT-000007",
|
||||
"status": "released",
|
||||
"collateralType": "livestock",
|
||||
"collateralDescription": "优质肉牛",
|
||||
"loanAmount": 100000,
|
||||
"collateralValue": 150000,
|
||||
"applicationTime": "2024-02-27T06:55:55.000Z",
|
||||
"processTime": "2024-02-27T07:30:00.000Z",
|
||||
"completeTime": "2024-02-27T08:00:00.000Z",
|
||||
"phone": "13800138000",
|
||||
"idCard": "511123199001013017",
|
||||
"reason": "需要资金周转,申请部分解押",
|
||||
"remark": "客户信用良好",
|
||||
"history": [
|
||||
{
|
||||
"id": 1,
|
||||
"action": "apply",
|
||||
"operator": "刘超",
|
||||
"time": "2025-09-26T03:38:51.000Z",
|
||||
"comment": "提交解押申请"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"action": "complete",
|
||||
"operator": "系统管理员",
|
||||
"time": "2025-09-26T03:38:51.000Z",
|
||||
"comment": "解押手续办理完成"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 创建解押申请
|
||||
|
||||
- **接口地址**: `POST /api/loan-releases`
|
||||
- **功能描述**: 创建新的解押申请
|
||||
- **认证要求**: 需要JWT Token
|
||||
- **请求参数**:
|
||||
```json
|
||||
{
|
||||
"contract_id": 1,
|
||||
"customer_name": "测试用户",
|
||||
"customer_phone": "13800138000",
|
||||
"customer_id_card": "511123199001010001",
|
||||
"farmer_name": "测试用户",
|
||||
"product_name": "测试产品",
|
||||
"collateral_type": "livestock",
|
||||
"collateral_description": "测试抵押物",
|
||||
"collateral_value": 100000,
|
||||
"loan_amount": 80000,
|
||||
"release_amount": 50000,
|
||||
"release_quantity": "5头",
|
||||
"application_reason": "测试解押申请",
|
||||
"remark": "测试备注"
|
||||
}
|
||||
```
|
||||
|
||||
- **字段说明**:
|
||||
- `contract_id` (integer, optional): 关联合同ID
|
||||
- `customer_name` (string, required): 客户姓名
|
||||
- `customer_phone` (string, required): 客户电话
|
||||
- `customer_id_card` (string, required): 客户身份证号
|
||||
- `farmer_name` (string, optional): 养殖户姓名
|
||||
- `product_name` (string, optional): 贷款产品名称
|
||||
- `collateral_type` (string, required): 抵押物类型,可选值:
|
||||
- `house`: 房产
|
||||
- `car`: 车辆
|
||||
- `land`: 土地
|
||||
- `equipment`: 设备
|
||||
- `livestock`: 牲畜
|
||||
- `collateral_description` (string, optional): 抵押物描述
|
||||
- `collateral_value` (number, optional): 抵押物价值
|
||||
- `loan_amount` (number, optional): 贷款金额
|
||||
- `release_amount` (number, required): 解押金额
|
||||
- `release_quantity` (string, optional): 解押数量
|
||||
- `application_reason` (string, optional): 申请原因
|
||||
- `remark` (string, optional): 备注
|
||||
|
||||
- **请求示例**:
|
||||
```bash
|
||||
POST /api/loan-releases
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer <your-jwt-token>
|
||||
|
||||
{
|
||||
"customer_name": "测试用户",
|
||||
"customer_phone": "13800138000",
|
||||
"customer_id_card": "511123199001010001",
|
||||
"collateral_type": "livestock",
|
||||
"release_amount": 50000,
|
||||
"release_quantity": "5头",
|
||||
"application_reason": "测试解押申请"
|
||||
}
|
||||
```
|
||||
|
||||
- **响应数据**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "解押申请创建成功",
|
||||
"data": {
|
||||
"id": 13,
|
||||
"applicationNumber": "REL-1758868323652"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 更新解押申请
|
||||
|
||||
- **接口地址**: `PUT /api/loan-releases/:id`
|
||||
- **功能描述**: 更新指定解押申请信息
|
||||
- **认证要求**: 需要JWT Token
|
||||
- **路径参数**:
|
||||
- `id` (integer, required): 解押申请ID
|
||||
- **请求参数**: 同创建解押申请参数
|
||||
- **限制条件**: 只有待处理状态的申请才能更新
|
||||
|
||||
- **请求示例**:
|
||||
```bash
|
||||
PUT /api/loan-releases/13
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer <your-jwt-token>
|
||||
|
||||
{
|
||||
"release_amount": 60000,
|
||||
"application_reason": "更新后的申请原因"
|
||||
}
|
||||
```
|
||||
|
||||
- **响应数据**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "解押申请更新成功"
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 处理解押申请
|
||||
|
||||
- **接口地址**: `POST /api/loan-releases/:id/process`
|
||||
- **功能描述**: 处理解押申请(通过/拒绝)
|
||||
- **认证要求**: 需要JWT Token
|
||||
- **路径参数**:
|
||||
- `id` (integer, required): 解押申请ID
|
||||
- **请求参数**:
|
||||
```json
|
||||
{
|
||||
"action": "approve",
|
||||
"comment": "审核通过",
|
||||
"remark": "测试处理"
|
||||
}
|
||||
```
|
||||
|
||||
- **字段说明**:
|
||||
- `action` (string, required): 处理动作,可选值:
|
||||
- `approve`: 通过
|
||||
- `reject`: 拒绝
|
||||
- `comment` (string, required): 处理意见
|
||||
- `remark` (string, optional): 备注
|
||||
|
||||
- **请求示例**:
|
||||
```bash
|
||||
POST /api/loan-releases/13/process
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer <your-jwt-token>
|
||||
|
||||
{
|
||||
"action": "approve",
|
||||
"comment": "审核通过,同意解押",
|
||||
"remark": "客户信用良好"
|
||||
}
|
||||
```
|
||||
|
||||
- **响应数据**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "解押申请已通过",
|
||||
"data": {
|
||||
"id": 13,
|
||||
"status": "approved",
|
||||
"action": "approve",
|
||||
"comment": "审核通过"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 6. 完成解押
|
||||
|
||||
- **接口地址**: `POST /api/loan-releases/:id/complete`
|
||||
- **功能描述**: 完成解押手续
|
||||
- **认证要求**: 需要JWT Token
|
||||
- **路径参数**:
|
||||
- `id` (integer, required): 解押申请ID
|
||||
- **请求参数**:
|
||||
```json
|
||||
{
|
||||
"comment": "解押手续办理完成"
|
||||
}
|
||||
```
|
||||
|
||||
- **字段说明**:
|
||||
- `comment` (string, required): 完成说明
|
||||
|
||||
- **请求示例**:
|
||||
```bash
|
||||
POST /api/loan-releases/13/complete
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer <your-jwt-token>
|
||||
|
||||
{
|
||||
"comment": "解押手续办理完成,抵押物已释放"
|
||||
}
|
||||
```
|
||||
|
||||
- **响应数据**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "解押完成",
|
||||
"data": {
|
||||
"id": 13,
|
||||
"status": "completed"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 7. 删除解押申请
|
||||
|
||||
- **接口地址**: `DELETE /api/loan-releases/:id`
|
||||
- **功能描述**: 删除指定解押申请(仅限待处理状态)
|
||||
- **认证要求**: 需要JWT Token
|
||||
- **路径参数**:
|
||||
- `id` (integer, required): 解押申请ID
|
||||
- **限制条件**: 只有待处理状态的申请才能删除
|
||||
|
||||
- **请求示例**:
|
||||
```bash
|
||||
DELETE /api/loan-releases/13
|
||||
Authorization: Bearer <your-jwt-token>
|
||||
```
|
||||
|
||||
- **响应数据**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "解押申请删除成功"
|
||||
}
|
||||
```
|
||||
|
||||
## 8. 获取解押统计信息
|
||||
|
||||
- **接口地址**: `GET /api/loan-releases/stats`
|
||||
- **功能描述**: 获取解押申请统计信息
|
||||
- **认证要求**: 需要JWT Token
|
||||
|
||||
- **请求示例**:
|
||||
```bash
|
||||
GET /api/loan-releases/stats
|
||||
Authorization: Bearer <your-jwt-token>
|
||||
```
|
||||
|
||||
- **响应数据**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"total": 6,
|
||||
"pending": 0,
|
||||
"processing": 0,
|
||||
"approved": 0,
|
||||
"rejected": 0,
|
||||
"completed": 0,
|
||||
"released": 6,
|
||||
"statusStats": [
|
||||
{
|
||||
"status": "released",
|
||||
"count": 6
|
||||
}
|
||||
],
|
||||
"amountStats": {
|
||||
"totalReleaseAmount": 1500000,
|
||||
"averageReleaseAmount": 250000
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 状态说明
|
||||
|
||||
### 解押申请状态流转
|
||||
|
||||
1. **pending** (待处理) - 初始状态,申请已提交
|
||||
2. **processing** (处理中) - 正在审核中
|
||||
3. **approved** (已通过) - 审核通过
|
||||
4. **rejected** (已拒绝) - 审核拒绝
|
||||
5. **completed** (已完成) - 解押手续完成
|
||||
6. **released** (已解押) - 抵押物已释放
|
||||
|
||||
### 状态流转规则
|
||||
|
||||
- `pending` → `processing` (开始处理)
|
||||
- `processing` → `approved` (审核通过)
|
||||
- `processing` → `rejected` (审核拒绝)
|
||||
- `approved` → `completed` (完成解押)
|
||||
- `completed` → `released` (释放抵押物)
|
||||
|
||||
## 错误码说明
|
||||
|
||||
| 错误码 | 说明 |
|
||||
|--------|------|
|
||||
| 400 | 请求参数错误 |
|
||||
| 401 | 未授权访问 |
|
||||
| 403 | 禁止访问 |
|
||||
| 404 | 解押申请不存在 |
|
||||
| 409 | 状态不允许此操作 |
|
||||
| 422 | 数据验证失败 |
|
||||
| 500 | 服务器内部错误 |
|
||||
|
||||
## 数据验证规则
|
||||
|
||||
### 必填字段验证
|
||||
- `customer_name`: 客户姓名不能为空
|
||||
- `customer_phone`: 客户电话不能为空,格式验证
|
||||
- `customer_id_card`: 客户身份证号不能为空,格式验证
|
||||
- `collateral_type`: 抵押物类型不能为空
|
||||
- `release_amount`: 解押金额不能为空,必须大于0
|
||||
|
||||
### 格式验证
|
||||
- `customer_phone`: 手机号格式验证
|
||||
- `customer_id_card`: 身份证号格式验证
|
||||
- `release_amount`: 金额格式验证,保留2位小数
|
||||
|
||||
### 业务规则验证
|
||||
- 解押金额不能超过贷款金额
|
||||
- 只有待处理状态的申请才能更新或删除
|
||||
- 只有已通过状态的申请才能完成解押
|
||||
|
||||
## 权限说明
|
||||
|
||||
- **查看权限**: 所有认证用户
|
||||
- **创建权限**: 所有认证用户
|
||||
- **更新权限**: 申请创建者或管理员
|
||||
- **处理权限**: 贷款专员或管理员
|
||||
- **删除权限**: 申请创建者或管理员
|
||||
|
||||
---
|
||||
|
||||
**文档版本**: v1.0.0
|
||||
**最后更新**: 2025-09-26
|
||||
**维护人员**: 开发团队
|
||||
@@ -1,82 +0,0 @@
|
||||
const { User } = require('./models');
|
||||
const { sequelize } = require('./config/database');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
async function fixPasswordFinal() {
|
||||
try {
|
||||
console.log('=== 最终修复密码问题 ===\n');
|
||||
|
||||
const testPassword = 'Admin123456';
|
||||
console.log('目标密码:', testPassword);
|
||||
|
||||
// 1. 生成新的密码哈希
|
||||
const newHash = await bcrypt.hash(testPassword, 10);
|
||||
console.log('1. 新生成的哈希:', newHash);
|
||||
console.log('哈希长度:', newHash.length);
|
||||
|
||||
// 2. 验证新生成的哈希
|
||||
const hashValid = await bcrypt.compare(testPassword, newHash);
|
||||
console.log('2. 新哈希验证:', hashValid);
|
||||
|
||||
if (!hashValid) {
|
||||
console.log('❌ 生成的哈希无效,退出');
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 直接使用SQL更新密码
|
||||
console.log('3. 直接使用SQL更新密码...');
|
||||
const [affectedRows] = await sequelize.query(
|
||||
'UPDATE bank_users SET password = ?, status = ?, login_attempts = 0, locked_until = NULL WHERE username = ?',
|
||||
{
|
||||
replacements: [newHash, 'active', 'admin'],
|
||||
type: sequelize.QueryTypes.UPDATE
|
||||
}
|
||||
);
|
||||
|
||||
console.log('SQL更新影响行数:', affectedRows);
|
||||
|
||||
// 4. 验证数据库更新
|
||||
console.log('4. 验证数据库更新...');
|
||||
const [results] = await sequelize.query(
|
||||
'SELECT username, password, status, login_attempts FROM bank_users WHERE username = ?',
|
||||
{
|
||||
replacements: ['admin'],
|
||||
type: sequelize.QueryTypes.SELECT
|
||||
}
|
||||
);
|
||||
|
||||
if (results) {
|
||||
console.log('数据库中的数据:');
|
||||
console.log('用户名:', results.username);
|
||||
console.log('状态:', results.status);
|
||||
console.log('登录尝试次数:', results.login_attempts);
|
||||
console.log('密码哈希:', results.password);
|
||||
console.log('哈希长度:', results.password.length);
|
||||
|
||||
// 5. 验证更新后的密码
|
||||
const dbValid = await bcrypt.compare(testPassword, results.password);
|
||||
console.log('5. 数据库密码验证:', dbValid);
|
||||
|
||||
if (dbValid) {
|
||||
console.log('🎉 密码修复成功!');
|
||||
console.log('\n=== 登录信息 ===');
|
||||
console.log('用户名: admin');
|
||||
console.log('密码: Admin123456');
|
||||
console.log('状态: active');
|
||||
console.log('现在可以尝试登录了!');
|
||||
} else {
|
||||
console.log('❌ 密码验证仍然失败');
|
||||
}
|
||||
} else {
|
||||
console.log('❌ 查询数据库失败');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('修复失败:', error.message);
|
||||
console.error('错误堆栈:', error.stack);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
fixPasswordFinal();
|
||||
@@ -1,52 +0,0 @@
|
||||
const { sequelize } = require('./config/database');
|
||||
|
||||
async function fixSupervisionTasks() {
|
||||
try {
|
||||
console.log('开始修复supervision_tasks表...');
|
||||
|
||||
// 检查并添加created_by字段
|
||||
const createdByExists = await sequelize.query(
|
||||
'SHOW COLUMNS FROM supervision_tasks LIKE "created_by"',
|
||||
{ type: sequelize.QueryTypes.SELECT }
|
||||
);
|
||||
|
||||
if (createdByExists.length === 0) {
|
||||
await sequelize.query(
|
||||
'ALTER TABLE supervision_tasks ADD COLUMN created_by INT(11) AFTER remarks',
|
||||
{ type: sequelize.QueryTypes.RAW }
|
||||
);
|
||||
console.log('✅ 添加created_by字段成功');
|
||||
} else {
|
||||
console.log('✅ created_by字段已存在');
|
||||
}
|
||||
|
||||
// 检查并添加updated_by字段
|
||||
const updatedByExists = await sequelize.query(
|
||||
'SHOW COLUMNS FROM supervision_tasks LIKE "updated_by"',
|
||||
{ type: sequelize.QueryTypes.SELECT }
|
||||
);
|
||||
|
||||
if (updatedByExists.length === 0) {
|
||||
await sequelize.query(
|
||||
'ALTER TABLE supervision_tasks ADD COLUMN updated_by INT(11) AFTER created_by',
|
||||
{ type: sequelize.QueryTypes.RAW }
|
||||
);
|
||||
console.log('✅ 添加updated_by字段成功');
|
||||
} else {
|
||||
console.log('✅ updated_by字段已存在');
|
||||
}
|
||||
|
||||
// 显示表结构
|
||||
const columns = await sequelize.query('DESCRIBE supervision_tasks', { type: sequelize.QueryTypes.SELECT });
|
||||
console.log('\nsupervision_tasks表结构:');
|
||||
columns.forEach(col => console.log('-', col.Field, col.Type));
|
||||
|
||||
console.log('\n✅ supervision_tasks表修复完成!');
|
||||
} catch (error) {
|
||||
console.error('❌ 修复失败:', error.message);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
fixSupervisionTasks();
|
||||
@@ -1,75 +0,0 @@
|
||||
const { sequelize } = require('./config/database');
|
||||
|
||||
async function fixTestData() {
|
||||
try {
|
||||
console.log('开始修复测试数据...');
|
||||
|
||||
// 添加installation_tasks测试数据
|
||||
await sequelize.query(`
|
||||
INSERT INTO installation_tasks (
|
||||
applicationNumber, contractNumber, productName, customerName, idType, idNumber,
|
||||
assetType, equipmentToInstall, installationStatus, taskGenerationTime, completionTime,
|
||||
installationNotes, installerName, installerPhone, installationAddress,
|
||||
createdBy, updatedBy, createdAt, updatedAt
|
||||
) VALUES
|
||||
('APP001', 'CON001', '养殖设备', '张三', 'ID_CARD', '110101199001010001',
|
||||
'cattle', '耳标设备', 'pending', NOW(), NULL,
|
||||
'测试安装任务', '安装员1', '13900139000', '北京市朝阳区农场',
|
||||
2, 2, NOW(), NOW()),
|
||||
('APP002', 'CON002', '种植设备', '李四', 'ID_CARD', '110101199002020002',
|
||||
'sheep', '项圈设备', 'completed', NOW(), NOW(),
|
||||
'测试安装任务2', '安装员2', '14000140000', '北京市海淀区农场',
|
||||
2, 2, NOW(), NOW())
|
||||
`, { type: sequelize.QueryTypes.RAW });
|
||||
console.log('✅ 添加installation_tasks测试数据成功');
|
||||
|
||||
// 添加completed_supervisions测试数据
|
||||
await sequelize.query(`
|
||||
INSERT INTO completed_supervisions (
|
||||
applicationNumber, contractNumber, productName, customerName, idType, idNumber,
|
||||
assetType, assetQuantity, totalRepaymentPeriods, settlementStatus, settlementDate,
|
||||
importTime, settlementAmount, remainingAmount, settlementNotes,
|
||||
createdBy, updatedBy, createdAt, updatedAt
|
||||
) VALUES
|
||||
('APP001', 'CON001', '养殖贷款', '张三', 'ID_CARD', '110101199001010001',
|
||||
'cattle', 10, 12, 'partial', '2024-01-15',
|
||||
NOW(), 30000.00, 20000.00, '已结清部分',
|
||||
2, 2, NOW(), NOW()),
|
||||
('APP002', 'CON002', '种植贷款', '李四', 'ID_CARD', '110101199002020002',
|
||||
'sheep', 20, 10, 'settled', '2024-01-20',
|
||||
NOW(), 30000.00, 0.00, '已完全结清',
|
||||
2, 2, NOW(), NOW())
|
||||
`, { type: sequelize.QueryTypes.RAW });
|
||||
console.log('✅ 添加completed_supervisions测试数据成功');
|
||||
|
||||
// 添加loan_applications测试数据
|
||||
await sequelize.query(`
|
||||
INSERT INTO loan_applications (
|
||||
customer_name, customer_phone, customer_id_card, loan_amount, loan_term,
|
||||
interest_rate, application_date, status
|
||||
) VALUES
|
||||
('张三', '13800138000', '110101199001010001', 50000.00, 12, 0.05, '2024-01-01', 'pending'),
|
||||
('李四', '13900139000', '110101199002020002', 30000.00, 10, 0.06, '2024-01-02', 'approved')
|
||||
`, { type: sequelize.QueryTypes.RAW });
|
||||
console.log('✅ 添加loan_applications测试数据成功');
|
||||
|
||||
// 添加loan_contracts测试数据
|
||||
await sequelize.query(`
|
||||
INSERT INTO loan_contracts (
|
||||
contract_number, customer_name, customer_phone, customer_id_card, loan_amount,
|
||||
loan_term, interest_rate, contract_date, status
|
||||
) VALUES
|
||||
('CON001', '张三', '13800138000', '110101199001010001', 50000.00, 12, 0.05, '2024-01-01', 'active'),
|
||||
('CON002', '李四', '13900139000', '110101199002020002', 30000.00, 10, 0.06, '2024-01-02', 'completed')
|
||||
`, { type: sequelize.QueryTypes.RAW });
|
||||
console.log('✅ 添加loan_contracts测试数据成功');
|
||||
|
||||
console.log('\n✅ 所有测试数据修复完成!');
|
||||
} catch (error) {
|
||||
console.error('❌ 修复测试数据失败:', error.message);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
fixTestData();
|
||||
201
bank-backend/models/LoanRelease.js
Normal file
201
bank-backend/models/LoanRelease.js
Normal file
@@ -0,0 +1,201 @@
|
||||
/**
|
||||
* 贷款解押模型
|
||||
* @file LoanRelease.js
|
||||
* @description 银行系统贷款解押数据模型
|
||||
*/
|
||||
const { DataTypes } = require('sequelize');
|
||||
const BaseModel = require('./BaseModel');
|
||||
|
||||
class LoanRelease extends BaseModel {
|
||||
/**
|
||||
* 获取解押状态文本
|
||||
* @returns {String} 状态文本
|
||||
*/
|
||||
getStatusText() {
|
||||
const statusMap = {
|
||||
pending: '待处理',
|
||||
processing: '处理中',
|
||||
approved: '已通过',
|
||||
rejected: '已拒绝',
|
||||
completed: '已完成',
|
||||
released: '已解押'
|
||||
};
|
||||
return statusMap[this.status] || this.status;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取解押状态颜色
|
||||
* @returns {String} 颜色
|
||||
*/
|
||||
getStatusColor() {
|
||||
const colorMap = {
|
||||
pending: 'orange',
|
||||
processing: 'blue',
|
||||
approved: 'green',
|
||||
rejected: 'red',
|
||||
completed: 'green',
|
||||
released: 'default'
|
||||
};
|
||||
return colorMap[this.status] || 'default';
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化解押金额
|
||||
* @returns {String} 格式化后的金额
|
||||
*/
|
||||
getFormattedAmount() {
|
||||
return `${this.release_amount.toFixed(2)}元`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取抵押物类型文本
|
||||
* @returns {String} 类型文本
|
||||
*/
|
||||
getCollateralTypeText() {
|
||||
const typeMap = {
|
||||
house: '房产',
|
||||
car: '车辆',
|
||||
land: '土地',
|
||||
equipment: '设备',
|
||||
livestock: '牲畜'
|
||||
};
|
||||
return typeMap[this.collateral_type] || this.collateral_type;
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化LoanRelease模型
|
||||
LoanRelease.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
application_number: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '解押申请单号'
|
||||
},
|
||||
contract_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '关联合同ID'
|
||||
},
|
||||
customer_name: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '客户姓名'
|
||||
},
|
||||
customer_phone: {
|
||||
type: DataTypes.STRING(20),
|
||||
allowNull: false,
|
||||
comment: '客户电话'
|
||||
},
|
||||
customer_id_card: {
|
||||
type: DataTypes.STRING(18),
|
||||
allowNull: false,
|
||||
comment: '客户身份证号'
|
||||
},
|
||||
farmer_name: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
comment: '养殖户姓名'
|
||||
},
|
||||
product_name: {
|
||||
type: DataTypes.STRING(200),
|
||||
allowNull: true,
|
||||
comment: '贷款产品名称'
|
||||
},
|
||||
collateral_type: {
|
||||
type: DataTypes.ENUM('house', 'car', 'land', 'equipment', 'livestock'),
|
||||
allowNull: false,
|
||||
comment: '抵押物类型'
|
||||
},
|
||||
collateral_description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '抵押物描述'
|
||||
},
|
||||
collateral_value: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
comment: '抵押物价值'
|
||||
},
|
||||
loan_amount: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
comment: '贷款金额'
|
||||
},
|
||||
release_amount: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: false,
|
||||
comment: '解押金额'
|
||||
},
|
||||
release_quantity: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
comment: '解押数量'
|
||||
},
|
||||
application_reason: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '申请原因'
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('pending', 'processing', 'approved', 'rejected', 'completed', 'released'),
|
||||
allowNull: false,
|
||||
defaultValue: 'pending',
|
||||
comment: '解押状态'
|
||||
},
|
||||
application_date: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW,
|
||||
comment: '申请日期'
|
||||
},
|
||||
process_date: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
comment: '处理日期'
|
||||
},
|
||||
complete_date: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
comment: '完成日期'
|
||||
},
|
||||
processor_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '处理人ID'
|
||||
},
|
||||
processor_name: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
comment: '处理人姓名'
|
||||
},
|
||||
process_comment: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '处理意见'
|
||||
},
|
||||
rejection_reason: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '拒绝原因'
|
||||
},
|
||||
remark: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '备注'
|
||||
}
|
||||
}, {
|
||||
sequelize: require('../config/database').sequelize,
|
||||
modelName: 'LoanRelease',
|
||||
tableName: 'bank_loan_releases',
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at'
|
||||
});
|
||||
|
||||
module.exports = LoanRelease;
|
||||
108
bank-backend/models/LoanReleaseHistory.js
Normal file
108
bank-backend/models/LoanReleaseHistory.js
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* 贷款解押历史记录模型
|
||||
* @file LoanReleaseHistory.js
|
||||
* @description 银行系统贷款解押历史记录数据模型
|
||||
*/
|
||||
const { DataTypes } = require('sequelize');
|
||||
const BaseModel = require('./BaseModel');
|
||||
|
||||
class LoanReleaseHistory extends BaseModel {
|
||||
/**
|
||||
* 获取操作动作文本
|
||||
* @returns {String} 动作文本
|
||||
*/
|
||||
getActionText() {
|
||||
const actionMap = {
|
||||
apply: '提交申请',
|
||||
process: '开始处理',
|
||||
approve: '审核通过',
|
||||
reject: '审核拒绝',
|
||||
complete: '处理完成',
|
||||
release: '解押完成'
|
||||
};
|
||||
return actionMap[this.action] || this.action;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取操作动作颜色
|
||||
* @returns {String} 颜色
|
||||
*/
|
||||
getActionColor() {
|
||||
const colorMap = {
|
||||
apply: 'blue',
|
||||
process: 'orange',
|
||||
approve: 'green',
|
||||
reject: 'red',
|
||||
complete: 'green',
|
||||
release: 'default'
|
||||
};
|
||||
return colorMap[this.action] || 'default';
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化LoanReleaseHistory模型
|
||||
LoanReleaseHistory.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
release_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '解押申请ID'
|
||||
},
|
||||
action: {
|
||||
type: DataTypes.ENUM(
|
||||
'apply',
|
||||
'process',
|
||||
'approve',
|
||||
'reject',
|
||||
'complete',
|
||||
'release'
|
||||
),
|
||||
allowNull: false,
|
||||
comment: '操作动作'
|
||||
},
|
||||
operator: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '操作人'
|
||||
},
|
||||
operator_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '操作人ID'
|
||||
},
|
||||
comment: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '操作备注'
|
||||
},
|
||||
previous_status: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
comment: '操作前状态'
|
||||
},
|
||||
new_status: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
comment: '操作后状态'
|
||||
},
|
||||
operation_time: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW,
|
||||
comment: '操作时间'
|
||||
}
|
||||
}, {
|
||||
sequelize: require('../config/database').sequelize,
|
||||
modelName: 'LoanReleaseHistory',
|
||||
tableName: 'bank_loan_release_histories',
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at'
|
||||
});
|
||||
|
||||
module.exports = LoanReleaseHistory;
|
||||
@@ -16,6 +16,9 @@ class User extends BaseModel {
|
||||
*/
|
||||
async validPassword(password) {
|
||||
try {
|
||||
if (!password || !this.password) {
|
||||
return false;
|
||||
}
|
||||
const bcrypt = require('bcryptjs');
|
||||
return await bcrypt.compare(password, this.password);
|
||||
} catch (error) {
|
||||
@@ -178,12 +181,12 @@ User.init({
|
||||
updatedAt: 'updated_at',
|
||||
hooks: {
|
||||
beforeCreate: async (user) => {
|
||||
if (user.password) {
|
||||
if (user.password && !user.password.startsWith('$2a$')) {
|
||||
user.password = await bcrypt.hash(user.password, 10);
|
||||
}
|
||||
},
|
||||
beforeUpdate: async (user) => {
|
||||
if (user.changed('password')) {
|
||||
if (user.changed('password') && user.password && !user.password.startsWith('$2a$')) {
|
||||
user.password = await bcrypt.hash(user.password, 10);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ const CompletedSupervision = require('./CompletedSupervision');
|
||||
const LoanApplication = require('./LoanApplication');
|
||||
const AuditRecord = require('./AuditRecord');
|
||||
const LoanContract = require('./LoanContract');
|
||||
const LoanRelease = require('./LoanRelease');
|
||||
const LoanReleaseHistory = require('./LoanReleaseHistory');
|
||||
|
||||
// 定义模型关联关系
|
||||
|
||||
@@ -226,6 +228,54 @@ User.hasMany(AuditRecord, {
|
||||
// 贷款合同暂时不关联用户表,因为当前表结构中没有外键字段
|
||||
// 如果需要关联,需要先添加相应的外键字段到数据库表中
|
||||
|
||||
// 贷款解押与用户关联(处理人)
|
||||
LoanRelease.belongsTo(User, {
|
||||
foreignKey: 'processor_id',
|
||||
as: 'processor',
|
||||
targetKey: 'id'
|
||||
});
|
||||
|
||||
User.hasMany(LoanRelease, {
|
||||
foreignKey: 'processor_id',
|
||||
as: 'processedReleases'
|
||||
});
|
||||
|
||||
// 贷款解押与贷款合同关联
|
||||
LoanRelease.belongsTo(LoanContract, {
|
||||
foreignKey: 'contract_id',
|
||||
as: 'contract',
|
||||
targetKey: 'id'
|
||||
});
|
||||
|
||||
LoanContract.hasMany(LoanRelease, {
|
||||
foreignKey: 'contract_id',
|
||||
as: 'releases'
|
||||
});
|
||||
|
||||
// 贷款解押历史记录与解押申请关联
|
||||
LoanReleaseHistory.belongsTo(LoanRelease, {
|
||||
foreignKey: 'release_id',
|
||||
as: 'release',
|
||||
targetKey: 'id'
|
||||
});
|
||||
|
||||
LoanRelease.hasMany(LoanReleaseHistory, {
|
||||
foreignKey: 'release_id',
|
||||
as: 'histories'
|
||||
});
|
||||
|
||||
// 贷款解押历史记录与用户关联(操作人)
|
||||
LoanReleaseHistory.belongsTo(User, {
|
||||
foreignKey: 'operator_id',
|
||||
as: 'operatorUser',
|
||||
targetKey: 'id'
|
||||
});
|
||||
|
||||
User.hasMany(LoanReleaseHistory, {
|
||||
foreignKey: 'operator_id',
|
||||
as: 'releaseHistories'
|
||||
});
|
||||
|
||||
// 导出所有模型和数据库实例
|
||||
module.exports = {
|
||||
sequelize,
|
||||
@@ -244,5 +294,7 @@ module.exports = {
|
||||
CompletedSupervision,
|
||||
LoanApplication,
|
||||
AuditRecord,
|
||||
LoanContract
|
||||
LoanContract,
|
||||
LoanRelease,
|
||||
LoanReleaseHistory
|
||||
};
|
||||
@@ -1,26 +0,0 @@
|
||||
const { sequelize, Project, User } = require('./models');
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
console.log('DB should be ningxia_bank test:');
|
||||
const [dbRow] = await sequelize.query('SELECT DATABASE() AS db');
|
||||
console.log('DB:', dbRow[0].db);
|
||||
|
||||
console.log('Testing Project.findAndCountAll with includes...');
|
||||
const result = await Project.findAndCountAll({
|
||||
include: [
|
||||
{ model: User, as: 'creator', attributes: ['id', 'username'] },
|
||||
{ model: User, as: 'updater', attributes: ['id', 'username'] }
|
||||
],
|
||||
limit: 5
|
||||
});
|
||||
console.log('count:', result.count);
|
||||
console.log('rows length:', result.rows.length);
|
||||
} catch (err) {
|
||||
console.error('ERROR:', err.message);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
417
bank-backend/routes/loanRelease.js
Normal file
417
bank-backend/routes/loanRelease.js
Normal file
@@ -0,0 +1,417 @@
|
||||
/**
|
||||
* 贷款解押路由
|
||||
* @file loanRelease.js
|
||||
* @description 银行系统贷款解押相关路由
|
||||
*/
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { authMiddleware } = require('../middleware/auth');
|
||||
const {
|
||||
getReleases,
|
||||
getReleaseDetail,
|
||||
createRelease,
|
||||
updateRelease,
|
||||
processRelease,
|
||||
completeRelease,
|
||||
deleteRelease,
|
||||
getReleaseStats
|
||||
} = require('../controllers/loanReleaseController');
|
||||
|
||||
// 所有路由都需要认证
|
||||
router.use(authMiddleware);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/loan-releases:
|
||||
* get:
|
||||
* summary: 获取解押申请列表
|
||||
* tags: [Loan Release]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: page
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 1
|
||||
* description: 页码
|
||||
* - in: query
|
||||
* name: pageSize
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 10
|
||||
* description: 每页数量
|
||||
* - in: query
|
||||
* name: searchField
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [application_number, customer_name, product_name]
|
||||
* default: application_number
|
||||
* description: 搜索字段
|
||||
* - in: query
|
||||
* name: searchValue
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 搜索值
|
||||
* - in: query
|
||||
* name: status
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [pending, processing, approved, rejected, completed, released]
|
||||
* description: 解押状态筛选
|
||||
* - in: query
|
||||
* name: sortField
|
||||
* schema:
|
||||
* type: string
|
||||
* default: created_at
|
||||
* description: 排序字段
|
||||
* - in: query
|
||||
* name: sortOrder
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [asc, desc]
|
||||
* default: desc
|
||||
* description: 排序方向
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* releases:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/LoanRelease'
|
||||
* pagination:
|
||||
* $ref: '#/components/schemas/Pagination'
|
||||
* 401:
|
||||
* $ref: '#/components/responses/UnauthorizedError'
|
||||
* 500:
|
||||
* $ref: '#/components/responses/InternalServerError'
|
||||
*/
|
||||
router.get('/', getReleases);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/loan-releases/stats:
|
||||
* get:
|
||||
* summary: 获取解押统计信息
|
||||
* tags: [Loan Release]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* total:
|
||||
* type: integer
|
||||
* example: 6
|
||||
* pending:
|
||||
* type: integer
|
||||
* example: 0
|
||||
* processing:
|
||||
* type: integer
|
||||
* example: 0
|
||||
* approved:
|
||||
* type: integer
|
||||
* example: 0
|
||||
* rejected:
|
||||
* type: integer
|
||||
* example: 0
|
||||
* completed:
|
||||
* type: integer
|
||||
* example: 0
|
||||
* released:
|
||||
* type: integer
|
||||
* example: 6
|
||||
* 401:
|
||||
* $ref: '#/components/responses/UnauthorizedError'
|
||||
* 500:
|
||||
* $ref: '#/components/responses/InternalServerError'
|
||||
*/
|
||||
router.get('/stats', getReleaseStats);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/loan-releases/{id}:
|
||||
* get:
|
||||
* summary: 获取解押申请详情
|
||||
* tags: [Loan Release]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 解押申请ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* allOf:
|
||||
* - $ref: '#/components/schemas/LoanRelease'
|
||||
* - type: object
|
||||
* properties:
|
||||
* history:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/LoanReleaseHistory'
|
||||
* 401:
|
||||
* $ref: '#/components/responses/UnauthorizedError'
|
||||
* 404:
|
||||
* $ref: '#/components/responses/NotFoundError'
|
||||
* 500:
|
||||
* $ref: '#/components/responses/InternalServerError'
|
||||
*/
|
||||
router.get('/:id', getReleaseDetail);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/loan-releases:
|
||||
* post:
|
||||
* summary: 创建解押申请
|
||||
* tags: [Loan Release]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/LoanReleaseCreateRequest'
|
||||
* responses:
|
||||
* 201:
|
||||
* description: 创建成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* message:
|
||||
* type: string
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* applicationNumber:
|
||||
* type: string
|
||||
* 400:
|
||||
* $ref: '#/components/responses/ValidationError'
|
||||
* 401:
|
||||
* $ref: '#/components/responses/UnauthorizedError'
|
||||
* 500:
|
||||
* $ref: '#/components/responses/InternalServerError'
|
||||
*/
|
||||
router.post('/', createRelease);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/loan-releases/{id}:
|
||||
* put:
|
||||
* summary: 更新解押申请
|
||||
* tags: [Loan Release]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 解押申请ID
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/LoanReleaseCreateRequest'
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 更新成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Success'
|
||||
* 400:
|
||||
* $ref: '#/components/responses/ValidationError'
|
||||
* 401:
|
||||
* $ref: '#/components/responses/UnauthorizedError'
|
||||
* 404:
|
||||
* $ref: '#/components/responses/NotFoundError'
|
||||
* 500:
|
||||
* $ref: '#/components/responses/InternalServerError'
|
||||
*/
|
||||
router.put('/:id', updateRelease);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/loan-releases/{id}/process:
|
||||
* post:
|
||||
* summary: 处理解押申请(通过/拒绝)
|
||||
* tags: [Loan Release]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 解押申请ID
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/LoanReleaseProcessRequest'
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 处理成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* message:
|
||||
* type: string
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* status:
|
||||
* type: string
|
||||
* action:
|
||||
* type: string
|
||||
* comment:
|
||||
* type: string
|
||||
* 400:
|
||||
* $ref: '#/components/responses/ValidationError'
|
||||
* 401:
|
||||
* $ref: '#/components/responses/UnauthorizedError'
|
||||
* 404:
|
||||
* $ref: '#/components/responses/NotFoundError'
|
||||
* 500:
|
||||
* $ref: '#/components/responses/InternalServerError'
|
||||
*/
|
||||
router.post('/:id/process', processRelease);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/loan-releases/{id}/complete:
|
||||
* post:
|
||||
* summary: 完成解押
|
||||
* tags: [Loan Release]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 解押申请ID
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - comment
|
||||
* properties:
|
||||
* comment:
|
||||
* type: string
|
||||
* example: 解押手续办理完成
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 完成成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* message:
|
||||
* type: string
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* status:
|
||||
* type: string
|
||||
* 400:
|
||||
* $ref: '#/components/responses/ValidationError'
|
||||
* 401:
|
||||
* $ref: '#/components/responses/UnauthorizedError'
|
||||
* 404:
|
||||
* $ref: '#/components/responses/NotFoundError'
|
||||
* 500:
|
||||
* $ref: '#/components/responses/InternalServerError'
|
||||
*/
|
||||
router.post('/:id/complete', completeRelease);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/loan-releases/{id}:
|
||||
* delete:
|
||||
* summary: 删除解押申请
|
||||
* tags: [Loan Release]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 解押申请ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 删除成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Success'
|
||||
* 401:
|
||||
* $ref: '#/components/responses/UnauthorizedError'
|
||||
* 404:
|
||||
* $ref: '#/components/responses/NotFoundError'
|
||||
* 500:
|
||||
* $ref: '#/components/responses/InternalServerError'
|
||||
*/
|
||||
router.delete('/:id', deleteRelease);
|
||||
|
||||
module.exports = router;
|
||||
@@ -80,6 +80,7 @@ app.use('/api/installation-tasks', require('./routes/installationTasks'));
|
||||
app.use('/api/completed-supervisions', require('./routes/completedSupervisions'));
|
||||
app.use('/api/loan-applications', require('./routes/loanApplications'));
|
||||
app.use('/api/loan-contracts', require('./routes/loanContracts'));
|
||||
app.use('/api/loan-releases', require('./routes/loanRelease'));
|
||||
// app.use('/api/reports', require('./routes/reports'));
|
||||
|
||||
// 根路径
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
const { User } = require('./models');
|
||||
const { sequelize } = require('./config/database');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
async function testActualData() {
|
||||
try {
|
||||
console.log('=== 测试实际数据库数据 ===\n');
|
||||
|
||||
// 1. 直接查询数据库
|
||||
console.log('1. 直接查询数据库...');
|
||||
const [results] = await sequelize.query(
|
||||
'SELECT id, username, password, status FROM bank_users WHERE username = ?',
|
||||
{
|
||||
replacements: ['admin'],
|
||||
type: sequelize.QueryTypes.SELECT
|
||||
}
|
||||
);
|
||||
|
||||
if (!results) {
|
||||
console.log('❌ 数据库中未找到admin用户');
|
||||
console.log('查询结果:', results);
|
||||
return;
|
||||
}
|
||||
|
||||
const dbUser = results;
|
||||
console.log('数据库中的用户数据:');
|
||||
console.log('查询结果:', results);
|
||||
console.log('结果长度:', results.length);
|
||||
if (dbUser) {
|
||||
console.log('ID:', dbUser.id);
|
||||
console.log('用户名:', dbUser.username);
|
||||
console.log('状态:', dbUser.status);
|
||||
console.log('密码哈希:', dbUser.password);
|
||||
console.log('密码哈希长度:', dbUser.password ? dbUser.password.length : 0);
|
||||
}
|
||||
console.log('');
|
||||
|
||||
// 2. 使用Sequelize查询
|
||||
console.log('2. 使用Sequelize查询...');
|
||||
const sequelizeUser = await User.findOne({ where: { username: 'admin' } });
|
||||
if (sequelizeUser) {
|
||||
console.log('Sequelize查询到的用户数据:');
|
||||
console.log('ID:', sequelizeUser.id);
|
||||
console.log('用户名:', sequelizeUser.username);
|
||||
console.log('状态:', sequelizeUser.status);
|
||||
console.log('密码哈希:', sequelizeUser.password);
|
||||
console.log('密码哈希长度:', sequelizeUser.password ? sequelizeUser.password.length : 0);
|
||||
console.log('');
|
||||
|
||||
// 3. 比较两种查询结果
|
||||
console.log('3. 比较两种查询结果:');
|
||||
console.log('密码哈希是否相同:', dbUser.password === sequelizeUser.password);
|
||||
console.log('');
|
||||
|
||||
// 4. 测试密码验证
|
||||
const testPassword = 'Admin123456';
|
||||
console.log('4. 测试密码验证:');
|
||||
console.log('测试密码:', testPassword);
|
||||
|
||||
// 使用数据库查询的密码哈希
|
||||
const dbTest = await bcrypt.compare(testPassword, dbUser.password);
|
||||
console.log('数据库密码验证结果:', dbTest);
|
||||
|
||||
// 使用Sequelize查询的密码哈希
|
||||
const sequelizeTest = await bcrypt.compare(testPassword, sequelizeUser.password);
|
||||
console.log('Sequelize密码验证结果:', sequelizeTest);
|
||||
|
||||
// 使用User模型的validPassword方法
|
||||
const modelTest = await sequelizeUser.validPassword(testPassword);
|
||||
console.log('User模型验证结果:', modelTest);
|
||||
console.log('');
|
||||
|
||||
if (dbTest && sequelizeTest && modelTest) {
|
||||
console.log('🎉 所有验证都成功!');
|
||||
} else {
|
||||
console.log('❌ 验证失败');
|
||||
console.log('可能原因:');
|
||||
if (!dbTest) console.log('- 数据库中的密码哈希有问题');
|
||||
if (!sequelizeTest) console.log('- Sequelize查询的密码哈希有问题');
|
||||
if (!modelTest) console.log('- User模型的validPassword方法有问题');
|
||||
}
|
||||
} else {
|
||||
console.log('❌ Sequelize查询失败');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('测试失败:', error.message);
|
||||
console.error('错误堆栈:', error.stack);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
testActualData();
|
||||
@@ -1,291 +0,0 @@
|
||||
const http = require('http');
|
||||
|
||||
// 测试配置
|
||||
const BASE_URL = 'http://localhost:5351';
|
||||
let authToken = '';
|
||||
|
||||
// 辅助函数:发送HTTP请求
|
||||
function makeRequest(options, data = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = http.request(options, (res) => {
|
||||
let body = '';
|
||||
res.on('data', (chunk) => {
|
||||
body += chunk;
|
||||
});
|
||||
res.on('end', () => {
|
||||
try {
|
||||
const result = JSON.parse(body);
|
||||
resolve({ status: res.statusCode, data: result });
|
||||
} catch (error) {
|
||||
resolve({ status: res.statusCode, data: body });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (error) => {
|
||||
reject(error);
|
||||
});
|
||||
|
||||
if (data) {
|
||||
req.write(JSON.stringify(data));
|
||||
}
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
// 测试登录
|
||||
async function testLogin() {
|
||||
console.log('🔐 测试登录...');
|
||||
try {
|
||||
const options = {
|
||||
hostname: 'localhost',
|
||||
port: 5351,
|
||||
path: '/api/auth/login',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
const result = await makeRequest(options, {
|
||||
username: 'admin',
|
||||
password: 'Admin123456'
|
||||
});
|
||||
|
||||
if (result.status === 200 && result.data.success) {
|
||||
authToken = result.data.data.token;
|
||||
console.log('✅ 登录成功');
|
||||
return true;
|
||||
} else {
|
||||
console.log('❌ 登录失败:', result.data.message);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('❌ 登录请求失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试仪表盘统计
|
||||
async function testDashboardStats() {
|
||||
console.log('📊 测试仪表盘统计...');
|
||||
try {
|
||||
const options = {
|
||||
hostname: 'localhost',
|
||||
port: 5351,
|
||||
path: '/api/dashboard',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authToken}`
|
||||
}
|
||||
};
|
||||
|
||||
const result = await makeRequest(options);
|
||||
if (result.status === 200 && result.data.success) {
|
||||
console.log('✅ 仪表盘统计获取成功');
|
||||
console.log(' - 总用户数:', result.data.data.overview?.totalUsers || 0);
|
||||
console.log(' - 总账户数:', result.data.data.overview?.totalAccounts || 0);
|
||||
console.log(' - 今日交易数:', result.data.data.today?.transactionCount || 0);
|
||||
return true;
|
||||
} else {
|
||||
console.log('❌ 仪表盘统计获取失败:', result.data.message);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('❌ 仪表盘统计请求失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试用户列表
|
||||
async function testUsersList() {
|
||||
console.log('👥 测试用户列表...');
|
||||
try {
|
||||
const options = {
|
||||
hostname: 'localhost',
|
||||
port: 5351,
|
||||
path: '/api/users?page=1&pageSize=10',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authToken}`
|
||||
}
|
||||
};
|
||||
|
||||
const result = await makeRequest(options);
|
||||
if (result.status === 200 && result.data.success) {
|
||||
console.log('✅ 用户列表获取成功');
|
||||
console.log(' - 用户数量:', result.data.data.users?.length || 0);
|
||||
console.log(' - 分页信息:', result.data.data.pagination);
|
||||
return true;
|
||||
} else {
|
||||
console.log('❌ 用户列表获取失败:', result.data.message);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('❌ 用户列表请求失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试账户列表
|
||||
async function testAccountsList() {
|
||||
console.log('🏦 测试账户列表...');
|
||||
try {
|
||||
const options = {
|
||||
hostname: 'localhost',
|
||||
port: 5351,
|
||||
path: '/api/accounts?page=1&pageSize=10',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authToken}`
|
||||
}
|
||||
};
|
||||
|
||||
const result = await makeRequest(options);
|
||||
if (result.status === 200 && result.data.success) {
|
||||
console.log('✅ 账户列表获取成功');
|
||||
console.log(' - 账户数量:', result.data.data.accounts?.length || 0);
|
||||
console.log(' - 分页信息:', result.data.data.pagination);
|
||||
return true;
|
||||
} else {
|
||||
console.log('❌ 账户列表获取失败:', result.data.message);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('❌ 账户列表请求失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试交易记录列表
|
||||
async function testTransactionsList() {
|
||||
console.log('💳 测试交易记录列表...');
|
||||
try {
|
||||
const options = {
|
||||
hostname: 'localhost',
|
||||
port: 5351,
|
||||
path: '/api/transactions?page=1&pageSize=10',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authToken}`
|
||||
}
|
||||
};
|
||||
|
||||
const result = await makeRequest(options);
|
||||
if (result.status === 200 && result.data.success) {
|
||||
console.log('✅ 交易记录列表获取成功');
|
||||
console.log(' - 交易数量:', result.data.data.transactions?.length || 0);
|
||||
console.log(' - 分页信息:', result.data.data.pagination);
|
||||
return true;
|
||||
} else {
|
||||
console.log('❌ 交易记录列表获取失败:', result.data.message);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('❌ 交易记录列表请求失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试员工列表
|
||||
async function testEmployeesList() {
|
||||
console.log('👨💼 测试员工列表...');
|
||||
try {
|
||||
const options = {
|
||||
hostname: 'localhost',
|
||||
port: 5351,
|
||||
path: '/api/employees?page=1&pageSize=10',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authToken}`
|
||||
}
|
||||
};
|
||||
|
||||
const result = await makeRequest(options);
|
||||
if (result.status === 200 && result.data.success) {
|
||||
console.log('✅ 员工列表获取成功');
|
||||
console.log(' - 员工数量:', result.data.data.employees?.length || 0);
|
||||
console.log(' - 分页信息:', result.data.data.pagination);
|
||||
return true;
|
||||
} else {
|
||||
console.log('❌ 员工列表获取失败:', result.data.message);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('❌ 员工列表请求失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试贷款产品列表
|
||||
async function testLoanProductsList() {
|
||||
console.log('💰 测试贷款产品列表...');
|
||||
try {
|
||||
const options = {
|
||||
hostname: 'localhost',
|
||||
port: 5351,
|
||||
path: '/api/loan-products?page=1&pageSize=10',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authToken}`
|
||||
}
|
||||
};
|
||||
|
||||
const result = await makeRequest(options);
|
||||
if (result.status === 200 && result.data.success) {
|
||||
console.log('✅ 贷款产品列表获取成功');
|
||||
console.log(' - 产品数量:', result.data.data.products?.length || 0);
|
||||
console.log(' - 分页信息:', result.data.data.pagination);
|
||||
return true;
|
||||
} else {
|
||||
console.log('❌ 贷款产品列表获取失败:', result.data.message);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('❌ 贷款产品列表请求失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 主测试函数
|
||||
async function runTests() {
|
||||
console.log('🚀 开始API集成测试...\n');
|
||||
|
||||
const tests = [
|
||||
{ name: '登录', fn: testLogin },
|
||||
{ name: '仪表盘统计', fn: testDashboardStats },
|
||||
{ name: '用户列表', fn: testUsersList },
|
||||
{ name: '账户列表', fn: testAccountsList },
|
||||
{ name: '交易记录列表', fn: testTransactionsList },
|
||||
{ name: '员工列表', fn: testEmployeesList },
|
||||
{ name: '贷款产品列表', fn: testLoanProductsList }
|
||||
];
|
||||
|
||||
let passed = 0;
|
||||
let total = tests.length;
|
||||
|
||||
for (const test of tests) {
|
||||
try {
|
||||
const success = await test.fn();
|
||||
if (success) {
|
||||
passed++;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`❌ ${test.name}测试异常:`, error.message);
|
||||
}
|
||||
console.log(''); // 空行分隔
|
||||
}
|
||||
|
||||
console.log('📋 测试结果汇总:');
|
||||
console.log(`✅ 通过: ${passed}/${total}`);
|
||||
console.log(`❌ 失败: ${total - passed}/${total}`);
|
||||
|
||||
if (passed === total) {
|
||||
console.log('🎉 所有测试通过!API集成正常');
|
||||
} else {
|
||||
console.log('⚠️ 部分测试失败,请检查相关API接口');
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
runTests().catch(console.error);
|
||||
@@ -1,129 +0,0 @@
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
|
||||
const app = express();
|
||||
|
||||
// 中间件
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
// 模拟认证中间件
|
||||
const mockAuthMiddleware = (req, res, next) => {
|
||||
req.user = { id: 1, username: 'test' };
|
||||
next();
|
||||
};
|
||||
|
||||
// 模拟仪表盘控制器
|
||||
const mockDashboardController = {
|
||||
getStats: (req, res) => {
|
||||
res.json({
|
||||
success: true,
|
||||
message: '获取统计数据成功(模拟数据)',
|
||||
data: {
|
||||
overview: {
|
||||
totalUsers: 1250,
|
||||
totalAccounts: 3420,
|
||||
totalTransactions: 15680,
|
||||
totalBalance: 12500000.50,
|
||||
activeUsers: 1180,
|
||||
activeAccounts: 3200
|
||||
},
|
||||
today: {
|
||||
transactionCount: 156,
|
||||
transactionAmount: 125000.00
|
||||
},
|
||||
accountTypes: [
|
||||
{ type: 'savings', count: 2100, totalBalance: 8500000.00 },
|
||||
{ type: 'checking', count: 800, totalBalance: 3200000.00 },
|
||||
{ type: 'credit', count: 400, totalBalance: 500000.00 },
|
||||
{ type: 'loan', count: 120, totalBalance: 300000.00 }
|
||||
],
|
||||
trends: [
|
||||
{ date: '2024-01-15', count: 45, totalAmount: 12500.00 },
|
||||
{ date: '2024-01-16', count: 52, totalAmount: 15200.00 },
|
||||
{ date: '2024-01-17', count: 38, totalAmount: 9800.00 },
|
||||
{ date: '2024-01-18', count: 61, totalAmount: 18500.00 },
|
||||
{ date: '2024-01-19', count: 48, totalAmount: 13200.00 },
|
||||
{ date: '2024-01-20', count: 55, totalAmount: 16800.00 },
|
||||
{ date: '2024-01-21', count: 42, totalAmount: 11200.00 }
|
||||
]
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
getRecentTransactions: (req, res) => {
|
||||
const { limit = 10 } = req.query;
|
||||
|
||||
const mockTransactions = [
|
||||
{
|
||||
id: 1,
|
||||
type: 'deposit',
|
||||
amount: 1000.00,
|
||||
description: '存款',
|
||||
status: 'completed',
|
||||
created_at: new Date(),
|
||||
account: {
|
||||
account_number: '1234567890',
|
||||
user: {
|
||||
username: 'user1',
|
||||
real_name: '张三'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: 'withdrawal',
|
||||
amount: 500.00,
|
||||
description: '取款',
|
||||
status: 'completed',
|
||||
created_at: new Date(Date.now() - 3600000),
|
||||
account: {
|
||||
account_number: '1234567891',
|
||||
user: {
|
||||
username: 'user2',
|
||||
real_name: '李四'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: 'transfer',
|
||||
amount: 200.00,
|
||||
description: '转账',
|
||||
status: 'completed',
|
||||
created_at: new Date(Date.now() - 7200000),
|
||||
account: {
|
||||
account_number: '1234567892',
|
||||
user: {
|
||||
username: 'user3',
|
||||
real_name: '王五'
|
||||
}
|
||||
}
|
||||
}
|
||||
].slice(0, parseInt(limit));
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '获取最近交易记录成功(模拟数据)',
|
||||
data: mockTransactions
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 路由
|
||||
app.get('/health', (req, res) => {
|
||||
res.json({ message: '银行后端服务运行正常', timestamp: new Date().toISOString() });
|
||||
});
|
||||
|
||||
app.get('/api/dashboard', mockAuthMiddleware, mockDashboardController.getStats);
|
||||
app.get('/api/dashboard/recent-transactions', mockAuthMiddleware, mockDashboardController.getRecentTransactions);
|
||||
|
||||
// 启动服务器
|
||||
const PORT = 5351;
|
||||
app.listen(PORT, () => {
|
||||
console.log(`🚀 银行后端模拟服务已启动`);
|
||||
console.log(`📡 服务地址: http://localhost:${PORT}`);
|
||||
console.log(`🏥 健康检查: http://localhost:${PORT}/health`);
|
||||
console.log(`📊 仪表盘API: http://localhost:${PORT}/api/dashboard`);
|
||||
console.log(`💳 最近交易API: http://localhost:${PORT}/api/dashboard/recent-transactions`);
|
||||
});
|
||||
@@ -1,80 +0,0 @@
|
||||
const { User, Role } = require('./models');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
async function testAuth() {
|
||||
try {
|
||||
console.log('=== 测试认证逻辑 ===');
|
||||
|
||||
// 查找用户(包含角色)
|
||||
const user = await User.findOne({
|
||||
where: { username: 'admin' },
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role'
|
||||
}]
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
console.log('❌ 未找到admin用户');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('✅ 找到admin用户');
|
||||
console.log('用户名:', user.username);
|
||||
console.log('状态:', user.status);
|
||||
console.log('角色:', user.role ? user.role.name : '无角色');
|
||||
console.log('密码哈希:', user.password);
|
||||
|
||||
// 测试密码验证
|
||||
const testPassword = 'Admin123456';
|
||||
console.log('\n=== 测试密码验证 ===');
|
||||
console.log('测试密码:', testPassword);
|
||||
|
||||
// 直接使用bcrypt比较
|
||||
const directTest = await bcrypt.compare(testPassword, user.password);
|
||||
console.log('直接bcrypt验证:', directTest);
|
||||
|
||||
// 使用模型方法验证
|
||||
const modelTest = await user.validPassword(testPassword);
|
||||
console.log('模型验证:', modelTest);
|
||||
|
||||
if (!modelTest) {
|
||||
console.log('\n=== 重新生成密码 ===');
|
||||
const newHash = await bcrypt.hash(testPassword, 10);
|
||||
console.log('新哈希:', newHash);
|
||||
|
||||
await user.update({
|
||||
password: newHash,
|
||||
status: 'active',
|
||||
login_attempts: 0,
|
||||
locked_until: null
|
||||
});
|
||||
|
||||
console.log('✅ 密码已更新');
|
||||
|
||||
// 重新加载用户数据
|
||||
await user.reload();
|
||||
|
||||
// 再次验证
|
||||
const finalTest = await user.validPassword(testPassword);
|
||||
console.log('最终验证:', finalTest);
|
||||
|
||||
if (finalTest) {
|
||||
console.log('🎉 密码修复成功!');
|
||||
console.log('用户名: admin');
|
||||
console.log('密码: Admin123456');
|
||||
console.log('状态: active');
|
||||
}
|
||||
} else {
|
||||
console.log('✅ 密码验证成功!');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('测试失败:', error.message);
|
||||
console.error('错误堆栈:', error.stack);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
testAuth();
|
||||
@@ -1,121 +0,0 @@
|
||||
const axios = require('axios')
|
||||
|
||||
const BASE_URL = 'http://localhost:5351'
|
||||
|
||||
async function testCompletedSupervisionsAPI() {
|
||||
try {
|
||||
console.log('开始测试监管任务已结项API...')
|
||||
|
||||
// 1. 登录获取token
|
||||
console.log('\n1. 用户登录...')
|
||||
const loginResponse = await axios.post(`${BASE_URL}/api/auth/login`, {
|
||||
username: 'admin',
|
||||
password: 'admin123'
|
||||
})
|
||||
|
||||
if (!loginResponse.data.success) {
|
||||
throw new Error('登录失败: ' + loginResponse.data.message)
|
||||
}
|
||||
|
||||
const token = loginResponse.data.data.token
|
||||
console.log('✅ 登录成功')
|
||||
|
||||
const headers = {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
// 2. 获取监管任务已结项列表
|
||||
console.log('\n2. 获取监管任务已结项列表...')
|
||||
const listResponse = await axios.get(`${BASE_URL}/api/completed-supervisions`, {
|
||||
headers,
|
||||
params: {
|
||||
page: 1,
|
||||
limit: 10
|
||||
}
|
||||
})
|
||||
|
||||
console.log('监管任务已结项列表响应:', JSON.stringify(listResponse.data, null, 2))
|
||||
|
||||
// 3. 获取监管任务已结项统计
|
||||
console.log('\n3. 获取监管任务已结项统计...')
|
||||
const statsResponse = await axios.get(`${BASE_URL}/api/completed-supervisions/stats`, {
|
||||
headers
|
||||
})
|
||||
|
||||
console.log('监管任务已结项统计响应:', JSON.stringify(statsResponse.data, null, 2))
|
||||
|
||||
// 4. 创建新的监管任务已结项
|
||||
console.log('\n4. 创建新的监管任务已结项...')
|
||||
const newTask = {
|
||||
applicationNumber: 'APP2024999',
|
||||
contractNumber: 'LOAN2024999',
|
||||
productName: '测试养殖贷',
|
||||
customerName: '测试用户',
|
||||
idType: 'ID_CARD',
|
||||
idNumber: '440999999999999999',
|
||||
assetType: '测试动物',
|
||||
assetQuantity: 100,
|
||||
totalRepaymentPeriods: 12,
|
||||
settlementStatus: 'unsettled',
|
||||
settlementNotes: '这是一个测试任务'
|
||||
}
|
||||
|
||||
const createResponse = await axios.post(`${BASE_URL}/api/completed-supervisions`, newTask, {
|
||||
headers
|
||||
})
|
||||
|
||||
console.log('创建监管任务已结项响应:', JSON.stringify(createResponse.data, null, 2))
|
||||
|
||||
const createdTaskId = createResponse.data.data.id
|
||||
|
||||
// 5. 获取单个监管任务已结项详情
|
||||
console.log('\n5. 获取监管任务已结项详情...')
|
||||
const detailResponse = await axios.get(`${BASE_URL}/api/completed-supervisions/${createdTaskId}`, {
|
||||
headers
|
||||
})
|
||||
|
||||
console.log('监管任务已结项详情响应:', JSON.stringify(detailResponse.data, null, 2))
|
||||
|
||||
// 6. 更新监管任务已结项
|
||||
console.log('\n6. 更新监管任务已结项...')
|
||||
const updateData = {
|
||||
settlementStatus: 'settled',
|
||||
settlementDate: '2024-12-20',
|
||||
settlementNotes: '更新后的备注信息'
|
||||
}
|
||||
|
||||
const updateResponse = await axios.put(`${BASE_URL}/api/completed-supervisions/${createdTaskId}`, updateData, {
|
||||
headers
|
||||
})
|
||||
|
||||
console.log('更新监管任务已结项响应:', JSON.stringify(updateResponse.data, null, 2))
|
||||
|
||||
// 7. 批量更新状态
|
||||
console.log('\n7. 批量更新状态...')
|
||||
const batchUpdateResponse = await axios.put(`${BASE_URL}/api/completed-supervisions/batch/status`, {
|
||||
ids: [createdTaskId],
|
||||
settlementStatus: 'partial'
|
||||
}, {
|
||||
headers
|
||||
})
|
||||
|
||||
console.log('批量更新状态响应:', JSON.stringify(batchUpdateResponse.data, null, 2))
|
||||
|
||||
// 8. 删除监管任务已结项
|
||||
console.log('\n8. 删除监管任务已结项...')
|
||||
const deleteResponse = await axios.delete(`${BASE_URL}/api/completed-supervisions/${createdTaskId}`, {
|
||||
headers
|
||||
})
|
||||
|
||||
console.log('删除监管任务已结项响应:', JSON.stringify(deleteResponse.data, null, 2))
|
||||
|
||||
console.log('\n✅ 所有监管任务已结项API测试完成')
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 测试失败:', error.response?.data || error.message)
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
testCompletedSupervisionsAPI()
|
||||
@@ -1,30 +0,0 @@
|
||||
const axios = require('axios')
|
||||
|
||||
const BASE_URL = 'http://localhost:5351'
|
||||
|
||||
async function testCompletedSupervisionsSimple() {
|
||||
try {
|
||||
console.log('测试监管任务已结项API连接...')
|
||||
|
||||
const response = await axios.get(`${BASE_URL}/api/completed-supervisions`, {
|
||||
timeout: 5000
|
||||
})
|
||||
|
||||
console.log('✅ 监管任务已结项API连接成功')
|
||||
console.log('响应状态:', response.status)
|
||||
console.log('响应数据:', JSON.stringify(response.data, null, 2))
|
||||
|
||||
} catch (error) {
|
||||
if (error.response) {
|
||||
console.log('API响应错误:')
|
||||
console.log('状态码:', error.response.status)
|
||||
console.log('错误信息:', error.response.data)
|
||||
} else if (error.request) {
|
||||
console.log('❌ 无法连接到服务器,请确保后端服务正在运行')
|
||||
} else {
|
||||
console.log('❌ 请求配置错误:', error.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
testCompletedSupervisionsSimple()
|
||||
@@ -1,42 +0,0 @@
|
||||
/**
|
||||
* 测试创建贷款商品
|
||||
*/
|
||||
const { LoanProduct } = require('./models');
|
||||
|
||||
async function testCreateLoanProduct() {
|
||||
try {
|
||||
console.log('🧪 测试创建贷款商品...');
|
||||
|
||||
const productData = {
|
||||
productName: '测试贷款产品',
|
||||
loanAmount: '50000~5000000',
|
||||
loanTerm: 12,
|
||||
interestRate: 5.5,
|
||||
serviceArea: '北京市',
|
||||
servicePhone: '13800138000',
|
||||
productDescription: '这是一个测试贷款产品',
|
||||
applicationRequirements: '需要提供身份证和收入证明',
|
||||
requiredDocuments: '身份证、收入证明、银行流水',
|
||||
approvalProcess: '提交申请->审核->放款',
|
||||
riskLevel: 'LOW',
|
||||
minLoanAmount: 50000,
|
||||
maxLoanAmount: 5000000,
|
||||
createdBy: 2,
|
||||
updatedBy: 2
|
||||
};
|
||||
|
||||
console.log('创建数据:', productData);
|
||||
|
||||
const product = await LoanProduct.create(productData);
|
||||
|
||||
console.log('✅ 创建成功:', product.toJSON());
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 创建失败:', error.message);
|
||||
console.error('详细错误:', error);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
testCreateLoanProduct();
|
||||
@@ -1,96 +0,0 @@
|
||||
/**
|
||||
* 测试创建项目接口
|
||||
*/
|
||||
const axios = require('axios');
|
||||
|
||||
const API_BASE_URL = 'http://localhost:5351';
|
||||
|
||||
async function testCreateProject() {
|
||||
try {
|
||||
console.log('🚀 开始测试创建项目接口...\n');
|
||||
|
||||
// 1. 先登录获取token
|
||||
console.log('1. 登录获取认证token...');
|
||||
const loginResponse = await axios.post(`${API_BASE_URL}/api/auth/login`, {
|
||||
username: 'admin',
|
||||
password: 'Admin123456'
|
||||
});
|
||||
|
||||
if (!loginResponse.data.success) {
|
||||
throw new Error('登录失败: ' + loginResponse.data.message);
|
||||
}
|
||||
|
||||
const token = loginResponse.data.data.token;
|
||||
console.log('✅ 登录成功,获取到token\n');
|
||||
|
||||
// 2. 测试创建项目
|
||||
console.log('2. 测试创建新项目...');
|
||||
const newProject = {
|
||||
name: '测试项目_' + new Date().getTime(),
|
||||
status: 'supervision',
|
||||
farmName: '测试养殖场',
|
||||
supervisionObject: '牛',
|
||||
supervisionQuantity: 100,
|
||||
supervisionPeriod: '12个月',
|
||||
supervisionAmount: 500000.00,
|
||||
startTime: '2024-01-01',
|
||||
endTime: '2024-12-31',
|
||||
earTag: 50,
|
||||
collar: 30,
|
||||
host: 20,
|
||||
loanOfficer: '张专员',
|
||||
description: '这是一个测试项目,用于验证创建接口功能'
|
||||
};
|
||||
|
||||
const createResponse = await axios.post(`${API_BASE_URL}/api/projects`, newProject, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (createResponse.data.success) {
|
||||
console.log('✅ 项目创建成功!');
|
||||
console.log('📋 创建的项目信息:');
|
||||
console.log(` - 项目ID: ${createResponse.data.data.id}`);
|
||||
console.log(` - 项目名称: ${createResponse.data.data.name}`);
|
||||
console.log(` - 养殖场: ${createResponse.data.data.farmName}`);
|
||||
console.log(` - 监管对象: ${createResponse.data.data.supervisionObject}`);
|
||||
console.log(` - 监管数量: ${createResponse.data.data.supervisionQuantity}`);
|
||||
console.log(` - 监管金额: ${createResponse.data.data.supervisionAmount}元`);
|
||||
console.log(` - 贷款专员: ${createResponse.data.data.loanOfficer}`);
|
||||
console.log(` - 创建时间: ${createResponse.data.data.createdAt}`);
|
||||
} else {
|
||||
console.log('❌ 项目创建失败:', createResponse.data.message);
|
||||
}
|
||||
|
||||
// 3. 验证项目是否在列表中
|
||||
console.log('\n3. 验证项目是否在列表中...');
|
||||
const listResponse = await axios.get(`${API_BASE_URL}/api/projects`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
if (listResponse.data.success) {
|
||||
const projects = listResponse.data.data.projects;
|
||||
const createdProject = projects.find(p => p.name === newProject.name);
|
||||
|
||||
if (createdProject) {
|
||||
console.log('✅ 项目已成功添加到列表中');
|
||||
console.log(`📊 当前总项目数: ${projects.length}`);
|
||||
} else {
|
||||
console.log('❌ 项目未在列表中找到');
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 测试失败:', error.message);
|
||||
if (error.response) {
|
||||
console.error('响应数据:', error.response.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
testCreateProject();
|
||||
@@ -1,80 +0,0 @@
|
||||
const { User } = require('./models');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
async function testDatabaseStorage() {
|
||||
try {
|
||||
console.log('=== 测试数据库存储问题 ===\n');
|
||||
|
||||
// 1. 生成一个新的密码哈希
|
||||
const testPassword = 'Admin123456';
|
||||
const newHash = await bcrypt.hash(testPassword, 10);
|
||||
console.log('1. 生成新的密码哈希:');
|
||||
console.log('原始密码:', testPassword);
|
||||
console.log('生成的哈希:', newHash);
|
||||
console.log('哈希长度:', newHash.length);
|
||||
console.log('');
|
||||
|
||||
// 2. 验证新生成的哈希
|
||||
console.log('2. 验证新生成的哈希:');
|
||||
const isValid = await bcrypt.compare(testPassword, newHash);
|
||||
console.log('新哈希验证结果:', isValid);
|
||||
console.log('');
|
||||
|
||||
// 3. 更新数据库
|
||||
console.log('3. 更新数据库:');
|
||||
const user = await User.findOne({ where: { username: 'admin' } });
|
||||
if (!user) {
|
||||
console.log('❌ 未找到admin用户');
|
||||
return;
|
||||
}
|
||||
|
||||
await user.update({ password: newHash });
|
||||
console.log('✅ 数据库已更新');
|
||||
console.log('');
|
||||
|
||||
// 4. 重新查询数据库
|
||||
console.log('4. 重新查询数据库:');
|
||||
const updatedUser = await User.findOne({ where: { username: 'admin' } });
|
||||
if (updatedUser) {
|
||||
console.log('查询到的密码哈希:', updatedUser.password);
|
||||
console.log('查询到的哈希长度:', updatedUser.password.length);
|
||||
console.log('哈希是否匹配:', updatedUser.password === newHash);
|
||||
console.log('');
|
||||
|
||||
// 5. 测试查询到的哈希
|
||||
console.log('5. 测试查询到的哈希:');
|
||||
const queryTest = await bcrypt.compare(testPassword, updatedUser.password);
|
||||
console.log('查询到的哈希验证结果:', queryTest);
|
||||
console.log('');
|
||||
|
||||
// 6. 测试User模型的validPassword方法
|
||||
console.log('6. 测试User模型的validPassword方法:');
|
||||
const modelTest = await updatedUser.validPassword(testPassword);
|
||||
console.log('模型验证结果:', modelTest);
|
||||
console.log('');
|
||||
|
||||
if (queryTest && modelTest) {
|
||||
console.log('🎉 数据库存储和验证都正常!');
|
||||
} else if (queryTest && !modelTest) {
|
||||
console.log('❌ 数据库存储正常,但User模型验证失败');
|
||||
console.log('可能原因: User模型的validPassword方法有问题');
|
||||
} else if (!queryTest && modelTest) {
|
||||
console.log('❌ 数据库存储有问题,但User模型验证成功');
|
||||
console.log('可能原因: 数据库存储时数据被截断或损坏');
|
||||
} else {
|
||||
console.log('❌ 数据库存储和User模型验证都失败');
|
||||
console.log('可能原因: 数据库字段长度不够或编码问题');
|
||||
}
|
||||
} else {
|
||||
console.log('❌ 重新查询用户失败');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('测试失败:', error.message);
|
||||
console.error('错误堆栈:', error.stack);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
testDatabaseStorage();
|
||||
@@ -1,121 +0,0 @@
|
||||
const axios = require('axios')
|
||||
|
||||
const BASE_URL = 'http://localhost:5351'
|
||||
|
||||
async function testInstallationTasksAPI() {
|
||||
try {
|
||||
console.log('开始测试待安装任务API...')
|
||||
|
||||
// 1. 登录获取token
|
||||
console.log('\n1. 用户登录...')
|
||||
const loginResponse = await axios.post(`${BASE_URL}/api/auth/login`, {
|
||||
username: 'admin',
|
||||
password: 'admin123'
|
||||
})
|
||||
|
||||
if (!loginResponse.data.success) {
|
||||
throw new Error('登录失败: ' + loginResponse.data.message)
|
||||
}
|
||||
|
||||
const token = loginResponse.data.data.token
|
||||
console.log('✅ 登录成功')
|
||||
|
||||
const headers = {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
// 2. 获取待安装任务列表
|
||||
console.log('\n2. 获取待安装任务列表...')
|
||||
const listResponse = await axios.get(`${BASE_URL}/api/installation-tasks`, {
|
||||
headers,
|
||||
params: {
|
||||
page: 1,
|
||||
limit: 10
|
||||
}
|
||||
})
|
||||
|
||||
console.log('待安装任务列表响应:', JSON.stringify(listResponse.data, null, 2))
|
||||
|
||||
// 3. 获取待安装任务统计
|
||||
console.log('\n3. 获取待安装任务统计...')
|
||||
const statsResponse = await axios.get(`${BASE_URL}/api/installation-tasks/stats`, {
|
||||
headers
|
||||
})
|
||||
|
||||
console.log('待安装任务统计响应:', JSON.stringify(statsResponse.data, null, 2))
|
||||
|
||||
// 4. 创建新的待安装任务
|
||||
console.log('\n4. 创建新的待安装任务...')
|
||||
const newTask = {
|
||||
applicationNumber: 'APP2024999',
|
||||
contractNumber: 'LOAN2024999',
|
||||
productName: '测试养殖贷',
|
||||
customerName: '测试用户',
|
||||
idType: 'ID_CARD',
|
||||
idNumber: '440999999999999999',
|
||||
assetType: '测试动物',
|
||||
equipmentToInstall: '测试设备',
|
||||
installationNotes: '这是一个测试任务',
|
||||
installerName: '测试安装员',
|
||||
installerPhone: '13800138999',
|
||||
installationAddress: '测试地址'
|
||||
}
|
||||
|
||||
const createResponse = await axios.post(`${BASE_URL}/api/installation-tasks`, newTask, {
|
||||
headers
|
||||
})
|
||||
|
||||
console.log('创建待安装任务响应:', JSON.stringify(createResponse.data, null, 2))
|
||||
|
||||
const createdTaskId = createResponse.data.data.id
|
||||
|
||||
// 5. 获取单个待安装任务详情
|
||||
console.log('\n5. 获取待安装任务详情...')
|
||||
const detailResponse = await axios.get(`${BASE_URL}/api/installation-tasks/${createdTaskId}`, {
|
||||
headers
|
||||
})
|
||||
|
||||
console.log('待安装任务详情响应:', JSON.stringify(detailResponse.data, null, 2))
|
||||
|
||||
// 6. 更新待安装任务
|
||||
console.log('\n6. 更新待安装任务...')
|
||||
const updateData = {
|
||||
installationStatus: 'in-progress',
|
||||
installationNotes: '更新后的备注信息'
|
||||
}
|
||||
|
||||
const updateResponse = await axios.put(`${BASE_URL}/api/installation-tasks/${createdTaskId}`, updateData, {
|
||||
headers
|
||||
})
|
||||
|
||||
console.log('更新待安装任务响应:', JSON.stringify(updateResponse.data, null, 2))
|
||||
|
||||
// 7. 批量更新状态
|
||||
console.log('\n7. 批量更新状态...')
|
||||
const batchUpdateResponse = await axios.put(`${BASE_URL}/api/installation-tasks/batch/status`, {
|
||||
ids: [createdTaskId],
|
||||
installationStatus: 'completed'
|
||||
}, {
|
||||
headers
|
||||
})
|
||||
|
||||
console.log('批量更新状态响应:', JSON.stringify(batchUpdateResponse.data, null, 2))
|
||||
|
||||
// 8. 删除待安装任务
|
||||
console.log('\n8. 删除待安装任务...')
|
||||
const deleteResponse = await axios.delete(`${BASE_URL}/api/installation-tasks/${createdTaskId}`, {
|
||||
headers
|
||||
})
|
||||
|
||||
console.log('删除待安装任务响应:', JSON.stringify(deleteResponse.data, null, 2))
|
||||
|
||||
console.log('\n✅ 所有待安装任务API测试完成')
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 测试失败:', error.response?.data || error.message)
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
testInstallationTasksAPI()
|
||||
@@ -1,38 +0,0 @@
|
||||
const { sequelize, InstallationTask, User } = require('./models');
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
console.log('测试InstallationTask模型...');
|
||||
|
||||
// 测试基本查询
|
||||
console.log('1. 测试基本count查询...');
|
||||
const count = await InstallationTask.count();
|
||||
console.log('InstallationTask count:', count);
|
||||
|
||||
// 测试findAndCountAll查询
|
||||
console.log('2. 测试findAndCountAll查询...');
|
||||
const result = await InstallationTask.findAndCountAll({
|
||||
limit: 5
|
||||
});
|
||||
console.log('findAndCountAll result count:', result.count);
|
||||
console.log('findAndCountAll rows length:', result.rows.length);
|
||||
|
||||
// 测试带include的查询
|
||||
console.log('3. 测试带include的查询...');
|
||||
const resultWithInclude = await InstallationTask.findAndCountAll({
|
||||
include: [
|
||||
{ model: User, as: 'creator', attributes: ['id', 'username'] },
|
||||
{ model: User, as: 'updater', attributes: ['id', 'username'] }
|
||||
],
|
||||
limit: 5
|
||||
});
|
||||
console.log('include查询 count:', resultWithInclude.count);
|
||||
console.log('include查询 rows length:', resultWithInclude.rows.length);
|
||||
|
||||
} catch (err) {
|
||||
console.error('错误:', err.message);
|
||||
console.error('SQL:', err.sql);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
})();
|
||||
@@ -1,30 +0,0 @@
|
||||
const axios = require('axios')
|
||||
|
||||
const BASE_URL = 'http://localhost:5351'
|
||||
|
||||
async function testInstallationTasksSimple() {
|
||||
try {
|
||||
console.log('测试待安装任务API连接...')
|
||||
|
||||
const response = await axios.get(`${BASE_URL}/api/installation-tasks`, {
|
||||
timeout: 5000
|
||||
})
|
||||
|
||||
console.log('✅ 待安装任务API连接成功')
|
||||
console.log('响应状态:', response.status)
|
||||
console.log('响应数据:', JSON.stringify(response.data, null, 2))
|
||||
|
||||
} catch (error) {
|
||||
if (error.response) {
|
||||
console.log('API响应错误:')
|
||||
console.log('状态码:', error.response.status)
|
||||
console.log('错误信息:', error.response.data)
|
||||
} else if (error.request) {
|
||||
console.log('❌ 无法连接到服务器,请确保后端服务正在运行')
|
||||
} else {
|
||||
console.log('❌ 请求配置错误:', error.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
testInstallationTasksSimple()
|
||||
@@ -1,117 +0,0 @@
|
||||
/**
|
||||
* 贷款申请API测试
|
||||
* @file test-loan-applications-api.js
|
||||
*/
|
||||
const axios = require('axios');
|
||||
|
||||
async function testLoanApplicationsAPI() {
|
||||
try {
|
||||
console.log('🔍 测试贷款申请API...');
|
||||
|
||||
// 1. 登录获取token
|
||||
console.log('\n1. 登录测试...');
|
||||
const loginResponse = await axios.post('http://localhost:5351/api/auth/login', {
|
||||
username: 'admin',
|
||||
password: 'Admin123456'
|
||||
});
|
||||
|
||||
if (!loginResponse.data.success) {
|
||||
throw new Error('登录失败: ' + loginResponse.data.message);
|
||||
}
|
||||
|
||||
const token = loginResponse.data.data.token;
|
||||
console.log('✅ 登录成功');
|
||||
|
||||
// 设置授权头
|
||||
const authHeaders = {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
// 2. 获取贷款申请列表
|
||||
console.log('\n2. 获取申请列表...');
|
||||
const listResponse = await axios.get('http://localhost:5351/api/loan-applications', {
|
||||
headers: authHeaders
|
||||
});
|
||||
|
||||
if (!listResponse.data.success) {
|
||||
throw new Error('获取列表失败: ' + listResponse.data.message);
|
||||
}
|
||||
|
||||
console.log('✅ 获取申请列表成功');
|
||||
console.log(`📊 申请数量: ${listResponse.data.data.applications.length}`);
|
||||
console.log(`📊 总数: ${listResponse.data.data.pagination.total}`);
|
||||
|
||||
if (listResponse.data.data.applications.length > 0) {
|
||||
const firstApp = listResponse.data.data.applications[0];
|
||||
console.log(`📋 第一个申请: ${firstApp.applicationNumber} - ${firstApp.productName} - ${firstApp.status}`);
|
||||
|
||||
// 3. 获取申请详情
|
||||
console.log('\n3. 获取申请详情...');
|
||||
const detailResponse = await axios.get(`http://localhost:5351/api/loan-applications/${firstApp.id}`, {
|
||||
headers: authHeaders
|
||||
});
|
||||
|
||||
if (!detailResponse.data.success) {
|
||||
throw new Error('获取详情失败: ' + detailResponse.data.message);
|
||||
}
|
||||
|
||||
console.log('✅ 获取申请详情成功');
|
||||
console.log(`📋 申请详情: ${detailResponse.data.data.applicationNumber}`);
|
||||
console.log(`📋 审核记录数: ${detailResponse.data.data.auditRecords.length}`);
|
||||
|
||||
// 4. 测试审核功能(仅对待审核的申请)
|
||||
if (firstApp.status === 'pending_review') {
|
||||
console.log('\n4. 测试审核功能...');
|
||||
const auditResponse = await axios.post(`http://localhost:5351/api/loan-applications/${firstApp.id}/audit`, {
|
||||
action: 'approve',
|
||||
comment: 'API测试审核通过'
|
||||
}, {
|
||||
headers: authHeaders
|
||||
});
|
||||
|
||||
if (!auditResponse.data.success) {
|
||||
throw new Error('审核失败: ' + auditResponse.data.message);
|
||||
}
|
||||
|
||||
console.log('✅ 审核功能测试成功');
|
||||
console.log(`📋 审核结果: ${auditResponse.data.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 获取统计信息
|
||||
console.log('\n5. 获取统计信息...');
|
||||
const statsResponse = await axios.get('http://localhost:5351/api/loan-applications/stats', {
|
||||
headers: authHeaders
|
||||
});
|
||||
|
||||
if (!statsResponse.data.success) {
|
||||
throw new Error('获取统计失败: ' + statsResponse.data.message);
|
||||
}
|
||||
|
||||
console.log('✅ 获取统计信息成功');
|
||||
console.log(`📊 总申请数: ${statsResponse.data.data.total.applications}`);
|
||||
console.log(`📊 总金额: ${statsResponse.data.data.total.amount.toFixed(2)}元`);
|
||||
console.log('📊 按状态统计:');
|
||||
Object.entries(statsResponse.data.data.byStatus.counts).forEach(([status, count]) => {
|
||||
if (count > 0) {
|
||||
console.log(` - ${status}: ${count}个申请`);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('\n🎉 所有API测试完成!');
|
||||
|
||||
} catch (error) {
|
||||
console.error('\n❌ API测试失败:', error.message);
|
||||
if (error.response) {
|
||||
console.error('响应状态:', error.response.status);
|
||||
console.error('响应数据:', error.response.data);
|
||||
} else if (error.code) {
|
||||
console.error('错误代码:', error.code);
|
||||
}
|
||||
console.error('完整错误:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
testLoanApplicationsAPI();
|
||||
@@ -1,125 +0,0 @@
|
||||
const axios = require('axios');
|
||||
|
||||
const BASE_URL = 'http://localhost:5351';
|
||||
|
||||
// 测试贷款商品API
|
||||
async function testLoanProductsAPI() {
|
||||
try {
|
||||
console.log('开始测试贷款商品API...');
|
||||
|
||||
// 1. 登录获取token
|
||||
console.log('\n1. 用户登录...');
|
||||
const loginResponse = await axios.post(`${BASE_URL}/api/auth/login`, {
|
||||
username: 'admin',
|
||||
password: 'admin123'
|
||||
});
|
||||
|
||||
if (!loginResponse.data.success) {
|
||||
throw new Error(`登录失败: ${loginResponse.data.message}`);
|
||||
}
|
||||
|
||||
const token = loginResponse.data.data.token;
|
||||
console.log('✅ 登录成功');
|
||||
|
||||
// 设置请求头
|
||||
const headers = {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
// 2. 获取贷款商品列表
|
||||
console.log('\n2. 获取贷款商品列表...');
|
||||
const listResponse = await axios.get(`${BASE_URL}/api/loan-products`, { headers });
|
||||
console.log('✅ 获取贷款商品列表成功');
|
||||
console.log(` 总数: ${listResponse.data.data.pagination.total}`);
|
||||
console.log(` 当前页: ${listResponse.data.data.pagination.current}`);
|
||||
console.log(` 每页数量: ${listResponse.data.data.pagination.pageSize}`);
|
||||
|
||||
// 3. 获取贷款商品统计信息
|
||||
console.log('\n3. 获取贷款商品统计信息...');
|
||||
const statsResponse = await axios.get(`${BASE_URL}/api/loan-products/stats`, { headers });
|
||||
console.log('✅ 获取统计信息成功');
|
||||
console.log(` 总产品数: ${statsResponse.data.data.totalProducts}`);
|
||||
console.log(` 在售产品: ${statsResponse.data.data.onSaleProducts}`);
|
||||
console.log(` 停售产品: ${statsResponse.data.data.offSaleProducts}`);
|
||||
|
||||
// 4. 创建新的贷款商品
|
||||
console.log('\n4. 创建新的贷款商品...');
|
||||
const newProduct = {
|
||||
productName: '测试贷款产品',
|
||||
loanAmount: '100000~500000元',
|
||||
loanTerm: 12,
|
||||
interestRate: 5.5,
|
||||
serviceArea: '测试区域',
|
||||
servicePhone: '13800138000',
|
||||
productDescription: '这是一个测试贷款产品',
|
||||
applicationRequirements: '测试申请条件',
|
||||
requiredDocuments: '测试所需材料',
|
||||
approvalProcess: '测试审批流程',
|
||||
riskLevel: 'MEDIUM',
|
||||
minLoanAmount: 100000,
|
||||
maxLoanAmount: 500000
|
||||
};
|
||||
|
||||
const createResponse = await axios.post(`${BASE_URL}/api/loan-products`, newProduct, { headers });
|
||||
console.log('✅ 创建贷款商品成功');
|
||||
console.log(` 产品ID: ${createResponse.data.data.id}`);
|
||||
console.log(` 产品名称: ${createResponse.data.data.productName}`);
|
||||
|
||||
const productId = createResponse.data.data.id;
|
||||
|
||||
// 5. 根据ID获取贷款商品详情
|
||||
console.log('\n5. 获取贷款商品详情...');
|
||||
const detailResponse = await axios.get(`${BASE_URL}/api/loan-products/${productId}`, { headers });
|
||||
console.log('✅ 获取贷款商品详情成功');
|
||||
console.log(` 产品名称: ${detailResponse.data.data.productName}`);
|
||||
console.log(` 贷款利率: ${detailResponse.data.data.interestRate}%`);
|
||||
|
||||
// 6. 更新贷款商品
|
||||
console.log('\n6. 更新贷款商品...');
|
||||
const updateData = {
|
||||
productName: '更新后的测试贷款产品',
|
||||
interestRate: 6.0,
|
||||
productDescription: '这是更新后的测试贷款产品描述'
|
||||
};
|
||||
|
||||
const updateResponse = await axios.put(`${BASE_URL}/api/loan-products/${productId}`, updateData, { headers });
|
||||
console.log('✅ 更新贷款商品成功');
|
||||
console.log(` 更新后产品名称: ${updateResponse.data.data.productName}`);
|
||||
console.log(` 更新后利率: ${updateResponse.data.data.interestRate}%`);
|
||||
|
||||
// 7. 批量更新在售状态
|
||||
console.log('\n7. 批量更新在售状态...');
|
||||
const batchUpdateResponse = await axios.put(`${BASE_URL}/api/loan-products/batch/status`, {
|
||||
ids: [productId],
|
||||
onSaleStatus: false
|
||||
}, { headers });
|
||||
console.log('✅ 批量更新状态成功');
|
||||
|
||||
// 8. 删除贷款商品
|
||||
console.log('\n8. 删除贷款商品...');
|
||||
const deleteResponse = await axios.delete(`${BASE_URL}/api/loan-products/${productId}`, { headers });
|
||||
console.log('✅ 删除贷款商品成功');
|
||||
|
||||
console.log('\n🎉 所有贷款商品API测试通过!');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 测试失败:', error.response?.data || error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
testLoanProductsAPI()
|
||||
.then(() => {
|
||||
console.log('贷款商品API测试完成');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('贷款商品API测试失败:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = testLoanProductsAPI;
|
||||
@@ -1,49 +0,0 @@
|
||||
const axios = require('axios');
|
||||
|
||||
async function testLoanProductsAPI() {
|
||||
try {
|
||||
console.log('测试贷款商品API...');
|
||||
|
||||
// 1. 登录获取token
|
||||
console.log('\n1. 用户登录...');
|
||||
const loginResponse = await axios.post('http://localhost:5351/api/auth/login', {
|
||||
username: 'admin',
|
||||
password: 'admin123'
|
||||
});
|
||||
|
||||
if (!loginResponse.data.success) {
|
||||
throw new Error(`登录失败: ${loginResponse.data.message}`);
|
||||
}
|
||||
|
||||
const token = loginResponse.data.data.token;
|
||||
console.log('✅ 登录成功');
|
||||
|
||||
// 设置请求头
|
||||
const headers = {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
// 2. 获取贷款商品列表
|
||||
console.log('\n2. 获取贷款商品列表...');
|
||||
const listResponse = await axios.get('http://localhost:5351/api/loan-products', { headers });
|
||||
|
||||
if (listResponse.data.success) {
|
||||
console.log('✅ 获取贷款商品列表成功');
|
||||
console.log('响应数据结构:');
|
||||
console.log(JSON.stringify(listResponse.data, null, 2));
|
||||
|
||||
if (listResponse.data.data && listResponse.data.data.products) {
|
||||
console.log('\n产品数据示例:');
|
||||
console.log(JSON.stringify(listResponse.data.data.products[0], null, 2));
|
||||
}
|
||||
} else {
|
||||
console.log('❌ 获取贷款商品列表失败:', listResponse.data.message);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 测试失败:', error.response?.data || error.message);
|
||||
}
|
||||
}
|
||||
|
||||
testLoanProductsAPI();
|
||||
@@ -1,34 +0,0 @@
|
||||
const { Project } = require('./models');
|
||||
|
||||
async function testProjectDirect() {
|
||||
try {
|
||||
console.log('🚀 直接测试项目模型...');
|
||||
|
||||
// 测试基本查询
|
||||
const count = await Project.count();
|
||||
console.log('✅ 项目总数:', count);
|
||||
|
||||
// 测试获取前5个项目
|
||||
const projects = await Project.findAll({
|
||||
limit: 5,
|
||||
order: [['createdAt', 'DESC']]
|
||||
});
|
||||
|
||||
console.log('✅ 获取项目成功,数量:', projects.length);
|
||||
|
||||
if (projects.length > 0) {
|
||||
console.log('第一个项目:', {
|
||||
id: projects[0].id,
|
||||
name: projects[0].name,
|
||||
status: projects[0].status,
|
||||
farmName: projects[0].farmName
|
||||
});
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 测试失败:', error.message);
|
||||
console.error('错误堆栈:', error.stack);
|
||||
}
|
||||
}
|
||||
|
||||
testProjectDirect();
|
||||
@@ -1,388 +0,0 @@
|
||||
const http = require('http');
|
||||
|
||||
// 测试配置
|
||||
const API_BASE_URL = 'http://localhost:5351';
|
||||
let authToken = '';
|
||||
|
||||
// 辅助函数:发送HTTP请求
|
||||
function makeRequest(options, data = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = http.request(options, (res) => {
|
||||
let responseData = '';
|
||||
|
||||
res.on('data', (chunk) => {
|
||||
responseData += chunk;
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
try {
|
||||
const result = {
|
||||
statusCode: res.statusCode,
|
||||
headers: res.headers,
|
||||
data: responseData ? JSON.parse(responseData) : null
|
||||
};
|
||||
resolve(result);
|
||||
} catch (error) {
|
||||
reject(new Error(`解析响应失败: ${error.message}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (error) => {
|
||||
reject(error);
|
||||
});
|
||||
|
||||
if (data) {
|
||||
req.write(JSON.stringify(data));
|
||||
}
|
||||
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
// 登录获取认证令牌
|
||||
async function login() {
|
||||
console.log('🔐 正在登录...');
|
||||
|
||||
const options = {
|
||||
hostname: 'localhost',
|
||||
port: 5351,
|
||||
path: '/api/auth/login',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
const loginData = {
|
||||
username: 'admin',
|
||||
password: 'Admin123456'
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await makeRequest(options, loginData);
|
||||
|
||||
if (response.statusCode === 200 && response.data.success) {
|
||||
authToken = response.data.data.token;
|
||||
console.log('✅ 登录成功');
|
||||
return true;
|
||||
} else {
|
||||
console.error('❌ 登录失败:', response.data.message);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 登录请求失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试获取项目列表
|
||||
async function testGetProjects() {
|
||||
console.log('\n📋 测试获取项目列表...');
|
||||
|
||||
const options = {
|
||||
hostname: 'localhost',
|
||||
port: 5351,
|
||||
path: '/api/projects',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authToken}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await makeRequest(options);
|
||||
|
||||
if (response.statusCode === 200 && response.data.success) {
|
||||
console.log('✅ 获取项目列表成功');
|
||||
console.log(` 项目数量: ${response.data.data.projects.length}`);
|
||||
console.log(` 总数量: ${response.data.data.pagination.total}`);
|
||||
|
||||
// 显示前3个项目的基本信息
|
||||
const projects = response.data.data.projects.slice(0, 3);
|
||||
projects.forEach((project, index) => {
|
||||
console.log(` 项目${index + 1}: ${project.name} (${project.status}) - ${project.farmName}`);
|
||||
});
|
||||
|
||||
return true;
|
||||
} else {
|
||||
console.error('❌ 获取项目列表失败:', response.data.message);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 获取项目列表请求失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试获取项目统计
|
||||
async function testGetProjectStats() {
|
||||
console.log('\n📊 测试获取项目统计...');
|
||||
|
||||
const options = {
|
||||
hostname: 'localhost',
|
||||
port: 5351,
|
||||
path: '/api/projects/stats',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authToken}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await makeRequest(options);
|
||||
|
||||
if (response.statusCode === 200 && response.data.success) {
|
||||
console.log('✅ 获取项目统计成功');
|
||||
const stats = response.data.data;
|
||||
console.log(` 总项目数: ${stats.total}`);
|
||||
console.log(` 监管中: ${stats.supervision}`);
|
||||
console.log(` 已结项: ${stats.completed}`);
|
||||
console.log(` 总监管金额: ${stats.totalAmount?.toFixed(2) || 0} 元`);
|
||||
console.log(` 总监管数量: ${stats.totalQuantity || 0} 头`);
|
||||
return true;
|
||||
} else {
|
||||
console.error('❌ 获取项目统计失败:', response.data.message);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 获取项目统计请求失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试创建项目
|
||||
async function testCreateProject() {
|
||||
console.log('\n➕ 测试创建项目...');
|
||||
|
||||
const options = {
|
||||
hostname: 'localhost',
|
||||
port: 5351,
|
||||
path: '/api/projects',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authToken}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
const projectData = {
|
||||
name: '测试项目',
|
||||
status: 'supervision',
|
||||
farmName: '测试养殖场',
|
||||
supervisionObject: '牛',
|
||||
supervisionQuantity: 20,
|
||||
supervisionPeriod: '30天',
|
||||
supervisionAmount: 50000.00,
|
||||
startTime: '2024-01-01',
|
||||
endTime: '2024-12-31',
|
||||
earTag: 10,
|
||||
collar: 10,
|
||||
host: 1,
|
||||
loanOfficer: '测试专员',
|
||||
description: '这是一个测试项目'
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await makeRequest(options, projectData);
|
||||
|
||||
if (response.statusCode === 201 && response.data.success) {
|
||||
console.log('✅ 创建项目成功');
|
||||
console.log(` 项目ID: ${response.data.data.id}`);
|
||||
console.log(` 项目名称: ${response.data.data.name}`);
|
||||
return response.data.data.id;
|
||||
} else {
|
||||
console.error('❌ 创建项目失败:', response.data.message);
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 创建项目请求失败:', error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试获取项目详情
|
||||
async function testGetProjectById(projectId) {
|
||||
console.log('\n🔍 测试获取项目详情...');
|
||||
|
||||
const options = {
|
||||
hostname: 'localhost',
|
||||
port: 5351,
|
||||
path: `/api/projects/${projectId}`,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authToken}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await makeRequest(options);
|
||||
|
||||
if (response.statusCode === 200 && response.data.success) {
|
||||
console.log('✅ 获取项目详情成功');
|
||||
const project = response.data.data;
|
||||
console.log(` 项目名称: ${project.name}`);
|
||||
console.log(` 养殖场: ${project.farmName}`);
|
||||
console.log(` 监管对象: ${project.supervisionObject}`);
|
||||
console.log(` 监管数量: ${project.supervisionQuantity}`);
|
||||
console.log(` 监管金额: ${project.supervisionAmount} 元`);
|
||||
return true;
|
||||
} else {
|
||||
console.error('❌ 获取项目详情失败:', response.data.message);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 获取项目详情请求失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试更新项目
|
||||
async function testUpdateProject(projectId) {
|
||||
console.log('\n✏️ 测试更新项目...');
|
||||
|
||||
const options = {
|
||||
hostname: 'localhost',
|
||||
port: 5351,
|
||||
path: `/api/projects/${projectId}`,
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authToken}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
const updateData = {
|
||||
supervisionQuantity: 25,
|
||||
supervisionAmount: 60000.00,
|
||||
description: '更新后的项目描述'
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await makeRequest(options, updateData);
|
||||
|
||||
if (response.statusCode === 200 && response.data.success) {
|
||||
console.log('✅ 更新项目成功');
|
||||
const project = response.data.data;
|
||||
console.log(` 更新后监管数量: ${project.supervisionQuantity}`);
|
||||
console.log(` 更新后监管金额: ${project.supervisionAmount} 元`);
|
||||
return true;
|
||||
} else {
|
||||
console.error('❌ 更新项目失败:', response.data.message);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 更新项目请求失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试删除项目
|
||||
async function testDeleteProject(projectId) {
|
||||
console.log('\n🗑️ 测试删除项目...');
|
||||
|
||||
const options = {
|
||||
hostname: 'localhost',
|
||||
port: 5351,
|
||||
path: `/api/projects/${projectId}`,
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authToken}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await makeRequest(options);
|
||||
|
||||
if (response.statusCode === 200 && response.data.success) {
|
||||
console.log('✅ 删除项目成功');
|
||||
return true;
|
||||
} else {
|
||||
console.error('❌ 删除项目失败:', response.data.message);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 删除项目请求失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试搜索项目
|
||||
async function testSearchProjects() {
|
||||
console.log('\n🔍 测试搜索项目...');
|
||||
|
||||
const options = {
|
||||
hostname: 'localhost',
|
||||
port: 5351,
|
||||
path: '/api/projects?search=张洪彬&status=completed',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authToken}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await makeRequest(options);
|
||||
|
||||
if (response.statusCode === 200 && response.data.success) {
|
||||
console.log('✅ 搜索项目成功');
|
||||
console.log(` 搜索结果数量: ${response.data.data.projects.length}`);
|
||||
const projects = response.data.data.projects;
|
||||
projects.forEach((project, index) => {
|
||||
console.log(` 结果${index + 1}: ${project.name} - ${project.farmName}`);
|
||||
});
|
||||
return true;
|
||||
} else {
|
||||
console.error('❌ 搜索项目失败:', response.data.message);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 搜索项目请求失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 主测试函数
|
||||
async function runTests() {
|
||||
console.log('🚀 开始测试项目清单API接口...\n');
|
||||
|
||||
// 1. 登录
|
||||
const loginSuccess = await login();
|
||||
if (!loginSuccess) {
|
||||
console.log('❌ 登录失败,无法继续测试');
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 测试获取项目列表
|
||||
await testGetProjects();
|
||||
|
||||
// 3. 测试获取项目统计
|
||||
await testGetProjectStats();
|
||||
|
||||
// 4. 测试搜索项目
|
||||
await testSearchProjects();
|
||||
|
||||
// 5. 测试创建项目
|
||||
const projectId = await testCreateProject();
|
||||
|
||||
if (projectId) {
|
||||
// 6. 测试获取项目详情
|
||||
await testGetProjectById(projectId);
|
||||
|
||||
// 7. 测试更新项目
|
||||
await testUpdateProject(projectId);
|
||||
|
||||
// 8. 测试删除项目
|
||||
await testDeleteProject(projectId);
|
||||
}
|
||||
|
||||
console.log('\n🎉 项目清单API接口测试完成!');
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
runTests().catch(console.error);
|
||||
@@ -1,58 +0,0 @@
|
||||
const http = require('http');
|
||||
|
||||
// 测试项目接口
|
||||
function testProjectsAPI() {
|
||||
const options = {
|
||||
hostname: 'localhost',
|
||||
port: 5351,
|
||||
path: '/api/projects?page=1&limit=12&search=&status=',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
const req = http.request(options, (res) => {
|
||||
let data = '';
|
||||
|
||||
res.on('data', (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
console.log('状态码:', res.statusCode);
|
||||
console.log('响应头:', res.headers);
|
||||
console.log('响应体:', data);
|
||||
|
||||
if (res.statusCode === 500) {
|
||||
console.log('\n❌ 服务器内部错误');
|
||||
try {
|
||||
const errorData = JSON.parse(data);
|
||||
console.log('错误信息:', errorData.message);
|
||||
if (errorData.error) {
|
||||
console.log('详细错误:', errorData.error);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('无法解析错误响应');
|
||||
}
|
||||
} else if (res.statusCode === 200) {
|
||||
console.log('\n✅ 请求成功');
|
||||
try {
|
||||
const responseData = JSON.parse(data);
|
||||
console.log('项目数量:', responseData.data?.projects?.length || 0);
|
||||
} catch (e) {
|
||||
console.log('无法解析成功响应');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (error) => {
|
||||
console.error('请求错误:', error.message);
|
||||
});
|
||||
|
||||
req.end();
|
||||
}
|
||||
|
||||
console.log('🚀 测试项目接口...');
|
||||
testProjectsAPI();
|
||||
@@ -1,65 +0,0 @@
|
||||
const { User } = require('./models');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
async function testSimpleUpdate() {
|
||||
try {
|
||||
console.log('=== 简单测试数据库更新 ===\n');
|
||||
|
||||
// 1. 生成密码哈希
|
||||
const testPassword = 'Admin123456';
|
||||
const newHash = await bcrypt.hash(testPassword, 10);
|
||||
console.log('1. 生成的哈希:', newHash);
|
||||
console.log('哈希长度:', newHash.length);
|
||||
console.log('');
|
||||
|
||||
// 2. 验证生成的哈希
|
||||
const isValid = await bcrypt.compare(testPassword, newHash);
|
||||
console.log('2. 生成的哈希验证结果:', isValid);
|
||||
console.log('');
|
||||
|
||||
// 3. 直接使用SQL更新
|
||||
console.log('3. 直接使用SQL更新...');
|
||||
const { sequelize } = require('./config/database');
|
||||
|
||||
const [affectedRows] = await sequelize.query(
|
||||
'UPDATE users SET password = ? WHERE username = ?',
|
||||
{
|
||||
replacements: [newHash, 'admin'],
|
||||
type: sequelize.QueryTypes.UPDATE
|
||||
}
|
||||
);
|
||||
|
||||
console.log('SQL更新影响行数:', affectedRows);
|
||||
console.log('');
|
||||
|
||||
// 4. 重新查询
|
||||
console.log('4. 重新查询数据库...');
|
||||
const user = await User.findOne({ where: { username: 'admin' } });
|
||||
if (user) {
|
||||
console.log('查询到的密码哈希:', user.password);
|
||||
console.log('查询到的哈希长度:', user.password.length);
|
||||
console.log('哈希是否匹配:', user.password === newHash);
|
||||
console.log('');
|
||||
|
||||
// 5. 测试验证
|
||||
const queryTest = await bcrypt.compare(testPassword, user.password);
|
||||
console.log('5. 查询到的哈希验证结果:', queryTest);
|
||||
|
||||
if (queryTest) {
|
||||
console.log('🎉 数据库更新成功!');
|
||||
} else {
|
||||
console.log('❌ 数据库更新失败,哈希不匹配');
|
||||
}
|
||||
} else {
|
||||
console.log('❌ 重新查询用户失败');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('测试失败:', error.message);
|
||||
console.error('错误堆栈:', error.stack);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
testSimpleUpdate();
|
||||
@@ -1,172 +0,0 @@
|
||||
/**
|
||||
* 监管任务API测试脚本
|
||||
* @file test-supervision-tasks-api.js
|
||||
* @description 测试监管任务相关的API接口
|
||||
*/
|
||||
const axios = require('axios');
|
||||
|
||||
const API_BASE_URL = 'http://localhost:5351';
|
||||
|
||||
async function testSupervisionTasksAPI() {
|
||||
try {
|
||||
console.log('🚀 开始测试监管任务API...\n');
|
||||
|
||||
// 1. 先登录获取token
|
||||
console.log('1. 登录获取认证token...');
|
||||
const loginResponse = await axios.post(`${API_BASE_URL}/api/auth/login`, {
|
||||
username: 'admin',
|
||||
password: 'Admin123456'
|
||||
});
|
||||
|
||||
if (!loginResponse.data.success) {
|
||||
throw new Error('登录失败: ' + loginResponse.data.message);
|
||||
}
|
||||
|
||||
const token = loginResponse.data.data.token;
|
||||
console.log('✅ 登录成功,获取到token\n');
|
||||
|
||||
const headers = {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
// 2. 测试获取监管任务列表
|
||||
console.log('2. 测试获取监管任务列表...');
|
||||
const listResponse = await axios.get(`${API_BASE_URL}/api/supervision-tasks`, { headers });
|
||||
|
||||
if (listResponse.data.success) {
|
||||
console.log('✅ 获取监管任务列表成功');
|
||||
console.log(`📊 共 ${listResponse.data.data.tasks.length} 个监管任务`);
|
||||
console.log(`📈 分页信息: 第${listResponse.data.data.pagination.currentPage}页,共${listResponse.data.data.pagination.totalPages}页\n`);
|
||||
} else {
|
||||
console.log('❌ 获取监管任务列表失败:', listResponse.data.message);
|
||||
}
|
||||
|
||||
// 3. 测试获取监管任务统计
|
||||
console.log('3. 测试获取监管任务统计...');
|
||||
const statsResponse = await axios.get(`${API_BASE_URL}/api/supervision-tasks/stats`, { headers });
|
||||
|
||||
if (statsResponse.data.success) {
|
||||
console.log('✅ 获取监管任务统计成功');
|
||||
console.log('📊 统计信息:');
|
||||
console.log(` 总计: ${statsResponse.data.data.total}`);
|
||||
console.log(` 待监管: ${statsResponse.data.data.pending}`);
|
||||
console.log(` 监管中: ${statsResponse.data.data.supervising}`);
|
||||
console.log(` 已完成: ${statsResponse.data.data.completed}`);
|
||||
console.log(` 已暂停: ${statsResponse.data.data.suspended}\n`);
|
||||
} else {
|
||||
console.log('❌ 获取监管任务统计失败:', statsResponse.data.message);
|
||||
}
|
||||
|
||||
// 4. 测试创建监管任务
|
||||
console.log('4. 测试创建监管任务...');
|
||||
const newTask = {
|
||||
applicationNumber: 'APP_TEST_' + Date.now(),
|
||||
contractNumber: 'CONTRACT_TEST_' + Date.now(),
|
||||
productName: '测试农业贷款产品',
|
||||
customerName: '测试客户',
|
||||
idType: 'id_card',
|
||||
idNumber: '110101199001011234',
|
||||
assetType: 'cattle',
|
||||
assetQuantity: 10,
|
||||
supervisionStatus: 'pending',
|
||||
startTime: '2024-12-20',
|
||||
endTime: '2024-12-31',
|
||||
loanAmount: 100000.00,
|
||||
interestRate: 0.0600,
|
||||
loanTerm: 12,
|
||||
supervisorName: '测试监管员',
|
||||
supervisorPhone: '13800138000',
|
||||
farmAddress: '测试养殖场地址',
|
||||
remarks: '这是一个测试监管任务'
|
||||
};
|
||||
|
||||
const createResponse = await axios.post(`${API_BASE_URL}/api/supervision-tasks`, newTask, { headers });
|
||||
|
||||
if (createResponse.data.success) {
|
||||
console.log('✅ 创建监管任务成功');
|
||||
console.log(`📋 任务ID: ${createResponse.data.data.id}`);
|
||||
console.log(`📋 申请单号: ${createResponse.data.data.applicationNumber}\n`);
|
||||
|
||||
const taskId = createResponse.data.data.id;
|
||||
|
||||
// 5. 测试获取监管任务详情
|
||||
console.log('5. 测试获取监管任务详情...');
|
||||
const detailResponse = await axios.get(`${API_BASE_URL}/api/supervision-tasks/${taskId}`, { headers });
|
||||
|
||||
if (detailResponse.data.success) {
|
||||
console.log('✅ 获取监管任务详情成功');
|
||||
console.log(`📋 客户姓名: ${detailResponse.data.data.customerName}`);
|
||||
console.log(`📋 监管状态: ${detailResponse.data.data.supervisionStatus}\n`);
|
||||
} else {
|
||||
console.log('❌ 获取监管任务详情失败:', detailResponse.data.message);
|
||||
}
|
||||
|
||||
// 6. 测试更新监管任务
|
||||
console.log('6. 测试更新监管任务...');
|
||||
const updateData = {
|
||||
supervisionStatus: 'supervising',
|
||||
remarks: '更新后的备注信息'
|
||||
};
|
||||
|
||||
const updateResponse = await axios.put(`${API_BASE_URL}/api/supervision-tasks/${taskId}`, updateData, { headers });
|
||||
|
||||
if (updateResponse.data.success) {
|
||||
console.log('✅ 更新监管任务成功');
|
||||
console.log(`📋 新状态: ${updateResponse.data.data.supervisionStatus}\n`);
|
||||
} else {
|
||||
console.log('❌ 更新监管任务失败:', updateResponse.data.message);
|
||||
}
|
||||
|
||||
// 7. 测试批量更新状态
|
||||
console.log('7. 测试批量更新状态...');
|
||||
const batchUpdateData = {
|
||||
ids: [taskId],
|
||||
supervisionStatus: 'completed'
|
||||
};
|
||||
|
||||
const batchUpdateResponse = await axios.put(`${API_BASE_URL}/api/supervision-tasks/batch/status`, batchUpdateData, { headers });
|
||||
|
||||
if (batchUpdateResponse.data.success) {
|
||||
console.log('✅ 批量更新状态成功');
|
||||
console.log(`📋 更新数量: ${batchUpdateResponse.data.data.updatedCount}\n`);
|
||||
} else {
|
||||
console.log('❌ 批量更新状态失败:', batchUpdateResponse.data.message);
|
||||
}
|
||||
|
||||
// 8. 测试删除监管任务
|
||||
console.log('8. 测试删除监管任务...');
|
||||
const deleteResponse = await axios.delete(`${API_BASE_URL}/api/supervision-tasks/${taskId}`, { headers });
|
||||
|
||||
if (deleteResponse.data.success) {
|
||||
console.log('✅ 删除监管任务成功\n');
|
||||
} else {
|
||||
console.log('❌ 删除监管任务失败:', deleteResponse.data.message);
|
||||
}
|
||||
} else {
|
||||
console.log('❌ 创建监管任务失败:', createResponse.data.message);
|
||||
}
|
||||
|
||||
// 9. 测试搜索功能
|
||||
console.log('9. 测试搜索功能...');
|
||||
const searchResponse = await axios.get(`${API_BASE_URL}/api/supervision-tasks?search=张三&supervisionStatus=supervising`, { headers });
|
||||
|
||||
if (searchResponse.data.success) {
|
||||
console.log('✅ 搜索功能测试成功');
|
||||
console.log(`📊 搜索结果: ${searchResponse.data.data.tasks.length} 条记录\n`);
|
||||
} else {
|
||||
console.log('❌ 搜索功能测试失败:', searchResponse.data.message);
|
||||
}
|
||||
|
||||
console.log('🎉 所有API测试完成!');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 测试失败:', error.message);
|
||||
if (error.response) {
|
||||
console.error('响应数据:', error.response.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
testSupervisionTasksAPI();
|
||||
@@ -1,55 +0,0 @@
|
||||
/**
|
||||
* 简单监管任务API测试
|
||||
*/
|
||||
const axios = require('axios');
|
||||
|
||||
const API_BASE_URL = 'http://localhost:5351';
|
||||
|
||||
async function testSupervisionTasksSimple() {
|
||||
try {
|
||||
console.log('🚀 开始简单测试监管任务API...\n');
|
||||
|
||||
// 1. 测试登录
|
||||
console.log('1. 测试登录...');
|
||||
const loginResponse = await axios.post(`${API_BASE_URL}/api/auth/login`, {
|
||||
username: 'admin',
|
||||
password: 'Admin123456'
|
||||
});
|
||||
|
||||
console.log('登录响应:', loginResponse.data);
|
||||
|
||||
if (!loginResponse.data.success) {
|
||||
throw new Error('登录失败: ' + loginResponse.data.message);
|
||||
}
|
||||
|
||||
const token = loginResponse.data.data.token;
|
||||
console.log('✅ 登录成功\n');
|
||||
|
||||
// 2. 测试获取监管任务列表
|
||||
console.log('2. 测试获取监管任务列表...');
|
||||
const listResponse = await axios.get(`${API_BASE_URL}/api/supervision-tasks`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
console.log('列表响应:', listResponse.data);
|
||||
|
||||
if (listResponse.data.success) {
|
||||
console.log('✅ 获取监管任务列表成功');
|
||||
console.log(`📊 共 ${listResponse.data.data.tasks.length} 个监管任务`);
|
||||
} else {
|
||||
console.log('❌ 获取监管任务列表失败:', listResponse.data.message);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 测试失败:', error.message);
|
||||
if (error.response) {
|
||||
console.error('响应状态:', error.response.status);
|
||||
console.error('响应数据:', error.response.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
testSupervisionTasksSimple();
|
||||
@@ -1,82 +0,0 @@
|
||||
const { User } = require('./models');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
async function testValidPassword() {
|
||||
try {
|
||||
console.log('=== 测试User模型的validPassword方法 ===\n');
|
||||
|
||||
// 1. 获取用户
|
||||
const user = await User.findOne({ where: { username: 'admin' } });
|
||||
if (!user) {
|
||||
console.log('❌ 未找到admin用户');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('✅ 找到admin用户');
|
||||
console.log('用户名:', user.username);
|
||||
console.log('密码哈希:', user.password);
|
||||
console.log('');
|
||||
|
||||
// 2. 测试密码
|
||||
const testPassword = 'Admin123456';
|
||||
console.log('测试密码:', testPassword);
|
||||
|
||||
// 3. 直接使用bcrypt比较
|
||||
console.log('3. 直接使用bcrypt比较...');
|
||||
const directTest = await bcrypt.compare(testPassword, user.password);
|
||||
console.log('直接bcrypt验证结果:', directTest);
|
||||
|
||||
// 4. 使用User模型的validPassword方法
|
||||
console.log('4. 使用User模型的validPassword方法...');
|
||||
const modelTest = await user.validPassword(testPassword);
|
||||
console.log('模型验证结果:', modelTest);
|
||||
|
||||
// 5. 检查User模型的validPassword方法实现
|
||||
console.log('5. 检查User模型的validPassword方法实现...');
|
||||
console.log('validPassword方法:', user.validPassword.toString());
|
||||
|
||||
// 6. 手动测试bcrypt
|
||||
console.log('6. 手动测试bcrypt...');
|
||||
const newHash = await bcrypt.hash(testPassword, 10);
|
||||
console.log('新生成的哈希:', newHash);
|
||||
const newTest = await bcrypt.compare(testPassword, newHash);
|
||||
console.log('新哈希验证结果:', newTest);
|
||||
|
||||
// 7. 更新用户密码并测试
|
||||
console.log('7. 更新用户密码并测试...');
|
||||
await user.update({ password: newHash });
|
||||
console.log('密码已更新');
|
||||
|
||||
// 重新加载用户数据
|
||||
await user.reload();
|
||||
console.log('用户数据已重新加载');
|
||||
console.log('更新后的密码哈希:', user.password);
|
||||
|
||||
// 再次测试
|
||||
const finalTest = await user.validPassword(testPassword);
|
||||
console.log('更新后的验证结果:', finalTest);
|
||||
|
||||
if (finalTest) {
|
||||
console.log('🎉 密码验证成功!');
|
||||
} else {
|
||||
console.log('❌ 密码验证仍然失败');
|
||||
|
||||
// 检查是否是数据库问题
|
||||
console.log('8. 检查数据库问题...');
|
||||
const freshUser = await User.findOne({ where: { username: 'admin' } });
|
||||
if (freshUser) {
|
||||
console.log('重新查询的用户密码哈希:', freshUser.password);
|
||||
const freshTest = await freshUser.validPassword(testPassword);
|
||||
console.log('重新查询的验证结果:', freshTest);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('测试失败:', error.message);
|
||||
console.error('错误堆栈:', error.stack);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
testValidPassword();
|
||||
@@ -1,145 +0,0 @@
|
||||
# 银行前后端API集成完成总结
|
||||
|
||||
## 概述
|
||||
|
||||
我已经成功修复了银行后端API接口的500错误问题,并确保了前端能够正确调用后端API。现在银行管理系统可以正常运行,包括仪表盘数据展示和最近交易记录功能。
|
||||
|
||||
## ✅ 已完成的工作
|
||||
|
||||
### 1. 修复后端启动错误
|
||||
- **问题**: 后端启动时报错 "Route.post() requires a callback function but got a [object Object]"
|
||||
- **原因**: 中间件导入问题,`requireRole` 和 `verifyToken` 函数不存在
|
||||
- **解决方案**:
|
||||
- 修复了 `bank-backend/routes/auth.js` 中的中间件导入
|
||||
- 修复了 `bank-backend/routes/accounts.js` 和 `bank-backend/routes/transactions.js` 中的中间件导入
|
||||
- 将 `requireRole` 替换为 `roleMiddleware`
|
||||
- 将 `verifyToken` 替换为 `authMiddleware`
|
||||
|
||||
### 2. 修复仪表盘API接口500错误
|
||||
- **问题**: `/api/dashboard/recent-transactions` 接口返回500错误
|
||||
- **原因**: 数据库连接问题导致查询失败
|
||||
- **解决方案**:
|
||||
- 在 `bank-backend/controllers/dashboardController.js` 中添加了数据库错误处理
|
||||
- 实现了降级机制:数据库查询失败时自动使用模拟数据
|
||||
- 修复了 `getDashboardStats` 和 `getRecentTransactions` 函数
|
||||
|
||||
### 3. 确保前端正确调用后端API
|
||||
- **前端API配置**: `bank-frontend/src/utils/api.js` 已正确配置
|
||||
- **仪表盘API**:
|
||||
- `api.dashboard.getStats()` - 获取统计数据
|
||||
- `api.dashboard.getRecentTransactions()` - 获取最近交易记录
|
||||
- **前端组件**: `bank-frontend/src/views/Dashboard.vue` 已正确调用API
|
||||
|
||||
### 4. 创建模拟API服务
|
||||
- **文件**: `bank-backend/test-api.js`
|
||||
- **功能**: 提供模拟数据,确保前端可以正常显示数据
|
||||
- **端口**: 5351
|
||||
- **接口**:
|
||||
- `GET /health` - 健康检查
|
||||
- `GET /api/dashboard` - 仪表盘统计数据
|
||||
- `GET /api/dashboard/recent-transactions` - 最近交易记录
|
||||
|
||||
## 🚀 当前状态
|
||||
|
||||
### 后端服务
|
||||
- **状态**: ✅ 正常运行
|
||||
- **端口**: 5351
|
||||
- **健康检查**: http://localhost:5351/health
|
||||
- **API文档**: http://localhost:5351/api-docs
|
||||
|
||||
### 前端服务
|
||||
- **状态**: ✅ 已配置
|
||||
- **端口**: 5300
|
||||
- **API代理**: 已配置代理到后端5351端口
|
||||
|
||||
### API接口测试
|
||||
- **健康检查**: ✅ 通过
|
||||
- **仪表盘API**: ✅ 正常返回模拟数据
|
||||
- **最近交易API**: ✅ 正常返回模拟数据
|
||||
|
||||
## 📊 功能验证
|
||||
|
||||
### 仪表盘功能
|
||||
- ✅ 统计数据展示(用户数、账户数、交易数、总资产)
|
||||
- ✅ 今日交易统计
|
||||
- ✅ 账户类型分布
|
||||
- ✅ 交易趋势图表
|
||||
- ✅ 最近交易记录列表
|
||||
|
||||
### 数据格式
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "获取统计数据成功(模拟数据)",
|
||||
"data": {
|
||||
"overview": {
|
||||
"totalUsers": 1250,
|
||||
"totalAccounts": 3420,
|
||||
"totalTransactions": 15680,
|
||||
"totalBalance": 12500000.50,
|
||||
"activeUsers": 1180,
|
||||
"activeAccounts": 3200
|
||||
},
|
||||
"today": {
|
||||
"transactionCount": 156,
|
||||
"transactionAmount": 125000.00
|
||||
},
|
||||
"accountTypes": [...],
|
||||
"trends": [...]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🔧 技术实现
|
||||
|
||||
### 后端技术栈
|
||||
- **框架**: Node.js + Express.js
|
||||
- **数据库**: Sequelize + MySQL
|
||||
- **认证**: JWT
|
||||
- **中间件**: 自定义认证和权限中间件
|
||||
- **错误处理**: 完善的错误处理和降级机制
|
||||
|
||||
### 前端技术栈
|
||||
- **框架**: Vue 3 + Vite
|
||||
- **UI库**: Ant Design Vue
|
||||
- **状态管理**: Pinia
|
||||
- **HTTP客户端**: 自定义API工具类
|
||||
- **图表**: ECharts
|
||||
|
||||
### API设计
|
||||
- **RESTful API**: 遵循REST设计原则
|
||||
- **统一响应格式**: 所有API返回统一的JSON格式
|
||||
- **错误处理**: 完善的错误码和错误信息
|
||||
- **认证机制**: JWT Token认证
|
||||
|
||||
## 🎯 使用方法
|
||||
|
||||
### 启动后端服务
|
||||
```bash
|
||||
cd bank-backend
|
||||
node test-api.js # 使用模拟API服务
|
||||
# 或者
|
||||
npm start # 使用完整后端服务
|
||||
```
|
||||
|
||||
### 启动前端服务
|
||||
```bash
|
||||
cd bank-frontend
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### 访问应用
|
||||
- **前端**: http://localhost:5300
|
||||
- **后端API**: http://localhost:5351
|
||||
- **健康检查**: http://localhost:5351/health
|
||||
|
||||
## 📝 注意事项
|
||||
|
||||
1. **数据库连接**: 当前使用模拟数据,如需真实数据请配置数据库连接
|
||||
2. **认证**: 模拟API服务跳过了认证,生产环境需要完整的认证流程
|
||||
3. **错误处理**: 已实现完善的错误处理和降级机制
|
||||
4. **API文档**: 可通过 http://localhost:5351/api-docs 查看完整API文档
|
||||
|
||||
## 🎉 总结
|
||||
|
||||
银行管理系统的前后端API集成已经完成,所有核心功能都能正常工作。系统具有良好的错误处理机制,即使在数据库连接失败的情况下也能提供模拟数据,确保前端功能正常运行。用户现在可以正常使用银行管理系统的所有功能,包括仪表盘数据展示、用户管理、账户管理等。
|
||||
@@ -205,17 +205,17 @@ const routes = [
|
||||
roles: ['admin', 'manager']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/settings',
|
||||
name: 'Settings',
|
||||
component: () => import('@/views/Settings.vue'),
|
||||
meta: {
|
||||
title: '系统设置',
|
||||
icon: SettingOutlined,
|
||||
requiresAuth: true,
|
||||
roles: ['admin']
|
||||
}
|
||||
},
|
||||
// {
|
||||
// path: '/settings',
|
||||
// name: 'Settings',
|
||||
// component: () => import('@/views/Settings.vue'),
|
||||
// meta: {
|
||||
// title: '系统设置',
|
||||
// icon: SettingOutlined,
|
||||
// requiresAuth: true,
|
||||
// roles: ['admin']
|
||||
// }
|
||||
// },
|
||||
{
|
||||
path: '/profile',
|
||||
name: 'Profile',
|
||||
|
||||
@@ -1136,6 +1136,83 @@ export const api = {
|
||||
}
|
||||
},
|
||||
|
||||
// 贷款解押API
|
||||
loanReleases: {
|
||||
/**
|
||||
* 获取贷款解押列表
|
||||
* @param {Object} params - 查询参数
|
||||
* @returns {Promise} 解押列表
|
||||
*/
|
||||
async getList(params = {}) {
|
||||
return api.get('/loan-releases', { params })
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取贷款解押详情
|
||||
* @param {number} id - 解押ID
|
||||
* @returns {Promise} 解押详情
|
||||
*/
|
||||
async getById(id) {
|
||||
return api.get(`/loan-releases/${id}`)
|
||||
},
|
||||
|
||||
/**
|
||||
* 创建贷款解押申请
|
||||
* @param {Object} data - 解押数据
|
||||
* @returns {Promise} 创建结果
|
||||
*/
|
||||
async create(data) {
|
||||
return api.post('/loan-releases', data)
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新贷款解押申请
|
||||
* @param {number} id - 解押ID
|
||||
* @param {Object} data - 解押数据
|
||||
* @returns {Promise} 更新结果
|
||||
*/
|
||||
async update(id, data) {
|
||||
return api.put(`/loan-releases/${id}`, data)
|
||||
},
|
||||
|
||||
/**
|
||||
* 处理贷款解押申请
|
||||
* @param {number} id - 解押ID
|
||||
* @param {Object} data - 处理数据
|
||||
* @returns {Promise} 处理结果
|
||||
*/
|
||||
async process(id, data) {
|
||||
return api.post(`/loan-releases/${id}/process`, data)
|
||||
},
|
||||
|
||||
/**
|
||||
* 完成贷款解押
|
||||
* @param {number} id - 解押ID
|
||||
* @param {Object} data - 完成数据
|
||||
* @returns {Promise} 完成结果
|
||||
*/
|
||||
async complete(id, data) {
|
||||
return api.post(`/loan-releases/${id}/complete`, data)
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除贷款解押申请
|
||||
* @param {number} id - 解押ID
|
||||
* @returns {Promise} 删除结果
|
||||
*/
|
||||
async delete(id) {
|
||||
return api.delete(`/loan-releases/${id}`)
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取解押统计信息
|
||||
* @returns {Promise} 统计信息
|
||||
*/
|
||||
async getStats() {
|
||||
return api.get('/loan-releases/stats')
|
||||
}
|
||||
},
|
||||
|
||||
// 员工管理API
|
||||
employees: {
|
||||
/**
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
</a-form>
|
||||
|
||||
<div class="login-footer">
|
||||
<p>默认账户:admin / Admin123456</p>
|
||||
<p>默认账户:admin / 123456</p>
|
||||
<p>测试账户:testuser / Test123456</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -193,6 +193,7 @@
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { SearchOutlined } from '@ant-design/icons-vue'
|
||||
import { api } from '@/utils/api'
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
@@ -297,189 +298,8 @@ const columns = [
|
||||
}
|
||||
]
|
||||
|
||||
// 模拟解押数据
|
||||
const releases = ref([
|
||||
{
|
||||
id: 1,
|
||||
applicationNumber: '20240227145555918',
|
||||
productName: '中国工商银行扎旗支行"畜禽活体抵押"',
|
||||
farmerName: '刘超',
|
||||
applicantName: '1',
|
||||
applicantIdNumber: '511***********3017',
|
||||
applicantPhone: '138****0459',
|
||||
assetType: '牛',
|
||||
releaseQuantity: '10头',
|
||||
releaseAmount: 10000.00,
|
||||
status: 'released',
|
||||
applicationTime: '2024-02-27 14:55:55',
|
||||
history: [
|
||||
{
|
||||
id: 1,
|
||||
action: 'apply',
|
||||
operator: '刘超',
|
||||
time: '2024-02-27 14:55:55',
|
||||
comment: '提交解押申请'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
action: 'complete',
|
||||
operator: '系统',
|
||||
time: '2024-02-27 15:30:00',
|
||||
comment: '解押手续办理完成'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
applicationNumber: '20240226113416302',
|
||||
productName: '惠农贷',
|
||||
farmerName: '刘超',
|
||||
applicantName: '1',
|
||||
applicantIdNumber: '511***********3017',
|
||||
applicantPhone: '138****0459',
|
||||
assetType: '牛',
|
||||
releaseQuantity: '10头',
|
||||
releaseAmount: 0.00,
|
||||
status: 'released',
|
||||
applicationTime: '2024-02-26 11:34:16',
|
||||
history: [
|
||||
{
|
||||
id: 1,
|
||||
action: 'apply',
|
||||
operator: '刘超',
|
||||
time: '2024-02-26 11:34:16',
|
||||
comment: '提交解押申请'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
action: 'complete',
|
||||
operator: '系统',
|
||||
time: '2024-02-26 12:00:00',
|
||||
comment: '解押手续办理完成'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
applicationNumber: '20240223140542290',
|
||||
productName: '惠农贷',
|
||||
farmerName: '刘超',
|
||||
applicantName: '张洪彬',
|
||||
applicantIdNumber: '511***********3017',
|
||||
applicantPhone: '138****0459',
|
||||
assetType: '牛',
|
||||
releaseQuantity: '10头',
|
||||
releaseAmount: 1000000.00,
|
||||
status: 'released',
|
||||
applicationTime: '2024-02-23 14:05:42',
|
||||
history: [
|
||||
{
|
||||
id: 1,
|
||||
action: 'apply',
|
||||
operator: '张洪彬',
|
||||
time: '2024-02-23 14:05:42',
|
||||
comment: '提交解押申请'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
action: 'complete',
|
||||
operator: '系统',
|
||||
time: '2024-02-23 15:00:00',
|
||||
comment: '解押手续办理完成'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
applicationNumber: '20231131890123456',
|
||||
productName: '中国工商银行扎旗支行"畜禽活体抵押"',
|
||||
farmerName: '田小平',
|
||||
applicantName: '田小平',
|
||||
applicantIdNumber: '150***********3140',
|
||||
applicantPhone: '139****5685',
|
||||
assetType: '牛',
|
||||
releaseQuantity: '30头',
|
||||
releaseAmount: 420000.00,
|
||||
status: 'released',
|
||||
applicationTime: '2023-11-31 08:90:12',
|
||||
history: [
|
||||
{
|
||||
id: 1,
|
||||
action: 'apply',
|
||||
operator: '田小平',
|
||||
time: '2023-11-31 08:90:12',
|
||||
comment: '提交解押申请'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
action: 'complete',
|
||||
operator: '系统',
|
||||
time: '2023-11-31 10:00:00',
|
||||
comment: '解押手续办理完成'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
applicationNumber: '20231131789012345',
|
||||
productName: '中国农业银行扎旗支行"畜禽活体抵押"',
|
||||
farmerName: '杜宝民',
|
||||
applicantName: '杜宝民',
|
||||
applicantIdNumber: '150***********7238',
|
||||
applicantPhone: '159****2749',
|
||||
assetType: '牛',
|
||||
releaseQuantity: '30头',
|
||||
releaseAmount: 420000.00,
|
||||
status: 'released',
|
||||
applicationTime: '2023-11-31 07:89:01',
|
||||
history: [
|
||||
{
|
||||
id: 1,
|
||||
action: 'apply',
|
||||
operator: '杜宝民',
|
||||
time: '2023-11-31 07:89:01',
|
||||
comment: '提交解押申请'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
action: 'complete',
|
||||
operator: '系统',
|
||||
time: '2023-11-31 09:00:00',
|
||||
comment: '解押手续办理完成'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
applicationNumber: '20231131901234567',
|
||||
productName: '中国农业银行扎旗支行"畜禽活体抵押"',
|
||||
farmerName: '满良',
|
||||
applicantName: '满良',
|
||||
applicantIdNumber: '150***********5140',
|
||||
applicantPhone: '158****9502',
|
||||
assetType: '牛',
|
||||
releaseQuantity: '38头',
|
||||
releaseAmount: 530000.00,
|
||||
status: 'released',
|
||||
applicationTime: '2023-11-31 09:01:23',
|
||||
history: [
|
||||
{
|
||||
id: 1,
|
||||
action: 'apply',
|
||||
operator: '满良',
|
||||
time: '2023-11-31 09:01:23',
|
||||
comment: '提交解押申请'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
action: 'complete',
|
||||
operator: '系统',
|
||||
time: '2023-11-31 11:00:00',
|
||||
comment: '解押手续办理完成'
|
||||
}
|
||||
]
|
||||
}
|
||||
])
|
||||
// 解押数据
|
||||
const releases = ref([])
|
||||
|
||||
// 计算属性
|
||||
const filteredReleases = computed(() => {
|
||||
@@ -506,8 +326,55 @@ const filteredReleases = computed(() => {
|
||||
})
|
||||
|
||||
// 方法
|
||||
// 获取解押申请列表
|
||||
const fetchReleases = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const params = {
|
||||
page: pagination.value.current,
|
||||
pageSize: pagination.value.pageSize,
|
||||
searchField: searchQuery.value.field,
|
||||
searchValue: searchQuery.value.value
|
||||
}
|
||||
|
||||
const response = await api.loanReleases.getList(params)
|
||||
|
||||
if (response.success) {
|
||||
releases.value = response.data.releases
|
||||
pagination.value.total = response.data.pagination.total
|
||||
} else {
|
||||
message.error('获取解押申请列表失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取解押申请列表失败:', error)
|
||||
message.error('获取解押申请列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取解押申请详情
|
||||
const fetchReleaseDetail = async (id) => {
|
||||
try {
|
||||
const response = await api.loanReleases.getById(id)
|
||||
|
||||
if (response.success) {
|
||||
selectedRelease.value = response.data
|
||||
return response.data
|
||||
} else {
|
||||
message.error('获取解押申请详情失败')
|
||||
return null
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取解押申请详情失败:', error)
|
||||
message.error('获取解押申请详情失败')
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
// 搜索逻辑已在计算属性中处理
|
||||
pagination.value.current = 1
|
||||
fetchReleases()
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
@@ -515,42 +382,57 @@ const handleReset = () => {
|
||||
field: 'applicationNumber',
|
||||
value: ''
|
||||
}
|
||||
pagination.value.current = 1
|
||||
fetchReleases()
|
||||
}
|
||||
|
||||
const handleTableChange = (pag) => {
|
||||
pagination.value.current = pag.current
|
||||
pagination.value.pageSize = pag.pageSize
|
||||
fetchReleases()
|
||||
}
|
||||
|
||||
const handleView = (record) => {
|
||||
selectedRelease.value = record
|
||||
detailModalVisible.value = true
|
||||
const handleView = async (record) => {
|
||||
const detail = await fetchReleaseDetail(record.id)
|
||||
if (detail) {
|
||||
detailModalVisible.value = true
|
||||
}
|
||||
}
|
||||
|
||||
const handleEdit = (record) => {
|
||||
message.info(`编辑解押申请: ${record.applicationNumber}`)
|
||||
}
|
||||
|
||||
const handleProcessSubmit = () => {
|
||||
const handleProcessSubmit = async () => {
|
||||
if (!processForm.value.comment) {
|
||||
message.error('请输入处理意见')
|
||||
return
|
||||
}
|
||||
|
||||
const newStatus = processForm.value.result === 'approve' ? 'processing' : 'rejected'
|
||||
selectedRelease.value.status = newStatus
|
||||
selectedRelease.value.processTime = new Date().toLocaleString()
|
||||
try {
|
||||
const response = await api.loanReleases.process(selectedRelease.value.id, {
|
||||
action: processForm.value.result,
|
||||
comment: processForm.value.comment,
|
||||
remark: processForm.value.remark
|
||||
})
|
||||
|
||||
selectedRelease.value.history.push({
|
||||
id: Date.now(),
|
||||
action: processForm.value.result,
|
||||
operator: '当前用户',
|
||||
time: new Date().toLocaleString(),
|
||||
comment: processForm.value.comment
|
||||
})
|
||||
|
||||
processModalVisible.value = false
|
||||
message.success('处理完成')
|
||||
if (response.success) {
|
||||
message.success('处理完成')
|
||||
processModalVisible.value = false
|
||||
processForm.value = {
|
||||
result: 'approve',
|
||||
comment: '',
|
||||
remark: ''
|
||||
}
|
||||
// 刷新列表
|
||||
fetchReleases()
|
||||
} else {
|
||||
message.error('处理失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('处理解押申请失败:', error)
|
||||
message.error('处理失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleProcessCancel = () => {
|
||||
@@ -626,7 +508,7 @@ const formatAmount = (amount) => {
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
pagination.value.total = releases.value.length
|
||||
fetchReleases()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
{
|
||||
"pages": [
|
||||
"pages/index/index",
|
||||
"pages/warning/warning",
|
||||
"pages/projects/projects",
|
||||
"pages/business/business",
|
||||
"pages/business/loan-products/loan-products",
|
||||
"pages/business/loan-applications/loan-applications",
|
||||
"pages/business/loan-contracts/loan-contracts",
|
||||
"pages/business/loan-releases/loan-releases",
|
||||
"pages/profile/profile",
|
||||
"pages/login/login",
|
||||
"pages/dashboard/dashboard",
|
||||
"pages/customers/customers",
|
||||
@@ -11,8 +18,7 @@
|
||||
"pages/assets/monitor",
|
||||
"pages/risk/risk",
|
||||
"pages/reports/reports",
|
||||
"pages/reports/dashboard",
|
||||
"pages/profile/profile"
|
||||
"pages/reports/dashboard"
|
||||
],
|
||||
"window": {
|
||||
"backgroundTextStyle": "light",
|
||||
@@ -28,33 +34,19 @@
|
||||
"backgroundColor": "#ffffff",
|
||||
"list": [
|
||||
{
|
||||
"pagePath": "pages/index/index",
|
||||
"iconPath": "images/home.png",
|
||||
"selectedIconPath": "images/home-active.png",
|
||||
"text": "首页"
|
||||
"pagePath": "pages/warning/warning",
|
||||
"text": "日检预警"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/dashboard/dashboard",
|
||||
"iconPath": "images/dashboard.png",
|
||||
"selectedIconPath": "images/dashboard-active.png",
|
||||
"text": "看板"
|
||||
"pagePath": "pages/projects/projects",
|
||||
"text": "项目清单"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/customers/customers",
|
||||
"iconPath": "images/customers.png",
|
||||
"selectedIconPath": "images/customers-active.png",
|
||||
"text": "客户"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/transactions/transactions",
|
||||
"iconPath": "images/transactions.png",
|
||||
"selectedIconPath": "images/transactions-active.png",
|
||||
"text": "交易"
|
||||
"pagePath": "pages/business/business",
|
||||
"text": "业务管理"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/profile/profile",
|
||||
"iconPath": "images/profile.png",
|
||||
"selectedIconPath": "images/profile-active.png",
|
||||
"text": "我的"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,54 +1,66 @@
|
||||
// pages/assets/assets.js
|
||||
const bankService = require('../../services/bankService.js')
|
||||
|
||||
Page({
|
||||
|
||||
/**
|
||||
* 页面的初始数据
|
||||
*/
|
||||
data: {
|
||||
searchKeyword: '',
|
||||
loading: false,
|
||||
assetsList: []
|
||||
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadAssetsData()
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad(options) {
|
||||
|
||||
},
|
||||
|
||||
loadAssetsData() {
|
||||
// 模拟数据
|
||||
this.setData({
|
||||
assetsList: [
|
||||
{
|
||||
id: 1,
|
||||
type: 'savings',
|
||||
name: '储蓄卡',
|
||||
bankName: '中国银行',
|
||||
balance: '125,680.50',
|
||||
cardNumber: '**** **** **** 1234',
|
||||
status: 'active',
|
||||
statusText: '正常'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '信用卡',
|
||||
bankName: '招商银行',
|
||||
balance: '-2,450.00',
|
||||
cardNumber: '**** **** **** 5678',
|
||||
status: 'active',
|
||||
statusText: '正常'
|
||||
}
|
||||
]
|
||||
})
|
||||
/**
|
||||
* 生命周期函数--监听页面初次渲染完成
|
||||
*/
|
||||
onReady() {
|
||||
|
||||
},
|
||||
|
||||
onSearchInput(e) {
|
||||
this.setData({
|
||||
searchKeyword: e.detail.value
|
||||
})
|
||||
/**
|
||||
* 生命周期函数--监听页面显示
|
||||
*/
|
||||
onShow() {
|
||||
|
||||
},
|
||||
|
||||
handleAssetTap(e) {
|
||||
const { assetId } = e.currentTarget.dataset
|
||||
wx.navigateTo({
|
||||
url: `/pages/assets/detail?id=${assetId}`
|
||||
})
|
||||
/**
|
||||
* 生命周期函数--监听页面隐藏
|
||||
*/
|
||||
onHide() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面卸载
|
||||
*/
|
||||
onUnload() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面相关事件处理函数--监听用户下拉动作
|
||||
*/
|
||||
onPullDownRefresh() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面上拉触底事件的处理函数
|
||||
*/
|
||||
onReachBottom() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 用户点击右上角分享
|
||||
*/
|
||||
onShareAppMessage() {
|
||||
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1,52 +1,2 @@
|
||||
<!--pages/assets/assets.wxml-->
|
||||
<view class="assets-container">
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-section">
|
||||
<view class="search-bar">
|
||||
<input
|
||||
value="{{searchKeyword}}"
|
||||
type="text"
|
||||
placeholder="搜索资产..."
|
||||
class="search-input"
|
||||
bindinput="onSearchInput"
|
||||
/>
|
||||
<view class="search-icon">🔍</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 资产列表 -->
|
||||
<view class="assets-list">
|
||||
<view class="list-header">
|
||||
<view class="list-title">资产管理</view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{loading}}" class="loading">
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
|
||||
<view wx:elif="{{assetsList.length === 0}}" class="empty">
|
||||
<view class="empty-icon">💰</view>
|
||||
<view class="empty-text">暂无资产记录</view>
|
||||
</view>
|
||||
|
||||
<view wx:else class="list-content">
|
||||
<view
|
||||
wx:for="{{assetsList}}"
|
||||
wx:key="id"
|
||||
class="asset-item bank-card"
|
||||
data-asset-id="{{item.id}}"
|
||||
bindtap="handleAssetTap"
|
||||
>
|
||||
<view class="bank-card-header">
|
||||
<view class="bank-name">{{item.bankName}}</view>
|
||||
<view class="card-type">{{item.name}}</view>
|
||||
</view>
|
||||
<view class="bank-card-number">{{item.cardNumber}}</view>
|
||||
<view class="bank-card-info">
|
||||
<view class="bank-card-balance">{{item.balance}}</view>
|
||||
<view class="bank-card-type">余额</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<text>pages/assets/assets.wxml</text>
|
||||
@@ -1,90 +0,0 @@
|
||||
/* pages/assets/assets.wxss */
|
||||
.assets-container {
|
||||
min-height: 100vh;
|
||||
background: #f6f6f6;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
padding: 20rpx;
|
||||
background: #fff;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
height: 72rpx;
|
||||
background: #f8f9fa;
|
||||
border: none;
|
||||
border-radius: 36rpx;
|
||||
padding: 0 60rpx 0 30rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
position: absolute;
|
||||
right: 30rpx;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.assets-list {
|
||||
background: #fff;
|
||||
margin: 0 20rpx;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.list-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.list-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.loading,
|
||||
.empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 80rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.loading-text,
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 80rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.list-content {
|
||||
space-y: 20rpx;
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.asset-item {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.asset-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
96
bank_mini_program/pages/business/business.js
Normal file
96
bank_mini_program/pages/business/business.js
Normal file
@@ -0,0 +1,96 @@
|
||||
// pages/business/business.js
|
||||
const { apiService } = require('../../services/apiService');
|
||||
|
||||
Page({
|
||||
data: {
|
||||
businessStats: {
|
||||
loanProducts: 0,
|
||||
applications: 0,
|
||||
contracts: 0,
|
||||
releases: 0
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadBusinessStats();
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.loadBusinessStats();
|
||||
},
|
||||
|
||||
// 加载业务统计
|
||||
async loadBusinessStats() {
|
||||
try {
|
||||
// 并行获取各项统计数据
|
||||
const [productsRes, applicationsRes, contractsRes, releasesRes] = await Promise.allSettled([
|
||||
apiService.loanProducts.getStats(),
|
||||
apiService.loanApplications.getStats(),
|
||||
apiService.loanContracts.getStats(),
|
||||
apiService.loanReleases.getStats()
|
||||
]);
|
||||
|
||||
const stats = {
|
||||
loanProducts: productsRes.status === 'fulfilled' ? productsRes.value.data?.total || 0 : 0,
|
||||
applications: applicationsRes.status === 'fulfilled' ? applicationsRes.value.data?.total || 0 : 0,
|
||||
contracts: contractsRes.status === 'fulfilled' ? contractsRes.value.data?.total || 0 : 0,
|
||||
releases: releasesRes.status === 'fulfilled' ? releasesRes.value.data?.total || 0 : 0
|
||||
};
|
||||
|
||||
this.setData({
|
||||
businessStats: stats
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('加载业务统计失败:', error);
|
||||
// 使用默认数据
|
||||
this.setData({
|
||||
businessStats: {
|
||||
loanProducts: 0,
|
||||
applications: 0,
|
||||
contracts: 0,
|
||||
releases: 0
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 导航到对应页面
|
||||
navigateToPage(e) {
|
||||
const type = e.currentTarget.dataset.type;
|
||||
|
||||
switch (type) {
|
||||
case 'loan-products':
|
||||
wx.navigateTo({
|
||||
url: '/pages/business/loan-products/loan-products'
|
||||
});
|
||||
break;
|
||||
case 'loan-applications':
|
||||
wx.navigateTo({
|
||||
url: '/pages/business/loan-applications/loan-applications'
|
||||
});
|
||||
break;
|
||||
case 'loan-contracts':
|
||||
wx.navigateTo({
|
||||
url: '/pages/business/loan-contracts/loan-contracts'
|
||||
});
|
||||
break;
|
||||
case 'loan-releases':
|
||||
wx.navigateTo({
|
||||
url: '/pages/business/loan-releases/loan-releases'
|
||||
});
|
||||
break;
|
||||
default:
|
||||
wx.showToast({
|
||||
title: '功能开发中',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 下拉刷新
|
||||
onPullDownRefresh() {
|
||||
this.loadBusinessStats().finally(() => {
|
||||
wx.stopPullDownRefresh();
|
||||
});
|
||||
}
|
||||
});
|
||||
3
bank_mini_program/pages/business/business.json
Normal file
3
bank_mini_program/pages/business/business.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"usingComponents": {}
|
||||
}
|
||||
41
bank_mini_program/pages/business/business.wxml
Normal file
41
bank_mini_program/pages/business/business.wxml
Normal file
@@ -0,0 +1,41 @@
|
||||
<!--pages/business/business.wxml-->
|
||||
<view class="business-container">
|
||||
<!-- 业务菜单列表 -->
|
||||
<view class="menu-list">
|
||||
<view
|
||||
class="menu-item"
|
||||
data-type="loan-products"
|
||||
bindtap="navigateToPage"
|
||||
>
|
||||
<text class="menu-text">普惠商品</text>
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="menu-item"
|
||||
data-type="loan-applications"
|
||||
bindtap="navigateToPage"
|
||||
>
|
||||
<text class="menu-text">业务申请进度</text>
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="menu-item"
|
||||
data-type="loan-contracts"
|
||||
bindtap="navigateToPage"
|
||||
>
|
||||
<text class="menu-text">业务合同</text>
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="menu-item"
|
||||
data-type="loan-releases"
|
||||
bindtap="navigateToPage"
|
||||
>
|
||||
<text class="menu-text">业务解押</text>
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
42
bank_mini_program/pages/business/business.wxss
Normal file
42
bank_mini_program/pages/business/business.wxss
Normal file
@@ -0,0 +1,42 @@
|
||||
/* pages/business/business.wxss */
|
||||
.business-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding-bottom: 100rpx;
|
||||
}
|
||||
|
||||
/* 业务菜单列表 */
|
||||
.menu-list {
|
||||
background-color: #fff;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
background-color: #fff;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.menu-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.menu-item:active {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
.menu-text {
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.menu-arrow {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
font-weight: 300;
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
// pages/business/loan-applications/loan-applications.js
|
||||
const { apiService } = require('../../../services/apiService');
|
||||
|
||||
Page({
|
||||
data: {
|
||||
applications: [],
|
||||
loading: false,
|
||||
searchKeyword: '',
|
||||
filterStatus: 'all',
|
||||
typeMap: {
|
||||
'personal': '个人贷款',
|
||||
'mortgage': '住房贷款',
|
||||
'business': '企业贷款',
|
||||
'agricultural': '农业贷款'
|
||||
},
|
||||
statusMap: {
|
||||
'pending': '待审核',
|
||||
'processing': '审核中',
|
||||
'approved': '已通过',
|
||||
'rejected': '已拒绝'
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadApplications();
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.loadApplications();
|
||||
},
|
||||
|
||||
// 加载申请数据
|
||||
async loadApplications() {
|
||||
this.setData({ loading: true });
|
||||
|
||||
try {
|
||||
const params = {
|
||||
page: 1,
|
||||
limit: 20,
|
||||
search: this.data.searchKeyword,
|
||||
status: this.data.filterStatus === 'all' ? '' : this.data.filterStatus
|
||||
};
|
||||
|
||||
const response = await apiService.loanApplications.getList(params);
|
||||
|
||||
if (response.success) {
|
||||
const applications = response.data.applications.map(application => ({
|
||||
...application,
|
||||
typeText: this.data.typeMap[application.type] || application.type,
|
||||
statusText: this.data.statusMap[application.status] || application.status,
|
||||
applicationTime: this.formatDate(application.applicationTime)
|
||||
}));
|
||||
|
||||
this.setData({
|
||||
applications,
|
||||
loading: false
|
||||
});
|
||||
} else {
|
||||
throw new Error(response.message || '获取申请列表失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载申请失败:', error);
|
||||
|
||||
// 使用模拟数据作为降级处理
|
||||
const mockApplications = [
|
||||
{
|
||||
id: 1,
|
||||
applicationNumber: 'APP-202401180001',
|
||||
applicantName: '张三',
|
||||
type: 'personal',
|
||||
typeText: '个人贷款',
|
||||
status: 'pending',
|
||||
statusText: '待审核',
|
||||
amount: 200000,
|
||||
term: 24,
|
||||
interestRate: 6.5,
|
||||
applicationTime: '2024-01-18 09:30',
|
||||
phone: '13800138000',
|
||||
purpose: '个人消费'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
applicationNumber: 'APP-202401180002',
|
||||
applicantName: '李四',
|
||||
type: 'mortgage',
|
||||
typeText: '住房贷款',
|
||||
status: 'approved',
|
||||
statusText: '已通过',
|
||||
amount: 500000,
|
||||
term: 240,
|
||||
interestRate: 4.5,
|
||||
applicationTime: '2024-01-18 10:15',
|
||||
phone: '13800138001',
|
||||
purpose: '购买住房'
|
||||
}
|
||||
];
|
||||
|
||||
this.setData({
|
||||
applications: mockApplications,
|
||||
loading: false
|
||||
});
|
||||
|
||||
wx.showToast({
|
||||
title: '使用模拟数据',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 格式化日期
|
||||
formatDate(dateString) {
|
||||
if (!dateString) return '';
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
},
|
||||
|
||||
// 搜索输入
|
||||
onSearchInput(e) {
|
||||
this.setData({
|
||||
searchKeyword: e.detail.value
|
||||
});
|
||||
this.loadApplications();
|
||||
},
|
||||
|
||||
// 筛选状态
|
||||
onFilterChange(e) {
|
||||
const status = e.detail.value;
|
||||
this.setData({
|
||||
filterStatus: status
|
||||
});
|
||||
this.loadApplications();
|
||||
},
|
||||
|
||||
// 查看申请详情
|
||||
viewApplicationDetail(e) {
|
||||
const applicationId = e.currentTarget.dataset.applicationId;
|
||||
wx.navigateTo({
|
||||
url: `/pages/business/loan-applications/detail?applicationId=${applicationId}`
|
||||
});
|
||||
},
|
||||
|
||||
// 下拉刷新
|
||||
onPullDownRefresh() {
|
||||
this.loadApplications().finally(() => {
|
||||
wx.stopPullDownRefresh();
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,101 @@
|
||||
<!--pages/business/loan-applications/loan-applications.wxml-->
|
||||
<view class="loan-applications-container">
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-bar">
|
||||
<view class="search-input">
|
||||
<text class="search-icon">🔍</text>
|
||||
<input
|
||||
placeholder="搜索申请人姓名或申请单号"
|
||||
value="{{searchKeyword}}"
|
||||
bindinput="onSearchInput"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 筛选栏 -->
|
||||
<view class="filter-bar">
|
||||
<picker
|
||||
bindchange="onFilterChange"
|
||||
value="{{filterStatus}}"
|
||||
range="{{['全部', '待审核', '审核中', '已通过', '已拒绝']}}"
|
||||
range-key=""
|
||||
>
|
||||
<view class="filter-item">
|
||||
<text>状态筛选</text>
|
||||
<text class="filter-value">{{filterStatus === 'all' ? '全部' : filterStatus}}</text>
|
||||
<text class="arrow">></text>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<!-- 申请列表 -->
|
||||
<view class="application-list">
|
||||
<view
|
||||
class="application-item"
|
||||
wx:for="{{applications}}"
|
||||
wx:key="id"
|
||||
data-application-id="{{item.id}}"
|
||||
bindtap="viewApplicationDetail"
|
||||
>
|
||||
<view class="application-header">
|
||||
<text class="application-number">{{item.applicationNumber}}</text>
|
||||
<view class="application-status {{item.status}}">
|
||||
<text>{{item.statusText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="application-info">
|
||||
<view class="info-row">
|
||||
<text class="info-label">申请人:</text>
|
||||
<text class="info-value">{{item.applicantName}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="info-label">申请类型:</text>
|
||||
<text class="info-value">{{item.typeText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="info-label">申请金额:</text>
|
||||
<text class="info-value">{{item.amount}}元</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="info-label">申请期限:</text>
|
||||
<text class="info-value">{{item.term}}个月</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<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.applicationTime}}</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.purpose}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading" wx:if="{{loading}}">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" wx:if="{{!loading && applications.length === 0}}">
|
||||
<text class="empty-icon">📋</text>
|
||||
<text class="empty-text">暂无申请数据</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,157 @@
|
||||
/* pages/business/loan-applications/loan-applications.wxss */
|
||||
.loan-applications-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding-bottom: 100rpx;
|
||||
}
|
||||
|
||||
/* 搜索栏 */
|
||||
.search-bar {
|
||||
padding: 20rpx 30rpx;
|
||||
background-color: #fff;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 25rpx;
|
||||
padding: 0 20rpx;
|
||||
height: 70rpx;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
margin-right: 15rpx;
|
||||
}
|
||||
|
||||
.search-input input {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 筛选栏 */
|
||||
.filter-bar {
|
||||
background-color: #fff;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 30rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.filter-value {
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 申请列表 */
|
||||
.application-list {
|
||||
padding: 20rpx 30rpx;
|
||||
}
|
||||
|
||||
.application-item {
|
||||
background-color: #fff;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 30rpx;
|
||||
border-radius: 8rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.application-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.application-number {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.application-status {
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
font-size: 24rpx;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.application-status.pending {
|
||||
background-color: #faad14;
|
||||
}
|
||||
|
||||
.application-status.processing {
|
||||
background-color: #1890ff;
|
||||
}
|
||||
|
||||
.application-status.approved {
|
||||
background-color: #52c41a;
|
||||
}
|
||||
|
||||
.application-status.rejected {
|
||||
background-color: #f5222d;
|
||||
}
|
||||
|
||||
.application-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #666;
|
||||
min-width: 140rpx;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 60rpx 0;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 0;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 80rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
// pages/business/loan-contracts/loan-contracts.js
|
||||
const { apiService } = require('../../../services/apiService');
|
||||
|
||||
Page({
|
||||
data: {
|
||||
contracts: [],
|
||||
loading: false,
|
||||
searchKeyword: '',
|
||||
filterStatus: 'all',
|
||||
typeMap: {
|
||||
'personal': '个人贷款',
|
||||
'mortgage': '住房贷款',
|
||||
'business': '企业贷款',
|
||||
'agricultural': '农业贷款'
|
||||
},
|
||||
statusMap: {
|
||||
'active': '生效中',
|
||||
'expired': '已到期',
|
||||
'terminated': '已终止'
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadContracts();
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.loadContracts();
|
||||
},
|
||||
|
||||
// 加载合同数据
|
||||
async loadContracts() {
|
||||
this.setData({ loading: true });
|
||||
|
||||
try {
|
||||
const params = {
|
||||
page: 1,
|
||||
limit: 20,
|
||||
search: this.data.searchKeyword,
|
||||
status: this.data.filterStatus === 'all' ? '' : this.data.filterStatus
|
||||
};
|
||||
|
||||
const response = await apiService.loanContracts.getList(params);
|
||||
|
||||
if (response.success) {
|
||||
const contracts = response.data.contracts.map(contract => ({
|
||||
...contract,
|
||||
typeText: this.data.typeMap[contract.type] || contract.type,
|
||||
statusText: this.data.statusMap[contract.status] || contract.status,
|
||||
signDate: this.formatDate(contract.signDate),
|
||||
expiryDate: this.formatDate(contract.expiryDate)
|
||||
}));
|
||||
|
||||
this.setData({
|
||||
contracts,
|
||||
loading: false
|
||||
});
|
||||
} else {
|
||||
throw new Error(response.message || '获取合同列表失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载合同失败:', error);
|
||||
|
||||
// 使用模拟数据作为降级处理
|
||||
const mockContracts = [
|
||||
{
|
||||
id: 1,
|
||||
contractNumber: 'CONTRACT-202401180001',
|
||||
customerName: '张三',
|
||||
type: 'personal',
|
||||
typeText: '个人贷款',
|
||||
status: 'active',
|
||||
statusText: '生效中',
|
||||
amount: 200000,
|
||||
term: 24,
|
||||
interestRate: 6.5,
|
||||
signDate: '2024-01-18',
|
||||
expiryDate: '2026-01-18',
|
||||
phone: '13800138000'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
contractNumber: 'CONTRACT-202401180002',
|
||||
customerName: '李四',
|
||||
type: 'mortgage',
|
||||
typeText: '住房贷款',
|
||||
status: 'active',
|
||||
statusText: '生效中',
|
||||
amount: 500000,
|
||||
term: 240,
|
||||
interestRate: 4.5,
|
||||
signDate: '2024-01-18',
|
||||
expiryDate: '2044-01-18',
|
||||
phone: '13800138001'
|
||||
}
|
||||
];
|
||||
|
||||
this.setData({
|
||||
contracts: mockContracts,
|
||||
loading: false
|
||||
});
|
||||
|
||||
wx.showToast({
|
||||
title: '使用模拟数据',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 格式化日期
|
||||
formatDate(dateString) {
|
||||
if (!dateString) return '';
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('zh-CN');
|
||||
},
|
||||
|
||||
// 搜索输入
|
||||
onSearchInput(e) {
|
||||
this.setData({
|
||||
searchKeyword: e.detail.value
|
||||
});
|
||||
this.loadContracts();
|
||||
},
|
||||
|
||||
// 筛选状态
|
||||
onFilterChange(e) {
|
||||
const status = e.detail.value;
|
||||
this.setData({
|
||||
filterStatus: status
|
||||
});
|
||||
this.loadContracts();
|
||||
},
|
||||
|
||||
// 查看合同详情
|
||||
viewContractDetail(e) {
|
||||
const contractId = e.currentTarget.dataset.contractId;
|
||||
wx.navigateTo({
|
||||
url: `/pages/business/loan-contracts/detail?contractId=${contractId}`
|
||||
});
|
||||
},
|
||||
|
||||
// 下拉刷新
|
||||
onPullDownRefresh() {
|
||||
this.loadContracts().finally(() => {
|
||||
wx.stopPullDownRefresh();
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,101 @@
|
||||
<!--pages/business/loan-contracts/loan-contracts.wxml-->
|
||||
<view class="loan-contracts-container">
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-bar">
|
||||
<view class="search-input">
|
||||
<text class="search-icon">🔍</text>
|
||||
<input
|
||||
placeholder="搜索合同编号或客户姓名"
|
||||
value="{{searchKeyword}}"
|
||||
bindinput="onSearchInput"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 筛选栏 -->
|
||||
<view class="filter-bar">
|
||||
<picker
|
||||
bindchange="onFilterChange"
|
||||
value="{{filterStatus}}"
|
||||
range="{{['全部', '生效中', '已到期', '已终止']}}"
|
||||
range-key=""
|
||||
>
|
||||
<view class="filter-item">
|
||||
<text>状态筛选</text>
|
||||
<text class="filter-value">{{filterStatus === 'all' ? '全部' : filterStatus}}</text>
|
||||
<text class="arrow">></text>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<!-- 合同列表 -->
|
||||
<view class="contract-list">
|
||||
<view
|
||||
class="contract-item"
|
||||
wx:for="{{contracts}}"
|
||||
wx:key="id"
|
||||
data-contract-id="{{item.id}}"
|
||||
bindtap="viewContractDetail"
|
||||
>
|
||||
<view class="contract-header">
|
||||
<text class="contract-number">{{item.contractNumber}}</text>
|
||||
<view class="contract-status {{item.status}}">
|
||||
<text>{{item.statusText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="contract-info">
|
||||
<view class="info-row">
|
||||
<text class="info-label">客户姓名:</text>
|
||||
<text class="info-value">{{item.customerName}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="info-label">合同类型:</text>
|
||||
<text class="info-value">{{item.typeText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="info-label">合同金额:</text>
|
||||
<text class="info-value">{{item.amount}}元</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="info-label">合同期限:</text>
|
||||
<text class="info-value">{{item.term}}个月</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<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.signDate}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="info-label">到期时间:</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>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading" wx:if="{{loading}}">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" wx:if="{{!loading && contracts.length === 0}}">
|
||||
<text class="empty-icon">📄</text>
|
||||
<text class="empty-text">暂无合同数据</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,153 @@
|
||||
/* pages/business/loan-contracts/loan-contracts.wxss */
|
||||
.loan-contracts-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding-bottom: 100rpx;
|
||||
}
|
||||
|
||||
/* 搜索栏 */
|
||||
.search-bar {
|
||||
padding: 20rpx 30rpx;
|
||||
background-color: #fff;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 25rpx;
|
||||
padding: 0 20rpx;
|
||||
height: 70rpx;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
margin-right: 15rpx;
|
||||
}
|
||||
|
||||
.search-input input {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 筛选栏 */
|
||||
.filter-bar {
|
||||
background-color: #fff;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 30rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.filter-value {
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 合同列表 */
|
||||
.contract-list {
|
||||
padding: 20rpx 30rpx;
|
||||
}
|
||||
|
||||
.contract-item {
|
||||
background-color: #fff;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 30rpx;
|
||||
border-radius: 8rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.contract-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.contract-number {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.contract-status {
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
font-size: 24rpx;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.contract-status.active {
|
||||
background-color: #52c41a;
|
||||
}
|
||||
|
||||
.contract-status.expired {
|
||||
background-color: #faad14;
|
||||
}
|
||||
|
||||
.contract-status.terminated {
|
||||
background-color: #f5222d;
|
||||
}
|
||||
|
||||
.contract-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #666;
|
||||
min-width: 140rpx;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 60rpx 0;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 0;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 80rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
136
bank_mini_program/pages/business/loan-products/loan-products.js
Normal file
136
bank_mini_program/pages/business/loan-products/loan-products.js
Normal file
@@ -0,0 +1,136 @@
|
||||
// pages/business/loan-products/loan-products.js
|
||||
const { apiService } = require('../../../services/apiService');
|
||||
|
||||
Page({
|
||||
data: {
|
||||
products: [],
|
||||
loading: false,
|
||||
searchKeyword: '',
|
||||
filterStatus: 'all',
|
||||
typeMap: {
|
||||
'personal': '个人贷款',
|
||||
'mortgage': '住房贷款',
|
||||
'business': '企业贷款',
|
||||
'agricultural': '农业贷款'
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadProducts();
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.loadProducts();
|
||||
},
|
||||
|
||||
// 加载商品数据
|
||||
async loadProducts() {
|
||||
this.setData({ loading: true });
|
||||
|
||||
try {
|
||||
const params = {
|
||||
page: 1,
|
||||
limit: 20,
|
||||
search: this.data.searchKeyword,
|
||||
status: this.data.filterStatus === 'all' ? '' : this.data.filterStatus
|
||||
};
|
||||
|
||||
const response = await apiService.loanProducts.getList(params);
|
||||
|
||||
if (response.success) {
|
||||
const products = response.data.products.map(product => ({
|
||||
...product,
|
||||
statusText: product.status === 'active' ? '在售' : '停售',
|
||||
typeText: this.data.typeMap[product.type] || product.type
|
||||
}));
|
||||
|
||||
this.setData({
|
||||
products,
|
||||
loading: false
|
||||
});
|
||||
} else {
|
||||
throw new Error(response.message || '获取商品列表失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载商品失败:', error);
|
||||
|
||||
// 使用模拟数据作为降级处理
|
||||
const mockProducts = [
|
||||
{
|
||||
id: 1,
|
||||
name: '个人住房贷款',
|
||||
code: 'LOAN-001',
|
||||
type: 'mortgage',
|
||||
typeText: '住房贷款',
|
||||
status: 'active',
|
||||
statusText: '在售',
|
||||
minAmount: 100000,
|
||||
maxAmount: 5000000,
|
||||
minTerm: 12,
|
||||
maxTerm: 360,
|
||||
interestRate: 4.5,
|
||||
maxInterestRate: 6.5,
|
||||
description: '专为个人购房提供的住房抵押贷款产品'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '个人消费贷款',
|
||||
code: 'LOAN-002',
|
||||
type: 'personal',
|
||||
typeText: '个人贷款',
|
||||
status: 'active',
|
||||
statusText: '在售',
|
||||
minAmount: 10000,
|
||||
maxAmount: 500000,
|
||||
minTerm: 6,
|
||||
maxTerm: 60,
|
||||
interestRate: 6.8,
|
||||
maxInterestRate: 12.5,
|
||||
description: '用于个人消费支出的信用贷款产品'
|
||||
}
|
||||
];
|
||||
|
||||
this.setData({
|
||||
products: mockProducts,
|
||||
loading: false
|
||||
});
|
||||
|
||||
wx.showToast({
|
||||
title: '使用模拟数据',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 搜索输入
|
||||
onSearchInput(e) {
|
||||
this.setData({
|
||||
searchKeyword: e.detail.value
|
||||
});
|
||||
this.loadProducts();
|
||||
},
|
||||
|
||||
// 筛选状态
|
||||
onFilterChange(e) {
|
||||
const status = e.detail.value;
|
||||
this.setData({
|
||||
filterStatus: status
|
||||
});
|
||||
this.loadProducts();
|
||||
},
|
||||
|
||||
// 查看商品详情
|
||||
viewProductDetail(e) {
|
||||
const productId = e.currentTarget.dataset.productId;
|
||||
wx.navigateTo({
|
||||
url: `/pages/business/loan-products/detail?productId=${productId}`
|
||||
});
|
||||
},
|
||||
|
||||
// 下拉刷新
|
||||
onPullDownRefresh() {
|
||||
this.loadProducts().finally(() => {
|
||||
wx.stopPullDownRefresh();
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
<!--pages/business/loan-products/loan-products.wxml-->
|
||||
<view class="loan-products-container">
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-bar">
|
||||
<view class="search-input">
|
||||
<text class="search-icon">🔍</text>
|
||||
<input
|
||||
placeholder="搜索商品名称"
|
||||
value="{{searchKeyword}}"
|
||||
bindinput="onSearchInput"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 筛选栏 -->
|
||||
<view class="filter-bar">
|
||||
<picker
|
||||
bindchange="onFilterChange"
|
||||
value="{{filterStatus}}"
|
||||
range="{{['全部', '在售', '停售']}}"
|
||||
range-key=""
|
||||
>
|
||||
<view class="filter-item">
|
||||
<text>状态筛选</text>
|
||||
<text class="filter-value">{{filterStatus === 'all' ? '全部' : filterStatus}}</text>
|
||||
<text class="arrow">></text>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<!-- 商品列表 -->
|
||||
<view class="product-list">
|
||||
<view
|
||||
class="product-item"
|
||||
wx:for="{{products}}"
|
||||
wx:key="id"
|
||||
data-product-id="{{item.id}}"
|
||||
bindtap="viewProductDetail"
|
||||
>
|
||||
<view class="product-header">
|
||||
<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="info-row">
|
||||
<text class="info-label">产品代码:</text>
|
||||
<text class="info-value">{{item.code}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="info-label">产品类型:</text>
|
||||
<text class="info-value">{{item.typeText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="info-label">贷款额度:</text>
|
||||
<text class="info-value">{{item.minAmount}} - {{item.maxAmount}}元</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="info-label">贷款期限:</text>
|
||||
<text class="info-value">{{item.minTerm}} - {{item.maxTerm}}个月</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="info-label">利率范围:</text>
|
||||
<text class="info-value">{{item.interestRate}}% - {{item.maxInterestRate}}%</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="product-desc">
|
||||
<text class="desc-text">{{item.description}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading" wx:if="{{loading}}">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" wx:if="{{!loading && products.length === 0}}">
|
||||
<text class="empty-icon">📦</text>
|
||||
<text class="empty-text">暂无商品数据</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,159 @@
|
||||
/* pages/business/loan-products/loan-products.wxss */
|
||||
.loan-products-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding-bottom: 100rpx;
|
||||
}
|
||||
|
||||
/* 搜索栏 */
|
||||
.search-bar {
|
||||
padding: 20rpx 30rpx;
|
||||
background-color: #fff;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 25rpx;
|
||||
padding: 0 20rpx;
|
||||
height: 70rpx;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
margin-right: 15rpx;
|
||||
}
|
||||
|
||||
.search-input input {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 筛选栏 */
|
||||
.filter-bar {
|
||||
background-color: #fff;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 30rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.filter-value {
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 商品列表 */
|
||||
.product-list {
|
||||
padding: 20rpx 30rpx;
|
||||
}
|
||||
|
||||
.product-item {
|
||||
background-color: #fff;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 30rpx;
|
||||
border-radius: 8rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.product-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.product-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.product-status {
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
font-size: 24rpx;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.product-status.active {
|
||||
background-color: #52c41a;
|
||||
}
|
||||
|
||||
.product-status.inactive {
|
||||
background-color: #faad14;
|
||||
}
|
||||
|
||||
.product-info {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10rpx;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #666;
|
||||
min-width: 140rpx;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.product-desc {
|
||||
padding-top: 20rpx;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.desc-text {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 60rpx 0;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 0;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 80rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
147
bank_mini_program/pages/business/loan-releases/loan-releases.js
Normal file
147
bank_mini_program/pages/business/loan-releases/loan-releases.js
Normal file
@@ -0,0 +1,147 @@
|
||||
// pages/business/loan-releases/loan-releases.js
|
||||
const { apiService } = require('../../../services/apiService');
|
||||
|
||||
Page({
|
||||
data: {
|
||||
releases: [],
|
||||
loading: false,
|
||||
searchKeyword: '',
|
||||
filterStatus: 'all',
|
||||
statusMap: {
|
||||
'pending': '待处理',
|
||||
'processing': '处理中',
|
||||
'approved': '已通过',
|
||||
'rejected': '已拒绝',
|
||||
'completed': '已完成',
|
||||
'released': '已解押'
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadReleases();
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.loadReleases();
|
||||
},
|
||||
|
||||
// 加载解押申请数据
|
||||
async loadReleases() {
|
||||
this.setData({ loading: true });
|
||||
|
||||
try {
|
||||
const params = {
|
||||
page: 1,
|
||||
limit: 20,
|
||||
searchValue: this.data.searchKeyword,
|
||||
status: this.data.filterStatus === 'all' ? '' : this.data.filterStatus
|
||||
};
|
||||
|
||||
const response = await apiService.loanReleases.getList(params);
|
||||
|
||||
if (response.success) {
|
||||
const releases = response.data.releases.map(release => ({
|
||||
...release,
|
||||
statusText: this.data.statusMap[release.status] || release.status,
|
||||
applicationTime: this.formatDate(release.applicationTime)
|
||||
}));
|
||||
|
||||
this.setData({
|
||||
releases,
|
||||
loading: false
|
||||
});
|
||||
} else {
|
||||
throw new Error(response.message || '获取解押申请列表失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载解押申请失败:', error);
|
||||
|
||||
// 使用模拟数据作为降级处理
|
||||
const mockReleases = [
|
||||
{
|
||||
id: 1,
|
||||
applicationNumber: '20240227145555918',
|
||||
customerName: '刘超',
|
||||
productName: '中国工商银行扎旗支行"畜禽活体抵押"',
|
||||
assetType: '牛',
|
||||
releaseQuantity: '10头',
|
||||
releaseAmount: 10000,
|
||||
status: 'released',
|
||||
statusText: '已解押',
|
||||
applicationTime: '2024-02-27 06:55',
|
||||
applicantPhone: '13800138000',
|
||||
processComment: '审核通过,同意解押'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
applicationNumber: '20240227145555919',
|
||||
customerName: '张三',
|
||||
productName: '个人住房贷款',
|
||||
assetType: '房产',
|
||||
releaseQuantity: '1套',
|
||||
releaseAmount: 50000,
|
||||
status: 'pending',
|
||||
statusText: '待处理',
|
||||
applicationTime: '2024-02-27 10:30',
|
||||
applicantPhone: '13800138001',
|
||||
processComment: ''
|
||||
}
|
||||
];
|
||||
|
||||
this.setData({
|
||||
releases: mockReleases,
|
||||
loading: false
|
||||
});
|
||||
|
||||
wx.showToast({
|
||||
title: '使用模拟数据',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 格式化日期
|
||||
formatDate(dateString) {
|
||||
if (!dateString) return '';
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
},
|
||||
|
||||
// 搜索输入
|
||||
onSearchInput(e) {
|
||||
this.setData({
|
||||
searchKeyword: e.detail.value
|
||||
});
|
||||
this.loadReleases();
|
||||
},
|
||||
|
||||
// 筛选状态
|
||||
onFilterChange(e) {
|
||||
const status = e.detail.value;
|
||||
this.setData({
|
||||
filterStatus: status
|
||||
});
|
||||
this.loadReleases();
|
||||
},
|
||||
|
||||
// 查看解押申请详情
|
||||
viewReleaseDetail(e) {
|
||||
const releaseId = e.currentTarget.dataset.releaseId;
|
||||
wx.navigateTo({
|
||||
url: `/pages/business/loan-releases/detail?releaseId=${releaseId}`
|
||||
});
|
||||
},
|
||||
|
||||
// 下拉刷新
|
||||
onPullDownRefresh() {
|
||||
this.loadReleases().finally(() => {
|
||||
wx.stopPullDownRefresh();
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,101 @@
|
||||
<!--pages/business/loan-releases/loan-releases.wxml-->
|
||||
<view class="loan-releases-container">
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-bar">
|
||||
<view class="search-input">
|
||||
<text class="search-icon">🔍</text>
|
||||
<input
|
||||
placeholder="搜索申请单号或客户姓名"
|
||||
value="{{searchKeyword}}"
|
||||
bindinput="onSearchInput"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 筛选栏 -->
|
||||
<view class="filter-bar">
|
||||
<picker
|
||||
bindchange="onFilterChange"
|
||||
value="{{filterStatus}}"
|
||||
range="{{['全部', '待处理', '处理中', '已通过', '已拒绝', '已完成', '已解押']}}"
|
||||
range-key=""
|
||||
>
|
||||
<view class="filter-item">
|
||||
<text>状态筛选</text>
|
||||
<text class="filter-value">{{filterStatus === 'all' ? '全部' : filterStatus}}</text>
|
||||
<text class="arrow">></text>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<!-- 解押申请列表 -->
|
||||
<view class="release-list">
|
||||
<view
|
||||
class="release-item"
|
||||
wx:for="{{releases}}"
|
||||
wx:key="id"
|
||||
data-release-id="{{item.id}}"
|
||||
bindtap="viewReleaseDetail"
|
||||
>
|
||||
<view class="release-header">
|
||||
<text class="release-number">{{item.applicationNumber}}</text>
|
||||
<view class="release-status {{item.status}}">
|
||||
<text>{{item.statusText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="release-info">
|
||||
<view class="info-row">
|
||||
<text class="info-label">客户姓名:</text>
|
||||
<text class="info-value">{{item.customerName}}</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.assetType}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="info-label">解押数量:</text>
|
||||
<text class="info-value">{{item.releaseQuantity}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="info-label">解押金额:</text>
|
||||
<text class="info-value">{{item.releaseAmount}}元</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="info-label">申请时间:</text>
|
||||
<text class="info-value">{{item.applicationTime}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="info-label">联系电话:</text>
|
||||
<text class="info-value">{{item.applicantPhone}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row" wx:if="{{item.processComment}}">
|
||||
<text class="info-label">处理意见:</text>
|
||||
<text class="info-value">{{item.processComment}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading" wx:if="{{loading}}">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" wx:if="{{!loading && releases.length === 0}}">
|
||||
<text class="empty-icon">🔓</text>
|
||||
<text class="empty-text">暂无解押申请数据</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,165 @@
|
||||
/* pages/business/loan-releases/loan-releases.wxss */
|
||||
.loan-releases-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding-bottom: 100rpx;
|
||||
}
|
||||
|
||||
/* 搜索栏 */
|
||||
.search-bar {
|
||||
padding: 20rpx 30rpx;
|
||||
background-color: #fff;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 25rpx;
|
||||
padding: 0 20rpx;
|
||||
height: 70rpx;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
margin-right: 15rpx;
|
||||
}
|
||||
|
||||
.search-input input {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 筛选栏 */
|
||||
.filter-bar {
|
||||
background-color: #fff;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 30rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.filter-value {
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 解押申请列表 */
|
||||
.release-list {
|
||||
padding: 20rpx 30rpx;
|
||||
}
|
||||
|
||||
.release-item {
|
||||
background-color: #fff;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 30rpx;
|
||||
border-radius: 8rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.release-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.release-number {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.release-status {
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
font-size: 24rpx;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.release-status.pending {
|
||||
background-color: #faad14;
|
||||
}
|
||||
|
||||
.release-status.processing {
|
||||
background-color: #1890ff;
|
||||
}
|
||||
|
||||
.release-status.approved {
|
||||
background-color: #52c41a;
|
||||
}
|
||||
|
||||
.release-status.rejected {
|
||||
background-color: #f5222d;
|
||||
}
|
||||
|
||||
.release-status.completed {
|
||||
background-color: #13c2c2;
|
||||
}
|
||||
|
||||
.release-status.released {
|
||||
background-color: #722ed1;
|
||||
}
|
||||
|
||||
.release-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #666;
|
||||
min-width: 140rpx;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 60rpx 0;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 0;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 80rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
@@ -1,69 +1,66 @@
|
||||
// pages/customers/customers.js
|
||||
const bankService = require('../../services/bankService.js')
|
||||
|
||||
Page({
|
||||
|
||||
/**
|
||||
* 页面的初始数据
|
||||
*/
|
||||
data: {
|
||||
searchKeyword: '',
|
||||
loading: false,
|
||||
customersList: []
|
||||
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadCustomersData()
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad(options) {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面初次渲染完成
|
||||
*/
|
||||
onReady() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面显示
|
||||
*/
|
||||
onShow() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面隐藏
|
||||
*/
|
||||
onHide() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面卸载
|
||||
*/
|
||||
onUnload() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面相关事件处理函数--监听用户下拉动作
|
||||
*/
|
||||
onPullDownRefresh() {
|
||||
this.loadCustomersData()
|
||||
setTimeout(() => {
|
||||
wx.stopPullDownRefresh()
|
||||
}, 1000)
|
||||
|
||||
},
|
||||
|
||||
loadCustomersData() {
|
||||
// 模拟数据
|
||||
this.setData({
|
||||
customersList: [
|
||||
{
|
||||
id: 1,
|
||||
name: '张三',
|
||||
phone: '13800138000',
|
||||
email: 'zhangsan@example.com',
|
||||
creditScore: 850,
|
||||
totalAssets: '¥125,680.50',
|
||||
status: 'active',
|
||||
statusText: '活跃'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '李四',
|
||||
phone: '13800138001',
|
||||
email: 'lisi@example.com',
|
||||
creditScore: 720,
|
||||
totalAssets: '¥89,450.00',
|
||||
status: 'active',
|
||||
statusText: '活跃'
|
||||
}
|
||||
]
|
||||
})
|
||||
/**
|
||||
* 页面上拉触底事件的处理函数
|
||||
*/
|
||||
onReachBottom() {
|
||||
|
||||
},
|
||||
|
||||
onSearchInput(e) {
|
||||
this.setData({
|
||||
searchKeyword: e.detail.value
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 用户点击右上角分享
|
||||
*/
|
||||
onShareAppMessage() {
|
||||
|
||||
handleAdd() {
|
||||
wx.showToast({
|
||||
title: '新增功能待实现',
|
||||
icon: 'none'
|
||||
})
|
||||
},
|
||||
|
||||
handleCustomerTap(e) {
|
||||
const { customerId } = e.currentTarget.dataset
|
||||
wx.navigateTo({
|
||||
url: `/pages/customers/detail?id=${customerId}`
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1,64 +1,2 @@
|
||||
<!--pages/customers/customers.wxml-->
|
||||
<view class="customers-container">
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-section">
|
||||
<view class="search-bar">
|
||||
<input
|
||||
value="{{searchKeyword}}"
|
||||
type="text"
|
||||
placeholder="搜索客户..."
|
||||
class="search-input"
|
||||
bindinput="onSearchInput"
|
||||
/>
|
||||
<view class="search-icon">🔍</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 客户列表 -->
|
||||
<view class="customers-list">
|
||||
<view class="list-header">
|
||||
<view class="list-title">客户管理</view>
|
||||
<view class="add-btn" bindtap="handleAdd">
|
||||
<text class="add-text">新增</text>
|
||||
<view class="add-icon">+</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{loading}}" class="loading">
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
|
||||
<view wx:elif="{{customersList.length === 0}}" class="empty">
|
||||
<view class="empty-icon">👥</view>
|
||||
<view class="empty-text">暂无客户记录</view>
|
||||
</view>
|
||||
|
||||
<view wx:else class="list-content">
|
||||
<view
|
||||
wx:for="{{customersList}}"
|
||||
wx:key="id"
|
||||
class="customer-item"
|
||||
data-customer-id="{{item.id}}"
|
||||
bindtap="handleCustomerTap"
|
||||
>
|
||||
<view class="item-avatar">
|
||||
<image src="/images/avatar.png" class="avatar-img" />
|
||||
</view>
|
||||
<view class="item-content">
|
||||
<view class="item-header">
|
||||
<view class="item-name">{{item.name}}</view>
|
||||
<view class="item-status {{item.status}}">
|
||||
{{item.statusText}}
|
||||
</view>
|
||||
</view>
|
||||
<view class="item-info">
|
||||
<view class="item-phone">{{item.phone}}</view>
|
||||
<view class="item-email">{{item.email}}</view>
|
||||
<view class="item-assets">总资产: {{item.totalAssets}}</view>
|
||||
<view class="item-credit">信用评分: {{item.creditScore}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<text>pages/customers/customers.wxml</text>
|
||||
@@ -1,166 +0,0 @@
|
||||
/* pages/customers/customers.wxss */
|
||||
.customers-container {
|
||||
min-height: 100vh;
|
||||
background: #f6f6f6;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
padding: 20rpx;
|
||||
background: #fff;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
height: 72rpx;
|
||||
background: #f8f9fa;
|
||||
border: none;
|
||||
border-radius: 36rpx;
|
||||
padding: 0 60rpx 0 30rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
position: absolute;
|
||||
right: 30rpx;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.customers-list {
|
||||
background: #fff;
|
||||
margin: 0 20rpx;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.list-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.list-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16rpx 24rpx;
|
||||
background: #1890ff;
|
||||
border-radius: 24rpx;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.add-text {
|
||||
font-size: 24rpx;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.add-icon {
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.loading,
|
||||
.empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 80rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.loading-text,
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 80rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.list-content {
|
||||
space-y: 0;
|
||||
}
|
||||
|
||||
.customer-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.customer-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.item-avatar {
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.avatar-img {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 40rpx;
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.item-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.item-name {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.item-status {
|
||||
font-size: 20rpx;
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.item-status.active {
|
||||
background: #f6ffed;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.item-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
space-y: 8rpx;
|
||||
}
|
||||
|
||||
.item-phone,
|
||||
.item-email,
|
||||
.item-assets,
|
||||
.item-credit {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
@@ -1,87 +1,66 @@
|
||||
// pages/dashboard/dashboard.js
|
||||
const bankService = require('../../services/bankService.js')
|
||||
|
||||
Page({
|
||||
|
||||
/**
|
||||
* 页面的初始数据
|
||||
*/
|
||||
data: {
|
||||
overviewCards: [
|
||||
{
|
||||
key: 'totalAssets',
|
||||
icon: '💰',
|
||||
label: '总资产',
|
||||
value: '¥1,234,567.89',
|
||||
trend: 'up',
|
||||
trendText: '+2.5%',
|
||||
type: 'primary'
|
||||
},
|
||||
{
|
||||
key: 'monthlyIncome',
|
||||
icon: '📈',
|
||||
label: '月收入',
|
||||
value: '¥85,000.00',
|
||||
trend: 'up',
|
||||
trendText: '+5.2%',
|
||||
type: 'success'
|
||||
},
|
||||
{
|
||||
key: 'activeCustomers',
|
||||
icon: '👥',
|
||||
label: '活跃客户',
|
||||
value: '1,234',
|
||||
trend: 'up',
|
||||
trendText: '+12',
|
||||
type: 'warning'
|
||||
},
|
||||
{
|
||||
key: 'riskLevel',
|
||||
icon: '⚠️',
|
||||
label: '风险等级',
|
||||
value: '低',
|
||||
trend: 'down',
|
||||
trendText: '-0.5%',
|
||||
type: 'danger'
|
||||
}
|
||||
],
|
||||
chartData: {
|
||||
labels: ['1月', '2月', '3月', '4月', '5月', '6月'],
|
||||
income: [65000, 72000, 68000, 75000, 82000, 85000],
|
||||
expense: [45000, 48000, 52000, 49000, 55000, 58000]
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadDashboardData()
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad(options) {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面初次渲染完成
|
||||
*/
|
||||
onReady() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面显示
|
||||
*/
|
||||
onShow() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面隐藏
|
||||
*/
|
||||
onHide() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面卸载
|
||||
*/
|
||||
onUnload() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面相关事件处理函数--监听用户下拉动作
|
||||
*/
|
||||
onPullDownRefresh() {
|
||||
this.loadDashboardData()
|
||||
setTimeout(() => {
|
||||
wx.stopPullDownRefresh()
|
||||
}, 1000)
|
||||
|
||||
},
|
||||
|
||||
async loadDashboardData() {
|
||||
try {
|
||||
const data = await bankService.getDashboardData()
|
||||
if (data) {
|
||||
this.updateOverviewCards(data)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载仪表板数据失败:', error)
|
||||
wx.showToast({
|
||||
title: '加载数据失败',
|
||||
icon: 'error'
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 页面上拉触底事件的处理函数
|
||||
*/
|
||||
onReachBottom() {
|
||||
|
||||
},
|
||||
|
||||
updateOverviewCards(data) {
|
||||
const overviewCards = this.data.overviewCards.map(card => {
|
||||
const newValue = data[card.key] || card.value
|
||||
return {
|
||||
...card,
|
||||
value: newValue
|
||||
}
|
||||
})
|
||||
this.setData({ overviewCards })
|
||||
|
||||
/**
|
||||
* 用户点击右上角分享
|
||||
*/
|
||||
onShareAppMessage() {
|
||||
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1,66 +1,2 @@
|
||||
<!--pages/dashboard/dashboard.wxml-->
|
||||
<view class="dashboard-container">
|
||||
<!-- 数据概览卡片 -->
|
||||
<view class="overview-cards">
|
||||
<view
|
||||
wx:for="{{overviewCards}}"
|
||||
wx:key="key"
|
||||
class="overview-card {{item.type}}"
|
||||
>
|
||||
<view class="card-icon">{{item.icon}}</view>
|
||||
<view class="card-content">
|
||||
<view class="card-value">{{item.value}}</view>
|
||||
<view class="card-label">{{item.label}}</view>
|
||||
<view class="card-trend {{item.trend}}">
|
||||
{{item.trendText}}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 图表区域 -->
|
||||
<view class="charts-section">
|
||||
<view class="section-title">收入支出趋势</view>
|
||||
<view class="chart-container">
|
||||
<view class="chart-placeholder">
|
||||
<view class="chart-icon">📊</view>
|
||||
<view class="chart-text">图表数据加载中...</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 快速统计 -->
|
||||
<view class="quick-stats">
|
||||
<view class="section-title">快速统计</view>
|
||||
<view class="stats-grid">
|
||||
<view class="stat-item">
|
||||
<view class="stat-icon">💳</view>
|
||||
<view class="stat-info">
|
||||
<view class="stat-value">1,234</view>
|
||||
<view class="stat-label">今日交易</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<view class="stat-icon">👥</view>
|
||||
<view class="stat-info">
|
||||
<view class="stat-value">89</view>
|
||||
<view class="stat-label">新增客户</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<view class="stat-icon">💰</view>
|
||||
<view class="stat-info">
|
||||
<view class="stat-value">¥2.5M</view>
|
||||
<view class="stat-label">今日流水</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<view class="stat-icon">📈</view>
|
||||
<view class="stat-info">
|
||||
<view class="stat-value">+15%</view>
|
||||
<view class="stat-label">增长率</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<text>pages/dashboard/dashboard.wxml</text>
|
||||
@@ -1,147 +0,0 @@
|
||||
/* pages/dashboard/dashboard.wxss */
|
||||
.dashboard-container {
|
||||
min-height: 100vh;
|
||||
background: #f6f6f6;
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.overview-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 20rpx;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.overview-card {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.overview-card.primary {
|
||||
border-left: 8rpx solid #1890ff;
|
||||
}
|
||||
|
||||
.overview-card.success {
|
||||
border-left: 8rpx solid #52c41a;
|
||||
}
|
||||
|
||||
.overview-card.warning {
|
||||
border-left: 8rpx solid #faad14;
|
||||
}
|
||||
|
||||
.overview-card.danger {
|
||||
border-left: 8rpx solid #ff4d4f;
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
font-size: 48rpx;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.card-value {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.card-label {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.card-trend {
|
||||
font-size: 20rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.card-trend.up {
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.card-trend.down {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
.charts-section,
|
||||
.quick-stats {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
height: 400rpx;
|
||||
background: #f8f9fa;
|
||||
border-radius: 12rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.chart-placeholder {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.chart-icon {
|
||||
font-size: 80rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.chart-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
background: #f8f9fa;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
font-size: 32rpx;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.stat-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
}
|
||||
@@ -1,88 +1,66 @@
|
||||
// pages/profile/profile.js
|
||||
const auth = require('../../utils/auth.js')
|
||||
|
||||
Page({
|
||||
|
||||
/**
|
||||
* 页面的初始数据
|
||||
*/
|
||||
data: {
|
||||
userInfo: {},
|
||||
menuItems: [
|
||||
{
|
||||
key: 'settings',
|
||||
title: '设置',
|
||||
icon: '⚙️',
|
||||
path: ''
|
||||
},
|
||||
{
|
||||
key: 'about',
|
||||
title: '关于',
|
||||
icon: 'ℹ️',
|
||||
path: ''
|
||||
},
|
||||
{
|
||||
key: 'help',
|
||||
title: '帮助',
|
||||
icon: '❓',
|
||||
path: ''
|
||||
},
|
||||
{
|
||||
key: 'feedback',
|
||||
title: '反馈',
|
||||
icon: '💬',
|
||||
path: ''
|
||||
}
|
||||
]
|
||||
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadUserInfo()
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad(options) {
|
||||
|
||||
},
|
||||
|
||||
loadUserInfo() {
|
||||
const userInfo = auth.getUser()
|
||||
this.setData({
|
||||
userInfo: userInfo || {}
|
||||
})
|
||||
/**
|
||||
* 生命周期函数--监听页面初次渲染完成
|
||||
*/
|
||||
onReady() {
|
||||
|
||||
},
|
||||
|
||||
handleMenuTap(e) {
|
||||
const { key } = e.currentTarget.dataset
|
||||
|
||||
switch (key) {
|
||||
case 'settings':
|
||||
wx.showToast({
|
||||
title: '设置功能待实现',
|
||||
icon: 'none'
|
||||
})
|
||||
break
|
||||
case 'about':
|
||||
wx.showToast({
|
||||
title: '关于功能待实现',
|
||||
icon: 'none'
|
||||
})
|
||||
break
|
||||
case 'help':
|
||||
wx.showToast({
|
||||
title: '帮助功能待实现',
|
||||
icon: 'none'
|
||||
})
|
||||
break
|
||||
case 'feedback':
|
||||
wx.showToast({
|
||||
title: '反馈功能待实现',
|
||||
icon: 'none'
|
||||
})
|
||||
break
|
||||
}
|
||||
/**
|
||||
* 生命周期函数--监听页面显示
|
||||
*/
|
||||
onShow() {
|
||||
|
||||
},
|
||||
|
||||
handleLogout() {
|
||||
wx.showModal({
|
||||
title: '确认退出',
|
||||
content: '确定要退出登录吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
auth.logout()
|
||||
}
|
||||
}
|
||||
})
|
||||
/**
|
||||
* 生命周期函数--监听页面隐藏
|
||||
*/
|
||||
onHide() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面卸载
|
||||
*/
|
||||
onUnload() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面相关事件处理函数--监听用户下拉动作
|
||||
*/
|
||||
onPullDownRefresh() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面上拉触底事件的处理函数
|
||||
*/
|
||||
onReachBottom() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 用户点击右上角分享
|
||||
*/
|
||||
onShareAppMessage() {
|
||||
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1,36 +1,2 @@
|
||||
<!--pages/profile/profile.wxml-->
|
||||
<view class="profile-container">
|
||||
<!-- 用户信息区域 -->
|
||||
<view class="user-section">
|
||||
<view class="user-avatar">
|
||||
<image src="/images/avatar.png" class="avatar-img" />
|
||||
</view>
|
||||
<view class="user-info">
|
||||
<view class="username">{{userInfo.name || '银行管理员'}}</view>
|
||||
<view class="user-role">{{userInfo.role || '客户经理'}}</view>
|
||||
<view class="user-phone">{{userInfo.phone || '13800138000'}}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 功能菜单 -->
|
||||
<view class="menu-section">
|
||||
<view
|
||||
wx:for="{{menuItems}}"
|
||||
wx:key="key"
|
||||
class="menu-item"
|
||||
data-key="{{item.key}}"
|
||||
bindtap="handleMenuTap"
|
||||
>
|
||||
<view class="menu-icon">{{item.icon}}</view>
|
||||
<view class="menu-title">{{item.title}}</view>
|
||||
<view class="menu-arrow">></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 退出登录 -->
|
||||
<view class="logout-section">
|
||||
<button class="logout-btn" bindtap="handleLogout">
|
||||
退出登录
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
<text>pages/profile/profile.wxml</text>
|
||||
@@ -1,103 +0,0 @@
|
||||
/* pages/profile/profile.wxss */
|
||||
.profile-container {
|
||||
min-height: 100vh;
|
||||
background: #f6f6f6;
|
||||
}
|
||||
|
||||
.user-section {
|
||||
background: #fff;
|
||||
padding: 60rpx 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
margin-right: 30rpx;
|
||||
}
|
||||
|
||||
.avatar-img {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 60rpx;
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.username {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.user-role {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.user-phone {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.menu-section {
|
||||
background: #fff;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.menu-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.menu-item:active {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
font-size: 32rpx;
|
||||
margin-right: 24rpx;
|
||||
width: 40rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.menu-title {
|
||||
flex: 1;
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.menu-arrow {
|
||||
font-size: 24rpx;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.logout-section {
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
background: #ff4d4f;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 44rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
133
bank_mini_program/pages/projects/projects.js
Normal file
133
bank_mini_program/pages/projects/projects.js
Normal file
@@ -0,0 +1,133 @@
|
||||
// pages/projects/projects.js
|
||||
const { apiService } = require('../../services/apiService');
|
||||
|
||||
Page({
|
||||
data: {
|
||||
projects: [],
|
||||
activeTab: 'all',
|
||||
loading: false,
|
||||
statusMap: {
|
||||
'supervision': '监管中',
|
||||
'completed': '已结项',
|
||||
'pending': '待监管'
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadProjects();
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.loadProjects();
|
||||
},
|
||||
|
||||
// 加载项目数据
|
||||
async loadProjects() {
|
||||
this.setData({ loading: true });
|
||||
|
||||
try {
|
||||
const params = {
|
||||
page: 1,
|
||||
limit: 20,
|
||||
status: this.data.activeTab === 'all' ? '' : this.data.activeTab
|
||||
};
|
||||
|
||||
const response = await apiService.projects.getList(params);
|
||||
|
||||
if (response.success) {
|
||||
const projects = response.data.projects.map(project => ({
|
||||
...project,
|
||||
statusText: this.data.statusMap[project.status] || project.status,
|
||||
showDeviceInfo: project.status === 'completed' // 只有已结项的项目显示设备信息
|
||||
}));
|
||||
|
||||
this.setData({
|
||||
projects,
|
||||
loading: false
|
||||
});
|
||||
} else {
|
||||
throw new Error(response.message || '获取项目列表失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载项目失败:', error);
|
||||
|
||||
// 使用模拟数据作为降级处理
|
||||
const mockProjects = [
|
||||
{
|
||||
id: 1,
|
||||
farmName: '大数据中心',
|
||||
loanOfficer: '1',
|
||||
status: 'completed',
|
||||
statusText: '已结项',
|
||||
supervisionObject: '牛',
|
||||
supervisionQuantity: 10,
|
||||
supervisionPeriod: '23天',
|
||||
supervisionAmount: '10000.00',
|
||||
startTime: '2024-02-21',
|
||||
endTime: '2024-03-15',
|
||||
earTag: 0,
|
||||
collar: 0,
|
||||
host: 0,
|
||||
showDeviceInfo: true
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
farmName: '大数据中心',
|
||||
loanOfficer: '1',
|
||||
status: 'completed',
|
||||
statusText: '已结项',
|
||||
supervisionObject: '牛',
|
||||
supervisionQuantity: 0,
|
||||
supervisionPeriod: '0天',
|
||||
supervisionAmount: '0.00',
|
||||
startTime: '2024-02-26',
|
||||
endTime: '2024-02-27',
|
||||
earTag: 0,
|
||||
collar: 0,
|
||||
host: 0,
|
||||
showDeviceInfo: false
|
||||
}
|
||||
];
|
||||
|
||||
this.setData({
|
||||
projects: mockProjects,
|
||||
loading: false
|
||||
});
|
||||
|
||||
wx.showToast({
|
||||
title: '使用模拟数据',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 切换标签
|
||||
onTabChange(e) {
|
||||
const tab = e.currentTarget.dataset.tab;
|
||||
this.setData({
|
||||
activeTab: tab
|
||||
});
|
||||
this.loadProjects();
|
||||
},
|
||||
|
||||
// 查看项目详情
|
||||
viewProjectDetail(e) {
|
||||
const projectId = e.currentTarget.dataset.projectId;
|
||||
wx.navigateTo({
|
||||
url: `/pages/projects/detail?projectId=${projectId}`
|
||||
});
|
||||
},
|
||||
|
||||
// 下拉刷新
|
||||
onPullDownRefresh() {
|
||||
this.loadProjects().finally(() => {
|
||||
wx.stopPullDownRefresh();
|
||||
});
|
||||
},
|
||||
|
||||
// 上拉加载更多
|
||||
onReachBottom() {
|
||||
// 这里可以实现分页加载
|
||||
console.log('加载更多项目');
|
||||
}
|
||||
});
|
||||
3
bank_mini_program/pages/projects/projects.json
Normal file
3
bank_mini_program/pages/projects/projects.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"usingComponents": {}
|
||||
}
|
||||
131
bank_mini_program/pages/projects/projects.wxml
Normal file
131
bank_mini_program/pages/projects/projects.wxml
Normal file
@@ -0,0 +1,131 @@
|
||||
<!--pages/projects/projects.wxml-->
|
||||
<view class="projects-container">
|
||||
|
||||
|
||||
<!-- 标题栏 -->
|
||||
<view class="header">
|
||||
<text class="title">项目清单</text>
|
||||
|
||||
</view>
|
||||
|
||||
<!-- 筛选标签 -->
|
||||
<view class="filter-tabs">
|
||||
<view
|
||||
class="tab-item {{activeTab === 'all' ? 'active' : ''}}"
|
||||
data-tab="all"
|
||||
bindtap="onTabChange"
|
||||
>
|
||||
<text>全部</text>
|
||||
</view>
|
||||
<view
|
||||
class="tab-item {{activeTab === 'supervision' ? 'active' : ''}}"
|
||||
data-tab="supervision"
|
||||
bindtap="onTabChange"
|
||||
>
|
||||
<text>监管中</text>
|
||||
</view>
|
||||
<view
|
||||
class="tab-item {{activeTab === 'pending' ? 'active' : ''}}"
|
||||
data-tab="pending"
|
||||
bindtap="onTabChange"
|
||||
>
|
||||
<text>待监管</text>
|
||||
</view>
|
||||
<view
|
||||
class="tab-item {{activeTab === 'completed' ? 'active' : ''}}"
|
||||
data-tab="completed"
|
||||
bindtap="onTabChange"
|
||||
>
|
||||
<text>已结项</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 项目列表 -->
|
||||
<view class="project-list">
|
||||
<view
|
||||
class="project-item"
|
||||
wx:for="{{projects}}"
|
||||
wx:key="id"
|
||||
data-project-id="{{item.id}}"
|
||||
bindtap="viewProjectDetail"
|
||||
>
|
||||
<!-- 项目标题 -->
|
||||
<view class="project-title">{{item.farmName}}</view>
|
||||
|
||||
<!-- 项目信息 -->
|
||||
<view class="project-info">
|
||||
<view class="info-row">
|
||||
<text class="info-label">法人/负责人:</text>
|
||||
<text class="info-value">{{item.loanOfficer || '1'}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="info-label">当前状态:</text>
|
||||
<view class="status-tag {{item.status}}">
|
||||
<text>{{item.statusText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="info-label">监管对象:</text>
|
||||
<text class="info-value">{{item.supervisionObject}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="info-label">监管数量:</text>
|
||||
<text class="info-value">{{item.supervisionQuantity}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="info-label">监管周期:</text>
|
||||
<text class="info-value">{{item.supervisionPeriod}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="info-label">监管金额:</text>
|
||||
<text class="info-value">{{item.supervisionAmount}}元</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="info-label">起止时间:</text>
|
||||
<text class="info-value">{{item.startTime}} 至 {{item.endTime}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 设备信息 -->
|
||||
<view class="device-info" wx:if="{{item.showDeviceInfo}}">
|
||||
<view class="device-row">
|
||||
<text class="device-label">脚环:</text>
|
||||
<text class="device-value">{{item.earTag || 0}}</text>
|
||||
</view>
|
||||
<view class="device-row">
|
||||
<text class="device-label">耳标:</text>
|
||||
<text class="device-value">{{item.earTag || 0}}</text>
|
||||
</view>
|
||||
<view class="device-row">
|
||||
<text class="device-label">项圈:</text>
|
||||
<text class="device-value">{{item.collar || 0}}</text>
|
||||
</view>
|
||||
<view class="device-row">
|
||||
<text class="device-label">主机:</text>
|
||||
<text class="device-value">{{item.host || 0}}</text>
|
||||
</view>
|
||||
<view class="device-row">
|
||||
<text class="device-label">饲喂机:</text>
|
||||
<text class="device-value">0</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading" wx:if="{{loading}}">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" wx:if="{{!loading && projects.length === 0}}">
|
||||
<text class="empty-icon">📋</text>
|
||||
<text class="empty-text">暂无项目数据</text>
|
||||
</view>
|
||||
</view>
|
||||
218
bank_mini_program/pages/projects/projects.wxss
Normal file
218
bank_mini_program/pages/projects/projects.wxss
Normal file
@@ -0,0 +1,218 @@
|
||||
/* pages/projects/projects.wxss */
|
||||
.projects-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding-bottom: 100rpx;
|
||||
}
|
||||
|
||||
/* 状态栏 */
|
||||
.status-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx 30rpx;
|
||||
background-color: #fff;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
}
|
||||
|
||||
.time {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.status-icons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15rpx;
|
||||
}
|
||||
|
||||
.icon, .signal, .wifi, .network, .signal-bars, .battery {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 标题栏 */
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
background-color: #fff;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header-icons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
font-size: 32rpx;
|
||||
color: #666;
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 30rpx;
|
||||
}
|
||||
|
||||
/* 筛选标签 */
|
||||
.filter-tabs {
|
||||
display: flex;
|
||||
background-color: #fff;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
padding: 0 30rpx;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 30rpx 0;
|
||||
position: relative;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.tab-item.active {
|
||||
color: #1890ff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.tab-item.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 60rpx;
|
||||
height: 4rpx;
|
||||
background-color: #1890ff;
|
||||
border-radius: 2rpx;
|
||||
}
|
||||
|
||||
/* 项目列表 */
|
||||
.project-list {
|
||||
padding: 20rpx 30rpx;
|
||||
}
|
||||
|
||||
.project-item {
|
||||
background-color: #fff;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 30rpx;
|
||||
border-radius: 8rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.project-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.project-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #666;
|
||||
min-width: 160rpx;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
font-size: 24rpx;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.status-tag.supervision {
|
||||
background-color: #1890ff;
|
||||
}
|
||||
|
||||
.status-tag.completed {
|
||||
background-color: #52c41a;
|
||||
}
|
||||
|
||||
.status-tag.pending {
|
||||
background-color: #faad14;
|
||||
}
|
||||
|
||||
/* 设备信息 */
|
||||
.device-info {
|
||||
margin-top: 20rpx;
|
||||
padding-top: 20rpx;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.device-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 24rpx;
|
||||
min-width: 120rpx;
|
||||
}
|
||||
|
||||
.device-label {
|
||||
color: #999;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.device-value {
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 60rpx 0;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 0;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 80rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
@@ -1,51 +1,66 @@
|
||||
// pages/reports/reports.js
|
||||
const bankService = require('../../services/bankService.js')
|
||||
|
||||
Page({
|
||||
|
||||
/**
|
||||
* 页面的初始数据
|
||||
*/
|
||||
data: {
|
||||
searchKeyword: '',
|
||||
loading: false,
|
||||
reportsList: []
|
||||
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadReportsData()
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad(options) {
|
||||
|
||||
},
|
||||
|
||||
loadReportsData() {
|
||||
// 模拟数据
|
||||
this.setData({
|
||||
reportsList: [
|
||||
{
|
||||
id: 1,
|
||||
title: '月度财务报表',
|
||||
type: 'monthly',
|
||||
createTime: '2024-01-15 10:00',
|
||||
status: 'completed',
|
||||
statusText: '已完成'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '风险分析报告',
|
||||
type: 'risk',
|
||||
createTime: '2024-01-14 15:30',
|
||||
status: 'pending',
|
||||
statusText: '生成中'
|
||||
}
|
||||
]
|
||||
})
|
||||
/**
|
||||
* 生命周期函数--监听页面初次渲染完成
|
||||
*/
|
||||
onReady() {
|
||||
|
||||
},
|
||||
|
||||
onSearchInput(e) {
|
||||
this.setData({
|
||||
searchKeyword: e.detail.value
|
||||
})
|
||||
/**
|
||||
* 生命周期函数--监听页面显示
|
||||
*/
|
||||
onShow() {
|
||||
|
||||
},
|
||||
|
||||
handleReportTap(e) {
|
||||
const { reportId } = e.currentTarget.dataset
|
||||
wx.navigateTo({
|
||||
url: `/pages/reports/detail?id=${reportId}`
|
||||
})
|
||||
/**
|
||||
* 生命周期函数--监听页面隐藏
|
||||
*/
|
||||
onHide() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面卸载
|
||||
*/
|
||||
onUnload() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面相关事件处理函数--监听用户下拉动作
|
||||
*/
|
||||
onPullDownRefresh() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面上拉触底事件的处理函数
|
||||
*/
|
||||
onReachBottom() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 用户点击右上角分享
|
||||
*/
|
||||
onShareAppMessage() {
|
||||
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1,55 +1,2 @@
|
||||
<!--pages/reports/reports.wxml-->
|
||||
<view class="reports-container">
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-section">
|
||||
<view class="search-bar">
|
||||
<input
|
||||
value="{{searchKeyword}}"
|
||||
type="text"
|
||||
placeholder="搜索报表..."
|
||||
class="search-input"
|
||||
bindinput="onSearchInput"
|
||||
/>
|
||||
<view class="search-icon">🔍</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 报表列表 -->
|
||||
<view class="reports-list">
|
||||
<view class="list-header">
|
||||
<view class="list-title">报表分析</view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{loading}}" class="loading">
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
|
||||
<view wx:elif="{{reportsList.length === 0}}" class="empty">
|
||||
<view class="empty-icon">📈</view>
|
||||
<view class="empty-text">暂无报表记录</view>
|
||||
</view>
|
||||
|
||||
<view wx:else class="list-content">
|
||||
<view
|
||||
wx:for="{{reportsList}}"
|
||||
wx:key="id"
|
||||
class="report-item"
|
||||
data-report-id="{{item.id}}"
|
||||
bindtap="handleReportTap"
|
||||
>
|
||||
<view class="report-icon">
|
||||
<text wx:if="{{item.type === 'monthly'}}">📊</text>
|
||||
<text wx:elif="{{item.type === 'risk'}}">⚠️</text>
|
||||
<text wx:else>📋</text>
|
||||
</view>
|
||||
<view class="report-content">
|
||||
<view class="report-title">{{item.title}}</view>
|
||||
<view class="report-time">{{item.createTime}}</view>
|
||||
</view>
|
||||
<view class="report-status {{item.status}}">
|
||||
{{item.statusText}}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<text>pages/reports/reports.wxml</text>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user