添加银行后端接口,前端代码
This commit is contained in:
164
bank-backend/SUPERVISION_TASKS_INTEGRATION_SUMMARY.md
Normal file
164
bank-backend/SUPERVISION_TASKS_INTEGRATION_SUMMARY.md
Normal file
@@ -0,0 +1,164 @@
|
||||
# 监管任务前后端集成完成总结
|
||||
|
||||
## 🎯 项目概述
|
||||
已成功完成银行端监管任务管理系统的前后端集成,实现了完整的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服务
|
||||
- ✅ 功能丰富的前端界面
|
||||
- ✅ 真实的数据存储和查询
|
||||
- ✅ 完善的错误处理
|
||||
- ✅ 用户友好的交互体验
|
||||
|
||||
系统现在可以正常使用,支持监管任务的完整生命周期管理。
|
||||
460
bank-backend/controllers/projectController.js
Normal file
460
bank-backend/controllers/projectController.js
Normal file
@@ -0,0 +1,460 @@
|
||||
const { Project, User } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// 获取项目列表
|
||||
const getProjects = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 10,
|
||||
search = '',
|
||||
status = '',
|
||||
sortBy = 'createdAt',
|
||||
sortOrder = 'DESC'
|
||||
} = req.query;
|
||||
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
// 构建查询条件
|
||||
const where = {};
|
||||
|
||||
// 搜索条件
|
||||
if (search) {
|
||||
where[Op.or] = [
|
||||
{ name: { [Op.like]: `%${search}%` } },
|
||||
{ farmName: { [Op.like]: `%${search}%` } },
|
||||
{ loanOfficer: { [Op.like]: `%${search}%` } }
|
||||
];
|
||||
}
|
||||
|
||||
// 状态筛选
|
||||
if (status) {
|
||||
where.status = status;
|
||||
}
|
||||
|
||||
// 查询项目列表
|
||||
const { count, rows: projects } = await Project.findAndCountAll({
|
||||
where,
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'creator',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: 'updater',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
}
|
||||
],
|
||||
order: [[sortBy, sortOrder.toUpperCase()]],
|
||||
limit: parseInt(limit),
|
||||
offset: parseInt(offset)
|
||||
});
|
||||
|
||||
// 计算分页信息
|
||||
const totalPages = Math.ceil(count / limit);
|
||||
const hasNextPage = page < totalPages;
|
||||
const hasPrevPage = page > 1;
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
projects,
|
||||
pagination: {
|
||||
current: parseInt(page),
|
||||
pageSize: parseInt(limit),
|
||||
total: count,
|
||||
totalPages,
|
||||
hasNextPage,
|
||||
hasPrevPage
|
||||
}
|
||||
},
|
||||
message: '获取项目列表成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取项目列表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取项目列表失败',
|
||||
error: process.env.NODE_ENV === 'development' ? error.message : undefined
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 获取项目详情
|
||||
const getProjectById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const project = await Project.findByPk(id, {
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'creator',
|
||||
attributes: ['id', 'username', 'name']
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: 'updater',
|
||||
attributes: ['id', 'username', 'name']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!project) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '项目不存在'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: project,
|
||||
message: '获取项目详情成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取项目详情失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取项目详情失败',
|
||||
error: process.env.NODE_ENV === 'development' ? error.message : undefined
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 创建项目
|
||||
const createProject = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
name,
|
||||
status = 'supervision',
|
||||
farmName,
|
||||
supervisionObject,
|
||||
supervisionQuantity = 0,
|
||||
supervisionPeriod,
|
||||
supervisionAmount = 0.00,
|
||||
startTime,
|
||||
endTime,
|
||||
earTag = 0,
|
||||
collar = 0,
|
||||
host = 0,
|
||||
loanOfficer,
|
||||
description
|
||||
} = req.body;
|
||||
|
||||
// 验证必填字段
|
||||
const requiredFields = ['name', 'farmName', 'supervisionObject', 'supervisionPeriod', 'startTime', 'endTime'];
|
||||
const missingFields = requiredFields.filter(field => !req.body[field]);
|
||||
|
||||
if (missingFields.length > 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: `请填写所有必填字段: ${missingFields.join(', ')}`
|
||||
});
|
||||
}
|
||||
|
||||
// 验证字段长度
|
||||
if (name.length > 100) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '项目名称不能超过100个字符'
|
||||
});
|
||||
}
|
||||
|
||||
if (farmName.length > 200) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '养殖场名称不能超过200个字符'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证数值字段
|
||||
if (supervisionQuantity < 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '监管数量不能为负数'
|
||||
});
|
||||
}
|
||||
|
||||
if (supervisionAmount < 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '监管金额不能为负数'
|
||||
});
|
||||
}
|
||||
|
||||
if (earTag < 0 || collar < 0 || host < 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '设备数量不能为负数'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证状态
|
||||
if (!['supervision', 'completed'].includes(status)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '项目状态只能是 supervision 或 completed'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证日期格式和逻辑
|
||||
const startDate = new Date(startTime);
|
||||
const endDate = new Date(endTime);
|
||||
|
||||
if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '日期格式不正确'
|
||||
});
|
||||
}
|
||||
|
||||
if (startDate >= endDate) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '结束时间必须晚于开始时间'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查项目名称是否已存在
|
||||
const existingProject = await Project.findOne({
|
||||
where: { name }
|
||||
});
|
||||
|
||||
if (existingProject) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '项目名称已存在,请使用其他名称'
|
||||
});
|
||||
}
|
||||
|
||||
// 创建项目
|
||||
const project = await Project.create({
|
||||
name,
|
||||
status,
|
||||
farmName,
|
||||
supervisionObject,
|
||||
supervisionQuantity: parseInt(supervisionQuantity),
|
||||
supervisionPeriod,
|
||||
supervisionAmount: parseFloat(supervisionAmount),
|
||||
startTime,
|
||||
endTime,
|
||||
earTag: parseInt(earTag),
|
||||
collar: parseInt(collar),
|
||||
host: parseInt(host),
|
||||
loanOfficer,
|
||||
description,
|
||||
createdBy: req.user?.userId || req.user?.id,
|
||||
updatedBy: req.user?.userId || req.user?.id
|
||||
});
|
||||
|
||||
// 获取创建的项目详情(包含关联信息)
|
||||
const createdProject = await Project.findByPk(project.id, {
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'creator',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: 'updater',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: createdProject,
|
||||
message: '创建项目成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('创建项目失败:', error);
|
||||
|
||||
// 处理数据库约束错误
|
||||
if (error.name === 'SequelizeValidationError') {
|
||||
const validationErrors = error.errors.map(err => err.message).join(', ');
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: `数据验证失败: ${validationErrors}`
|
||||
});
|
||||
}
|
||||
|
||||
if (error.name === 'SequelizeUniqueConstraintError') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '项目名称已存在,请使用其他名称'
|
||||
});
|
||||
}
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '创建项目失败',
|
||||
error: process.env.NODE_ENV === 'development' ? error.message : undefined
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 更新项目
|
||||
const updateProject = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const updateData = req.body;
|
||||
|
||||
// 验证日期
|
||||
if (updateData.startTime && updateData.endTime) {
|
||||
if (new Date(updateData.startTime) >= new Date(updateData.endTime)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '结束时间必须晚于开始时间'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const project = await Project.findByPk(id);
|
||||
if (!project) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '项目不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 更新项目
|
||||
await project.update({
|
||||
...updateData,
|
||||
updatedBy: req.user?.id
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: project,
|
||||
message: '更新项目成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('更新项目失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '更新项目失败',
|
||||
error: process.env.NODE_ENV === 'development' ? error.message : undefined
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 删除项目
|
||||
const deleteProject = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const project = await Project.findByPk(id);
|
||||
if (!project) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '项目不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await project.destroy();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '删除项目成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('删除项目失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '删除项目失败',
|
||||
error: process.env.NODE_ENV === 'development' ? error.message : undefined
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 获取项目统计
|
||||
const getProjectStats = async (req, res) => {
|
||||
try {
|
||||
const totalProjects = await Project.count();
|
||||
const supervisionProjects = await Project.count({ where: { status: 'supervision' } });
|
||||
const completedProjects = await Project.count({ where: { status: 'completed' } });
|
||||
|
||||
// 计算总监管金额
|
||||
const totalAmount = await Project.sum('supervisionAmount');
|
||||
|
||||
// 计算总监管数量
|
||||
const totalQuantity = await Project.sum('supervisionQuantity');
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
total: totalProjects,
|
||||
supervision: supervisionProjects,
|
||||
completed: completedProjects,
|
||||
totalAmount: totalAmount || 0,
|
||||
totalQuantity: totalQuantity || 0
|
||||
},
|
||||
message: '获取项目统计成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取项目统计失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取项目统计失败',
|
||||
error: process.env.NODE_ENV === 'development' ? error.message : undefined
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 批量更新项目状态
|
||||
const batchUpdateStatus = async (req, res) => {
|
||||
try {
|
||||
const { projectIds, status } = req.body;
|
||||
|
||||
if (!projectIds || !Array.isArray(projectIds) || projectIds.length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请选择要更新的项目'
|
||||
});
|
||||
}
|
||||
|
||||
if (!['supervision', 'completed'].includes(status)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '无效的项目状态'
|
||||
});
|
||||
}
|
||||
|
||||
await Project.update(
|
||||
{
|
||||
status,
|
||||
updatedBy: req.user?.id
|
||||
},
|
||||
{
|
||||
where: { id: { [Op.in]: projectIds } }
|
||||
}
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `成功更新 ${projectIds.length} 个项目的状态`
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('批量更新项目状态失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '批量更新项目状态失败',
|
||||
error: process.env.NODE_ENV === 'development' ? error.message : undefined
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getProjects,
|
||||
getProjectById,
|
||||
createProject,
|
||||
updateProject,
|
||||
deleteProject,
|
||||
getProjectStats,
|
||||
batchUpdateStatus
|
||||
};
|
||||
573
bank-backend/controllers/supervisionTaskController.js
Normal file
573
bank-backend/controllers/supervisionTaskController.js
Normal file
@@ -0,0 +1,573 @@
|
||||
/**
|
||||
* 监管任务控制器
|
||||
* @file supervisionTaskController.js
|
||||
* @description 监管任务相关的API控制器
|
||||
*/
|
||||
const { SupervisionTask, User } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// 获取监管任务列表
|
||||
const getSupervisionTasks = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 10,
|
||||
search = '',
|
||||
supervisionStatus = '',
|
||||
dateRange = '',
|
||||
sortBy = 'createdAt',
|
||||
sortOrder = 'DESC'
|
||||
} = req.query;
|
||||
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
// 构建查询条件
|
||||
const where = {};
|
||||
|
||||
// 搜索条件
|
||||
if (search) {
|
||||
where[Op.or] = [
|
||||
{ applicationNumber: { [Op.like]: `%${search}%` } },
|
||||
{ contractNumber: { [Op.like]: `%${search}%` } },
|
||||
{ customerName: { [Op.like]: `%${search}%` } },
|
||||
{ productName: { [Op.like]: `%${search}%` } }
|
||||
];
|
||||
}
|
||||
|
||||
// 状态筛选
|
||||
if (supervisionStatus) {
|
||||
where.supervisionStatus = supervisionStatus;
|
||||
}
|
||||
|
||||
// 日期范围筛选
|
||||
if (dateRange) {
|
||||
const [startDate, endDate] = dateRange.split(',');
|
||||
if (startDate && endDate) {
|
||||
where.importTime = {
|
||||
[Op.between]: [new Date(startDate), new Date(endDate)]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 查询监管任务列表
|
||||
const { count, rows: tasks } = await SupervisionTask.findAndCountAll({
|
||||
where,
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'creator',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: 'updater',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
}
|
||||
],
|
||||
order: [[sortBy, sortOrder.toUpperCase()]],
|
||||
limit: parseInt(limit),
|
||||
offset: parseInt(offset)
|
||||
});
|
||||
|
||||
// 计算分页信息
|
||||
const totalPages = Math.ceil(count / limit);
|
||||
const hasNextPage = page < totalPages;
|
||||
const hasPrevPage = page > 1;
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: '监管任务列表获取成功',
|
||||
data: {
|
||||
tasks,
|
||||
pagination: {
|
||||
total: count,
|
||||
currentPage: parseInt(page),
|
||||
pageSize: parseInt(limit),
|
||||
totalPages,
|
||||
hasNextPage,
|
||||
hasPrevPage
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取监管任务列表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取监管任务列表失败',
|
||||
error: process.env.NODE_ENV === 'development' ? error.message : undefined
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 获取监管任务详情
|
||||
const getSupervisionTaskById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const task = await SupervisionTask.findByPk(id, {
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'creator',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: 'updater',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!task) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '监管任务不存在'
|
||||
});
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: '监管任务详情获取成功',
|
||||
data: task
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取监管任务详情失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取监管任务详情失败',
|
||||
error: process.env.NODE_ENV === 'development' ? error.message : undefined
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 创建监管任务
|
||||
const createSupervisionTask = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
applicationNumber,
|
||||
contractNumber,
|
||||
productName,
|
||||
customerName,
|
||||
idType = 'id_card',
|
||||
idNumber,
|
||||
assetType = 'cattle',
|
||||
assetQuantity = 0,
|
||||
supervisionStatus = 'pending',
|
||||
startTime,
|
||||
endTime,
|
||||
loanAmount = 0,
|
||||
interestRate = 0,
|
||||
loanTerm = 12,
|
||||
supervisorName,
|
||||
supervisorPhone,
|
||||
farmAddress,
|
||||
remarks
|
||||
} = req.body;
|
||||
|
||||
// 验证必填字段
|
||||
const requiredFields = ['applicationNumber', 'contractNumber', 'productName', 'customerName', 'idNumber', 'startTime', 'endTime'];
|
||||
const missingFields = requiredFields.filter(field => !req.body[field]);
|
||||
|
||||
if (missingFields.length > 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: `请填写所有必填字段: ${missingFields.join(', ')}`
|
||||
});
|
||||
}
|
||||
|
||||
// 验证字段长度
|
||||
if (applicationNumber.length > 50) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '申请单号不能超过50个字符'
|
||||
});
|
||||
}
|
||||
|
||||
if (contractNumber.length > 50) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '放款合同编号不能超过50个字符'
|
||||
});
|
||||
}
|
||||
|
||||
if (productName.length > 100) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '产品名称不能超过100个字符'
|
||||
});
|
||||
}
|
||||
|
||||
if (customerName.length > 50) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '客户姓名不能超过50个字符'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证数值字段
|
||||
if (assetQuantity < 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '监管生资数量不能为负数'
|
||||
});
|
||||
}
|
||||
|
||||
if (loanAmount < 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '贷款金额不能为负数'
|
||||
});
|
||||
}
|
||||
|
||||
if (interestRate < 0 || interestRate > 1) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '利率必须在0-1之间'
|
||||
});
|
||||
}
|
||||
|
||||
if (loanTerm < 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '贷款期限不能为负数'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证状态和类型
|
||||
if (!['pending', 'supervising', 'completed', 'suspended'].includes(supervisionStatus)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '监管状态无效'
|
||||
});
|
||||
}
|
||||
|
||||
if (!['id_card', 'passport', 'other'].includes(idType)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '证件类型无效'
|
||||
});
|
||||
}
|
||||
|
||||
if (!['cattle', 'sheep', 'pig', 'poultry', 'other'].includes(assetType)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '养殖生资种类无效'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证日期格式和逻辑
|
||||
const startDate = new Date(startTime);
|
||||
const endDate = new Date(endTime);
|
||||
|
||||
if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '日期格式不正确'
|
||||
});
|
||||
}
|
||||
|
||||
if (startDate >= endDate) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '监管结束时间必须晚于开始时间'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查申请单号和合同编号是否已存在
|
||||
const existingTask = await SupervisionTask.findOne({
|
||||
where: {
|
||||
[Op.or]: [
|
||||
{ applicationNumber },
|
||||
{ contractNumber }
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
if (existingTask) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: existingTask.applicationNumber === applicationNumber ?
|
||||
'申请单号已存在' : '放款合同编号已存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 创建监管任务
|
||||
const task = await SupervisionTask.create({
|
||||
applicationNumber,
|
||||
contractNumber,
|
||||
productName,
|
||||
customerName,
|
||||
idType,
|
||||
idNumber,
|
||||
assetType,
|
||||
assetQuantity: parseInt(assetQuantity),
|
||||
supervisionStatus,
|
||||
startTime,
|
||||
endTime,
|
||||
loanAmount: parseFloat(loanAmount),
|
||||
interestRate: parseFloat(interestRate),
|
||||
loanTerm: parseInt(loanTerm),
|
||||
supervisorName,
|
||||
supervisorPhone,
|
||||
farmAddress,
|
||||
remarks,
|
||||
createdBy: req.user?.userId || req.user?.id,
|
||||
updatedBy: req.user?.userId || req.user?.id
|
||||
});
|
||||
|
||||
// 获取创建的任务详情(包含关联信息)
|
||||
const createdTask = await SupervisionTask.findByPk(task.id, {
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'creator',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: 'updater',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: createdTask,
|
||||
message: '监管任务创建成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('创建监管任务失败:', error);
|
||||
|
||||
// 处理数据库约束错误
|
||||
if (error.name === 'SequelizeValidationError') {
|
||||
const validationErrors = error.errors.map(err => err.message).join(', ');
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: `数据验证失败: ${validationErrors}`
|
||||
});
|
||||
}
|
||||
|
||||
if (error.name === 'SequelizeUniqueConstraintError') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '申请单号或放款合同编号已存在'
|
||||
});
|
||||
}
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '创建监管任务失败',
|
||||
error: process.env.NODE_ENV === 'development' ? error.message : undefined
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 更新监管任务
|
||||
const updateSupervisionTask = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const updateData = req.body;
|
||||
|
||||
// 验证日期
|
||||
if (updateData.startTime && updateData.endTime) {
|
||||
const startDate = new Date(updateData.startTime);
|
||||
const endDate = new Date(updateData.endTime);
|
||||
if (startDate >= endDate) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '监管结束时间必须晚于开始时间'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const task = await SupervisionTask.findByPk(id);
|
||||
|
||||
if (!task) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '监管任务不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 如果更新申请单号或合同编号,检查是否重复
|
||||
if (updateData.applicationNumber || updateData.contractNumber) {
|
||||
const whereCondition = { id: { [Op.ne]: id } };
|
||||
if (updateData.applicationNumber) {
|
||||
whereCondition.applicationNumber = updateData.applicationNumber;
|
||||
}
|
||||
if (updateData.contractNumber) {
|
||||
whereCondition.contractNumber = updateData.contractNumber;
|
||||
}
|
||||
|
||||
const existingTask = await SupervisionTask.findOne({ where: whereCondition });
|
||||
if (existingTask) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '申请单号或放款合同编号已存在'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await task.update({
|
||||
...updateData,
|
||||
updatedBy: req.user?.userId || req.user?.id
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: task,
|
||||
message: '监管任务更新成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('更新监管任务失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '更新监管任务失败',
|
||||
error: process.env.NODE_ENV === 'development' ? error.message : undefined
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 删除监管任务
|
||||
const deleteSupervisionTask = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const task = await SupervisionTask.findByPk(id);
|
||||
|
||||
if (!task) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '监管任务不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await task.destroy();
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: '监管任务删除成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('删除监管任务失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '删除监管任务失败',
|
||||
error: process.env.NODE_ENV === 'development' ? error.message : undefined
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 获取监管任务统计
|
||||
const getSupervisionTaskStats = async (req, res) => {
|
||||
try {
|
||||
const total = await SupervisionTask.count();
|
||||
const pending = await SupervisionTask.count({ where: { supervisionStatus: 'pending' } });
|
||||
const supervising = await SupervisionTask.count({ where: { supervisionStatus: 'supervising' } });
|
||||
const completed = await SupervisionTask.count({ where: { supervisionStatus: 'completed' } });
|
||||
const suspended = await SupervisionTask.count({ where: { supervisionStatus: 'suspended' } });
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: '监管任务统计获取成功',
|
||||
data: {
|
||||
total,
|
||||
pending,
|
||||
supervising,
|
||||
completed,
|
||||
suspended
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取监管任务统计失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取监管任务统计失败',
|
||||
error: process.env.NODE_ENV === 'development' ? error.message : undefined
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 批量更新监管任务状态
|
||||
const batchUpdateStatus = async (req, res) => {
|
||||
try {
|
||||
const { ids, supervisionStatus } = req.body;
|
||||
|
||||
if (!Array.isArray(ids) || ids.length === 0 || !supervisionStatus) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请求参数无效'
|
||||
});
|
||||
}
|
||||
|
||||
if (!['pending', 'supervising', 'completed', 'suspended'].includes(supervisionStatus)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '监管状态无效'
|
||||
});
|
||||
}
|
||||
|
||||
const [updatedCount] = await SupervisionTask.update(
|
||||
{
|
||||
supervisionStatus,
|
||||
updatedBy: req.user?.userId || req.user?.id
|
||||
},
|
||||
{ where: { id: { [Op.in]: ids } } }
|
||||
);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: `成功更新 ${updatedCount} 个监管任务状态`,
|
||||
data: { updatedCount }
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('批量更新监管任务状态失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '批量更新监管任务状态失败',
|
||||
error: process.env.NODE_ENV === 'development' ? error.message : undefined
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 批量删除监管任务
|
||||
const batchDeleteTasks = async (req, res) => {
|
||||
try {
|
||||
const { ids } = req.body;
|
||||
|
||||
if (!Array.isArray(ids) || ids.length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请求参数无效'
|
||||
});
|
||||
}
|
||||
|
||||
const deletedCount = await SupervisionTask.destroy({
|
||||
where: { id: { [Op.in]: ids } }
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: `成功删除 ${deletedCount} 个监管任务`,
|
||||
data: { deletedCount }
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('批量删除监管任务失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '批量删除监管任务失败',
|
||||
error: process.env.NODE_ENV === 'development' ? error.message : undefined
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getSupervisionTasks,
|
||||
getSupervisionTaskById,
|
||||
createSupervisionTask,
|
||||
updateSupervisionTask,
|
||||
deleteSupervisionTask,
|
||||
getSupervisionTaskStats,
|
||||
batchUpdateStatus,
|
||||
batchDeleteTasks
|
||||
};
|
||||
403
bank-backend/docs/PROJECT_API.md
Normal file
403
bank-backend/docs/PROJECT_API.md
Normal file
@@ -0,0 +1,403 @@
|
||||
# 项目清单 API 接口文档
|
||||
|
||||
## 概述
|
||||
项目清单管理系统的后端API接口,提供项目的增删改查功能。
|
||||
|
||||
## 基础信息
|
||||
- **基础URL**: `http://localhost:5351/api/projects`
|
||||
- **认证方式**: Bearer Token
|
||||
- **内容类型**: `application/json`
|
||||
|
||||
## 接口列表
|
||||
|
||||
### 1. 创建项目
|
||||
**POST** `/api/projects`
|
||||
|
||||
#### 请求头
|
||||
```
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
#### 请求参数
|
||||
| 字段名 | 类型 | 必填 | 说明 | 示例 |
|
||||
|--------|------|------|------|------|
|
||||
| name | string | 是 | 项目名称,最大100字符 | "张洪彬养殖项目" |
|
||||
| farmName | string | 是 | 养殖场名称,最大200字符 | "张洪彬养殖场" |
|
||||
| supervisionObject | string | 是 | 监管对象 | "牛" |
|
||||
| supervisionPeriod | string | 是 | 监管周期 | "12个月" |
|
||||
| startTime | string | 是 | 起始时间,格式:YYYY-MM-DD | "2024-01-01" |
|
||||
| endTime | string | 是 | 结束时间,格式:YYYY-MM-DD | "2024-12-31" |
|
||||
| status | string | 否 | 项目状态,默认:supervision | "supervision" 或 "completed" |
|
||||
| supervisionQuantity | number | 否 | 监管数量,默认:0 | 100 |
|
||||
| supervisionAmount | number | 否 | 监管金额,默认:0.00 | 500000.00 |
|
||||
| earTag | number | 否 | 耳标数量,默认:0 | 50 |
|
||||
| collar | number | 否 | 项圈数量,默认:0 | 30 |
|
||||
| host | number | 否 | 主机数量,默认:0 | 20 |
|
||||
| loanOfficer | string | 否 | 贷款专员 | "张专员" |
|
||||
| description | string | 否 | 项目描述 | "这是一个测试项目" |
|
||||
|
||||
#### 请求示例
|
||||
```json
|
||||
{
|
||||
"name": "张洪彬养殖项目",
|
||||
"farmName": "张洪彬养殖场",
|
||||
"supervisionObject": "牛",
|
||||
"supervisionPeriod": "12个月",
|
||||
"startTime": "2024-01-01",
|
||||
"endTime": "2024-12-31",
|
||||
"status": "supervision",
|
||||
"supervisionQuantity": 100,
|
||||
"supervisionAmount": 500000.00,
|
||||
"earTag": 50,
|
||||
"collar": 30,
|
||||
"host": 20,
|
||||
"loanOfficer": "张专员",
|
||||
"description": "这是一个测试项目"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
**成功响应 (201)**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "创建项目成功",
|
||||
"data": {
|
||||
"id": 9,
|
||||
"name": "张洪彬养殖项目",
|
||||
"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": "这是一个测试项目",
|
||||
"createdBy": 1,
|
||||
"updatedBy": 1,
|
||||
"createdAt": "2024-12-20T10:30:00.000Z",
|
||||
"updatedAt": "2024-12-20T10:30:00.000Z",
|
||||
"creator": {
|
||||
"id": 1,
|
||||
"username": "admin",
|
||||
"real_name": "管理员"
|
||||
},
|
||||
"updater": {
|
||||
"id": 1,
|
||||
"username": "admin",
|
||||
"real_name": "管理员"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应 (400)**
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "请填写所有必填字段: name, farmName"
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应 (400)**
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "项目名称已存在,请使用其他名称"
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应 (400)**
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "结束时间必须晚于开始时间"
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 获取项目列表
|
||||
**GET** `/api/projects`
|
||||
|
||||
#### 查询参数
|
||||
| 参数名 | 类型 | 必填 | 说明 | 示例 |
|
||||
|--------|------|------|------|------|
|
||||
| page | number | 否 | 页码,默认:1 | 1 |
|
||||
| limit | number | 否 | 每页数量,默认:10 | 12 |
|
||||
| search | string | 否 | 搜索关键词 | "张洪彬" |
|
||||
| status | string | 否 | 状态筛选 | "supervision" 或 "completed" |
|
||||
| sortBy | string | 否 | 排序字段,默认:createdAt | "name" |
|
||||
| sortOrder | string | 否 | 排序方向,默认:DESC | "ASC" 或 "DESC" |
|
||||
|
||||
#### 请求示例
|
||||
```
|
||||
GET /api/projects?page=1&limit=12&search=张洪彬&status=supervision&sortBy=name&sortOrder=ASC
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "获取项目列表成功",
|
||||
"data": {
|
||||
"projects": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "张洪彬",
|
||||
"status": "completed",
|
||||
"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": "项目描述",
|
||||
"createdAt": "2024-12-20T10:30:00.000Z",
|
||||
"updatedAt": "2024-12-20T10:30:00.000Z",
|
||||
"creator": {
|
||||
"id": 1,
|
||||
"username": "admin",
|
||||
"real_name": "管理员"
|
||||
},
|
||||
"updater": {
|
||||
"id": 1,
|
||||
"username": "admin",
|
||||
"real_name": "管理员"
|
||||
}
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"total": 8,
|
||||
"currentPage": 1,
|
||||
"pageSize": 12,
|
||||
"totalPages": 1,
|
||||
"hasNextPage": false,
|
||||
"hasPrevPage": false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 获取项目详情
|
||||
**GET** `/api/projects/:id`
|
||||
|
||||
#### 路径参数
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| id | number | 是 | 项目ID |
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "获取项目详情成功",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"name": "张洪彬",
|
||||
"status": "completed",
|
||||
"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": "项目描述",
|
||||
"createdAt": "2024-12-20T10:30:00.000Z",
|
||||
"updatedAt": "2024-12-20T10:30:00.000Z",
|
||||
"creator": {
|
||||
"id": 1,
|
||||
"username": "admin",
|
||||
"real_name": "管理员"
|
||||
},
|
||||
"updater": {
|
||||
"id": 1,
|
||||
"username": "admin",
|
||||
"real_name": "管理员"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 更新项目
|
||||
**PUT** `/api/projects/:id`
|
||||
|
||||
#### 请求参数
|
||||
与创建项目相同,所有字段都是可选的。
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "项目更新成功",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"name": "更新后的项目名称",
|
||||
"status": "completed",
|
||||
// ... 其他字段
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 删除项目
|
||||
**DELETE** `/api/projects/:id`
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "项目删除成功"
|
||||
}
|
||||
```
|
||||
|
||||
### 6. 获取项目统计
|
||||
**GET** `/api/projects/stats`
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "项目统计获取成功",
|
||||
"data": {
|
||||
"total": 8,
|
||||
"supervision": 3,
|
||||
"completed": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7. 批量更新项目状态
|
||||
**PUT** `/api/projects/batch/status`
|
||||
|
||||
#### 请求参数
|
||||
```json
|
||||
{
|
||||
"ids": [1, 2, 3],
|
||||
"status": "completed"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "成功更新 3 个项目状态",
|
||||
"data": {
|
||||
"updatedCount": 3
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 错误码说明
|
||||
|
||||
| 状态码 | 说明 |
|
||||
|--------|------|
|
||||
| 200 | 请求成功 |
|
||||
| 201 | 创建成功 |
|
||||
| 400 | 请求参数错误 |
|
||||
| 401 | 未授权,需要登录 |
|
||||
| 403 | 禁止访问,权限不足 |
|
||||
| 404 | 资源不存在 |
|
||||
| 500 | 服务器内部错误 |
|
||||
|
||||
## 数据验证规则
|
||||
|
||||
### 必填字段验证
|
||||
- `name`: 项目名称,不能为空
|
||||
- `farmName`: 养殖场名称,不能为空
|
||||
- `supervisionObject`: 监管对象,不能为空
|
||||
- `supervisionPeriod`: 监管周期,不能为空
|
||||
- `startTime`: 起始时间,不能为空
|
||||
- `endTime`: 结束时间,不能为空
|
||||
|
||||
### 字段长度限制
|
||||
- `name`: 最大100个字符
|
||||
- `farmName`: 最大200个字符
|
||||
- `supervisionObject`: 最大50个字符
|
||||
- `supervisionPeriod`: 最大50个字符
|
||||
- `loanOfficer`: 最大100个字符
|
||||
|
||||
### 数值字段验证
|
||||
- `supervisionQuantity`: 不能为负数
|
||||
- `supervisionAmount`: 不能为负数
|
||||
- `earTag`: 不能为负数
|
||||
- `collar`: 不能为负数
|
||||
- `host`: 不能为负数
|
||||
|
||||
### 日期验证
|
||||
- `startTime` 和 `endTime` 必须是有效的日期格式 (YYYY-MM-DD)
|
||||
- `endTime` 必须晚于 `startTime`
|
||||
|
||||
### 状态验证
|
||||
- `status` 只能是 "supervision" 或 "completed"
|
||||
|
||||
## 使用示例
|
||||
|
||||
### JavaScript (axios)
|
||||
```javascript
|
||||
// 创建项目
|
||||
const createProject = async (projectData) => {
|
||||
try {
|
||||
const response = await axios.post('/api/projects', projectData, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('创建项目失败:', error.response.data);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// 获取项目列表
|
||||
const getProjects = async (params = {}) => {
|
||||
try {
|
||||
const response = await axios.get('/api/projects', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
params
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('获取项目列表失败:', error.response.data);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### cURL
|
||||
```bash
|
||||
# 创建项目
|
||||
curl -X POST http://localhost:5351/api/projects \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "测试项目",
|
||||
"farmName": "测试养殖场",
|
||||
"supervisionObject": "牛",
|
||||
"supervisionPeriod": "12个月",
|
||||
"startTime": "2024-01-01",
|
||||
"endTime": "2024-12-31"
|
||||
}'
|
||||
|
||||
# 获取项目列表
|
||||
curl -X GET "http://localhost:5351/api/projects?page=1&limit=10" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
459
bank-backend/docs/SUPERVISION_TASKS_API.md
Normal file
459
bank-backend/docs/SUPERVISION_TASKS_API.md
Normal file
@@ -0,0 +1,459 @@
|
||||
# 监管任务 API 接口文档
|
||||
|
||||
## 概述
|
||||
监管任务管理系统的后端API接口,提供监管任务的增删改查功能。
|
||||
|
||||
## 基础信息
|
||||
- **基础URL**: `http://localhost:5351/api/supervision-tasks`
|
||||
- **认证方式**: Bearer Token
|
||||
- **内容类型**: `application/json`
|
||||
|
||||
## 接口列表
|
||||
|
||||
### 1. 创建监管任务
|
||||
**POST** `/api/supervision-tasks`
|
||||
|
||||
#### 请求头
|
||||
```
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
#### 请求参数
|
||||
| 字段名 | 类型 | 必填 | 说明 | 示例 |
|
||||
|--------|------|------|------|------|
|
||||
| applicationNumber | string | 是 | 申请单号,最大50字符,唯一 | "APP001" |
|
||||
| contractNumber | string | 是 | 放款合同编号,最大50字符,唯一 | "CONTRACT001" |
|
||||
| productName | string | 是 | 产品名称,最大100字符 | "农业贷款产品A" |
|
||||
| customerName | string | 是 | 客户姓名,最大50字符 | "张三" |
|
||||
| idType | string | 是 | 证件类型 | "id_card" (身份证), "passport" (护照), "other" (其他) |
|
||||
| idNumber | string | 是 | 证件号码,最大50字符 | "110101199001011234" |
|
||||
| assetType | string | 是 | 养殖生资种类 | "cattle" (牛), "sheep" (羊), "pig" (猪), "poultry" (家禽), "other" (其他) |
|
||||
| assetQuantity | number | 是 | 监管生资数量,非负数 | 10 |
|
||||
| startTime | string | 是 | 监管起始时间,格式:YYYY-MM-DD | "2024-01-15" |
|
||||
| endTime | string | 是 | 监管结束时间,格式:YYYY-MM-DD | "2024-12-15" |
|
||||
| supervisionStatus | string | 否 | 监管状态,默认:pending | "pending", "supervising", "completed", "suspended" |
|
||||
| loanAmount | number | 否 | 贷款金额,默认:0 | 500000.00 |
|
||||
| interestRate | number | 否 | 利率,默认:0,范围:0-1 | 0.0650 |
|
||||
| loanTerm | number | 否 | 贷款期限(月),默认:12 | 12 |
|
||||
| supervisorName | string | 否 | 监管员姓名,最大50字符 | "李监管员" |
|
||||
| supervisorPhone | string | 否 | 监管员电话,最大20字符 | "13800138001" |
|
||||
| farmAddress | string | 否 | 养殖场地址,最大200字符 | "北京市朝阳区某某养殖场" |
|
||||
| remarks | string | 否 | 备注 | "重点监管项目,需要定期检查" |
|
||||
|
||||
#### 请求示例
|
||||
```json
|
||||
{
|
||||
"applicationNumber": "APP001",
|
||||
"contractNumber": "CONTRACT001",
|
||||
"productName": "农业贷款产品A",
|
||||
"customerName": "张三",
|
||||
"idType": "id_card",
|
||||
"idNumber": "110101199001011234",
|
||||
"assetType": "cattle",
|
||||
"assetQuantity": 10,
|
||||
"supervisionStatus": "pending",
|
||||
"startTime": "2024-01-15",
|
||||
"endTime": "2024-12-15",
|
||||
"loanAmount": 500000.00,
|
||||
"interestRate": 0.0650,
|
||||
"loanTerm": 12,
|
||||
"supervisorName": "李监管员",
|
||||
"supervisorPhone": "13800138001",
|
||||
"farmAddress": "北京市朝阳区某某养殖场",
|
||||
"remarks": "重点监管项目,需要定期检查"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
**成功响应 (201)**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "监管任务创建成功",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"applicationNumber": "APP001",
|
||||
"contractNumber": "CONTRACT001",
|
||||
"productName": "农业贷款产品A",
|
||||
"customerName": "张三",
|
||||
"idType": "id_card",
|
||||
"idNumber": "110101199001011234",
|
||||
"assetType": "cattle",
|
||||
"assetQuantity": 10,
|
||||
"supervisionStatus": "pending",
|
||||
"importTime": "2024-12-20T10:30:00.000Z",
|
||||
"startTime": "2024-01-15",
|
||||
"endTime": "2024-12-15",
|
||||
"loanAmount": "500000.00",
|
||||
"interestRate": "0.0650",
|
||||
"loanTerm": 12,
|
||||
"supervisorName": "李监管员",
|
||||
"supervisorPhone": "13800138001",
|
||||
"farmAddress": "北京市朝阳区某某养殖场",
|
||||
"remarks": "重点监管项目,需要定期检查",
|
||||
"createdBy": 1,
|
||||
"updatedBy": 1,
|
||||
"createdAt": "2024-12-20T10:30:00.000Z",
|
||||
"updatedAt": "2024-12-20T10:30:00.000Z",
|
||||
"creator": {
|
||||
"id": 1,
|
||||
"username": "admin",
|
||||
"real_name": "管理员"
|
||||
},
|
||||
"updater": {
|
||||
"id": 1,
|
||||
"username": "admin",
|
||||
"real_name": "管理员"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 获取监管任务列表
|
||||
**GET** `/api/supervision-tasks`
|
||||
|
||||
#### 查询参数
|
||||
| 参数名 | 类型 | 必填 | 说明 | 示例 |
|
||||
|--------|------|------|------|------|
|
||||
| page | number | 否 | 页码,默认:1 | 1 |
|
||||
| limit | number | 否 | 每页数量,默认:10 | 10 |
|
||||
| search | string | 否 | 搜索关键词(申请单号、合同编号、客户姓名、产品名称) | "张三" |
|
||||
| supervisionStatus | string | 否 | 状态筛选 | "pending", "supervising", "completed", "suspended" |
|
||||
| dateRange | string | 否 | 日期范围筛选,格式:startDate,endDate | "2024-01-01,2024-12-31" |
|
||||
| sortBy | string | 否 | 排序字段,默认:createdAt | "applicationNumber", "customerName", "supervisionStatus" |
|
||||
| sortOrder | string | 否 | 排序方向,默认:DESC | "ASC", "DESC" |
|
||||
|
||||
#### 请求示例
|
||||
```
|
||||
GET /api/supervision-tasks?page=1&limit=10&search=张三&supervisionStatus=supervising&dateRange=2024-01-01,2024-12-31&sortBy=customerName&sortOrder=ASC
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "监管任务列表获取成功",
|
||||
"data": {
|
||||
"tasks": [
|
||||
{
|
||||
"id": 1,
|
||||
"applicationNumber": "APP001",
|
||||
"contractNumber": "CONTRACT001",
|
||||
"productName": "农业贷款产品A",
|
||||
"customerName": "张三",
|
||||
"idType": "id_card",
|
||||
"idNumber": "110101199001011234",
|
||||
"assetType": "cattle",
|
||||
"assetQuantity": 10,
|
||||
"supervisionStatus": "supervising",
|
||||
"importTime": "2024-01-15T10:30:00.000Z",
|
||||
"startTime": "2024-01-15",
|
||||
"endTime": "2024-12-15",
|
||||
"loanAmount": "500000.00",
|
||||
"interestRate": "0.0650",
|
||||
"loanTerm": 12,
|
||||
"supervisorName": "李监管员",
|
||||
"supervisorPhone": "13800138001",
|
||||
"farmAddress": "北京市朝阳区某某养殖场",
|
||||
"remarks": "重点监管项目,需要定期检查",
|
||||
"createdAt": "2024-12-20T10:30:00.000Z",
|
||||
"updatedAt": "2024-12-20T10:30:00.000Z",
|
||||
"creator": {
|
||||
"id": 1,
|
||||
"username": "admin",
|
||||
"real_name": "管理员"
|
||||
},
|
||||
"updater": {
|
||||
"id": 1,
|
||||
"username": "admin",
|
||||
"real_name": "管理员"
|
||||
}
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"total": 8,
|
||||
"currentPage": 1,
|
||||
"pageSize": 10,
|
||||
"totalPages": 1,
|
||||
"hasNextPage": false,
|
||||
"hasPrevPage": false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 获取监管任务详情
|
||||
**GET** `/api/supervision-tasks/:id`
|
||||
|
||||
#### 路径参数
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| id | number | 是 | 监管任务ID |
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "监管任务详情获取成功",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"applicationNumber": "APP001",
|
||||
"contractNumber": "CONTRACT001",
|
||||
"productName": "农业贷款产品A",
|
||||
"customerName": "张三",
|
||||
"idType": "id_card",
|
||||
"idNumber": "110101199001011234",
|
||||
"assetType": "cattle",
|
||||
"assetQuantity": 10,
|
||||
"supervisionStatus": "supervising",
|
||||
"importTime": "2024-01-15T10:30:00.000Z",
|
||||
"startTime": "2024-01-15",
|
||||
"endTime": "2024-12-15",
|
||||
"loanAmount": "500000.00",
|
||||
"interestRate": "0.0650",
|
||||
"loanTerm": 12,
|
||||
"supervisorName": "李监管员",
|
||||
"supervisorPhone": "13800138001",
|
||||
"farmAddress": "北京市朝阳区某某养殖场",
|
||||
"remarks": "重点监管项目,需要定期检查",
|
||||
"createdAt": "2024-12-20T10:30:00.000Z",
|
||||
"updatedAt": "2024-12-20T10:30:00.000Z",
|
||||
"creator": {
|
||||
"id": 1,
|
||||
"username": "admin",
|
||||
"real_name": "管理员"
|
||||
},
|
||||
"updater": {
|
||||
"id": 1,
|
||||
"username": "admin",
|
||||
"real_name": "管理员"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 更新监管任务
|
||||
**PUT** `/api/supervision-tasks/:id`
|
||||
|
||||
#### 请求参数
|
||||
与创建监管任务相同,所有字段都是可选的。
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "监管任务更新成功",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"applicationNumber": "APP001",
|
||||
"contractNumber": "CONTRACT001",
|
||||
"productName": "农业贷款产品A",
|
||||
"customerName": "张三",
|
||||
"supervisionStatus": "supervising",
|
||||
"remarks": "更新后的备注信息",
|
||||
"updatedAt": "2024-12-20T11:00:00.000Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 删除监管任务
|
||||
**DELETE** `/api/supervision-tasks/:id`
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "监管任务删除成功"
|
||||
}
|
||||
```
|
||||
|
||||
### 6. 获取监管任务统计
|
||||
**GET** `/api/supervision-tasks/stats`
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "监管任务统计获取成功",
|
||||
"data": {
|
||||
"total": 8,
|
||||
"pending": 2,
|
||||
"supervising": 3,
|
||||
"completed": 2,
|
||||
"suspended": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7. 批量更新监管任务状态
|
||||
**PUT** `/api/supervision-tasks/batch/status`
|
||||
|
||||
#### 请求参数
|
||||
```json
|
||||
{
|
||||
"ids": [1, 2, 3],
|
||||
"supervisionStatus": "completed"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "成功更新 3 个监管任务状态",
|
||||
"data": {
|
||||
"updatedCount": 3
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8. 批量删除监管任务
|
||||
**DELETE** `/api/supervision-tasks/batch`
|
||||
|
||||
#### 请求参数
|
||||
```json
|
||||
{
|
||||
"ids": [1, 2, 3]
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "成功删除 3 个监管任务",
|
||||
"data": {
|
||||
"deletedCount": 3
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 错误码说明
|
||||
|
||||
| 状态码 | 说明 |
|
||||
|--------|------|
|
||||
| 200 | 请求成功 |
|
||||
| 201 | 创建成功 |
|
||||
| 400 | 请求参数错误 |
|
||||
| 401 | 未授权,需要登录 |
|
||||
| 403 | 禁止访问,权限不足 |
|
||||
| 404 | 资源不存在 |
|
||||
| 500 | 服务器内部错误 |
|
||||
|
||||
## 数据验证规则
|
||||
|
||||
### 必填字段验证
|
||||
- `applicationNumber`: 申请单号,不能为空,最大50字符,唯一
|
||||
- `contractNumber`: 放款合同编号,不能为空,最大50字符,唯一
|
||||
- `productName`: 产品名称,不能为空,最大100字符
|
||||
- `customerName`: 客户姓名,不能为空,最大50字符
|
||||
- `idNumber`: 证件号码,不能为空,最大50字符
|
||||
- `startTime`: 监管起始时间,不能为空
|
||||
- `endTime`: 监管结束时间,不能为空
|
||||
|
||||
### 枚举值验证
|
||||
- `idType`: 只能是 "id_card", "passport", "other"
|
||||
- `assetType`: 只能是 "cattle", "sheep", "pig", "poultry", "other"
|
||||
- `supervisionStatus`: 只能是 "pending", "supervising", "completed", "suspended"
|
||||
|
||||
### 数值字段验证
|
||||
- `assetQuantity`: 不能为负数
|
||||
- `loanAmount`: 不能为负数
|
||||
- `interestRate`: 必须在0-1之间
|
||||
- `loanTerm`: 不能为负数
|
||||
|
||||
### 日期验证
|
||||
- `startTime` 和 `endTime` 必须是有效的日期格式 (YYYY-MM-DD)
|
||||
- `endTime` 必须晚于 `startTime`
|
||||
|
||||
## 使用示例
|
||||
|
||||
### JavaScript (axios)
|
||||
```javascript
|
||||
// 创建监管任务
|
||||
const createSupervisionTask = async (taskData) => {
|
||||
try {
|
||||
const response = await axios.post('/api/supervision-tasks', taskData, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('创建监管任务失败:', error.response.data);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// 获取监管任务列表
|
||||
const getSupervisionTasks = async (params = {}) => {
|
||||
try {
|
||||
const response = await axios.get('/api/supervision-tasks', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
params
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('获取监管任务列表失败:', error.response.data);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### cURL
|
||||
```bash
|
||||
# 创建监管任务
|
||||
curl -X POST http://localhost:5351/api/supervision-tasks \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"applicationNumber": "APP001",
|
||||
"contractNumber": "CONTRACT001",
|
||||
"productName": "农业贷款产品A",
|
||||
"customerName": "张三",
|
||||
"idType": "id_card",
|
||||
"idNumber": "110101199001011234",
|
||||
"assetType": "cattle",
|
||||
"assetQuantity": 10,
|
||||
"startTime": "2024-01-15",
|
||||
"endTime": "2024-12-15"
|
||||
}'
|
||||
|
||||
# 获取监管任务列表
|
||||
curl -X GET "http://localhost:5351/api/supervision-tasks?page=1&limit=10" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
## 数据库表结构
|
||||
|
||||
### supervision_tasks 表
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| id | INTEGER | 主键,自增 |
|
||||
| applicationNumber | VARCHAR(50) | 申请单号,唯一 |
|
||||
| contractNumber | VARCHAR(50) | 放款合同编号,唯一 |
|
||||
| productName | VARCHAR(100) | 产品名称 |
|
||||
| customerName | VARCHAR(50) | 客户姓名 |
|
||||
| idType | ENUM | 证件类型 |
|
||||
| idNumber | VARCHAR(50) | 证件号码 |
|
||||
| assetType | ENUM | 养殖生资种类 |
|
||||
| assetQuantity | INTEGER | 监管生资数量 |
|
||||
| supervisionStatus | ENUM | 监管状态 |
|
||||
| importTime | DATETIME | 任务导入时间 |
|
||||
| startTime | DATE | 监管起始时间 |
|
||||
| endTime | DATE | 监管结束时间 |
|
||||
| loanAmount | DECIMAL(15,2) | 贷款金额 |
|
||||
| interestRate | DECIMAL(5,4) | 利率 |
|
||||
| loanTerm | INTEGER | 贷款期限(月) |
|
||||
| supervisorName | VARCHAR(50) | 监管员姓名 |
|
||||
| supervisorPhone | VARCHAR(20) | 监管员电话 |
|
||||
| farmAddress | VARCHAR(200) | 养殖场地址 |
|
||||
| remarks | TEXT | 备注 |
|
||||
| createdBy | INTEGER | 创建人ID |
|
||||
| updatedBy | INTEGER | 更新人ID |
|
||||
| createdAt | DATETIME | 创建时间 |
|
||||
| updatedAt | DATETIME | 更新时间 |
|
||||
121
bank-backend/migrations/20241220000002-create-projects.js
Normal file
121
bank-backend/migrations/20241220000002-create-projects.js
Normal file
@@ -0,0 +1,121 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('projects', {
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
allowNull: false
|
||||
},
|
||||
name: {
|
||||
type: Sequelize.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '项目名称'
|
||||
},
|
||||
status: {
|
||||
type: Sequelize.ENUM('supervision', 'completed'),
|
||||
allowNull: false,
|
||||
defaultValue: 'supervision',
|
||||
comment: '项目状态:supervision-监管中,completed-已结项'
|
||||
},
|
||||
farmName: {
|
||||
type: Sequelize.STRING(200),
|
||||
allowNull: false,
|
||||
comment: '养殖场名称'
|
||||
},
|
||||
supervisionObject: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '监管对象'
|
||||
},
|
||||
supervisionQuantity: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '监管数量'
|
||||
},
|
||||
supervisionPeriod: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '监管周期'
|
||||
},
|
||||
supervisionAmount: {
|
||||
type: Sequelize.DECIMAL(15, 2),
|
||||
allowNull: false,
|
||||
defaultValue: 0.00,
|
||||
comment: '监管金额'
|
||||
},
|
||||
startTime: {
|
||||
type: Sequelize.DATEONLY,
|
||||
allowNull: false,
|
||||
comment: '起始时间'
|
||||
},
|
||||
endTime: {
|
||||
type: Sequelize.DATEONLY,
|
||||
allowNull: false,
|
||||
comment: '结束时间'
|
||||
},
|
||||
earTag: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '耳标数量'
|
||||
},
|
||||
collar: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '项圈数量'
|
||||
},
|
||||
host: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '主机数量'
|
||||
},
|
||||
loanOfficer: {
|
||||
type: Sequelize.STRING(100),
|
||||
allowNull: true,
|
||||
comment: '贷款专员'
|
||||
},
|
||||
description: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
comment: '项目描述'
|
||||
},
|
||||
createdBy: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '创建人ID'
|
||||
},
|
||||
updatedBy: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '更新人ID'
|
||||
},
|
||||
createdAt: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.NOW
|
||||
},
|
||||
updatedAt: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.NOW
|
||||
}
|
||||
});
|
||||
|
||||
// 添加索引
|
||||
await queryInterface.addIndex('projects', ['status']);
|
||||
await queryInterface.addIndex('projects', ['farmName']);
|
||||
await queryInterface.addIndex('projects', ['createdBy']);
|
||||
await queryInterface.addIndex('projects', ['startTime']);
|
||||
await queryInterface.addIndex('projects', ['endTime']);
|
||||
},
|
||||
|
||||
async down(queryInterface, Sequelize) {
|
||||
await queryInterface.dropTable('projects');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,152 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('supervision_tasks', {
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
allowNull: false
|
||||
},
|
||||
applicationNumber: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '申请单号'
|
||||
},
|
||||
contractNumber: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '放款合同编号'
|
||||
},
|
||||
productName: {
|
||||
type: Sequelize.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '产品名称'
|
||||
},
|
||||
customerName: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '客户姓名'
|
||||
},
|
||||
idType: {
|
||||
type: Sequelize.ENUM('id_card', 'passport', 'other'),
|
||||
allowNull: false,
|
||||
defaultValue: 'id_card',
|
||||
comment: '证件类型:id_card-身份证,passport-护照,other-其他'
|
||||
},
|
||||
idNumber: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '证件号码'
|
||||
},
|
||||
assetType: {
|
||||
type: Sequelize.ENUM('cattle', 'sheep', 'pig', 'poultry', 'other'),
|
||||
allowNull: false,
|
||||
defaultValue: 'cattle',
|
||||
comment: '养殖生资种类:cattle-牛,sheep-羊,pig-猪,poultry-家禽,other-其他'
|
||||
},
|
||||
assetQuantity: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '监管生资数量'
|
||||
},
|
||||
supervisionStatus: {
|
||||
type: Sequelize.ENUM('pending', 'supervising', 'completed', 'suspended'),
|
||||
allowNull: false,
|
||||
defaultValue: 'pending',
|
||||
comment: '监管状态:pending-待监管,supervising-监管中,completed-已完成,suspended-已暂停'
|
||||
},
|
||||
importTime: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
|
||||
comment: '任务导入时间'
|
||||
},
|
||||
startTime: {
|
||||
type: Sequelize.DATEONLY,
|
||||
allowNull: false,
|
||||
comment: '监管起始时间'
|
||||
},
|
||||
endTime: {
|
||||
type: Sequelize.DATEONLY,
|
||||
allowNull: false,
|
||||
comment: '监管结束时间'
|
||||
},
|
||||
loanAmount: {
|
||||
type: Sequelize.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
defaultValue: 0.00,
|
||||
comment: '贷款金额'
|
||||
},
|
||||
interestRate: {
|
||||
type: Sequelize.DECIMAL(5, 4),
|
||||
allowNull: true,
|
||||
defaultValue: 0.0000,
|
||||
comment: '利率'
|
||||
},
|
||||
loanTerm: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
defaultValue: 12,
|
||||
comment: '贷款期限(月)'
|
||||
},
|
||||
supervisorName: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: true,
|
||||
comment: '监管员姓名'
|
||||
},
|
||||
supervisorPhone: {
|
||||
type: Sequelize.STRING(20),
|
||||
allowNull: true,
|
||||
comment: '监管员电话'
|
||||
},
|
||||
farmAddress: {
|
||||
type: Sequelize.STRING(200),
|
||||
allowNull: true,
|
||||
comment: '养殖场地址'
|
||||
},
|
||||
remarks: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
comment: '备注'
|
||||
},
|
||||
createdBy: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '创建人ID'
|
||||
},
|
||||
updatedBy: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '更新人ID'
|
||||
},
|
||||
createdAt: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
|
||||
},
|
||||
updatedAt: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')
|
||||
}
|
||||
});
|
||||
|
||||
// 添加索引
|
||||
await queryInterface.addIndex('supervision_tasks', ['applicationNumber']);
|
||||
await queryInterface.addIndex('supervision_tasks', ['contractNumber']);
|
||||
await queryInterface.addIndex('supervision_tasks', ['customerName']);
|
||||
await queryInterface.addIndex('supervision_tasks', ['supervisionStatus']);
|
||||
await queryInterface.addIndex('supervision_tasks', ['createdBy']);
|
||||
await queryInterface.addIndex('supervision_tasks', ['startTime']);
|
||||
await queryInterface.addIndex('supervision_tasks', ['endTime']);
|
||||
},
|
||||
|
||||
async down(queryInterface, Sequelize) {
|
||||
await queryInterface.dropTable('supervision_tasks');
|
||||
}
|
||||
};
|
||||
119
bank-backend/models/Project.js
Normal file
119
bank-backend/models/Project.js
Normal file
@@ -0,0 +1,119 @@
|
||||
const { DataTypes } = require('sequelize');
|
||||
const { sequelize } = require('../config/database');
|
||||
|
||||
const Project = sequelize.define('Project', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '项目名称'
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('supervision', 'completed'),
|
||||
allowNull: false,
|
||||
defaultValue: 'supervision',
|
||||
comment: '项目状态:supervision-监管中,completed-已结项'
|
||||
},
|
||||
farmName: {
|
||||
type: DataTypes.STRING(200),
|
||||
allowNull: false,
|
||||
comment: '养殖场名称'
|
||||
},
|
||||
supervisionObject: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '监管对象'
|
||||
},
|
||||
supervisionQuantity: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '监管数量'
|
||||
},
|
||||
supervisionPeriod: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '监管周期'
|
||||
},
|
||||
supervisionAmount: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: false,
|
||||
defaultValue: 0.00,
|
||||
comment: '监管金额'
|
||||
},
|
||||
startTime: {
|
||||
type: DataTypes.DATEONLY,
|
||||
allowNull: false,
|
||||
comment: '起始时间'
|
||||
},
|
||||
endTime: {
|
||||
type: DataTypes.DATEONLY,
|
||||
allowNull: false,
|
||||
comment: '结束时间'
|
||||
},
|
||||
earTag: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '耳标数量'
|
||||
},
|
||||
collar: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '项圈数量'
|
||||
},
|
||||
host: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '主机数量'
|
||||
},
|
||||
loanOfficer: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
comment: '贷款专员'
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '项目描述'
|
||||
},
|
||||
createdBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '创建人ID'
|
||||
},
|
||||
updatedBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '更新人ID'
|
||||
}
|
||||
}, {
|
||||
tableName: 'projects',
|
||||
timestamps: true,
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt',
|
||||
comment: '项目清单表'
|
||||
});
|
||||
|
||||
// 定义关联关系
|
||||
Project.associate = (models) => {
|
||||
// 项目与用户关联(创建人)
|
||||
Project.belongsTo(models.User, {
|
||||
foreignKey: 'createdBy',
|
||||
as: 'creator'
|
||||
});
|
||||
|
||||
// 项目与用户关联(更新人)
|
||||
Project.belongsTo(models.User, {
|
||||
foreignKey: 'updatedBy',
|
||||
as: 'updater'
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = Project;
|
||||
166
bank-backend/models/SupervisionTask.js
Normal file
166
bank-backend/models/SupervisionTask.js
Normal file
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* 监管任务模型
|
||||
* @file SupervisionTask.js
|
||||
* @description 监管任务数据模型定义
|
||||
*/
|
||||
const { DataTypes } = require('sequelize');
|
||||
const BaseModel = require('./BaseModel');
|
||||
const { sequelize } = require('../config/database');
|
||||
|
||||
class SupervisionTask extends BaseModel {}
|
||||
|
||||
SupervisionTask.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
applicationNumber: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '申请单号'
|
||||
},
|
||||
contractNumber: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '放款合同编号'
|
||||
},
|
||||
productName: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '产品名称'
|
||||
},
|
||||
customerName: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '客户姓名'
|
||||
},
|
||||
idType: {
|
||||
type: DataTypes.ENUM('id_card', 'passport', 'other'),
|
||||
allowNull: false,
|
||||
defaultValue: 'id_card',
|
||||
comment: '证件类型:id_card-身份证,passport-护照,other-其他'
|
||||
},
|
||||
idNumber: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '证件号码'
|
||||
},
|
||||
assetType: {
|
||||
type: DataTypes.ENUM('cattle', 'sheep', 'pig', 'poultry', 'other'),
|
||||
allowNull: false,
|
||||
defaultValue: 'cattle',
|
||||
comment: '养殖生资种类:cattle-牛,sheep-羊,pig-猪,poultry-家禽,other-其他'
|
||||
},
|
||||
assetQuantity: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '监管生资数量'
|
||||
},
|
||||
supervisionStatus: {
|
||||
type: DataTypes.ENUM('pending', 'supervising', 'completed', 'suspended'),
|
||||
allowNull: false,
|
||||
defaultValue: 'pending',
|
||||
comment: '监管状态:pending-待监管,supervising-监管中,completed-已完成,suspended-已暂停'
|
||||
},
|
||||
importTime: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW,
|
||||
comment: '任务导入时间'
|
||||
},
|
||||
startTime: {
|
||||
type: DataTypes.DATEONLY,
|
||||
allowNull: false,
|
||||
comment: '监管起始时间'
|
||||
},
|
||||
endTime: {
|
||||
type: DataTypes.DATEONLY,
|
||||
allowNull: false,
|
||||
comment: '监管结束时间'
|
||||
},
|
||||
loanAmount: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
defaultValue: 0.00,
|
||||
comment: '贷款金额'
|
||||
},
|
||||
interestRate: {
|
||||
type: DataTypes.DECIMAL(5, 4),
|
||||
allowNull: true,
|
||||
defaultValue: 0.0000,
|
||||
comment: '利率'
|
||||
},
|
||||
loanTerm: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
defaultValue: 12,
|
||||
comment: '贷款期限(月)'
|
||||
},
|
||||
supervisorName: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
comment: '监管员姓名'
|
||||
},
|
||||
supervisorPhone: {
|
||||
type: DataTypes.STRING(20),
|
||||
allowNull: true,
|
||||
comment: '监管员电话'
|
||||
},
|
||||
farmAddress: {
|
||||
type: DataTypes.STRING(200),
|
||||
allowNull: true,
|
||||
comment: '养殖场地址'
|
||||
},
|
||||
remarks: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '备注'
|
||||
},
|
||||
createdBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '创建人ID'
|
||||
},
|
||||
updatedBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '更新人ID'
|
||||
},
|
||||
createdAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW
|
||||
},
|
||||
updatedAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'SupervisionTask',
|
||||
tableName: 'supervision_tasks',
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt',
|
||||
comment: '监管任务表'
|
||||
});
|
||||
|
||||
// 定义关联关系
|
||||
SupervisionTask.associate = (models) => {
|
||||
SupervisionTask.belongsTo(models.User, {
|
||||
foreignKey: 'createdBy',
|
||||
as: 'creator'
|
||||
});
|
||||
SupervisionTask.belongsTo(models.User, {
|
||||
foreignKey: 'updatedBy',
|
||||
as: 'updater'
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = SupervisionTask;
|
||||
@@ -15,6 +15,8 @@ const Employee = require('./Employee');
|
||||
const Department = require('./Department');
|
||||
const Position = require('./Position');
|
||||
const Report = require('./Report');
|
||||
const Project = require('./Project');
|
||||
const SupervisionTask = require('./SupervisionTask');
|
||||
|
||||
// 定义模型关联关系
|
||||
|
||||
@@ -91,6 +93,54 @@ User.hasMany(Report, {
|
||||
as: 'reports'
|
||||
});
|
||||
|
||||
// 项目与用户关联(创建人)
|
||||
Project.belongsTo(User, {
|
||||
foreignKey: 'createdBy',
|
||||
as: 'creator',
|
||||
targetKey: 'id'
|
||||
});
|
||||
|
||||
User.hasMany(Project, {
|
||||
foreignKey: 'createdBy',
|
||||
as: 'createdProjects'
|
||||
});
|
||||
|
||||
// 项目与用户关联(更新人)
|
||||
Project.belongsTo(User, {
|
||||
foreignKey: 'updatedBy',
|
||||
as: 'updater',
|
||||
targetKey: 'id'
|
||||
});
|
||||
|
||||
User.hasMany(Project, {
|
||||
foreignKey: 'updatedBy',
|
||||
as: 'updatedProjects'
|
||||
});
|
||||
|
||||
// 监管任务与用户关联(创建人)
|
||||
SupervisionTask.belongsTo(User, {
|
||||
foreignKey: 'createdBy',
|
||||
as: 'creator',
|
||||
targetKey: 'id'
|
||||
});
|
||||
|
||||
User.hasMany(SupervisionTask, {
|
||||
foreignKey: 'createdBy',
|
||||
as: 'createdSupervisionTasks'
|
||||
});
|
||||
|
||||
// 监管任务与用户关联(更新人)
|
||||
SupervisionTask.belongsTo(User, {
|
||||
foreignKey: 'updatedBy',
|
||||
as: 'updater',
|
||||
targetKey: 'id'
|
||||
});
|
||||
|
||||
User.hasMany(SupervisionTask, {
|
||||
foreignKey: 'updatedBy',
|
||||
as: 'updatedSupervisionTasks'
|
||||
});
|
||||
|
||||
// 导出所有模型和数据库实例
|
||||
module.exports = {
|
||||
sequelize,
|
||||
@@ -102,5 +152,7 @@ module.exports = {
|
||||
Employee,
|
||||
Department,
|
||||
Position,
|
||||
Report
|
||||
Report,
|
||||
Project,
|
||||
SupervisionTask
|
||||
};
|
||||
37
bank-backend/routes/projects.js
Normal file
37
bank-backend/routes/projects.js
Normal file
@@ -0,0 +1,37 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const projectController = require('../controllers/projectController');
|
||||
const { authMiddleware } = require('../middleware/auth');
|
||||
|
||||
// 应用认证中间件到所有路由
|
||||
router.use(authMiddleware);
|
||||
|
||||
// 获取项目列表
|
||||
// GET /api/projects
|
||||
router.get('/', projectController.getProjects);
|
||||
|
||||
// 获取项目统计
|
||||
// GET /api/projects/stats
|
||||
router.get('/stats', projectController.getProjectStats);
|
||||
|
||||
// 获取项目详情
|
||||
// GET /api/projects/:id
|
||||
router.get('/:id', projectController.getProjectById);
|
||||
|
||||
// 创建项目
|
||||
// POST /api/projects
|
||||
router.post('/', projectController.createProject);
|
||||
|
||||
// 更新项目
|
||||
// PUT /api/projects/:id
|
||||
router.put('/:id', projectController.updateProject);
|
||||
|
||||
// 删除项目
|
||||
// DELETE /api/projects/:id
|
||||
router.delete('/:id', projectController.deleteProject);
|
||||
|
||||
// 批量更新项目状态
|
||||
// PUT /api/projects/batch/status
|
||||
router.put('/batch/status', projectController.batchUpdateStatus);
|
||||
|
||||
module.exports = router;
|
||||
41
bank-backend/routes/supervisionTasks.js
Normal file
41
bank-backend/routes/supervisionTasks.js
Normal file
@@ -0,0 +1,41 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const supervisionTaskController = require('../controllers/supervisionTaskController');
|
||||
const { authMiddleware } = require('../middleware/auth');
|
||||
|
||||
// 应用认证中间件到所有路由
|
||||
router.use(authMiddleware);
|
||||
|
||||
// 获取监管任务列表
|
||||
// GET /api/supervision-tasks
|
||||
router.get('/', supervisionTaskController.getSupervisionTasks);
|
||||
|
||||
// 获取监管任务统计
|
||||
// GET /api/supervision-tasks/stats
|
||||
router.get('/stats', supervisionTaskController.getSupervisionTaskStats);
|
||||
|
||||
// 获取监管任务详情
|
||||
// GET /api/supervision-tasks/:id
|
||||
router.get('/:id', supervisionTaskController.getSupervisionTaskById);
|
||||
|
||||
// 创建监管任务
|
||||
// POST /api/supervision-tasks
|
||||
router.post('/', supervisionTaskController.createSupervisionTask);
|
||||
|
||||
// 更新监管任务
|
||||
// PUT /api/supervision-tasks/:id
|
||||
router.put('/:id', supervisionTaskController.updateSupervisionTask);
|
||||
|
||||
// 删除监管任务
|
||||
// DELETE /api/supervision-tasks/:id
|
||||
router.delete('/:id', supervisionTaskController.deleteSupervisionTask);
|
||||
|
||||
// 批量更新监管任务状态
|
||||
// PUT /api/supervision-tasks/batch/status
|
||||
router.put('/batch/status', supervisionTaskController.batchUpdateStatus);
|
||||
|
||||
// 批量删除监管任务
|
||||
// DELETE /api/supervision-tasks/batch
|
||||
router.delete('/batch', supervisionTaskController.batchDeleteTasks);
|
||||
|
||||
module.exports = router;
|
||||
200
bank-backend/scripts/seed-projects.js
Normal file
200
bank-backend/scripts/seed-projects.js
Normal file
@@ -0,0 +1,200 @@
|
||||
const { Project, User } = require('../models');
|
||||
const { sequelize } = require('../config/database');
|
||||
|
||||
// 项目测试数据
|
||||
const projectsData = [
|
||||
{
|
||||
name: '张洪彬',
|
||||
status: 'completed',
|
||||
farmName: '大数据中心',
|
||||
supervisionObject: '牛',
|
||||
supervisionQuantity: 10,
|
||||
supervisionPeriod: '23天',
|
||||
supervisionAmount: 10000.00,
|
||||
startTime: '2024-02-21',
|
||||
endTime: '2024-03-15',
|
||||
earTag: 0,
|
||||
collar: 0,
|
||||
host: 0,
|
||||
loanOfficer: 'mapleaf',
|
||||
description: '大数据中心养殖项目'
|
||||
},
|
||||
{
|
||||
name: '田小平',
|
||||
status: 'completed',
|
||||
farmName: '139****5685_养殖场',
|
||||
supervisionObject: '牛',
|
||||
supervisionQuantity: 0,
|
||||
supervisionPeriod: '0天',
|
||||
supervisionAmount: 0.00,
|
||||
startTime: '2022-12-12',
|
||||
endTime: '2023-12-12',
|
||||
earTag: 0,
|
||||
collar: 0,
|
||||
host: 0,
|
||||
loanOfficer: '',
|
||||
description: '田小平养殖场项目'
|
||||
},
|
||||
{
|
||||
name: '杜宝民',
|
||||
status: 'completed',
|
||||
farmName: '杜宝民养殖场',
|
||||
supervisionObject: '牛',
|
||||
supervisionQuantity: 1,
|
||||
supervisionPeriod: '20天',
|
||||
supervisionAmount: 1000000.00,
|
||||
startTime: '2023-05-08',
|
||||
endTime: '2028-05-08',
|
||||
earTag: 0,
|
||||
collar: 0,
|
||||
host: 0,
|
||||
loanOfficer: 'mapleaf',
|
||||
description: '杜宝民养殖场长期项目'
|
||||
},
|
||||
{
|
||||
name: '满良',
|
||||
status: 'completed',
|
||||
farmName: '满良养殖场',
|
||||
supervisionObject: '牛',
|
||||
supervisionQuantity: 30,
|
||||
supervisionPeriod: '365天',
|
||||
supervisionAmount: 420000.00,
|
||||
startTime: '2023-01-01',
|
||||
endTime: '2024-01-01',
|
||||
earTag: 0,
|
||||
collar: 0,
|
||||
host: 0,
|
||||
loanOfficer: 'mapleaf',
|
||||
description: '满良养殖场年度项目'
|
||||
},
|
||||
{
|
||||
name: '敖日布仁琴',
|
||||
status: 'supervision',
|
||||
farmName: '敖日布仁琴养殖场',
|
||||
supervisionObject: '牛',
|
||||
supervisionQuantity: 38,
|
||||
supervisionPeriod: '1827天',
|
||||
supervisionAmount: 530000.00,
|
||||
startTime: '2019-01-01',
|
||||
endTime: '2024-01-01',
|
||||
earTag: 0,
|
||||
collar: 0,
|
||||
host: 0,
|
||||
loanOfficer: 'mapleaf',
|
||||
description: '敖日布仁琴养殖场长期监管项目'
|
||||
},
|
||||
{
|
||||
name: '那顺乌日图',
|
||||
status: 'supervision',
|
||||
farmName: '那顺乌日图养殖场',
|
||||
supervisionObject: '牛',
|
||||
supervisionQuantity: 36,
|
||||
supervisionPeriod: '1827天',
|
||||
supervisionAmount: 500000.00,
|
||||
startTime: '2019-01-01',
|
||||
endTime: '2024-01-01',
|
||||
earTag: 0,
|
||||
collar: 0,
|
||||
host: 0,
|
||||
loanOfficer: 'mapleaf',
|
||||
description: '那顺乌日图养殖场长期监管项目'
|
||||
},
|
||||
{
|
||||
name: '巴特尔',
|
||||
status: 'supervision',
|
||||
farmName: '巴特尔养殖场',
|
||||
supervisionObject: '牛',
|
||||
supervisionQuantity: 41,
|
||||
supervisionPeriod: '1827天',
|
||||
supervisionAmount: 570000.00,
|
||||
startTime: '2019-01-01',
|
||||
endTime: '2024-01-01',
|
||||
earTag: 0,
|
||||
collar: 0,
|
||||
host: 0,
|
||||
loanOfficer: 'mapleaf',
|
||||
description: '巴特尔养殖场长期监管项目'
|
||||
},
|
||||
{
|
||||
name: '王五',
|
||||
status: 'completed',
|
||||
farmName: '王五养殖场',
|
||||
supervisionObject: '牛',
|
||||
supervisionQuantity: 50,
|
||||
supervisionPeriod: '365天',
|
||||
supervisionAmount: 700000.00,
|
||||
startTime: '2023-01-01',
|
||||
endTime: '2024-01-01',
|
||||
earTag: 0,
|
||||
collar: 0,
|
||||
host: 0,
|
||||
loanOfficer: 'mapleaf',
|
||||
description: '王五养殖场年度项目'
|
||||
}
|
||||
];
|
||||
|
||||
async function seedProjects() {
|
||||
try {
|
||||
console.log('开始创建项目测试数据...');
|
||||
|
||||
// 测试数据库连接
|
||||
await sequelize.authenticate();
|
||||
console.log('✅ 数据库连接成功');
|
||||
|
||||
// 查找管理员用户作为创建人
|
||||
const adminUser = await User.findOne({ where: { username: 'admin' } });
|
||||
const createdBy = adminUser ? adminUser.id : null;
|
||||
|
||||
// 清空现有项目数据
|
||||
await Project.destroy({ where: {} });
|
||||
console.log('✅ 清空现有项目数据');
|
||||
|
||||
// 创建项目数据
|
||||
for (const projectData of projectsData) {
|
||||
await Project.create({
|
||||
...projectData,
|
||||
createdBy: createdBy,
|
||||
updatedBy: createdBy
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`✅ 成功创建 ${projectsData.length} 个项目`);
|
||||
|
||||
// 验证数据
|
||||
const projectCount = await Project.count();
|
||||
console.log(`📊 数据库中现有项目数量: ${projectCount}`);
|
||||
|
||||
// 显示统计信息
|
||||
const supervisionCount = await Project.count({ where: { status: 'supervision' } });
|
||||
const completedCount = await Project.count({ where: { status: 'completed' } });
|
||||
const totalAmount = await Project.sum('supervisionAmount');
|
||||
const totalQuantity = await Project.sum('supervisionQuantity');
|
||||
|
||||
console.log('\n📈 项目统计信息:');
|
||||
console.log(` 监管中项目: ${supervisionCount}`);
|
||||
console.log(` 已结项项目: ${completedCount}`);
|
||||
console.log(` 总监管金额: ${totalAmount?.toFixed(2) || 0} 元`);
|
||||
console.log(` 总监管数量: ${totalQuantity || 0} 头`);
|
||||
|
||||
console.log('\n🎉 项目数据创建完成!');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 创建项目数据失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
seedProjects()
|
||||
.then(() => {
|
||||
console.log('✅ 脚本执行完成');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('❌ 脚本执行失败:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = seedProjects;
|
||||
261
bank-backend/scripts/seed-supervision-tasks.js
Normal file
261
bank-backend/scripts/seed-supervision-tasks.js
Normal file
@@ -0,0 +1,261 @@
|
||||
/**
|
||||
* 监管任务测试数据种子文件
|
||||
* @file seed-supervision-tasks.js
|
||||
* @description 为监管任务表添加测试数据
|
||||
*/
|
||||
const { sequelize, SupervisionTask, User } = require('../models');
|
||||
|
||||
async function seedSupervisionTasks() {
|
||||
try {
|
||||
console.log('🌱 开始添加监管任务测试数据...');
|
||||
|
||||
// 检查是否已有数据
|
||||
const existingCount = await SupervisionTask.count();
|
||||
if (existingCount > 0) {
|
||||
console.log(`⚠️ 监管任务表已有 ${existingCount} 条数据,跳过种子数据添加`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取管理员用户ID
|
||||
const adminUser = await User.findOne({ where: { username: 'admin' } });
|
||||
if (!adminUser) {
|
||||
console.log('❌ 未找到管理员用户,请先运行用户种子数据');
|
||||
return;
|
||||
}
|
||||
|
||||
const adminId = adminUser.id;
|
||||
|
||||
// 监管任务测试数据
|
||||
const supervisionTasks = [
|
||||
{
|
||||
applicationNumber: 'APP001',
|
||||
contractNumber: 'CONTRACT001',
|
||||
productName: '农业贷款产品A',
|
||||
customerName: '张三',
|
||||
idType: 'id_card',
|
||||
idNumber: '110101199001011234',
|
||||
assetType: 'cattle',
|
||||
assetQuantity: 10,
|
||||
supervisionStatus: 'supervising',
|
||||
importTime: new Date('2024-01-15 10:30:00'),
|
||||
startTime: '2024-01-15',
|
||||
endTime: '2024-12-15',
|
||||
loanAmount: 500000.00,
|
||||
interestRate: 0.0650,
|
||||
loanTerm: 12,
|
||||
supervisorName: '李监管员',
|
||||
supervisorPhone: '13800138001',
|
||||
farmAddress: '北京市朝阳区某某养殖场',
|
||||
remarks: '重点监管项目,需要定期检查',
|
||||
createdBy: adminId,
|
||||
updatedBy: adminId
|
||||
},
|
||||
{
|
||||
applicationNumber: 'APP002',
|
||||
contractNumber: 'CONTRACT002',
|
||||
productName: '农业贷款产品B',
|
||||
customerName: '李四',
|
||||
idType: 'id_card',
|
||||
idNumber: '110101199002021234',
|
||||
assetType: 'sheep',
|
||||
assetQuantity: 20,
|
||||
supervisionStatus: 'pending',
|
||||
importTime: new Date('2024-01-16 14:20:00'),
|
||||
startTime: '2024-01-16',
|
||||
endTime: '2024-12-16',
|
||||
loanAmount: 300000.00,
|
||||
interestRate: 0.0600,
|
||||
loanTerm: 12,
|
||||
supervisorName: '王监管员',
|
||||
supervisorPhone: '13800138002',
|
||||
farmAddress: '北京市海淀区某某农场',
|
||||
remarks: '新申请项目,待开始监管',
|
||||
createdBy: adminId,
|
||||
updatedBy: adminId
|
||||
},
|
||||
{
|
||||
applicationNumber: 'APP003',
|
||||
contractNumber: 'CONTRACT003',
|
||||
productName: '农业贷款产品C',
|
||||
customerName: '王五',
|
||||
idType: 'id_card',
|
||||
idNumber: '110101199003031234',
|
||||
assetType: 'pig',
|
||||
assetQuantity: 15,
|
||||
supervisionStatus: 'completed',
|
||||
importTime: new Date('2024-01-10 09:15:00'),
|
||||
startTime: '2024-01-10',
|
||||
endTime: '2024-01-20',
|
||||
loanAmount: 200000.00,
|
||||
interestRate: 0.0550,
|
||||
loanTerm: 6,
|
||||
supervisorName: '赵监管员',
|
||||
supervisorPhone: '13800138003',
|
||||
farmAddress: '北京市丰台区某某猪场',
|
||||
remarks: '监管任务已完成,客户还款正常',
|
||||
createdBy: adminId,
|
||||
updatedBy: adminId
|
||||
},
|
||||
{
|
||||
applicationNumber: 'APP004',
|
||||
contractNumber: 'CONTRACT004',
|
||||
productName: '农业贷款产品D',
|
||||
customerName: '赵六',
|
||||
idType: 'id_card',
|
||||
idNumber: '110101199004041234',
|
||||
assetType: 'poultry',
|
||||
assetQuantity: 50,
|
||||
supervisionStatus: 'supervising',
|
||||
importTime: new Date('2024-01-20 11:45:00'),
|
||||
startTime: '2024-01-20',
|
||||
endTime: '2024-06-20',
|
||||
loanAmount: 150000.00,
|
||||
interestRate: 0.0700,
|
||||
loanTerm: 6,
|
||||
supervisorName: '孙监管员',
|
||||
supervisorPhone: '13800138004',
|
||||
farmAddress: '北京市通州区某某鸡场',
|
||||
remarks: '家禽养殖项目,需要特别关注防疫情况',
|
||||
createdBy: adminId,
|
||||
updatedBy: adminId
|
||||
},
|
||||
{
|
||||
applicationNumber: 'APP005',
|
||||
contractNumber: 'CONTRACT005',
|
||||
productName: '农业贷款产品E',
|
||||
customerName: '孙七',
|
||||
idType: 'id_card',
|
||||
idNumber: '110101199005051234',
|
||||
assetType: 'cattle',
|
||||
assetQuantity: 25,
|
||||
supervisionStatus: 'suspended',
|
||||
importTime: new Date('2024-01-25 16:30:00'),
|
||||
startTime: '2024-01-25',
|
||||
endTime: '2024-12-25',
|
||||
loanAmount: 800000.00,
|
||||
interestRate: 0.0625,
|
||||
loanTerm: 18,
|
||||
supervisorName: '周监管员',
|
||||
supervisorPhone: '13800138005',
|
||||
farmAddress: '北京市昌平区某某牧场',
|
||||
remarks: '因客户原因暂停监管,等待进一步通知',
|
||||
createdBy: adminId,
|
||||
updatedBy: adminId
|
||||
},
|
||||
{
|
||||
applicationNumber: 'APP006',
|
||||
contractNumber: 'CONTRACT006',
|
||||
productName: '农业贷款产品F',
|
||||
customerName: '周八',
|
||||
idType: 'passport',
|
||||
idNumber: 'P123456789',
|
||||
assetType: 'other',
|
||||
assetQuantity: 30,
|
||||
supervisionStatus: 'supervising',
|
||||
importTime: new Date('2024-02-01 08:20:00'),
|
||||
startTime: '2024-02-01',
|
||||
endTime: '2024-08-01',
|
||||
loanAmount: 400000.00,
|
||||
interestRate: 0.0680,
|
||||
loanTerm: 6,
|
||||
supervisorName: '吴监管员',
|
||||
supervisorPhone: '13800138006',
|
||||
farmAddress: '北京市顺义区某某特种养殖场',
|
||||
remarks: '特种养殖项目,需要专业监管',
|
||||
createdBy: adminId,
|
||||
updatedBy: adminId
|
||||
},
|
||||
{
|
||||
applicationNumber: 'APP007',
|
||||
contractNumber: 'CONTRACT007',
|
||||
productName: '农业贷款产品G',
|
||||
customerName: '吴九',
|
||||
idType: 'id_card',
|
||||
idNumber: '110101199007071234',
|
||||
assetType: 'sheep',
|
||||
assetQuantity: 40,
|
||||
supervisionStatus: 'pending',
|
||||
importTime: new Date('2024-02-05 13:10:00'),
|
||||
startTime: '2024-02-05',
|
||||
endTime: '2024-12-05',
|
||||
loanAmount: 600000.00,
|
||||
interestRate: 0.0590,
|
||||
loanTerm: 12,
|
||||
supervisorName: '郑监管员',
|
||||
supervisorPhone: '13800138007',
|
||||
farmAddress: '北京市房山区某某羊场',
|
||||
remarks: '大规模羊群养殖,需要加强监管',
|
||||
createdBy: adminId,
|
||||
updatedBy: adminId
|
||||
},
|
||||
{
|
||||
applicationNumber: 'APP008',
|
||||
contractNumber: 'CONTRACT008',
|
||||
productName: '农业贷款产品H',
|
||||
customerName: '郑十',
|
||||
idType: 'id_card',
|
||||
idNumber: '110101199008081234',
|
||||
assetType: 'pig',
|
||||
assetQuantity: 35,
|
||||
supervisionStatus: 'completed',
|
||||
importTime: new Date('2024-01-05 15:45:00'),
|
||||
startTime: '2024-01-05',
|
||||
endTime: '2024-03-05',
|
||||
loanAmount: 350000.00,
|
||||
interestRate: 0.0575,
|
||||
loanTerm: 3,
|
||||
supervisorName: '冯监管员',
|
||||
supervisorPhone: '13800138008',
|
||||
farmAddress: '北京市大兴区某某养猪场',
|
||||
remarks: '短期养殖项目,已顺利完成监管',
|
||||
createdBy: adminId,
|
||||
updatedBy: adminId
|
||||
}
|
||||
];
|
||||
|
||||
// 批量创建监管任务
|
||||
await SupervisionTask.bulkCreate(supervisionTasks);
|
||||
|
||||
console.log(`✅ 成功添加 ${supervisionTasks.length} 条监管任务测试数据`);
|
||||
|
||||
// 显示统计信息
|
||||
const stats = await SupervisionTask.findAll({
|
||||
attributes: [
|
||||
'supervisionStatus',
|
||||
[sequelize.fn('COUNT', sequelize.col('id')), 'count']
|
||||
],
|
||||
group: ['supervisionStatus'],
|
||||
raw: true
|
||||
});
|
||||
|
||||
console.log('📊 监管任务状态统计:');
|
||||
stats.forEach(stat => {
|
||||
const statusNames = {
|
||||
'pending': '待监管',
|
||||
'supervising': '监管中',
|
||||
'completed': '已完成',
|
||||
'suspended': '已暂停'
|
||||
};
|
||||
console.log(` ${statusNames[stat.supervisionStatus] || stat.supervisionStatus}: ${stat.count} 条`);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 添加监管任务测试数据失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此文件
|
||||
if (require.main === module) {
|
||||
seedSupervisionTasks()
|
||||
.then(() => {
|
||||
console.log('🎉 监管任务种子数据添加完成');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('💥 监管任务种子数据添加失败:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = seedSupervisionTasks;
|
||||
60
bank-backend/scripts/setup-projects-simple.js
Normal file
60
bank-backend/scripts/setup-projects-simple.js
Normal file
@@ -0,0 +1,60 @@
|
||||
const { sequelize } = require('../config/database');
|
||||
const { Project } = require('../models');
|
||||
const seedProjects = require('./seed-projects');
|
||||
|
||||
async function setupProjectsSimple() {
|
||||
try {
|
||||
console.log('🚀 开始设置项目清单功能...\n');
|
||||
|
||||
// 1. 测试数据库连接
|
||||
console.log('1️⃣ 测试数据库连接...');
|
||||
await sequelize.authenticate();
|
||||
console.log('✅ 数据库连接成功\n');
|
||||
|
||||
// 2. 同步项目模型(创建表)
|
||||
console.log('2️⃣ 创建项目表...');
|
||||
await Project.sync({ force: false }); // force: false 表示如果表已存在则不删除
|
||||
console.log('✅ 项目表创建成功\n');
|
||||
|
||||
// 3. 创建项目测试数据
|
||||
console.log('3️⃣ 创建项目测试数据...');
|
||||
await seedProjects();
|
||||
console.log('✅ 项目测试数据创建完成\n');
|
||||
|
||||
// 4. 验证数据
|
||||
console.log('4️⃣ 验证项目数据...');
|
||||
const projectCount = await Project.count();
|
||||
const supervisionCount = await Project.count({ where: { status: 'supervision' } });
|
||||
const completedCount = await Project.count({ where: { status: 'completed' } });
|
||||
|
||||
console.log(` 总项目数: ${projectCount}`);
|
||||
console.log(` 监管中项目: ${supervisionCount}`);
|
||||
console.log(` 已结项项目: ${completedCount}`);
|
||||
console.log('✅ 项目数据验证完成\n');
|
||||
|
||||
console.log('🎉 项目清单功能设置完成!');
|
||||
console.log('📝 接下来可以:');
|
||||
console.log(' 1. 启动后端服务器: npm start');
|
||||
console.log(' 2. 运行API测试: node test-projects-api.js');
|
||||
console.log(' 3. 在前端访问项目清单页面');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 设置项目清单功能失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
setupProjectsSimple()
|
||||
.then(() => {
|
||||
console.log('✅ 脚本执行完成');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('❌ 脚本执行失败:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = setupProjectsSimple;
|
||||
79
bank-backend/scripts/setup-projects.js
Normal file
79
bank-backend/scripts/setup-projects.js
Normal file
@@ -0,0 +1,79 @@
|
||||
const { sequelize } = require('../config/database');
|
||||
const seedProjects = require('./seed-projects');
|
||||
|
||||
async function setupProjects() {
|
||||
try {
|
||||
console.log('🚀 开始设置项目清单功能...\n');
|
||||
|
||||
// 1. 测试数据库连接
|
||||
console.log('1️⃣ 测试数据库连接...');
|
||||
await sequelize.authenticate();
|
||||
console.log('✅ 数据库连接成功\n');
|
||||
|
||||
// 2. 运行项目表迁移
|
||||
console.log('2️⃣ 运行项目表迁移...');
|
||||
try {
|
||||
const { QueryInterface } = require('sequelize');
|
||||
const queryInterface = sequelize.getQueryInterface();
|
||||
|
||||
// 检查表是否已存在
|
||||
const tableExists = await queryInterface.showAllTables().then(tables =>
|
||||
tables.includes('projects')
|
||||
);
|
||||
|
||||
if (!tableExists) {
|
||||
// 运行迁移
|
||||
const migration = require('../migrations/20241220000002-create-projects');
|
||||
await migration.up(queryInterface, sequelize);
|
||||
console.log('✅ 项目表创建成功\n');
|
||||
} else {
|
||||
console.log('✅ 项目表已存在\n');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 项目表迁移失败:', error.message);
|
||||
throw error;
|
||||
}
|
||||
|
||||
// 3. 创建项目测试数据
|
||||
console.log('3️⃣ 创建项目测试数据...');
|
||||
await seedProjects();
|
||||
console.log('✅ 项目测试数据创建完成\n');
|
||||
|
||||
// 4. 验证数据
|
||||
console.log('4️⃣ 验证项目数据...');
|
||||
const { Project } = require('../models');
|
||||
const projectCount = await Project.count();
|
||||
const supervisionCount = await Project.count({ where: { status: 'supervision' } });
|
||||
const completedCount = await Project.count({ where: { status: 'completed' } });
|
||||
|
||||
console.log(` 总项目数: ${projectCount}`);
|
||||
console.log(` 监管中项目: ${supervisionCount}`);
|
||||
console.log(` 已结项项目: ${completedCount}`);
|
||||
console.log('✅ 项目数据验证完成\n');
|
||||
|
||||
console.log('🎉 项目清单功能设置完成!');
|
||||
console.log('📝 接下来可以:');
|
||||
console.log(' 1. 启动后端服务器: npm start');
|
||||
console.log(' 2. 运行API测试: node test-projects-api.js');
|
||||
console.log(' 3. 在前端访问项目清单页面');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 设置项目清单功能失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
setupProjects()
|
||||
.then(() => {
|
||||
console.log('✅ 脚本执行完成');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('❌ 脚本执行失败:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = setupProjects;
|
||||
43
bank-backend/scripts/setup-supervision-tasks.js
Normal file
43
bank-backend/scripts/setup-supervision-tasks.js
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* 监管任务设置脚本
|
||||
* @file setup-supervision-tasks.js
|
||||
* @description 创建监管任务表并添加测试数据
|
||||
*/
|
||||
const { sequelize, SupervisionTask } = require('../models');
|
||||
const seedSupervisionTasks = require('./seed-supervision-tasks');
|
||||
|
||||
async function setupSupervisionTasks() {
|
||||
try {
|
||||
console.log('🚀 开始设置监管任务...');
|
||||
|
||||
// 测试数据库连接
|
||||
await sequelize.authenticate();
|
||||
console.log('✅ 数据库连接成功');
|
||||
|
||||
// 同步监管任务模型(创建表)
|
||||
await sequelize.sync({ force: false });
|
||||
console.log('✅ 数据库表同步完成');
|
||||
|
||||
// 添加测试数据
|
||||
await seedSupervisionTasks();
|
||||
|
||||
console.log('🎉 监管任务设置完成!');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 监管任务设置失败:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
// 运行设置
|
||||
setupSupervisionTasks()
|
||||
.then(() => {
|
||||
console.log('✅ 所有操作完成');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('💥 操作失败:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -74,6 +74,8 @@ app.use('/api/transactions', require('./routes/transactions'));
|
||||
app.use('/api/dashboard', require('./routes/dashboard'));
|
||||
app.use('/api/loan-products', require('./routes/loanProducts'));
|
||||
app.use('/api/employees', require('./routes/employees'));
|
||||
app.use('/api/projects', require('./routes/projects'));
|
||||
app.use('/api/supervision-tasks', require('./routes/supervisionTasks'));
|
||||
// app.use('/api/reports', require('./routes/reports'));
|
||||
|
||||
// 根路径
|
||||
|
||||
96
bank-backend/test-create-project.js
Normal file
96
bank-backend/test-create-project.js
Normal file
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* 测试创建项目接口
|
||||
*/
|
||||
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();
|
||||
34
bank-backend/test-project-direct.js
Normal file
34
bank-backend/test-project-direct.js
Normal file
@@ -0,0 +1,34 @@
|
||||
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();
|
||||
388
bank-backend/test-projects-api.js
Normal file
388
bank-backend/test-projects-api.js
Normal file
@@ -0,0 +1,388 @@
|
||||
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);
|
||||
58
bank-backend/test-projects-simple.js
Normal file
58
bank-backend/test-projects-simple.js
Normal file
@@ -0,0 +1,58 @@
|
||||
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();
|
||||
172
bank-backend/test-supervision-tasks-api.js
Normal file
172
bank-backend/test-supervision-tasks-api.js
Normal file
@@ -0,0 +1,172 @@
|
||||
/**
|
||||
* 监管任务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();
|
||||
55
bank-backend/test-supervision-tasks-simple.js
Normal file
55
bank-backend/test-supervision-tasks-simple.js
Normal file
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 简单监管任务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();
|
||||
@@ -9,70 +9,67 @@
|
||||
</div>
|
||||
|
||||
<!-- 桌面端布局 -->
|
||||
<a-layout v-else class="desktop-layout">
|
||||
<a-layout-header class="header">
|
||||
<div class="logo">
|
||||
<a-button
|
||||
type="text"
|
||||
@click="settingsStore.toggleSidebar"
|
||||
style="color: white; margin-right: 8px;"
|
||||
>
|
||||
<menu-unfold-outlined v-if="sidebarCollapsed" />
|
||||
<menu-fold-outlined v-else />
|
||||
</a-button>
|
||||
银行管理后台系统
|
||||
</div>
|
||||
<div class="user-info">
|
||||
<a-dropdown>
|
||||
<a-button type="text" style="color: white;">
|
||||
<user-outlined />
|
||||
{{ userData?.real_name || userData?.username }}
|
||||
<down-outlined />
|
||||
<div v-else class="desktop-layout">
|
||||
<!-- 固定头部 -->
|
||||
<header class="header">
|
||||
<div class="header-content">
|
||||
<div class="logo">
|
||||
<a-button
|
||||
type="text"
|
||||
@click="settingsStore.toggleSidebar"
|
||||
class="menu-toggle"
|
||||
>
|
||||
<menu-unfold-outlined v-if="sidebarCollapsed" />
|
||||
<menu-fold-outlined v-else />
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="profile" @click="goToProfile">
|
||||
<user-outlined />
|
||||
个人中心
|
||||
</a-menu-item>
|
||||
<a-menu-item key="settings" @click="goToSettings">
|
||||
<setting-outlined />
|
||||
系统设置
|
||||
</a-menu-item>
|
||||
<a-menu-divider />
|
||||
<a-menu-item key="logout" @click="handleLogout">
|
||||
<logout-outlined />
|
||||
退出登录
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
<span class="logo-text">银行管理后台系统</span>
|
||||
</div>
|
||||
<div class="user-info">
|
||||
<a-dropdown>
|
||||
<a-button type="text" class="user-button">
|
||||
<user-outlined />
|
||||
{{ userData?.real_name || userData?.username }}
|
||||
<down-outlined />
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="profile" @click="goToProfile">
|
||||
<user-outlined />
|
||||
个人中心
|
||||
</a-menu-item>
|
||||
<a-menu-item key="settings" @click="goToSettings">
|
||||
<setting-outlined />
|
||||
系统设置
|
||||
</a-menu-item>
|
||||
<a-menu-divider />
|
||||
<a-menu-item key="logout" @click="handleLogout">
|
||||
<logout-outlined />
|
||||
退出登录
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</a-layout-header>
|
||||
</header>
|
||||
|
||||
<a-layout class="main-layout">
|
||||
<a-layout-sider
|
||||
class="sidebar"
|
||||
width="200"
|
||||
:collapsed="sidebarCollapsed"
|
||||
collapsible
|
||||
>
|
||||
<DynamicMenu :collapsed="sidebarCollapsed" />
|
||||
</a-layout-sider>
|
||||
|
||||
<a-layout class="content-layout">
|
||||
<a-layout-content class="main-content">
|
||||
<div class="content-wrapper">
|
||||
<router-view />
|
||||
</div>
|
||||
</a-layout-content>
|
||||
|
||||
<a-layout-footer class="footer">
|
||||
银行管理后台系统 ©2025
|
||||
</a-layout-footer>
|
||||
</a-layout>
|
||||
</a-layout>
|
||||
</a-layout>
|
||||
<!-- 侧边栏 -->
|
||||
<aside class="sidebar" :class="{ collapsed: sidebarCollapsed }">
|
||||
<DynamicMenu :collapsed="sidebarCollapsed" />
|
||||
</aside>
|
||||
|
||||
<!-- 主内容区域 -->
|
||||
<main class="main-content">
|
||||
<div class="content-wrapper">
|
||||
<router-view />
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- 页脚 -->
|
||||
<footer class="footer">
|
||||
银行管理后台系统 ©2025
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<router-view />
|
||||
@@ -156,57 +153,139 @@ onUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 全局重置 */
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
#app {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 桌面端布局样式 */
|
||||
.desktop-layout {
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
"header header"
|
||||
"sidebar main";
|
||||
grid-template-columns: 200px 1fr;
|
||||
grid-template-rows: 64px 1fr;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.main-layout {
|
||||
height: calc(100vh - 64px);
|
||||
overflow: hidden;
|
||||
.desktop-layout.collapsed {
|
||||
grid-template-columns: 80px 1fr;
|
||||
}
|
||||
|
||||
/* 头部样式 */
|
||||
/* 头部样式 - 完全修复 */
|
||||
.header {
|
||||
grid-area: header;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
z-index: 1001;
|
||||
background: #001529;
|
||||
color: white;
|
||||
height: 64px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
width: 100vw;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: #001529;
|
||||
color: white;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0 24px;
|
||||
height: 64px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.menu-toggle {
|
||||
color: white !important;
|
||||
margin-right: 8px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
line-height: 64px;
|
||||
height: 64px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 侧边栏样式 */
|
||||
.user-button {
|
||||
color: white !important;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
/* 侧边栏样式 - 完全修复 */
|
||||
.sidebar {
|
||||
grid-area: sidebar;
|
||||
position: fixed;
|
||||
top: 64px;
|
||||
left: 0;
|
||||
width: 200px;
|
||||
height: calc(100vh - 64px);
|
||||
background: #001529 !important;
|
||||
z-index: 999;
|
||||
background: #001529;
|
||||
z-index: 1000;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
transition: width 0.2s;
|
||||
}
|
||||
|
||||
.sidebar.collapsed {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.sidebar::-webkit-scrollbar {
|
||||
@@ -226,35 +305,48 @@ onUnmounted(() => {
|
||||
background: #40a9ff;
|
||||
}
|
||||
|
||||
/* 内容区域样式 */
|
||||
.content-layout {
|
||||
/* 主内容区域样式 - 完全修复 */
|
||||
.main-content {
|
||||
grid-area: main;
|
||||
margin-left: 200px;
|
||||
height: calc(100vh - 64px);
|
||||
transition: margin-left 0.2s;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
height: calc(100vh - 64px - 70px);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
background: #f0f2f5;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
transition: margin-left 0.2s;
|
||||
}
|
||||
|
||||
.desktop-layout.collapsed .main-content {
|
||||
margin-left: 80px;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding: 24px;
|
||||
min-height: 100%;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
min-height: calc(100vh - 64px - 70px);
|
||||
background: #f0f2f5;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 页脚样式 - 完全修复 */
|
||||
.footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 200px;
|
||||
right: 0;
|
||||
height: 70px;
|
||||
line-height: 70px;
|
||||
text-align: center;
|
||||
background: #fff;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
color: #666;
|
||||
z-index: 999;
|
||||
transition: left 0.2s;
|
||||
}
|
||||
|
||||
.desktop-layout.collapsed .footer {
|
||||
left: 80px;
|
||||
}
|
||||
|
||||
/* 侧边栏折叠时的样式 */
|
||||
|
||||
@@ -128,11 +128,103 @@ watch(
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 完全重置菜单样式 - 确保从顶部开始 */
|
||||
.ant-menu {
|
||||
border-right: none;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
height: 100% !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.ant-menu-root {
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
height: 100% !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.ant-menu-vertical {
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
height: 100% !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.ant-menu-inline {
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
height: 100% !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
/* 强制移除所有菜单容器的空白 */
|
||||
.ant-menu ul {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.ant-menu li {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.ant-menu li:first-child {
|
||||
margin-top: 0 !important;
|
||||
padding-top: 0 !important;
|
||||
border-top: none !important;
|
||||
}
|
||||
|
||||
/* 菜单项样式重置 */
|
||||
.ant-menu-item {
|
||||
margin: 0 !important;
|
||||
padding: 0 16px !important;
|
||||
height: 48px !important;
|
||||
line-height: 48px !important;
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
}
|
||||
|
||||
.ant-menu-item:first-child {
|
||||
margin-top: 0 !important;
|
||||
padding-top: 0 !important;
|
||||
border-top: none !important;
|
||||
}
|
||||
|
||||
.ant-menu-submenu-title {
|
||||
margin: 0 !important;
|
||||
padding: 0 16px !important;
|
||||
height: 48px !important;
|
||||
line-height: 48px !important;
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
}
|
||||
|
||||
/* 确保菜单项图标和文字正确对齐 */
|
||||
.ant-menu-item .anticon,
|
||||
.ant-menu-submenu-title .anticon {
|
||||
margin-right: 8px !important;
|
||||
font-size: 16px !important;
|
||||
flex-shrink: 0 !important;
|
||||
}
|
||||
|
||||
.ant-menu-item span,
|
||||
.ant-menu-submenu-title span {
|
||||
flex: 1 !important;
|
||||
white-space: nowrap !important;
|
||||
overflow: hidden !important;
|
||||
text-overflow: ellipsis !important;
|
||||
}
|
||||
|
||||
.ant-menu::-webkit-scrollbar {
|
||||
@@ -158,6 +250,13 @@ watch(
|
||||
align-items: center;
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
margin: 0 !important;
|
||||
padding: 0 16px !important;
|
||||
}
|
||||
|
||||
.ant-menu-item:first-child,
|
||||
.ant-menu-submenu:first-child .ant-menu-submenu-title {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
.ant-menu-item .anticon,
|
||||
|
||||
@@ -17,14 +17,22 @@ import {
|
||||
CreditCardOutlined,
|
||||
DesktopOutlined,
|
||||
TeamOutlined,
|
||||
UserSwitchOutlined
|
||||
UserSwitchOutlined,
|
||||
ProjectOutlined,
|
||||
ClockCircleOutlined,
|
||||
ToolOutlined,
|
||||
CheckCircleOutlined,
|
||||
SearchOutlined,
|
||||
DollarOutlined,
|
||||
BellOutlined,
|
||||
WarningOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
|
||||
// 路由配置
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/dashboard'
|
||||
redirect: '/project-list',
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
@@ -37,14 +45,13 @@ const routes = [
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/dashboard',
|
||||
name: 'Dashboard',
|
||||
component: () => import('@/views/Dashboard.vue'),
|
||||
path: '/test-projects',
|
||||
name: 'TestProjects',
|
||||
component: () => import('@/views/TestProjects.vue'),
|
||||
meta: {
|
||||
title: '仪表盘',
|
||||
icon: DashboardOutlined,
|
||||
requiresAuth: true,
|
||||
roles: ['admin', 'manager', 'teller', 'user']
|
||||
title: '项目接口测试',
|
||||
requiresAuth: false,
|
||||
hideInMenu: true
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -53,7 +60,40 @@ const routes = [
|
||||
component: () => import('@/views/ProjectList.vue'),
|
||||
meta: {
|
||||
title: '项目清单',
|
||||
icon: FileTextOutlined,
|
||||
icon: ProjectOutlined,
|
||||
requiresAuth: true,
|
||||
roles: ['admin', 'manager', 'teller']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/supervision-tasks',
|
||||
name: 'SupervisionTasks',
|
||||
component: () => import('@/views/SupervisionTasks.vue'),
|
||||
meta: {
|
||||
title: '监管任务',
|
||||
icon: ClockCircleOutlined,
|
||||
requiresAuth: true,
|
||||
roles: ['admin', 'manager', 'teller']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/pending-installation',
|
||||
name: 'PendingInstallation',
|
||||
component: () => import('@/views/PendingInstallation.vue'),
|
||||
meta: {
|
||||
title: '待安装任务',
|
||||
icon: ToolOutlined,
|
||||
requiresAuth: true,
|
||||
roles: ['admin', 'manager', 'teller']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/completed-supervision',
|
||||
name: 'CompletedSupervision',
|
||||
component: () => import('@/views/CompletedSupervision.vue'),
|
||||
meta: {
|
||||
title: '监管任务已结项',
|
||||
icon: CheckCircleOutlined,
|
||||
requiresAuth: true,
|
||||
roles: ['admin', 'manager', 'teller']
|
||||
}
|
||||
@@ -64,62 +104,28 @@ const routes = [
|
||||
component: () => import('@/views/SystemCheck.vue'),
|
||||
meta: {
|
||||
title: '系统日检',
|
||||
icon: SafetyOutlined,
|
||||
icon: SearchOutlined,
|
||||
requiresAuth: true,
|
||||
roles: ['admin', 'manager']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/market-trends',
|
||||
name: 'MarketTrends',
|
||||
component: () => import('@/views/MarketTrends.vue'),
|
||||
meta: {
|
||||
title: '市场行情',
|
||||
icon: LineChartOutlined,
|
||||
requiresAuth: true,
|
||||
roles: ['admin', 'manager', 'teller', 'user']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/users',
|
||||
name: 'Users',
|
||||
component: () => import('@/views/Users.vue'),
|
||||
meta: {
|
||||
title: '用户管理',
|
||||
icon: UserOutlined,
|
||||
requiresAuth: true,
|
||||
roles: ['admin', 'manager']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/accounts',
|
||||
name: 'Accounts',
|
||||
component: () => import('@/views/Accounts.vue'),
|
||||
meta: {
|
||||
title: '账户管理',
|
||||
icon: BankOutlined,
|
||||
requiresAuth: true,
|
||||
roles: ['admin', 'manager', 'teller', 'user']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/transactions',
|
||||
name: 'Transactions',
|
||||
component: () => import('@/views/Transactions.vue'),
|
||||
meta: {
|
||||
title: '交易管理',
|
||||
icon: TransactionOutlined,
|
||||
requiresAuth: true,
|
||||
roles: ['admin', 'manager', 'teller', 'user']
|
||||
}
|
||||
},
|
||||
// {
|
||||
// path: '/market-trends',
|
||||
// name: 'MarketTrends',
|
||||
// component: () => import('@/views/MarketTrends.vue'),
|
||||
// meta: {
|
||||
// title: '市场行情',
|
||||
// icon: BarChartOutlined,
|
||||
// requiresAuth: true,
|
||||
// roles: ['admin', 'manager', 'teller', 'user']
|
||||
// }
|
||||
// },
|
||||
{
|
||||
path: '/loan-management',
|
||||
name: 'LoanManagement',
|
||||
component: () => import('@/views/LoanManagement.vue'),
|
||||
meta: {
|
||||
title: '贷款管理',
|
||||
icon: CreditCardOutlined,
|
||||
icon: DollarOutlined,
|
||||
requiresAuth: true,
|
||||
roles: ['admin', 'manager', 'teller']
|
||||
},
|
||||
@@ -166,15 +172,26 @@ const routes = [
|
||||
}
|
||||
]
|
||||
},
|
||||
// {
|
||||
// path: '/message-notification',
|
||||
// name: 'MessageNotification',
|
||||
// component: () => import('@/views/MessageNotification.vue'),
|
||||
// meta: {
|
||||
// title: '消息通知',
|
||||
// icon: BellOutlined,
|
||||
// requiresAuth: true,
|
||||
// roles: ['admin', 'manager', 'teller', 'user']
|
||||
// }
|
||||
// },
|
||||
{
|
||||
path: '/hardware-management',
|
||||
name: 'HardwareManagement',
|
||||
component: () => import('@/views/HardwareManagement.vue'),
|
||||
path: '/device-warning',
|
||||
name: 'DeviceWarning',
|
||||
component: () => import('@/views/DeviceWarning.vue'),
|
||||
meta: {
|
||||
title: '硬件管理',
|
||||
icon: DesktopOutlined,
|
||||
title: '设备预警',
|
||||
icon: WarningOutlined,
|
||||
requiresAuth: true,
|
||||
roles: ['admin', 'manager']
|
||||
roles: ['admin', 'manager', 'teller', 'user']
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -188,17 +205,6 @@ const routes = [
|
||||
roles: ['admin', 'manager']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/reports',
|
||||
name: 'Reports',
|
||||
component: () => import('@/views/Reports.vue'),
|
||||
meta: {
|
||||
title: '报表统计',
|
||||
icon: BarChartOutlined,
|
||||
requiresAuth: true,
|
||||
roles: ['admin', 'manager', 'teller']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/settings',
|
||||
name: 'Settings',
|
||||
|
||||
@@ -9,6 +9,189 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 强制重置所有元素的默认样式 */
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 确保body和html没有默认空白 */
|
||||
html {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
overflow-x: hidden !important;
|
||||
box-sizing: border-box !important;
|
||||
font-size: 14px !important;
|
||||
line-height: 1.5715 !important;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
overflow-x: hidden !important;
|
||||
box-sizing: border-box !important;
|
||||
position: relative !important;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif !important;
|
||||
color: #262626 !important;
|
||||
background-color: #f0f2f5 !important;
|
||||
}
|
||||
|
||||
/* 确保#app容器没有空白 */
|
||||
#app {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
position: relative !important;
|
||||
}
|
||||
|
||||
/* 强制修复Ant Design菜单空白问题 */
|
||||
.ant-menu,
|
||||
.ant-menu-root,
|
||||
.ant-menu-vertical,
|
||||
.ant-menu-inline {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.ant-menu-item,
|
||||
.ant-menu-submenu-title {
|
||||
margin: 0 !important;
|
||||
padding: 0 16px !important;
|
||||
}
|
||||
|
||||
.ant-menu-item:first-child,
|
||||
.ant-menu-submenu:first-child .ant-menu-submenu-title {
|
||||
margin-top: 0 !important;
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
|
||||
/* 确保侧边栏菜单从顶部开始 */
|
||||
.ant-layout-sider .ant-menu,
|
||||
.ant-layout-sider .ant-menu-root,
|
||||
.ant-layout-sider .ant-menu-vertical,
|
||||
.ant-layout-sider .ant-menu-inline {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
/* 强制移除菜单容器的所有空白 */
|
||||
.ant-menu > li:first-child {
|
||||
margin-top: 0 !important;
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
|
||||
.ant-menu .ant-menu-item:first-child {
|
||||
margin-top: 0 !important;
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
|
||||
/* 针对侧边栏的特殊处理 */
|
||||
.ant-layout-sider .ant-menu > li:first-child {
|
||||
margin-top: 0 !important;
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
|
||||
.ant-layout-sider .ant-menu .ant-menu-item:first-child {
|
||||
margin-top: 0 !important;
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
|
||||
/* 最终强制重置 - 确保没有任何空白 */
|
||||
.ant-layout-sider {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
border: none !important;
|
||||
background: #001529 !important;
|
||||
}
|
||||
|
||||
.ant-layout-sider .ant-menu {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
.ant-layout-sider .ant-menu ul {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.ant-layout-sider .ant-menu li {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.ant-layout-sider .ant-menu li:first-child {
|
||||
margin-top: 0 !important;
|
||||
padding-top: 0 !important;
|
||||
border-top: none !important;
|
||||
}
|
||||
|
||||
/* 强制重置菜单项样式 */
|
||||
.ant-layout-sider .ant-menu-item {
|
||||
margin: 0 !important;
|
||||
padding: 0 16px !important;
|
||||
height: 48px !important;
|
||||
line-height: 48px !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.ant-layout-sider .ant-menu-item:first-child {
|
||||
margin-top: 0 !important;
|
||||
padding-top: 0 !important;
|
||||
border-top: none !important;
|
||||
}
|
||||
|
||||
.ant-layout-sider .ant-menu-submenu-title {
|
||||
margin: 0 !important;
|
||||
padding: 0 16px !important;
|
||||
height: 48px !important;
|
||||
line-height: 48px !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
/* 确保头部完全显示 */
|
||||
.ant-layout-header {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
line-height: 64px !important;
|
||||
height: 64px !important;
|
||||
position: fixed !important;
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
right: 0 !important;
|
||||
z-index: 1001 !important;
|
||||
width: 100vw !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
/* 强制重置所有可能的头部样式 */
|
||||
.ant-layout-header * {
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
/* 确保头部内容不被截断 */
|
||||
.ant-layout-header .logo,
|
||||
.ant-layout-header .user-info {
|
||||
line-height: 64px !important;
|
||||
height: 64px !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
}
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
@@ -654,6 +654,147 @@ export const api = {
|
||||
async getStats(params = {}) {
|
||||
return api.get('/transactions/stats', { params })
|
||||
}
|
||||
},
|
||||
|
||||
// 项目清单API
|
||||
projects: {
|
||||
/**
|
||||
* 获取项目列表
|
||||
* @param {Object} params - 查询参数
|
||||
* @returns {Promise} 项目列表
|
||||
*/
|
||||
async getList(params = {}) {
|
||||
return api.get('/projects', { params })
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取项目详情
|
||||
* @param {number} id - 项目ID
|
||||
* @returns {Promise} 项目详情
|
||||
*/
|
||||
async getById(id) {
|
||||
return api.get(`/projects/${id}`)
|
||||
},
|
||||
|
||||
/**
|
||||
* 创建项目
|
||||
* @param {Object} data - 项目数据
|
||||
* @returns {Promise} 创建结果
|
||||
*/
|
||||
async create(data) {
|
||||
return api.post('/projects', data)
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新项目
|
||||
* @param {number} id - 项目ID
|
||||
* @param {Object} data - 项目数据
|
||||
* @returns {Promise} 更新结果
|
||||
*/
|
||||
async update(id, data) {
|
||||
return api.put(`/projects/${id}`, data)
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除项目
|
||||
* @param {number} id - 项目ID
|
||||
* @returns {Promise} 删除结果
|
||||
*/
|
||||
async delete(id) {
|
||||
return api.delete(`/projects/${id}`)
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取项目统计
|
||||
* @returns {Promise} 统计数据
|
||||
*/
|
||||
async getStats() {
|
||||
return api.get('/projects/stats')
|
||||
},
|
||||
|
||||
/**
|
||||
* 批量更新项目状态
|
||||
* @param {Object} data - 批量更新数据
|
||||
* @returns {Promise} 更新结果
|
||||
*/
|
||||
async batchUpdateStatus(data) {
|
||||
return api.put('/projects/batch/status', data)
|
||||
}
|
||||
},
|
||||
|
||||
// 监管任务API
|
||||
supervisionTasks: {
|
||||
/**
|
||||
* 获取监管任务列表
|
||||
* @param {Object} params - 查询参数
|
||||
* @returns {Promise} 监管任务列表
|
||||
*/
|
||||
async getList(params = {}) {
|
||||
return api.get('/supervision-tasks', { params })
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取监管任务详情
|
||||
* @param {number} id - 监管任务ID
|
||||
* @returns {Promise} 监管任务详情
|
||||
*/
|
||||
async getById(id) {
|
||||
return api.get(`/supervision-tasks/${id}`)
|
||||
},
|
||||
|
||||
/**
|
||||
* 创建监管任务
|
||||
* @param {Object} data - 监管任务数据
|
||||
* @returns {Promise} 创建结果
|
||||
*/
|
||||
async create(data) {
|
||||
return api.post('/supervision-tasks', data)
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新监管任务
|
||||
* @param {number} id - 监管任务ID
|
||||
* @param {Object} data - 监管任务数据
|
||||
* @returns {Promise} 更新结果
|
||||
*/
|
||||
async update(id, data) {
|
||||
return api.put(`/supervision-tasks/${id}`, data)
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除监管任务
|
||||
* @param {number} id - 监管任务ID
|
||||
* @returns {Promise} 删除结果
|
||||
*/
|
||||
async delete(id) {
|
||||
return api.delete(`/supervision-tasks/${id}`)
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取监管任务统计
|
||||
* @returns {Promise} 统计数据
|
||||
*/
|
||||
async getStats() {
|
||||
return api.get('/supervision-tasks/stats')
|
||||
},
|
||||
|
||||
/**
|
||||
* 批量更新监管任务状态
|
||||
* @param {Object} data - 批量更新数据
|
||||
* @returns {Promise} 更新结果
|
||||
*/
|
||||
async batchUpdateStatus(data) {
|
||||
return api.put('/supervision-tasks/batch/status', data)
|
||||
},
|
||||
|
||||
/**
|
||||
* 批量删除监管任务
|
||||
* @param {Object} data - 批量删除数据
|
||||
* @returns {Promise} 删除结果
|
||||
*/
|
||||
async batchDelete(data) {
|
||||
return api.delete('/supervision-tasks/batch', { data })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
336
bank-frontend/src/views/CompletedSupervision.vue
Normal file
336
bank-frontend/src/views/CompletedSupervision.vue
Normal file
@@ -0,0 +1,336 @@
|
||||
<template>
|
||||
<div class="completed-supervision-page">
|
||||
<!-- 页面头部 -->
|
||||
<div class="page-header">
|
||||
<h1>监管任务结项</h1>
|
||||
<div class="header-actions">
|
||||
<a-button type="primary" @click="handleBatchImport">
|
||||
<upload-outlined />
|
||||
批量导入
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索筛选区域 -->
|
||||
<div class="search-section">
|
||||
<a-row :gutter="16" align="middle">
|
||||
<a-col :span="4">
|
||||
<a-select
|
||||
v-model:value="searchForm.contractNumber"
|
||||
placeholder="合同编号"
|
||||
allow-clear
|
||||
style="width: 100%"
|
||||
>
|
||||
<a-select-option value="LOAN2024001">LOAN2024001</a-select-option>
|
||||
<a-select-option value="LOAN2024002">LOAN2024002</a-select-option>
|
||||
<a-select-option value="LOAN2024003">LOAN2024003</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-input
|
||||
v-model:value="searchForm.keyword"
|
||||
placeholder="请输入关键字"
|
||||
allow-clear
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
<search-outlined />
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button @click="handleReset">重置</a-button>
|
||||
</a-space>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<div class="table-section">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="filteredTasks"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
@change="handleTableChange"
|
||||
row-key="id"
|
||||
:locale="{ emptyText: '暂无数据' }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'settlementStatus'">
|
||||
<a-tag :color="getSettlementStatusColor(record.settlementStatus)">
|
||||
{{ getSettlementStatusName(record.settlementStatus) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="viewTask(record)">查看</a-button>
|
||||
<a-button type="link" size="small" @click="editTask(record)">编辑</a-button>
|
||||
<a-button type="link" size="small" @click="exportTask(record)">导出</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { UploadOutlined, SearchOutlined } from '@ant-design/icons-vue'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
const loading = ref(false)
|
||||
const tasks = ref([])
|
||||
|
||||
const searchForm = reactive({
|
||||
contractNumber: undefined,
|
||||
keyword: '',
|
||||
})
|
||||
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total) => `共 ${total} 条`,
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{ title: '申请单号', dataIndex: 'applicationNumber', key: 'applicationNumber', sorter: true },
|
||||
{ title: '放款合同编号', dataIndex: 'contractNumber', key: 'contractNumber', sorter: true },
|
||||
{ title: '产品名称', dataIndex: 'productName', key: 'productName' },
|
||||
{ title: '客户姓名', dataIndex: 'customerName', key: 'customerName' },
|
||||
{ title: '证件类型', dataIndex: 'idType', key: 'idType' },
|
||||
{ title: '证件号码', dataIndex: 'idNumber', key: 'idNumber' },
|
||||
{ title: '养殖生资种类', dataIndex: 'assetType', key: 'assetType' },
|
||||
{ title: '监管生资数量', dataIndex: 'assetQuantity', key: 'assetQuantity', sorter: true },
|
||||
{ title: '总还款期数', dataIndex: 'totalRepaymentPeriods', key: 'totalRepaymentPeriods', sorter: true },
|
||||
{ title: '结清状态', dataIndex: 'settlementStatus', key: 'settlementStatus', filters: [
|
||||
{ text: '已结清', value: 'settled' },
|
||||
{ text: '未结清', value: 'unsettled' },
|
||||
{ text: '部分结清', value: 'partial' },
|
||||
]},
|
||||
{ title: '结清日期', dataIndex: 'settlementDate', key: 'settlementDate', sorter: true },
|
||||
{ title: '结清任务导入时间', dataIndex: 'importTime', key: 'importTime', sorter: true },
|
||||
{ title: '操作', key: 'action', fixed: 'right', width: 180 },
|
||||
]
|
||||
|
||||
// 模拟数据
|
||||
const mockTasks = [
|
||||
{
|
||||
id: 'S001',
|
||||
applicationNumber: 'APP2024001',
|
||||
contractNumber: 'LOAN2024001',
|
||||
productName: '生猪养殖贷',
|
||||
customerName: '张三',
|
||||
idType: 'ID_CARD',
|
||||
idNumber: '4401XXXXXXXXXXXXXX',
|
||||
assetType: '生猪',
|
||||
assetQuantity: 500,
|
||||
totalRepaymentPeriods: 12,
|
||||
settlementStatus: 'settled',
|
||||
settlementDate: '2024-01-15',
|
||||
importTime: '2024-01-15 10:30:00',
|
||||
},
|
||||
{
|
||||
id: 'S002',
|
||||
applicationNumber: 'APP2024002',
|
||||
contractNumber: 'LOAN2024002',
|
||||
productName: '肉牛养殖贷',
|
||||
customerName: '李四',
|
||||
idType: 'ID_CARD',
|
||||
idNumber: '4402XXXXXXXXXXXXXX',
|
||||
assetType: '肉牛',
|
||||
assetQuantity: 150,
|
||||
totalRepaymentPeriods: 24,
|
||||
settlementStatus: 'partial',
|
||||
settlementDate: '2024-01-20',
|
||||
importTime: '2024-01-20 14:20:00',
|
||||
},
|
||||
{
|
||||
id: 'S003',
|
||||
applicationNumber: 'APP2024003',
|
||||
contractNumber: 'LOAN2024003',
|
||||
productName: '蛋鸡养殖贷',
|
||||
customerName: '王五',
|
||||
idType: 'ID_CARD',
|
||||
idNumber: '4403XXXXXXXXXXXXXX',
|
||||
assetType: '蛋鸡',
|
||||
assetQuantity: 10000,
|
||||
totalRepaymentPeriods: 18,
|
||||
settlementStatus: 'unsettled',
|
||||
settlementDate: null,
|
||||
importTime: '2024-01-10 09:15:00',
|
||||
},
|
||||
]
|
||||
|
||||
const fetchTasks = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 实际项目中这里会调用API获取数据
|
||||
// const response = await api.completedSupervision.getList({
|
||||
// page: pagination.current,
|
||||
// pageSize: pagination.pageSize,
|
||||
// ...searchForm,
|
||||
// })
|
||||
|
||||
// 使用模拟数据
|
||||
tasks.value = mockTasks.map(task => ({
|
||||
...task,
|
||||
settlementDate: task.settlementDate ? dayjs(task.settlementDate) : null,
|
||||
importTime: dayjs(task.importTime),
|
||||
}))
|
||||
pagination.total = mockTasks.length
|
||||
} catch (error) {
|
||||
console.error('获取结项任务失败:', error)
|
||||
message.error('获取结项任务失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const filteredTasks = computed(() => {
|
||||
let result = tasks.value
|
||||
|
||||
if (searchForm.contractNumber) {
|
||||
result = result.filter(task => task.contractNumber === searchForm.contractNumber)
|
||||
}
|
||||
|
||||
if (searchForm.keyword) {
|
||||
result = result.filter(task =>
|
||||
task.applicationNumber.toLowerCase().includes(searchForm.keyword.toLowerCase()) ||
|
||||
task.customerName.toLowerCase().includes(searchForm.keyword.toLowerCase()) ||
|
||||
task.productName.toLowerCase().includes(searchForm.keyword.toLowerCase())
|
||||
)
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.current = 1
|
||||
fetchTasks()
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
searchForm.contractNumber = undefined
|
||||
searchForm.keyword = ''
|
||||
pagination.current = 1
|
||||
fetchTasks()
|
||||
}
|
||||
|
||||
const handleBatchImport = () => {
|
||||
message.info('批量导入功能开发中...')
|
||||
}
|
||||
|
||||
const handleTableChange = (pag, filters, sorter) => {
|
||||
pagination.current = pag.current
|
||||
pagination.pageSize = pag.pageSize
|
||||
fetchTasks()
|
||||
}
|
||||
|
||||
const getSettlementStatusColor = (status) => {
|
||||
const colors = {
|
||||
'settled': 'green',
|
||||
'unsettled': 'red',
|
||||
'partial': 'orange',
|
||||
}
|
||||
return colors[status] || 'default'
|
||||
}
|
||||
|
||||
const getSettlementStatusName = (status) => {
|
||||
const names = {
|
||||
'settled': '已结清',
|
||||
'unsettled': '未结清',
|
||||
'partial': '部分结清',
|
||||
}
|
||||
return names[status] || status
|
||||
}
|
||||
|
||||
const viewTask = (record) => {
|
||||
message.info(`查看任务: ${record.applicationNumber}`)
|
||||
}
|
||||
|
||||
const editTask = (record) => {
|
||||
message.info(`编辑任务: ${record.applicationNumber}`)
|
||||
}
|
||||
|
||||
const exportTask = (record) => {
|
||||
message.success(`导出任务: ${record.applicationNumber}`)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchTasks()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.completed-supervision-page {
|
||||
padding: 0;
|
||||
background: #f0f2f5;
|
||||
min-height: calc(100vh - 134px);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
padding: 24px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
background: white;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.table-section {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.search-section .ant-col {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.search-section .ant-col:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
421
bank-frontend/src/views/DeviceWarning.vue
Normal file
421
bank-frontend/src/views/DeviceWarning.vue
Normal file
@@ -0,0 +1,421 @@
|
||||
<template>
|
||||
<div class="device-warning">
|
||||
<div class="page-header">
|
||||
<h1>设备预警</h1>
|
||||
<p>监控和管理银行设备运行状态预警</p>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<!-- 预警统计 -->
|
||||
<div class="overview-section">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic
|
||||
title="总预警数"
|
||||
:value="warningStats.total"
|
||||
:value-style="{ color: '#ff4d4f' }"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic
|
||||
title="严重预警"
|
||||
:value="warningStats.critical"
|
||||
:value-style="{ color: '#ff4d4f' }"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic
|
||||
title="一般预警"
|
||||
:value="warningStats.warning"
|
||||
:value-style="{ color: '#faad14' }"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic
|
||||
title="已处理"
|
||||
:value="warningStats.resolved"
|
||||
:value-style="{ color: '#52c41a' }"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 搜索和筛选 -->
|
||||
<div class="search-section">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<a-input-search
|
||||
v-model:value="searchText"
|
||||
placeholder="搜索设备名称"
|
||||
enter-button="搜索"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-select
|
||||
v-model:value="levelFilter"
|
||||
placeholder="预警级别"
|
||||
allow-clear
|
||||
@change="handleFilter"
|
||||
>
|
||||
<a-select-option value="critical">严重</a-select-option>
|
||||
<a-select-option value="warning">一般</a-select-option>
|
||||
<a-select-option value="info">信息</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-select
|
||||
v-model:value="statusFilter"
|
||||
placeholder="处理状态"
|
||||
allow-clear
|
||||
@change="handleFilter"
|
||||
>
|
||||
<a-select-option value="pending">待处理</a-select-option>
|
||||
<a-select-option value="processing">处理中</a-select-option>
|
||||
<a-select-option value="resolved">已解决</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-button type="primary" @click="refreshWarnings">
|
||||
<reload-outlined /> 刷新
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 预警列表 -->
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="filteredWarnings"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
@change="handleTableChange"
|
||||
row-key="id"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'level'">
|
||||
<a-tag :color="getLevelColor(record.level)">
|
||||
{{ getLevelName(record.level) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusName(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="viewWarning(record)">查看</a-button>
|
||||
<a-button type="link" size="small" @click="handleWarning(record.id)">处理</a-button>
|
||||
<a-button type="link" size="small" @click="resolveWarning(record.id)">解决</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
|
||||
<!-- 预警详情对话框 -->
|
||||
<a-modal
|
||||
v-model:open="detailModalVisible"
|
||||
title="预警详情"
|
||||
width="800px"
|
||||
:footer="null"
|
||||
>
|
||||
<div v-if="selectedWarning" class="warning-detail">
|
||||
<a-descriptions :column="2" bordered>
|
||||
<a-descriptions-item label="设备名称">
|
||||
{{ selectedWarning.deviceName }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="预警级别">
|
||||
<a-tag :color="getLevelColor(selectedWarning.level)">
|
||||
{{ getLevelName(selectedWarning.level) }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="预警类型">
|
||||
{{ selectedWarning.type }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="处理状态">
|
||||
<a-tag :color="getStatusColor(selectedWarning.status)">
|
||||
{{ getStatusName(selectedWarning.status) }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="发生时间">
|
||||
{{ selectedWarning.occurTime }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="处理人员">
|
||||
{{ selectedWarning.handler || '未分配' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="预警描述" :span="2">
|
||||
{{ selectedWarning.description }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { ReloadOutlined } from '@ant-design/icons-vue'
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const searchText = ref('')
|
||||
const levelFilter = ref(undefined)
|
||||
const statusFilter = ref(undefined)
|
||||
const detailModalVisible = ref(false)
|
||||
const selectedWarning = ref(null)
|
||||
|
||||
// 预警统计
|
||||
const warningStats = ref({
|
||||
total: 23,
|
||||
critical: 5,
|
||||
warning: 12,
|
||||
resolved: 6
|
||||
})
|
||||
|
||||
// 分页配置
|
||||
const pagination = ref({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条,共 ${total} 条`
|
||||
})
|
||||
|
||||
// 表格列配置
|
||||
const columns = [
|
||||
{
|
||||
title: '设备名称',
|
||||
dataIndex: 'deviceName',
|
||||
key: 'deviceName',
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: '预警级别',
|
||||
dataIndex: 'level',
|
||||
key: 'level',
|
||||
filters: [
|
||||
{ text: '严重', value: 'critical' },
|
||||
{ text: '一般', value: 'warning' },
|
||||
{ text: '信息', value: 'info' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '预警类型',
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
},
|
||||
{
|
||||
title: '处理状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
filters: [
|
||||
{ text: '待处理', value: 'pending' },
|
||||
{ text: '处理中', value: 'processing' },
|
||||
{ text: '已解决', value: 'resolved' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '发生时间',
|
||||
dataIndex: 'occurTime',
|
||||
key: 'occurTime',
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: '处理人员',
|
||||
dataIndex: 'handler',
|
||||
key: 'handler',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 200,
|
||||
},
|
||||
]
|
||||
|
||||
// 模拟预警数据
|
||||
const warnings = ref([
|
||||
{
|
||||
id: 1,
|
||||
deviceName: 'ATM-001',
|
||||
level: 'critical',
|
||||
type: '硬件故障',
|
||||
status: 'pending',
|
||||
occurTime: '2024-01-22 14:30:25',
|
||||
handler: null,
|
||||
description: 'ATM机读卡器故障,无法正常读取银行卡'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
deviceName: 'POS-002',
|
||||
level: 'warning',
|
||||
type: '网络异常',
|
||||
status: 'processing',
|
||||
occurTime: '2024-01-22 13:15:10',
|
||||
handler: '张三',
|
||||
description: 'POS机网络连接不稳定,偶尔出现断线'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
deviceName: 'SERVER-003',
|
||||
level: 'critical',
|
||||
type: '系统异常',
|
||||
status: 'resolved',
|
||||
occurTime: '2024-01-22 10:20:30',
|
||||
handler: '李四',
|
||||
description: '服务器CPU使用率过高,已重启解决'
|
||||
}
|
||||
])
|
||||
|
||||
// 计算属性
|
||||
const filteredWarnings = computed(() => {
|
||||
let result = warnings.value
|
||||
|
||||
if (searchText.value) {
|
||||
result = result.filter(warning =>
|
||||
warning.deviceName.toLowerCase().includes(searchText.value.toLowerCase())
|
||||
)
|
||||
}
|
||||
|
||||
if (levelFilter.value) {
|
||||
result = result.filter(warning => warning.level === levelFilter.value)
|
||||
}
|
||||
|
||||
if (statusFilter.value) {
|
||||
result = result.filter(warning => warning.status === statusFilter.value)
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
// 方法
|
||||
const getLevelColor = (level) => {
|
||||
const colors = {
|
||||
'critical': 'red',
|
||||
'warning': 'orange',
|
||||
'info': 'blue'
|
||||
}
|
||||
return colors[level] || 'default'
|
||||
}
|
||||
|
||||
const getLevelName = (level) => {
|
||||
const names = {
|
||||
'critical': '严重',
|
||||
'warning': '一般',
|
||||
'info': '信息'
|
||||
}
|
||||
return names[level] || level
|
||||
}
|
||||
|
||||
const getStatusColor = (status) => {
|
||||
const colors = {
|
||||
'pending': 'red',
|
||||
'processing': 'orange',
|
||||
'resolved': 'green'
|
||||
}
|
||||
return colors[status] || 'default'
|
||||
}
|
||||
|
||||
const getStatusName = (status) => {
|
||||
const names = {
|
||||
'pending': '待处理',
|
||||
'processing': '处理中',
|
||||
'resolved': '已解决'
|
||||
}
|
||||
return names[status] || status
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.value.current = 1
|
||||
}
|
||||
|
||||
const handleFilter = () => {
|
||||
pagination.value.current = 1
|
||||
}
|
||||
|
||||
const handleTableChange = (pag) => {
|
||||
pagination.value = pag
|
||||
}
|
||||
|
||||
const refreshWarnings = () => {
|
||||
loading.value = true
|
||||
setTimeout(() => {
|
||||
loading.value = false
|
||||
message.success('预警信息已刷新')
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const viewWarning = (warning) => {
|
||||
selectedWarning.value = warning
|
||||
detailModalVisible.value = true
|
||||
}
|
||||
|
||||
const handleWarning = (id) => {
|
||||
const warning = warnings.value.find(w => w.id === id)
|
||||
if (warning) {
|
||||
warning.status = 'processing'
|
||||
warning.handler = '当前用户'
|
||||
message.success('预警已开始处理')
|
||||
}
|
||||
}
|
||||
|
||||
const resolveWarning = (id) => {
|
||||
const warning = warnings.value.find(w => w.id === id)
|
||||
if (warning) {
|
||||
warning.status = 'resolved'
|
||||
message.success('预警已解决')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
pagination.value.total = warnings.value.length
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.device-warning {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
margin: 0;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.overview-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
margin-bottom: 24px;
|
||||
padding: 16px;
|
||||
background: #fafafa;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.warning-detail {
|
||||
padding: 16px 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,168 +1,64 @@
|
||||
<template>
|
||||
<div class="employee-management">
|
||||
<div class="employee-management-page">
|
||||
<div class="page-header">
|
||||
<h1>员工管理</h1>
|
||||
<p>管理和维护银行员工信息</p>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<!-- 员工概览 -->
|
||||
<div class="overview-section">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic
|
||||
title="员工总数"
|
||||
:value="employeeStats.total"
|
||||
:value-style="{ color: '#1890ff' }"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic
|
||||
title="在职员工"
|
||||
:value="employeeStats.active"
|
||||
:value-style="{ color: '#52c41a' }"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic
|
||||
title="离职员工"
|
||||
:value="employeeStats.inactive"
|
||||
:value-style="{ color: '#ff4d4f' }"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic
|
||||
title="本月入职"
|
||||
:value="employeeStats.newHires"
|
||||
:value-style="{ color: '#faad14' }"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
<div class="search-filter-section">
|
||||
<a-row :gutter="16" align="middle">
|
||||
<a-col :span="4">
|
||||
<a-button type="primary" @click="handleAddEmployee">
|
||||
<plus-outlined /> 新增人员
|
||||
</a-button>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-input
|
||||
v-model:value="searchText"
|
||||
placeholder="请输入员工姓名"
|
||||
allow-clear
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
<search-outlined /> 搜索
|
||||
</a-button>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-button @click="handleReset">重置</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 搜索和筛选 -->
|
||||
<div class="search-section">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<a-input-search
|
||||
v-model:value="searchText"
|
||||
placeholder="搜索员工姓名或工号"
|
||||
enter-button="搜索"
|
||||
@search="handleSearch"
|
||||
<div class="employees-table-section">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="filteredEmployees"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
row-key="id"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-switch
|
||||
v-model:checked="record.status"
|
||||
checked-children="启用"
|
||||
un-checked-children="禁用"
|
||||
@change="handleToggleStatus(record)"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-select
|
||||
v-model:value="statusFilter"
|
||||
placeholder="员工状态"
|
||||
allow-clear
|
||||
@change="handleFilter"
|
||||
>
|
||||
<a-select-option value="active">在职</a-select-option>
|
||||
<a-select-option value="inactive">离职</a-select-option>
|
||||
<a-select-option value="suspended">停职</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-select
|
||||
v-model:value="departmentFilter"
|
||||
placeholder="部门"
|
||||
allow-clear
|
||||
@change="handleFilter"
|
||||
>
|
||||
<a-select-option value="admin">行政部</a-select-option>
|
||||
<a-select-option value="finance">财务部</a-select-option>
|
||||
<a-select-option value="it">技术部</a-select-option>
|
||||
<a-select-option value="hr">人事部</a-select-option>
|
||||
<a-select-option value="sales">销售部</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-select
|
||||
v-model:value="positionFilter"
|
||||
placeholder="职位"
|
||||
allow-clear
|
||||
@change="handleFilter"
|
||||
>
|
||||
<a-select-option value="manager">经理</a-select-option>
|
||||
<a-select-option value="supervisor">主管</a-select-option>
|
||||
<a-select-option value="staff">员工</a-select-option>
|
||||
<a-select-option value="intern">实习生</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleAddEmployee">
|
||||
<PlusOutlined />
|
||||
添加员工
|
||||
<a-button type="link" size="small" @click="handleResetPassword(record)">
|
||||
重设密码
|
||||
</a-button>
|
||||
<a-button @click="handleExport">
|
||||
<DownloadOutlined />
|
||||
导出
|
||||
<a-button type="link" size="small" @click="handleEdit(record)">
|
||||
编辑
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 员工列表 -->
|
||||
<div class="table-section">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="filteredEmployees"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
row-key="id"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'department'">
|
||||
<a-tag :color="getDepartmentColor(record.department)">
|
||||
{{ getDepartmentText(record.department) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'position'">
|
||||
{{ getPositionText(record.position) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'avatar'">
|
||||
<a-avatar :src="record.avatar" :size="32">
|
||||
{{ record.name.charAt(0) }}
|
||||
</a-avatar>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="handleView(record)">
|
||||
查看
|
||||
</a-button>
|
||||
<a-button type="link" size="small" @click="handleEdit(record)">
|
||||
编辑
|
||||
</a-button>
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleToggleStatus(record)"
|
||||
:danger="record.status === 'active'"
|
||||
>
|
||||
{{ record.status === 'active' ? '停职' : '复职' }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
|
||||
<!-- 员工详情模态框 -->
|
||||
@@ -266,26 +162,15 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { PlusOutlined, DownloadOutlined } from '@ant-design/icons-vue'
|
||||
import { PlusOutlined, SearchOutlined } from '@ant-design/icons-vue'
|
||||
import { api } from '@/utils/api'
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const searchText = ref('')
|
||||
const statusFilter = ref(undefined)
|
||||
const departmentFilter = ref(undefined)
|
||||
const positionFilter = ref(undefined)
|
||||
const detailModalVisible = ref(false)
|
||||
const selectedEmployee = ref(null)
|
||||
|
||||
// 员工统计
|
||||
const employeeStats = ref({
|
||||
total: 156,
|
||||
active: 142,
|
||||
inactive: 14,
|
||||
newHires: 8
|
||||
})
|
||||
|
||||
// 分页配置
|
||||
const pagination = ref({
|
||||
current: 1,
|
||||
@@ -293,58 +178,45 @@ const pagination = ref({
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条,共 ${total} 条`
|
||||
showTotal: (total) => `共${total}条`
|
||||
})
|
||||
|
||||
// 表格列配置
|
||||
const columns = [
|
||||
{
|
||||
title: '头像',
|
||||
dataIndex: 'avatar',
|
||||
key: 'avatar',
|
||||
width: 80
|
||||
title: '序号',
|
||||
dataIndex: 'index',
|
||||
key: 'index',
|
||||
width: 80,
|
||||
customRender: ({ index }) => index + 1
|
||||
},
|
||||
{
|
||||
title: '姓名',
|
||||
title: '员工姓名',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '工号',
|
||||
dataIndex: 'employeeId',
|
||||
key: 'employeeId',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '部门',
|
||||
dataIndex: 'department',
|
||||
key: 'department',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '职位',
|
||||
dataIndex: 'position',
|
||||
key: 'position',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '手机号',
|
||||
title: '联系电话',
|
||||
dataIndex: 'phone',
|
||||
key: 'phone',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '贷款专员',
|
||||
dataIndex: 'isLoanSpecialist',
|
||||
key: 'isLoanSpecialist',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '入职日期',
|
||||
dataIndex: 'hireDate',
|
||||
key: 'hireDate',
|
||||
width: 120
|
||||
title: '账号状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: 120,
|
||||
filters: [
|
||||
{ text: '启用', value: true },
|
||||
{ text: '禁用', value: false }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
@@ -361,21 +233,22 @@ const employees = ref([])
|
||||
const mockEmployees = [
|
||||
{
|
||||
id: 1,
|
||||
name: '张三',
|
||||
name: '刘超',
|
||||
phone: '150****1368',
|
||||
isLoanSpecialist: '否',
|
||||
status: true,
|
||||
employeeId: 'EMP001',
|
||||
gender: '男',
|
||||
age: 28,
|
||||
phone: '13800138000',
|
||||
email: 'zhangsan@bank.com',
|
||||
email: 'liuchao@bank.com',
|
||||
idCard: '110101199001011234',
|
||||
hireDate: '2020-03-15',
|
||||
department: 'admin',
|
||||
position: 'manager',
|
||||
status: 'active',
|
||||
supervisor: '李总',
|
||||
salaryLevel: 'L5',
|
||||
workLocation: '总行',
|
||||
emergencyContact: '张四',
|
||||
emergencyContact: '刘四',
|
||||
emergencyPhone: '13900139000',
|
||||
bio: '具有5年银行管理经验,擅长团队管理和业务规划',
|
||||
avatar: null,
|
||||
@@ -387,76 +260,6 @@ const mockEmployees = [
|
||||
startDate: '2020-03-15',
|
||||
endDate: null,
|
||||
description: '负责行政部日常管理工作'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '行政专员',
|
||||
company: '某银行',
|
||||
startDate: '2018-06-01',
|
||||
endDate: '2020-03-14',
|
||||
description: '负责行政事务处理'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '李四',
|
||||
employeeId: 'EMP002',
|
||||
gender: '女',
|
||||
age: 25,
|
||||
phone: '13900139000',
|
||||
email: 'lisi@bank.com',
|
||||
idCard: '110101199002021234',
|
||||
hireDate: '2021-07-01',
|
||||
department: 'finance',
|
||||
position: 'staff',
|
||||
status: 'active',
|
||||
supervisor: '王经理',
|
||||
salaryLevel: 'L3',
|
||||
workLocation: '分行',
|
||||
emergencyContact: '李五',
|
||||
emergencyPhone: '13700137000',
|
||||
bio: '财务专业毕业,具有3年财务工作经验',
|
||||
avatar: null,
|
||||
experience: [
|
||||
{
|
||||
id: 1,
|
||||
title: '财务专员',
|
||||
company: '某银行',
|
||||
startDate: '2021-07-01',
|
||||
endDate: null,
|
||||
description: '负责财务核算和报表编制'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '王五',
|
||||
employeeId: 'EMP003',
|
||||
gender: '男',
|
||||
age: 32,
|
||||
phone: '13700137000',
|
||||
email: 'wangwu@bank.com',
|
||||
idCard: '110101199003031234',
|
||||
hireDate: '2019-01-10',
|
||||
department: 'it',
|
||||
position: 'supervisor',
|
||||
status: 'inactive',
|
||||
supervisor: '赵总',
|
||||
salaryLevel: 'L4',
|
||||
workLocation: '总行',
|
||||
emergencyContact: '王六',
|
||||
emergencyPhone: '13600136000',
|
||||
bio: '计算机专业,具有8年IT工作经验,擅长系统开发',
|
||||
avatar: null,
|
||||
experience: [
|
||||
{
|
||||
id: 1,
|
||||
title: '技术主管',
|
||||
company: '某银行',
|
||||
startDate: '2019-01-10',
|
||||
endDate: '2024-01-15',
|
||||
description: '负责技术团队管理和系统开发'
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -468,46 +271,24 @@ const filteredEmployees = computed(() => {
|
||||
|
||||
if (searchText.value) {
|
||||
result = result.filter(employee =>
|
||||
employee.name.toLowerCase().includes(searchText.value.toLowerCase()) ||
|
||||
employee.employeeId.toLowerCase().includes(searchText.value.toLowerCase())
|
||||
employee.name.toLowerCase().includes(searchText.value.toLowerCase())
|
||||
)
|
||||
}
|
||||
|
||||
if (statusFilter.value) {
|
||||
result = result.filter(employee => employee.status === statusFilter.value)
|
||||
}
|
||||
|
||||
if (departmentFilter.value) {
|
||||
result = result.filter(employee => employee.department === departmentFilter.value)
|
||||
}
|
||||
|
||||
if (positionFilter.value) {
|
||||
result = result.filter(employee => employee.position === positionFilter.value)
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
// 方法
|
||||
const handleAddEmployee = () => {
|
||||
message.info('新增人员功能开发中...')
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
// 搜索逻辑已在计算属性中处理
|
||||
}
|
||||
|
||||
const handleFilter = () => {
|
||||
// 筛选逻辑已在计算属性中处理
|
||||
}
|
||||
|
||||
const handleTableChange = (pag) => {
|
||||
pagination.value.current = pag.current
|
||||
pagination.value.pageSize = pag.pageSize
|
||||
}
|
||||
|
||||
const handleAddEmployee = () => {
|
||||
message.info('添加员工功能开发中...')
|
||||
}
|
||||
|
||||
const handleExport = () => {
|
||||
message.info('导出功能开发中...')
|
||||
const handleReset = () => {
|
||||
searchText.value = ''
|
||||
}
|
||||
|
||||
const handleView = (record) => {
|
||||
@@ -519,10 +300,13 @@ const handleEdit = (record) => {
|
||||
message.info(`编辑员工: ${record.name}`)
|
||||
}
|
||||
|
||||
const handleResetPassword = (record) => {
|
||||
message.info(`重设密码: ${record.name}`)
|
||||
}
|
||||
|
||||
const handleToggleStatus = (record) => {
|
||||
const newStatus = record.status === 'active' ? 'inactive' : 'active'
|
||||
record.status = newStatus
|
||||
message.success(`员工已${newStatus === 'active' ? '复职' : '停职'}`)
|
||||
record.status = !record.status
|
||||
message.success(`员工账号已${record.status ? '启用' : '禁用'}`)
|
||||
}
|
||||
|
||||
const getStatusColor = (status) => {
|
||||
@@ -583,9 +367,6 @@ const fetchEmployees = async (params = {}) => {
|
||||
page: pagination.value.current,
|
||||
limit: pagination.value.pageSize,
|
||||
search: searchText.value,
|
||||
status: statusFilter.value,
|
||||
department: departmentFilter.value,
|
||||
position: positionFilter.value,
|
||||
...params
|
||||
})
|
||||
|
||||
@@ -609,32 +390,6 @@ const fetchEmployees = async (params = {}) => {
|
||||
}
|
||||
}
|
||||
|
||||
const fetchEmployeeStats = async () => {
|
||||
try {
|
||||
const response = await api.employees.getStats()
|
||||
if (response.success) {
|
||||
employeeStats.value = {
|
||||
total: response.data.total || 0,
|
||||
active: response.data.active || 0,
|
||||
inactive: response.data.inactive || 0,
|
||||
newHires: 0 // 这个需要单独计算
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取员工统计失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.value.current = 1
|
||||
fetchEmployees()
|
||||
}
|
||||
|
||||
const handleFilter = () => {
|
||||
pagination.value.current = 1
|
||||
fetchEmployees()
|
||||
}
|
||||
|
||||
const handleTableChange = (paginationInfo) => {
|
||||
pagination.value = paginationInfo
|
||||
fetchEmployees()
|
||||
@@ -643,49 +398,47 @@ const handleTableChange = (paginationInfo) => {
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
fetchEmployees()
|
||||
fetchEmployeeStats()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.employee-management {
|
||||
.employee-management-page {
|
||||
padding: 24px;
|
||||
background-color: #f0f2f5;
|
||||
min-height: calc(100vh - 134px);
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
background-color: #fff;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
font-size: 28px;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.content {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
.search-filter-section {
|
||||
background-color: #fff;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.overview-section {
|
||||
border-radius: 8px;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.search-section {
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.table-section {
|
||||
margin-top: 16px;
|
||||
.employees-table-section {
|
||||
background-color: #fff;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.employee-detail {
|
||||
@@ -756,4 +509,49 @@ onMounted(() => {
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.ant-table-thead > tr > th) {
|
||||
background-color: #fafafa;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
:deep(.ant-table-tbody > tr > td) {
|
||||
padding: 12px 8px;
|
||||
}
|
||||
|
||||
:deep(.ant-table-tbody > tr:hover > td) {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 操作按钮样式 */
|
||||
:deep(.ant-btn-link) {
|
||||
padding: 4px 8px;
|
||||
height: auto;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* 分页样式 */
|
||||
:deep(.ant-pagination) {
|
||||
margin-top: 16px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 768px) {
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.search-filter-section .ant-col {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.search-filter-section .ant-col:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
397
bank-frontend/src/views/MessageNotification.vue
Normal file
397
bank-frontend/src/views/MessageNotification.vue
Normal file
@@ -0,0 +1,397 @@
|
||||
<template>
|
||||
<div class="message-notification">
|
||||
<div class="page-header">
|
||||
<h1>消息通知</h1>
|
||||
<p>管理和查看系统消息通知</p>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<!-- 消息统计 -->
|
||||
<div class="overview-section">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic
|
||||
title="总消息数"
|
||||
:value="messageStats.total"
|
||||
:value-style="{ color: '#1890ff' }"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic
|
||||
title="未读消息"
|
||||
:value="messageStats.unread"
|
||||
:value-style="{ color: '#ff4d4f' }"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic
|
||||
title="系统通知"
|
||||
:value="messageStats.system"
|
||||
:value-style="{ color: '#52c41a' }"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic
|
||||
title="业务通知"
|
||||
:value="messageStats.business"
|
||||
:value-style="{ color: '#faad14' }"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 搜索和筛选 -->
|
||||
<div class="search-section">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<a-input-search
|
||||
v-model:value="searchText"
|
||||
placeholder="搜索消息内容"
|
||||
enter-button="搜索"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-select
|
||||
v-model:value="typeFilter"
|
||||
placeholder="消息类型"
|
||||
allow-clear
|
||||
@change="handleFilter"
|
||||
>
|
||||
<a-select-option value="system">系统通知</a-select-option>
|
||||
<a-select-option value="business">业务通知</a-select-option>
|
||||
<a-select-option value="warning">预警通知</a-select-option>
|
||||
<a-select-option value="info">信息通知</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-select
|
||||
v-model:value="statusFilter"
|
||||
placeholder="阅读状态"
|
||||
allow-clear
|
||||
@change="handleFilter"
|
||||
>
|
||||
<a-select-option value="unread">未读</a-select-option>
|
||||
<a-select-option value="read">已读</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-button type="primary" @click="markAllRead">
|
||||
<check-outlined /> 全部标记为已读
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 消息列表 -->
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="filteredMessages"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
@change="handleTableChange"
|
||||
row-key="id"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'type'">
|
||||
<a-tag :color="getTypeColor(record.type)">
|
||||
{{ getTypeName(record.type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="record.status === 'read' ? 'green' : 'red'">
|
||||
{{ record.status === 'read' ? '已读' : '未读' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="viewMessage(record)">查看</a-button>
|
||||
<a-button type="link" size="small" @click="markAsRead(record.id)">标记已读</a-button>
|
||||
<a-button type="link" size="small" danger @click="deleteMessage(record.id)">删除</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
|
||||
<!-- 消息详情对话框 -->
|
||||
<a-modal
|
||||
v-model:open="detailModalVisible"
|
||||
title="消息详情"
|
||||
width="600px"
|
||||
:footer="null"
|
||||
>
|
||||
<div v-if="selectedMessage" class="message-detail">
|
||||
<a-descriptions :column="1" bordered>
|
||||
<a-descriptions-item label="消息标题">
|
||||
{{ selectedMessage.title }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="消息类型">
|
||||
<a-tag :color="getTypeColor(selectedMessage.type)">
|
||||
{{ getTypeName(selectedMessage.type) }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="发送时间">
|
||||
{{ selectedMessage.sendTime }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="发送人">
|
||||
{{ selectedMessage.sender }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="消息内容">
|
||||
<div class="message-content">{{ selectedMessage.content }}</div>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { CheckOutlined } from '@ant-design/icons-vue'
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const searchText = ref('')
|
||||
const typeFilter = ref(undefined)
|
||||
const statusFilter = ref(undefined)
|
||||
const detailModalVisible = ref(false)
|
||||
const selectedMessage = ref(null)
|
||||
|
||||
// 消息统计
|
||||
const messageStats = ref({
|
||||
total: 45,
|
||||
unread: 12,
|
||||
system: 20,
|
||||
business: 25
|
||||
})
|
||||
|
||||
// 分页配置
|
||||
const pagination = ref({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条,共 ${total} 条`
|
||||
})
|
||||
|
||||
// 表格列配置
|
||||
const columns = [
|
||||
{
|
||||
title: '消息标题',
|
||||
dataIndex: 'title',
|
||||
key: 'title',
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: '消息类型',
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
filters: [
|
||||
{ text: '系统通知', value: 'system' },
|
||||
{ text: '业务通知', value: 'business' },
|
||||
{ text: '预警通知', value: 'warning' },
|
||||
{ text: '信息通知', value: 'info' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '发送人',
|
||||
dataIndex: 'sender',
|
||||
key: 'sender',
|
||||
},
|
||||
{
|
||||
title: '发送时间',
|
||||
dataIndex: 'sendTime',
|
||||
key: 'sendTime',
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
filters: [
|
||||
{ text: '未读', value: 'unread' },
|
||||
{ text: '已读', value: 'read' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 200,
|
||||
},
|
||||
]
|
||||
|
||||
// 模拟消息数据
|
||||
const messages = ref([
|
||||
{
|
||||
id: 1,
|
||||
title: '系统维护通知',
|
||||
type: 'system',
|
||||
sender: '系统管理员',
|
||||
sendTime: '2024-01-22 14:30:25',
|
||||
status: 'unread',
|
||||
content: '系统将于今晚22:00-24:00进行维护,期间可能影响部分功能使用,请提前做好准备。'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '新用户注册提醒',
|
||||
type: 'business',
|
||||
sender: '业务系统',
|
||||
sendTime: '2024-01-22 13:15:10',
|
||||
status: 'read',
|
||||
content: '有新的用户注册,请及时审核用户信息。'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '设备异常预警',
|
||||
type: 'warning',
|
||||
sender: '监控系统',
|
||||
sendTime: '2024-01-22 10:20:30',
|
||||
status: 'unread',
|
||||
content: 'ATM-001设备出现异常,请及时检查处理。'
|
||||
}
|
||||
])
|
||||
|
||||
// 计算属性
|
||||
const filteredMessages = computed(() => {
|
||||
let result = messages.value
|
||||
|
||||
if (searchText.value) {
|
||||
result = result.filter(msg =>
|
||||
msg.title.toLowerCase().includes(searchText.value.toLowerCase()) ||
|
||||
msg.content.toLowerCase().includes(searchText.value.toLowerCase())
|
||||
)
|
||||
}
|
||||
|
||||
if (typeFilter.value) {
|
||||
result = result.filter(msg => msg.type === typeFilter.value)
|
||||
}
|
||||
|
||||
if (statusFilter.value) {
|
||||
result = result.filter(msg => msg.status === statusFilter.value)
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
// 方法
|
||||
const getTypeColor = (type) => {
|
||||
const colors = {
|
||||
'system': 'blue',
|
||||
'business': 'green',
|
||||
'warning': 'orange',
|
||||
'info': 'purple'
|
||||
}
|
||||
return colors[type] || 'default'
|
||||
}
|
||||
|
||||
const getTypeName = (type) => {
|
||||
const names = {
|
||||
'system': '系统通知',
|
||||
'business': '业务通知',
|
||||
'warning': '预警通知',
|
||||
'info': '信息通知'
|
||||
}
|
||||
return names[type] || type
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.value.current = 1
|
||||
}
|
||||
|
||||
const handleFilter = () => {
|
||||
pagination.value.current = 1
|
||||
}
|
||||
|
||||
const handleTableChange = (pag) => {
|
||||
pagination.value = pag
|
||||
}
|
||||
|
||||
const markAllRead = () => {
|
||||
messages.value.forEach(msg => {
|
||||
msg.status = 'read'
|
||||
})
|
||||
message.success('所有消息已标记为已读')
|
||||
}
|
||||
|
||||
const viewMessage = (msg) => {
|
||||
selectedMessage.value = msg
|
||||
detailModalVisible.value = true
|
||||
// 查看消息时自动标记为已读
|
||||
if (msg.status === 'unread') {
|
||||
msg.status = 'read'
|
||||
}
|
||||
}
|
||||
|
||||
const markAsRead = (id) => {
|
||||
const msg = messages.value.find(m => m.id === id)
|
||||
if (msg) {
|
||||
msg.status = 'read'
|
||||
message.success('消息已标记为已读')
|
||||
}
|
||||
}
|
||||
|
||||
const deleteMessage = (id) => {
|
||||
const index = messages.value.findIndex(m => m.id === id)
|
||||
if (index > -1) {
|
||||
messages.value.splice(index, 1)
|
||||
message.success('消息已删除')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
pagination.value.total = messages.value.length
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.message-notification {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
margin: 0;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.overview-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
margin-bottom: 24px;
|
||||
padding: 16px;
|
||||
background: #fafafa;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.message-detail {
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
.message-content {
|
||||
white-space: pre-wrap;
|
||||
line-height: 1.6;
|
||||
}
|
||||
</style>
|
||||
@@ -1 +1,16 @@
|
||||
<template>\n <div class=page-container>404 Not Found</div>\n</template>\n\n<script setup>\n</script>\n\n<style scoped>\n.page-container { padding: 24px; text-align:center; color:#8c8c8c; }\n</style>
|
||||
<template>
|
||||
<div class="page-container">
|
||||
404 Not Found
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-container {
|
||||
padding: 24px;
|
||||
text-align: center;
|
||||
color: #8c8c8c;
|
||||
}
|
||||
</style>
|
||||
351
bank-frontend/src/views/PendingInstallation.vue
Normal file
351
bank-frontend/src/views/PendingInstallation.vue
Normal file
@@ -0,0 +1,351 @@
|
||||
<template>
|
||||
<div class="pending-installation-page">
|
||||
<!-- 页面头部 -->
|
||||
<div class="page-header">
|
||||
<h1>待安装任务</h1>
|
||||
<div class="header-actions">
|
||||
<a-button type="primary" @click="handleExport">
|
||||
<download-outlined />
|
||||
安装任务导出
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索筛选区域 -->
|
||||
<div class="search-section">
|
||||
<a-row :gutter="16" align="middle">
|
||||
<a-col :span="6">
|
||||
<a-input
|
||||
v-model:value="searchForm.contractNumber"
|
||||
placeholder="请输入合同编号"
|
||||
allow-clear
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-range-picker
|
||||
v-model:value="searchForm.dateRange"
|
||||
:placeholder="['开始日期', '结束日期']"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-select
|
||||
v-model:value="searchForm.installationStatus"
|
||||
placeholder="请选择安装状态"
|
||||
allow-clear
|
||||
style="width: 100%"
|
||||
>
|
||||
<a-select-option value="pending">待安装</a-select-option>
|
||||
<a-select-option value="in-progress">安装中</a-select-option>
|
||||
<a-select-option value="completed">已完成</a-select-option>
|
||||
<a-select-option value="failed">安装失败</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
<search-outlined />
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button @click="handleReset">重置</a-button>
|
||||
</a-space>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<div class="table-section">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="filteredTasks"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
@change="handleTableChange"
|
||||
row-key="id"
|
||||
:locale="{ emptyText: '暂无数据' }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'installationStatus'">
|
||||
<a-tag :color="getStatusColor(record.installationStatus)">
|
||||
{{ getStatusName(record.installationStatus) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="viewTask(record)">查看</a-button>
|
||||
<a-button type="link" size="small" @click="editTask(record)">编辑</a-button>
|
||||
<a-button type="link" size="small" @click="startInstallation(record)">开始安装</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { DownloadOutlined, SearchOutlined } from '@ant-design/icons-vue'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
const loading = ref(false)
|
||||
const tasks = ref([])
|
||||
|
||||
const searchForm = reactive({
|
||||
contractNumber: '',
|
||||
dateRange: [],
|
||||
installationStatus: undefined,
|
||||
})
|
||||
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total) => `共 ${total} 条`,
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{ title: '申请单号', dataIndex: 'applicationNumber', key: 'applicationNumber', sorter: true },
|
||||
{ title: '放款合同编号', dataIndex: 'contractNumber', key: 'contractNumber', sorter: true },
|
||||
{ title: '产品名称', dataIndex: 'productName', key: 'productName' },
|
||||
{ title: '客户姓名', dataIndex: 'customerName', key: 'customerName' },
|
||||
{ title: '证件类型', dataIndex: 'idType', key: 'idType' },
|
||||
{ title: '证件号码', dataIndex: 'idNumber', key: 'idNumber' },
|
||||
{ title: '养殖生资种类', dataIndex: 'assetType', key: 'assetType' },
|
||||
{ title: '待安装设备', dataIndex: 'equipmentToInstall', key: 'equipmentToInstall' },
|
||||
{ title: '安装状态', dataIndex: 'installationStatus', key: 'installationStatus', filters: [
|
||||
{ text: '待安装', value: 'pending' },
|
||||
{ text: '安装中', value: 'in-progress' },
|
||||
{ text: '已完成', value: 'completed' },
|
||||
{ text: '安装失败', value: 'failed' },
|
||||
]},
|
||||
{ title: '生成安装任务时间', dataIndex: 'taskGenerationTime', key: 'taskGenerationTime', sorter: true },
|
||||
{ title: '安装完成生效时间', dataIndex: 'completionTime', key: 'completionTime', sorter: true },
|
||||
{ title: '操作', key: 'action', fixed: 'right', width: 180 },
|
||||
]
|
||||
|
||||
// 模拟数据
|
||||
const mockTasks = [
|
||||
{
|
||||
id: 'I001',
|
||||
applicationNumber: 'APP2024001',
|
||||
contractNumber: 'LOAN2024001',
|
||||
productName: '生猪养殖贷',
|
||||
customerName: '张三',
|
||||
idType: 'ID_CARD',
|
||||
idNumber: '4401XXXXXXXXXXXXXX',
|
||||
assetType: '生猪',
|
||||
equipmentToInstall: '耳标设备',
|
||||
installationStatus: 'pending',
|
||||
taskGenerationTime: '2024-01-15 10:30:00',
|
||||
completionTime: null,
|
||||
},
|
||||
{
|
||||
id: 'I002',
|
||||
applicationNumber: 'APP2024002',
|
||||
contractNumber: 'LOAN2024002',
|
||||
productName: '肉牛养殖贷',
|
||||
customerName: '李四',
|
||||
idType: 'ID_CARD',
|
||||
idNumber: '4402XXXXXXXXXXXXXX',
|
||||
assetType: '肉牛',
|
||||
equipmentToInstall: '项圈设备',
|
||||
installationStatus: 'in-progress',
|
||||
taskGenerationTime: '2024-01-16 14:20:00',
|
||||
completionTime: null,
|
||||
},
|
||||
{
|
||||
id: 'I003',
|
||||
applicationNumber: 'APP2024003',
|
||||
contractNumber: 'LOAN2024003',
|
||||
productName: '蛋鸡养殖贷',
|
||||
customerName: '王五',
|
||||
idType: 'ID_CARD',
|
||||
idNumber: '4403XXXXXXXXXXXXXX',
|
||||
assetType: '蛋鸡',
|
||||
equipmentToInstall: '监控设备',
|
||||
installationStatus: 'completed',
|
||||
taskGenerationTime: '2024-01-10 09:15:00',
|
||||
completionTime: '2024-01-20 16:30:00',
|
||||
},
|
||||
]
|
||||
|
||||
const fetchTasks = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 实际项目中这里会调用API获取数据
|
||||
// const response = await api.installationTasks.getList({
|
||||
// page: pagination.current,
|
||||
// pageSize: pagination.pageSize,
|
||||
// ...searchForm,
|
||||
// })
|
||||
|
||||
// 使用模拟数据
|
||||
tasks.value = mockTasks.map(task => ({
|
||||
...task,
|
||||
taskGenerationTime: dayjs(task.taskGenerationTime),
|
||||
completionTime: task.completionTime ? dayjs(task.completionTime) : null,
|
||||
}))
|
||||
pagination.total = mockTasks.length
|
||||
} catch (error) {
|
||||
console.error('获取安装任务失败:', error)
|
||||
message.error('获取安装任务失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const filteredTasks = computed(() => {
|
||||
let result = tasks.value
|
||||
|
||||
if (searchForm.contractNumber) {
|
||||
result = result.filter(task =>
|
||||
task.contractNumber.toLowerCase().includes(searchForm.contractNumber.toLowerCase())
|
||||
)
|
||||
}
|
||||
|
||||
if (searchForm.installationStatus) {
|
||||
result = result.filter(task => task.installationStatus === searchForm.installationStatus)
|
||||
}
|
||||
|
||||
if (searchForm.dateRange && searchForm.dateRange.length === 2) {
|
||||
const [startDate, endDate] = searchForm.dateRange
|
||||
result = result.filter(task => {
|
||||
const taskTime = dayjs(task.taskGenerationTime)
|
||||
return taskTime.isAfter(startDate.startOf('day')) && taskTime.isBefore(endDate.endOf('day'))
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.current = 1
|
||||
fetchTasks()
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
searchForm.contractNumber = ''
|
||||
searchForm.dateRange = []
|
||||
searchForm.installationStatus = undefined
|
||||
pagination.current = 1
|
||||
fetchTasks()
|
||||
}
|
||||
|
||||
const handleExport = () => {
|
||||
message.info('安装任务导出功能开发中...')
|
||||
}
|
||||
|
||||
const handleTableChange = (pag, filters, sorter) => {
|
||||
pagination.current = pag.current
|
||||
pagination.pageSize = pag.pageSize
|
||||
fetchTasks()
|
||||
}
|
||||
|
||||
const getStatusColor = (status) => {
|
||||
const colors = {
|
||||
'pending': 'blue',
|
||||
'in-progress': 'orange',
|
||||
'completed': 'green',
|
||||
'failed': 'red',
|
||||
}
|
||||
return colors[status] || 'default'
|
||||
}
|
||||
|
||||
const getStatusName = (status) => {
|
||||
const names = {
|
||||
'pending': '待安装',
|
||||
'in-progress': '安装中',
|
||||
'completed': '已完成',
|
||||
'failed': '安装失败',
|
||||
}
|
||||
return names[status] || status
|
||||
}
|
||||
|
||||
const viewTask = (record) => {
|
||||
message.info(`查看任务: ${record.applicationNumber}`)
|
||||
}
|
||||
|
||||
const editTask = (record) => {
|
||||
message.info(`编辑任务: ${record.applicationNumber}`)
|
||||
}
|
||||
|
||||
const startInstallation = (record) => {
|
||||
message.success(`开始安装任务: ${record.applicationNumber}`)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchTasks()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pending-installation-page {
|
||||
padding: 0;
|
||||
background: #f0f2f5;
|
||||
min-height: calc(100vh - 134px);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
padding: 24px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
background: white;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.table-section {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.search-section .ant-col {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.search-section .ant-col:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
691
bank-frontend/src/views/SupervisionTasks.vue
Normal file
691
bank-frontend/src/views/SupervisionTasks.vue
Normal file
@@ -0,0 +1,691 @@
|
||||
<template>
|
||||
<div class="supervision-tasks">
|
||||
<!-- 页面头部 -->
|
||||
<div class="page-header">
|
||||
<h1>监管任务导入</h1>
|
||||
<div class="header-actions">
|
||||
<a-button type="primary" @click="showAddTaskModal">
|
||||
<plus-outlined /> 新增监管任务
|
||||
</a-button>
|
||||
<a-button type="primary" @click="showBatchAddModal">
|
||||
<plus-outlined /> 批量新增
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索筛选区域 -->
|
||||
<div class="search-section">
|
||||
<div class="search-form">
|
||||
<a-input
|
||||
v-model:value="searchForm.contractNumber"
|
||||
placeholder="请输入合同编号"
|
||||
class="search-input"
|
||||
/>
|
||||
|
||||
<a-range-picker
|
||||
v-model:value="searchForm.dateRange"
|
||||
:placeholder="['开始日期', '结束日期']"
|
||||
class="date-picker"
|
||||
/>
|
||||
|
||||
<a-select
|
||||
v-model:value="searchForm.supervisionStatus"
|
||||
placeholder="请选择监管状态"
|
||||
class="status-select"
|
||||
>
|
||||
<a-select-option value="pending">待监管</a-select-option>
|
||||
<a-select-option value="supervising">监管中</a-select-option>
|
||||
<a-select-option value="completed">已完成</a-select-option>
|
||||
<a-select-option value="suspended">已暂停</a-select-option>
|
||||
</a-select>
|
||||
|
||||
<div class="action-buttons">
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
<search-outlined /> 搜索
|
||||
</a-button>
|
||||
<a-button @click="handleExport">
|
||||
<download-outlined /> 任务导出
|
||||
</a-button>
|
||||
<a-button @click="handleReset">
|
||||
重置
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<div class="table-section">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="filteredTasks"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
@change="handleTableChange"
|
||||
row-key="id"
|
||||
:locale="{ emptyText: '暂无数据' }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'supervisionStatus'">
|
||||
<a-tag :color="getStatusColor(record.supervisionStatus)">
|
||||
{{ getStatusName(record.supervisionStatus) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="viewTask(record)">查看</a-button>
|
||||
<a-button type="link" size="small" @click="editTask(record)">编辑</a-button>
|
||||
<a-button type="link" size="small" @click="deleteTask(record.id)">删除</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
|
||||
<!-- 新增监管任务对话框 -->
|
||||
<a-modal
|
||||
v-model:open="addTaskModalVisible"
|
||||
title="新增监管任务"
|
||||
width="800px"
|
||||
@ok="handleAddTask"
|
||||
@cancel="handleCancelAdd"
|
||||
>
|
||||
<a-form
|
||||
ref="addTaskFormRef"
|
||||
:model="addTaskForm"
|
||||
:rules="addTaskRules"
|
||||
layout="vertical"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="申请单号" name="applicationNumber">
|
||||
<a-input v-model:value="addTaskForm.applicationNumber" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="放款合同编号" name="contractNumber">
|
||||
<a-input v-model:value="addTaskForm.contractNumber" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="产品名称" name="productName">
|
||||
<a-input v-model:value="addTaskForm.productName" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="客户姓名" name="customerName">
|
||||
<a-input v-model:value="addTaskForm.customerName" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="证件类型" name="idType">
|
||||
<a-select v-model:value="addTaskForm.idType">
|
||||
<a-select-option value="id_card">身份证</a-select-option>
|
||||
<a-select-option value="passport">护照</a-select-option>
|
||||
<a-select-option value="other">其他</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="证件号码" name="idNumber">
|
||||
<a-input v-model:value="addTaskForm.idNumber" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="养殖生资种类" name="assetType">
|
||||
<a-select v-model:value="addTaskForm.assetType">
|
||||
<a-select-option value="cattle">牛</a-select-option>
|
||||
<a-select-option value="sheep">羊</a-select-option>
|
||||
<a-select-option value="pig">猪</a-select-option>
|
||||
<a-select-option value="poultry">家禽</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="监管生资数量" name="assetQuantity">
|
||||
<a-input-number v-model:value="addTaskForm.assetQuantity" style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="监管起始时间" name="startTime">
|
||||
<a-date-picker v-model:value="addTaskForm.startTime" style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="监管结束时间" name="endTime">
|
||||
<a-date-picker v-model:value="addTaskForm.endTime" style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
<!-- 任务详情对话框 -->
|
||||
<a-modal
|
||||
v-model:open="detailModalVisible"
|
||||
title="任务详情"
|
||||
width="800px"
|
||||
:footer="null"
|
||||
>
|
||||
<div v-if="selectedTask" class="task-detail">
|
||||
<a-descriptions :column="2" bordered>
|
||||
<a-descriptions-item label="申请单号">
|
||||
{{ selectedTask.applicationNumber }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="放款合同编号">
|
||||
{{ selectedTask.contractNumber }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="产品名称">
|
||||
{{ selectedTask.productName }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="客户姓名">
|
||||
{{ selectedTask.customerName }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="证件类型">
|
||||
{{ getIDTypeName(selectedTask.idType) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="证件号码">
|
||||
{{ selectedTask.idNumber }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="养殖生资种类">
|
||||
{{ getAssetTypeName(selectedTask.assetType) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="监管生资数量">
|
||||
{{ selectedTask.assetQuantity }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="监管状态">
|
||||
<a-tag :color="getStatusColor(selectedTask.supervisionStatus)">
|
||||
{{ getStatusName(selectedTask.supervisionStatus) }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="任务导入时间">
|
||||
{{ selectedTask.importTime }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="监管起始时间">
|
||||
{{ selectedTask.startTime }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="监管结束时间">
|
||||
{{ selectedTask.endTime }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { api } from '@/utils/api'
|
||||
import {
|
||||
PlusOutlined,
|
||||
SearchOutlined,
|
||||
DownloadOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const addTaskModalVisible = ref(false)
|
||||
const detailModalVisible = ref(false)
|
||||
const selectedTask = ref(null)
|
||||
const addTaskFormRef = ref()
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = ref({
|
||||
contractNumber: '',
|
||||
dateRange: [],
|
||||
supervisionStatus: undefined
|
||||
})
|
||||
|
||||
// 新增任务表单
|
||||
const addTaskForm = ref({
|
||||
applicationNumber: '',
|
||||
contractNumber: '',
|
||||
productName: '',
|
||||
customerName: '',
|
||||
idType: '',
|
||||
idNumber: '',
|
||||
assetType: '',
|
||||
assetQuantity: 0,
|
||||
startTime: null,
|
||||
endTime: null
|
||||
})
|
||||
|
||||
// 表单验证规则
|
||||
const addTaskRules = {
|
||||
applicationNumber: [
|
||||
{ required: true, message: '请输入申请单号', trigger: 'blur' }
|
||||
],
|
||||
contractNumber: [
|
||||
{ required: true, message: '请输入放款合同编号', trigger: 'blur' }
|
||||
],
|
||||
productName: [
|
||||
{ required: true, message: '请输入产品名称', trigger: 'blur' }
|
||||
],
|
||||
customerName: [
|
||||
{ required: true, message: '请输入客户姓名', trigger: 'blur' }
|
||||
],
|
||||
idType: [
|
||||
{ required: true, message: '请选择证件类型', trigger: 'change' }
|
||||
],
|
||||
idNumber: [
|
||||
{ required: true, message: '请输入证件号码', trigger: 'blur' }
|
||||
],
|
||||
assetType: [
|
||||
{ required: true, message: '请选择养殖生资种类', trigger: 'change' }
|
||||
],
|
||||
assetQuantity: [
|
||||
{ required: true, message: '请输入监管生资数量', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
// 分页配置
|
||||
const pagination = ref({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total, range) => `共${total}条`
|
||||
})
|
||||
|
||||
// 表格列配置
|
||||
const columns = [
|
||||
{
|
||||
title: '申请单号',
|
||||
dataIndex: 'applicationNumber',
|
||||
key: 'applicationNumber',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '放款合同编号',
|
||||
dataIndex: 'contractNumber',
|
||||
key: 'contractNumber',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '产品名称',
|
||||
dataIndex: 'productName',
|
||||
key: 'productName',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '客户姓名',
|
||||
dataIndex: 'customerName',
|
||||
key: 'customerName',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '证件类型',
|
||||
dataIndex: 'idType',
|
||||
key: 'idType',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '证件号码',
|
||||
dataIndex: 'idNumber',
|
||||
key: 'idNumber',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '养殖生资种类',
|
||||
dataIndex: 'assetType',
|
||||
key: 'assetType',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '监管生资数量',
|
||||
dataIndex: 'assetQuantity',
|
||||
key: 'assetQuantity',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '监管状态',
|
||||
dataIndex: 'supervisionStatus',
|
||||
key: 'supervisionStatus',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '任务导入时间',
|
||||
dataIndex: 'importTime',
|
||||
key: 'importTime',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '监管起始时间',
|
||||
dataIndex: 'startTime',
|
||||
key: 'startTime',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '监管结束时间',
|
||||
dataIndex: 'endTime',
|
||||
key: 'endTime',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 150,
|
||||
fixed: 'right'
|
||||
},
|
||||
]
|
||||
|
||||
// 监管任务数据
|
||||
const tasks = ref([])
|
||||
|
||||
// 计算属性
|
||||
const filteredTasks = computed(() => {
|
||||
return tasks.value
|
||||
})
|
||||
|
||||
// 方法
|
||||
const getStatusColor = (status) => {
|
||||
const colors = {
|
||||
'pending': 'orange',
|
||||
'supervising': 'blue',
|
||||
'completed': 'green',
|
||||
'suspended': 'red'
|
||||
}
|
||||
return colors[status] || 'default'
|
||||
}
|
||||
|
||||
const getStatusName = (status) => {
|
||||
const names = {
|
||||
'pending': '待监管',
|
||||
'supervising': '监管中',
|
||||
'completed': '已完成',
|
||||
'suspended': '已暂停'
|
||||
}
|
||||
return names[status] || status
|
||||
}
|
||||
|
||||
const getIDTypeName = (type) => {
|
||||
const names = {
|
||||
'id_card': '身份证',
|
||||
'passport': '护照',
|
||||
'other': '其他'
|
||||
}
|
||||
return names[type] || type
|
||||
}
|
||||
|
||||
const getAssetTypeName = (type) => {
|
||||
const names = {
|
||||
'cattle': '牛',
|
||||
'sheep': '羊',
|
||||
'pig': '猪',
|
||||
'poultry': '家禽'
|
||||
}
|
||||
return names[type] || type
|
||||
}
|
||||
|
||||
const showAddTaskModal = () => {
|
||||
addTaskModalVisible.value = true
|
||||
}
|
||||
|
||||
const showBatchAddModal = () => {
|
||||
message.info('批量新增功能开发中...')
|
||||
}
|
||||
|
||||
|
||||
const resetAddTaskForm = () => {
|
||||
addTaskForm.value = {
|
||||
applicationNumber: '',
|
||||
contractNumber: '',
|
||||
productName: '',
|
||||
customerName: '',
|
||||
idType: '',
|
||||
idNumber: '',
|
||||
assetType: '',
|
||||
assetQuantity: 0,
|
||||
startTime: null,
|
||||
endTime: null
|
||||
}
|
||||
}
|
||||
|
||||
const viewTask = (task) => {
|
||||
selectedTask.value = task
|
||||
detailModalVisible.value = true
|
||||
}
|
||||
|
||||
// API调用函数
|
||||
const fetchTasks = async (params = {}) => {
|
||||
try {
|
||||
loading.value = true
|
||||
console.log('开始获取监管任务列表...', {
|
||||
page: pagination.value.current,
|
||||
limit: pagination.value.pageSize,
|
||||
search: searchForm.value.contractNumber,
|
||||
supervisionStatus: searchForm.value.supervisionStatus,
|
||||
dateRange: searchForm.value.dateRange ?
|
||||
`${searchForm.value.dateRange[0].format('YYYY-MM-DD')},${searchForm.value.dateRange[1].format('YYYY-MM-DD')}` : ''
|
||||
})
|
||||
|
||||
const response = await api.supervisionTasks.getList({
|
||||
page: pagination.value.current,
|
||||
limit: pagination.value.pageSize,
|
||||
search: searchForm.value.contractNumber,
|
||||
supervisionStatus: searchForm.value.supervisionStatus,
|
||||
dateRange: searchForm.value.dateRange ?
|
||||
`${searchForm.value.dateRange[0].format('YYYY-MM-DD')},${searchForm.value.dateRange[1].format('YYYY-MM-DD')}` : '',
|
||||
...params
|
||||
})
|
||||
|
||||
console.log('监管任务列表响应:', response)
|
||||
|
||||
if (response.success) {
|
||||
tasks.value = response.data.tasks || []
|
||||
pagination.value.total = response.data.pagination?.total || 0
|
||||
console.log('监管任务数据已更新:', tasks.value.length, '个任务')
|
||||
} else {
|
||||
console.error('API返回错误:', response.message)
|
||||
message.error(response.message || '获取监管任务列表失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取监管任务列表失败:', error)
|
||||
message.error('获取监管任务列表失败: ' + error.message)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleTableChange = (paginationInfo) => {
|
||||
pagination.value = paginationInfo
|
||||
fetchTasks()
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.value.current = 1
|
||||
fetchTasks()
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
searchForm.value = {
|
||||
contractNumber: '',
|
||||
dateRange: null,
|
||||
supervisionStatus: ''
|
||||
}
|
||||
pagination.value.current = 1
|
||||
fetchTasks()
|
||||
}
|
||||
|
||||
const editTask = async (task) => {
|
||||
try {
|
||||
// 这里可以实现编辑功能
|
||||
message.info(`编辑任务: ${task.applicationNumber}`)
|
||||
} catch (error) {
|
||||
console.error('编辑任务失败:', error)
|
||||
message.error('编辑任务失败')
|
||||
}
|
||||
}
|
||||
|
||||
const deleteTask = async (id) => {
|
||||
try {
|
||||
const response = await api.supervisionTasks.delete(id)
|
||||
if (response.success) {
|
||||
message.success('任务删除成功')
|
||||
await fetchTasks()
|
||||
} else {
|
||||
message.error(response.message || '删除任务失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除任务失败:', error)
|
||||
message.error('删除任务失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleAddTask = async () => {
|
||||
try {
|
||||
await addTaskFormRef.value.validate()
|
||||
|
||||
const response = await api.supervisionTasks.create(addTaskForm.value)
|
||||
|
||||
if (response.success) {
|
||||
message.success('监管任务创建成功')
|
||||
addTaskModalVisible.value = false
|
||||
addTaskFormRef.value.resetFields()
|
||||
await fetchTasks()
|
||||
} else {
|
||||
message.error(response.message || '创建监管任务失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('创建监管任务失败:', error)
|
||||
message.error('创建监管任务失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleCancelAdd = () => {
|
||||
addTaskModalVisible.value = false
|
||||
addTaskFormRef.value.resetFields()
|
||||
}
|
||||
|
||||
const handleExport = () => {
|
||||
message.info('任务导出功能开发中...')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchTasks()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.supervision-tasks {
|
||||
padding: 0;
|
||||
background: #f0f2f5;
|
||||
min-height: calc(100vh - 134px);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
padding: 24px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
background: white;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.date-picker {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.status-select {
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.table-section {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.task-detail {
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 1200px) {
|
||||
.search-form {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
margin-left: 0;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.search-input,
|
||||
.date-picker,
|
||||
.status-select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -2,12 +2,11 @@
|
||||
<div class="system-check">
|
||||
<div class="page-header">
|
||||
<h1>系统日检</h1>
|
||||
<p>每日系统健康检查和监控</p>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<!-- 检查概览 -->
|
||||
<div class="overview-section">
|
||||
<!-- <div class="overview-section">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
@@ -46,7 +45,7 @@
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="action-section">
|
||||
|
||||
147
bank-frontend/src/views/TestProjects.vue
Normal file
147
bank-frontend/src/views/TestProjects.vue
Normal file
@@ -0,0 +1,147 @@
|
||||
<template>
|
||||
<div class="test-projects">
|
||||
<h1>项目接口测试页面</h1>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>1. 登录测试</h2>
|
||||
<a-button @click="testLogin" :loading="loginLoading">
|
||||
测试登录
|
||||
</a-button>
|
||||
<div v-if="loginResult" class="result">
|
||||
<pre>{{ loginResult }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>2. 项目接口测试</h2>
|
||||
<a-button @click="testProjects" :loading="projectsLoading" :disabled="!token">
|
||||
测试项目接口
|
||||
</a-button>
|
||||
<div v-if="projectsResult" class="result">
|
||||
<pre>{{ projectsResult }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>3. 项目数据展示</h2>
|
||||
<div v-if="projects.length > 0">
|
||||
<p>共 {{ projects.length }} 个项目</p>
|
||||
<div v-for="project in projects" :key="project.id" class="project-item">
|
||||
<h3>{{ project.name }}</h3>
|
||||
<p>状态: {{ project.status }}</p>
|
||||
<p>养殖场: {{ project.farmName }}</p>
|
||||
<p>监管对象: {{ project.supervisionObject }}</p>
|
||||
<p>监管数量: {{ project.supervisionQuantity }}</p>
|
||||
<p>监管金额: {{ project.supervisionAmount }} 元</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { api } from '@/utils/api'
|
||||
|
||||
const loginLoading = ref(false)
|
||||
const projectsLoading = ref(false)
|
||||
const loginResult = ref('')
|
||||
const projectsResult = ref('')
|
||||
const token = ref('')
|
||||
const projects = ref([])
|
||||
|
||||
const testLogin = async () => {
|
||||
try {
|
||||
loginLoading.value = true
|
||||
loginResult.value = '正在登录...'
|
||||
|
||||
const response = await api.auth.login('admin', 'Admin123456')
|
||||
|
||||
if (response.success) {
|
||||
token.value = response.data.token
|
||||
loginResult.value = `登录成功!\nToken: ${token.value.substring(0, 50)}...\n用户: ${response.data.user.username}`
|
||||
message.success('登录成功')
|
||||
} else {
|
||||
loginResult.value = `登录失败: ${response.message}`
|
||||
message.error('登录失败')
|
||||
}
|
||||
} catch (error) {
|
||||
loginResult.value = `登录错误: ${error.message}`
|
||||
message.error('登录错误')
|
||||
} finally {
|
||||
loginLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const testProjects = async () => {
|
||||
try {
|
||||
projectsLoading.value = true
|
||||
projectsResult.value = '正在获取项目列表...'
|
||||
|
||||
const response = await api.projects.getList({
|
||||
page: 1,
|
||||
limit: 12,
|
||||
search: '',
|
||||
status: ''
|
||||
})
|
||||
|
||||
if (response.success) {
|
||||
projects.value = response.data.projects || []
|
||||
projectsResult.value = `获取成功!\n项目数量: ${projects.value.length}\n总数量: ${response.data.pagination.total}`
|
||||
message.success('获取项目列表成功')
|
||||
} else {
|
||||
projectsResult.value = `获取失败: ${response.message}`
|
||||
message.error('获取项目列表失败')
|
||||
}
|
||||
} catch (error) {
|
||||
projectsResult.value = `获取错误: ${error.message}`
|
||||
message.error('获取项目列表错误')
|
||||
} finally {
|
||||
projectsLoading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.test-projects {
|
||||
padding: 24px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.test-section {
|
||||
margin-bottom: 32px;
|
||||
padding: 16px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.result {
|
||||
margin-top: 16px;
|
||||
padding: 12px;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
white-space: pre-wrap;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.project-item {
|
||||
margin-bottom: 16px;
|
||||
padding: 12px;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 4px;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.project-item h3 {
|
||||
margin: 0 0 8px 0;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.project-item p {
|
||||
margin: 4px 0;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
@@ -1,120 +1,80 @@
|
||||
<template>
|
||||
<div class="loan-applications">
|
||||
<div class="loan-applications-page">
|
||||
<div class="page-header">
|
||||
<h1>贷款申请进度</h1>
|
||||
<p>管理和跟踪贷款申请流程</p>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<!-- 搜索和筛选 -->
|
||||
<div class="search-section">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<a-input-search
|
||||
v-model:value="searchText"
|
||||
placeholder="搜索申请人或申请编号"
|
||||
enter-button="搜索"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-select
|
||||
v-model:value="statusFilter"
|
||||
placeholder="申请状态"
|
||||
allow-clear
|
||||
@change="handleFilter"
|
||||
>
|
||||
<a-select-option value="pending">待审核</a-select-option>
|
||||
<a-select-option value="approved">已通过</a-select-option>
|
||||
<a-select-option value="rejected">已拒绝</a-select-option>
|
||||
<a-select-option value="processing">处理中</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-select
|
||||
v-model:value="typeFilter"
|
||||
placeholder="贷款类型"
|
||||
allow-clear
|
||||
@change="handleFilter"
|
||||
>
|
||||
<a-select-option value="personal">个人贷款</a-select-option>
|
||||
<a-select-option value="business">企业贷款</a-select-option>
|
||||
<a-select-option value="mortgage">抵押贷款</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-range-picker
|
||||
v-model:value="dateRange"
|
||||
@change="handleFilter"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-button type="primary" @click="handleAddApplication">
|
||||
<PlusOutlined />
|
||||
新建申请
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
<div class="search-filter-section">
|
||||
<a-row :gutter="16" align="middle">
|
||||
<a-col :span="4">
|
||||
<a-select
|
||||
v-model:value="searchQuery.field"
|
||||
placeholder="申请单号"
|
||||
style="width: 100%"
|
||||
>
|
||||
<a-select-option value="applicationNumber">申请单号</a-select-option>
|
||||
<a-select-option value="customerName">客户姓名</a-select-option>
|
||||
<a-select-option value="productName">贷款产品</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-input
|
||||
v-model:value="searchQuery.value"
|
||||
placeholder="请输入内容"
|
||||
allow-clear
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
<search-outlined /> 搜索
|
||||
</a-button>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-button @click="handleReset">重置</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 申请列表 -->
|
||||
<div class="table-section">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="filteredApplications"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
row-key="id"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'type'">
|
||||
<a-tag :color="getTypeColor(record.type)">
|
||||
{{ getTypeText(record.type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'amount'">
|
||||
{{ formatAmount(record.amount) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'progress'">
|
||||
<a-progress
|
||||
:percent="getProgressPercent(record.status)"
|
||||
:status="getProgressStatus(record.status)"
|
||||
size="small"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="handleView(record)">
|
||||
查看
|
||||
</a-button>
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleApprove(record)"
|
||||
v-if="record.status === 'pending'"
|
||||
>
|
||||
审核
|
||||
</a-button>
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleReject(record)"
|
||||
v-if="record.status === 'pending'"
|
||||
danger
|
||||
>
|
||||
拒绝
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<div class="applications-table-section">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="filteredApplications"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
row-key="id"
|
||||
@change="handleTableChange"
|
||||
:expand-row-by-click="false"
|
||||
:expand-icon-column-index="0"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
<template v-else-if="column.key === 'policyInfo'">
|
||||
<a-button type="link" size="small" @click="viewPolicy(record)">
|
||||
查看保单
|
||||
</a-button>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'amount'">
|
||||
{{ formatAmount(record.amount) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="handleReject(record)">
|
||||
打回
|
||||
</a-button>
|
||||
<a-button type="link" size="small" @click="handleApprove(record)">
|
||||
通过
|
||||
</a-button>
|
||||
<a-button type="link" size="small" @click="handleView(record)">
|
||||
详情
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
|
||||
<!-- 申请详情模态框 -->
|
||||
@@ -222,14 +182,14 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { PlusOutlined } from '@ant-design/icons-vue'
|
||||
import { SearchOutlined } from '@ant-design/icons-vue'
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const searchText = ref('')
|
||||
const statusFilter = ref(undefined)
|
||||
const typeFilter = ref(undefined)
|
||||
const dateRange = ref([])
|
||||
const searchQuery = ref({
|
||||
field: 'applicationNumber',
|
||||
value: ''
|
||||
})
|
||||
const detailModalVisible = ref(false)
|
||||
const auditModalVisible = ref(false)
|
||||
const selectedApplication = ref(null)
|
||||
@@ -245,52 +205,85 @@ const pagination = ref({
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条,共 ${total} 条`
|
||||
showTotal: (total) => `共${total}条`
|
||||
})
|
||||
|
||||
// 表格列配置
|
||||
const columns = [
|
||||
{
|
||||
title: '申请编号',
|
||||
title: '',
|
||||
key: 'expand',
|
||||
width: 50,
|
||||
customRender: () => '>'
|
||||
},
|
||||
{
|
||||
title: '申请单号',
|
||||
dataIndex: 'applicationNumber',
|
||||
key: 'applicationNumber',
|
||||
width: 150
|
||||
width: 180
|
||||
},
|
||||
{
|
||||
title: '申请人',
|
||||
dataIndex: 'applicantName',
|
||||
key: 'applicantName',
|
||||
title: '贷款产品',
|
||||
dataIndex: 'productName',
|
||||
key: 'productName',
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
title: '申请养殖户',
|
||||
dataIndex: 'farmerName',
|
||||
key: 'farmerName',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
title: '贷款人姓名',
|
||||
dataIndex: 'borrowerName',
|
||||
key: 'borrowerName',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '贷款人身份证号',
|
||||
dataIndex: 'borrowerIdNumber',
|
||||
key: 'borrowerIdNumber',
|
||||
width: 180
|
||||
},
|
||||
{
|
||||
title: '生资种类',
|
||||
dataIndex: 'assetType',
|
||||
key: 'assetType',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
title: '申请数量',
|
||||
dataIndex: 'applicationQuantity',
|
||||
key: 'applicationQuantity',
|
||||
width: 120,
|
||||
sorter: true
|
||||
},
|
||||
{
|
||||
title: '保单信息',
|
||||
dataIndex: 'policyInfo',
|
||||
key: 'policyInfo',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '申请金额',
|
||||
title: '申请额度',
|
||||
dataIndex: 'amount',
|
||||
key: 'amount',
|
||||
width: 120
|
||||
width: 120,
|
||||
sorter: true
|
||||
},
|
||||
{
|
||||
title: '申请时间',
|
||||
dataIndex: 'applicationTime',
|
||||
key: 'applicationTime',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '进度',
|
||||
dataIndex: 'progress',
|
||||
key: 'progress',
|
||||
width: 150
|
||||
title: '当前状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: 120,
|
||||
filters: [
|
||||
{ text: '待初审', value: 'pending_review' },
|
||||
{ text: '核验待放款', value: 'verification_pending' },
|
||||
{ text: '待绑定', value: 'pending_binding' },
|
||||
{ text: '已通过', value: 'approved' },
|
||||
{ text: '已拒绝', value: 'rejected' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
@@ -304,87 +297,86 @@ const columns = [
|
||||
const applications = ref([
|
||||
{
|
||||
id: 1,
|
||||
applicationNumber: 'APP-202401180001',
|
||||
applicantName: '张三',
|
||||
type: 'personal',
|
||||
status: 'pending',
|
||||
amount: 200000,
|
||||
term: 24,
|
||||
interestRate: 6.5,
|
||||
applicationTime: '2024-01-18 09:30:00',
|
||||
applicationNumber: '20240325123703784',
|
||||
productName: '惠农贷',
|
||||
farmerName: '刘超',
|
||||
borrowerName: '11',
|
||||
borrowerIdNumber: '511***********3017',
|
||||
assetType: '牛',
|
||||
applicationQuantity: '10头',
|
||||
policyInfo: '查看保单',
|
||||
amount: 100000.00,
|
||||
status: 'pending_review',
|
||||
applicationTime: '2024-03-25 12:37:03',
|
||||
phone: '13800138000',
|
||||
idCard: '110101199001011234',
|
||||
purpose: '个人消费',
|
||||
purpose: '养殖贷款',
|
||||
remark: '',
|
||||
auditRecords: [
|
||||
{
|
||||
id: 1,
|
||||
action: 'submit',
|
||||
auditor: '张三',
|
||||
time: '2024-01-18 09:30:00',
|
||||
auditor: '刘超',
|
||||
time: '2024-03-25 12:37:03',
|
||||
comment: '提交申请'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
applicationNumber: 'APP-202401180002',
|
||||
applicantName: '李四',
|
||||
type: 'business',
|
||||
status: 'approved',
|
||||
amount: 1000000,
|
||||
term: 36,
|
||||
interestRate: 5.8,
|
||||
applicationTime: '2024-01-17 14:20:00',
|
||||
applicationNumber: '20240229110801968',
|
||||
productName: '中国工商银行扎旗支行"畜禽活体抵押"',
|
||||
farmerName: '刘超',
|
||||
borrowerName: '1',
|
||||
borrowerIdNumber: '511***********3017',
|
||||
assetType: '牛',
|
||||
applicationQuantity: '10头',
|
||||
policyInfo: '查看保单',
|
||||
amount: 100000.00,
|
||||
status: 'verification_pending',
|
||||
applicationTime: '2024-02-29 11:08:01',
|
||||
phone: '13900139000',
|
||||
idCard: '110101199002021234',
|
||||
purpose: '企业经营',
|
||||
purpose: '养殖贷款',
|
||||
remark: '',
|
||||
auditRecords: [
|
||||
{
|
||||
id: 1,
|
||||
action: 'submit',
|
||||
auditor: '李四',
|
||||
time: '2024-01-17 14:20:00',
|
||||
auditor: '刘超',
|
||||
time: '2024-02-29 11:08:01',
|
||||
comment: '提交申请'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
action: 'approve',
|
||||
auditor: '王经理',
|
||||
time: '2024-01-18 10:15:00',
|
||||
time: '2024-03-01 10:15:00',
|
||||
comment: '资料齐全,符合条件,同意放款'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
applicationNumber: 'APP-202401180003',
|
||||
applicantName: '王五',
|
||||
type: 'mortgage',
|
||||
status: 'rejected',
|
||||
amount: 500000,
|
||||
term: 120,
|
||||
interestRate: 4.5,
|
||||
applicationTime: '2024-01-16 16:45:00',
|
||||
applicationNumber: '20240229105806431',
|
||||
productName: '惠农贷',
|
||||
farmerName: '刘超',
|
||||
borrowerName: '1',
|
||||
borrowerIdNumber: '511***********3017',
|
||||
assetType: '牛',
|
||||
applicationQuantity: '10头',
|
||||
policyInfo: '查看保单',
|
||||
amount: 100000.00,
|
||||
status: 'pending_binding',
|
||||
applicationTime: '2024-02-29 10:58:06',
|
||||
phone: '13700137000',
|
||||
idCard: '110101199003031234',
|
||||
purpose: '购房',
|
||||
purpose: '养殖贷款',
|
||||
remark: '',
|
||||
auditRecords: [
|
||||
{
|
||||
id: 1,
|
||||
action: 'submit',
|
||||
auditor: '王五',
|
||||
time: '2024-01-16 16:45:00',
|
||||
auditor: '刘超',
|
||||
time: '2024-02-29 10:58:06',
|
||||
comment: '提交申请'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
action: 'reject',
|
||||
auditor: '赵经理',
|
||||
time: '2024-01-17 11:30:00',
|
||||
comment: '抵押物价值不足,不符合放款条件'
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -394,19 +386,21 @@ const applications = ref([
|
||||
const filteredApplications = computed(() => {
|
||||
let result = applications.value
|
||||
|
||||
if (searchText.value) {
|
||||
result = result.filter(app =>
|
||||
app.applicantName.toLowerCase().includes(searchText.value.toLowerCase()) ||
|
||||
app.applicationNumber.toLowerCase().includes(searchText.value.toLowerCase())
|
||||
)
|
||||
}
|
||||
|
||||
if (statusFilter.value) {
|
||||
result = result.filter(app => app.status === statusFilter.value)
|
||||
}
|
||||
|
||||
if (typeFilter.value) {
|
||||
result = result.filter(app => app.type === typeFilter.value)
|
||||
if (searchQuery.value.value) {
|
||||
const searchValue = searchQuery.value.value.toLowerCase()
|
||||
const field = searchQuery.value.field
|
||||
|
||||
result = result.filter(app => {
|
||||
if (field === 'applicationNumber') {
|
||||
return app.applicationNumber.toLowerCase().includes(searchValue)
|
||||
} else if (field === 'customerName') {
|
||||
return app.borrowerName.toLowerCase().includes(searchValue) ||
|
||||
app.farmerName.toLowerCase().includes(searchValue)
|
||||
} else if (field === 'productName') {
|
||||
return app.productName.toLowerCase().includes(searchValue)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -417,8 +411,11 @@ const handleSearch = () => {
|
||||
// 搜索逻辑已在计算属性中处理
|
||||
}
|
||||
|
||||
const handleFilter = () => {
|
||||
// 筛选逻辑已在计算属性中处理
|
||||
const handleReset = () => {
|
||||
searchQuery.value = {
|
||||
field: 'applicationNumber',
|
||||
value: ''
|
||||
}
|
||||
}
|
||||
|
||||
const handleTableChange = (pag) => {
|
||||
@@ -426,10 +423,6 @@ const handleTableChange = (pag) => {
|
||||
pagination.value.pageSize = pag.pageSize
|
||||
}
|
||||
|
||||
const handleAddApplication = () => {
|
||||
message.info('新建申请功能开发中...')
|
||||
}
|
||||
|
||||
const handleView = (record) => {
|
||||
selectedApplication.value = record
|
||||
detailModalVisible.value = true
|
||||
@@ -449,6 +442,11 @@ const handleReject = (record) => {
|
||||
auditModalVisible.value = true
|
||||
}
|
||||
|
||||
const viewPolicy = (record) => {
|
||||
message.info(`查看保单: ${record.applicationNumber}`)
|
||||
// 实际项目中这里会打开保单详情页面
|
||||
}
|
||||
|
||||
const handleAuditSubmit = () => {
|
||||
if (!auditForm.value.comment) {
|
||||
message.error('请输入审核意见')
|
||||
@@ -478,20 +476,22 @@ const handleAuditCancel = () => {
|
||||
|
||||
const getStatusColor = (status) => {
|
||||
const colors = {
|
||||
pending: 'orange',
|
||||
pending_review: 'blue',
|
||||
verification_pending: 'blue',
|
||||
pending_binding: 'blue',
|
||||
approved: 'green',
|
||||
rejected: 'red',
|
||||
processing: 'blue'
|
||||
rejected: 'red'
|
||||
}
|
||||
return colors[status] || 'default'
|
||||
}
|
||||
|
||||
const getStatusText = (status) => {
|
||||
const texts = {
|
||||
pending: '待审核',
|
||||
pending_review: '待初审',
|
||||
verification_pending: '核验待放款',
|
||||
pending_binding: '待绑定',
|
||||
approved: '已通过',
|
||||
rejected: '已拒绝',
|
||||
processing: '处理中'
|
||||
rejected: '已拒绝'
|
||||
}
|
||||
return texts[status] || status
|
||||
}
|
||||
@@ -549,10 +549,7 @@ const getAuditActionText = (action) => {
|
||||
}
|
||||
|
||||
const formatAmount = (amount) => {
|
||||
if (amount >= 10000) {
|
||||
return (amount / 10000).toFixed(0) + '万'
|
||||
}
|
||||
return amount.toString()
|
||||
return `${amount.toFixed(2)}元`
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
@@ -562,40 +559,43 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.loan-applications {
|
||||
.loan-applications-page {
|
||||
padding: 24px;
|
||||
background-color: #f0f2f5;
|
||||
min-height: calc(100vh - 134px);
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
background-color: #fff;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
font-size: 28px;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.content {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
.search-filter-section {
|
||||
background-color: #fff;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
border-radius: 8px;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.table-section {
|
||||
margin-top: 16px;
|
||||
.applications-table-section {
|
||||
background-color: #fff;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.application-detail {
|
||||
@@ -644,4 +644,56 @@ onMounted(() => {
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.ant-table-thead > tr > th) {
|
||||
background-color: #fafafa;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
:deep(.ant-table-tbody > tr > td) {
|
||||
padding: 12px 8px;
|
||||
}
|
||||
|
||||
:deep(.ant-table-tbody > tr:hover > td) {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 状态标签样式 */
|
||||
:deep(.ant-tag) {
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
padding: 2px 8px;
|
||||
}
|
||||
|
||||
/* 操作按钮样式 */
|
||||
:deep(.ant-btn-link) {
|
||||
padding: 4px 8px;
|
||||
height: auto;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* 分页样式 */
|
||||
:deep(.ant-pagination) {
|
||||
margin-top: 16px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 768px) {
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.search-filter-section .ant-col {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.search-filter-section .ant-col:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,112 +1,74 @@
|
||||
<template>
|
||||
<div class="loan-contracts">
|
||||
<div class="loan-contracts-page">
|
||||
<div class="page-header">
|
||||
<h1>贷款合同</h1>
|
||||
<p>管理和跟踪贷款合同状态</p>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<!-- 搜索和筛选 -->
|
||||
<div class="search-section">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<a-input-search
|
||||
v-model:value="searchText"
|
||||
placeholder="搜索合同编号或客户姓名"
|
||||
enter-button="搜索"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-select
|
||||
v-model:value="statusFilter"
|
||||
placeholder="合同状态"
|
||||
allow-clear
|
||||
@change="handleFilter"
|
||||
>
|
||||
<a-select-option value="draft">草稿</a-select-option>
|
||||
<a-select-option value="pending">待签署</a-select-option>
|
||||
<a-select-option value="signed">已签署</a-select-option>
|
||||
<a-select-option value="active">生效中</a-select-option>
|
||||
<a-select-option value="completed">已完成</a-select-option>
|
||||
<a-select-option value="terminated">已终止</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-select
|
||||
v-model:value="typeFilter"
|
||||
placeholder="合同类型"
|
||||
allow-clear
|
||||
@change="handleFilter"
|
||||
>
|
||||
<a-select-option value="personal">个人贷款</a-select-option>
|
||||
<a-select-option value="business">企业贷款</a-select-option>
|
||||
<a-select-option value="mortgage">抵押贷款</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-range-picker
|
||||
v-model:value="dateRange"
|
||||
@change="handleFilter"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-button type="primary" @click="handleCreateContract">
|
||||
<PlusOutlined />
|
||||
新建合同
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
<div class="search-filter-section">
|
||||
<a-row :gutter="16" align="middle">
|
||||
<a-col :span="4">
|
||||
<a-select
|
||||
v-model:value="searchQuery.field"
|
||||
placeholder="申请单号"
|
||||
style="width: 100%"
|
||||
>
|
||||
<a-select-option value="applicationNumber">申请单号</a-select-option>
|
||||
<a-select-option value="customerName">客户姓名</a-select-option>
|
||||
<a-select-option value="productName">贷款产品</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-input
|
||||
v-model:value="searchQuery.value"
|
||||
placeholder="请输入内容"
|
||||
allow-clear
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
<search-outlined /> 搜索
|
||||
</a-button>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-button @click="handleReset">重置</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 合同列表 -->
|
||||
<div class="table-section">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="filteredContracts"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
row-key="id"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'type'">
|
||||
<a-tag :color="getTypeColor(record.type)">
|
||||
{{ getTypeText(record.type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'amount'">
|
||||
{{ formatAmount(record.amount) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="handleView(record)">
|
||||
查看
|
||||
</a-button>
|
||||
<a-button type="link" size="small" @click="handleEdit(record)">
|
||||
编辑
|
||||
</a-button>
|
||||
<a-button type="link" size="small" @click="handleDownload(record)">
|
||||
下载
|
||||
</a-button>
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleSign(record)"
|
||||
v-if="record.status === 'pending'"
|
||||
>
|
||||
签署
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<div class="contracts-table-section">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="filteredContracts"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
row-key="id"
|
||||
@change="handleTableChange"
|
||||
:locale="{ emptyText: '暂无数据' }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
<template v-else-if="column.key === 'amount'">
|
||||
{{ formatAmount(record.amount) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="handleView(record)">
|
||||
查看
|
||||
</a-button>
|
||||
<a-button type="link" size="small" @click="handleEdit(record)">
|
||||
编辑
|
||||
</a-button>
|
||||
<a-button type="link" size="small" @click="handleDownload(record)">
|
||||
下载
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
|
||||
<!-- 合同详情模态框 -->
|
||||
@@ -232,14 +194,14 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { PlusOutlined } from '@ant-design/icons-vue'
|
||||
import { SearchOutlined } from '@ant-design/icons-vue'
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const searchText = ref('')
|
||||
const statusFilter = ref(undefined)
|
||||
const typeFilter = ref(undefined)
|
||||
const dateRange = ref([])
|
||||
const searchQuery = ref({
|
||||
field: 'applicationNumber',
|
||||
value: ''
|
||||
})
|
||||
const detailModalVisible = ref(false)
|
||||
const signModalVisible = ref(false)
|
||||
const selectedContract = ref(null)
|
||||
@@ -255,202 +217,97 @@ const pagination = ref({
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条,共 ${total} 条`
|
||||
showTotal: (total) => `共${total}条`
|
||||
})
|
||||
|
||||
// 表格列配置
|
||||
const columns = [
|
||||
{
|
||||
title: '合同编号',
|
||||
dataIndex: 'contractNumber',
|
||||
key: 'contractNumber',
|
||||
width: 150
|
||||
title: '申请单号',
|
||||
dataIndex: 'applicationNumber',
|
||||
key: 'applicationNumber',
|
||||
width: 180
|
||||
},
|
||||
{
|
||||
title: '客户姓名',
|
||||
dataIndex: 'customerName',
|
||||
key: 'customerName',
|
||||
title: '贷款产品',
|
||||
dataIndex: 'productName',
|
||||
key: 'productName',
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
title: '申请养殖户',
|
||||
dataIndex: 'farmerName',
|
||||
key: 'farmerName',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
title: '贷款人姓名',
|
||||
dataIndex: 'borrowerName',
|
||||
key: 'borrowerName',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '贷款人身份证号',
|
||||
dataIndex: 'borrowerIdNumber',
|
||||
key: 'borrowerIdNumber',
|
||||
width: 180
|
||||
},
|
||||
{
|
||||
title: '生资种类',
|
||||
dataIndex: 'assetType',
|
||||
key: 'assetType',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: 100
|
||||
title: '申请数量',
|
||||
dataIndex: 'applicationQuantity',
|
||||
key: 'applicationQuantity',
|
||||
width: 120,
|
||||
sorter: true
|
||||
},
|
||||
{
|
||||
title: '贷款金额',
|
||||
title: '申请额度',
|
||||
dataIndex: 'amount',
|
||||
key: 'amount',
|
||||
width: 120
|
||||
width: 120,
|
||||
sorter: true
|
||||
},
|
||||
{
|
||||
title: '期限',
|
||||
dataIndex: 'term',
|
||||
key: 'term',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '年利率',
|
||||
dataIndex: 'interestRate',
|
||||
key: 'interestRate',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '签署日期',
|
||||
dataIndex: 'signDate',
|
||||
key: 'signDate',
|
||||
title: '当前状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 250,
|
||||
width: 200,
|
||||
fixed: 'right'
|
||||
}
|
||||
]
|
||||
|
||||
// 模拟合同数据
|
||||
const contracts = ref([
|
||||
{
|
||||
id: 1,
|
||||
contractNumber: 'CON-202401180001',
|
||||
customerName: '张三',
|
||||
type: 'personal',
|
||||
status: 'signed',
|
||||
amount: 200000,
|
||||
term: 24,
|
||||
interestRate: 6.5,
|
||||
repaymentMethod: 'equal_installment',
|
||||
signDate: '2024-01-18',
|
||||
effectiveDate: '2024-01-18',
|
||||
maturityDate: '2026-01-18',
|
||||
phone: '13800138000',
|
||||
idCard: '110101199001011234',
|
||||
terms: [
|
||||
'借款人应按期还款,不得逾期',
|
||||
'借款人应按时支付利息',
|
||||
'借款人不得将贷款用于非法用途',
|
||||
'借款人应配合银行进行贷后管理'
|
||||
],
|
||||
history: [
|
||||
{
|
||||
id: 1,
|
||||
action: 'create',
|
||||
operator: '系统',
|
||||
time: '2024-01-18 09:30:00',
|
||||
comment: '合同创建'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
action: 'sign',
|
||||
operator: '张三',
|
||||
time: '2024-01-18 10:15:00',
|
||||
comment: '客户签署合同'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
contractNumber: 'CON-202401180002',
|
||||
customerName: '李四',
|
||||
type: 'business',
|
||||
status: 'pending',
|
||||
amount: 1000000,
|
||||
term: 36,
|
||||
interestRate: 5.8,
|
||||
repaymentMethod: 'balloon',
|
||||
signDate: null,
|
||||
effectiveDate: null,
|
||||
maturityDate: '2027-01-18',
|
||||
phone: '13900139000',
|
||||
idCard: '110101199002021234',
|
||||
terms: [
|
||||
'企业应按期还款,不得逾期',
|
||||
'企业应按时支付利息',
|
||||
'企业应提供财务报表',
|
||||
'企业应配合银行进行贷后管理'
|
||||
],
|
||||
history: [
|
||||
{
|
||||
id: 1,
|
||||
action: 'create',
|
||||
operator: '系统',
|
||||
time: '2024-01-18 14:20:00',
|
||||
comment: '合同创建'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
contractNumber: 'CON-202401180003',
|
||||
customerName: '王五',
|
||||
type: 'mortgage',
|
||||
status: 'active',
|
||||
amount: 500000,
|
||||
term: 120,
|
||||
interestRate: 4.5,
|
||||
repaymentMethod: 'equal_installment',
|
||||
signDate: '2024-01-17',
|
||||
effectiveDate: '2024-01-17',
|
||||
maturityDate: '2034-01-17',
|
||||
phone: '13700137000',
|
||||
idCard: '110101199003031234',
|
||||
terms: [
|
||||
'借款人应按期还款,不得逾期',
|
||||
'借款人应按时支付利息',
|
||||
'抵押物不得转让或处置',
|
||||
'借款人应配合银行进行贷后管理'
|
||||
],
|
||||
history: [
|
||||
{
|
||||
id: 1,
|
||||
action: 'create',
|
||||
operator: '系统',
|
||||
time: '2024-01-17 16:45:00',
|
||||
comment: '合同创建'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
action: 'sign',
|
||||
operator: '王五',
|
||||
time: '2024-01-17 17:30:00',
|
||||
comment: '客户签署合同'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
action: 'activate',
|
||||
operator: '系统',
|
||||
time: '2024-01-17 18:00:00',
|
||||
comment: '合同生效'
|
||||
}
|
||||
]
|
||||
}
|
||||
])
|
||||
// 模拟合同数据 - 设置为空数据以匹配图片
|
||||
const contracts = ref([])
|
||||
|
||||
// 计算属性
|
||||
const filteredContracts = computed(() => {
|
||||
let result = contracts.value
|
||||
|
||||
if (searchText.value) {
|
||||
result = result.filter(contract =>
|
||||
contract.customerName.toLowerCase().includes(searchText.value.toLowerCase()) ||
|
||||
contract.contractNumber.toLowerCase().includes(searchText.value.toLowerCase())
|
||||
)
|
||||
}
|
||||
|
||||
if (statusFilter.value) {
|
||||
result = result.filter(contract => contract.status === statusFilter.value)
|
||||
}
|
||||
|
||||
if (typeFilter.value) {
|
||||
result = result.filter(contract => contract.type === typeFilter.value)
|
||||
if (searchQuery.value.value) {
|
||||
const searchValue = searchQuery.value.value.toLowerCase()
|
||||
const field = searchQuery.value.field
|
||||
|
||||
result = result.filter(contract => {
|
||||
if (field === 'applicationNumber') {
|
||||
return contract.applicationNumber.toLowerCase().includes(searchValue)
|
||||
} else if (field === 'customerName') {
|
||||
return contract.borrowerName.toLowerCase().includes(searchValue) ||
|
||||
contract.farmerName.toLowerCase().includes(searchValue)
|
||||
} else if (field === 'productName') {
|
||||
return contract.productName.toLowerCase().includes(searchValue)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -461,8 +318,11 @@ const handleSearch = () => {
|
||||
// 搜索逻辑已在计算属性中处理
|
||||
}
|
||||
|
||||
const handleFilter = () => {
|
||||
// 筛选逻辑已在计算属性中处理
|
||||
const handleReset = () => {
|
||||
searchQuery.value = {
|
||||
field: 'applicationNumber',
|
||||
value: ''
|
||||
}
|
||||
}
|
||||
|
||||
const handleTableChange = (pag) => {
|
||||
@@ -470,28 +330,17 @@ const handleTableChange = (pag) => {
|
||||
pagination.value.pageSize = pag.pageSize
|
||||
}
|
||||
|
||||
const handleCreateContract = () => {
|
||||
message.info('新建合同功能开发中...')
|
||||
}
|
||||
|
||||
const handleView = (record) => {
|
||||
selectedContract.value = record
|
||||
detailModalVisible.value = true
|
||||
}
|
||||
|
||||
const handleEdit = (record) => {
|
||||
message.info(`编辑合同: ${record.contractNumber}`)
|
||||
message.info(`编辑合同: ${record.applicationNumber}`)
|
||||
}
|
||||
|
||||
const handleDownload = (record) => {
|
||||
message.info(`下载合同: ${record.contractNumber}`)
|
||||
}
|
||||
|
||||
const handleSign = (record) => {
|
||||
selectedContract.value = record
|
||||
signForm.value.password = ''
|
||||
signForm.value.comment = ''
|
||||
signModalVisible.value = true
|
||||
message.info(`下载合同: ${record.applicationNumber}`)
|
||||
}
|
||||
|
||||
const handleSignSubmit = () => {
|
||||
@@ -525,9 +374,12 @@ const handleSignCancel = () => {
|
||||
|
||||
const getStatusColor = (status) => {
|
||||
const colors = {
|
||||
draft: 'default',
|
||||
pending: 'orange',
|
||||
signed: 'blue',
|
||||
pending_review: 'blue',
|
||||
verification_pending: 'blue',
|
||||
pending_binding: 'blue',
|
||||
approved: 'green',
|
||||
rejected: 'red',
|
||||
signed: 'green',
|
||||
active: 'green',
|
||||
completed: 'success',
|
||||
terminated: 'red'
|
||||
@@ -537,8 +389,11 @@ const getStatusColor = (status) => {
|
||||
|
||||
const getStatusText = (status) => {
|
||||
const texts = {
|
||||
draft: '草稿',
|
||||
pending: '待签署',
|
||||
pending_review: '待初审',
|
||||
verification_pending: '核验待放款',
|
||||
pending_binding: '待绑定',
|
||||
approved: '已通过',
|
||||
rejected: '已拒绝',
|
||||
signed: '已签署',
|
||||
active: '生效中',
|
||||
completed: '已完成',
|
||||
@@ -596,10 +451,7 @@ const getHistoryActionText = (action) => {
|
||||
}
|
||||
|
||||
const formatAmount = (amount) => {
|
||||
if (amount >= 10000) {
|
||||
return (amount / 10000).toFixed(0) + '万'
|
||||
}
|
||||
return amount.toString()
|
||||
return `${amount.toFixed(2)}元`
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
@@ -609,40 +461,43 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.loan-contracts {
|
||||
.loan-contracts-page {
|
||||
padding: 24px;
|
||||
background-color: #f0f2f5;
|
||||
min-height: calc(100vh - 134px);
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
background-color: #fff;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
font-size: 28px;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.content {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
.search-filter-section {
|
||||
background-color: #fff;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
border-radius: 8px;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.table-section {
|
||||
margin-top: 16px;
|
||||
.contracts-table-section {
|
||||
background-color: #fff;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.contract-detail {
|
||||
@@ -713,4 +568,66 @@ onMounted(() => {
|
||||
.sign-content {
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.ant-table-thead > tr > th) {
|
||||
background-color: #fafafa;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
:deep(.ant-table-tbody > tr > td) {
|
||||
padding: 12px 8px;
|
||||
}
|
||||
|
||||
:deep(.ant-table-tbody > tr:hover > td) {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 状态标签样式 */
|
||||
:deep(.ant-tag) {
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
padding: 2px 8px;
|
||||
}
|
||||
|
||||
/* 操作按钮样式 */
|
||||
:deep(.ant-btn-link) {
|
||||
padding: 4px 8px;
|
||||
height: auto;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* 分页样式 */
|
||||
:deep(.ant-pagination) {
|
||||
margin-top: 16px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* 空数据样式 */
|
||||
:deep(.ant-empty) {
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
:deep(.ant-empty-description) {
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 768px) {
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.search-filter-section .ant-col {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.search-filter-section .ant-col:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,469 +1,387 @@
|
||||
<template>
|
||||
<div class="loan-products">
|
||||
<div class="loan-products-page">
|
||||
<!-- 页面头部 -->
|
||||
<div class="page-header">
|
||||
<h1>贷款商品</h1>
|
||||
<p>管理和配置银行贷款产品</p>
|
||||
<a-button type="primary" @click="handleAddProduct">
|
||||
<plus-outlined />
|
||||
新增贷款
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<!-- 搜索和筛选 -->
|
||||
<div class="search-section">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="8">
|
||||
<a-input-search
|
||||
v-model:value="searchText"
|
||||
placeholder="搜索产品名称或编号"
|
||||
enter-button="搜索"
|
||||
@search="handleSearch"
|
||||
<!-- 搜索筛选区域 -->
|
||||
<div class="search-section">
|
||||
<a-row :gutter="16" align="middle">
|
||||
<a-col :span="8">
|
||||
<a-input
|
||||
v-model:value="searchText"
|
||||
placeholder="请输入贷款产品"
|
||||
allow-clear
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
<search-outlined />
|
||||
搜索
|
||||
</a-button>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-button @click="handleReset">重置</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<div class="table-section">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="filteredProducts"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
@change="handleTableChange"
|
||||
row-key="id"
|
||||
:locale="{ emptyText: '暂无数据' }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'onSaleStatus'">
|
||||
<a-switch
|
||||
v-model:checked="record.onSaleStatus"
|
||||
@change="handleToggleStatus(record)"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-select
|
||||
v-model:value="statusFilter"
|
||||
placeholder="产品状态"
|
||||
allow-clear
|
||||
@change="handleFilter"
|
||||
>
|
||||
<a-select-option value="active">启用</a-select-option>
|
||||
<a-select-option value="inactive">停用</a-select-option>
|
||||
<a-select-option value="draft">草稿</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-select
|
||||
v-model:value="typeFilter"
|
||||
placeholder="产品类型"
|
||||
allow-clear
|
||||
@change="handleFilter"
|
||||
>
|
||||
<a-select-option value="personal">个人贷款</a-select-option>
|
||||
<a-select-option value="business">企业贷款</a-select-option>
|
||||
<a-select-option value="mortgage">抵押贷款</a-select-option>
|
||||
<a-select-option value="credit">信用贷款</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-button type="primary" @click="handleAddProduct">
|
||||
<PlusOutlined />
|
||||
新建产品
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 产品列表 -->
|
||||
<div class="table-section">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="filteredProducts"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
row-key="id"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'type'">
|
||||
<a-tag :color="getTypeColor(record.type)">
|
||||
{{ getTypeText(record.type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'interestRate'">
|
||||
{{ record.interestRate }}% - {{ record.maxInterestRate }}%
|
||||
</template>
|
||||
<template v-else-if="column.key === 'amount'">
|
||||
{{ formatAmount(record.minAmount) }} - {{ formatAmount(record.maxAmount) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="handleView(record)">
|
||||
查看
|
||||
</a-button>
|
||||
<a-button type="link" size="small" @click="handleEdit(record)">
|
||||
编辑
|
||||
</a-button>
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleToggleStatus(record)"
|
||||
:danger="record.status === 'active'"
|
||||
>
|
||||
{{ record.status === 'active' ? '停用' : '启用' }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="handleEdit(record)">编辑</a-button>
|
||||
<a-button type="link" size="small" @click="handleView(record)">详情</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
|
||||
<!-- 产品详情模态框 -->
|
||||
<a-modal
|
||||
v-model:open="detailModalVisible"
|
||||
title="产品详情"
|
||||
width="800px"
|
||||
:footer="null"
|
||||
>
|
||||
<div v-if="selectedProduct" class="product-detail">
|
||||
<a-descriptions :column="2" bordered>
|
||||
<a-descriptions-item label="产品名称">
|
||||
{{ selectedProduct.name }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="产品编号">
|
||||
{{ selectedProduct.code }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="产品类型">
|
||||
<a-tag :color="getTypeColor(selectedProduct.type)">
|
||||
{{ getTypeText(selectedProduct.type) }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="产品状态">
|
||||
<a-tag :color="getStatusColor(selectedProduct.status)">
|
||||
{{ getStatusText(selectedProduct.status) }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="贷款额度">
|
||||
{{ formatAmount(selectedProduct.minAmount) }} - {{ formatAmount(selectedProduct.maxAmount) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="贷款期限">
|
||||
{{ selectedProduct.minTerm }} - {{ selectedProduct.maxTerm }} 个月
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="利率范围">
|
||||
{{ selectedProduct.interestRate }}% - {{ selectedProduct.maxInterestRate }}%
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="申请条件">
|
||||
{{ selectedProduct.requirements }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="产品描述" :span="2">
|
||||
{{ selectedProduct.description }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { PlusOutlined } from '@ant-design/icons-vue'
|
||||
import { api } from '@/utils/api'
|
||||
import { PlusOutlined, SearchOutlined } from '@ant-design/icons-vue'
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const searchText = ref('')
|
||||
const statusFilter = ref(undefined)
|
||||
const typeFilter = ref(undefined)
|
||||
const detailModalVisible = ref(false)
|
||||
const selectedProduct = ref(null)
|
||||
|
||||
// 分页配置
|
||||
const pagination = ref({
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条,共 ${total} 条`
|
||||
showTotal: (total) => `共 ${total} 条`,
|
||||
})
|
||||
|
||||
// 表格列配置
|
||||
const columns = [
|
||||
{
|
||||
title: '产品名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
{
|
||||
title: '贷款产品',
|
||||
dataIndex: 'productName',
|
||||
key: 'productName',
|
||||
sorter: true,
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
title: '产品编号',
|
||||
dataIndex: 'code',
|
||||
key: 'code',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '贷款额度',
|
||||
dataIndex: 'amount',
|
||||
key: 'amount',
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
title: '利率范围',
|
||||
dataIndex: 'interestRate',
|
||||
key: 'interestRate',
|
||||
{
|
||||
title: '贷款额度',
|
||||
dataIndex: 'loanAmount',
|
||||
key: 'loanAmount',
|
||||
sorter: true,
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '期限',
|
||||
dataIndex: 'term',
|
||||
key: 'term',
|
||||
{
|
||||
title: '贷款周期',
|
||||
dataIndex: 'loanTerm',
|
||||
key: 'loanTerm',
|
||||
sorter: true,
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '贷款利率',
|
||||
dataIndex: 'interestRate',
|
||||
key: 'interestRate',
|
||||
sorter: true,
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '服务区域',
|
||||
dataIndex: 'serviceArea',
|
||||
key: 'serviceArea',
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
title: '服务电话',
|
||||
dataIndex: 'servicePhone',
|
||||
key: 'servicePhone',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
{
|
||||
title: '服务客户总数量',
|
||||
dataIndex: 'totalCustomers',
|
||||
key: 'totalCustomers',
|
||||
sorter: true,
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '监管中客户',
|
||||
dataIndex: 'supervisionCustomers',
|
||||
key: 'supervisionCustomers',
|
||||
sorter: true,
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '已结项客户',
|
||||
dataIndex: 'completedCustomers',
|
||||
key: 'completedCustomers',
|
||||
sorter: true,
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '添加时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
sorter: true,
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '在售状态',
|
||||
dataIndex: 'onSaleStatus',
|
||||
key: 'onSaleStatus',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 200,
|
||||
fixed: 'right'
|
||||
}
|
||||
]
|
||||
|
||||
// 产品数据
|
||||
const products = ref([])
|
||||
|
||||
// 模拟产品数据(作为备用)
|
||||
const mockProducts = [
|
||||
// 模拟数据
|
||||
const products = ref([
|
||||
{
|
||||
id: 1,
|
||||
productName: '惠农贷',
|
||||
loanAmount: '50000~5000000元',
|
||||
loanTerm: '24',
|
||||
interestRate: '3.90%',
|
||||
serviceArea: '内蒙古自治区:通辽市',
|
||||
servicePhone: '15004901368',
|
||||
totalCustomers: 16,
|
||||
supervisionCustomers: 11,
|
||||
completedCustomers: 5,
|
||||
createTime: '2023-12-18 16:23:03',
|
||||
onSaleStatus: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '个人消费贷款',
|
||||
code: 'LOAN-002',
|
||||
type: 'personal',
|
||||
status: 'active',
|
||||
minAmount: 10000,
|
||||
maxAmount: 500000,
|
||||
minTerm: 6,
|
||||
maxTerm: 60,
|
||||
interestRate: 6.8,
|
||||
maxInterestRate: 12.5,
|
||||
requirements: '年满18周岁,有稳定收入来源,信用记录良好',
|
||||
description: '用于个人消费支出的信用贷款产品',
|
||||
createTime: '2024-01-05'
|
||||
productName: '中国工商银行扎旗支行"畜禽活体抵押"',
|
||||
loanAmount: '200000~1000000元',
|
||||
loanTerm: '12',
|
||||
interestRate: '4.70%',
|
||||
serviceArea: '内蒙古自治区:通辽市',
|
||||
servicePhone: '15004901368',
|
||||
totalCustomers: 10,
|
||||
supervisionCustomers: 5,
|
||||
completedCustomers: 5,
|
||||
createTime: '2023-06-20 17:36:17',
|
||||
onSaleStatus: true,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '企业经营贷款',
|
||||
code: 'LOAN-003',
|
||||
type: 'business',
|
||||
status: 'active',
|
||||
minAmount: 500000,
|
||||
maxAmount: 50000000,
|
||||
minTerm: 12,
|
||||
maxTerm: 120,
|
||||
interestRate: 5.2,
|
||||
maxInterestRate: 8.5,
|
||||
requirements: '企业成立满2年,年营业额达到500万以上',
|
||||
description: '为企业经营发展提供的流动资金贷款',
|
||||
createTime: '2024-01-10'
|
||||
productName: '中国银行扎旗支行"畜禽活体抵押"',
|
||||
loanAmount: '200000~1000000元',
|
||||
loanTerm: '12',
|
||||
interestRate: '4.60%',
|
||||
serviceArea: '内蒙古自治区:通辽市',
|
||||
servicePhone: '15004901368',
|
||||
totalCustomers: 2,
|
||||
supervisionCustomers: 2,
|
||||
completedCustomers: 0,
|
||||
createTime: '2023-06-20 17:34:33',
|
||||
onSaleStatus: true,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: '小微企业贷款',
|
||||
code: 'LOAN-004',
|
||||
type: 'business',
|
||||
status: 'draft',
|
||||
minAmount: 50000,
|
||||
maxAmount: 1000000,
|
||||
minTerm: 6,
|
||||
maxTerm: 36,
|
||||
interestRate: 7.5,
|
||||
maxInterestRate: 10.5,
|
||||
requirements: '小微企业,年营业额100万以上',
|
||||
description: '专为小微企业提供的快速贷款产品',
|
||||
createTime: '2024-01-15'
|
||||
}
|
||||
];
|
||||
productName: '中国农业银行扎旗支行"畜禽活体抵押"',
|
||||
loanAmount: '200000~1000000元',
|
||||
loanTerm: '12',
|
||||
interestRate: '4.80%',
|
||||
serviceArea: '内蒙古自治区:通辽市',
|
||||
servicePhone: '15004901368',
|
||||
totalCustomers: 26,
|
||||
supervisionCustomers: 24,
|
||||
completedCustomers: 2,
|
||||
createTime: '2023-06-20 17:09:39',
|
||||
onSaleStatus: true,
|
||||
},
|
||||
])
|
||||
|
||||
// 计算属性
|
||||
const filteredProducts = computed(() => {
|
||||
let result = products.value
|
||||
|
||||
if (searchText.value) {
|
||||
result = result.filter(product =>
|
||||
product.name.toLowerCase().includes(searchText.value.toLowerCase()) ||
|
||||
product.code.toLowerCase().includes(searchText.value.toLowerCase())
|
||||
)
|
||||
}
|
||||
|
||||
if (statusFilter.value) {
|
||||
result = result.filter(product => product.status === statusFilter.value)
|
||||
}
|
||||
|
||||
if (typeFilter.value) {
|
||||
result = result.filter(product => product.type === typeFilter.value)
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
// 方法
|
||||
|
||||
const handleAddProduct = () => {
|
||||
message.info('新建产品功能开发中...')
|
||||
}
|
||||
|
||||
const handleView = (record) => {
|
||||
selectedProduct.value = record
|
||||
detailModalVisible.value = true
|
||||
}
|
||||
|
||||
const handleEdit = (record) => {
|
||||
message.info(`编辑产品: ${record.name}`)
|
||||
}
|
||||
|
||||
const handleToggleStatus = (record) => {
|
||||
const newStatus = record.status === 'active' ? 'inactive' : 'active'
|
||||
record.status = newStatus
|
||||
message.success(`产品已${newStatus === 'active' ? '启用' : '停用'}`)
|
||||
}
|
||||
|
||||
const getStatusColor = (status) => {
|
||||
const colors = {
|
||||
active: 'green',
|
||||
inactive: 'red',
|
||||
draft: 'orange'
|
||||
}
|
||||
return colors[status] || 'default'
|
||||
}
|
||||
|
||||
const getStatusText = (status) => {
|
||||
const texts = {
|
||||
active: '启用',
|
||||
inactive: '停用',
|
||||
draft: '草稿'
|
||||
}
|
||||
return texts[status] || status
|
||||
}
|
||||
|
||||
const getTypeColor = (type) => {
|
||||
const colors = {
|
||||
personal: 'blue',
|
||||
business: 'green',
|
||||
mortgage: 'purple',
|
||||
credit: 'orange'
|
||||
}
|
||||
return colors[type] || 'default'
|
||||
}
|
||||
|
||||
const getTypeText = (type) => {
|
||||
const texts = {
|
||||
personal: '个人贷款',
|
||||
business: '企业贷款',
|
||||
mortgage: '抵押贷款',
|
||||
credit: '信用贷款'
|
||||
}
|
||||
return texts[type] || type
|
||||
}
|
||||
|
||||
const formatAmount = (amount) => {
|
||||
if (amount >= 10000) {
|
||||
return (amount / 10000).toFixed(0) + '万'
|
||||
}
|
||||
return amount.toString()
|
||||
}
|
||||
|
||||
// API调用函数
|
||||
const fetchProducts = async (params = {}) => {
|
||||
const fetchProducts = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await api.loanProducts.getList({
|
||||
page: pagination.value.current,
|
||||
limit: pagination.value.pageSize,
|
||||
search: searchText.value,
|
||||
status: statusFilter.value,
|
||||
type: typeFilter.value,
|
||||
...params
|
||||
})
|
||||
// 实际项目中这里会调用API获取数据
|
||||
// const response = await api.loanProducts.getList({
|
||||
// page: pagination.current,
|
||||
// pageSize: pagination.pageSize,
|
||||
// search: searchText.value,
|
||||
// })
|
||||
|
||||
if (response.success) {
|
||||
products.value = response.data.products || []
|
||||
pagination.value.total = response.data.pagination?.total || 0
|
||||
} else {
|
||||
message.error(response.message || '获取产品列表失败')
|
||||
// 使用模拟数据作为备用
|
||||
products.value = mockProducts
|
||||
pagination.value.total = mockProducts.length
|
||||
}
|
||||
// 使用模拟数据
|
||||
pagination.total = products.value.length
|
||||
} catch (error) {
|
||||
console.error('获取产品列表失败:', error)
|
||||
message.error('获取产品列表失败')
|
||||
// 使用模拟数据作为备用
|
||||
products.value = mockProducts
|
||||
pagination.value.total = mockProducts.length
|
||||
console.error('获取贷款商品失败:', error)
|
||||
message.error('获取贷款商品失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const filteredProducts = computed(() => {
|
||||
let result = products.value
|
||||
|
||||
if (searchText.value) {
|
||||
result = result.filter(product =>
|
||||
product.productName.toLowerCase().includes(searchText.value.toLowerCase())
|
||||
)
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.value.current = 1
|
||||
pagination.current = 1
|
||||
fetchProducts()
|
||||
}
|
||||
|
||||
const handleFilter = () => {
|
||||
pagination.value.current = 1
|
||||
const handleReset = () => {
|
||||
searchText.value = ''
|
||||
pagination.current = 1
|
||||
fetchProducts()
|
||||
}
|
||||
|
||||
const handleTableChange = (paginationInfo) => {
|
||||
pagination.value = paginationInfo
|
||||
const handleAddProduct = () => {
|
||||
message.info('新增贷款功能开发中...')
|
||||
}
|
||||
|
||||
const handleEdit = (record) => {
|
||||
message.info(`编辑产品: ${record.productName}`)
|
||||
}
|
||||
|
||||
const handleView = (record) => {
|
||||
message.info(`查看详情: ${record.productName}`)
|
||||
}
|
||||
|
||||
const handleToggleStatus = (record) => {
|
||||
const status = record.onSaleStatus ? '启用' : '停用'
|
||||
message.success(`${record.productName} 已${status}`)
|
||||
}
|
||||
|
||||
const handleTableChange = (pag, filters, sorter) => {
|
||||
pagination.current = pag.current
|
||||
pagination.pageSize = pag.pageSize
|
||||
fetchProducts()
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
fetchProducts()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.loan-products {
|
||||
padding: 24px;
|
||||
.loan-products-page {
|
||||
padding: 0;
|
||||
background: #f0f2f5;
|
||||
min-height: calc(100vh - 134px);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
padding: 24px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
margin: 0 0 8px 0;
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
margin: 0;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.content {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
background: white;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.table-section {
|
||||
margin-top: 16px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.product-detail {
|
||||
padding: 16px 0;
|
||||
/* 表格样式优化 */
|
||||
:deep(.ant-table-thead > tr > th) {
|
||||
background: #fafafa;
|
||||
font-weight: 600;
|
||||
color: #262626;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
</style>
|
||||
|
||||
:deep(.ant-table-tbody > tr:hover > td) {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
:deep(.ant-table-tbody > tr:nth-child(even) > td) {
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
:deep(.ant-table-tbody > tr:nth-child(odd) > td) {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
/* 开关样式 */
|
||||
:deep(.ant-switch-checked) {
|
||||
background-color: #1890ff;
|
||||
}
|
||||
|
||||
/* 链接按钮样式 */
|
||||
:deep(.ant-btn-link) {
|
||||
color: #1890ff;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
:deep(.ant-btn-link:hover) {
|
||||
color: #40a9ff;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.search-section .ant-col {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.search-section .ant-col:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,125 +1,72 @@
|
||||
<template>
|
||||
<div class="loan-release">
|
||||
<div class="loan-release-page">
|
||||
<div class="page-header">
|
||||
<h1>贷款解押</h1>
|
||||
<p>管理和处理贷款抵押物解押业务</p>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<!-- 搜索和筛选 -->
|
||||
<div class="search-section">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<a-input-search
|
||||
v-model:value="searchText"
|
||||
placeholder="搜索客户姓名或合同编号"
|
||||
enter-button="搜索"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-select
|
||||
v-model:value="statusFilter"
|
||||
placeholder="解押状态"
|
||||
allow-clear
|
||||
@change="handleFilter"
|
||||
>
|
||||
<a-select-option value="pending">待处理</a-select-option>
|
||||
<a-select-option value="processing">处理中</a-select-option>
|
||||
<a-select-option value="completed">已完成</a-select-option>
|
||||
<a-select-option value="rejected">已拒绝</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-select
|
||||
v-model:value="typeFilter"
|
||||
placeholder="抵押物类型"
|
||||
allow-clear
|
||||
@change="handleFilter"
|
||||
>
|
||||
<a-select-option value="house">房产</a-select-option>
|
||||
<a-select-option value="car">车辆</a-select-option>
|
||||
<a-select-option value="land">土地</a-select-option>
|
||||
<a-select-option value="equipment">设备</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-range-picker
|
||||
v-model:value="dateRange"
|
||||
@change="handleFilter"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-button type="primary" @click="handleCreateRelease">
|
||||
<PlusOutlined />
|
||||
新建解押
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
<div class="search-filter-section">
|
||||
<a-row :gutter="16" align="middle">
|
||||
<a-col :span="4">
|
||||
<a-select
|
||||
v-model:value="searchQuery.field"
|
||||
placeholder="申请单号"
|
||||
style="width: 100%"
|
||||
>
|
||||
<a-select-option value="applicationNumber">申请单号</a-select-option>
|
||||
<a-select-option value="customerName">客户姓名</a-select-option>
|
||||
<a-select-option value="productName">贷款产品</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-input
|
||||
v-model:value="searchQuery.value"
|
||||
placeholder="请输入内容"
|
||||
allow-clear
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
<search-outlined /> 搜索
|
||||
</a-button>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-button @click="handleReset">重置</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 解押列表 -->
|
||||
<div class="table-section">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="filteredReleases"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
row-key="id"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'collateralType'">
|
||||
<a-tag :color="getCollateralTypeColor(record.collateralType)">
|
||||
{{ getCollateralTypeText(record.collateralType) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'loanAmount'">
|
||||
{{ formatAmount(record.loanAmount) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'collateralValue'">
|
||||
{{ formatAmount(record.collateralValue) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="handleView(record)">
|
||||
查看
|
||||
</a-button>
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleProcess(record)"
|
||||
v-if="record.status === 'pending'"
|
||||
>
|
||||
处理
|
||||
</a-button>
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleComplete(record)"
|
||||
v-if="record.status === 'processing'"
|
||||
>
|
||||
完成
|
||||
</a-button>
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleReject(record)"
|
||||
v-if="record.status === 'pending'"
|
||||
danger
|
||||
>
|
||||
拒绝
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<div class="releases-table-section">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="filteredReleases"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
row-key="id"
|
||||
@change="handleTableChange"
|
||||
:expand-row-by-click="false"
|
||||
:expand-icon-column-index="0"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
<template v-else-if="column.key === 'releaseAmount'">
|
||||
{{ formatAmount(record.releaseAmount) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="handleEdit(record)">
|
||||
编辑
|
||||
</a-button>
|
||||
<a-button type="link" size="small" @click="handleView(record)">
|
||||
详情
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
|
||||
<!-- 解押详情模态框 -->
|
||||
@@ -245,14 +192,14 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { PlusOutlined } from '@ant-design/icons-vue'
|
||||
import { SearchOutlined } from '@ant-design/icons-vue'
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const searchText = ref('')
|
||||
const statusFilter = ref(undefined)
|
||||
const typeFilter = ref(undefined)
|
||||
const dateRange = ref([])
|
||||
const searchQuery = ref({
|
||||
field: 'applicationNumber',
|
||||
value: ''
|
||||
})
|
||||
const detailModalVisible = ref(false)
|
||||
const processModalVisible = ref(false)
|
||||
const selectedRelease = ref(null)
|
||||
@@ -269,58 +216,78 @@ const pagination = ref({
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条,共 ${total} 条`
|
||||
showTotal: (total) => `共${total}条`
|
||||
})
|
||||
|
||||
// 表格列配置
|
||||
const columns = [
|
||||
{
|
||||
title: '解押编号',
|
||||
dataIndex: 'releaseNumber',
|
||||
key: 'releaseNumber',
|
||||
width: 150
|
||||
title: '',
|
||||
key: 'expand',
|
||||
width: 50,
|
||||
customRender: () => '>'
|
||||
},
|
||||
{
|
||||
title: '客户姓名',
|
||||
dataIndex: 'customerName',
|
||||
key: 'customerName',
|
||||
title: '申请单号',
|
||||
dataIndex: 'applicationNumber',
|
||||
key: 'applicationNumber',
|
||||
width: 180
|
||||
},
|
||||
{
|
||||
title: '贷款产品',
|
||||
dataIndex: 'productName',
|
||||
key: 'productName',
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
title: '申请养殖户',
|
||||
dataIndex: 'farmerName',
|
||||
key: 'farmerName',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '合同编号',
|
||||
dataIndex: 'contractNumber',
|
||||
key: 'contractNumber',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '抵押物类型',
|
||||
dataIndex: 'collateralType',
|
||||
key: 'collateralType',
|
||||
title: '申请人姓名',
|
||||
dataIndex: 'applicantName',
|
||||
key: 'applicantName',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
title: '申请人身份证号',
|
||||
dataIndex: 'applicantIdNumber',
|
||||
key: 'applicantIdNumber',
|
||||
width: 180
|
||||
},
|
||||
{
|
||||
title: '申请人电话',
|
||||
dataIndex: 'applicantPhone',
|
||||
key: 'applicantPhone',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '生资种类',
|
||||
dataIndex: 'assetType',
|
||||
key: 'assetType',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '贷款金额',
|
||||
dataIndex: 'loanAmount',
|
||||
key: 'loanAmount',
|
||||
width: 120
|
||||
title: '申请解押数量',
|
||||
dataIndex: 'releaseQuantity',
|
||||
key: 'releaseQuantity',
|
||||
width: 150,
|
||||
sorter: true
|
||||
},
|
||||
{
|
||||
title: '抵押物价值',
|
||||
dataIndex: 'collateralValue',
|
||||
key: 'collateralValue',
|
||||
width: 120
|
||||
title: '申请解押额度',
|
||||
dataIndex: 'releaseAmount',
|
||||
key: 'releaseAmount',
|
||||
width: 150,
|
||||
sorter: true
|
||||
},
|
||||
{
|
||||
title: '申请时间',
|
||||
dataIndex: 'applicationTime',
|
||||
key: 'applicationTime',
|
||||
width: 150
|
||||
title: '当前状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
@@ -334,102 +301,180 @@ const columns = [
|
||||
const releases = ref([
|
||||
{
|
||||
id: 1,
|
||||
releaseNumber: 'REL-202401180001',
|
||||
customerName: '张三',
|
||||
contractNumber: 'CON-202401180001',
|
||||
collateralType: 'house',
|
||||
status: 'pending',
|
||||
collateralDescription: '北京市朝阳区某小区3室2厅,建筑面积120平米',
|
||||
loanAmount: 200000,
|
||||
collateralValue: 500000,
|
||||
applicationTime: '2024-01-18 09:30:00',
|
||||
processTime: null,
|
||||
completeTime: null,
|
||||
phone: '13800138000',
|
||||
idCard: '110101199001011234',
|
||||
reason: '贷款已还清,申请解押房产',
|
||||
remark: '',
|
||||
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-01-18 09:30:00',
|
||||
operator: '刘超',
|
||||
time: '2024-02-27 14:55:55',
|
||||
comment: '提交解押申请'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
action: 'complete',
|
||||
operator: '系统',
|
||||
time: '2024-02-27 15:30:00',
|
||||
comment: '解押手续办理完成'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
releaseNumber: 'REL-202401180002',
|
||||
customerName: '李四',
|
||||
contractNumber: 'CON-202401180002',
|
||||
collateralType: 'car',
|
||||
status: 'processing',
|
||||
collateralDescription: '2020年宝马X5,车牌号京A12345',
|
||||
loanAmount: 500000,
|
||||
collateralValue: 600000,
|
||||
applicationTime: '2024-01-17 14:20:00',
|
||||
processTime: '2024-01-18 10:15:00',
|
||||
completeTime: null,
|
||||
phone: '13900139000',
|
||||
idCard: '110101199002021234',
|
||||
reason: '车辆贷款已还清,申请解押车辆',
|
||||
remark: '',
|
||||
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-01-17 14:20:00',
|
||||
operator: '刘超',
|
||||
time: '2024-02-26 11:34:16',
|
||||
comment: '提交解押申请'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
action: 'process',
|
||||
operator: '王经理',
|
||||
time: '2024-01-18 10:15:00',
|
||||
comment: '开始处理解押申请'
|
||||
action: 'complete',
|
||||
operator: '系统',
|
||||
time: '2024-02-26 12:00:00',
|
||||
comment: '解押手续办理完成'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
releaseNumber: 'REL-202401180003',
|
||||
customerName: '王五',
|
||||
contractNumber: 'CON-202401180003',
|
||||
collateralType: 'land',
|
||||
status: 'completed',
|
||||
collateralDescription: '北京市海淀区某地块,面积500平米',
|
||||
loanAmount: 1000000,
|
||||
collateralValue: 2000000,
|
||||
applicationTime: '2024-01-16 16:45:00',
|
||||
processTime: '2024-01-17 09:30:00',
|
||||
completeTime: '2024-01-17 15:20:00',
|
||||
phone: '13700137000',
|
||||
idCard: '110101199003031234',
|
||||
reason: '土地贷款已还清,申请解押土地',
|
||||
remark: '',
|
||||
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-01-16 16:45:00',
|
||||
operator: '张洪彬',
|
||||
time: '2024-02-23 14:05:42',
|
||||
comment: '提交解押申请'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
action: 'process',
|
||||
operator: '赵经理',
|
||||
time: '2024-01-17 09:30:00',
|
||||
comment: '开始处理解押申请'
|
||||
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: 3,
|
||||
id: 2,
|
||||
action: 'complete',
|
||||
operator: '赵经理',
|
||||
time: '2024-01-17 15:20:00',
|
||||
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: '解押手续办理完成'
|
||||
}
|
||||
]
|
||||
@@ -440,19 +485,21 @@ const releases = ref([
|
||||
const filteredReleases = computed(() => {
|
||||
let result = releases.value
|
||||
|
||||
if (searchText.value) {
|
||||
result = result.filter(release =>
|
||||
release.customerName.toLowerCase().includes(searchText.value.toLowerCase()) ||
|
||||
release.contractNumber.toLowerCase().includes(searchText.value.toLowerCase())
|
||||
)
|
||||
}
|
||||
|
||||
if (statusFilter.value) {
|
||||
result = result.filter(release => release.status === statusFilter.value)
|
||||
}
|
||||
|
||||
if (typeFilter.value) {
|
||||
result = result.filter(release => release.collateralType === typeFilter.value)
|
||||
if (searchQuery.value.value) {
|
||||
const searchValue = searchQuery.value.value.toLowerCase()
|
||||
const field = searchQuery.value.field
|
||||
|
||||
result = result.filter(release => {
|
||||
if (field === 'applicationNumber') {
|
||||
return release.applicationNumber.toLowerCase().includes(searchValue)
|
||||
} else if (field === 'customerName') {
|
||||
return release.applicantName.toLowerCase().includes(searchValue) ||
|
||||
release.farmerName.toLowerCase().includes(searchValue)
|
||||
} else if (field === 'productName') {
|
||||
return release.productName.toLowerCase().includes(searchValue)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -463,8 +510,11 @@ const handleSearch = () => {
|
||||
// 搜索逻辑已在计算属性中处理
|
||||
}
|
||||
|
||||
const handleFilter = () => {
|
||||
// 筛选逻辑已在计算属性中处理
|
||||
const handleReset = () => {
|
||||
searchQuery.value = {
|
||||
field: 'applicationNumber',
|
||||
value: ''
|
||||
}
|
||||
}
|
||||
|
||||
const handleTableChange = (pag) => {
|
||||
@@ -472,51 +522,13 @@ const handleTableChange = (pag) => {
|
||||
pagination.value.pageSize = pag.pageSize
|
||||
}
|
||||
|
||||
const handleCreateRelease = () => {
|
||||
message.info('新建解押功能开发中...')
|
||||
}
|
||||
|
||||
const handleView = (record) => {
|
||||
selectedRelease.value = record
|
||||
detailModalVisible.value = true
|
||||
}
|
||||
|
||||
const handleProcess = (record) => {
|
||||
selectedRelease.value = record
|
||||
processForm.value.result = 'approve'
|
||||
processForm.value.comment = ''
|
||||
processForm.value.remark = ''
|
||||
processModalVisible.value = true
|
||||
}
|
||||
|
||||
const handleComplete = (record) => {
|
||||
record.status = 'completed'
|
||||
record.completeTime = new Date().toLocaleString()
|
||||
|
||||
record.history.push({
|
||||
id: Date.now(),
|
||||
action: 'complete',
|
||||
operator: '当前用户',
|
||||
time: new Date().toLocaleString(),
|
||||
comment: '解押手续办理完成'
|
||||
})
|
||||
|
||||
message.success('解押完成')
|
||||
}
|
||||
|
||||
const handleReject = (record) => {
|
||||
record.status = 'rejected'
|
||||
record.processTime = new Date().toLocaleString()
|
||||
|
||||
record.history.push({
|
||||
id: Date.now(),
|
||||
action: 'reject',
|
||||
operator: '当前用户',
|
||||
time: new Date().toLocaleString(),
|
||||
comment: '解押申请被拒绝'
|
||||
})
|
||||
|
||||
message.success('解押申请已拒绝')
|
||||
const handleEdit = (record) => {
|
||||
message.info(`编辑解押申请: ${record.applicationNumber}`)
|
||||
}
|
||||
|
||||
const handleProcessSubmit = () => {
|
||||
@@ -548,6 +560,7 @@ const handleProcessCancel = () => {
|
||||
|
||||
const getStatusColor = (status) => {
|
||||
const colors = {
|
||||
released: 'default',
|
||||
pending: 'orange',
|
||||
processing: 'blue',
|
||||
completed: 'green',
|
||||
@@ -558,6 +571,7 @@ const getStatusColor = (status) => {
|
||||
|
||||
const getStatusText = (status) => {
|
||||
const texts = {
|
||||
released: '已解押',
|
||||
pending: '待处理',
|
||||
processing: '处理中',
|
||||
completed: '已完成',
|
||||
@@ -607,10 +621,7 @@ const getHistoryActionText = (action) => {
|
||||
}
|
||||
|
||||
const formatAmount = (amount) => {
|
||||
if (amount >= 10000) {
|
||||
return (amount / 10000).toFixed(0) + '万'
|
||||
}
|
||||
return amount.toString()
|
||||
return `${amount.toFixed(2)}元`
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
@@ -620,40 +631,43 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.loan-release {
|
||||
.loan-release-page {
|
||||
padding: 24px;
|
||||
background-color: #f0f2f5;
|
||||
min-height: calc(100vh - 134px);
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
background-color: #fff;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
font-size: 28px;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.content {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
.search-filter-section {
|
||||
background-color: #fff;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
border-radius: 8px;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.table-section {
|
||||
margin-top: 16px;
|
||||
.releases-table-section {
|
||||
background-color: #fff;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.release-detail {
|
||||
@@ -706,4 +720,56 @@ onMounted(() => {
|
||||
.process-content {
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.ant-table-thead > tr > th) {
|
||||
background-color: #fafafa;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
:deep(.ant-table-tbody > tr > td) {
|
||||
padding: 12px 8px;
|
||||
}
|
||||
|
||||
:deep(.ant-table-tbody > tr:hover > td) {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 状态标签样式 */
|
||||
:deep(.ant-tag) {
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
padding: 2px 8px;
|
||||
}
|
||||
|
||||
/* 操作按钮样式 */
|
||||
:deep(.ant-btn-link) {
|
||||
padding: 4px 8px;
|
||||
height: auto;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* 分页样式 */
|
||||
:deep(.ant-pagination) {
|
||||
margin-top: 16px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 768px) {
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.search-filter-section .ant-col {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.search-filter-section .ant-col:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
184
bank-frontend/test-add-project.html
Normal file
184
bank-frontend/test-add-project.html
Normal file
@@ -0,0 +1,184 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>新增项目功能测试</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 20px; }
|
||||
.form-group { margin-bottom: 15px; }
|
||||
label { display: block; margin-bottom: 5px; font-weight: bold; }
|
||||
input, select, textarea { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; }
|
||||
button { background: #1890ff; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; }
|
||||
button:hover { background: #40a9ff; }
|
||||
.result { margin-top: 20px; padding: 15px; background: #f5f5f5; border-radius: 4px; }
|
||||
.success { background: #f6ffed; border: 1px solid #b7eb8f; }
|
||||
.error { background: #fff2f0; border: 1px solid #ffccc7; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>新增项目功能测试</h1>
|
||||
|
||||
<div class="form-group">
|
||||
<label>项目名称:</label>
|
||||
<input type="text" id="name" value="测试项目_20241220" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>养殖场名称:</label>
|
||||
<input type="text" id="farmName" value="测试养殖场" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>监管对象:</label>
|
||||
<input type="text" id="supervisionObject" value="牛" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>监管周期:</label>
|
||||
<input type="text" id="supervisionPeriod" value="12个月" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>监管数量:</label>
|
||||
<input type="number" id="supervisionQuantity" value="100" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>监管金额:</label>
|
||||
<input type="number" id="supervisionAmount" value="500000" step="0.01" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>起始时间:</label>
|
||||
<input type="date" id="startTime" value="2024-01-01" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>结束时间:</label>
|
||||
<input type="date" id="endTime" value="2024-12-31" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>耳标数量:</label>
|
||||
<input type="number" id="earTag" value="50" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>项圈数量:</label>
|
||||
<input type="number" id="collar" value="30" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>主机数量:</label>
|
||||
<input type="number" id="host" value="20" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>贷款专员:</label>
|
||||
<input type="text" id="loanOfficer" value="张专员" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>项目描述:</label>
|
||||
<textarea id="description" rows="3">这是一个测试项目</textarea>
|
||||
</div>
|
||||
|
||||
<button onclick="testCreateProject()">测试创建项目</button>
|
||||
|
||||
<div id="result" class="result" style="display: none;"></div>
|
||||
|
||||
<script>
|
||||
let token = '';
|
||||
|
||||
async function testCreateProject() {
|
||||
try {
|
||||
// 1. 先登录
|
||||
console.log('1. 登录获取token...');
|
||||
const loginResponse = await fetch('http://localhost:5351/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username: 'admin',
|
||||
password: 'Admin123456'
|
||||
})
|
||||
});
|
||||
|
||||
const loginData = await loginResponse.json();
|
||||
if (!loginData.success) {
|
||||
throw new Error('登录失败: ' + loginData.message);
|
||||
}
|
||||
|
||||
token = loginData.data.token;
|
||||
console.log('登录成功,token:', token.substring(0, 20) + '...');
|
||||
|
||||
// 2. 创建项目
|
||||
console.log('2. 创建项目...');
|
||||
const projectData = {
|
||||
name: document.getElementById('name').value,
|
||||
status: 'supervision',
|
||||
farmName: document.getElementById('farmName').value,
|
||||
supervisionObject: document.getElementById('supervisionObject').value,
|
||||
supervisionQuantity: parseInt(document.getElementById('supervisionQuantity').value),
|
||||
supervisionPeriod: document.getElementById('supervisionPeriod').value,
|
||||
supervisionAmount: parseFloat(document.getElementById('supervisionAmount').value),
|
||||
startTime: document.getElementById('startTime').value,
|
||||
endTime: document.getElementById('endTime').value,
|
||||
earTag: parseInt(document.getElementById('earTag').value),
|
||||
collar: parseInt(document.getElementById('collar').value),
|
||||
host: parseInt(document.getElementById('host').value),
|
||||
loanOfficer: document.getElementById('loanOfficer').value,
|
||||
description: document.getElementById('description').value
|
||||
};
|
||||
|
||||
console.log('项目数据:', projectData);
|
||||
|
||||
const createResponse = await fetch('http://localhost:5351/api/projects', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(projectData)
|
||||
});
|
||||
|
||||
const createData = await createResponse.json();
|
||||
console.log('创建响应:', createData);
|
||||
|
||||
const resultDiv = document.getElementById('result');
|
||||
resultDiv.style.display = 'block';
|
||||
|
||||
if (createData.success) {
|
||||
resultDiv.className = 'result success';
|
||||
resultDiv.innerHTML = `
|
||||
<h3>✅ 项目创建成功!</h3>
|
||||
<p><strong>项目ID:</strong> ${createData.data.id}</p>
|
||||
<p><strong>项目名称:</strong> ${createData.data.name}</p>
|
||||
<p><strong>养殖场:</strong> ${createData.data.farmName}</p>
|
||||
<p><strong>监管对象:</strong> ${createData.data.supervisionObject}</p>
|
||||
<p><strong>监管数量:</strong> ${createData.data.supervisionQuantity}</p>
|
||||
<p><strong>监管金额:</strong> ${createData.data.supervisionAmount}元</p>
|
||||
<p><strong>创建时间:</strong> ${new Date(createData.data.createdAt).toLocaleString()}</p>
|
||||
`;
|
||||
} else {
|
||||
resultDiv.className = 'result error';
|
||||
resultDiv.innerHTML = `
|
||||
<h3>❌ 项目创建失败</h3>
|
||||
<p><strong>错误信息:</strong> ${createData.message}</p>
|
||||
`;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('测试失败:', error);
|
||||
const resultDiv = document.getElementById('result');
|
||||
resultDiv.style.display = 'block';
|
||||
resultDiv.className = 'result error';
|
||||
resultDiv.innerHTML = `
|
||||
<h3>❌ 测试失败</h3>
|
||||
<p><strong>错误信息:</strong> ${error.message}</p>
|
||||
`;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
81
bank-frontend/test-api.html
Normal file
81
bank-frontend/test-api.html
Normal file
@@ -0,0 +1,81 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>API测试</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>API测试页面</h1>
|
||||
<button onclick="testLogin()">测试登录</button>
|
||||
<button onclick="testProjects()">测试项目接口</button>
|
||||
<div id="result"></div>
|
||||
|
||||
<script>
|
||||
let token = '';
|
||||
|
||||
async function testLogin() {
|
||||
try {
|
||||
const response = await fetch('http://localhost:5351/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username: 'admin',
|
||||
password: 'Admin123456'
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
console.log('登录响应:', data);
|
||||
|
||||
if (data.success) {
|
||||
token = data.data.token;
|
||||
document.getElementById('result').innerHTML =
|
||||
'<p style="color: green;">登录成功!Token: ' + token.substring(0, 20) + '...</p>';
|
||||
} else {
|
||||
document.getElementById('result').innerHTML =
|
||||
'<p style="color: red;">登录失败: ' + data.message + '</p>';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('登录错误:', error);
|
||||
document.getElementById('result').innerHTML =
|
||||
'<p style="color: red;">登录错误: ' + error.message + '</p>';
|
||||
}
|
||||
}
|
||||
|
||||
async function testProjects() {
|
||||
if (!token) {
|
||||
document.getElementById('result').innerHTML =
|
||||
'<p style="color: red;">请先登录!</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('http://localhost:5351/api/projects?page=1&limit=12&search=&status=', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
console.log('项目接口响应:', data);
|
||||
|
||||
if (data.success) {
|
||||
document.getElementById('result').innerHTML =
|
||||
'<p style="color: green;">项目接口成功!项目数量: ' + data.data.projects.length + '</p>' +
|
||||
'<pre>' + JSON.stringify(data.data.projects.slice(0, 2), null, 2) + '</pre>';
|
||||
} else {
|
||||
document.getElementById('result').innerHTML =
|
||||
'<p style="color: red;">项目接口失败: ' + data.message + '</p>';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('项目接口错误:', error);
|
||||
document.getElementById('result').innerHTML =
|
||||
'<p style="color: red;">项目接口错误: ' + error.message + '</p>';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
206
bank-frontend/test-supervision-tasks.html
Normal file
206
bank-frontend/test-supervision-tasks.html
Normal file
@@ -0,0 +1,206 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>监管任务API测试</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 20px; }
|
||||
.test-section { margin-bottom: 20px; padding: 15px; border: 1px solid #ddd; border-radius: 4px; }
|
||||
button { background: #1890ff; color: white; padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; margin: 5px; }
|
||||
button:hover { background: #40a9ff; }
|
||||
.result { margin-top: 10px; padding: 10px; background: #f5f5f5; border-radius: 4px; white-space: pre-wrap; font-family: monospace; font-size: 12px; }
|
||||
.success { background: #f6ffed; border: 1px solid #b7eb8f; }
|
||||
.error { background: #fff2f0; border: 1px solid #ffccc7; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>监管任务API测试页面</h1>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>1. 登录测试</h2>
|
||||
<button onclick="testLogin()">测试登录</button>
|
||||
<div id="loginResult" class="result" style="display: none;"></div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>2. 获取监管任务列表</h2>
|
||||
<button onclick="testGetTasks()" id="getTasksBtn" disabled>获取监管任务列表</button>
|
||||
<div id="tasksResult" class="result" style="display: none;"></div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>3. 获取监管任务统计</h2>
|
||||
<button onclick="testGetStats()" id="getStatsBtn" disabled>获取监管任务统计</button>
|
||||
<div id="statsResult" class="result" style="display: none;"></div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>4. 创建监管任务</h2>
|
||||
<button onclick="testCreateTask()" id="createTaskBtn" disabled>创建监管任务</button>
|
||||
<div id="createResult" class="result" style="display: none;"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let token = '';
|
||||
|
||||
async function testLogin() {
|
||||
try {
|
||||
const response = await fetch('http://localhost:5351/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username: 'admin',
|
||||
password: 'Admin123456'
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
const resultDiv = document.getElementById('loginResult');
|
||||
resultDiv.style.display = 'block';
|
||||
|
||||
if (data.success) {
|
||||
token = data.data.token;
|
||||
resultDiv.className = 'result success';
|
||||
resultDiv.textContent = `登录成功!\nToken: ${token.substring(0, 50)}...\n用户: ${data.data.user.username}`;
|
||||
|
||||
// 启用其他按钮
|
||||
document.getElementById('getTasksBtn').disabled = false;
|
||||
document.getElementById('getStatsBtn').disabled = false;
|
||||
document.getElementById('createTaskBtn').disabled = false;
|
||||
} else {
|
||||
resultDiv.className = 'result error';
|
||||
resultDiv.textContent = `登录失败: ${data.message}`;
|
||||
}
|
||||
} catch (error) {
|
||||
const resultDiv = document.getElementById('loginResult');
|
||||
resultDiv.style.display = 'block';
|
||||
resultDiv.className = 'result error';
|
||||
resultDiv.textContent = `登录错误: ${error.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
async function testGetTasks() {
|
||||
if (!token) {
|
||||
alert('请先登录!');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('http://localhost:5351/api/supervision-tasks?page=1&limit=10', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
const resultDiv = document.getElementById('tasksResult');
|
||||
resultDiv.style.display = 'block';
|
||||
|
||||
if (data.success) {
|
||||
resultDiv.className = 'result success';
|
||||
resultDiv.textContent = `获取监管任务列表成功!\n任务数量: ${data.data.tasks.length}\n总数量: ${data.data.pagination.total}\n\n前3个任务:\n${JSON.stringify(data.data.tasks.slice(0, 3), null, 2)}`;
|
||||
} else {
|
||||
resultDiv.className = 'result error';
|
||||
resultDiv.textContent = `获取失败: ${data.message}`;
|
||||
}
|
||||
} catch (error) {
|
||||
const resultDiv = document.getElementById('tasksResult');
|
||||
resultDiv.style.display = 'block';
|
||||
resultDiv.className = 'result error';
|
||||
resultDiv.textContent = `请求错误: ${error.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
async function testGetStats() {
|
||||
if (!token) {
|
||||
alert('请先登录!');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('http://localhost:5351/api/supervision-tasks/stats', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
const resultDiv = document.getElementById('statsResult');
|
||||
resultDiv.style.display = 'block';
|
||||
|
||||
if (data.success) {
|
||||
resultDiv.className = 'result success';
|
||||
resultDiv.textContent = `获取监管任务统计成功!\n总计: ${data.data.total}\n待监管: ${data.data.pending}\n监管中: ${data.data.supervising}\n已完成: ${data.data.completed}\n已暂停: ${data.data.suspended}`;
|
||||
} else {
|
||||
resultDiv.className = 'result error';
|
||||
resultDiv.textContent = `获取失败: ${data.message}`;
|
||||
}
|
||||
} catch (error) {
|
||||
const resultDiv = document.getElementById('statsResult');
|
||||
resultDiv.style.display = 'block';
|
||||
resultDiv.className = 'result error';
|
||||
resultDiv.textContent = `请求错误: ${error.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
async function testCreateTask() {
|
||||
if (!token) {
|
||||
alert('请先登录!');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
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 response = await fetch('http://localhost:5351/api/supervision-tasks', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(newTask)
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
const resultDiv = document.getElementById('createResult');
|
||||
resultDiv.style.display = 'block';
|
||||
|
||||
if (data.success) {
|
||||
resultDiv.className = 'result success';
|
||||
resultDiv.textContent = `创建监管任务成功!\n任务ID: ${data.data.id}\n申请单号: ${data.data.applicationNumber}\n客户姓名: ${data.data.customerName}\n监管状态: ${data.data.supervisionStatus}`;
|
||||
} else {
|
||||
resultDiv.className = 'result error';
|
||||
resultDiv.textContent = `创建失败: ${data.message}`;
|
||||
}
|
||||
} catch (error) {
|
||||
const resultDiv = document.getElementById('createResult');
|
||||
resultDiv.style.display = 'block';
|
||||
resultDiv.className = 'result error';
|
||||
resultDiv.textContent = `请求错误: ${error.message}`;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -6,13 +6,7 @@
|
||||
<h2 v-if="!collapsed">政府管理系统</h2>
|
||||
<h2 v-else>政府</h2>
|
||||
</div>
|
||||
<a-menu
|
||||
v-model:selectedKeys="selectedKeys"
|
||||
theme="dark"
|
||||
mode="inline"
|
||||
:items="menus"
|
||||
@click="handleMenuClick"
|
||||
/>
|
||||
<Sidebar />
|
||||
</a-layout-sider>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
@@ -70,288 +64,46 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, h } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import {
|
||||
MenuUnfoldOutlined,
|
||||
MenuFoldOutlined,
|
||||
UserOutlined,
|
||||
DownOutlined,
|
||||
LogoutOutlined,
|
||||
DashboardOutlined,
|
||||
UserAddOutlined,
|
||||
EyeOutlined,
|
||||
CheckCircleOutlined,
|
||||
LineChartOutlined,
|
||||
FileOutlined,
|
||||
TeamOutlined,
|
||||
SettingOutlined,
|
||||
MedicineBoxOutlined,
|
||||
ShoppingOutlined,
|
||||
FolderOutlined,
|
||||
BarChartOutlined,
|
||||
PieChartOutlined,
|
||||
ShoppingCartOutlined,
|
||||
FileTextOutlined,
|
||||
DatabaseOutlined,
|
||||
HomeOutlined,
|
||||
ShopOutlined,
|
||||
MessageOutlined,
|
||||
BookOutlined,
|
||||
VideoCameraOutlined,
|
||||
EnvironmentOutlined
|
||||
LogoutOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import Sidebar from '@/layout/Sidebar.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const collapsed = ref(false)
|
||||
const selectedKeys = ref([route.name])
|
||||
const menus = ref([])
|
||||
|
||||
// 图标映射
|
||||
const iconMap = {
|
||||
DashboardOutlined: () => h(DashboardOutlined),
|
||||
UserAddOutlined: () => h(UserAddOutlined),
|
||||
EyeOutlined: () => h(EyeOutlined),
|
||||
CheckCircleOutlined: () => h(CheckCircleOutlined),
|
||||
LineChartOutlined: () => h(LineChartOutlined),
|
||||
FileOutlined: () => h(FileOutlined),
|
||||
TeamOutlined: () => h(TeamOutlined),
|
||||
SettingOutlined: () => h(SettingOutlined),
|
||||
MedicineBoxOutlined: () => h(MedicineBoxOutlined),
|
||||
ShoppingOutlined: () => h(ShoppingOutlined),
|
||||
FolderOutlined: () => h(FolderOutlined),
|
||||
BarChartOutlined: () => h(BarChartOutlined),
|
||||
PieChartOutlined: () => h(PieChartOutlined),
|
||||
ShoppingCartOutlined: () => h(ShoppingCartOutlined),
|
||||
FileTextOutlined: () => h(FileTextOutlined),
|
||||
DatabaseOutlined: () => h(DatabaseOutlined),
|
||||
HomeOutlined: () => h(HomeOutlined),
|
||||
ShopOutlined: () => h(ShopOutlined),
|
||||
MessageOutlined: () => h(MessageOutlined),
|
||||
BookOutlined: () => h(BookOutlined),
|
||||
VideoCameraOutlined: () => h(VideoCameraOutlined),
|
||||
ShopOutlined: () => h(ShopOutlined),
|
||||
EnvironmentOutlined: () => h(EnvironmentOutlined)
|
||||
};
|
||||
|
||||
// 格式化菜单数据
|
||||
const formatMenuItems = (menuList) => {
|
||||
return menuList.map(menu => {
|
||||
const menuItem = {
|
||||
key: menu.key,
|
||||
label: menu.label,
|
||||
path: menu.path
|
||||
};
|
||||
|
||||
// 添加图标
|
||||
if (menu.icon && iconMap[menu.icon]) {
|
||||
menuItem.icon = iconMap[menu.icon];
|
||||
}
|
||||
|
||||
// 添加子菜单
|
||||
if (menu.children && menu.children.length > 0) {
|
||||
menuItem.children = formatMenuItems(menu.children);
|
||||
}
|
||||
|
||||
return menuItem;
|
||||
});
|
||||
};
|
||||
|
||||
// 获取菜单数据
|
||||
const fetchMenus = async () => {
|
||||
try {
|
||||
// 这里可以根据实际情况从API获取菜单数据
|
||||
// 由于没有实际的API,这里提供默认菜单作为备用
|
||||
menus.value = [
|
||||
{
|
||||
key: 'DataCenter',
|
||||
icon: 'DatabaseOutlined',
|
||||
label: '数据览仓',
|
||||
path: '/index/data_center'
|
||||
},
|
||||
{
|
||||
key: 'MarketPrice',
|
||||
icon: 'BarChartOutlined',
|
||||
label: '市场行情',
|
||||
path: '/price/price_list'
|
||||
},
|
||||
{
|
||||
key: 'PersonnelManagement',
|
||||
icon: 'TeamOutlined',
|
||||
label: '人员管理',
|
||||
path: '/personnel'
|
||||
},
|
||||
{
|
||||
key: 'FarmerManagement',
|
||||
icon: 'UserAddOutlined',
|
||||
label: '养殖户管理',
|
||||
path: '/farmer'
|
||||
},
|
||||
{
|
||||
key: 'SmartWarehouse',
|
||||
icon: 'FolderOutlined',
|
||||
label: '智能仓库',
|
||||
path: '/smart-warehouse'
|
||||
},
|
||||
{
|
||||
key: 'BreedImprovement',
|
||||
icon: 'SettingOutlined',
|
||||
label: '品种改良管理',
|
||||
path: '/breed-improvement'
|
||||
},
|
||||
{
|
||||
key: 'PaperlessService',
|
||||
icon: 'FileTextOutlined',
|
||||
label: '无纸化服务',
|
||||
path: '/paperless'
|
||||
},
|
||||
{
|
||||
key: 'SlaughterHarmless',
|
||||
icon: 'EnvironmentOutlined',
|
||||
label: '屠宰无害化',
|
||||
path: '/slaughter'
|
||||
},
|
||||
{
|
||||
key: 'FinanceInsurance',
|
||||
icon: 'ShoppingOutlined',
|
||||
label: '金融保险',
|
||||
path: '/finance'
|
||||
},
|
||||
{
|
||||
key: 'ProductCertification',
|
||||
icon: 'CheckCircleOutlined',
|
||||
label: '生资认证',
|
||||
path: '/examine/index'
|
||||
},
|
||||
{
|
||||
key: 'ProductTrade',
|
||||
icon: 'ShoppingCartOutlined',
|
||||
label: '生资交易',
|
||||
path: '/shengzijiaoyi'
|
||||
},
|
||||
{
|
||||
key: 'CommunicationCommunity',
|
||||
icon: 'MessageOutlined',
|
||||
label: '交流社区',
|
||||
path: '/community'
|
||||
},
|
||||
{
|
||||
key: 'OnlineConsultation',
|
||||
icon: 'EyeOutlined',
|
||||
label: '线上问诊',
|
||||
path: '/consultation'
|
||||
},
|
||||
{
|
||||
key: 'CattleAcademy',
|
||||
icon: 'BookOutlined',
|
||||
label: '养牛学院',
|
||||
path: '/academy'
|
||||
},
|
||||
{
|
||||
key: 'MessageNotification',
|
||||
icon: 'VideoCameraOutlined',
|
||||
label: '消息通知',
|
||||
path: '/notification'
|
||||
},
|
||||
{
|
||||
key: 'UserManagement',
|
||||
icon: 'UserAddOutlined',
|
||||
label: '用户管理',
|
||||
path: '/users'
|
||||
},
|
||||
{
|
||||
key: 'WarehouseManagement',
|
||||
icon: 'MedicineBoxOutlined',
|
||||
label: '仓库管理',
|
||||
path: '/warehouse'
|
||||
},
|
||||
{
|
||||
key: 'FileManagement',
|
||||
icon: 'FolderOutlined',
|
||||
label: '文件管理',
|
||||
path: '/files'
|
||||
},
|
||||
{
|
||||
key: 'ServiceManagement',
|
||||
icon: 'SettingOutlined',
|
||||
label: '服务管理',
|
||||
path: '/service'
|
||||
},
|
||||
{
|
||||
key: 'ApprovalProcess',
|
||||
icon: 'CheckCircleOutlined',
|
||||
label: '审批流程',
|
||||
path: '/approval'
|
||||
},
|
||||
{
|
||||
key: 'EpidemicManagement',
|
||||
icon: 'LineChartOutlined',
|
||||
label: '防疫管理',
|
||||
path: '/epidemic'
|
||||
},
|
||||
{
|
||||
key: 'SupervisionDashboard',
|
||||
icon: 'EyeOutlined',
|
||||
label: '监管大屏',
|
||||
path: '/supervision'
|
||||
},
|
||||
{
|
||||
key: 'VisualAnalysis',
|
||||
icon: 'PieChartOutlined',
|
||||
label: '数据分析',
|
||||
path: '/visualization'
|
||||
},
|
||||
{
|
||||
key: 'LogManagement',
|
||||
icon: 'FileOutlined',
|
||||
label: '日志管理',
|
||||
path: '/log'
|
||||
}
|
||||
];
|
||||
|
||||
// 应用图标映射
|
||||
menus.value = formatMenuItems(menus.value);
|
||||
} catch (error) {
|
||||
console.error('获取菜单失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 菜单点击处理
|
||||
const handleMenuClick = (e) => {
|
||||
const menuItem = menus.value.find(item => item.key === e.key);
|
||||
if (menuItem && menuItem.path) {
|
||||
router.push(menuItem.path);
|
||||
}
|
||||
};
|
||||
|
||||
// 退出登录处理
|
||||
// 退出登录
|
||||
const handleLogout = () => {
|
||||
userStore.logout();
|
||||
router.push('/login');
|
||||
};
|
||||
|
||||
// 组件挂载时获取菜单
|
||||
onMounted(() => {
|
||||
fetchMenus();
|
||||
});
|
||||
userStore.logout()
|
||||
router.push('/login')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.logo {
|
||||
height: 32px;
|
||||
margin: 16px;
|
||||
color: white;
|
||||
height: 64px;
|
||||
line-height: 64px;
|
||||
background: #002140;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.logo h2 {
|
||||
color: white;
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.trigger {
|
||||
font-size: 18px;
|
||||
line-height: 64px;
|
||||
padding: 0 24px;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
@@ -359,4 +111,11 @@ onMounted(() => {
|
||||
.trigger:hover {
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.ant-dropdown-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
@@ -4,198 +4,148 @@
|
||||
v-model:openKeys="openKeys"
|
||||
mode="inline"
|
||||
theme="dark"
|
||||
:root-sub-menu-open-close="false"
|
||||
@select="handleMenuSelect"
|
||||
@openChange="handleOpenChange"
|
||||
class="sidebar-menu"
|
||||
>
|
||||
<!-- 首页 -->
|
||||
<a-menu-item key="/" :icon="DashboardOutlined">
|
||||
<span>首页</span>
|
||||
<!-- 数据览仓 -->
|
||||
<a-menu-item key="/index/data_center">
|
||||
<template #icon><BarChartOutlined /></template>
|
||||
<span>数据览仓</span>
|
||||
</a-menu-item>
|
||||
|
||||
<!-- 监管实体管理 -->
|
||||
<a-sub-menu
|
||||
v-if="hasPermission('supervision')"
|
||||
key="supervision"
|
||||
:icon="FileOutlined"
|
||||
>
|
||||
<template #title>
|
||||
<span>监管实体管理</span>
|
||||
</template>
|
||||
<a-menu-item key="/supervision/list">
|
||||
<span>实体列表</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="/supervision/add">
|
||||
<span>新增实体</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="/supervision/stats">
|
||||
<span>统计分析</span>
|
||||
</a-menu-item>
|
||||
</a-sub-menu>
|
||||
|
||||
<!-- 检查记录 -->
|
||||
<a-sub-menu
|
||||
v-if="hasPermission('inspection')"
|
||||
key="inspection"
|
||||
:icon="CheckCircleOutlined"
|
||||
>
|
||||
<template #title>
|
||||
<span>检查记录</span>
|
||||
</template>
|
||||
<a-menu-item key="/inspection/list">
|
||||
<span>记录列表</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="/inspection/add">
|
||||
<span>新增记录</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="/inspection/stats">
|
||||
<span>统计分析</span>
|
||||
</a-menu-item>
|
||||
</a-sub-menu>
|
||||
|
||||
<!-- 违规处理 -->
|
||||
<a-sub-menu
|
||||
v-if="hasPermission('violation')"
|
||||
key="violation"
|
||||
:icon="ExclamationCircleOutlined"
|
||||
>
|
||||
<template #title>
|
||||
<span>违规处理</span>
|
||||
</template>
|
||||
<a-menu-item key="/violation/list">
|
||||
<span>违规记录</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="/violation/add">
|
||||
<span>新增违规</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="/violation/process">
|
||||
<span>处理流程</span>
|
||||
</a-menu-item>
|
||||
</a-sub-menu>
|
||||
|
||||
<!-- 防疫管理 -->
|
||||
<a-sub-menu
|
||||
v-if="hasPermission('epidemic')"
|
||||
key="epidemic"
|
||||
:icon="AlertOutlined"
|
||||
>
|
||||
<template #title>
|
||||
<span>防疫管理</span>
|
||||
</template>
|
||||
<a-menu-item key="/epidemic/list">
|
||||
<span>防疫记录</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="/epidemic/add">
|
||||
<span>新增记录</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="/epidemic/stats">
|
||||
<span>疫情统计</span>
|
||||
</a-menu-item>
|
||||
</a-sub-menu>
|
||||
|
||||
<!-- 审批管理 -->
|
||||
<a-sub-menu
|
||||
v-if="hasPermission('approval')"
|
||||
key="approval"
|
||||
:icon="FormOutlined"
|
||||
>
|
||||
<template #title>
|
||||
<span>审批管理</span>
|
||||
</template>
|
||||
<a-menu-item key="/approval/list">
|
||||
<span>审批列表</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="/approval/pending">
|
||||
<span>待我审批</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="/approval/history">
|
||||
<span>审批历史</span>
|
||||
</a-menu-item>
|
||||
</a-sub-menu>
|
||||
<!-- 市场行情 -->
|
||||
<a-menu-item key="/price/price_list">
|
||||
<template #icon><LineChartOutlined /></template>
|
||||
<span>市场行情</span>
|
||||
</a-menu-item>
|
||||
|
||||
<!-- 人员管理 -->
|
||||
<a-sub-menu
|
||||
v-if="hasPermission('personnel')"
|
||||
key="personnel"
|
||||
:icon="UserOutlined"
|
||||
>
|
||||
<a-sub-menu key="personnel">
|
||||
<template #icon><UserOutlined /></template>
|
||||
<template #title>
|
||||
<span>人员管理</span>
|
||||
</template>
|
||||
<a-menu-item key="/personnel/list">
|
||||
<span>人员列表</span>
|
||||
<a-menu-item key="/personnel/department">
|
||||
<span>行政部门</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="/personnel/add">
|
||||
<span>新增人员</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="/personnel/role">
|
||||
<span>角色管理</span>
|
||||
<a-menu-item key="/personnel/staff">
|
||||
<span>行政人员</span>
|
||||
</a-menu-item>
|
||||
</a-sub-menu>
|
||||
|
||||
<!-- 系统设置 -->
|
||||
<a-sub-menu
|
||||
v-if="hasPermission('system')"
|
||||
key="system"
|
||||
:icon="SettingOutlined"
|
||||
>
|
||||
<!-- 养殖户管理 -->
|
||||
<a-menu-item key="/farmer">
|
||||
<template #icon><TeamOutlined /></template>
|
||||
<span>养殖户管理</span>
|
||||
</a-menu-item>
|
||||
|
||||
<!-- 智慧仓库 -->
|
||||
<a-sub-menu key="smart-warehouse">
|
||||
<template #icon><HddOutlined /></template>
|
||||
<template #title>
|
||||
<span>系统设置</span>
|
||||
<span>智慧仓库</span>
|
||||
</template>
|
||||
<a-menu-item key="/system/config">
|
||||
<span>系统配置</span>
|
||||
<a-menu-item key="/smart-warehouse/smart-collar">
|
||||
<span>智能项圈</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="/system/logs">
|
||||
<span>系统日志</span>
|
||||
<a-menu-item key="/smart-warehouse/smart-earmark">
|
||||
<span>智能耳标</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="/system/backup">
|
||||
<span>数据备份</span>
|
||||
<a-menu-item key="/smart-warehouse/smart-host">
|
||||
<span>智能主机</span>
|
||||
</a-menu-item>
|
||||
</a-sub-menu>
|
||||
|
||||
<!-- 帮助中心 -->
|
||||
<a-menu-item key="/help" :icon="QuestionCircleOutlined">
|
||||
<span>帮助中心</span>
|
||||
<!-- 无纸化服务 -->
|
||||
<a-menu-item key="/paperless">
|
||||
<template #icon><FileTextOutlined /></template>
|
||||
<span>无纸化服务</span>
|
||||
</a-menu-item>
|
||||
|
||||
<!-- 屠宰无害化 -->
|
||||
<a-menu-item key="/slaughter">
|
||||
<template #icon><SafetyOutlined /></template>
|
||||
<span>屠宰无害化</span>
|
||||
</a-menu-item>
|
||||
|
||||
<!-- 生资认证 -->
|
||||
<a-menu-item key="/examine/index">
|
||||
<template #icon><CheckCircleOutlined /></template>
|
||||
<span>生资认证</span>
|
||||
</a-menu-item>
|
||||
|
||||
<!-- 线上问诊 -->
|
||||
<a-menu-item key="/consultation">
|
||||
<template #icon><MedicineBoxOutlined /></template>
|
||||
<span>线上问诊</span>
|
||||
</a-menu-item>
|
||||
|
||||
<!-- 养牛学院 -->
|
||||
<a-menu-item key="/academy">
|
||||
<template #icon><ReadOutlined /></template>
|
||||
<span>养牛学院</span>
|
||||
</a-menu-item>
|
||||
|
||||
<!-- 消息通知 -->
|
||||
<a-menu-item key="/notification">
|
||||
<template #icon><BellOutlined /></template>
|
||||
<span>消息通知</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
import { ref, onMounted, watch, h } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import {
|
||||
DashboardOutlined,
|
||||
FileOutlined,
|
||||
CheckCircleOutlined,
|
||||
ExclamationCircleOutlined,
|
||||
AlertOutlined,
|
||||
FormOutlined,
|
||||
BarChartOutlined,
|
||||
LineChartOutlined,
|
||||
UserOutlined,
|
||||
SettingOutlined,
|
||||
QuestionCircleOutlined
|
||||
TeamOutlined,
|
||||
HddOutlined,
|
||||
ExperimentOutlined,
|
||||
FileTextOutlined,
|
||||
SafetyOutlined,
|
||||
BankOutlined,
|
||||
CheckCircleOutlined,
|
||||
ShoppingOutlined,
|
||||
CommentOutlined,
|
||||
MedicineBoxOutlined,
|
||||
ReadOutlined,
|
||||
BellOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const authStore = useAuthStore()
|
||||
export default {
|
||||
setup() {
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
// 当前选中的菜单项
|
||||
const selectedKeys = ref([])
|
||||
// 当前选中的菜单项
|
||||
const selectedKeys = ref([])
|
||||
|
||||
// 展开的菜单项
|
||||
const openKeys = ref([])
|
||||
// 展开的菜单项
|
||||
const openKeys = ref([])
|
||||
|
||||
// 检查是否有权限
|
||||
const hasPermission = (permission) => {
|
||||
return authStore.hasPermission([permission])
|
||||
}
|
||||
// 检查是否有权限
|
||||
const hasPermission = (permission) => {
|
||||
return authStore.hasPermission([permission])
|
||||
}
|
||||
|
||||
// 处理菜单选择
|
||||
const handleMenuSelect = ({ key }) => {
|
||||
router.push(key)
|
||||
}
|
||||
// 处理菜单选择
|
||||
const handleMenuSelect = ({ key }) => {
|
||||
// 确保路由存在
|
||||
if (key) {
|
||||
console.log('导航到路由:', key)
|
||||
// 使用replace而不是push,避免历史记录堆积
|
||||
router.replace(key).catch(err => {
|
||||
console.error('路由导航失败:', err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 处理展开/收起
|
||||
const handleOpenChange = (keys) => {
|
||||
@@ -211,7 +161,8 @@ const getParentMenuKey = (path) => {
|
||||
'/epidemic': 'epidemic',
|
||||
'/approval': 'approval',
|
||||
'/personnel': 'personnel',
|
||||
'/system': 'system'
|
||||
'/system': 'system',
|
||||
'/smart-warehouse': 'smart-warehouse'
|
||||
}
|
||||
|
||||
for (const [prefix, key] of Object.entries(menuMap)) {
|
||||
@@ -219,6 +170,12 @@ const getParentMenuKey = (path) => {
|
||||
return key
|
||||
}
|
||||
}
|
||||
|
||||
// 特殊处理智慧仓库路径
|
||||
if (path.includes('smart-warehouse')) {
|
||||
return 'smart-warehouse'
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
@@ -243,10 +200,37 @@ watch(
|
||||
}
|
||||
)
|
||||
|
||||
// 返回需要在模板中使用的内容
|
||||
return {
|
||||
hasPermission,
|
||||
handleMenuSelect,
|
||||
handleOpenChange,
|
||||
selectedKeys,
|
||||
openKeys,
|
||||
DashboardOutlined: () => h(DashboardOutlined),
|
||||
BarChartOutlined: () => h(BarChartOutlined),
|
||||
LineChartOutlined: () => h(LineChartOutlined),
|
||||
UserOutlined: () => h(UserOutlined),
|
||||
TeamOutlined: () => h(TeamOutlined),
|
||||
HddOutlined: () => h(HddOutlined),
|
||||
ExperimentOutlined: () => h(ExperimentOutlined),
|
||||
FileTextOutlined: () => h(FileTextOutlined),
|
||||
SafetyOutlined: () => h(SafetyOutlined),
|
||||
BankOutlined: () => h(BankOutlined),
|
||||
CheckCircleOutlined: () => h(CheckCircleOutlined),
|
||||
ShoppingOutlined: () => h(ShoppingOutlined),
|
||||
CommentOutlined: () => h(CommentOutlined),
|
||||
MedicineBoxOutlined: () => h(MedicineBoxOutlined),
|
||||
ReadOutlined: () => h(ReadOutlined),
|
||||
BellOutlined: () => h(BellOutlined)
|
||||
}
|
||||
|
||||
// 组件挂载时初始化
|
||||
onMounted(() => {
|
||||
updateSelectedState()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import store from './stores'
|
||||
import { createPinia } from 'pinia'
|
||||
import Antd from 'ant-design-vue'
|
||||
import { ConfigProvider } from 'ant-design-vue'
|
||||
import 'ant-design-vue/dist/reset.css'
|
||||
import dayjs from 'dayjs'
|
||||
import './mock' // 导入mock服务
|
||||
@@ -21,9 +22,25 @@ globalThis.dayjs = dayjs
|
||||
|
||||
// 创建应用实例
|
||||
const app = createApp(App)
|
||||
// 创建 Pinia 实例
|
||||
const pinia = createPinia()
|
||||
|
||||
app.use(router)
|
||||
app.use(store)
|
||||
app.use(pinia)
|
||||
app.use(Antd)
|
||||
|
||||
// 配置 Ant Design Vue
|
||||
app.use(ConfigProvider, {
|
||||
locale: zhCN,
|
||||
// 明确配置日期库为dayjs
|
||||
dateFormatter: 'dayjs',
|
||||
// 提供完整配置的dayjs实例,确保在组件中能正确访问到配置好的dayjs
|
||||
getDayjsInstance: () => {
|
||||
// 确保返回一个已经正确配置了语言和插件的dayjs实例
|
||||
return dayjs
|
||||
},
|
||||
// 安全地获取弹出层容器,防止trigger为null导致的错误
|
||||
getPopupContainer: (trigger) => trigger?.parentElement || document.body
|
||||
})
|
||||
|
||||
app.mount('#app')
|
||||
@@ -2,16 +2,16 @@ import { createRouter, createWebHistory } from 'vue-router'
|
||||
import Layout from '@/components/Layout.vue'
|
||||
import Login from '@/views/Login.vue'
|
||||
import Dashboard from '@/views/Dashboard.vue'
|
||||
import UserManagement from '@/views/UserManagement.vue'
|
||||
import SupervisionDashboard from '@/views/SupervisionDashboard.vue'
|
||||
// import UserManagement from '@/views/UserManagement.vue'
|
||||
// import SupervisionDashboard from '@/views/SupervisionDashboard.vue'
|
||||
import ApprovalProcess from '@/views/ApprovalProcess.vue'
|
||||
import EpidemicManagement from '@/views/EpidemicManagement.vue'
|
||||
import VisualAnalysis from '@/views/VisualAnalysis.vue'
|
||||
import FileManagement from '@/views/FileManagement.vue'
|
||||
// import EpidemicManagement from '@/views/EpidemicManagement.vue'
|
||||
// import VisualAnalysis from '@/views/VisualAnalysis.vue'
|
||||
// import FileManagement from '@/views/FileManagement.vue'
|
||||
import PersonnelManagement from '@/views/PersonnelManagement.vue'
|
||||
import ServiceManagement from '@/views/ServiceManagement.vue'
|
||||
import WarehouseManagement from '@/views/WarehouseManagement.vue'
|
||||
import LogManagement from '@/views/LogManagement.vue'
|
||||
// import WarehouseManagement from '@/views/WarehouseManagement.vue'
|
||||
// import LogManagement from '@/views/LogManagement.vue'
|
||||
// 新增页面组件
|
||||
import MarketPrice from '@/views/MarketPrice.vue'
|
||||
import DataCenter from '@/views/DataCenter.vue'
|
||||
@@ -25,6 +25,8 @@ import CommunicationCommunity from '@/views/CommunicationCommunity.vue'
|
||||
import OnlineConsultation from '@/views/OnlineConsultation.vue'
|
||||
import CattleAcademy from '@/views/CattleAcademy.vue'
|
||||
import MessageNotification from '@/views/MessageNotification.vue'
|
||||
import AdminDepartment from '@/views/AdminDepartment.vue'
|
||||
import AdminStaff from '@/views/AdminStaff.vue'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
@@ -59,7 +61,21 @@ const routes = [
|
||||
path: 'personnel',
|
||||
name: 'PersonnelManagement',
|
||||
component: PersonnelManagement,
|
||||
meta: { title: '人员管理' }
|
||||
meta: { title: '人员管理' },
|
||||
children: [
|
||||
{
|
||||
path: 'department',
|
||||
name: 'AdminDepartment',
|
||||
component: AdminDepartment,
|
||||
meta: { title: '行政部门' }
|
||||
},
|
||||
{
|
||||
path: 'staff',
|
||||
name: 'AdminStaff',
|
||||
component: AdminStaff,
|
||||
meta: { title: '行政人员' }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'farmer',
|
||||
@@ -71,7 +87,27 @@ const routes = [
|
||||
path: 'smart-warehouse',
|
||||
name: 'SmartWarehouse',
|
||||
component: SmartWarehouse,
|
||||
meta: { title: '智能仓库' }
|
||||
meta: { title: '智慧仓库' },
|
||||
children: [
|
||||
{
|
||||
path: 'smart-collar',
|
||||
name: 'SmartCollar',
|
||||
component: () => import('@/views/smart-warehouse/smart-hardware/SmartCollar.vue'),
|
||||
meta: { title: '智能项圈' }
|
||||
},
|
||||
{
|
||||
path: 'smart-earmark',
|
||||
name: 'SmartEarmark',
|
||||
component: () => import('@/views/smart-warehouse/smart-hardware/SmartEarmark.vue'),
|
||||
meta: { title: '智能耳标' }
|
||||
},
|
||||
{
|
||||
path: 'smart-host',
|
||||
name: 'SmartHost',
|
||||
component: () => import('@/views/smart-warehouse/smart-hardware/SmartHost.vue'),
|
||||
meta: { title: '智能主机' }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'breed-improvement',
|
||||
@@ -133,60 +169,60 @@ const routes = [
|
||||
component: MessageNotification,
|
||||
meta: { title: '消息通知' }
|
||||
},
|
||||
{
|
||||
path: 'users',
|
||||
name: 'UserManagement',
|
||||
component: UserManagement,
|
||||
meta: { title: '用户管理' }
|
||||
},
|
||||
{
|
||||
path: 'supervision',
|
||||
name: 'SupervisionDashboard',
|
||||
component: SupervisionDashboard,
|
||||
meta: { title: '监管仪表板' }
|
||||
},
|
||||
{
|
||||
path: 'approval',
|
||||
name: 'ApprovalProcess',
|
||||
component: ApprovalProcess,
|
||||
meta: { title: '审批流程' }
|
||||
},
|
||||
{
|
||||
path: 'file',
|
||||
name: 'FileManagement',
|
||||
component: FileManagement,
|
||||
meta: { title: '文件管理' }
|
||||
},
|
||||
{
|
||||
path: 'service',
|
||||
name: 'ServiceManagement',
|
||||
component: ServiceManagement,
|
||||
meta: { title: '服务管理' }
|
||||
},
|
||||
{
|
||||
path: 'warehouse',
|
||||
name: 'WarehouseManagement',
|
||||
component: WarehouseManagement,
|
||||
meta: { title: '仓库管理' }
|
||||
},
|
||||
{
|
||||
path: 'log',
|
||||
name: 'LogManagement',
|
||||
component: LogManagement,
|
||||
meta: { title: '日志管理' }
|
||||
},
|
||||
{
|
||||
path: 'epidemic',
|
||||
name: 'EpidemicManagement',
|
||||
component: EpidemicManagement,
|
||||
meta: { title: '疫情管理' }
|
||||
},
|
||||
{
|
||||
path: 'visualization',
|
||||
name: 'VisualAnalysis',
|
||||
component: VisualAnalysis,
|
||||
meta: { title: '可视化分析' }
|
||||
}
|
||||
// {
|
||||
// path: 'users',
|
||||
// name: 'UserManagement',
|
||||
// component: UserManagement,
|
||||
// meta: { title: '用户管理' }
|
||||
// },
|
||||
// {
|
||||
// path: 'supervision',
|
||||
// name: 'SupervisionDashboard',
|
||||
// component: SupervisionDashboard,
|
||||
// meta: { title: '监管仪表板' }
|
||||
// },
|
||||
// {
|
||||
// path: 'approval',
|
||||
// name: 'ApprovalProcess',
|
||||
// component: ApprovalProcess,
|
||||
// meta: { title: '审批流程' }
|
||||
// },
|
||||
// {
|
||||
// path: 'file',
|
||||
// name: 'FileManagement',
|
||||
// component: FileManagement,
|
||||
// meta: { title: '文件管理' }
|
||||
// },
|
||||
// {
|
||||
// path: 'service',
|
||||
// name: 'ServiceManagement',
|
||||
// component: ServiceManagement,
|
||||
// meta: { title: '服务管理' }
|
||||
// },
|
||||
// {
|
||||
// path: 'warehouse',
|
||||
// name: 'WarehouseManagement',
|
||||
// component: WarehouseManagement,
|
||||
// meta: { title: '仓库管理' }
|
||||
// },
|
||||
// {
|
||||
// path: 'log',
|
||||
// name: 'LogManagement',
|
||||
// component: LogManagement,
|
||||
// meta: { title: '日志管理' }
|
||||
// },
|
||||
// {
|
||||
// path: 'epidemic',
|
||||
// name: 'EpidemicManagement',
|
||||
// component: EpidemicManagement,
|
||||
// meta: { title: '疫情管理' }
|
||||
// },
|
||||
// {
|
||||
// path: 'visualization',
|
||||
// name: 'VisualAnalysis',
|
||||
// component: VisualAnalysis,
|
||||
// meta: { title: '可视化分析' }
|
||||
// }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
244
government-admin/src/views/AdminDepartment.vue
Normal file
244
government-admin/src/views/AdminDepartment.vue
Normal file
@@ -0,0 +1,244 @@
|
||||
<template>
|
||||
<div class="admin-department-container">
|
||||
<div class="header">
|
||||
<div class="title">部门:防疫部门</div>
|
||||
<div class="actions">
|
||||
<a-button type="primary" class="action-btn">删除部门</a-button>
|
||||
<a-button type="primary" class="action-btn">新增岗位</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-table :columns="columns" :data-source="departmentData" :pagination="false" class="department-table">
|
||||
<template #headerCell="{ column }">
|
||||
<span>{{ column.title }}</span>
|
||||
</template>
|
||||
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'permission'">
|
||||
<a-tag v-if="record.permission === '已设置权限'" color="success">{{ record.permission }}</a-tag>
|
||||
<a-tag v-else color="warning">{{ record.permission }}</a-tag>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" @click="editPosition(record)">编辑岗位名称</a-button>
|
||||
<a-button type="link" @click="deletePosition(record)">删除岗位</a-button>
|
||||
<a-button type="link" @click="setPermission(record)">设置权限</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
|
||||
<div class="header">
|
||||
<div class="title">部门:检疫部门</div>
|
||||
<div class="actions">
|
||||
<a-button type="primary" class="action-btn">删除部门</a-button>
|
||||
<a-button type="primary" class="action-btn">新增岗位</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-table :columns="columns" :data-source="inspectionData" :pagination="false" class="department-table">
|
||||
<template #headerCell="{ column }">
|
||||
<span>{{ column.title }}</span>
|
||||
</template>
|
||||
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'permission'">
|
||||
<a-tag v-if="record.permission === '已设置权限'" color="success">{{ record.permission }}</a-tag>
|
||||
<a-tag v-else color="warning">{{ record.permission }}</a-tag>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" @click="editPosition(record)">编辑岗位名称</a-button>
|
||||
<a-button type="link" @click="deletePosition(record)">删除岗位</a-button>
|
||||
<a-button type="link" @click="setPermission(record)">设置权限</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
|
||||
<div class="header">
|
||||
<div class="title">部门:冷配管理部门</div>
|
||||
<div class="actions">
|
||||
<a-button type="primary" class="action-btn">删除部门</a-button>
|
||||
<a-button type="primary" class="action-btn">新增岗位</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-table :columns="columns" :data-source="coldChainData" :pagination="false" class="department-table">
|
||||
<template #headerCell="{ column }">
|
||||
<span>{{ column.title }}</span>
|
||||
</template>
|
||||
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'permission'">
|
||||
<a-tag v-if="record.permission === '已设置权限'" color="success">{{ record.permission }}</a-tag>
|
||||
<a-tag v-else color="warning">{{ record.permission }}</a-tag>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" @click="editPosition(record)">编辑岗位名称</a-button>
|
||||
<a-button type="link" @click="deletePosition(record)">删除岗位</a-button>
|
||||
<a-button type="link" @click="setPermission(record)">设置权限</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
|
||||
<div class="header">
|
||||
<div class="title">部门:测试部门</div>
|
||||
<div class="actions">
|
||||
<a-button type="primary" class="action-btn">删除部门</a-button>
|
||||
<a-button type="primary" class="action-btn">新增岗位</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-table :columns="columns" :data-source="testingData" :pagination="false" class="department-table">
|
||||
<template #headerCell="{ column }">
|
||||
<span>{{ column.title }}</span>
|
||||
</template>
|
||||
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'permission'">
|
||||
<a-tag v-if="record.permission === '已设置权限'" color="success">{{ record.permission }}</a-tag>
|
||||
<a-tag v-else color="warning">{{ record.permission }}</a-tag>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" @click="editPosition(record)">编辑岗位名称</a-button>
|
||||
<a-button type="link" @click="deletePosition(record)">删除岗位</a-button>
|
||||
<a-button type="link" @click="setPermission(record)">设置权限</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
|
||||
<div class="add-department">
|
||||
<a-button type="primary" @click="showAddDepartmentModal">新增部门</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
|
||||
// 表格列定义
|
||||
const columns = [
|
||||
{
|
||||
title: '岗位',
|
||||
dataIndex: 'position',
|
||||
key: 'position',
|
||||
},
|
||||
{
|
||||
title: '系统基础权限',
|
||||
dataIndex: 'permission',
|
||||
key: 'permission',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
}
|
||||
]
|
||||
|
||||
// 防疫部门数据
|
||||
const departmentData = ref([
|
||||
{
|
||||
key: '1',
|
||||
position: '防疫员',
|
||||
permission: '已设置权限',
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
position: '防疫管理员',
|
||||
permission: '已设置权限',
|
||||
}
|
||||
])
|
||||
|
||||
// 检疫部门数据
|
||||
const inspectionData = ref([
|
||||
{
|
||||
key: '1',
|
||||
position: '检疫员',
|
||||
permission: '未设置权限',
|
||||
}
|
||||
])
|
||||
|
||||
// 冷配管理部门数据
|
||||
const coldChainData = ref([
|
||||
{
|
||||
key: '1',
|
||||
position: '冷配员',
|
||||
permission: '已设置权限',
|
||||
}
|
||||
])
|
||||
|
||||
// 测试部门数据
|
||||
const testingData = ref([
|
||||
{
|
||||
key: '1',
|
||||
position: '内部测试账号',
|
||||
permission: '已设置权限',
|
||||
}
|
||||
])
|
||||
|
||||
// 编辑岗位
|
||||
const editPosition = (record) => {
|
||||
message.info(`编辑岗位:${record.position}`)
|
||||
}
|
||||
|
||||
// 删除岗位
|
||||
const deletePosition = (record) => {
|
||||
message.info(`删除岗位:${record.position}`)
|
||||
}
|
||||
|
||||
// 设置权限
|
||||
const setPermission = (record) => {
|
||||
message.info(`设置权限:${record.position}`)
|
||||
}
|
||||
|
||||
// 显示新增部门模态框
|
||||
const showAddDepartmentModal = () => {
|
||||
message.info('显示新增部门对话框')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.admin-department-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.department-table {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.add-department {
|
||||
margin-top: 24px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
265
government-admin/src/views/AdminStaff.vue
Normal file
265
government-admin/src/views/AdminStaff.vue
Normal file
@@ -0,0 +1,265 @@
|
||||
<template>
|
||||
<div class="admin-staff-container">
|
||||
<div class="header">
|
||||
<a-button type="primary">新增人员</a-button>
|
||||
<a-input-search
|
||||
v-model:value="searchValue"
|
||||
placeholder="请输入员工姓名"
|
||||
style="width: 200px"
|
||||
@search="onSearch"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<a-table :columns="columns" :data-source="staffData" :pagination="pagination" class="staff-table">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-switch v-model:checked="record.status" />
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" @click="editStaff(record)">编辑</a-button>
|
||||
<a-button type="link" @click="resetPassword(record)">修改密码</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
|
||||
// 搜索值
|
||||
const searchValue = ref('')
|
||||
|
||||
// 表格列定义
|
||||
const columns = [
|
||||
{
|
||||
title: '员工姓名',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: '所属部门',
|
||||
dataIndex: 'department',
|
||||
key: 'department',
|
||||
},
|
||||
{
|
||||
title: '任职岗位',
|
||||
dataIndex: 'position',
|
||||
key: 'position',
|
||||
},
|
||||
{
|
||||
title: '联系电话',
|
||||
dataIndex: 'phone',
|
||||
key: 'phone',
|
||||
},
|
||||
{
|
||||
title: '身份证号码',
|
||||
dataIndex: 'idCard',
|
||||
key: 'idCard',
|
||||
},
|
||||
{
|
||||
title: '账号使用状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createdAt',
|
||||
key: 'createdAt',
|
||||
sorter: (a, b) => new Date(a.createdAt) - new Date(b.createdAt),
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
}
|
||||
]
|
||||
|
||||
// 员工数据
|
||||
const staffData = ref([
|
||||
{
|
||||
key: '1',
|
||||
name: '内部测试账号',
|
||||
department: '测试部门',
|
||||
position: '内部测试账号',
|
||||
phone: '187****4778',
|
||||
idCard: '--',
|
||||
status: true,
|
||||
createdAt: '2023-11-24 17:24:32'
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
name: '扎拉嘎',
|
||||
department: '冷配管理部门',
|
||||
position: '冷配员',
|
||||
phone: '195****9912',
|
||||
idCard: '--',
|
||||
status: true,
|
||||
createdAt: '2023-10-10 13:59:03'
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
name: '董立鹏',
|
||||
department: '防疫部',
|
||||
position: '防疫管理员',
|
||||
phone: '151****7022',
|
||||
idCard: '--',
|
||||
status: true,
|
||||
createdAt: '2023-05-19 15:47:54'
|
||||
},
|
||||
{
|
||||
key: '4',
|
||||
name: '李海涛',
|
||||
department: '防疫部',
|
||||
position: '防疫管理员',
|
||||
phone: '139****6955',
|
||||
idCard: '--',
|
||||
status: true,
|
||||
createdAt: '2023-05-09 14:29:15'
|
||||
},
|
||||
{
|
||||
key: '5',
|
||||
name: '高凤鸣',
|
||||
department: '防疫部',
|
||||
position: '防疫管理员',
|
||||
phone: '158****4601',
|
||||
idCard: '--',
|
||||
status: true,
|
||||
createdAt: '2023-05-09 14:28:57'
|
||||
},
|
||||
{
|
||||
key: '6',
|
||||
name: '刘全',
|
||||
department: '防疫部',
|
||||
position: '防疫管理员',
|
||||
phone: '139****6009',
|
||||
idCard: '--',
|
||||
status: true,
|
||||
createdAt: '2023-05-09 14:28:39'
|
||||
},
|
||||
{
|
||||
key: '7',
|
||||
name: '李志伟',
|
||||
department: '防疫部',
|
||||
position: '防疫管理员',
|
||||
phone: '153****0457',
|
||||
idCard: '--',
|
||||
status: true,
|
||||
createdAt: '2023-05-09 14:28:14'
|
||||
},
|
||||
{
|
||||
key: '8',
|
||||
name: '李景学',
|
||||
department: '防疫部',
|
||||
position: '防疫管理员',
|
||||
phone: '153****5588',
|
||||
idCard: '--',
|
||||
status: true,
|
||||
createdAt: '2023-05-09 14:27:57'
|
||||
},
|
||||
{
|
||||
key: '9',
|
||||
name: '王海龙',
|
||||
department: '防疫部',
|
||||
position: '防疫管理员',
|
||||
phone: '150****7222',
|
||||
idCard: '--',
|
||||
status: true,
|
||||
createdAt: '2023-05-09 14:27:39'
|
||||
},
|
||||
{
|
||||
key: '10',
|
||||
name: '德力根',
|
||||
department: '防疫部',
|
||||
position: '防疫管理员',
|
||||
phone: '150****2938',
|
||||
idCard: '--',
|
||||
status: true,
|
||||
createdAt: '2023-05-09 14:27:22'
|
||||
},
|
||||
{
|
||||
key: '11',
|
||||
name: '巴日斯',
|
||||
department: '防疫部',
|
||||
position: '防疫管理员',
|
||||
phone: '131****1366',
|
||||
idCard: '--',
|
||||
status: true,
|
||||
createdAt: '2023-05-09 14:27:03'
|
||||
},
|
||||
{
|
||||
key: '12',
|
||||
name: '邵志勇',
|
||||
department: '防疫部',
|
||||
position: '防疫管理员',
|
||||
phone: '139****0778',
|
||||
idCard: '--',
|
||||
status: true,
|
||||
createdAt: '2023-05-09 14:26:26'
|
||||
},
|
||||
{
|
||||
key: '13',
|
||||
name: '张日林',
|
||||
department: '防疫部',
|
||||
position: '防疫管理员',
|
||||
phone: '156****6300',
|
||||
idCard: '--',
|
||||
status: true,
|
||||
createdAt: '2023-05-09 14:25:37'
|
||||
},
|
||||
{
|
||||
key: '14',
|
||||
name: '李燕',
|
||||
department: '防疫部',
|
||||
position: '防疫管理员',
|
||||
phone: '151****1613',
|
||||
idCard: '--',
|
||||
status: true,
|
||||
createdAt: '2023-05-09 14:25:12'
|
||||
}
|
||||
])
|
||||
|
||||
// 分页配置
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: staffData.value.length,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total) => `共 ${total} 条记录`
|
||||
})
|
||||
|
||||
// 搜索处理
|
||||
const onSearch = (value) => {
|
||||
message.info(`搜索: ${value}`)
|
||||
}
|
||||
|
||||
// 编辑员工
|
||||
const editStaff = (record) => {
|
||||
message.info(`编辑员工: ${record.name}`)
|
||||
}
|
||||
|
||||
// 重置密码
|
||||
const resetPassword = (record) => {
|
||||
message.info(`重置密码: ${record.name}`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.admin-staff-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.staff-table {
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,152 +1,158 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>数据览仓</h1>
|
||||
|
||||
<!-- 数据概览卡片 -->
|
||||
<a-row gutter={24} style="margin-bottom: 16px;">
|
||||
<a-col :span="6">
|
||||
<a-card hoverable>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon stat-icon-primary">
|
||||
<span class="iconfont icon-renyuanguanli" style="font-size: 24px;"></span>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="data-center-container">
|
||||
<!-- 顶部统计卡片 -->
|
||||
<div class="stat-cards">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<a-card class="stat-card">
|
||||
<div class="stat-header">
|
||||
<div class="stat-title">养殖户总数</div>
|
||||
<div class="stat-value">{{养殖户总数}}</div>
|
||||
<div class="stat-change">
|
||||
<span class="text-success">+5.2%</span> 较上月
|
||||
<div class="stat-icon">
|
||||
<span class="iconfont icon-renyuanguanli"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card hoverable>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon stat-icon-success">
|
||||
<span class="iconfont icon-niu" style="font-size: 24px;"></span>
|
||||
<div class="stat-body">
|
||||
<div class="stat-value">{{ 养殖户总数 }}</div>
|
||||
<div class="stat-trend">
|
||||
<span class="trend-value">+5.2%</span>
|
||||
<span class="trend-label">较上月</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card class="stat-card">
|
||||
<div class="stat-header">
|
||||
<div class="stat-title">存栏总量</div>
|
||||
<div class="stat-value">{{存栏总量}}</div>
|
||||
<div class="stat-change">
|
||||
<span class="text-success">+2.8%</span> 较上月
|
||||
<div class="stat-icon">
|
||||
<span class="iconfont icon-niu"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card hoverable>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon stat-icon-warning">
|
||||
<span class="iconfont icon-shengzijiaoyi" style="font-size: 24px;"></span>
|
||||
<div class="stat-body">
|
||||
<div class="stat-value">{{ 存栏总量 }}</div>
|
||||
<div class="stat-trend">
|
||||
<span class="trend-value">+2.8%</span>
|
||||
<span class="trend-label">较上月</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card class="stat-card">
|
||||
<div class="stat-header">
|
||||
<div class="stat-title">交易量</div>
|
||||
<div class="stat-value">{{交易量}}</div>
|
||||
<div class="stat-change">
|
||||
<span class="text-danger">-1.5%</span> 较上月
|
||||
<div class="stat-icon">
|
||||
<span class="iconfont icon-shengzijiaoyi"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card hoverable>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon stat-icon-danger">
|
||||
<span class="iconfont icon-yonghu" style="font-size: 24px;"></span>
|
||||
<div class="stat-body">
|
||||
<div class="stat-value">{{ 交易量 }}</div>
|
||||
<div class="stat-trend negative">
|
||||
<span class="trend-value">-1.5%</span>
|
||||
<span class="trend-label">较上月</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card class="stat-card">
|
||||
<div class="stat-header">
|
||||
<div class="stat-title">活跃用户</div>
|
||||
<div class="stat-value">{{活跃用户}}</div>
|
||||
<div class="stat-change">
|
||||
<span class="text-success">+8.3%</span> 较上月
|
||||
<div class="stat-icon">
|
||||
<span class="iconfont icon-yonghu"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div class="stat-body">
|
||||
<div class="stat-value">{{ 活跃用户 }}</div>
|
||||
<div class="stat-trend">
|
||||
<span class="trend-value">+8.3%</span>
|
||||
<span class="trend-label">较上月</span>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 主要图表区域 -->
|
||||
<a-row gutter={24} style="margin-bottom: 16px;">
|
||||
<a-col :span="12">
|
||||
<a-card title="养殖区域分布" style="height: 400px;">
|
||||
<div style="height: 340px;" ref="mapChartRef"></div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-card title="养殖规模分布" style="height: 400px;">
|
||||
<div style="height: 340px;" ref="scaleChartRef"></div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div class="chart-section">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="16">
|
||||
<a-card class="chart-card" title="月度交易量趋势">
|
||||
<div ref="transactionChartRef" class="chart-container"></div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-card class="chart-card" title="品类占比分析">
|
||||
<div ref="categoryChartRef" class="chart-container"></div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 第二行图表 -->
|
||||
<a-row gutter={24} style="margin-bottom: 16px;">
|
||||
<a-col :span="12">
|
||||
<a-card title="月度交易量趋势" style="height: 350px;">
|
||||
<div style="height: 290px;" ref="transactionChartRef"></div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-card title="品类占比分析" style="height: 350px;">
|
||||
<div style="height: 290px;" ref="categoryChartRef"></div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div class="chart-section">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-card class="chart-card" title="养殖区域分布">
|
||||
<div ref="mapChartRef" class="chart-container"></div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-card class="chart-card" title="养殖规模分布">
|
||||
<div ref="scaleChartRef" class="chart-container"></div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 最近数据更新信息 -->
|
||||
<a-card title="数据更新信息">
|
||||
<a-timeline mode="alternate">
|
||||
<a-timeline-item color="blue">
|
||||
<div class="timeline-content">
|
||||
<div class="timeline-title">市场行情数据更新</div>
|
||||
<div class="timeline-time">2024-04-10 15:30:00</div>
|
||||
<div class="timeline-desc">更新全国主要地区牛肉、牛奶、饲料价格数据</div>
|
||||
</div>
|
||||
</a-timeline-item>
|
||||
<a-timeline-item color="green">
|
||||
<div class="timeline-content">
|
||||
<div class="timeline-title">养殖户数据同步</div>
|
||||
<div class="timeline-time">2024-04-10 10:15:00</div>
|
||||
<div class="timeline-desc">新增56家养殖户信息,更新32家养殖户状态</div>
|
||||
</div>
|
||||
</a-timeline-item>
|
||||
<a-timeline-item color="orange">
|
||||
<div class="timeline-content">
|
||||
<div class="timeline-title">产品认证数据导入</div>
|
||||
<div class="timeline-time">2024-04-09 16:45:00</div>
|
||||
<div class="timeline-desc">导入120条生资产品认证信息</div>
|
||||
</div>
|
||||
</a-timeline-item>
|
||||
<a-timeline-item color="red">
|
||||
<div class="timeline-content">
|
||||
<div class="timeline-title">疫病监测数据更新</div>
|
||||
<div class="timeline-time">2024-04-09 09:20:00</div>
|
||||
<div class="timeline-desc">更新本周疫病监测数据,暂无异常情况</div>
|
||||
</div>
|
||||
</a-timeline-item>
|
||||
</a-timeline>
|
||||
</a-card>
|
||||
|
||||
<!-- 数据导出功能 -->
|
||||
<a-card title="数据导出" style="margin-top: 16px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; padding: 16px 0;">
|
||||
<div>
|
||||
<a-select v-model:value="exportDataType" style="width: 200px; margin-right: 16px;">
|
||||
<a-select-option value="market_price">市场行情数据</a-select-option>
|
||||
<a-select-option value="farmer_info">养殖户信息</a-select-option>
|
||||
<a-select-option value="transaction_data">交易数据</a-select-option>
|
||||
<a-select-option value="epidemic_data">疫病监测数据</a-select-option>
|
||||
</a-select>
|
||||
<a-input v-model:value="exportDateRange" style="width: 250px;" placeholder="选择日期范围 (YYYY-MM-DD ~ YYYY-MM-DD)"></a-input>
|
||||
</div>
|
||||
<a-button type="primary" danger @click="handleExportData">导出数据</a-button>
|
||||
</div>
|
||||
</a-card>
|
||||
<!-- 数据更新信息 -->
|
||||
<div class="update-section">
|
||||
<a-card class="update-card" title="数据更新信息">
|
||||
<a-list>
|
||||
<a-list-item>
|
||||
<div class="update-item">
|
||||
<div class="update-icon blue"></div>
|
||||
<div class="update-content">
|
||||
<div class="update-title">市场行情数据更新</div>
|
||||
<div class="update-time">2024-04-10 15:30:00</div>
|
||||
<div class="update-desc">更新全国主要地区牛肉、牛奶、饲料价格数据</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<div class="update-item">
|
||||
<div class="update-icon green"></div>
|
||||
<div class="update-content">
|
||||
<div class="update-title">养殖户数据同步</div>
|
||||
<div class="update-time">2024-04-10 10:15:00</div>
|
||||
<div class="update-desc">新增56家养殖户信息,更新32家养殖户状态</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<div class="update-item">
|
||||
<div class="update-icon orange"></div>
|
||||
<div class="update-content">
|
||||
<div class="update-title">产品认证数据导入</div>
|
||||
<div class="update-time">2024-04-09 16:45:00</div>
|
||||
<div class="update-desc">导入120条生资产品认证信息</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<div class="update-item">
|
||||
<div class="update-icon red"></div>
|
||||
<div class="update-content">
|
||||
<div class="update-title">疫病监测数据更新</div>
|
||||
<div class="update-time">2024-04-09 09:20:00</div>
|
||||
<div class="update-desc">更新本周疫病监测数据,暂无异常情况</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</a-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -166,9 +172,122 @@ const scaleChartRef = ref(null)
|
||||
const transactionChartRef = ref(null)
|
||||
const categoryChartRef = ref(null)
|
||||
|
||||
// 导出数据类型
|
||||
const exportDataType = ref('market_price')
|
||||
const exportDateRange = ref('2024-03-10 ~ 2024-04-10')
|
||||
// 初始化交易量趋势图表
|
||||
const initTransactionChart = () => {
|
||||
if (!transactionChartRef.value) return
|
||||
|
||||
const chart = echarts.init(transactionChartRef.value)
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: ['1月', '2月', '3月', '4月', '5月', '6月']
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '交易额(亿元)'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '交易量',
|
||||
type: 'line',
|
||||
stack: 'Total',
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(58, 77, 233, 0.8)' },
|
||||
{ offset: 1, color: 'rgba(58, 77, 233, 0.1)' }
|
||||
])
|
||||
},
|
||||
emphasis: {
|
||||
focus: 'series'
|
||||
},
|
||||
data: [2.8, 3.1, 3.5, 3.26, 3.4, 3.6],
|
||||
itemStyle: {
|
||||
color: '#3a4de9'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 3,
|
||||
color: '#3a4de9'
|
||||
},
|
||||
symbol: 'circle',
|
||||
symbolSize: 8
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
chart.setOption(option)
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
chart.resize()
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化品类占比图表
|
||||
const initCategoryChart = () => {
|
||||
if (!categoryChartRef.value) return
|
||||
|
||||
const chart = echarts.init(categoryChartRef.value)
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
legend: {
|
||||
bottom: '5%',
|
||||
left: 'center'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '品类占比',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
center: ['50%', '40%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2
|
||||
},
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center'
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: '18',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
data: [
|
||||
{ value: 45, name: '肉牛养殖', itemStyle: { color: '#3a4de9' } },
|
||||
{ value: 30, name: '奶牛养殖', itemStyle: { color: '#4cd137' } },
|
||||
{ value: 15, name: '犊牛养殖', itemStyle: { color: '#e1b12c' } },
|
||||
{ value: 10, name: '其他养殖', itemStyle: { color: '#44bd32' } }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
chart.setOption(option)
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
chart.resize()
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化养殖区域分布图表
|
||||
const initMapChart = () => {
|
||||
@@ -182,8 +301,8 @@ const initMapChart = () => {
|
||||
formatter: '{a} <br/>{b}: {c} ({d}%)'
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left',
|
||||
bottom: '5%',
|
||||
left: 'center',
|
||||
data: ['东北地区', '华北地区', '华东地区', '华南地区', '西南地区', '西北地区']
|
||||
},
|
||||
series: [
|
||||
@@ -212,12 +331,12 @@ const initMapChart = () => {
|
||||
show: false
|
||||
},
|
||||
data: [
|
||||
{ value: 28, name: '东北地区' },
|
||||
{ value: 22, name: '华北地区' },
|
||||
{ value: 18, name: '华东地区' },
|
||||
{ value: 12, name: '华南地区' },
|
||||
{ value: 15, name: '西南地区' },
|
||||
{ value: 5, name: '西北地区' }
|
||||
{ value: 28, name: '东北地区', itemStyle: { color: '#3a4de9' } },
|
||||
{ value: 22, name: '华北地区', itemStyle: { color: '#4cd137' } },
|
||||
{ value: 18, name: '华东地区', itemStyle: { color: '#e1b12c' } },
|
||||
{ value: 12, name: '华南地区', itemStyle: { color: '#44bd32' } },
|
||||
{ value: 15, name: '西南地区', itemStyle: { color: '#0097e6' } },
|
||||
{ value: 5, name: '西北地区', itemStyle: { color: '#8c7ae6' } }
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -265,9 +384,8 @@ const initScaleChart = () => {
|
||||
data: [6850, 1250, 156],
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: '#83bff6' },
|
||||
{ offset: 0.5, color: '#188df0' },
|
||||
{ offset: 1, color: '#188df0' }
|
||||
{ offset: 0, color: '#3a4de9' },
|
||||
{ offset: 1, color: '#8c7ae6' }
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -281,210 +399,162 @@ const initScaleChart = () => {
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化交易量趋势图表
|
||||
const initTransactionChart = () => {
|
||||
if (!transactionChartRef.value) return
|
||||
|
||||
const chart = echarts.init(transactionChartRef.value)
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: ['1月', '2月', '3月', '4月', '5月', '6月']
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '交易额(亿元)'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '交易量',
|
||||
type: 'line',
|
||||
stack: 'Total',
|
||||
areaStyle: {},
|
||||
emphasis: {
|
||||
focus: 'series'
|
||||
},
|
||||
data: [2.8, 3.1, 3.5, 3.26, null, null],
|
||||
itemStyle: {
|
||||
color: '#52c41a'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 3
|
||||
},
|
||||
symbol: 'circle',
|
||||
symbolSize: 8
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
chart.setOption(option)
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
chart.resize()
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化品类占比图表
|
||||
const initCategoryChart = () => {
|
||||
if (!categoryChartRef.value) return
|
||||
|
||||
const chart = echarts.init(categoryChartRef.value)
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
legend: {
|
||||
top: 'bottom'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '品类占比',
|
||||
type: 'pie',
|
||||
radius: '65%',
|
||||
center: ['50%', '40%'],
|
||||
data: [
|
||||
{ value: 45, name: '肉牛养殖' },
|
||||
{ value: 30, name: '奶牛养殖' },
|
||||
{ value: 15, name: '犊牛养殖' },
|
||||
{ value: 10, name: '其他养殖' }
|
||||
],
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
chart.setOption(option)
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
chart.resize()
|
||||
})
|
||||
}
|
||||
|
||||
// 导出数据处理函数
|
||||
const handleExportData = () => {
|
||||
console.log('导出数据类型:', exportDataType.value)
|
||||
console.log('导出日期范围:', exportDateRange.value)
|
||||
// 这里应该有实际的导出逻辑
|
||||
|
||||
// 显示导出成功提示
|
||||
const message = `成功导出${exportDataType.value === 'market_price' ? '市场行情' :
|
||||
exportDataType.value === 'farmer_info' ? '养殖户信息' :
|
||||
exportDataType.value === 'transaction_data' ? '交易数据' : '疫病监测数据'}数据`
|
||||
|
||||
// 在实际项目中,这里应该使用Ant Design的message组件
|
||||
alert(message)
|
||||
}
|
||||
|
||||
// 组件挂载时初始化所有图表
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
initMapChart()
|
||||
initScaleChart()
|
||||
initTransactionChart()
|
||||
initCategoryChart()
|
||||
initMapChart()
|
||||
initScaleChart()
|
||||
}, 100)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.stat-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 16px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.stat-icon-primary {
|
||||
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 100%);
|
||||
}
|
||||
|
||||
.stat-icon-success {
|
||||
background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%);
|
||||
}
|
||||
|
||||
.stat-icon-warning {
|
||||
background: linear-gradient(135deg, #fa8c16 0%, #ffa940 100%);
|
||||
}
|
||||
|
||||
.stat-icon-danger {
|
||||
background: linear-gradient(135deg, #f5222d 0%, #ff4d4f 100%);
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stat-title {
|
||||
font-size: 14px;
|
||||
color: #8c8c8c;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.stat-change {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.text-success {
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: #f5222d;
|
||||
}
|
||||
|
||||
.timeline-content {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.timeline-title {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.timeline-time {
|
||||
font-size: 12px;
|
||||
color: #8c8c8c;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.timeline-desc {
|
||||
font-size: 13px;
|
||||
color: #595959;
|
||||
}
|
||||
.data-center-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.stat-cards {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
height: 100%;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.stat-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.stat-title {
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background-color: #f5f5f5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #3a4de9;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.stat-body {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.stat-trend {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.stat-trend.negative {
|
||||
color: #f5222d;
|
||||
}
|
||||
|
||||
.trend-value {
|
||||
font-weight: bold;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.trend-label {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.chart-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.chart-card {
|
||||
height: 100%;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.update-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.update-card {
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.update-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.update-icon {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
margin-right: 16px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.update-icon.blue {
|
||||
background-color: #1890ff;
|
||||
}
|
||||
|
||||
.update-icon.green {
|
||||
background-color: #52c41a;
|
||||
}
|
||||
|
||||
.update-icon.orange {
|
||||
background-color: #fa8c16;
|
||||
}
|
||||
|
||||
.update-icon.red {
|
||||
background-color: #f5222d;
|
||||
}
|
||||
|
||||
.update-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.update-title {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.update-time {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.update-desc {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
@@ -1,436 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>疫情管理</h1>
|
||||
|
||||
<!-- 数据统计卡片 -->
|
||||
<a-row gutter={24}>
|
||||
<a-col :span="6">
|
||||
<a-card class="stat-card" :body-style="{ padding: '20px' }">
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">{{ epidemicStats.vaccinated }}</div>
|
||||
<div class="stat-label">累计接种人数</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card class="stat-card" :body-style="{ padding: '20px' }">
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">{{ epidemicStats.tested }}</div>
|
||||
<div class="stat-label">累计检测次数</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card class="stat-card" :body-style="{ padding: '20px' }">
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">{{ currentCases.confirmed }}</div>
|
||||
<div class="stat-label">当前确诊</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card class="stat-card" :body-style="{ padding: '20px' }">
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">{{ currentCases.observed }}</div>
|
||||
<div class="stat-label">隔离观察</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 图表区域 -->
|
||||
<a-row gutter={24} style="margin-top: 24px;">
|
||||
<a-col :span="12">
|
||||
<a-card title="疫情趋势" :body-style="{ padding: 0 }">
|
||||
<div style="height: 300px; padding: 10px;">
|
||||
<div id="epidemic-trend-chart" style="width: 100%; height: 100%;"></div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-card title="区域分布" :body-style="{ padding: 0 }">
|
||||
<div style="height: 300px; padding: 10px;">
|
||||
<div id="area-distribution-chart" style="width: 100%; height: 100%;"></div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 数据过滤器和表格 -->
|
||||
<a-card title="疫情数据记录" style="margin-top: 24px;">
|
||||
<div style="margin-bottom: 16px;">
|
||||
<a-row gutter={16}>
|
||||
<a-col :span="6">
|
||||
<a-select v-model:value="filters.region" placeholder="选择地区" style="width: 100%;">
|
||||
<a-select-option value="all">全部地区</a-select-option>
|
||||
<a-select-option value="east">东区</a-select-option>
|
||||
<a-select-option value="west">西区</a-select-option>
|
||||
<a-select-option value="south">南区</a-select-option>
|
||||
<a-select-option value="north">北区</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-select v-model:value="filters.type" placeholder="选择数据类型" style="width: 100%;">
|
||||
<a-select-option value="all">全部类型</a-select-option>
|
||||
<a-select-option value="confirmed">确诊病例</a-select-option>
|
||||
<a-select-option value="suspected">疑似病例</a-select-option>
|
||||
<a-select-option value="observed">隔离观察</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-range-picker v-model:value="filters.dateRange" style="width: 100%;" />
|
||||
</a-col>
|
||||
<a-col :span="6" style="text-align: right;">
|
||||
<a-button type="primary" @click="searchData">查询</a-button>
|
||||
<a-button style="margin-left: 8px;" @click="resetFilters">重置</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
<a-table :columns="epidemicColumns" :data-source="epidemicData" :pagination="{ pageSize: 10 }">
|
||||
<template #bodyCell:type="{ record }">
|
||||
<a-tag :color="getTypeColor(record.type)">
|
||||
{{ getTypeText(record.type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template #bodyCell:action="{ record }">
|
||||
<a-button type="link" @click="viewEpidemicDetail(record.id)">查看详情</a-button>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { message } from 'antd'
|
||||
import axios from 'axios'
|
||||
import * as echarts from 'echarts'
|
||||
import { getEpidemicStats } from '@/mock'
|
||||
|
||||
const epidemicStats = ref({
|
||||
vaccinated: 0,
|
||||
tested: 0
|
||||
})
|
||||
const currentCases = ref({
|
||||
confirmed: 0,
|
||||
observed: 0,
|
||||
suspected: 0
|
||||
})
|
||||
const epidemicData = ref([])
|
||||
const filters = ref({
|
||||
region: 'all',
|
||||
type: 'all',
|
||||
dateRange: []
|
||||
})
|
||||
let trendChart = null
|
||||
let distributionChart = null
|
||||
|
||||
// 疫情数据表格列定义
|
||||
const epidemicColumns = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id'
|
||||
},
|
||||
{
|
||||
title: '地区',
|
||||
dataIndex: 'region',
|
||||
key: 'region'
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
slots: { customRender: 'type' }
|
||||
},
|
||||
{
|
||||
title: '数量',
|
||||
dataIndex: 'count',
|
||||
key: 'count'
|
||||
},
|
||||
{
|
||||
title: '报告时间',
|
||||
dataIndex: 'report_time',
|
||||
key: 'report_time'
|
||||
},
|
||||
{
|
||||
title: '报告人',
|
||||
dataIndex: 'reporter',
|
||||
key: 'reporter'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
slots: { customRender: 'action' }
|
||||
}
|
||||
]
|
||||
|
||||
// 根据类型获取标签颜色
|
||||
const getTypeColor = (type) => {
|
||||
const colorMap = {
|
||||
confirmed: 'red',
|
||||
suspected: 'orange',
|
||||
observed: 'blue'
|
||||
}
|
||||
return colorMap[type] || 'default'
|
||||
}
|
||||
|
||||
// 根据类型获取显示文本
|
||||
const getTypeText = (type) => {
|
||||
const textMap = {
|
||||
confirmed: '确诊病例',
|
||||
suspected: '疑似病例',
|
||||
observed: '隔离观察'
|
||||
}
|
||||
return textMap[type] || type
|
||||
}
|
||||
|
||||
// 获取疫情统计数据
|
||||
const fetchEpidemicStats = async () => {
|
||||
try {
|
||||
// 尝试从API获取数据
|
||||
const response = await axios.get('/api/epidemic/stats')
|
||||
if (response.data.code === 200) {
|
||||
const data = response.data.data
|
||||
epidemicStats.value.vaccinated = data.vaccinated || 0
|
||||
epidemicStats.value.tested = data.tested || 0
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取疫情统计数据失败,使用模拟数据:', error)
|
||||
// 使用模拟数据
|
||||
const mockResponse = await getEpidemicStats()
|
||||
if (mockResponse.code === 200) {
|
||||
const data = mockResponse.data
|
||||
epidemicStats.value.vaccinated = data.vaccinated || 0
|
||||
epidemicStats.value.tested = data.tested || 0
|
||||
}
|
||||
}
|
||||
|
||||
// 设置其他模拟数据
|
||||
currentCases.value.confirmed = Math.floor(Math.random() * 50)
|
||||
currentCases.value.observed = Math.floor(Math.random() * 200) + 50
|
||||
currentCases.value.suspected = Math.floor(Math.random() * 30)
|
||||
}
|
||||
|
||||
// 获取疫情记录数据
|
||||
const fetchEpidemicData = async () => {
|
||||
try {
|
||||
// 这里应该从API获取数据
|
||||
// 由于没有实际API,使用模拟数据
|
||||
epidemicData.value = [
|
||||
{ id: 1, region: '东区', type: 'confirmed', count: 15, report_time: '2024-01-10 09:00:00', reporter: '张三' },
|
||||
{ id: 2, region: '西区', type: 'confirmed', count: 8, report_time: '2024-01-10 10:30:00', reporter: '李四' },
|
||||
{ id: 3, region: '南区', type: 'suspected', count: 12, report_time: '2024-01-09 14:20:00', reporter: '王五' },
|
||||
{ id: 4, region: '北区', type: 'observed', count: 60, report_time: '2024-01-09 16:45:00', reporter: '赵六' },
|
||||
{ id: 5, region: '东区', type: 'observed', count: 45, report_time: '2024-01-08 08:30:00', reporter: '钱七' },
|
||||
{ id: 6, region: '西区', type: 'suspected', count: 7, report_time: '2024-01-08 11:15:00', reporter: '孙八' },
|
||||
{ id: 7, region: '南区', type: 'confirmed', count: 10, report_time: '2024-01-07 14:50:00', reporter: '周九' },
|
||||
{ id: 8, region: '北区', type: 'observed', count: 52, report_time: '2024-01-07 17:20:00', reporter: '吴十' },
|
||||
{ id: 9, region: '东区', type: 'suspected', count: 9, report_time: '2024-01-06 09:40:00', reporter: '郑一' },
|
||||
{ id: 10, region: '西区', type: 'confirmed', count: 6, report_time: '2024-01-06 13:30:00', reporter: '王二' }
|
||||
]
|
||||
} catch (error) {
|
||||
console.error('获取疫情记录数据失败:', error)
|
||||
message.error('获取疫情记录数据失败,请稍后重试')
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化图表
|
||||
const initCharts = () => {
|
||||
// 疫情趋势图
|
||||
const trendChartDom = document.getElementById('epidemic-trend-chart')
|
||||
if (trendChartDom) {
|
||||
trendChart = echarts.init(trendChartDom)
|
||||
const trendOption = {
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
legend: {
|
||||
data: ['确诊病例', '疑似病例', '隔离观察']
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['1月', '2月', '3月', '4月', '5月', '6月']
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '确诊病例',
|
||||
type: 'line',
|
||||
data: [12, 19, 15, 10, 8, 12],
|
||||
smooth: true,
|
||||
lineStyle: {
|
||||
color: '#ff4d4f'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '疑似病例',
|
||||
type: 'line',
|
||||
data: [8, 15, 10, 7, 5, 8],
|
||||
smooth: true,
|
||||
lineStyle: {
|
||||
color: '#fa8c16'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '隔离观察',
|
||||
type: 'line',
|
||||
data: [45, 60, 50, 35, 40, 45],
|
||||
smooth: true,
|
||||
lineStyle: {
|
||||
color: '#1890ff'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
trendChart.setOption(trendOption)
|
||||
}
|
||||
|
||||
// 区域分布图
|
||||
const distributionChartDom = document.getElementById('area-distribution-chart')
|
||||
if (distributionChartDom) {
|
||||
distributionChart = echarts.init(distributionChartDom)
|
||||
const distributionOption = {
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
legend: {
|
||||
top: '5%',
|
||||
left: 'center'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '区域分布',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2
|
||||
},
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center'
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: '18',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: 30,
|
||||
name: '东区'
|
||||
},
|
||||
{
|
||||
value: 25,
|
||||
name: '西区'
|
||||
},
|
||||
{
|
||||
value: 22,
|
||||
name: '南区'
|
||||
},
|
||||
{
|
||||
value: 23,
|
||||
name: '北区'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
distributionChart.setOption(distributionOption)
|
||||
}
|
||||
}
|
||||
|
||||
// 响应窗口大小变化
|
||||
const handleResize = () => {
|
||||
if (trendChart) {
|
||||
trendChart.resize()
|
||||
}
|
||||
if (distributionChart) {
|
||||
distributionChart.resize()
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索数据
|
||||
const searchData = () => {
|
||||
message.info('根据筛选条件查询数据')
|
||||
// 这里应该根据筛选条件重新请求数据
|
||||
// 目前使用模拟数据,实际项目中需要调用API
|
||||
}
|
||||
|
||||
// 重置筛选条件
|
||||
const resetFilters = () => {
|
||||
filters.value = {
|
||||
region: 'all',
|
||||
type: 'all',
|
||||
dateRange: []
|
||||
}
|
||||
}
|
||||
|
||||
// 查看疫情详情
|
||||
const viewEpidemicDetail = (id) => {
|
||||
message.info(`查看ID: ${id} 的疫情详情`)
|
||||
// 这里可以跳转到详情页面
|
||||
// router.push(`/epidemic/detail/${id}`)
|
||||
}
|
||||
|
||||
// 组件挂载
|
||||
onMounted(() => {
|
||||
fetchEpidemicStats()
|
||||
fetchEpidemicData()
|
||||
setTimeout(() => {
|
||||
initCharts()
|
||||
}, 100)
|
||||
window.addEventListener('resize', handleResize)
|
||||
})
|
||||
|
||||
// 组件卸载
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
if (trendChart) {
|
||||
trendChart.dispose()
|
||||
}
|
||||
if (distributionChart) {
|
||||
distributionChart.dispose()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.stat-card {
|
||||
height: 100%;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: #1890ff;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
@@ -1,290 +1,147 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>养殖户管理</h1>
|
||||
|
||||
<!-- 搜索和操作栏 -->
|
||||
<a-card style="margin-bottom: 16px;">
|
||||
<div style="display: flex; flex-wrap: wrap; gap: 16px; align-items: center;">
|
||||
<a-input v-model:value="searchKeyword" placeholder="输入养殖户名称或负责人姓名" style="width: 250px;">
|
||||
<template #prefix>
|
||||
<span class="iconfont icon-sousuo"></span>
|
||||
</template>
|
||||
</a-input>
|
||||
|
||||
<a-select v-model:value="statusFilter" placeholder="选择状态" style="width: 120px;">
|
||||
<a-select-option value="">全部</a-select-option>
|
||||
<a-select-option value="active">正常经营</a-select-option>
|
||||
<a-select-option value="inactive">暂停经营</a-select-option>
|
||||
<a-select-option value="closed">已关闭</a-select-option>
|
||||
</a-select>
|
||||
|
||||
<a-select v-model:value="scaleFilter" placeholder="养殖规模" style="width: 120px;">
|
||||
<a-select-option value="">全部</a-select-option>
|
||||
<a-select-option value="small">小型(<50头)</a-select-option>
|
||||
<a-select-option value="medium">中型(50-200头)</a-select-option>
|
||||
<a-select-option value="large">大型(>200头)</a-select-option>
|
||||
</a-select>
|
||||
|
||||
<a-select v-model:value="regionFilter" placeholder="所在地区" style="width: 150px;">
|
||||
<a-select-option value="">全部</a-select-option>
|
||||
<a-select-option value="northeast">东北地区</a-select-option>
|
||||
<a-select-option value="north">华北地区</a-select-option>
|
||||
<a-select-option value="east">华东地区</a-select-option>
|
||||
<a-select-option value="south">华南地区</a-select-option>
|
||||
<a-select-option value="southwest">西南地区</a-select-option>
|
||||
<a-select-option value="northwest">西北地区</a-select-option>
|
||||
</a-select>
|
||||
|
||||
<a-button type="primary" @click="handleSearch" style="margin-left: auto;">
|
||||
<span class="iconfont icon-sousuo"></span> 搜索
|
||||
</a-button>
|
||||
|
||||
<a-button type="default" @click="handleReset">重置</a-button>
|
||||
|
||||
<a-button type="dashed" @click="handleImport">
|
||||
<span class="iconfont icon-daoru"></span> 导入
|
||||
</a-button>
|
||||
|
||||
<a-button type="dashed" @click="handleExport">
|
||||
<span class="iconfont icon-daochu"></span> 导出
|
||||
</a-button>
|
||||
|
||||
<a-button type="primary" danger @click="handleAddFarmer">
|
||||
<span class="iconfont icon-tianjia"></span> 新增养殖户
|
||||
</a-button>
|
||||
<div style="display: flex; justify-content: space-between; margin-bottom: 16px;">
|
||||
<div>
|
||||
<a-button type="primary" @click="handleAddFarmer">新增养殖监管</a-button>
|
||||
<a-button style="margin-left: 8px;" @click="handleExport">导出表格</a-button>
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
<!-- 数据统计卡片 -->
|
||||
<a-row gutter={24} style="margin-bottom: 16px;">
|
||||
<a-col :span="6">
|
||||
<a-statistic title="总养殖户数" :value="totalFarmers" suffix="家" />
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-statistic title="正常经营" :value="activeFarmers" suffix="家" :valueStyle="{ color: '#3f8600' }" />
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-statistic title="暂停经营" :value="inactiveFarmers" suffix="家" :valueStyle="{ color: '#faad14' }" />
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-statistic title="已关闭" :value="closedFarmers" suffix="家" :valueStyle="{ color: '#cf1322' }" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div>
|
||||
<a-input-search
|
||||
placeholder="请输入"
|
||||
style="width: 200px; margin-right: 8px;"
|
||||
v-model:value="searchKeyword"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
<a-select
|
||||
placeholder="账号"
|
||||
style="width: 120px;"
|
||||
v-model:value="accountFilter"
|
||||
@change="handleSearch"
|
||||
>
|
||||
<a-select-option value="">全部</a-select-option>
|
||||
<a-select-option value="account1">账号筛选1</a-select-option>
|
||||
<a-select-option value="account2">账号筛选2</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 养殖户列表 -->
|
||||
<a-card>
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="farmersData"
|
||||
:pagination="pagination"
|
||||
row-key="id"
|
||||
:row-selection="{ selectedRowKeys, onChange: onSelectChange }"
|
||||
:scroll="{ x: 'max-content' }"
|
||||
>
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="farmersData"
|
||||
:pagination="pagination"
|
||||
row-key="id"
|
||||
:scroll="{ x: 'max-content' }"
|
||||
>
|
||||
<!-- 状态列 -->
|
||||
<template #bodyCell:status="{ record }">
|
||||
<a-tag :color="getStatusColor(record.status)">{{ getStatusText(record.status) }}</a-tag>
|
||||
</template>
|
||||
|
||||
<!-- 养殖规模列 -->
|
||||
<template #bodyCell:scale="{ record }">
|
||||
<span>{{ getScaleText(record.scale) }}</span>
|
||||
</template>
|
||||
|
||||
<!-- 操作列 -->
|
||||
<template #bodyCell:action="{ record }">
|
||||
<div style="display: flex; gap: 8px;">
|
||||
<a-button size="small" @click="handleView(record)">查看</a-button>
|
||||
<a-button size="small" type="primary" @click="handleEdit(record)">编辑</a-button>
|
||||
<a-button size="small" danger @click="handleDelete(record.id)">删除</a-button>
|
||||
</div>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">{{ getStatusText(record.status) }}</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<div style="display: flex; gap: 8px;">
|
||||
<a-button size="small" @click="handleView(record)">查看</a-button>
|
||||
<a-button size="small" type="primary" @click="handleEdit(record)">编辑</a-button>
|
||||
<a-button size="small" danger @click="handleDelete(record.id)">删除</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 新增/编辑养殖户模态框 -->
|
||||
<a-modal
|
||||
v-model:open="isAddEditModalOpen"
|
||||
title="新增/编辑养殖户"
|
||||
:footer="null"
|
||||
width={800}
|
||||
>
|
||||
<a-form
|
||||
:model="currentFarmer"
|
||||
layout="vertical"
|
||||
style="max-width: 600px; margin: 0 auto;"
|
||||
>
|
||||
<a-row gutter={24}>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="养殖户名称" name="name" :rules="[{ required: true, message: '请输入养殖户名称' }]">
|
||||
<a-input v-model:value="currentFarmer.name" placeholder="请输入养殖户名称" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="统一社会信用代码" name="creditCode" :rules="[{ required: true, message: '请输入统一社会信用代码' }]">
|
||||
<a-input v-model:value="currentFarmer.creditCode" placeholder="请输入统一社会信用代码" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row gutter={24}>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="负责人姓名" name="manager" :rules="[{ required: true, message: '请输入负责人姓名' }]">
|
||||
<a-input v-model:value="currentFarmer.manager" placeholder="请输入负责人姓名" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="联系电话" name="phone" :rules="[{ required: true, message: '请输入联系电话' }]">
|
||||
<a-input v-model:value="currentFarmer.phone" placeholder="请输入联系电话" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row gutter={24}>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="所在地区" name="region" :rules="[{ required: true, message: '请选择所在地区' }]">
|
||||
<a-select v-model:value="currentFarmer.region" placeholder="请选择所在地区">
|
||||
<a-select-option value="northeast">东北地区</a-select-option>
|
||||
<a-select-option value="north">华北地区</a-select-option>
|
||||
<a-select-option value="east">华东地区</a-select-option>
|
||||
<a-select-option value="south">华南地区</a-select-option>
|
||||
<a-select-option value="southwest">西南地区</a-select-option>
|
||||
<a-select-option value="northwest">西北地区</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="详细地址" name="address" :rules="[{ required: true, message: '请输入详细地址' }]">
|
||||
<a-input v-model:value="currentFarmer.address" placeholder="请输入详细地址" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row gutter={24}>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="养殖规模" name="scale" :rules="[{ required: true, message: '请选择养殖规模' }]">
|
||||
<a-select v-model:value="currentFarmer.scale" placeholder="请选择养殖规模">
|
||||
<a-select-option value="small">小型(<50头)</a-select-option>
|
||||
<a-select-option value="medium">中型(50-200头)</a-select-option>
|
||||
<a-select-option value="large">大型(>200头)</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="存栏数量" name="stockQuantity" :rules="[{ required: true, message: '请输入存栏数量' }]">
|
||||
<a-input-number v-model:value="currentFarmer.stockQuantity" :min="0" placeholder="请输入存栏数量" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item label="养殖类型" name="farmType">
|
||||
<a-checkbox-group v-model:value="currentFarmer.farmType">
|
||||
<a-checkbox value="beef">肉牛养殖</a-checkbox>
|
||||
<a-checkbox value="milk">奶牛养殖</a-checkbox>
|
||||
<a-checkbox value="calf">犊牛养殖</a-checkbox>
|
||||
<a-checkbox value="other">其他养殖</a-checkbox>
|
||||
</a-checkbox-group>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="经营状态" name="status" :rules="[{ required: true, message: '请选择经营状态' }]">
|
||||
<a-radio-group v-model:value="currentFarmer.status">
|
||||
<a-radio value="active">正常经营</a-radio>
|
||||
<a-radio value="inactive">暂停经营</a-radio>
|
||||
<a-radio value="closed">已关闭</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="备注信息" name="remark">
|
||||
<a-input.TextArea v-model:value="currentFarmer.remark" placeholder="请输入备注信息" :rows="3" />
|
||||
</a-form-item>
|
||||
|
||||
<div style="display: flex; justify-content: flex-end; gap: 16px;">
|
||||
<a-button @click="isAddEditModalOpen = false">取消</a-button>
|
||||
<a-button type="primary" @click="handleSave">保存</a-button>
|
||||
</div>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
<!-- 查看养殖户详情模态框 -->
|
||||
<a-modal
|
||||
v-model:open="isViewModalOpen"
|
||||
title="养殖户详情"
|
||||
:footer="null"
|
||||
width={800}
|
||||
>
|
||||
<div v-if="viewFarmer" class="farmer-detail">
|
||||
<div class="detail-row">
|
||||
<div class="detail-label">养殖户名称:</div>
|
||||
<div class="detail-value">{{ viewFarmer.name }}</div>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<div class="detail-label">统一社会信用代码:</div>
|
||||
<div class="detail-value">{{ viewFarmer.creditCode }}</div>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<div class="detail-label">负责人姓名:</div>
|
||||
<div class="detail-value">{{ viewFarmer.manager }}</div>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<div class="detail-label">联系电话:</div>
|
||||
<div class="detail-value">{{ viewFarmer.phone }}</div>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<div class="detail-label">所在地区:</div>
|
||||
<div class="detail-value">{{ getRegionText(viewFarmer.region) }}</div>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<div class="detail-label">详细地址:</div>
|
||||
<div class="detail-value">{{ viewFarmer.address }}</div>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<div class="detail-label">养殖规模:</div>
|
||||
<div class="detail-value">{{ getScaleText(viewFarmer.scale) }}</div>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<div class="detail-label">存栏数量:</div>
|
||||
<div class="detail-value">{{ viewFarmer.stockQuantity }}头</div>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<div class="detail-label">养殖类型:</div>
|
||||
<div class="detail-value">{{ getFarmTypeText(viewFarmer.farmType) }}</div>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<div class="detail-label">经营状态:</div>
|
||||
<div class="detail-value">
|
||||
<a-tag :color="getStatusColor(viewFarmer.status)">{{ getStatusText(viewFarmer.status) }}</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<div class="detail-label">创建时间:</div>
|
||||
<div class="detail-value">{{ viewFarmer.createTime }}</div>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<div class="detail-label">备注信息:</div>
|
||||
<div class="detail-value">{{ viewFarmer.remark || '无' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align: center; margin-top: 24px;">
|
||||
<a-button @click="isViewModalOpen = false">关闭</a-button>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
|
||||
// 搜索条件
|
||||
// 搜索关键词
|
||||
const searchKeyword = ref('')
|
||||
const statusFilter = ref('')
|
||||
const scaleFilter = ref('')
|
||||
const regionFilter = ref('')
|
||||
const accountFilter = ref('')
|
||||
|
||||
// 统计数据
|
||||
const totalFarmers = ref(1256)
|
||||
const activeFarmers = ref(986)
|
||||
const inactiveFarmers = ref(158)
|
||||
const closedFarmers = ref(112)
|
||||
// 表格列定义
|
||||
const columns = [
|
||||
{
|
||||
title: '账号',
|
||||
dataIndex: 'account',
|
||||
key: 'account',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '机构识别码',
|
||||
dataIndex: 'orgCode',
|
||||
key: 'orgCode',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '账号昵称',
|
||||
dataIndex: 'nickname',
|
||||
key: 'nickname',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '真实姓名',
|
||||
dataIndex: 'realName',
|
||||
key: 'realName',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '养殖场名称',
|
||||
dataIndex: 'farmName',
|
||||
key: 'farmName',
|
||||
width: 180
|
||||
},
|
||||
{
|
||||
title: '养殖场类型',
|
||||
dataIndex: 'farmType',
|
||||
key: 'farmType',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '养殖场种类',
|
||||
dataIndex: 'animalType',
|
||||
key: 'animalType',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '数量',
|
||||
dataIndex: 'animalCount',
|
||||
key: 'animalCount',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '养殖场地址',
|
||||
dataIndex: 'address',
|
||||
key: 'address',
|
||||
width: 250
|
||||
},
|
||||
{
|
||||
title: '登记时间',
|
||||
dataIndex: 'registerTime',
|
||||
key: 'registerTime',
|
||||
width: 150,
|
||||
sorter: (a, b) => new Date(a.registerTime) - new Date(b.registerTime)
|
||||
},
|
||||
{
|
||||
title: '登记人',
|
||||
dataIndex: 'registrar',
|
||||
key: 'registrar',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '更新时间',
|
||||
dataIndex: 'updateTime',
|
||||
key: 'updateTime',
|
||||
width: 150,
|
||||
sorter: (a, b) => new Date(a.updateTime) - new Date(b.updateTime)
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
width: 180
|
||||
}
|
||||
]
|
||||
|
||||
// 分页配置
|
||||
const pagination = reactive({
|
||||
@@ -292,417 +149,152 @@ const pagination = reactive({
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total) => `共 ${total} 条记录`
|
||||
})
|
||||
|
||||
// 选中的行
|
||||
const selectedRowKeys = ref([])
|
||||
const onSelectChange = (newSelectedRowKeys) => {
|
||||
selectedRowKeys.value = newSelectedRowKeys
|
||||
}
|
||||
|
||||
// 模态框状态
|
||||
const isAddEditModalOpen = ref(false)
|
||||
const isViewModalOpen = ref(false)
|
||||
|
||||
// 当前编辑的养殖户
|
||||
const currentFarmer = reactive({
|
||||
id: '',
|
||||
name: '',
|
||||
creditCode: '',
|
||||
manager: '',
|
||||
phone: '',
|
||||
region: '',
|
||||
address: '',
|
||||
scale: '',
|
||||
stockQuantity: 0,
|
||||
farmType: [],
|
||||
status: 'active',
|
||||
remark: '',
|
||||
createTime: ''
|
||||
})
|
||||
|
||||
// 查看的养殖户
|
||||
const viewFarmer = ref(null)
|
||||
|
||||
// 养殖户列表数据
|
||||
// 模拟数据
|
||||
const farmersData = ref([
|
||||
{
|
||||
id: '1',
|
||||
name: '瑞丰养殖场',
|
||||
creditCode: '91370100MA3C8Y6D6X',
|
||||
manager: '张明',
|
||||
phone: '13800138001',
|
||||
region: 'north',
|
||||
address: '北京市海淀区中关村南大街5号',
|
||||
scale: 'large',
|
||||
stockQuantity: 580,
|
||||
farmType: ['beef', 'milk'],
|
||||
status: 'active',
|
||||
remark: '市级重点养殖企业',
|
||||
createTime: '2020-01-15 10:30:00'
|
||||
account: '176****4187',
|
||||
orgCode: '18094-785778',
|
||||
nickname: '齐五旺',
|
||||
realName: '齐五旺',
|
||||
farmName: '齐五旺养殖场',
|
||||
farmType: '散养',
|
||||
animalType: '牛',
|
||||
animalCount: 10,
|
||||
address: '内蒙古自治区通辽市扎鲁特旗鲁北镇额尔格图木塔格镇',
|
||||
registerTime: '2023-05-22 08:59:44',
|
||||
registrar: '齐五旺',
|
||||
updateTime: '2025-08-10 06:07:41',
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: '绿源生态养殖合作社',
|
||||
creditCode: '93370100MA3C8Y6D6X',
|
||||
manager: '李华',
|
||||
phone: '13900139001',
|
||||
region: 'east',
|
||||
address: '上海市浦东新区张江高科技园区博云路2号',
|
||||
scale: 'medium',
|
||||
stockQuantity: 150,
|
||||
farmType: ['beef'],
|
||||
status: 'active',
|
||||
remark: '生态养殖示范基地',
|
||||
createTime: '2020-02-20 14:20:00'
|
||||
account: '132****9345',
|
||||
orgCode: '384708-449642',
|
||||
nickname: '扎鲁特数据中心',
|
||||
realName: '扎鲁特数据中心',
|
||||
farmName: '扎鲁特数据中心',
|
||||
farmType: '散养',
|
||||
animalType: '牛',
|
||||
animalCount: 10,
|
||||
address: '内蒙古自治区通辽市扎鲁特旗',
|
||||
registerTime: '2024-07-23 13:37:00',
|
||||
registrar: '扎鲁特数据中心',
|
||||
updateTime: '2024-10-16 11:33:02',
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: '金牛养殖场',
|
||||
creditCode: '91370100MA3C8Y6D6Y',
|
||||
manager: '王强',
|
||||
phone: '13700137001',
|
||||
region: 'south',
|
||||
address: '广州市天河区天河路385号',
|
||||
scale: 'small',
|
||||
stockQuantity: 30,
|
||||
farmType: ['milk'],
|
||||
status: 'inactive',
|
||||
remark: '暂时休业整顿',
|
||||
createTime: '2020-03-05 09:15:00'
|
||||
account: '139****1221',
|
||||
orgCode: '382987-319364',
|
||||
nickname: '邵晓艳',
|
||||
realName: '邵晓艳',
|
||||
farmName: '扎鲁特旗百顺养殖专业合作社',
|
||||
farmType: '规模',
|
||||
animalType: '牛',
|
||||
animalCount: 741,
|
||||
address: '内蒙古自治区通辽市扎鲁特旗巴日合镇巴日合村委会',
|
||||
registerTime: '2023-07-10 11:17:50',
|
||||
registrar: '赵忠林',
|
||||
updateTime: '2024-07-03 11:53:47',
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
name: '草原牧歌养殖公司',
|
||||
creditCode: '91370100MA3C8Y6D6Z',
|
||||
manager: '赵芳',
|
||||
phone: '13600136001',
|
||||
region: 'northwest',
|
||||
address: '西安市雁塔区科技路25号',
|
||||
scale: 'large',
|
||||
stockQuantity: 850,
|
||||
farmType: ['beef', 'calf'],
|
||||
status: 'active',
|
||||
remark: '省级农业产业化龙头企业',
|
||||
createTime: '2020-04-10 11:45:00'
|
||||
account: '151****1013',
|
||||
orgCode: '1069-899484',
|
||||
nickname: '双成家庭农场',
|
||||
realName: '双成',
|
||||
farmName: '双成家庭农场',
|
||||
farmType: '散养',
|
||||
animalType: '牛',
|
||||
animalCount: 74,
|
||||
address: '内蒙古自治区通辽市扎鲁特旗鲁北镇宝力根花镇',
|
||||
registerTime: '2023-04-24 10:54:22',
|
||||
registrar: '双成',
|
||||
updateTime: '2024-05-08 11:14:48',
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
name: '祥和养殖场',
|
||||
creditCode: '91370100MA3C8Y6D6A',
|
||||
manager: '孙建国',
|
||||
phone: '13500135001',
|
||||
region: 'southwest',
|
||||
address: '成都市武侯区一环路南四段1号',
|
||||
scale: 'medium',
|
||||
stockQuantity: 120,
|
||||
farmType: ['beef'],
|
||||
status: 'closed',
|
||||
remark: '经营不善已关闭',
|
||||
createTime: '2020-05-15 16:00:00'
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
name: '福满家养殖园',
|
||||
creditCode: '91370100MA3C8Y6D6B',
|
||||
manager: '周小丽',
|
||||
phone: '13400134001',
|
||||
region: 'northeast',
|
||||
address: '沈阳市沈河区青年大街100号',
|
||||
scale: 'small',
|
||||
stockQuantity: 40,
|
||||
farmType: ['milk', 'other'],
|
||||
status: 'active',
|
||||
remark: '家庭农场示范户',
|
||||
createTime: '2020-06-20 13:30:00'
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
name: '康源养殖有限公司',
|
||||
creditCode: '91370100MA3C8Y6D6C',
|
||||
manager: '吴大山',
|
||||
phone: '13300133001',
|
||||
region: 'east',
|
||||
address: '南京市鼓楼区中山北路1号',
|
||||
scale: 'large',
|
||||
stockQuantity: 620,
|
||||
farmType: ['beef', 'milk', 'calf'],
|
||||
status: 'active',
|
||||
remark: '现代化养殖企业',
|
||||
createTime: '2020-07-25 10:15:00'
|
||||
},
|
||||
{
|
||||
id: '8',
|
||||
name: '田园牧歌生态养殖',
|
||||
creditCode: '91370100MA3C8Y6D6D',
|
||||
manager: '郑小华',
|
||||
phone: '13200132001',
|
||||
region: 'north',
|
||||
address: '石家庄市桥西区自强路22号',
|
||||
scale: 'medium',
|
||||
stockQuantity: 95,
|
||||
farmType: ['beef'],
|
||||
status: 'inactive',
|
||||
remark: '设备升级中',
|
||||
createTime: '2020-08-30 15:45:00'
|
||||
},
|
||||
{
|
||||
id: '9',
|
||||
name: '龙凤养殖场',
|
||||
creditCode: '91370100MA3C8Y6D6E',
|
||||
manager: '钱小红',
|
||||
phone: '13100131001',
|
||||
region: 'south',
|
||||
address: '深圳市南山区科技园南区',
|
||||
scale: 'small',
|
||||
stockQuantity: 25,
|
||||
farmType: ['milk'],
|
||||
status: 'active',
|
||||
remark: '特色奶制品供应商',
|
||||
createTime: '2020-09-05 09:30:00'
|
||||
},
|
||||
{
|
||||
id: '10',
|
||||
name: '远大养殖集团',
|
||||
creditCode: '91370100MA3C8Y6D6F',
|
||||
manager: '陈明亮',
|
||||
phone: '13000130001',
|
||||
region: 'east',
|
||||
address: '杭州市西湖区文三路478号',
|
||||
scale: 'large',
|
||||
stockQuantity: 980,
|
||||
farmType: ['beef', 'calf'],
|
||||
status: 'active',
|
||||
remark: '国家级农业龙头企业',
|
||||
createTime: '2020-10-10 14:20:00'
|
||||
account: '150****1368',
|
||||
orgCode: '1451-668414',
|
||||
nickname: '刘超',
|
||||
realName: '刘超',
|
||||
farmName: '大数据中心',
|
||||
farmType: '散养',
|
||||
animalType: '牛',
|
||||
animalCount: 11,
|
||||
address: '内蒙古自治区通辽市扎鲁特旗鲁北镇',
|
||||
registerTime: '2023-04-11 15:36:37',
|
||||
registrar: '孙主任',
|
||||
updateTime: '2024-03-04 11:18:24',
|
||||
status: 'active'
|
||||
}
|
||||
])
|
||||
|
||||
// 表格列定义
|
||||
const columns = [
|
||||
{
|
||||
title: '养殖户名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '负责人',
|
||||
dataIndex: 'manager',
|
||||
key: 'manager',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '联系电话',
|
||||
dataIndex: 'phone',
|
||||
key: 'phone',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '所在地区',
|
||||
dataIndex: 'region',
|
||||
key: 'region',
|
||||
width: 100,
|
||||
customRender: ({ text }) => getRegionText(text)
|
||||
},
|
||||
{
|
||||
title: '养殖规模',
|
||||
dataIndex: 'scale',
|
||||
key: 'scale',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '存栏数量',
|
||||
dataIndex: 'stockQuantity',
|
||||
key: 'stockQuantity',
|
||||
width: 100,
|
||||
customRender: ({ text }) => `${text}头`
|
||||
},
|
||||
{
|
||||
title: '经营状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
width: 160
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 180,
|
||||
fixed: 'right'
|
||||
}
|
||||
]
|
||||
// 初始化数据
|
||||
onMounted(() => {
|
||||
pagination.total = farmersData.value.length
|
||||
})
|
||||
|
||||
// 处理搜索
|
||||
const handleSearch = () => {
|
||||
// 实际应用中这里应该调用API进行搜索
|
||||
message.success('搜索条件已应用')
|
||||
}
|
||||
|
||||
// 处理导出
|
||||
const handleExport = () => {
|
||||
message.success('数据导出中...')
|
||||
}
|
||||
|
||||
// 处理添加养殖户
|
||||
const handleAddFarmer = () => {
|
||||
message.success('打开新增养殖监管表单')
|
||||
}
|
||||
|
||||
// 处理查看详情
|
||||
const handleView = (record) => {
|
||||
message.info(`查看养殖户: ${record.farmName}`)
|
||||
}
|
||||
|
||||
// 处理编辑
|
||||
const handleEdit = (record) => {
|
||||
message.info(`编辑养殖户: ${record.farmName}`)
|
||||
}
|
||||
|
||||
// 处理删除
|
||||
const handleDelete = (id) => {
|
||||
message.success(`删除养殖户ID: ${id}`)
|
||||
}
|
||||
|
||||
// 获取状态颜色
|
||||
const getStatusColor = (status) => {
|
||||
switch (status) {
|
||||
case 'active': return 'green'
|
||||
case 'inactive': return 'orange'
|
||||
case 'closed': return 'red'
|
||||
default: return 'default'
|
||||
const statusMap = {
|
||||
active: 'green',
|
||||
inactive: 'orange',
|
||||
closed: 'red'
|
||||
}
|
||||
return statusMap[status] || 'default'
|
||||
}
|
||||
|
||||
// 获取状态文本
|
||||
const getStatusText = (status) => {
|
||||
switch (status) {
|
||||
case 'active': return '正常经营'
|
||||
case 'inactive': return '暂停经营'
|
||||
case 'closed': return '已关闭'
|
||||
default: return status
|
||||
const statusMap = {
|
||||
active: '正常',
|
||||
inactive: '暂停',
|
||||
closed: '关闭'
|
||||
}
|
||||
return statusMap[status] || '未知'
|
||||
}
|
||||
|
||||
// 获取规模文本
|
||||
const getScaleText = (scale) => {
|
||||
switch (scale) {
|
||||
case 'small': return '小型(<50头)'
|
||||
case 'medium': return '中型(50-200头)'
|
||||
case 'large': return '大型(>200头)'
|
||||
default: return scale
|
||||
}
|
||||
}
|
||||
|
||||
// 获取地区文本
|
||||
const getRegionText = (region) => {
|
||||
switch (region) {
|
||||
case 'northeast': return '东北地区'
|
||||
case 'north': return '华北地区'
|
||||
case 'east': return '华东地区'
|
||||
case 'south': return '华南地区'
|
||||
case 'southwest': return '西南地区'
|
||||
case 'northwest': return '西北地区'
|
||||
default: return region
|
||||
}
|
||||
}
|
||||
|
||||
// 获取养殖类型文本
|
||||
const getFarmTypeText = (farmTypes) => {
|
||||
if (!farmTypes || farmTypes.length === 0) return '无'
|
||||
const typeMap = {
|
||||
beef: '肉牛养殖',
|
||||
milk: '奶牛养殖',
|
||||
calf: '犊牛养殖',
|
||||
other: '其他养殖'
|
||||
}
|
||||
return farmTypes.map(type => typeMap[type] || type).join('、')
|
||||
}
|
||||
|
||||
// 搜索处理
|
||||
const handleSearch = () => {
|
||||
console.log('搜索条件:', {
|
||||
keyword: searchKeyword.value,
|
||||
status: statusFilter.value,
|
||||
scale: scaleFilter.value,
|
||||
region: regionFilter.value
|
||||
})
|
||||
// 这里应该有实际的搜索逻辑
|
||||
// 模拟搜索后的总数
|
||||
pagination.total = farmersData.value.length
|
||||
}
|
||||
|
||||
// 重置处理
|
||||
const handleReset = () => {
|
||||
searchKeyword.value = ''
|
||||
statusFilter.value = ''
|
||||
scaleFilter.value = ''
|
||||
regionFilter.value = ''
|
||||
selectedRowKeys.value = []
|
||||
}
|
||||
|
||||
// 导入处理
|
||||
const handleImport = () => {
|
||||
console.log('导入养殖户数据')
|
||||
// 这里应该有实际的导入逻辑
|
||||
}
|
||||
|
||||
// 导出处理
|
||||
const handleExport = () => {
|
||||
console.log('导出养殖户数据')
|
||||
// 这里应该有实际的导出逻辑
|
||||
}
|
||||
|
||||
// 新增养殖户
|
||||
const handleAddFarmer = () => {
|
||||
// 重置当前养殖户数据
|
||||
Object.assign(currentFarmer, {
|
||||
id: '',
|
||||
name: '',
|
||||
creditCode: '',
|
||||
manager: '',
|
||||
phone: '',
|
||||
region: '',
|
||||
address: '',
|
||||
scale: '',
|
||||
stockQuantity: 0,
|
||||
farmType: [],
|
||||
status: 'active',
|
||||
remark: '',
|
||||
createTime: ''
|
||||
})
|
||||
isAddEditModalOpen.value = true
|
||||
}
|
||||
|
||||
// 编辑养殖户
|
||||
const handleEdit = (record) => {
|
||||
// 复制记录数据到当前养殖户
|
||||
Object.assign(currentFarmer, JSON.parse(JSON.stringify(record)))
|
||||
isAddEditModalOpen.value = true
|
||||
}
|
||||
|
||||
// 查看养殖户
|
||||
const handleView = (record) => {
|
||||
viewFarmer.value = JSON.parse(JSON.stringify(record))
|
||||
isViewModalOpen.value = true
|
||||
}
|
||||
|
||||
// 删除养殖户
|
||||
const handleDelete = (id) => {
|
||||
console.log('删除养殖户:', id)
|
||||
// 这里应该有实际的删除逻辑和确认提示
|
||||
// 模拟删除成功
|
||||
alert(`成功删除养殖户ID: ${id}`)
|
||||
}
|
||||
|
||||
// 保存养殖户
|
||||
const handleSave = () => {
|
||||
console.log('保存养殖户:', currentFarmer)
|
||||
// 这里应该有实际的保存逻辑
|
||||
// 模拟保存成功
|
||||
isAddEditModalOpen.value = false
|
||||
alert('保存成功')
|
||||
}
|
||||
|
||||
// 初始化数据
|
||||
pagination.total = farmersData.value.length
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.farmer-detail {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.detail-row {
|
||||
display: flex;
|
||||
margin-bottom: 16px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
width: 150px;
|
||||
font-weight: bold;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
flex: 1;
|
||||
color: #333;
|
||||
}
|
||||
.ant-table {
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,270 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>文件管理</h1>
|
||||
<a-card style="margin-bottom: 16px;">
|
||||
<a-row gutter={24}>
|
||||
<a-col :span="12">
|
||||
<a-upload-dragger
|
||||
name="file"
|
||||
:multiple="true"
|
||||
:action="uploadUrl"
|
||||
@change="handleUploadChange"
|
||||
>
|
||||
<p class="ant-upload-drag-icon">
|
||||
<inbox-outlined />
|
||||
</p>
|
||||
<p class="ant-upload-text">点击或拖拽文件到此区域上传</p>
|
||||
<p class="ant-upload-hint">
|
||||
支持单个或批量上传,文件大小不超过100MB
|
||||
</p>
|
||||
</a-upload-dragger>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-card title="上传设置" :body-style="{ padding: '20px' }">
|
||||
<a-form layout="vertical">
|
||||
<a-form-item label="文件分类">
|
||||
<a-select v-model:value="fileCategory">
|
||||
<a-select-option value="policy">政策文件</a-select-option>
|
||||
<a-select-option value="report">报表文件</a-select-option>
|
||||
<a-select-option value="notice">通知文件</a-select-option>
|
||||
<a-select-option value="other">其他文件</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="文件描述">
|
||||
<a-input v-model:value="fileDescription" placeholder="请输入文件描述" />
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="handleUploadConfirm">确认上传</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
|
||||
<!-- 文件列表 -->
|
||||
<a-card title="文件列表">
|
||||
<a-row gutter={24} style="margin-bottom: 16px;">
|
||||
<a-col :span="6">
|
||||
<a-input v-model:value="searchKeyword" placeholder="搜索文件名" allow-clear />
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-select v-model:value="filterCategory" placeholder="筛选分类" allow-clear>
|
||||
<a-select-option value="policy">政策文件</a-select-option>
|
||||
<a-select-option value="report">报表文件</a-select-option>
|
||||
<a-select-option value="notice">通知文件</a-select-option>
|
||||
<a-select-option value="other">其他文件</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-date-picker v-model:value="uploadDateRange" style="width: 100%;" />
|
||||
</a-col>
|
||||
<a-col :span="6" style="text-align: right;">
|
||||
<a-button type="primary" @click="handleSearch">搜索</a-button>
|
||||
<a-button style="margin-left: 8px;" @click="handleReset">重置</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-table :columns="fileColumns" :data-source="fileList" :pagination="pagination" row-key="id">
|
||||
<template #bodyCell:name="{ record }">
|
||||
<a @click="handleFileDownload(record)">{{ record.name }}</a>
|
||||
</template>
|
||||
<template #bodyCell:size="{ record }">
|
||||
<span>{{ formatFileSize(record.size) }}</span>
|
||||
</template>
|
||||
<template #bodyCell:action="{ record }">
|
||||
<a-space>
|
||||
<a-button type="link" @click="handleFilePreview(record)">预览</a-button>
|
||||
<a-button type="link" @click="handleFileDownload(record)">下载</a-button>
|
||||
<a-button type="link" danger @click="handleFileDelete(record.id)">删除</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { InboxOutlined } from '@ant-design/icons-vue'
|
||||
import axios from 'axios'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
// 上传配置
|
||||
const uploadUrl = '/api/files/upload'
|
||||
const fileCategory = ref('')
|
||||
const fileDescription = ref('')
|
||||
|
||||
// 搜索和筛选
|
||||
const searchKeyword = ref('')
|
||||
const filterCategory = ref('')
|
||||
const uploadDateRange = ref(null)
|
||||
|
||||
// 分页配置
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
|
||||
// 文件列表数据
|
||||
const fileList = ref([
|
||||
{
|
||||
id: '1',
|
||||
name: '2024年第一季度工作报告.pdf',
|
||||
size: 2097152,
|
||||
type: 'pdf',
|
||||
category: 'report',
|
||||
uploadTime: '2024-04-01T08:30:00Z',
|
||||
uploader: '张三',
|
||||
downloadCount: 12
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: '关于加强养殖场监管的通知.docx',
|
||||
size: 1048576,
|
||||
type: 'docx',
|
||||
category: 'notice',
|
||||
uploadTime: '2024-03-28T14:15:00Z',
|
||||
uploader: '李四',
|
||||
downloadCount: 35
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: '动物防疫政策解读.pdf',
|
||||
size: 3145728,
|
||||
type: 'pdf',
|
||||
category: 'policy',
|
||||
uploadTime: '2024-03-15T10:45:00Z',
|
||||
uploader: '王五',
|
||||
downloadCount: 89
|
||||
}
|
||||
])
|
||||
|
||||
// 文件表格列定义
|
||||
const fileColumns = [
|
||||
{
|
||||
title: '文件名',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '文件类型',
|
||||
dataIndex: 'type',
|
||||
key: 'type'
|
||||
},
|
||||
{
|
||||
title: '分类',
|
||||
dataIndex: 'category',
|
||||
key: 'category',
|
||||
customRender: (text) => {
|
||||
const categoryMap = {
|
||||
policy: '政策文件',
|
||||
report: '报表文件',
|
||||
notice: '通知文件',
|
||||
other: '其他文件'
|
||||
}
|
||||
return categoryMap[text] || text
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '文件大小',
|
||||
dataIndex: 'size',
|
||||
key: 'size',
|
||||
customRender: (text) => formatFileSize(text)
|
||||
},
|
||||
{
|
||||
title: '上传时间',
|
||||
dataIndex: 'uploadTime',
|
||||
key: 'uploadTime',
|
||||
customRender: (text) => dayjs(text).format('YYYY-MM-DD HH:mm')
|
||||
},
|
||||
{
|
||||
title: '上传人',
|
||||
dataIndex: 'uploader',
|
||||
key: 'uploader'
|
||||
},
|
||||
{
|
||||
title: '下载次数',
|
||||
dataIndex: 'downloadCount',
|
||||
key: 'downloadCount'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 150,
|
||||
fixed: 'right'
|
||||
}
|
||||
]
|
||||
|
||||
// 格式化文件大小
|
||||
const formatFileSize = (bytes) => {
|
||||
if (bytes === 0) return '0 Bytes'
|
||||
const k = 1024
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
||||
}
|
||||
|
||||
// 上传文件变化处理
|
||||
const handleUploadChange = (info) => {
|
||||
console.log('文件上传变化:', info)
|
||||
}
|
||||
|
||||
// 确认上传
|
||||
const handleUploadConfirm = () => {
|
||||
// 这里应该有实际的上传逻辑
|
||||
console.log('确认上传', { fileCategory: fileCategory.value, fileDescription: fileDescription.value })
|
||||
}
|
||||
|
||||
// 文件搜索
|
||||
const handleSearch = () => {
|
||||
// 这里应该有实际的搜索逻辑
|
||||
console.log('搜索文件', { searchKeyword: searchKeyword.value, filterCategory: filterCategory.value, uploadDateRange: uploadDateRange.value })
|
||||
}
|
||||
|
||||
// 重置搜索条件
|
||||
const handleReset = () => {
|
||||
searchKeyword.value = ''
|
||||
filterCategory.value = ''
|
||||
uploadDateRange.value = null
|
||||
}
|
||||
|
||||
// 文件预览
|
||||
const handleFilePreview = (file) => {
|
||||
console.log('预览文件:', file)
|
||||
}
|
||||
|
||||
// 文件下载
|
||||
const handleFileDownload = (file) => {
|
||||
console.log('下载文件:', file)
|
||||
}
|
||||
|
||||
// 文件删除
|
||||
const handleFileDelete = (id) => {
|
||||
console.log('删除文件:', id)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.stat-card {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-top: 8px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,386 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>日志管理</h1>
|
||||
<a-card style="margin-bottom: 16px;">
|
||||
<a-row gutter={24}>
|
||||
<a-col :span="16">
|
||||
<a-input-search
|
||||
placeholder="搜索操作内容或操作人"
|
||||
allow-clear
|
||||
enter-button="搜索"
|
||||
size="large"
|
||||
style="width: 300px; margin-right: 16px;"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
<a-select v-model:value="filterOperationType" placeholder="筛选操作类型" allow-clear style="width: 150px; margin-right: 16px;">
|
||||
<a-select-option value="login">登录</a-select-option>
|
||||
<a-select-option value="logout">登出</a-select-option>
|
||||
<a-select-option value="create">创建</a-select-option>
|
||||
<a-select-option value="update">更新</a-select-option>
|
||||
<a-select-option value="delete">删除</a-select-option>
|
||||
<a-select-option value="query">查询</a-select-option>
|
||||
<a-select-option value="export">导出</a-select-option>
|
||||
</a-select>
|
||||
<a-select v-model:value="filterModule" placeholder="筛选模块" allow-clear style="width: 150px; margin-right: 16px;">
|
||||
<a-select-option value="user">用户管理</a-select-option>
|
||||
<a-select-option value="epidemic">疫情管理</a-select-option>
|
||||
<a-select-option value="supervision">监管管理</a-select-option>
|
||||
<a-select-option value="file">文件管理</a-select-option>
|
||||
<a-select-option value="warehouse">仓库管理</a-select-option>
|
||||
<a-select-option value="service">服务管理</a-select-option>
|
||||
<a-select-option value="personnel">人员管理</a-select-option>
|
||||
</a-select>
|
||||
<a-select v-model:value="filterStatus" placeholder="筛选状态" allow-clear style="width: 120px;">
|
||||
<a-select-option value="success">成功</a-select-option>
|
||||
<a-select-option value="fail">失败</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="8" style="text-align: right;">
|
||||
<a-range-picker v-model:value="dateRange" format="YYYY-MM-DD" @change="handleDateChange" style="width: 280px; margin-right: 16px;" />
|
||||
<a-button type="primary" @click="handleExportLogs">导出日志</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
|
||||
<!-- 日志统计图表 -->
|
||||
<a-card title="操作统计" style="margin-bottom: 16px;">
|
||||
<div style="height: 300px;" ref="chartRef"></div>
|
||||
</a-card>
|
||||
|
||||
<!-- 日志列表 -->
|
||||
<a-card title="日志列表">
|
||||
<a-table :columns="logColumns" :data-source="logList" :pagination="pagination" row-key="id">
|
||||
<template #bodyCell:operationType="{ record }">
|
||||
<a-tag :color="getOperationTypeColor(record.operationType)">{{ getOperationTypeLabel(record.operationType) }}</a-tag>
|
||||
</template>
|
||||
<template #bodyCell:module="{ record }">
|
||||
<a-tag color="blue">{{ getModuleLabel(record.module) }}</a-tag>
|
||||
</template>
|
||||
<template #bodyCell:status="{ record }">
|
||||
<span :class="getStatusClass(record.status)">{{ record.status === 'success' ? '成功' : '失败' }}</span>
|
||||
</template>
|
||||
<template #bodyCell:time="{ record }">
|
||||
{{ dayjs(record.time).format('YYYY-MM-DD HH:mm:ss') }}
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import axios from 'axios'
|
||||
import dayjs from 'dayjs'
|
||||
import * as echarts from 'echarts'
|
||||
|
||||
// 日期范围
|
||||
const dateRange = ref([dayjs().subtract(7, 'day'), dayjs()])
|
||||
|
||||
// 筛选条件
|
||||
const filterOperationType = ref('')
|
||||
const filterModule = ref('')
|
||||
const filterStatus = ref('')
|
||||
|
||||
// 分页配置
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
|
||||
// 日志列表数据
|
||||
const logList = ref([
|
||||
{
|
||||
id: '1',
|
||||
operator: 'admin',
|
||||
operationType: 'login',
|
||||
module: 'system',
|
||||
operationContent: '用户登录系统',
|
||||
ip: '192.168.1.100',
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36',
|
||||
status: 'success',
|
||||
time: '2024-04-10T09:00:00Z'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
operator: 'admin',
|
||||
operationType: 'update',
|
||||
module: 'user',
|
||||
operationContent: '更新用户信息,用户ID: 1001',
|
||||
ip: '192.168.1.100',
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36',
|
||||
status: 'success',
|
||||
time: '2024-04-10T10:15:00Z'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
operator: 'admin',
|
||||
operationType: 'create',
|
||||
module: 'epidemic',
|
||||
operationContent: '新增疫情记录,记录ID: 2001',
|
||||
ip: '192.168.1.100',
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36',
|
||||
status: 'success',
|
||||
time: '2024-04-10T11:30:00Z'
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
operator: 'user001',
|
||||
operationType: 'query',
|
||||
module: 'supervision',
|
||||
operationContent: '查询监管数据',
|
||||
ip: '192.168.1.101',
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Firefox/111.0 Safari/537.36',
|
||||
status: 'success',
|
||||
time: '2024-04-10T13:45:00Z'
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
operator: 'user002',
|
||||
operationType: 'export',
|
||||
module: 'file',
|
||||
operationContent: '导出文件,文件ID: 3001',
|
||||
ip: '192.168.1.102',
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edge/112.0.0.0 Safari/537.36',
|
||||
status: 'success',
|
||||
time: '2024-04-10T14:20:00Z'
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
operator: 'user003',
|
||||
operationType: 'delete',
|
||||
module: 'warehouse',
|
||||
operationContent: '删除物资,物资ID: 4001',
|
||||
ip: '192.168.1.103',
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36',
|
||||
status: 'fail',
|
||||
errorMsg: '权限不足',
|
||||
time: '2024-04-10T15:00:00Z'
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
operator: 'admin',
|
||||
operationType: 'logout',
|
||||
module: 'system',
|
||||
operationContent: '用户登出系统',
|
||||
ip: '192.168.1.100',
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36',
|
||||
status: 'success',
|
||||
time: '2024-04-10T17:30:00Z'
|
||||
}
|
||||
])
|
||||
|
||||
// 日志表格列定义
|
||||
const logColumns = [
|
||||
{
|
||||
title: '操作人',
|
||||
dataIndex: 'operator',
|
||||
key: 'operator',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '操作类型',
|
||||
dataIndex: 'operationType',
|
||||
key: 'operationType',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '所属模块',
|
||||
dataIndex: 'module',
|
||||
key: 'module',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '操作内容',
|
||||
dataIndex: 'operationContent',
|
||||
key: 'operationContent'
|
||||
},
|
||||
{
|
||||
title: 'IP地址',
|
||||
dataIndex: 'ip',
|
||||
key: 'ip',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '错误信息',
|
||||
dataIndex: 'errorMsg',
|
||||
key: 'errorMsg',
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
title: '操作时间',
|
||||
dataIndex: 'time',
|
||||
key: 'time',
|
||||
width: 180
|
||||
}
|
||||
]
|
||||
|
||||
// 图表引用
|
||||
const chartRef = ref(null)
|
||||
|
||||
// 操作类型标签映射
|
||||
const getOperationTypeLabel = (type) => {
|
||||
const map = {
|
||||
login: '登录',
|
||||
logout: '登出',
|
||||
create: '创建',
|
||||
update: '更新',
|
||||
delete: '删除',
|
||||
query: '查询',
|
||||
export: '导出'
|
||||
}
|
||||
return map[type] || type
|
||||
}
|
||||
|
||||
// 操作类型颜色映射
|
||||
const getOperationTypeColor = (type) => {
|
||||
const map = {
|
||||
login: 'blue',
|
||||
logout: 'purple',
|
||||
create: 'green',
|
||||
update: 'orange',
|
||||
delete: 'red',
|
||||
query: 'default',
|
||||
export: 'cyan'
|
||||
}
|
||||
return map[type] || 'default'
|
||||
}
|
||||
|
||||
// 模块标签映射
|
||||
const getModuleLabel = (module) => {
|
||||
const map = {
|
||||
system: '系统',
|
||||
user: '用户管理',
|
||||
epidemic: '疫情管理',
|
||||
supervision: '监管管理',
|
||||
file: '文件管理',
|
||||
warehouse: '仓库管理',
|
||||
service: '服务管理',
|
||||
personnel: '人员管理'
|
||||
}
|
||||
return map[module] || module
|
||||
}
|
||||
|
||||
// 状态样式类
|
||||
const getStatusClass = (status) => {
|
||||
return status === 'success' ? 'text-success' : 'text-danger'
|
||||
}
|
||||
|
||||
// 搜索日志
|
||||
const handleSearch = (keyword) => {
|
||||
console.log('搜索日志:', keyword)
|
||||
// 这里应该有实际的搜索逻辑
|
||||
}
|
||||
|
||||
// 日期范围改变
|
||||
const handleDateChange = (dates) => {
|
||||
console.log('日期范围改变:', dates)
|
||||
// 这里应该有实际的日期筛选逻辑
|
||||
}
|
||||
|
||||
// 导出日志
|
||||
const handleExportLogs = () => {
|
||||
console.log('导出日志')
|
||||
// 这里应该有实际的导出逻辑
|
||||
}
|
||||
|
||||
// 初始化图表
|
||||
const initChart = () => {
|
||||
if (!chartRef.value) return
|
||||
|
||||
const chart = echarts.init(chartRef.value)
|
||||
|
||||
// 统计数据
|
||||
const operationStats = {
|
||||
login: 15,
|
||||
logout: 12,
|
||||
create: 8,
|
||||
update: 23,
|
||||
delete: 5,
|
||||
query: 45,
|
||||
export: 10
|
||||
}
|
||||
|
||||
const moduleStats = {
|
||||
system: 27,
|
||||
user: 18,
|
||||
epidemic: 15,
|
||||
supervision: 20,
|
||||
file: 12,
|
||||
warehouse: 10,
|
||||
service: 8,
|
||||
personnel: 6
|
||||
}
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: ['操作类型统计', '模块统计']
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
data: Object.keys(operationStats).map(key => getOperationTypeLabel(key)),
|
||||
axisTick: {
|
||||
alignWithLabel: true
|
||||
}
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value'
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: '操作类型统计',
|
||||
type: 'bar',
|
||||
barWidth: '30%',
|
||||
data: Object.values(operationStats),
|
||||
itemStyle: {
|
||||
color: '#1890ff'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
chart.setOption(option)
|
||||
|
||||
// 响应式调整
|
||||
window.addEventListener('resize', () => {
|
||||
chart.resize()
|
||||
})
|
||||
}
|
||||
|
||||
// 组件挂载时初始化图表
|
||||
onMounted(() => {
|
||||
initChart()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.text-success {
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: #f5222d;
|
||||
}
|
||||
</style>
|
||||
@@ -1,347 +1,198 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>市场行情</h1>
|
||||
<a-card style="margin-bottom: 16px;">
|
||||
<a-row gutter={24}>
|
||||
<a-col :span="8">
|
||||
<a-select v-model:value="selectedProduct" placeholder="选择产品类型" allow-clear style="width: 200px;">
|
||||
<a-select-option value="beef">牛肉</a-select-option>
|
||||
<a-select-option value="milk">牛奶</a-select-option>
|
||||
<a-select-option value="calf">牛犊</a-select-option>
|
||||
<a-select-option value="feed">饲料</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-select v-model:value="selectedRegion" placeholder="选择地区" allow-clear style="width: 200px;">
|
||||
<a-select-option value="national">全国</a-select-option>
|
||||
<a-select-option value="north">北方地区</a-select-option>
|
||||
<a-select-option value="south">南方地区</a-select-option>
|
||||
<a-select-option value="east">东部地区</a-select-option>
|
||||
<a-select-option value="west">西部地区</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-button type="primary" @click="handleSearch">查询</a-button>
|
||||
<a-button style="margin-left: 8px;" @click="handleExport">导出数据</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
<div class="market-price-container">
|
||||
<!-- 产品类型选项卡 -->
|
||||
<div class="product-tabs">
|
||||
<a-tabs v-model:activeKey="activeProductTab">
|
||||
<a-tab-pane key="beef" tab="育肥牛"></a-tab-pane>
|
||||
<a-tab-pane key="dairy" tab="繁殖牛"></a-tab-pane>
|
||||
<a-tab-pane key="milk" tab="奶牛"></a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
|
||||
<!-- 价格走势图表 -->
|
||||
<a-card title="价格走势图" style="margin-bottom: 16px;">
|
||||
<div style="height: 400px;" ref="chartRef"></div>
|
||||
</a-card>
|
||||
|
||||
<!-- 最新市场价格表 -->
|
||||
<a-card title="最新市场价格表">
|
||||
<a-table :columns="priceColumns" :data-source="priceData" :pagination="pagination" row-key="id">
|
||||
<template #bodyCell:price="{ record }">
|
||||
<span style="font-weight: bold;">{{ record.price }}</span>
|
||||
</template>
|
||||
<template #bodyCell:change="{ record }">
|
||||
<span :class="record.change > 0 ? 'text-danger' : 'text-success'">
|
||||
{{ record.change > 0 ? '+' : '' }}{{ record.change }}%
|
||||
</span>
|
||||
</template>
|
||||
<template #bodyCell:date="{ record }">
|
||||
{{ dayjs(record.date).format('YYYY-MM-DD') }}
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 市场分析报告 -->
|
||||
<a-card title="市场分析报告" style="margin-top: 16px;">
|
||||
<div class="report-content">
|
||||
<h3>近期市场行情分析</h3>
|
||||
<p>根据最新数据显示,近期牛肉价格呈现{{ overallTrend }}趋势,主要受以下因素影响:</p>
|
||||
<ol>
|
||||
<li>市场供需关系变化</li>
|
||||
<li>养殖成本波动</li>
|
||||
<li>季节性需求变化</li>
|
||||
<li>政策因素影响</li>
|
||||
</ol>
|
||||
<p>预计未来一段时间内,价格将保持{{ futureTrend }}态势。建议养殖户和相关企业密切关注市场动态,合理安排生产和销售计划。</p>
|
||||
<div class="content-layout">
|
||||
<!-- 左侧内容 -->
|
||||
<div class="left-content">
|
||||
<!-- 本市价格信息卡片 -->
|
||||
<a-card class="price-info-card">
|
||||
<template #title>
|
||||
<div class="card-title">
|
||||
<span>本市{{ productTypeText }}价格</span>
|
||||
<span class="price-date">2025-01-01</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="price-info">
|
||||
<div class="current-price">
|
||||
<span class="price-value">{{ currentPrice }}</span>
|
||||
<span class="price-unit">元/公斤</span>
|
||||
</div>
|
||||
<div class="price-change">
|
||||
<span class="change-value">+0.30</span>
|
||||
<span class="change-percent">+1.32%</span>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
<!-- 价格走势图表 -->
|
||||
<a-card class="chart-card" title="价格走势">
|
||||
<div ref="priceChartRef" class="chart-container"></div>
|
||||
</a-card>
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
<!-- 右侧内容 -->
|
||||
<div class="right-content">
|
||||
<!-- 各省价格对比图表 -->
|
||||
<a-card class="province-chart-card" title="各省价格对比">
|
||||
<div ref="provinceChartRef" class="province-chart-container"></div>
|
||||
</a-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import axios from 'axios'
|
||||
import dayjs from 'dayjs'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
|
||||
// 选择的产品和地区
|
||||
const selectedProduct = ref('beef')
|
||||
const selectedRegion = ref('national')
|
||||
// 当前选中的产品类型
|
||||
const activeProductTab = ref('beef')
|
||||
|
||||
// 分页配置
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
// 产品类型文本映射
|
||||
const productTypeText = computed(() => {
|
||||
const map = {
|
||||
beef: '育肥牛',
|
||||
dairy: '繁殖牛',
|
||||
milk: '奶牛'
|
||||
}
|
||||
return map[activeProductTab.value] || '育肥牛'
|
||||
})
|
||||
|
||||
// 当前价格
|
||||
const currentPrice = ref('--')
|
||||
|
||||
// 图表引用
|
||||
const chartRef = ref(null)
|
||||
const priceChartRef = ref(null)
|
||||
const provinceChartRef = ref(null)
|
||||
|
||||
// 整体趋势和未来趋势
|
||||
const overallTrend = ref('稳中有升')
|
||||
const futureTrend = ref('相对稳定')
|
||||
|
||||
// 价格数据
|
||||
const priceData = ref([
|
||||
{
|
||||
id: '1',
|
||||
productType: '牛肉',
|
||||
spec: '一等品',
|
||||
unit: '元/公斤',
|
||||
price: 78.50,
|
||||
change: 1.2,
|
||||
region: '全国均价',
|
||||
date: '2024-04-10T00:00:00Z'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
productType: '牛肉',
|
||||
spec: '二等品',
|
||||
unit: '元/公斤',
|
||||
price: 68.20,
|
||||
change: 0.8,
|
||||
region: '全国均价',
|
||||
date: '2024-04-10T00:00:00Z'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
productType: '牛奶',
|
||||
spec: '生鲜乳',
|
||||
unit: '元/公斤',
|
||||
price: 4.30,
|
||||
change: -0.5,
|
||||
region: '全国均价',
|
||||
date: '2024-04-10T00:00:00Z'
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
productType: '牛犊',
|
||||
spec: '优良品种',
|
||||
unit: '元/头',
|
||||
price: 5800,
|
||||
change: 2.5,
|
||||
region: '全国均价',
|
||||
date: '2024-04-10T00:00:00Z'
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
productType: '饲料',
|
||||
spec: '精饲料',
|
||||
unit: '元/吨',
|
||||
price: 3200,
|
||||
change: 1.8,
|
||||
region: '全国均价',
|
||||
date: '2024-04-10T00:00:00Z'
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
productType: '牛肉',
|
||||
spec: '一等品',
|
||||
unit: '元/公斤',
|
||||
price: 82.30,
|
||||
change: 1.5,
|
||||
region: '北方地区',
|
||||
date: '2024-04-10T00:00:00Z'
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
productType: '牛肉',
|
||||
spec: '一等品',
|
||||
unit: '元/公斤',
|
||||
price: 76.80,
|
||||
change: 1.0,
|
||||
region: '南方地区',
|
||||
date: '2024-04-10T00:00:00Z'
|
||||
},
|
||||
{
|
||||
id: '8',
|
||||
productType: '牛肉',
|
||||
spec: '一等品',
|
||||
unit: '元/公斤',
|
||||
price: 85.50,
|
||||
change: 2.0,
|
||||
region: '东部地区',
|
||||
date: '2024-04-10T00:00:00Z'
|
||||
},
|
||||
{
|
||||
id: '9',
|
||||
productType: '牛肉',
|
||||
spec: '一等品',
|
||||
unit: '元/公斤',
|
||||
price: 72.10,
|
||||
change: 0.5,
|
||||
region: '西部地区',
|
||||
date: '2024-04-10T00:00:00Z'
|
||||
},
|
||||
{
|
||||
id: '10',
|
||||
productType: '饲料',
|
||||
spec: '粗饲料',
|
||||
unit: '元/吨',
|
||||
price: 1800,
|
||||
change: 0.2,
|
||||
region: '全国均价',
|
||||
date: '2024-04-10T00:00:00Z'
|
||||
}
|
||||
])
|
||||
|
||||
// 价格表格列定义
|
||||
const priceColumns = [
|
||||
{
|
||||
title: '产品类型',
|
||||
dataIndex: 'productType',
|
||||
key: 'productType',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '规格',
|
||||
dataIndex: 'spec',
|
||||
key: 'spec',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '单位',
|
||||
dataIndex: 'unit',
|
||||
key: 'unit',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '价格',
|
||||
dataIndex: 'price',
|
||||
key: 'price',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '涨跌幅',
|
||||
dataIndex: 'change',
|
||||
key: 'change',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '地区',
|
||||
dataIndex: 'region',
|
||||
key: 'region',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '数据日期',
|
||||
dataIndex: 'date',
|
||||
key: 'date',
|
||||
width: 150
|
||||
}
|
||||
]
|
||||
|
||||
// 查询按钮点击事件
|
||||
const handleSearch = () => {
|
||||
console.log('查询条件:', { selectedProduct: selectedProduct.value, selectedRegion: selectedRegion.value })
|
||||
// 这里应该有实际的查询逻辑
|
||||
}
|
||||
|
||||
// 导出按钮点击事件
|
||||
const handleExport = () => {
|
||||
console.log('导出数据')
|
||||
// 这里应该有实际的导出逻辑
|
||||
}
|
||||
|
||||
// 初始化图表
|
||||
const initChart = () => {
|
||||
if (!chartRef.value) return
|
||||
// 初始化价格走势图表
|
||||
const initPriceChart = () => {
|
||||
if (!priceChartRef.value) return
|
||||
|
||||
const chart = echarts.init(chartRef.value)
|
||||
const chart = echarts.init(priceChartRef.value)
|
||||
|
||||
// 模拟历史价格数据
|
||||
const historicalData = [
|
||||
{ date: '2024-03-11', beef: 75.2, milk: 4.4, feed: 3150 },
|
||||
{ date: '2024-03-18', beef: 76.5, milk: 4.4, feed: 3160 },
|
||||
{ date: '2024-03-25', beef: 77.1, milk: 4.3, feed: 3180 },
|
||||
{ date: '2024-04-01', beef: 77.8, milk: 4.3, feed: 3190 },
|
||||
{ date: '2024-04-08', beef: 78.2, milk: 4.3, feed: 3200 },
|
||||
{ date: '2024-04-15', beef: 78.5, milk: 4.3, feed: 3200 }
|
||||
]
|
||||
const dates = ['2024-12-19', '2024-12-20', '2024-12-21', '2024-12-22', '2024-12-23',
|
||||
'2024-12-24', '2024-12-25', '2024-12-26', '2024-12-27', '2024-12-28',
|
||||
'2024-12-29', '2024-12-30', '2024-12-31', '2025-01-01']
|
||||
|
||||
const prices = [23.10, 20.65, 24.90, 21.78, 22.15, 24.30, 23.20, 21.60, 20.35, 24.05, 23.45, 20.95, 22.50, 21.80]
|
||||
|
||||
const option = {
|
||||
title: {
|
||||
text: '产品价格走势(最近6周)',
|
||||
left: 'center'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross'
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: ['牛肉(元/公斤)', '牛奶(元/公斤)', '饲料(元/吨)'],
|
||||
bottom: 10
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '15%',
|
||||
top: '15%',
|
||||
bottom: '3%',
|
||||
top: '8%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: historicalData.map(item => item.date)
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: dates,
|
||||
axisLabel: {
|
||||
rotate: 45,
|
||||
fontSize: 10
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
name: '价格',
|
||||
position: 'left'
|
||||
}
|
||||
],
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
min: 0,
|
||||
max: 25,
|
||||
interval: 5
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '牛肉(元/公斤)',
|
||||
type: 'line',
|
||||
data: historicalData.map(item => item.beef),
|
||||
data: prices,
|
||||
symbol: 'circle',
|
||||
symbolSize: 8,
|
||||
symbolSize: 6,
|
||||
itemStyle: {
|
||||
color: '#1890ff'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 3
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '牛奶(元/公斤)',
|
||||
type: 'line',
|
||||
data: historicalData.map(item => item.milk),
|
||||
symbol: 'circle',
|
||||
symbolSize: 8,
|
||||
itemStyle: {
|
||||
color: '#52c41a'
|
||||
width: 2
|
||||
},
|
||||
lineStyle: {
|
||||
width: 3
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
formatter: '{c}'
|
||||
}
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
chart.setOption(option)
|
||||
|
||||
// 响应式调整
|
||||
window.addEventListener('resize', () => {
|
||||
chart.resize()
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化各省价格对比图表
|
||||
const initProvinceChart = () => {
|
||||
if (!provinceChartRef.value) return
|
||||
|
||||
const chart = echarts.init(provinceChartRef.value)
|
||||
|
||||
// 模拟各省价格数据
|
||||
const provinces = ['上海市', '云南省', '内蒙古自治区', '北京市', '吉林省', '四川省', '天津市', '宁夏回族自治区', '安徽省', '山东省', '山西省', '广东省', '广西壮族自治区', '新疆维吾尔自治区', '江苏省', '江西省', '河北省', '河南省', '浙江省', '海南省', '湖北省', '湖南省', '甘肃省', '福建省', '贵州省', '辽宁省', '重庆市', '陕西省', '青海省', '黑龙江省']
|
||||
|
||||
const prices = [36, 34.85, 34.85, 35.7, 33.26, 34.9, 35.4, 36.51, 33.5, 35.4, 35.8, 35.3, 32.6, 35.8, 34.1, 35.2, 33.2, 36, 35, 35.4, 32, 33.2, 34.8, 32.86, 32.7, 33, 32.8, 33.4, 33, 35.4]
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
top: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'value',
|
||||
min: 0,
|
||||
max: 40,
|
||||
interval: 10
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
data: provinces,
|
||||
axisLabel: {
|
||||
fontSize: 10
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '饲料(元/吨)',
|
||||
type: 'line',
|
||||
data: historicalData.map(item => item.feed),
|
||||
symbol: 'circle',
|
||||
symbolSize: 8,
|
||||
type: 'bar',
|
||||
data: prices,
|
||||
itemStyle: {
|
||||
color: '#fa8c16'
|
||||
color: '#4e7bff'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 3
|
||||
label: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
formatter: '{c}'
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -357,40 +208,102 @@ const initChart = () => {
|
||||
|
||||
// 组件挂载时初始化图表
|
||||
onMounted(() => {
|
||||
initChart()
|
||||
// 设置当前价格
|
||||
currentPrice.value = '23.10'
|
||||
|
||||
// 初始化图表
|
||||
setTimeout(() => {
|
||||
initPriceChart()
|
||||
initProvinceChart()
|
||||
}, 100)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.text-danger {
|
||||
color: #f5222d;
|
||||
}
|
||||
|
||||
.text-success {
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.report-content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.report-content h3 {
|
||||
margin-bottom: 16px;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.report-content p {
|
||||
margin-bottom: 12px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.report-content ol {
|
||||
margin-left: 24px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.report-content li {
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.market-price-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.product-tabs {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.content-layout {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.left-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.right-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.price-info-card {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.price-date {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.price-info {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 20px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.current-price {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.price-value {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
color: #1890ff;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.price-unit {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.price-change {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.change-value {
|
||||
font-size: 16px;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.change-percent {
|
||||
font-size: 14px;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.province-chart-container {
|
||||
height: 600px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,11 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>人员管理</h1>
|
||||
<a-card style="margin-bottom: 16px;">
|
||||
|
||||
<!-- 添加router-view来渲染子路由内容 -->
|
||||
<router-view />
|
||||
|
||||
<a-card style="margin-bottom: 16px;" v-if="!$route.path.includes('/personnel/')">
|
||||
<a-row gutter={24}>
|
||||
<a-col :span="8">
|
||||
<a-button type="primary" @click="handleAddUser">添加人员</a-button>
|
||||
@@ -22,7 +26,7 @@
|
||||
</a-card>
|
||||
|
||||
<!-- 人员列表 -->
|
||||
<a-card title="人员列表">
|
||||
<a-card title="人员列表" v-if="!$route.path.includes('/personnel/')">
|
||||
<a-table :columns="userColumns" :data-source="userList" :pagination="pagination" row-key="id">
|
||||
<template #bodyCell:status="{ record }">
|
||||
<a-badge :status="record.status === 'active' ? 'success' : 'default'" :text="record.status === 'active' ? '在职' : '离职'" />
|
||||
|
||||
@@ -1,371 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>监管仪表板</h1>
|
||||
|
||||
<!-- 数据统计卡片 -->
|
||||
<a-row gutter={24}>
|
||||
<a-col :span="6">
|
||||
<a-card class="stat-card" :body-style="{ padding: '20px' }">
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">{{ stats.entityCount }}</div>
|
||||
<div class="stat-label">监管实体总数</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card class="stat-card" :body-style="{ padding: '20px' }">
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">{{ stats.inspectionCount }}</div>
|
||||
<div class="stat-label">检查总次数</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card class="stat-card" :body-style="{ padding: '20px' }">
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">{{ stats.pendingCount }}</div>
|
||||
<div class="stat-label">待处理任务</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card class="stat-card" :body-style="{ padding: '20px' }">
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">{{ stats.abnormalCount }}</div>
|
||||
<div class="stat-label">异常情况数</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 图表区域 -->
|
||||
<a-row gutter={24} style="margin-top: 24px;">
|
||||
<a-col :span="12">
|
||||
<a-card title="监管趋势图" :body-style="{ padding: 0 }">
|
||||
<div style="height: 300px; padding: 10px;">
|
||||
<div id="trend-chart" style="width: 100%; height: 100%;"></div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-card title="监管类型分布" :body-style="{ padding: 0 }">
|
||||
<div style="height: 300px; padding: 10px;">
|
||||
<div id="distribution-chart" style="width: 100%; height: 100%;"></div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 最新监管任务 -->
|
||||
<a-card title="最新监管任务" style="margin-top: 24px;">
|
||||
<a-table :columns="taskColumns" :data-source="recentTasks" pagination={false}>
|
||||
<template #bodyCell:status="{ record }">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template #bodyCell:action="{ record }">
|
||||
<a-button type="link" @click="viewTaskDetail(record.id)">查看详情</a-button>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { message } from 'antd'
|
||||
import axios from 'axios'
|
||||
import * as echarts from 'echarts'
|
||||
import { getSupervisionStats } from '@/mock'
|
||||
|
||||
const stats = ref({
|
||||
entityCount: 0,
|
||||
inspectionCount: 0,
|
||||
pendingCount: 0,
|
||||
abnormalCount: 0
|
||||
})
|
||||
const recentTasks = ref([])
|
||||
let trendChart = null
|
||||
let distributionChart = null
|
||||
|
||||
// 任务表格列定义
|
||||
const taskColumns = [
|
||||
{
|
||||
title: '任务ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id'
|
||||
},
|
||||
{
|
||||
title: '任务名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name'
|
||||
},
|
||||
{
|
||||
title: '监管对象',
|
||||
dataIndex: 'target',
|
||||
key: 'target'
|
||||
},
|
||||
{
|
||||
title: '负责人',
|
||||
dataIndex: 'manager',
|
||||
key: 'manager'
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
slots: { customRender: 'status' }
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'create_time',
|
||||
key: 'create_time'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
slots: { customRender: 'action' }
|
||||
}
|
||||
]
|
||||
|
||||
// 根据状态获取标签颜色
|
||||
const getStatusColor = (status) => {
|
||||
const colorMap = {
|
||||
pending: 'orange',
|
||||
in_progress: 'blue',
|
||||
completed: 'green',
|
||||
abnormal: 'red',
|
||||
canceled: 'default'
|
||||
}
|
||||
return colorMap[status] || 'default'
|
||||
}
|
||||
|
||||
// 根据状态获取显示文本
|
||||
const getStatusText = (status) => {
|
||||
const textMap = {
|
||||
pending: '待处理',
|
||||
in_progress: '处理中',
|
||||
completed: '已完成',
|
||||
abnormal: '异常',
|
||||
canceled: '已取消'
|
||||
}
|
||||
return textMap[status] || status
|
||||
}
|
||||
|
||||
// 获取监管统计数据
|
||||
const fetchStats = async () => {
|
||||
try {
|
||||
// 尝试从API获取数据
|
||||
const response = await axios.get('/api/supervision/stats')
|
||||
if (response.data.code === 200) {
|
||||
const data = response.data.data
|
||||
stats.value.entityCount = data.entityCount || 0
|
||||
stats.value.inspectionCount = data.inspectionCount || 0
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取监管统计数据失败,使用模拟数据:', error)
|
||||
// 使用模拟数据
|
||||
const mockResponse = await getSupervisionStats()
|
||||
if (mockResponse.code === 200) {
|
||||
const data = mockResponse.data
|
||||
stats.value.entityCount = data.entityCount || 0
|
||||
stats.value.inspectionCount = data.inspectionCount || 0
|
||||
}
|
||||
}
|
||||
|
||||
// 设置其他模拟数据
|
||||
stats.value.pendingCount = Math.floor(Math.random() * 20) + 5
|
||||
stats.value.abnormalCount = Math.floor(Math.random() * 10)
|
||||
}
|
||||
|
||||
// 获取最新任务数据
|
||||
const fetchRecentTasks = async () => {
|
||||
try {
|
||||
// 这里应该从API获取数据
|
||||
// 由于没有实际API,使用模拟数据
|
||||
recentTasks.value = [
|
||||
{ id: 1, name: '企业安全检查', target: '某食品加工厂', manager: '张三', status: 'pending', create_time: '2024-01-10 09:00:00' },
|
||||
{ id: 2, name: '环境监测', target: '某化工厂', manager: '李四', status: 'in_progress', create_time: '2024-01-09 14:30:00' },
|
||||
{ id: 3, name: '疫情防控检查', target: '某商场', manager: '王五', status: 'completed', create_time: '2024-01-08 11:20:00' },
|
||||
{ id: 4, name: '消防安全检查', target: '某酒店', manager: '赵六', status: 'abnormal', create_time: '2024-01-07 16:40:00' },
|
||||
{ id: 5, name: '质量检查', target: '某药品生产企业', manager: '钱七', status: 'pending', create_time: '2024-01-06 10:15:00' }
|
||||
]
|
||||
} catch (error) {
|
||||
console.error('获取任务数据失败:', error)
|
||||
message.error('获取任务数据失败,请稍后重试')
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化图表
|
||||
const initCharts = () => {
|
||||
// 趋势图
|
||||
const trendChartDom = document.getElementById('trend-chart')
|
||||
if (trendChartDom) {
|
||||
trendChart = echarts.init(trendChartDom)
|
||||
const trendOption = {
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
legend: {
|
||||
data: ['检查次数', '异常情况']
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['1月', '2月', '3月', '4月', '5月', '6月']
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '检查次数',
|
||||
type: 'line',
|
||||
data: [12, 19, 3, 5, 2, 3],
|
||||
smooth: true
|
||||
},
|
||||
{
|
||||
name: '异常情况',
|
||||
type: 'line',
|
||||
data: [5, 3, 2, 4, 1, 2],
|
||||
smooth: true
|
||||
}
|
||||
]
|
||||
}
|
||||
trendChart.setOption(trendOption)
|
||||
}
|
||||
|
||||
// 分布饼图
|
||||
const distributionChartDom = document.getElementById('distribution-chart')
|
||||
if (distributionChartDom) {
|
||||
distributionChart = echarts.init(distributionChartDom)
|
||||
const distributionOption = {
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
legend: {
|
||||
top: '5%',
|
||||
left: 'center'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '监管类型',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2
|
||||
},
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center'
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: '18',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: 30,
|
||||
name: '安全检查'
|
||||
},
|
||||
{
|
||||
value: 25,
|
||||
name: '环境监测'
|
||||
},
|
||||
{
|
||||
value: 20,
|
||||
name: '疫情防控'
|
||||
},
|
||||
{
|
||||
value: 15,
|
||||
name: '质量检查'
|
||||
},
|
||||
{
|
||||
value: 10,
|
||||
name: '其他'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
distributionChart.setOption(distributionOption)
|
||||
}
|
||||
}
|
||||
|
||||
// 响应窗口大小变化
|
||||
const handleResize = () => {
|
||||
if (trendChart) {
|
||||
trendChart.resize()
|
||||
}
|
||||
if (distributionChart) {
|
||||
distributionChart.resize()
|
||||
}
|
||||
}
|
||||
|
||||
// 查看任务详情
|
||||
const viewTaskDetail = (taskId) => {
|
||||
message.info(`查看任务ID: ${taskId} 的详情`)
|
||||
// 这里可以跳转到任务详情页面
|
||||
// router.push(`/supervision/task/${taskId}`)
|
||||
}
|
||||
|
||||
// 组件挂载
|
||||
onMounted(() => {
|
||||
fetchStats()
|
||||
fetchRecentTasks()
|
||||
setTimeout(() => {
|
||||
initCharts()
|
||||
}, 100)
|
||||
window.addEventListener('resize', handleResize)
|
||||
})
|
||||
|
||||
// 组件卸载
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
if (trendChart) {
|
||||
trendChart.dispose()
|
||||
}
|
||||
if (distributionChart) {
|
||||
distributionChart.dispose()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.stat-card {
|
||||
height: 100%;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: #1890ff;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
@@ -1,366 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>用户管理</h1>
|
||||
<!-- 工具栏 -->
|
||||
<a-card style="margin-bottom: 16px;">
|
||||
<a-row gutter={16}>
|
||||
<a-col :span="8">
|
||||
<a-input
|
||||
v-model:value="searchText"
|
||||
placeholder="请输入用户名或ID搜索"
|
||||
allow-clear
|
||||
style="width: 100%;"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="16" style="text-align: right;">
|
||||
<a-button type="primary" @click="handleAddUser">
|
||||
<user-add-outlined /> 添加用户
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
|
||||
<!-- 用户表格 -->
|
||||
<a-card>
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="users"
|
||||
:pagination="pagination"
|
||||
row-key="id"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #bodyCell:actions="{ record }">
|
||||
<a-space>
|
||||
<a-button type="link" @click="handleEditUser(record)">编辑</a-button>
|
||||
<a-button type="link" danger @click="handleDeleteUser(record.id)">删除</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #bodyCell:status="{ record }">
|
||||
<a-tag :color="record.status === 1 ? 'green' : 'red'">
|
||||
{{ record.status === 1 ? '启用' : '禁用' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 添加/编辑用户弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="showModal"
|
||||
:title="modalTitle"
|
||||
footer=""
|
||||
width="600px"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formState"
|
||||
:rules="formRules"
|
||||
layout="vertical"
|
||||
@finish="handleSubmit"
|
||||
>
|
||||
<a-form-item label="用户名" name="username">
|
||||
<a-input v-model:value="formState.username" placeholder="请输入用户名" />
|
||||
</a-form-item>
|
||||
<a-form-item label="真实姓名" name="real_name">
|
||||
<a-input v-model:value="formState.real_name" placeholder="请输入真实姓名" />
|
||||
</a-form-item>
|
||||
<a-form-item label="手机号" name="phone">
|
||||
<a-input v-model:value="formState.phone" placeholder="请输入手机号" />
|
||||
</a-form-item>
|
||||
<a-form-item label="邮箱" name="email">
|
||||
<a-input v-model:value="formState.email" placeholder="请输入邮箱" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
v-if="isAddUser"
|
||||
label="密码"
|
||||
name="password"
|
||||
>
|
||||
<a-input-password v-model:value="formState.password" placeholder="请输入密码" />
|
||||
</a-form-item>
|
||||
<a-form-item label="用户角色" name="role">
|
||||
<a-select v-model:value="formState.role" placeholder="请选择用户角色">
|
||||
<a-select-option value="admin">管理员</a-select-option>
|
||||
<a-select-option value="user">普通用户</a-select-option>
|
||||
<a-select-option value="guest">访客</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="状态" name="status">
|
||||
<a-switch v-model:checked="formState.status" checked-children="启用" un-checked-children="禁用" />
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-space>
|
||||
<a-button @click="handleCancel">取消</a-button>
|
||||
<a-button type="primary" html-type="submit">确认</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { message, Modal } from 'antd'
|
||||
import { UserAddOutlined } from '@ant-design/icons-vue'
|
||||
import axios from 'axios'
|
||||
|
||||
// 搜索文本
|
||||
const searchText = ref('')
|
||||
// 用户数据
|
||||
const users = ref([])
|
||||
// 分页配置
|
||||
const pagination = {
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total) => `共 ${total} 条数据`
|
||||
}
|
||||
|
||||
// 弹窗状态
|
||||
const showModal = ref(false)
|
||||
const isAddUser = ref(true)
|
||||
const modalTitle = computed(() => isAddUser.value ? '添加用户' : '编辑用户')
|
||||
const formRef = ref()
|
||||
|
||||
// 表单状态
|
||||
const formState = reactive({
|
||||
id: '',
|
||||
username: '',
|
||||
real_name: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
password: '',
|
||||
role: 'user',
|
||||
status: true
|
||||
})
|
||||
|
||||
// 表单验证规则
|
||||
const formRules = {
|
||||
username: [
|
||||
{ required: true, message: '请输入用户名', trigger: 'blur' },
|
||||
{ min: 3, max: 20, message: '用户名长度应在3到20个字符之间', trigger: 'blur' }
|
||||
],
|
||||
real_name: [
|
||||
{ required: true, message: '请输入真实姓名', trigger: 'blur' }
|
||||
],
|
||||
phone: [
|
||||
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号格式', trigger: 'blur' }
|
||||
],
|
||||
email: [
|
||||
{ required: true, message: '请输入邮箱', trigger: 'blur' },
|
||||
{ type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||
{ min: 6, max: 32, message: '密码长度应在6到32个字符之间', trigger: 'blur' }
|
||||
],
|
||||
role: [
|
||||
{ required: true, message: '请选择用户角色', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
|
||||
// 表格列定义
|
||||
const columns = [
|
||||
{
|
||||
title: '用户ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id'
|
||||
},
|
||||
{
|
||||
title: '用户名',
|
||||
dataIndex: 'username',
|
||||
key: 'username'
|
||||
},
|
||||
{
|
||||
title: '真实姓名',
|
||||
dataIndex: 'real_name',
|
||||
key: 'real_name'
|
||||
},
|
||||
{
|
||||
title: '手机号',
|
||||
dataIndex: 'phone',
|
||||
key: 'phone'
|
||||
},
|
||||
{
|
||||
title: '邮箱',
|
||||
dataIndex: 'email',
|
||||
key: 'email'
|
||||
},
|
||||
{
|
||||
title: '角色',
|
||||
dataIndex: 'role',
|
||||
key: 'role'
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
slots: { customRender: 'status' }
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'created_at',
|
||||
key: 'created_at'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
slots: { customRender: 'actions' }
|
||||
}
|
||||
]
|
||||
|
||||
// 获取用户列表
|
||||
const fetchUsers = async (page = 1, pageSize = 10, keyword = '') => {
|
||||
try {
|
||||
// 这里应该从API获取用户数据
|
||||
// 由于没有实际API,使用模拟数据
|
||||
// 模拟分页数据
|
||||
const mockUsers = [
|
||||
{ id: 1, username: 'admin', real_name: '管理员', phone: '13800138000', email: 'admin@example.com', role: 'admin', status: 1, created_at: '2024-01-01 10:00:00' },
|
||||
{ id: 2, username: 'user1', real_name: '用户一', phone: '13800138001', email: 'user1@example.com', role: 'user', status: 1, created_at: '2024-01-02 11:00:00' },
|
||||
{ id: 3, username: 'user2', real_name: '用户二', phone: '13800138002', email: 'user2@example.com', role: 'user', status: 0, created_at: '2024-01-03 12:00:00' },
|
||||
{ id: 4, username: 'user3', real_name: '用户三', phone: '13800138003', email: 'user3@example.com', role: 'guest', status: 1, created_at: '2024-01-04 13:00:00' },
|
||||
{ id: 5, username: 'user4', real_name: '用户四', phone: '13800138004', email: 'user4@example.com', role: 'user', status: 1, created_at: '2024-01-05 14:00:00' }
|
||||
]
|
||||
|
||||
// 模拟搜索
|
||||
const filteredUsers = keyword ?
|
||||
mockUsers.filter(user =>
|
||||
user.username.includes(keyword) ||
|
||||
user.real_name.includes(keyword) ||
|
||||
user.id.toString().includes(keyword)
|
||||
) :
|
||||
mockUsers
|
||||
|
||||
// 模拟分页
|
||||
const start = (page - 1) * pageSize
|
||||
const end = start + pageSize
|
||||
const paginatedUsers = filteredUsers.slice(start, end)
|
||||
|
||||
users.value = paginatedUsers
|
||||
pagination.total = filteredUsers.length
|
||||
pagination.current = page
|
||||
pagination.pageSize = pageSize
|
||||
} catch (error) {
|
||||
console.error('获取用户列表失败:', error)
|
||||
message.error('获取用户列表失败,请稍后重试')
|
||||
}
|
||||
}
|
||||
|
||||
// 表格变化处理
|
||||
const handleTableChange = (paginationObj) => {
|
||||
pagination.current = paginationObj.current
|
||||
pagination.pageSize = paginationObj.pageSize
|
||||
fetchUsers(pagination.current, pagination.pageSize, searchText.value)
|
||||
}
|
||||
|
||||
// 搜索用户
|
||||
const handleSearch = () => {
|
||||
fetchUsers(1, pagination.pageSize, searchText.value)
|
||||
}
|
||||
|
||||
// 添加用户
|
||||
const handleAddUser = () => {
|
||||
isAddUser.value = true
|
||||
// 重置表单
|
||||
formState.id = ''
|
||||
formState.username = ''
|
||||
formState.real_name = ''
|
||||
formState.phone = ''
|
||||
formState.email = ''
|
||||
formState.password = ''
|
||||
formState.role = 'user'
|
||||
formState.status = true
|
||||
showModal.value = true
|
||||
}
|
||||
|
||||
// 编辑用户
|
||||
const handleEditUser = (record) => {
|
||||
isAddUser.value = false
|
||||
// 填充表单数据
|
||||
formState.id = record.id
|
||||
formState.username = record.username
|
||||
formState.real_name = record.real_name
|
||||
formState.phone = record.phone
|
||||
formState.email = record.email
|
||||
formState.role = record.role
|
||||
formState.status = record.status === 1
|
||||
showModal.value = true
|
||||
}
|
||||
|
||||
// 删除用户
|
||||
const handleDeleteUser = (userId) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: '确定要删除这个用户吗?',
|
||||
okText: '确定',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
try {
|
||||
// 这里应该调用API删除用户
|
||||
// 由于没有实际API,模拟删除
|
||||
console.log('删除用户:', userId)
|
||||
message.success('用户删除成功')
|
||||
// 重新获取用户列表
|
||||
fetchUsers(pagination.current, pagination.pageSize, searchText.value)
|
||||
} catch (error) {
|
||||
console.error('删除用户失败:', error)
|
||||
message.error('删除用户失败,请稍后重试')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
await formRef.value.validate()
|
||||
|
||||
// 准备提交数据
|
||||
const submitData = {
|
||||
username: formState.username,
|
||||
real_name: formState.real_name,
|
||||
phone: formState.phone,
|
||||
email: formState.email,
|
||||
role: formState.role,
|
||||
status: formState.status ? 1 : 0
|
||||
}
|
||||
|
||||
// 如果是添加用户,添加密码字段
|
||||
if (isAddUser.value) {
|
||||
submitData.password = formState.password
|
||||
}
|
||||
|
||||
// 这里应该调用API提交数据
|
||||
// 由于没有实际API,模拟提交
|
||||
console.log(isAddUser.value ? '添加用户:' : '编辑用户:', submitData)
|
||||
|
||||
// 显示成功消息
|
||||
message.success(isAddUser.value ? '用户添加成功' : '用户编辑成功')
|
||||
|
||||
// 关闭弹窗
|
||||
showModal.value = false
|
||||
|
||||
// 重新获取用户列表
|
||||
fetchUsers(pagination.current, pagination.pageSize, searchText.value)
|
||||
} catch (error) {
|
||||
console.error(isAddUser.value ? '添加用户失败:' : '编辑用户失败:', error)
|
||||
message.error(isAddUser.value ? '添加用户失败,请稍后重试' : '编辑用户失败,请稍后重试')
|
||||
}
|
||||
}
|
||||
|
||||
// 取消操作
|
||||
const handleCancel = () => {
|
||||
showModal.value = false
|
||||
formRef.value.resetFields()
|
||||
}
|
||||
|
||||
// 监听搜索文本变化
|
||||
searchText.value = ''
|
||||
fetchUsers()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 用户管理页面样式 */
|
||||
</style>
|
||||
@@ -1,874 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>可视化分析</h1>
|
||||
|
||||
<!-- 图表选择器 -->
|
||||
<div style="margin-bottom: 16px;">
|
||||
<a-select v-model:value="selectedChart" placeholder="选择图表类型" style="width: 200px;" @change="changeChart">
|
||||
<a-select-option value="overview">综合概览</a-select-option>
|
||||
<a-select-option value="supervision">监管数据</a-select-option>
|
||||
<a-select-option value="epidemic">疫情数据</a-select-option>
|
||||
<a-select-option value="approval">审批数据</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
|
||||
<!-- 综合概览 -->
|
||||
<div v-if="selectedChart === 'overview'">
|
||||
<a-row gutter={24}>
|
||||
<a-col :span="16">
|
||||
<a-card title="数据趋势总览" :body-style="{ padding: 0 }">
|
||||
<div style="height: 400px; padding: 10px;">
|
||||
<div id="overview-chart" style="width: 100%; height: 100%;"></div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-card title="数据分类占比" :body-style="{ padding: 0 }">
|
||||
<div style="height: 400px; padding: 10px;">
|
||||
<div id="category-chart" style="width: 100%; height: 100%;"></div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row gutter={24} style="margin-top: 24px;">
|
||||
<a-col :span="12">
|
||||
<a-card title="月度活跃度" :body-style="{ padding: 0 }">
|
||||
<div style="height: 300px; padding: 10px;">
|
||||
<div id="activity-chart" style="width: 100%; height: 100%;"></div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-card title="区域分布图" :body-style="{ padding: 0 }">
|
||||
<div style="height: 300px; padding: 10px;">
|
||||
<div id="region-chart" style="width: 100%; height: 100%;"></div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 监管数据可视化 -->
|
||||
<div v-if="selectedChart === 'supervision'">
|
||||
<a-row gutter={24}>
|
||||
<a-col :span="12">
|
||||
<a-card title="监管类型分布" :body-style="{ padding: 0 }">
|
||||
<div style="height: 300px; padding: 10px;">
|
||||
<div id="supervision-type-chart" style="width: 100%; height: 100%;"></div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-card title="异常情况趋势" :body-style="{ padding: 0 }">
|
||||
<div style="height: 300px; padding: 10px;">
|
||||
<div id="abnormal-trend-chart" style="width: 100%; height: 100%;"></div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row gutter={24} style="margin-top: 24px;">
|
||||
<a-col :span="24">
|
||||
<a-card title="监管覆盖率" :body-style="{ padding: 0 }">
|
||||
<div style="height: 300px; padding: 10px;">
|
||||
<div id="coverage-chart" style="width: 100%; height: 100%;"></div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 疫情数据可视化 -->
|
||||
<div v-if="selectedChart === 'epidemic'">
|
||||
<a-row gutter={24}>
|
||||
<a-col :span="16">
|
||||
<a-card title="疫情发展趋势" :body-style="{ padding: 0 }">
|
||||
<div style="height: 400px; padding: 10px;">
|
||||
<div id="epidemic-trend-chart" style="width: 100%; height: 100%;"></div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-card title="疫苗接种进度" :body-style="{ padding: 0 }">
|
||||
<div style="height: 400px; padding: 10px;">
|
||||
<div id="vaccine-progress-chart" style="width: 100%; height: 100%;"></div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row gutter={24} style="margin-top: 24px;">
|
||||
<a-col :span="12">
|
||||
<a-card title="区域疫情分布" :body-style="{ padding: 0 }">
|
||||
<div style="height: 300px; padding: 10px;">
|
||||
<div id="epidemic-region-chart" style="width: 100%; height: 100%;"></div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-card title="检测量统计" :body-style="{ padding: 0 }">
|
||||
<div style="height: 300px; padding: 10px;">
|
||||
<div id="testing-stats-chart" style="width: 100%; height: 100%;"></div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 审批数据可视化 -->
|
||||
<div v-if="selectedChart === 'approval'">
|
||||
<a-row gutter={24}>
|
||||
<a-col :span="12">
|
||||
<a-card title="审批类型分布" :body-style="{ padding: 0 }">
|
||||
<div style="height: 300px; padding: 10px;">
|
||||
<div id="approval-type-chart" style="width: 100%; height: 100%;"></div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-card title="审批状态分布" :body-style="{ padding: 0 }">
|
||||
<div style="height: 300px; padding: 10px;">
|
||||
<div id="approval-status-chart" style="width: 100%; height: 100%;"></div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row gutter={24} style="margin-top: 24px;">
|
||||
<a-col :span="24">
|
||||
<a-card title="审批处理时效" :body-style="{ padding: 0 }">
|
||||
<div style="height: 300px; padding: 10px;">
|
||||
<div id="approval-timeline-chart" style="width: 100%; height: 100%;"></div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted, watch } from 'vue'
|
||||
import axios from 'axios'
|
||||
import * as echarts from 'echarts'
|
||||
import { getVisualizationData } from '@/mock'
|
||||
|
||||
const selectedChart = ref('overview')
|
||||
const charts = ref({})
|
||||
const visualizationData = ref(null)
|
||||
|
||||
// 图表初始化函数
|
||||
const initCharts = () => {
|
||||
// 确保在切换图表类型时销毁之前的图表实例
|
||||
Object.values(charts.value).forEach(chart => {
|
||||
if (chart && chart.dispose) {
|
||||
chart.dispose()
|
||||
}
|
||||
})
|
||||
charts.value = {}
|
||||
|
||||
if (selectedChart.value === 'overview') {
|
||||
initOverviewCharts()
|
||||
} else if (selectedChart.value === 'supervision') {
|
||||
initSupervisionCharts()
|
||||
} else if (selectedChart.value === 'epidemic') {
|
||||
initEpidemicCharts()
|
||||
} else if (selectedChart.value === 'approval') {
|
||||
initApprovalCharts()
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化综合概览图表
|
||||
const initOverviewCharts = () => {
|
||||
// 数据趋势总览
|
||||
const overviewChartDom = document.getElementById('overview-chart')
|
||||
if (overviewChartDom) {
|
||||
charts.value.overviewChart = echarts.init(overviewChartDom)
|
||||
const overviewOption = {
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
legend: {
|
||||
data: ['监管事件', '审批数量', '疫情相关数据']
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '监管事件',
|
||||
type: 'line',
|
||||
data: [120, 132, 101, 134, 90, 230, 210, 180, 190, 230, 210, 250],
|
||||
smooth: true
|
||||
},
|
||||
{
|
||||
name: '审批数量',
|
||||
type: 'line',
|
||||
data: [220, 182, 191, 234, 290, 330, 310, 280, 290, 330, 310, 350],
|
||||
smooth: true
|
||||
},
|
||||
{
|
||||
name: '疫情相关数据',
|
||||
type: 'line',
|
||||
data: [150, 232, 201, 154, 190, 330, 410, 380, 390, 430, 410, 450],
|
||||
smooth: true
|
||||
}
|
||||
]
|
||||
}
|
||||
charts.value.overviewChart.setOption(overviewOption)
|
||||
}
|
||||
|
||||
// 数据分类占比
|
||||
const categoryChartDom = document.getElementById('category-chart')
|
||||
if (categoryChartDom) {
|
||||
charts.value.categoryChart = echarts.init(categoryChartDom)
|
||||
const categoryOption = {
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '数据分类',
|
||||
type: 'pie',
|
||||
radius: '65%',
|
||||
data: [
|
||||
{
|
||||
value: 30,
|
||||
name: '监管数据'
|
||||
},
|
||||
{
|
||||
value: 25,
|
||||
name: '审批数据'
|
||||
},
|
||||
{
|
||||
value: 25,
|
||||
name: '疫情数据'
|
||||
},
|
||||
{
|
||||
value: 10,
|
||||
name: '用户数据'
|
||||
},
|
||||
{
|
||||
value: 10,
|
||||
name: '其他数据'
|
||||
}
|
||||
],
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
charts.value.categoryChart.setOption(categoryOption)
|
||||
}
|
||||
|
||||
// 月度活跃度
|
||||
const activityChartDom = document.getElementById('activity-chart')
|
||||
if (activityChartDom) {
|
||||
charts.value.activityChart = echarts.init(activityChartDom)
|
||||
const activityOption = {
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['1月', '2月', '3月', '4月', '5月', '6月']
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: [120, 200, 150, 80, 70, 110],
|
||||
type: 'bar',
|
||||
barWidth: '60%'
|
||||
}
|
||||
]
|
||||
}
|
||||
charts.value.activityChart.setOption(activityOption)
|
||||
}
|
||||
|
||||
// 区域分布图
|
||||
const regionChartDom = document.getElementById('region-chart')
|
||||
if (regionChartDom) {
|
||||
charts.value.regionChart = echarts.init(regionChartDom)
|
||||
const regionOption = {
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
radar: {
|
||||
indicator: [
|
||||
{ name: '东区', max: 100 },
|
||||
{ name: '西区', max: 100 },
|
||||
{ name: '南区', max: 100 },
|
||||
{ name: '北区', max: 100 },
|
||||
{ name: '中区', max: 100 }
|
||||
]
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'radar',
|
||||
data: [
|
||||
{
|
||||
value: [80, 65, 70, 75, 60],
|
||||
name: '数据分布'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
charts.value.regionChart.setOption(regionOption)
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化监管数据图表
|
||||
const initSupervisionCharts = () => {
|
||||
// 监管类型分布
|
||||
const supervisionTypeChartDom = document.getElementById('supervision-type-chart')
|
||||
if (supervisionTypeChartDom) {
|
||||
charts.value.supervisionTypeChart = echarts.init(supervisionTypeChartDom)
|
||||
const supervisionTypeOption = {
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
legend: {
|
||||
top: '5%',
|
||||
left: 'center'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '监管类型',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2
|
||||
},
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center'
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: '18',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: 35,
|
||||
name: '安全检查'
|
||||
},
|
||||
{
|
||||
value: 25,
|
||||
name: '质量监管'
|
||||
},
|
||||
{
|
||||
value: 20,
|
||||
name: '环保监测'
|
||||
},
|
||||
{
|
||||
value: 15,
|
||||
name: '疫情防控'
|
||||
},
|
||||
{
|
||||
value: 5,
|
||||
name: '其他'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
charts.value.supervisionTypeChart.setOption(supervisionTypeOption)
|
||||
}
|
||||
|
||||
// 异常情况趋势
|
||||
const abnormalTrendChartDom = document.getElementById('abnormal-trend-chart')
|
||||
if (abnormalTrendChartDom) {
|
||||
charts.value.abnormalTrendChart = echarts.init(abnormalTrendChartDom)
|
||||
const abnormalTrendOption = {
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['1月', '2月', '3月', '4月', '5月', '6月']
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: [12, 19, 15, 10, 8, 12],
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
areaStyle: {}
|
||||
}
|
||||
]
|
||||
}
|
||||
charts.value.abnormalTrendChart.setOption(abnormalTrendOption)
|
||||
}
|
||||
|
||||
// 监管覆盖率
|
||||
const coverageChartDom = document.getElementById('coverage-chart')
|
||||
if (coverageChartDom) {
|
||||
charts.value.coverageChart = echarts.init(coverageChartDom)
|
||||
const coverageOption = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: ['计划覆盖率', '实际覆盖率']
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'value',
|
||||
boundaryGap: [0, 0.01]
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
data: ['企业', '学校', '医院', '商场', '餐饮', '娱乐场所']
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '计划覆盖率',
|
||||
type: 'bar',
|
||||
data: [100, 100, 100, 100, 100, 100]
|
||||
},
|
||||
{
|
||||
name: '实际覆盖率',
|
||||
type: 'bar',
|
||||
data: [85, 90, 95, 80, 88, 75]
|
||||
}
|
||||
]
|
||||
}
|
||||
charts.value.coverageChart.setOption(coverageOption)
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化疫情数据图表
|
||||
const initEpidemicCharts = () => {
|
||||
// 疫情发展趋势
|
||||
const epidemicTrendChartDom = document.getElementById('epidemic-trend-chart')
|
||||
if (epidemicTrendChartDom) {
|
||||
charts.value.epidemicTrendChart = echarts.init(epidemicTrendChartDom)
|
||||
const epidemicTrendOption = {
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
legend: {
|
||||
data: ['确诊病例', '疑似病例', '隔离观察']
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '确诊病例',
|
||||
type: 'line',
|
||||
data: [120, 132, 101, 134, 90, 230, 210, 180, 190, 230, 210, 250],
|
||||
smooth: true,
|
||||
lineStyle: {
|
||||
color: '#ff4d4f'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '疑似病例',
|
||||
type: 'line',
|
||||
data: [80, 82, 91, 84, 70, 130, 110, 90, 100, 130, 110, 150],
|
||||
smooth: true,
|
||||
lineStyle: {
|
||||
color: '#fa8c16'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '隔离观察',
|
||||
type: 'line',
|
||||
data: [150, 172, 161, 184, 160, 280, 260, 240, 250, 280, 260, 300],
|
||||
smooth: true,
|
||||
lineStyle: {
|
||||
color: '#1890ff'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
charts.value.epidemicTrendChart.setOption(epidemicTrendOption)
|
||||
}
|
||||
|
||||
// 疫苗接种进度
|
||||
const vaccineProgressChartDom = document.getElementById('vaccine-progress-chart')
|
||||
if (vaccineProgressChartDom) {
|
||||
charts.value.vaccineProgressChart = echarts.init(vaccineProgressChartDom)
|
||||
const vaccineProgressOption = {
|
||||
tooltip: {
|
||||
formatter: '{a} <br/>{b} : {c}%'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '接种进度',
|
||||
type: 'gauge',
|
||||
detail: {
|
||||
formatter: '{value}%'
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: 75,
|
||||
name: '接种率'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
charts.value.vaccineProgressChart.setOption(vaccineProgressOption)
|
||||
}
|
||||
|
||||
// 区域疫情分布
|
||||
const epidemicRegionChartDom = document.getElementById('epidemic-region-chart')
|
||||
if (epidemicRegionChartDom) {
|
||||
charts.value.epidemicRegionChart = echarts.init(epidemicRegionChartDom)
|
||||
const epidemicRegionOption = {
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
legend: {
|
||||
top: '5%',
|
||||
left: 'center'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '区域分布',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2
|
||||
},
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center'
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: '18',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: 30,
|
||||
name: '东区'
|
||||
},
|
||||
{
|
||||
value: 25,
|
||||
name: '西区'
|
||||
},
|
||||
{
|
||||
value: 22,
|
||||
name: '南区'
|
||||
},
|
||||
{
|
||||
value: 23,
|
||||
name: '北区'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
charts.value.epidemicRegionChart.setOption(epidemicRegionOption)
|
||||
}
|
||||
|
||||
// 检测量统计
|
||||
const testingStatsChartDom = document.getElementById('testing-stats-chart')
|
||||
if (testingStatsChartDom) {
|
||||
charts.value.testingStatsChart = echarts.init(testingStatsChartDom)
|
||||
const testingStatsOption = {
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['1月', '2月', '3月', '4月', '5月', '6月']
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: [220, 182, 191, 234, 290, 330],
|
||||
type: 'bar',
|
||||
barWidth: '60%'
|
||||
}
|
||||
]
|
||||
}
|
||||
charts.value.testingStatsChart.setOption(testingStatsOption)
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化审批数据图表
|
||||
const initApprovalCharts = () => {
|
||||
// 审批类型分布
|
||||
const approvalTypeChartDom = document.getElementById('approval-type-chart')
|
||||
if (approvalTypeChartDom) {
|
||||
charts.value.approvalTypeChart = echarts.init(approvalTypeChartDom)
|
||||
const approvalTypeOption = {
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
legend: {
|
||||
top: '5%',
|
||||
left: 'center'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '审批类型',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2
|
||||
},
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center'
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: '18',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: 35,
|
||||
name: '企业资质'
|
||||
},
|
||||
{
|
||||
value: 30,
|
||||
name: '许可证'
|
||||
},
|
||||
{
|
||||
value: 20,
|
||||
name: '项目审批'
|
||||
},
|
||||
{
|
||||
value: 15,
|
||||
name: '其他'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
charts.value.approvalTypeChart.setOption(approvalTypeOption)
|
||||
}
|
||||
|
||||
// 审批状态分布
|
||||
const approvalStatusChartDom = document.getElementById('approval-status-chart')
|
||||
if (approvalStatusChartDom) {
|
||||
charts.value.approvalStatusChart = echarts.init(approvalStatusChartDom)
|
||||
const approvalStatusOption = {
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
legend: {
|
||||
top: '5%',
|
||||
left: 'center'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '审批状态',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2
|
||||
},
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center'
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: '18',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: 40,
|
||||
name: '已通过'
|
||||
},
|
||||
{
|
||||
value: 25,
|
||||
name: '待审批'
|
||||
},
|
||||
{
|
||||
value: 20,
|
||||
name: '处理中'
|
||||
},
|
||||
{
|
||||
value: 15,
|
||||
name: '已拒绝'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
charts.value.approvalStatusChart.setOption(approvalStatusOption)
|
||||
}
|
||||
|
||||
// 审批处理时效
|
||||
const approvalTimelineChartDom = document.getElementById('approval-timeline-chart')
|
||||
if (approvalTimelineChartDom) {
|
||||
charts.value.approvalTimelineChart = echarts.init(approvalTimelineChartDom)
|
||||
const approvalTimelineOption = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: ['平均处理时间(天)', '最长处理时间(天)', '最短处理时间(天)']
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['企业资质', '许可证', '项目审批', '其他']
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '平均处理时间(天)',
|
||||
type: 'bar',
|
||||
data: [7, 10, 15, 8]
|
||||
},
|
||||
{
|
||||
name: '最长处理时间(天)',
|
||||
type: 'bar',
|
||||
data: [15, 20, 30, 16]
|
||||
},
|
||||
{
|
||||
name: '最短处理时间(天)',
|
||||
type: 'bar',
|
||||
data: [3, 5, 7, 4]
|
||||
}
|
||||
]
|
||||
}
|
||||
charts.value.approvalTimelineChart.setOption(approvalTimelineOption)
|
||||
}
|
||||
}
|
||||
|
||||
// 响应窗口大小变化
|
||||
const handleResize = () => {
|
||||
Object.values(charts.value).forEach(chart => {
|
||||
if (chart && chart.resize) {
|
||||
chart.resize()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 切换图表类型
|
||||
const changeChart = () => {
|
||||
// 延迟初始化,确保DOM已经更新
|
||||
setTimeout(() => {
|
||||
initCharts()
|
||||
}, 100)
|
||||
}
|
||||
|
||||
// 获取可视化数据
|
||||
const fetchVisualizationData = async () => {
|
||||
try {
|
||||
// 尝试从API获取数据
|
||||
const response = await axios.get('/api/visualization/data')
|
||||
if (response.data.code === 200) {
|
||||
visualizationData.value = response.data.data
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取可视化数据失败,使用默认数据:', error)
|
||||
// 使用默认数据
|
||||
const mockResponse = await getVisualizationData()
|
||||
if (mockResponse.code === 200) {
|
||||
visualizationData.value = mockResponse.data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载
|
||||
onMounted(() => {
|
||||
fetchVisualizationData()
|
||||
setTimeout(() => {
|
||||
initCharts()
|
||||
}, 100)
|
||||
window.addEventListener('resize', handleResize)
|
||||
})
|
||||
|
||||
// 组件卸载
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
Object.values(charts.value).forEach(chart => {
|
||||
if (chart && chart.dispose) {
|
||||
chart.dispose()
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 样式可以根据需要进行调整 */
|
||||
</style>
|
||||
@@ -1,504 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>仓库管理</h1>
|
||||
<a-card style="margin-bottom: 16px;">
|
||||
<a-row gutter={24}>
|
||||
<a-col :span="8">
|
||||
<a-button type="primary" @click="handleAddItem">添加物资</a-button>
|
||||
<a-button style="margin-left: 8px;" @click="handleImportItems">导入物资</a-button>
|
||||
<a-button style="margin-left: 8px;" @click="handleExportItems">导出物资</a-button>
|
||||
</a-col>
|
||||
<a-col :span="16" style="text-align: right;">
|
||||
<a-input-search
|
||||
placeholder="搜索物资名称或编号"
|
||||
allow-clear
|
||||
enter-button="搜索"
|
||||
size="large"
|
||||
style="width: 300px;"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
|
||||
<!-- 数据概览卡片 -->
|
||||
<a-row gutter={24} style="margin-bottom: 16px;">
|
||||
<a-col :span="6">
|
||||
<a-card class="stat-card" :body-style="{ padding: '20px' }">
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">{{ stats.totalItems }}</div>
|
||||
<div class="stat-label">物资种类</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card class="stat-card" :body-style="{ padding: '20px' }">
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">{{ stats.totalQuantity }}</div>
|
||||
<div class="stat-label">总数量</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card class="stat-card" :body-style="{ padding: '20px' }">
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">{{ stats.lowStock }}</div>
|
||||
<div class="stat-label">低库存</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card class="stat-card" :body-style="{ padding: '20px' }">
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">{{ stats.expiringItems }}</div>
|
||||
<div class="stat-label">临期物资</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 物资列表 -->
|
||||
<a-card title="物资列表">
|
||||
<a-row gutter={24} style="margin-bottom: 16px;">
|
||||
<a-col :span="6">
|
||||
<a-select v-model:value="filterCategory" placeholder="筛选物资类型" allow-clear>
|
||||
<a-select-option value="medicine">药品</a-select-option>
|
||||
<a-select-option value="equipment">设备</a-select-option>
|
||||
<a-select-option value="supplies">耗材</a-select-option>
|
||||
<a-select-option value="other">其他</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-select v-model:value="filterStatus" placeholder="筛选库存状态" allow-clear>
|
||||
<a-select-option value="normal">正常</a-select-option>
|
||||
<a-select-option value="low">低库存</a-select-option>
|
||||
<a-select-option value="out">缺货</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-select v-model:value="filterWarehouse" placeholder="筛选仓库" allow-clear>
|
||||
<a-select-option value="main">主仓库</a-select-option>
|
||||
<a-select-option value="sub1">副仓库1</a-select-option>
|
||||
<a-select-option value="sub2">副仓库2</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-table :columns="itemColumns" :data-source="itemList" :pagination="pagination" row-key="id">
|
||||
<template #bodyCell:quantity="{ record }">
|
||||
<span :class="getQuantityClass(record)">{{ record.quantity }}</span>
|
||||
</template>
|
||||
<template #bodyCell:expiryDate="{ record }">
|
||||
<span :class="getExpiryClass(record)">{{ record.expiryDate ? dayjs(record.expiryDate).format('YYYY-MM-DD') : '-' }}</span>
|
||||
</template>
|
||||
<template #bodyCell:action="{ record }">
|
||||
<a-space>
|
||||
<a-button type="link" @click="handleViewItem(record)">查看</a-button>
|
||||
<a-button type="link" @click="handleEditItem(record)">编辑</a-button>
|
||||
<a-button type="link" danger @click="handleDeleteItem(record.id)">删除</a-button>
|
||||
<a-button type="link" @click="handleStockInOut(record, 'in')">入库</a-button>
|
||||
<a-button type="link" @click="handleStockInOut(record, 'out')">出库</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 添加/编辑物资对话框 -->
|
||||
<a-modal
|
||||
v-model:open="itemModalVisible"
|
||||
:title="itemModalTitle"
|
||||
@ok="handleItemModalOk"
|
||||
@cancel="handleItemModalCancel"
|
||||
>
|
||||
<a-form :model="itemForm" layout="vertical">
|
||||
<a-form-item label="物资名称" name="name" :rules="[{ required: true, message: '请输入物资名称' }]">
|
||||
<a-input v-model:value="itemForm.name" placeholder="请输入物资名称" />
|
||||
</a-form-item>
|
||||
<a-form-item label="物资编号" name="code" :rules="[{ required: true, message: '请输入物资编号' }]">
|
||||
<a-input v-model:value="itemForm.code" placeholder="请输入物资编号" />
|
||||
</a-form-item>
|
||||
<a-form-item label="物资类型" name="category" :rules="[{ required: true, message: '请选择物资类型' }]">
|
||||
<a-select v-model:value="itemForm.category" placeholder="请选择物资类型">
|
||||
<a-select-option value="medicine">药品</a-select-option>
|
||||
<a-select-option value="equipment">设备</a-select-option>
|
||||
<a-select-option value="supplies">耗材</a-select-option>
|
||||
<a-select-option value="other">其他</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="当前库存" name="quantity" :rules="[{ required: true, message: '请输入当前库存' }]">
|
||||
<a-input-number v-model:value="itemForm.quantity" placeholder="请输入当前库存" :min="0" />
|
||||
</a-form-item>
|
||||
<a-form-item label="预警库存" name="warningStock" :rules="[{ required: true, message: '请输入预警库存' }]">
|
||||
<a-input-number v-model:value="itemForm.warningStock" placeholder="请输入预警库存" :min="0" />
|
||||
</a-form-item>
|
||||
<a-form-item label="所属仓库" name="warehouse" :rules="[{ required: true, message: '请选择所属仓库' }]">
|
||||
<a-select v-model:value="itemForm.warehouse" placeholder="请选择所属仓库">
|
||||
<a-select-option value="main">主仓库</a-select-option>
|
||||
<a-select-option value="sub1">副仓库1</a-select-option>
|
||||
<a-select-option value="sub2">副仓库2</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="有效期至" name="expiryDate">
|
||||
<a-date-picker v-model:value="itemForm.expiryDate" style="width: 100%;" />
|
||||
</a-form-item>
|
||||
<a-form-item label="物资描述" name="description">
|
||||
<a-textarea v-model:value="itemForm.description" placeholder="请输入物资描述" rows="3" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
<!-- 入库/出库对话框 -->
|
||||
<a-modal
|
||||
v-model:open="stockModalVisible"
|
||||
:title="stockModalTitle"
|
||||
@ok="handleStockModalOk"
|
||||
@cancel="handleStockModalCancel"
|
||||
>
|
||||
<a-form :model="stockForm" layout="vertical">
|
||||
<a-form-item label="物资名称">
|
||||
<a-input :value="stockForm.itemName" disabled />
|
||||
</a-form-item>
|
||||
<a-form-item label="当前库存">
|
||||
<a-input :value="stockForm.currentStock" disabled />
|
||||
</a-form-item>
|
||||
<a-form-item label="数量" name="quantity" :rules="[{ required: true, message: '请输入数量' }]">
|
||||
<a-input-number v-model:value="stockForm.quantity" placeholder="请输入数量" :min="1" />
|
||||
</a-form-item>
|
||||
<a-form-item label="操作人" name="operator" :rules="[{ required: true, message: '请输入操作人' }]">
|
||||
<a-input v-model:value="stockForm.operator" placeholder="请输入操作人" />
|
||||
</a-form-item>
|
||||
<a-form-item label="备注" name="remark">
|
||||
<a-textarea v-model:value="stockForm.remark" placeholder="请输入备注" rows="3" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import axios from 'axios'
|
||||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
import 'dayjs/locale/zh-cn'
|
||||
|
||||
dayjs.extend(relativeTime)
|
||||
dayjs.locale('zh-cn')
|
||||
|
||||
// 统计数据
|
||||
const stats = ref({
|
||||
totalItems: 5,
|
||||
totalQuantity: 1500,
|
||||
lowStock: 2,
|
||||
expiringItems: 1
|
||||
})
|
||||
|
||||
// 分页配置
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
|
||||
// 筛选条件
|
||||
const filterCategory = ref('')
|
||||
const filterStatus = ref('')
|
||||
const filterWarehouse = ref('')
|
||||
|
||||
// 物资列表数据
|
||||
const itemList = ref([
|
||||
{
|
||||
id: '1',
|
||||
name: '消毒液A',
|
||||
code: 'ITM001',
|
||||
category: 'supplies',
|
||||
quantity: 50,
|
||||
warningStock: 20,
|
||||
warehouse: 'main',
|
||||
expiryDate: '2024-12-31T00:00:00Z',
|
||||
description: '养殖场用消毒液',
|
||||
createTime: '2024-01-15T09:30:00Z',
|
||||
updateTime: '2024-04-10T14:20:00Z'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: '疫苗B',
|
||||
code: 'ITM002',
|
||||
category: 'medicine',
|
||||
quantity: 10,
|
||||
warningStock: 50,
|
||||
warehouse: 'main',
|
||||
expiryDate: '2024-06-30T00:00:00Z',
|
||||
description: '动物疫苗',
|
||||
createTime: '2024-01-20T10:15:00Z',
|
||||
updateTime: '2024-04-09T16:45:00Z'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: '防护服',
|
||||
code: 'ITM003',
|
||||
category: 'supplies',
|
||||
quantity: 200,
|
||||
warningStock: 50,
|
||||
warehouse: 'sub1',
|
||||
expiryDate: null,
|
||||
description: '防疫用防护服',
|
||||
createTime: '2024-02-01T14:30:00Z',
|
||||
updateTime: '2024-04-08T10:15:00Z'
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
name: '体温枪',
|
||||
code: 'ITM004',
|
||||
category: 'equipment',
|
||||
quantity: 5,
|
||||
warningStock: 3,
|
||||
warehouse: 'sub2',
|
||||
expiryDate: null,
|
||||
description: '动物体温测量设备',
|
||||
createTime: '2024-02-15T11:45:00Z',
|
||||
updateTime: '2024-04-07T13:30:00Z'
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
name: '口罩',
|
||||
code: 'ITM005',
|
||||
category: 'supplies',
|
||||
quantity: 1000,
|
||||
warningStock: 100,
|
||||
warehouse: 'sub1',
|
||||
expiryDate: '2025-03-31T00:00:00Z',
|
||||
description: '防护口罩',
|
||||
createTime: '2024-01-10T09:00:00Z',
|
||||
updateTime: '2024-03-01T16:20:00Z'
|
||||
}
|
||||
])
|
||||
|
||||
// 物资表格列定义
|
||||
const itemColumns = [
|
||||
{
|
||||
title: '物资名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name'
|
||||
},
|
||||
{
|
||||
title: '物资编号',
|
||||
dataIndex: 'code',
|
||||
key: 'code'
|
||||
},
|
||||
{
|
||||
title: '物资类型',
|
||||
dataIndex: 'category',
|
||||
key: 'category',
|
||||
customRender: (text) => {
|
||||
const categoryMap = {
|
||||
medicine: '药品',
|
||||
equipment: '设备',
|
||||
supplies: '耗材',
|
||||
other: '其他'
|
||||
}
|
||||
return categoryMap[text] || text
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '当前库存',
|
||||
dataIndex: 'quantity',
|
||||
key: 'quantity'
|
||||
},
|
||||
{
|
||||
title: '预警库存',
|
||||
dataIndex: 'warningStock',
|
||||
key: 'warningStock'
|
||||
},
|
||||
{
|
||||
title: '所属仓库',
|
||||
dataIndex: 'warehouse',
|
||||
key: 'warehouse',
|
||||
customRender: (text) => {
|
||||
const warehouseMap = {
|
||||
main: '主仓库',
|
||||
sub1: '副仓库1',
|
||||
sub2: '副仓库2'
|
||||
}
|
||||
return warehouseMap[text] || text
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '有效期至',
|
||||
dataIndex: 'expiryDate',
|
||||
key: 'expiryDate'
|
||||
},
|
||||
{
|
||||
title: '更新时间',
|
||||
dataIndex: 'updateTime',
|
||||
key: 'updateTime',
|
||||
customRender: (text) => dayjs(text).format('YYYY-MM-DD HH:mm')
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 200,
|
||||
fixed: 'right'
|
||||
}
|
||||
]
|
||||
|
||||
// 模态框配置 - 物资
|
||||
const itemModalVisible = ref(false)
|
||||
const itemModalTitle = ref('添加物资')
|
||||
const itemForm = reactive({
|
||||
id: '',
|
||||
name: '',
|
||||
code: '',
|
||||
category: '',
|
||||
quantity: 0,
|
||||
warningStock: 0,
|
||||
warehouse: '',
|
||||
expiryDate: null,
|
||||
description: ''
|
||||
})
|
||||
|
||||
// 模态框配置 - 库存操作
|
||||
const stockModalVisible = ref(false)
|
||||
const stockModalTitle = ref('入库')
|
||||
const stockForm = reactive({
|
||||
itemId: '',
|
||||
itemName: '',
|
||||
currentStock: 0,
|
||||
quantity: 0,
|
||||
operator: '',
|
||||
remark: '',
|
||||
type: 'in' // 'in' 入库, 'out' 出库
|
||||
})
|
||||
|
||||
// 获取库存数量样式类
|
||||
const getQuantityClass = (record) => {
|
||||
if (record.quantity === 0) {
|
||||
return 'text-danger'
|
||||
} else if (record.quantity <= record.warningStock) {
|
||||
return 'text-warning'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
// 获取有效期样式类
|
||||
const getExpiryClass = (record) => {
|
||||
if (!record.expiryDate) return ''
|
||||
|
||||
const daysLeft = dayjs(record.expiryDate).diff(dayjs(), 'day')
|
||||
if (daysLeft <= 30) {
|
||||
return 'text-danger'
|
||||
} else if (daysLeft <= 90) {
|
||||
return 'text-warning'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
// 搜索物资
|
||||
const handleSearch = (keyword) => {
|
||||
// 这里应该有实际的搜索逻辑
|
||||
console.log('搜索物资:', keyword)
|
||||
}
|
||||
|
||||
// 添加物资
|
||||
const handleAddItem = () => {
|
||||
itemModalTitle.value = '添加物资'
|
||||
Object.keys(itemForm).forEach(key => {
|
||||
itemForm[key] = key === 'quantity' || key === 'warningStock' ? 0 : ''
|
||||
})
|
||||
itemModalVisible.value = true
|
||||
}
|
||||
|
||||
// 编辑物资
|
||||
const handleEditItem = (item) => {
|
||||
itemModalTitle.value = '编辑物资'
|
||||
Object.keys(itemForm).forEach(key => {
|
||||
itemForm[key] = item[key] || (key === 'quantity' || key === 'warningStock' ? 0 : '')
|
||||
})
|
||||
itemModalVisible.value = true
|
||||
}
|
||||
|
||||
// 查看物资
|
||||
const handleViewItem = (item) => {
|
||||
console.log('查看物资:', item)
|
||||
}
|
||||
|
||||
// 删除物资
|
||||
const handleDeleteItem = (id) => {
|
||||
console.log('删除物资:', id)
|
||||
}
|
||||
|
||||
// 入库/出库
|
||||
const handleStockInOut = (item, type) => {
|
||||
stockModalTitle.value = type === 'in' ? '入库' : '出库'
|
||||
stockForm.itemId = item.id
|
||||
stockForm.itemName = item.name
|
||||
stockForm.currentStock = item.quantity
|
||||
stockForm.quantity = 0
|
||||
stockForm.operator = ''
|
||||
stockForm.remark = ''
|
||||
stockForm.type = type
|
||||
stockModalVisible.value = true
|
||||
}
|
||||
|
||||
// 导入物资
|
||||
const handleImportItems = () => {
|
||||
console.log('导入物资')
|
||||
}
|
||||
|
||||
// 导出物资
|
||||
const handleExportItems = () => {
|
||||
console.log('导出物资')
|
||||
}
|
||||
|
||||
// 模态框确认 - 物资
|
||||
const handleItemModalOk = () => {
|
||||
console.log('提交表单:', itemForm)
|
||||
itemModalVisible.value = false
|
||||
}
|
||||
|
||||
// 模态框取消 - 物资
|
||||
const handleItemModalCancel = () => {
|
||||
itemModalVisible.value = false
|
||||
}
|
||||
|
||||
// 模态框确认 - 库存操作
|
||||
const handleStockModalOk = () => {
|
||||
console.log('提交库存操作:', stockForm)
|
||||
stockModalVisible.value = false
|
||||
}
|
||||
|
||||
// 模态框取消 - 库存操作
|
||||
const handleStockModalCancel = () => {
|
||||
stockModalVisible.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.stat-card {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.text-warning {
|
||||
color: #faad14;
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: #f5222d;
|
||||
}
|
||||
</style>
|
||||
11
government-admin/src/views/smart-warehouse/SmartHardware.vue
Normal file
11
government-admin/src/views/smart-warehouse/SmartHardware.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<div>
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SmartHardware'
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,422 @@
|
||||
<template>
|
||||
<div class="smart-collar-container">
|
||||
<a-card title="智能项圈管理" :bordered="false">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="24">
|
||||
<a-form layout="inline" :model="searchForm">
|
||||
<a-form-item label="项圈编号">
|
||||
<a-input v-model:value="searchForm.collarId" placeholder="请输入项圈编号" />
|
||||
</a-form-item>
|
||||
<a-form-item label="状态">
|
||||
<a-select v-model:value="searchForm.status" placeholder="请选择状态" style="width: 120px">
|
||||
<a-select-option value="active">使用中</a-select-option>
|
||||
<a-select-option value="inactive">未使用</a-select-option>
|
||||
<a-select-option value="maintenance">维护中</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="handleSearch">查询</a-button>
|
||||
<a-button style="margin-left: 8px" @click="resetSearch">重置</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row style="margin-top: 16px">
|
||||
<a-col :span="24">
|
||||
<a-button type="primary" @click="showAddModal">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
添加项圈
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-table
|
||||
style="margin-top: 16px"
|
||||
:columns="columns"
|
||||
:data-source="dataSource"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
@change="handleTableChange"
|
||||
rowKey="id"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a @click="showEditModal(record)">编辑</a>
|
||||
<a-divider type="vertical" />
|
||||
<a @click="showDetailModal(record)">详情</a>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm
|
||||
title="确定要删除此项圈吗?"
|
||||
ok-text="确定"
|
||||
cancel-text="取消"
|
||||
@confirm="handleDelete(record)"
|
||||
>
|
||||
<a class="danger-text">删除</a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
|
||||
<!-- 添加/编辑模态框 -->
|
||||
<a-modal
|
||||
:title="modalTitle"
|
||||
:visible="modalVisible"
|
||||
:confirm-loading="modalLoading"
|
||||
@ok="handleModalOk"
|
||||
@cancel="handleModalCancel"
|
||||
>
|
||||
<a-form :model="formData" :rules="rules" ref="formRef" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
|
||||
<a-form-item label="项圈编号" name="collarId">
|
||||
<a-input v-model:value="formData.collarId" placeholder="请输入项圈编号" />
|
||||
</a-form-item>
|
||||
<a-form-item label="项圈名称" name="name">
|
||||
<a-input v-model:value="formData.name" placeholder="请输入项圈名称" />
|
||||
</a-form-item>
|
||||
<a-form-item label="状态" name="status">
|
||||
<a-select v-model:value="formData.status" placeholder="请选择状态">
|
||||
<a-select-option value="active">使用中</a-select-option>
|
||||
<a-select-option value="inactive">未使用</a-select-option>
|
||||
<a-select-option value="maintenance">维护中</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="电池电量" name="battery">
|
||||
<a-input-number v-model:value="formData.battery" :min="0" :max="100" addonAfter="%" style="width: 100%" />
|
||||
</a-form-item>
|
||||
<a-form-item label="备注" name="remark">
|
||||
<a-textarea v-model:value="formData.remark" placeholder="请输入备注" :rows="4" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
<!-- 详情模态框 -->
|
||||
<a-modal
|
||||
title="项圈详情"
|
||||
:visible="detailModalVisible"
|
||||
:footer="null"
|
||||
@cancel="handleDetailModalCancel"
|
||||
>
|
||||
<a-descriptions bordered>
|
||||
<a-descriptions-item label="项圈编号" span="3">{{ detailData.collarId }}</a-descriptions-item>
|
||||
<a-descriptions-item label="项圈名称" span="3">{{ detailData.name }}</a-descriptions-item>
|
||||
<a-descriptions-item label="状态" span="3">
|
||||
<a-tag :color="getStatusColor(detailData.status)">
|
||||
{{ getStatusText(detailData.status) }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="电池电量" span="3">{{ detailData.battery }}%</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间" span="3">{{ detailData.createdAt }}</a-descriptions-item>
|
||||
<a-descriptions-item label="更新时间" span="3">{{ detailData.updatedAt }}</a-descriptions-item>
|
||||
<a-descriptions-item label="备注" span="3">{{ detailData.remark || '无' }}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-modal>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
export default {
|
||||
name: 'SmartCollar',
|
||||
components: {
|
||||
PlusOutlined
|
||||
},
|
||||
setup() {
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
collarId: '',
|
||||
status: undefined
|
||||
});
|
||||
|
||||
// 表格数据
|
||||
const loading = ref(false);
|
||||
const dataSource = ref([]);
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total) => `共 ${total} 条`
|
||||
});
|
||||
|
||||
// 表格列定义
|
||||
const columns = [
|
||||
{
|
||||
title: '项圈编号',
|
||||
dataIndex: 'collarId',
|
||||
key: 'collarId',
|
||||
sorter: true
|
||||
},
|
||||
{
|
||||
title: '项圈名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name'
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
filters: [
|
||||
{ text: '使用中', value: 'active' },
|
||||
{ text: '未使用', value: 'inactive' },
|
||||
{ text: '维护中', value: 'maintenance' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '电池电量',
|
||||
dataIndex: 'battery',
|
||||
key: 'battery',
|
||||
customRender: ({ text }) => `${text}%`
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createdAt',
|
||||
key: 'createdAt',
|
||||
sorter: true
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action'
|
||||
}
|
||||
];
|
||||
|
||||
// 模态框相关
|
||||
const modalVisible = ref(false);
|
||||
const modalLoading = ref(false);
|
||||
const modalTitle = ref('添加项圈');
|
||||
const formRef = ref(null);
|
||||
const formData = reactive({
|
||||
id: null,
|
||||
collarId: '',
|
||||
name: '',
|
||||
status: 'inactive',
|
||||
battery: 100,
|
||||
remark: ''
|
||||
});
|
||||
const rules = {
|
||||
collarId: [{ required: true, message: '请输入项圈编号', trigger: 'blur' }],
|
||||
name: [{ required: true, message: '请输入项圈名称', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '请选择状态', trigger: 'change' }]
|
||||
};
|
||||
|
||||
// 详情模态框
|
||||
const detailModalVisible = ref(false);
|
||||
const detailData = reactive({});
|
||||
|
||||
// 模拟数据
|
||||
const mockData = [
|
||||
{
|
||||
id: '1',
|
||||
collarId: 'CL001',
|
||||
name: '智能项圈001',
|
||||
status: 'active',
|
||||
battery: 85,
|
||||
createdAt: '2023-09-15 10:00:00',
|
||||
updatedAt: '2023-09-20 14:30:00',
|
||||
remark: '用于示范的项圈'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
collarId: 'CL002',
|
||||
name: '智能项圈002',
|
||||
status: 'inactive',
|
||||
battery: 100,
|
||||
createdAt: '2023-09-16 11:20:00',
|
||||
updatedAt: '2023-09-16 11:20:00',
|
||||
remark: ''
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
collarId: 'CL003',
|
||||
name: '智能项圈003',
|
||||
status: 'maintenance',
|
||||
battery: 20,
|
||||
createdAt: '2023-09-10 09:15:00',
|
||||
updatedAt: '2023-09-21 16:45:00',
|
||||
remark: '电池需要更换'
|
||||
}
|
||||
];
|
||||
|
||||
// 获取数据
|
||||
const fetchData = () => {
|
||||
loading.value = true;
|
||||
// 模拟API请求
|
||||
setTimeout(() => {
|
||||
dataSource.value = mockData;
|
||||
pagination.total = mockData.length;
|
||||
loading.value = false;
|
||||
}, 500);
|
||||
};
|
||||
|
||||
// 状态相关
|
||||
const getStatusColor = (status) => {
|
||||
const statusMap = {
|
||||
active: 'green',
|
||||
inactive: 'blue',
|
||||
maintenance: 'orange'
|
||||
};
|
||||
return statusMap[status] || 'default';
|
||||
};
|
||||
|
||||
const getStatusText = (status) => {
|
||||
const statusMap = {
|
||||
active: '使用中',
|
||||
inactive: '未使用',
|
||||
maintenance: '维护中'
|
||||
};
|
||||
return statusMap[status] || '未知';
|
||||
};
|
||||
|
||||
// 搜索相关
|
||||
const handleSearch = () => {
|
||||
pagination.current = 1;
|
||||
fetchData();
|
||||
};
|
||||
|
||||
const resetSearch = () => {
|
||||
searchForm.collarId = '';
|
||||
searchForm.status = undefined;
|
||||
handleSearch();
|
||||
};
|
||||
|
||||
// 表格相关
|
||||
const handleTableChange = (pag, filters, sorter) => {
|
||||
pagination.current = pag.current;
|
||||
pagination.pageSize = pag.pageSize;
|
||||
fetchData();
|
||||
};
|
||||
|
||||
// 模态框相关
|
||||
const showAddModal = () => {
|
||||
modalTitle.value = '添加项圈';
|
||||
formData.id = null;
|
||||
formData.collarId = '';
|
||||
formData.name = '';
|
||||
formData.status = 'inactive';
|
||||
formData.battery = 100;
|
||||
formData.remark = '';
|
||||
modalVisible.value = true;
|
||||
};
|
||||
|
||||
const showEditModal = (record) => {
|
||||
modalTitle.value = '编辑项圈';
|
||||
formData.id = record.id;
|
||||
formData.collarId = record.collarId;
|
||||
formData.name = record.name;
|
||||
formData.status = record.status;
|
||||
formData.battery = record.battery;
|
||||
formData.remark = record.remark;
|
||||
modalVisible.value = true;
|
||||
};
|
||||
|
||||
const handleModalOk = () => {
|
||||
formRef.value.validate().then(() => {
|
||||
modalLoading.value = true;
|
||||
// 模拟API请求
|
||||
setTimeout(() => {
|
||||
if (formData.id) {
|
||||
// 编辑
|
||||
const index = mockData.findIndex(item => item.id === formData.id);
|
||||
if (index !== -1) {
|
||||
mockData[index] = {
|
||||
...mockData[index],
|
||||
...formData,
|
||||
updatedAt: new Date().toLocaleString()
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// 添加
|
||||
mockData.push({
|
||||
id: String(mockData.length + 1),
|
||||
...formData,
|
||||
createdAt: new Date().toLocaleString(),
|
||||
updatedAt: new Date().toLocaleString()
|
||||
});
|
||||
}
|
||||
modalLoading.value = false;
|
||||
modalVisible.value = false;
|
||||
fetchData();
|
||||
}, 500);
|
||||
});
|
||||
};
|
||||
|
||||
const handleModalCancel = () => {
|
||||
modalVisible.value = false;
|
||||
};
|
||||
|
||||
// 详情相关
|
||||
const showDetailModal = (record) => {
|
||||
Object.assign(detailData, record);
|
||||
detailModalVisible.value = true;
|
||||
};
|
||||
|
||||
const handleDetailModalCancel = () => {
|
||||
detailModalVisible.value = false;
|
||||
};
|
||||
|
||||
// 删除
|
||||
const handleDelete = (record) => {
|
||||
loading.value = true;
|
||||
// 模拟API请求
|
||||
setTimeout(() => {
|
||||
const index = mockData.findIndex(item => item.id === record.id);
|
||||
if (index !== -1) {
|
||||
mockData.splice(index, 1);
|
||||
}
|
||||
loading.value = false;
|
||||
fetchData();
|
||||
}, 500);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchData();
|
||||
});
|
||||
|
||||
return {
|
||||
searchForm,
|
||||
loading,
|
||||
dataSource,
|
||||
pagination,
|
||||
columns,
|
||||
modalVisible,
|
||||
modalLoading,
|
||||
modalTitle,
|
||||
formRef,
|
||||
formData,
|
||||
rules,
|
||||
detailModalVisible,
|
||||
detailData,
|
||||
getStatusColor,
|
||||
getStatusText,
|
||||
handleSearch,
|
||||
resetSearch,
|
||||
handleTableChange,
|
||||
showAddModal,
|
||||
showEditModal,
|
||||
handleModalOk,
|
||||
handleModalCancel,
|
||||
showDetailModal,
|
||||
handleDetailModalCancel,
|
||||
handleDelete
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.smart-collar-container {
|
||||
padding: 16px;
|
||||
}
|
||||
.danger-text {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,422 @@
|
||||
<template>
|
||||
<div class="smart-earmark-container">
|
||||
<a-card title="智能耳标管理" :bordered="false">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="24">
|
||||
<a-form layout="inline" :model="searchForm">
|
||||
<a-form-item label="耳标编号">
|
||||
<a-input v-model:value="searchForm.earmarkId" placeholder="请输入耳标编号" />
|
||||
</a-form-item>
|
||||
<a-form-item label="状态">
|
||||
<a-select v-model:value="searchForm.status" placeholder="请选择状态" style="width: 120px">
|
||||
<a-select-option value="active">使用中</a-select-option>
|
||||
<a-select-option value="inactive">未使用</a-select-option>
|
||||
<a-select-option value="maintenance">维护中</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="handleSearch">查询</a-button>
|
||||
<a-button style="margin-left: 8px" @click="resetSearch">重置</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row style="margin-top: 16px">
|
||||
<a-col :span="24">
|
||||
<a-button type="primary" @click="showAddModal">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
添加耳标
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-table
|
||||
style="margin-top: 16px"
|
||||
:columns="columns"
|
||||
:data-source="dataSource"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
@change="handleTableChange"
|
||||
rowKey="id"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a @click="showEditModal(record)">编辑</a>
|
||||
<a-divider type="vertical" />
|
||||
<a @click="showDetailModal(record)">详情</a>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm
|
||||
title="确定要删除此耳标吗?"
|
||||
ok-text="确定"
|
||||
cancel-text="取消"
|
||||
@confirm="handleDelete(record)"
|
||||
>
|
||||
<a class="danger-text">删除</a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
|
||||
<!-- 添加/编辑模态框 -->
|
||||
<a-modal
|
||||
:title="modalTitle"
|
||||
:visible="modalVisible"
|
||||
:confirm-loading="modalLoading"
|
||||
@ok="handleModalOk"
|
||||
@cancel="handleModalCancel"
|
||||
>
|
||||
<a-form :model="formData" :rules="rules" ref="formRef" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
|
||||
<a-form-item label="耳标编号" name="earmarkId">
|
||||
<a-input v-model:value="formData.earmarkId" placeholder="请输入耳标编号" />
|
||||
</a-form-item>
|
||||
<a-form-item label="耳标名称" name="name">
|
||||
<a-input v-model:value="formData.name" placeholder="请输入耳标名称" />
|
||||
</a-form-item>
|
||||
<a-form-item label="状态" name="status">
|
||||
<a-select v-model:value="formData.status" placeholder="请选择状态">
|
||||
<a-select-option value="active">使用中</a-select-option>
|
||||
<a-select-option value="inactive">未使用</a-select-option>
|
||||
<a-select-option value="maintenance">维护中</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="电池电量" name="battery">
|
||||
<a-input-number v-model:value="formData.battery" :min="0" :max="100" addonAfter="%" style="width: 100%" />
|
||||
</a-form-item>
|
||||
<a-form-item label="备注" name="remark">
|
||||
<a-textarea v-model:value="formData.remark" placeholder="请输入备注" :rows="4" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
<!-- 详情模态框 -->
|
||||
<a-modal
|
||||
title="耳标详情"
|
||||
:visible="detailModalVisible"
|
||||
:footer="null"
|
||||
@cancel="handleDetailModalCancel"
|
||||
>
|
||||
<a-descriptions bordered>
|
||||
<a-descriptions-item label="耳标编号" span="3">{{ detailData.earmarkId }}</a-descriptions-item>
|
||||
<a-descriptions-item label="耳标名称" span="3">{{ detailData.name }}</a-descriptions-item>
|
||||
<a-descriptions-item label="状态" span="3">
|
||||
<a-tag :color="getStatusColor(detailData.status)">
|
||||
{{ getStatusText(detailData.status) }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="电池电量" span="3">{{ detailData.battery }}%</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间" span="3">{{ detailData.createdAt }}</a-descriptions-item>
|
||||
<a-descriptions-item label="更新时间" span="3">{{ detailData.updatedAt }}</a-descriptions-item>
|
||||
<a-descriptions-item label="备注" span="3">{{ detailData.remark || '无' }}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-modal>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
export default {
|
||||
name: 'SmartEarmark',
|
||||
components: {
|
||||
PlusOutlined
|
||||
},
|
||||
setup() {
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
earmarkId: '',
|
||||
status: undefined
|
||||
});
|
||||
|
||||
// 表格数据
|
||||
const loading = ref(false);
|
||||
const dataSource = ref([]);
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total) => `共 ${total} 条`
|
||||
});
|
||||
|
||||
// 表格列定义
|
||||
const columns = [
|
||||
{
|
||||
title: '耳标编号',
|
||||
dataIndex: 'earmarkId',
|
||||
key: 'earmarkId',
|
||||
sorter: true
|
||||
},
|
||||
{
|
||||
title: '耳标名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name'
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
filters: [
|
||||
{ text: '使用中', value: 'active' },
|
||||
{ text: '未使用', value: 'inactive' },
|
||||
{ text: '维护中', value: 'maintenance' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '电池电量',
|
||||
dataIndex: 'battery',
|
||||
key: 'battery',
|
||||
customRender: ({ text }) => `${text}%`
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createdAt',
|
||||
key: 'createdAt',
|
||||
sorter: true
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action'
|
||||
}
|
||||
];
|
||||
|
||||
// 模态框相关
|
||||
const modalVisible = ref(false);
|
||||
const modalLoading = ref(false);
|
||||
const modalTitle = ref('添加耳标');
|
||||
const formRef = ref(null);
|
||||
const formData = reactive({
|
||||
id: null,
|
||||
earmarkId: '',
|
||||
name: '',
|
||||
status: 'inactive',
|
||||
battery: 100,
|
||||
remark: ''
|
||||
});
|
||||
const rules = {
|
||||
earmarkId: [{ required: true, message: '请输入耳标编号', trigger: 'blur' }],
|
||||
name: [{ required: true, message: '请输入耳标名称', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '请选择状态', trigger: 'change' }]
|
||||
};
|
||||
|
||||
// 详情模态框
|
||||
const detailModalVisible = ref(false);
|
||||
const detailData = reactive({});
|
||||
|
||||
// 模拟数据
|
||||
const mockData = [
|
||||
{
|
||||
id: '1',
|
||||
earmarkId: 'EM001',
|
||||
name: '智能耳标001',
|
||||
status: 'active',
|
||||
battery: 90,
|
||||
createdAt: '2023-09-15 10:00:00',
|
||||
updatedAt: '2023-09-20 14:30:00',
|
||||
remark: '用于示范的耳标'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
earmarkId: 'EM002',
|
||||
name: '智能耳标002',
|
||||
status: 'inactive',
|
||||
battery: 100,
|
||||
createdAt: '2023-09-16 11:20:00',
|
||||
updatedAt: '2023-09-16 11:20:00',
|
||||
remark: ''
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
earmarkId: 'EM003',
|
||||
name: '智能耳标003',
|
||||
status: 'maintenance',
|
||||
battery: 15,
|
||||
createdAt: '2023-09-10 09:15:00',
|
||||
updatedAt: '2023-09-21 16:45:00',
|
||||
remark: '电池需要更换'
|
||||
}
|
||||
];
|
||||
|
||||
// 获取数据
|
||||
const fetchData = () => {
|
||||
loading.value = true;
|
||||
// 模拟API请求
|
||||
setTimeout(() => {
|
||||
dataSource.value = mockData;
|
||||
pagination.total = mockData.length;
|
||||
loading.value = false;
|
||||
}, 500);
|
||||
};
|
||||
|
||||
// 状态相关
|
||||
const getStatusColor = (status) => {
|
||||
const statusMap = {
|
||||
active: 'green',
|
||||
inactive: 'blue',
|
||||
maintenance: 'orange'
|
||||
};
|
||||
return statusMap[status] || 'default';
|
||||
};
|
||||
|
||||
const getStatusText = (status) => {
|
||||
const statusMap = {
|
||||
active: '使用中',
|
||||
inactive: '未使用',
|
||||
maintenance: '维护中'
|
||||
};
|
||||
return statusMap[status] || '未知';
|
||||
};
|
||||
|
||||
// 搜索相关
|
||||
const handleSearch = () => {
|
||||
pagination.current = 1;
|
||||
fetchData();
|
||||
};
|
||||
|
||||
const resetSearch = () => {
|
||||
searchForm.earmarkId = '';
|
||||
searchForm.status = undefined;
|
||||
handleSearch();
|
||||
};
|
||||
|
||||
// 表格相关
|
||||
const handleTableChange = (pag, filters, sorter) => {
|
||||
pagination.current = pag.current;
|
||||
pagination.pageSize = pag.pageSize;
|
||||
fetchData();
|
||||
};
|
||||
|
||||
// 模态框相关
|
||||
const showAddModal = () => {
|
||||
modalTitle.value = '添加耳标';
|
||||
formData.id = null;
|
||||
formData.earmarkId = '';
|
||||
formData.name = '';
|
||||
formData.status = 'inactive';
|
||||
formData.battery = 100;
|
||||
formData.remark = '';
|
||||
modalVisible.value = true;
|
||||
};
|
||||
|
||||
const showEditModal = (record) => {
|
||||
modalTitle.value = '编辑耳标';
|
||||
formData.id = record.id;
|
||||
formData.earmarkId = record.earmarkId;
|
||||
formData.name = record.name;
|
||||
formData.status = record.status;
|
||||
formData.battery = record.battery;
|
||||
formData.remark = record.remark;
|
||||
modalVisible.value = true;
|
||||
};
|
||||
|
||||
const handleModalOk = () => {
|
||||
formRef.value.validate().then(() => {
|
||||
modalLoading.value = true;
|
||||
// 模拟API请求
|
||||
setTimeout(() => {
|
||||
if (formData.id) {
|
||||
// 编辑
|
||||
const index = mockData.findIndex(item => item.id === formData.id);
|
||||
if (index !== -1) {
|
||||
mockData[index] = {
|
||||
...mockData[index],
|
||||
...formData,
|
||||
updatedAt: new Date().toLocaleString()
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// 添加
|
||||
mockData.push({
|
||||
id: String(mockData.length + 1),
|
||||
...formData,
|
||||
createdAt: new Date().toLocaleString(),
|
||||
updatedAt: new Date().toLocaleString()
|
||||
});
|
||||
}
|
||||
modalLoading.value = false;
|
||||
modalVisible.value = false;
|
||||
fetchData();
|
||||
}, 500);
|
||||
});
|
||||
};
|
||||
|
||||
const handleModalCancel = () => {
|
||||
modalVisible.value = false;
|
||||
};
|
||||
|
||||
// 详情相关
|
||||
const showDetailModal = (record) => {
|
||||
Object.assign(detailData, record);
|
||||
detailModalVisible.value = true;
|
||||
};
|
||||
|
||||
const handleDetailModalCancel = () => {
|
||||
detailModalVisible.value = false;
|
||||
};
|
||||
|
||||
// 删除
|
||||
const handleDelete = (record) => {
|
||||
loading.value = true;
|
||||
// 模拟API请求
|
||||
setTimeout(() => {
|
||||
const index = mockData.findIndex(item => item.id === record.id);
|
||||
if (index !== -1) {
|
||||
mockData.splice(index, 1);
|
||||
}
|
||||
loading.value = false;
|
||||
fetchData();
|
||||
}, 500);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchData();
|
||||
});
|
||||
|
||||
return {
|
||||
searchForm,
|
||||
loading,
|
||||
dataSource,
|
||||
pagination,
|
||||
columns,
|
||||
modalVisible,
|
||||
modalLoading,
|
||||
modalTitle,
|
||||
formRef,
|
||||
formData,
|
||||
rules,
|
||||
detailModalVisible,
|
||||
detailData,
|
||||
getStatusColor,
|
||||
getStatusText,
|
||||
handleSearch,
|
||||
resetSearch,
|
||||
handleTableChange,
|
||||
showAddModal,
|
||||
showEditModal,
|
||||
handleModalOk,
|
||||
handleModalCancel,
|
||||
showDetailModal,
|
||||
handleDetailModalCancel,
|
||||
handleDelete
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.smart-earmark-container {
|
||||
padding: 16px;
|
||||
}
|
||||
.danger-text {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,422 @@
|
||||
<template>
|
||||
<div class="smart-host-container">
|
||||
<a-card title="智能主机管理" :bordered="false">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="24">
|
||||
<a-form layout="inline" :model="searchForm">
|
||||
<a-form-item label="主机编号">
|
||||
<a-input v-model:value="searchForm.hostId" placeholder="请输入主机编号" />
|
||||
</a-form-item>
|
||||
<a-form-item label="状态">
|
||||
<a-select v-model:value="searchForm.status" placeholder="请选择状态" style="width: 120px">
|
||||
<a-select-option value="online">在线</a-select-option>
|
||||
<a-select-option value="offline">离线</a-select-option>
|
||||
<a-select-option value="maintenance">维护中</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="handleSearch">查询</a-button>
|
||||
<a-button style="margin-left: 8px" @click="resetSearch">重置</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row style="margin-top: 16px">
|
||||
<a-col :span="24">
|
||||
<a-button type="primary" @click="showAddModal">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
添加主机
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-table
|
||||
style="margin-top: 16px"
|
||||
:columns="columns"
|
||||
:data-source="dataSource"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
@change="handleTableChange"
|
||||
rowKey="id"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a @click="showEditModal(record)">编辑</a>
|
||||
<a-divider type="vertical" />
|
||||
<a @click="showDetailModal(record)">详情</a>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm
|
||||
title="确定要删除此主机吗?"
|
||||
ok-text="确定"
|
||||
cancel-text="取消"
|
||||
@confirm="handleDelete(record)"
|
||||
>
|
||||
<a class="danger-text">删除</a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
|
||||
<!-- 添加/编辑模态框 -->
|
||||
<a-modal
|
||||
:title="modalTitle"
|
||||
:visible="modalVisible"
|
||||
:confirm-loading="modalLoading"
|
||||
@ok="handleModalOk"
|
||||
@cancel="handleModalCancel"
|
||||
>
|
||||
<a-form :model="formData" :rules="rules" ref="formRef" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
|
||||
<a-form-item label="主机编号" name="hostId">
|
||||
<a-input v-model:value="formData.hostId" placeholder="请输入主机编号" />
|
||||
</a-form-item>
|
||||
<a-form-item label="主机名称" name="name">
|
||||
<a-input v-model:value="formData.name" placeholder="请输入主机名称" />
|
||||
</a-form-item>
|
||||
<a-form-item label="IP地址" name="ipAddress">
|
||||
<a-input v-model:value="formData.ipAddress" placeholder="请输入IP地址" />
|
||||
</a-form-item>
|
||||
<a-form-item label="状态" name="status">
|
||||
<a-select v-model:value="formData.status" placeholder="请选择状态">
|
||||
<a-select-option value="online">在线</a-select-option>
|
||||
<a-select-option value="offline">离线</a-select-option>
|
||||
<a-select-option value="maintenance">维护中</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="备注" name="remark">
|
||||
<a-textarea v-model:value="formData.remark" placeholder="请输入备注" :rows="4" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
<!-- 详情模态框 -->
|
||||
<a-modal
|
||||
title="主机详情"
|
||||
:visible="detailModalVisible"
|
||||
:footer="null"
|
||||
@cancel="handleDetailModalCancel"
|
||||
>
|
||||
<a-descriptions bordered>
|
||||
<a-descriptions-item label="主机编号" span="3">{{ detailData.hostId }}</a-descriptions-item>
|
||||
<a-descriptions-item label="主机名称" span="3">{{ detailData.name }}</a-descriptions-item>
|
||||
<a-descriptions-item label="IP地址" span="3">{{ detailData.ipAddress }}</a-descriptions-item>
|
||||
<a-descriptions-item label="状态" span="3">
|
||||
<a-tag :color="getStatusColor(detailData.status)">
|
||||
{{ getStatusText(detailData.status) }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间" span="3">{{ detailData.createdAt }}</a-descriptions-item>
|
||||
<a-descriptions-item label="更新时间" span="3">{{ detailData.updatedAt }}</a-descriptions-item>
|
||||
<a-descriptions-item label="备注" span="3">{{ detailData.remark || '无' }}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-modal>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
export default {
|
||||
name: 'SmartHost',
|
||||
components: {
|
||||
PlusOutlined
|
||||
},
|
||||
setup() {
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
hostId: '',
|
||||
status: undefined
|
||||
});
|
||||
|
||||
// 表格数据
|
||||
const loading = ref(false);
|
||||
const dataSource = ref([]);
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total) => `共 ${total} 条`
|
||||
});
|
||||
|
||||
// 表格列定义
|
||||
const columns = [
|
||||
{
|
||||
title: '主机编号',
|
||||
dataIndex: 'hostId',
|
||||
key: 'hostId',
|
||||
sorter: true
|
||||
},
|
||||
{
|
||||
title: '主机名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name'
|
||||
},
|
||||
{
|
||||
title: 'IP地址',
|
||||
dataIndex: 'ipAddress',
|
||||
key: 'ipAddress'
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
filters: [
|
||||
{ text: '在线', value: 'online' },
|
||||
{ text: '离线', value: 'offline' },
|
||||
{ text: '维护中', value: 'maintenance' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createdAt',
|
||||
key: 'createdAt',
|
||||
sorter: true
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action'
|
||||
}
|
||||
];
|
||||
|
||||
// 模态框相关
|
||||
const modalVisible = ref(false);
|
||||
const modalLoading = ref(false);
|
||||
const modalTitle = ref('添加主机');
|
||||
const formRef = ref(null);
|
||||
const formData = reactive({
|
||||
id: null,
|
||||
hostId: '',
|
||||
name: '',
|
||||
ipAddress: '',
|
||||
status: 'offline',
|
||||
remark: ''
|
||||
});
|
||||
const rules = {
|
||||
hostId: [{ required: true, message: '请输入主机编号', trigger: 'blur' }],
|
||||
name: [{ required: true, message: '请输入主机名称', trigger: 'blur' }],
|
||||
ipAddress: [{ required: true, message: '请输入IP地址', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '请选择状态', trigger: 'change' }]
|
||||
};
|
||||
|
||||
// 详情模态框
|
||||
const detailModalVisible = ref(false);
|
||||
const detailData = reactive({});
|
||||
|
||||
// 模拟数据
|
||||
const mockData = [
|
||||
{
|
||||
id: '1',
|
||||
hostId: 'HOST001',
|
||||
name: '智能主机001',
|
||||
ipAddress: '192.168.1.100',
|
||||
status: 'online',
|
||||
createdAt: '2023-09-15 10:00:00',
|
||||
updatedAt: '2023-09-20 14:30:00',
|
||||
remark: '中心控制主机'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
hostId: 'HOST002',
|
||||
name: '智能主机002',
|
||||
ipAddress: '192.168.1.101',
|
||||
status: 'offline',
|
||||
createdAt: '2023-09-16 11:20:00',
|
||||
updatedAt: '2023-09-16 11:20:00',
|
||||
remark: ''
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
hostId: 'HOST003',
|
||||
name: '智能主机003',
|
||||
ipAddress: '192.168.1.102',
|
||||
status: 'maintenance',
|
||||
createdAt: '2023-09-10 09:15:00',
|
||||
updatedAt: '2023-09-21 16:45:00',
|
||||
remark: '系统升级中'
|
||||
}
|
||||
];
|
||||
|
||||
// 获取数据
|
||||
const fetchData = () => {
|
||||
loading.value = true;
|
||||
// 模拟API请求
|
||||
setTimeout(() => {
|
||||
dataSource.value = mockData;
|
||||
pagination.total = mockData.length;
|
||||
loading.value = false;
|
||||
}, 500);
|
||||
};
|
||||
|
||||
// 状态相关
|
||||
const getStatusColor = (status) => {
|
||||
const statusMap = {
|
||||
online: 'green',
|
||||
offline: 'gray',
|
||||
maintenance: 'orange'
|
||||
};
|
||||
return statusMap[status] || 'default';
|
||||
};
|
||||
|
||||
const getStatusText = (status) => {
|
||||
const statusMap = {
|
||||
online: '在线',
|
||||
offline: '离线',
|
||||
maintenance: '维护中'
|
||||
};
|
||||
return statusMap[status] || '未知';
|
||||
};
|
||||
|
||||
// 搜索相关
|
||||
const handleSearch = () => {
|
||||
pagination.current = 1;
|
||||
fetchData();
|
||||
};
|
||||
|
||||
const resetSearch = () => {
|
||||
searchForm.hostId = '';
|
||||
searchForm.status = undefined;
|
||||
handleSearch();
|
||||
};
|
||||
|
||||
// 表格相关
|
||||
const handleTableChange = (pag, filters, sorter) => {
|
||||
pagination.current = pag.current;
|
||||
pagination.pageSize = pag.pageSize;
|
||||
fetchData();
|
||||
};
|
||||
|
||||
// 模态框相关
|
||||
const showAddModal = () => {
|
||||
modalTitle.value = '添加主机';
|
||||
formData.id = null;
|
||||
formData.hostId = '';
|
||||
formData.name = '';
|
||||
formData.ipAddress = '';
|
||||
formData.status = 'offline';
|
||||
formData.remark = '';
|
||||
modalVisible.value = true;
|
||||
};
|
||||
|
||||
const showEditModal = (record) => {
|
||||
modalTitle.value = '编辑主机';
|
||||
formData.id = record.id;
|
||||
formData.hostId = record.hostId;
|
||||
formData.name = record.name;
|
||||
formData.ipAddress = record.ipAddress;
|
||||
formData.status = record.status;
|
||||
formData.remark = record.remark;
|
||||
modalVisible.value = true;
|
||||
};
|
||||
|
||||
const handleModalOk = () => {
|
||||
formRef.value.validate().then(() => {
|
||||
modalLoading.value = true;
|
||||
// 模拟API请求
|
||||
setTimeout(() => {
|
||||
if (formData.id) {
|
||||
// 编辑
|
||||
const index = mockData.findIndex(item => item.id === formData.id);
|
||||
if (index !== -1) {
|
||||
mockData[index] = {
|
||||
...mockData[index],
|
||||
...formData,
|
||||
updatedAt: new Date().toLocaleString()
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// 添加
|
||||
mockData.push({
|
||||
id: String(mockData.length + 1),
|
||||
...formData,
|
||||
createdAt: new Date().toLocaleString(),
|
||||
updatedAt: new Date().toLocaleString()
|
||||
});
|
||||
}
|
||||
modalLoading.value = false;
|
||||
modalVisible.value = false;
|
||||
fetchData();
|
||||
}, 500);
|
||||
});
|
||||
};
|
||||
|
||||
const handleModalCancel = () => {
|
||||
modalVisible.value = false;
|
||||
};
|
||||
|
||||
// 详情相关
|
||||
const showDetailModal = (record) => {
|
||||
Object.assign(detailData, record);
|
||||
detailModalVisible.value = true;
|
||||
};
|
||||
|
||||
const handleDetailModalCancel = () => {
|
||||
detailModalVisible.value = false;
|
||||
};
|
||||
|
||||
// 删除
|
||||
const handleDelete = (record) => {
|
||||
loading.value = true;
|
||||
// 模拟API请求
|
||||
setTimeout(() => {
|
||||
const index = mockData.findIndex(item => item.id === record.id);
|
||||
if (index !== -1) {
|
||||
mockData.splice(index, 1);
|
||||
}
|
||||
loading.value = false;
|
||||
fetchData();
|
||||
}, 500);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchData();
|
||||
});
|
||||
|
||||
return {
|
||||
searchForm,
|
||||
loading,
|
||||
dataSource,
|
||||
pagination,
|
||||
columns,
|
||||
modalVisible,
|
||||
modalLoading,
|
||||
modalTitle,
|
||||
formRef,
|
||||
formData,
|
||||
rules,
|
||||
detailModalVisible,
|
||||
detailData,
|
||||
getStatusColor,
|
||||
getStatusText,
|
||||
handleSearch,
|
||||
resetSearch,
|
||||
handleTableChange,
|
||||
showAddModal,
|
||||
showEditModal,
|
||||
handleModalOk,
|
||||
handleModalCancel,
|
||||
showDetailModal,
|
||||
handleDetailModalCancel,
|
||||
handleDelete
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.smart-host-container {
|
||||
padding: 16px;
|
||||
}
|
||||
.danger-text {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user