修改养殖端小程序,保险前后端和小程序
This commit is contained in:
@@ -46,6 +46,69 @@ const swaggerDefinition = {
|
||||
updatedAt: { type: 'string', format: 'date-time', description: '更新时间' }
|
||||
}
|
||||
},
|
||||
Menu: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'integer',
|
||||
description: '菜单ID'
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
description: '菜单名称'
|
||||
},
|
||||
key: {
|
||||
type: 'string',
|
||||
description: '菜单唯一标识'
|
||||
},
|
||||
path: {
|
||||
type: 'string',
|
||||
description: '路由路径'
|
||||
},
|
||||
icon: {
|
||||
type: 'string',
|
||||
description: '菜单图标'
|
||||
},
|
||||
parent_id: {
|
||||
type: 'integer',
|
||||
description: '父菜单ID'
|
||||
},
|
||||
component: {
|
||||
type: 'string',
|
||||
description: '组件路径'
|
||||
},
|
||||
order: {
|
||||
type: 'integer',
|
||||
description: '排序号'
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
enum: ['active', 'inactive'],
|
||||
description: '菜单状态'
|
||||
},
|
||||
show: {
|
||||
type: 'boolean',
|
||||
description: '是否显示'
|
||||
},
|
||||
children: {
|
||||
type: 'array',
|
||||
items: {
|
||||
$ref: '#/components/schemas/Menu'
|
||||
},
|
||||
description: '子菜单列表'
|
||||
},
|
||||
created_at: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: '创建时间'
|
||||
},
|
||||
updated_at: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: '更新时间'
|
||||
}
|
||||
}
|
||||
},
|
||||
InsuranceApplication: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
|
||||
210
insurance_backend/controllers/dataWarehouseController.js
Normal file
210
insurance_backend/controllers/dataWarehouseController.js
Normal file
@@ -0,0 +1,210 @@
|
||||
const { User, Role, InsuranceApplication, Policy, Claim, InsuranceType } = require('../models');
|
||||
const responseFormat = require('../utils/response');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// 获取数据览仓概览数据
|
||||
const getOverview = async (req, res) => {
|
||||
try {
|
||||
const [
|
||||
totalUsers,
|
||||
totalApplications,
|
||||
totalPolicies,
|
||||
totalClaims,
|
||||
activePolicies,
|
||||
approvedClaims,
|
||||
pendingClaims
|
||||
] = await Promise.all([
|
||||
User.count(),
|
||||
InsuranceApplication.count(),
|
||||
Policy.count(),
|
||||
Claim.count(),
|
||||
Policy.count({ where: { policy_status: 'active' } }),
|
||||
Claim.count({ where: { claim_status: 'approved' } }),
|
||||
Claim.count({ where: { claim_status: 'pending' } })
|
||||
]);
|
||||
|
||||
res.json(responseFormat.success({
|
||||
totalUsers,
|
||||
totalApplications,
|
||||
totalPolicies,
|
||||
totalClaims,
|
||||
activePolicies,
|
||||
approvedClaims,
|
||||
pendingClaims
|
||||
}, '获取数据览仓概览成功'));
|
||||
} catch (error) {
|
||||
console.error('获取数据览仓概览错误:', error);
|
||||
res.status(500).json(responseFormat.error('获取数据览仓概览失败'));
|
||||
}
|
||||
};
|
||||
|
||||
// 获取保险类型分布数据
|
||||
const getInsuranceTypeDistribution = async (req, res) => {
|
||||
try {
|
||||
const types = await InsuranceType.findAll({
|
||||
attributes: ['id', 'name', 'description'],
|
||||
where: { status: 'active' }
|
||||
});
|
||||
|
||||
const distribution = await Promise.all(
|
||||
types.map(async type => {
|
||||
const count = await InsuranceApplication.count({
|
||||
where: { insurance_type_id: type.id }
|
||||
});
|
||||
return {
|
||||
id: type.id,
|
||||
name: type.name,
|
||||
description: type.description,
|
||||
count
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
res.json(responseFormat.success(distribution, '获取保险类型分布成功'));
|
||||
} catch (error) {
|
||||
console.error('获取保险类型分布错误:', error);
|
||||
res.status(500).json(responseFormat.error('获取保险类型分布失败'));
|
||||
}
|
||||
};
|
||||
|
||||
// 获取申请状态分布数据
|
||||
const getApplicationStatusDistribution = async (req, res) => {
|
||||
try {
|
||||
const [
|
||||
pendingCount,
|
||||
approvedCount,
|
||||
rejectedCount,
|
||||
underReviewCount
|
||||
] = await Promise.all([
|
||||
InsuranceApplication.count({ where: { status: 'pending' } }),
|
||||
InsuranceApplication.count({ where: { status: 'approved' } }),
|
||||
InsuranceApplication.count({ where: { status: 'rejected' } }),
|
||||
InsuranceApplication.count({ where: { status: 'under_review' } })
|
||||
]);
|
||||
|
||||
res.json(responseFormat.success([
|
||||
{ status: 'pending', name: '待处理', count: pendingCount },
|
||||
{ status: 'under_review', name: '审核中', count: underReviewCount },
|
||||
{ status: 'approved', name: '已批准', count: approvedCount },
|
||||
{ status: 'rejected', name: '已拒绝', count: rejectedCount }
|
||||
], '获取申请状态分布成功'));
|
||||
} catch (error) {
|
||||
console.error('获取申请状态分布错误:', error);
|
||||
res.status(500).json(responseFormat.error('获取申请状态分布失败'));
|
||||
}
|
||||
};
|
||||
|
||||
// 获取近7天趋势数据
|
||||
const getTrendData = async (req, res) => {
|
||||
try {
|
||||
const sevenDaysAgo = new Date();
|
||||
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
|
||||
|
||||
// 生成近7天的日期数组
|
||||
const dateArray = [];
|
||||
for (let i = 6; i >= 0; i--) {
|
||||
const date = new Date(sevenDaysAgo);
|
||||
date.setDate(date.getDate() + i);
|
||||
dateArray.push(date.toISOString().split('T')[0]); // 格式化为YYYY-MM-DD
|
||||
}
|
||||
|
||||
// 获取每天的新增数据
|
||||
const dailyData = await Promise.all(
|
||||
dateArray.map(async date => {
|
||||
const startDate = new Date(`${date} 00:00:00`);
|
||||
const endDate = new Date(`${date} 23:59:59`);
|
||||
|
||||
const [newApplications, newPolicies, newClaims] = await Promise.all([
|
||||
InsuranceApplication.count({
|
||||
where: {
|
||||
created_at: {
|
||||
[Op.between]: [startDate, endDate]
|
||||
}
|
||||
}
|
||||
}),
|
||||
Policy.count({
|
||||
where: {
|
||||
created_at: {
|
||||
[Op.between]: [startDate, endDate]
|
||||
}
|
||||
}
|
||||
}),
|
||||
Claim.count({
|
||||
where: {
|
||||
created_at: {
|
||||
[Op.between]: [startDate, endDate]
|
||||
}
|
||||
}
|
||||
})
|
||||
]);
|
||||
|
||||
return {
|
||||
date,
|
||||
newApplications,
|
||||
newPolicies,
|
||||
newClaims
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
res.json(responseFormat.success(dailyData, '获取趋势数据成功'));
|
||||
} catch (error) {
|
||||
console.error('获取趋势数据错误:', error);
|
||||
res.status(500).json(responseFormat.error('获取趋势数据失败'));
|
||||
}
|
||||
};
|
||||
|
||||
// 获取赔付统计数据
|
||||
const getClaimStats = async (req, res) => {
|
||||
try {
|
||||
const claims = await Claim.findAll({
|
||||
attributes: ['id', 'claim_amount', 'policy_id'],
|
||||
include: [{
|
||||
model: Policy,
|
||||
as: 'policy',
|
||||
attributes: ['policy_no', 'insurance_type_id']
|
||||
}]
|
||||
});
|
||||
|
||||
// 按保险类型分组统计赔付金额
|
||||
const typeStats = {};
|
||||
claims.forEach(claim => {
|
||||
const typeId = claim.policy?.insurance_type_id;
|
||||
if (typeId) {
|
||||
if (!typeStats[typeId]) {
|
||||
typeStats[typeId] = { id: typeId, totalAmount: 0, count: 0 };
|
||||
}
|
||||
typeStats[typeId].totalAmount += parseFloat(claim.claim_amount || 0);
|
||||
typeStats[typeId].count += 1;
|
||||
}
|
||||
});
|
||||
|
||||
// 获取保险类型名称
|
||||
const typeIds = Object.keys(typeStats).map(id => parseInt(id));
|
||||
const types = await InsuranceType.findAll({
|
||||
attributes: ['id', 'name'],
|
||||
where: { id: typeIds }
|
||||
});
|
||||
|
||||
types.forEach(type => {
|
||||
if (typeStats[type.id]) {
|
||||
typeStats[type.id].name = type.name;
|
||||
}
|
||||
});
|
||||
|
||||
const result = Object.values(typeStats);
|
||||
|
||||
res.json(responseFormat.success(result, '获取赔付统计成功'));
|
||||
} catch (error) {
|
||||
console.error('获取赔付统计错误:', error);
|
||||
res.status(500).json(responseFormat.error('获取赔付统计失败'));
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getOverview,
|
||||
getInsuranceTypeDistribution,
|
||||
getApplicationStatusDistribution,
|
||||
getTrendData,
|
||||
getClaimStats
|
||||
};
|
||||
161
insurance_backend/controllers/menuController.js
Normal file
161
insurance_backend/controllers/menuController.js
Normal file
@@ -0,0 +1,161 @@
|
||||
const { User, Role, Menu } = require('../models');
|
||||
|
||||
/**
|
||||
* 获取菜单列表
|
||||
* @param {Object} req - Express请求对象
|
||||
* @param {Object} res - Express响应对象
|
||||
*/
|
||||
exports.getMenus = async (req, res) => {
|
||||
try {
|
||||
// 获取用户ID(从JWT中解析或通过其他方式获取)
|
||||
const userId = req.user?.id; // 假设通过认证中间件解析后存在
|
||||
|
||||
// 如果没有用户ID,返回基础菜单
|
||||
if (!userId) {
|
||||
const menus = await Menu.findAll({
|
||||
where: {
|
||||
parent_id: null,
|
||||
show: true,
|
||||
status: 'active'
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: Menu,
|
||||
as: 'children',
|
||||
where: {
|
||||
show: true,
|
||||
status: 'active'
|
||||
},
|
||||
required: false,
|
||||
order: [['order', 'ASC']]
|
||||
}
|
||||
],
|
||||
order: [['order', 'ASC']]
|
||||
});
|
||||
|
||||
return res.json({
|
||||
code: 200,
|
||||
status: 'success',
|
||||
data: menus,
|
||||
message: '获取菜单成功'
|
||||
});
|
||||
}
|
||||
|
||||
// 获取用户信息和角色
|
||||
const user = await User.findByPk(userId, {
|
||||
include: [
|
||||
{
|
||||
model: Role,
|
||||
attributes: ['id', 'name', 'permissions']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
status: 'error',
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 获取角色的权限列表
|
||||
const userPermissions = user.Role?.permissions || [];
|
||||
|
||||
// 查询菜单,这里简化处理,实际应用中可能需要根据权限过滤
|
||||
const menus = await Menu.findAll({
|
||||
where: {
|
||||
parent_id: null,
|
||||
show: true,
|
||||
status: 'active'
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: Menu,
|
||||
as: 'children',
|
||||
where: {
|
||||
show: true,
|
||||
status: 'active'
|
||||
},
|
||||
required: false,
|
||||
order: [['order', 'ASC']]
|
||||
}
|
||||
],
|
||||
order: [['order', 'ASC']]
|
||||
});
|
||||
|
||||
// 这里可以添加根据权限过滤菜单的逻辑
|
||||
// 简化示例,假设所有用户都能看到所有激活的菜单
|
||||
|
||||
return res.json({
|
||||
code: 200,
|
||||
status: 'success',
|
||||
data: menus,
|
||||
message: '获取菜单成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取菜单失败:', error);
|
||||
return res.status(500).json({
|
||||
code: 500,
|
||||
status: 'error',
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取所有菜单(包括非激活状态,仅管理员可用)
|
||||
* @param {Object} req - Express请求对象
|
||||
* @param {Object} res - Express响应对象
|
||||
*/
|
||||
exports.getAllMenus = async (req, res) => {
|
||||
try {
|
||||
// 检查用户是否为管理员(简化示例)
|
||||
const user = await User.findByPk(req.user?.id, {
|
||||
include: [
|
||||
{
|
||||
model: Role,
|
||||
attributes: ['id', 'name', 'permissions']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!user || !user.Role || !user.Role.permissions.includes('*:*')) {
|
||||
return res.status(403).json({
|
||||
code: 403,
|
||||
status: 'error',
|
||||
message: '没有权限查看所有菜单'
|
||||
});
|
||||
}
|
||||
|
||||
// 查询所有菜单
|
||||
const menus = await Menu.findAll({
|
||||
where: {
|
||||
parent_id: null
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: Menu,
|
||||
as: 'children',
|
||||
required: false,
|
||||
order: [['order', 'ASC']]
|
||||
}
|
||||
],
|
||||
order: [['order', 'ASC']]
|
||||
});
|
||||
|
||||
return res.json({
|
||||
code: 200,
|
||||
status: 'success',
|
||||
data: menus,
|
||||
message: '获取所有菜单成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取所有菜单失败:', error);
|
||||
return res.status(500).json({
|
||||
code: 500,
|
||||
status: 'error',
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
183
insurance_backend/create_admin_user.js
Normal file
183
insurance_backend/create_admin_user.js
Normal file
@@ -0,0 +1,183 @@
|
||||
const { sequelize } = require('./config/database');
|
||||
const bcrypt = require('bcrypt');
|
||||
|
||||
async function createAdminUser() {
|
||||
try {
|
||||
// 连接数据库
|
||||
await sequelize.authenticate();
|
||||
console.log('✅ 数据库连接成功');
|
||||
|
||||
// 检查是否有users表
|
||||
const [tables] = await sequelize.query(
|
||||
"SHOW TABLES LIKE 'users'"
|
||||
);
|
||||
|
||||
if (tables.length === 0) {
|
||||
console.log('⚠️ users表不存在,开始创建必要的表结构');
|
||||
|
||||
// 创建roles表
|
||||
await sequelize.query(`
|
||||
CREATE TABLE IF NOT EXISTS roles (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '角色ID',
|
||||
name VARCHAR(50) NOT NULL UNIQUE COMMENT '角色名称',
|
||||
description VARCHAR(255) NULL COMMENT '角色描述',
|
||||
permissions JSON NOT NULL COMMENT '权限配置',
|
||||
status ENUM('active', 'inactive') NOT NULL DEFAULT 'active' COMMENT '状态',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
INDEX idx_roles_name (name),
|
||||
INDEX idx_roles_status (status)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色表';
|
||||
`);
|
||||
console.log('✅ roles表创建完成');
|
||||
|
||||
// 创建users表
|
||||
await sequelize.query(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '用户ID',
|
||||
username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名',
|
||||
password VARCHAR(255) NOT NULL COMMENT '密码',
|
||||
real_name VARCHAR(50) NOT NULL COMMENT '真实姓名',
|
||||
email VARCHAR(100) NOT NULL UNIQUE COMMENT '邮箱',
|
||||
phone VARCHAR(20) NOT NULL COMMENT '手机号',
|
||||
role_id INT NOT NULL COMMENT '角色ID',
|
||||
status ENUM('active', 'inactive', 'suspended') NOT NULL DEFAULT 'active' COMMENT '状态',
|
||||
last_login TIMESTAMP NULL COMMENT '最后登录时间',
|
||||
avatar VARCHAR(255) NULL COMMENT '头像URL',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
INDEX idx_users_username (username),
|
||||
INDEX idx_users_email (email),
|
||||
INDEX idx_users_phone (phone),
|
||||
INDEX idx_users_role_id (role_id),
|
||||
INDEX idx_users_status (status),
|
||||
FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE RESTRICT
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';
|
||||
`);
|
||||
console.log('✅ users表创建完成');
|
||||
|
||||
// 创建system_configs表
|
||||
await sequelize.query(`
|
||||
CREATE TABLE IF NOT EXISTS system_configs (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
config_key VARCHAR(50) NOT NULL UNIQUE,
|
||||
config_value TEXT NOT NULL,
|
||||
description VARCHAR(255) NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_config_key (config_key)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
`);
|
||||
console.log('✅ system_configs表创建完成');
|
||||
} else {
|
||||
console.log('✅ users表已存在');
|
||||
}
|
||||
|
||||
// 检查admin角色是否存在
|
||||
const [adminRole] = await sequelize.query(
|
||||
'SELECT id FROM roles WHERE name = ?',
|
||||
{ replacements: ['admin'] }
|
||||
);
|
||||
|
||||
let roleId;
|
||||
if (adminRole.length === 0) {
|
||||
// 创建admin角色
|
||||
await sequelize.query(
|
||||
'INSERT INTO roles (name, description, permissions, status) VALUES (?, ?, ?, ?)',
|
||||
{
|
||||
replacements: [
|
||||
'admin',
|
||||
'系统管理员',
|
||||
JSON.stringify(['*']),
|
||||
'active'
|
||||
]
|
||||
}
|
||||
);
|
||||
console.log('✅ admin角色创建完成');
|
||||
|
||||
// 获取刚创建的角色ID
|
||||
const [newRole] = await sequelize.query(
|
||||
'SELECT id FROM roles WHERE name = ?',
|
||||
{ replacements: ['admin'] }
|
||||
);
|
||||
roleId = newRole[0].id;
|
||||
} else {
|
||||
roleId = adminRole[0].id;
|
||||
console.log('✅ admin角色已存在');
|
||||
}
|
||||
|
||||
// 检查admin用户是否存在
|
||||
const [adminUser] = await sequelize.query(
|
||||
'SELECT id FROM users WHERE username = ?',
|
||||
{ replacements: ['admin'] }
|
||||
);
|
||||
|
||||
if (adminUser.length === 0) {
|
||||
// 密码为123456,使用bcrypt进行哈希处理
|
||||
const plainPassword = '123456';
|
||||
const hashedPassword = await bcrypt.hash(plainPassword, 12);
|
||||
|
||||
// 创建admin用户
|
||||
await sequelize.query(
|
||||
`INSERT INTO users (username, password, real_name, email, phone, role_id, status)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
{
|
||||
replacements: [
|
||||
'admin',
|
||||
hashedPassword,
|
||||
'系统管理员',
|
||||
'admin@insurance.com',
|
||||
'13800138000',
|
||||
roleId,
|
||||
'active'
|
||||
]
|
||||
}
|
||||
);
|
||||
console.log('✅ admin用户创建完成,用户名: admin,密码: 123456');
|
||||
|
||||
// 插入默认系统配置
|
||||
await sequelize.query(
|
||||
`INSERT IGNORE INTO system_configs (config_key, config_value, description) VALUES
|
||||
('system_name', ?, '系统名称'),
|
||||
('company_name', ?, '公司名称'),
|
||||
('contact_email', ?, '联系邮箱'),
|
||||
('contact_phone', ?, '联系电话'),
|
||||
('max_file_size', ?, '最大文件上传大小(字节)'),
|
||||
('allowed_file_types', ?, '允许上传的文件类型')`,
|
||||
{
|
||||
replacements: [
|
||||
JSON.stringify('保险端口系统'),
|
||||
JSON.stringify('XX保险公司'),
|
||||
JSON.stringify('support@insurance.com'),
|
||||
JSON.stringify('400-123-4567'),
|
||||
'10485760',
|
||||
JSON.stringify(['jpg', 'jpeg', 'png', 'pdf', 'doc', 'docx'])
|
||||
]
|
||||
}
|
||||
);
|
||||
console.log('✅ 默认系统配置已插入');
|
||||
} else {
|
||||
console.log('⚠️ admin用户已存在,更新密码为: 123456');
|
||||
// 更新admin用户的密码
|
||||
const plainPassword = '123456';
|
||||
const hashedPassword = await bcrypt.hash(plainPassword, 12);
|
||||
|
||||
await sequelize.query(
|
||||
'UPDATE users SET password = ? WHERE username = ?',
|
||||
{ replacements: [hashedPassword, 'admin'] }
|
||||
);
|
||||
console.log('✅ admin用户密码已更新为: 123456');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 操作失败:', error.message);
|
||||
console.error(error.stack);
|
||||
} finally {
|
||||
// 关闭数据库连接
|
||||
await sequelize.close();
|
||||
console.log('🔒 数据库连接已关闭');
|
||||
}
|
||||
}
|
||||
|
||||
// 执行脚本
|
||||
createAdminUser();
|
||||
135
insurance_backend/docs/dynamic_menu_guide.md
Normal file
135
insurance_backend/docs/dynamic_menu_guide.md
Normal file
@@ -0,0 +1,135 @@
|
||||
# 动态菜单功能实现指南
|
||||
|
||||
## 概述
|
||||
|
||||
本文档描述了保险PC端管理系统中动态菜单功能的实现方案,包括后端API设计、数据库设计、前端集成和数据初始化方法。
|
||||
|
||||
## 功能特点
|
||||
|
||||
1. **全动态菜单**: 所有菜单数据从MySQL数据库动态获取
|
||||
2. **菜单层次结构**: 支持多级菜单结构
|
||||
3. **权限控制**: 支持基于用户角色的菜单权限控制
|
||||
4. **自动排序**: 菜单按预定义顺序显示
|
||||
5. **备用菜单**: 当API请求失败时提供默认菜单
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 1. 数据库设计
|
||||
|
||||
菜单数据存储在`menus`表中,使用自关联实现父子菜单关系:
|
||||
|
||||
```sql
|
||||
CREATE TABLE menus (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(50) NOT NULL,
|
||||
key VARCHAR(50) NOT NULL UNIQUE,
|
||||
path VARCHAR(100) NOT NULL,
|
||||
icon VARCHAR(50) NULL,
|
||||
parent_id INT NULL,
|
||||
component VARCHAR(100) NULL,
|
||||
`order` INT NOT NULL DEFAULT 0,
|
||||
status ENUM('active', 'inactive') DEFAULT 'active',
|
||||
show BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (parent_id) REFERENCES menus(id)
|
||||
);
|
||||
```
|
||||
|
||||
### 2. 后端实现
|
||||
|
||||
#### 2.1 菜单模型
|
||||
|
||||
`models/Menu.js`定义了菜单的数据结构和关联关系:
|
||||
- 支持自关联的父子菜单关系
|
||||
- 包含菜单的名称、唯一标识、路径、图标等属性
|
||||
- 支持菜单排序和状态控制
|
||||
|
||||
#### 2.2 菜单控制器
|
||||
|
||||
`controllers/menuController.js`实现了菜单相关的业务逻辑:
|
||||
- `getMenus`: 获取当前用户的菜单列表(考虑权限)
|
||||
- `getAllMenus`: 获取所有菜单(仅管理员可用)
|
||||
|
||||
#### 2.3 菜单路由
|
||||
|
||||
`routes/menus.js`定义了菜单相关的API接口:
|
||||
- `GET /api/menus`: 获取当前用户的菜单列表
|
||||
- `GET /api/menus/all`: 获取所有菜单(管理员专用)
|
||||
|
||||
#### 2.4 API文档集成
|
||||
|
||||
菜单API已集成到Swagger文档中,可通过`http://localhost:3000/api-docs/#/`访问查看。
|
||||
|
||||
### 3. 前端实现
|
||||
|
||||
#### 3.1 API调用
|
||||
|
||||
`utils/api.js`中添加了菜单相关的API调用方法:
|
||||
- `getMenus()`: 获取当前用户的菜单列表
|
||||
- `getAllMenus()`: 获取所有菜单(管理员专用)
|
||||
|
||||
#### 3.2 动态菜单渲染
|
||||
|
||||
`components/Layout.vue`组件实现了动态菜单的加载和渲染:
|
||||
- 使用`onMounted`生命周期钩子加载菜单数据
|
||||
- 支持菜单点击事件处理和路由跳转
|
||||
- 实现了图标映射和菜单格式化功能
|
||||
- 提供了默认菜单作为API请求失败时的备用方案
|
||||
|
||||
## 菜单数据初始化
|
||||
|
||||
### 方法一:直接执行SQL脚本
|
||||
|
||||
1. 登录MySQL数据库
|
||||
2. 选择`insurance_data`数据库
|
||||
3. 执行`scripts/init_menus.sql`脚本
|
||||
|
||||
```bash
|
||||
mysql -u root -p insurance_data < scripts/init_menus.sql
|
||||
```
|
||||
|
||||
### 方法二:使用Node.js脚本
|
||||
|
||||
1. 确保已安装依赖:`npm install mysql2 dotenv`
|
||||
2. 执行`seed_menus.js`脚本:
|
||||
|
||||
```bash
|
||||
node scripts/seed_menus.js
|
||||
```
|
||||
|
||||
## 菜单列表
|
||||
|
||||
系统包含以下菜单:
|
||||
|
||||
1. **仪表板**
|
||||
2. **数据揽仓**
|
||||
3. **监管任务**
|
||||
4. **待安装任务**
|
||||
5. **监管任务已结项**
|
||||
6. **投保客户单**
|
||||
- 参保申请
|
||||
7. **生资保单**
|
||||
- 生资保单列表
|
||||
8. **险种管理**
|
||||
- 险种管理
|
||||
9. **客户理赔**
|
||||
- 客户理赔
|
||||
10. **消息通知**
|
||||
11. **子账号管理**
|
||||
12. **系统设置**
|
||||
13. **个人中心**
|
||||
|
||||
## 使用说明
|
||||
|
||||
1. 确保后端服务正常运行
|
||||
2. 确保菜单数据已正确初始化到数据库
|
||||
3. 登录系统后,系统会自动从后端API获取并渲染菜单
|
||||
4. 点击菜单项会根据配置的路径进行路由跳转
|
||||
|
||||
## 扩展建议
|
||||
|
||||
1. 可以扩展菜单模型,添加更多的权限控制字段
|
||||
2. 实现菜单管理界面,支持动态增删改查菜单
|
||||
3. 优化菜单缓存机制,减少重复的API请求
|
||||
4. 完善菜单的国际化支持
|
||||
85
insurance_backend/models/Menu.js
Normal file
85
insurance_backend/models/Menu.js
Normal file
@@ -0,0 +1,85 @@
|
||||
const { sequelize } = require('../config/database');
|
||||
const { DataTypes } = require('sequelize');
|
||||
|
||||
const Menu = sequelize.define('Menu', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
validate: {
|
||||
len: [2, 50]
|
||||
}
|
||||
},
|
||||
key: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
validate: {
|
||||
len: [2, 50]
|
||||
}
|
||||
},
|
||||
path: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
validate: {
|
||||
len: [1, 100]
|
||||
}
|
||||
},
|
||||
icon: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true
|
||||
},
|
||||
parent_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'menus',
|
||||
key: 'id'
|
||||
},
|
||||
defaultValue: null
|
||||
},
|
||||
component: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
},
|
||||
order: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('active', 'inactive'),
|
||||
defaultValue: 'active'
|
||||
},
|
||||
show: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: true
|
||||
}
|
||||
}, {
|
||||
tableName: 'menus',
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
indexes: [
|
||||
{ fields: ['key'] },
|
||||
{ fields: ['parent_id'] },
|
||||
{ fields: ['status'] },
|
||||
{ fields: ['order'] }
|
||||
]
|
||||
});
|
||||
|
||||
// 设置自关联
|
||||
Menu.hasMany(Menu, {
|
||||
as: 'children',
|
||||
foreignKey: 'parent_id'
|
||||
});
|
||||
|
||||
Menu.belongsTo(Menu, {
|
||||
as: 'parent',
|
||||
foreignKey: 'parent_id'
|
||||
});
|
||||
|
||||
module.exports = Menu;
|
||||
@@ -1,12 +1,12 @@
|
||||
const { sequelize } = require('../config/database');
|
||||
|
||||
// 导入所有模型
|
||||
// 导入数据库配置和所有模型
|
||||
const { sequelize } = require('../config/database');
|
||||
const User = require('./User');
|
||||
const Role = require('./Role');
|
||||
const InsuranceApplication = require('./InsuranceApplication');
|
||||
const InsuranceType = require('./InsuranceType');
|
||||
const Policy = require('./Policy');
|
||||
const Claim = require('./Claim');
|
||||
const Menu = require('./Menu');
|
||||
|
||||
// 定义模型关联关系
|
||||
|
||||
@@ -62,5 +62,6 @@ module.exports = {
|
||||
InsuranceApplication,
|
||||
InsuranceType,
|
||||
Policy,
|
||||
Claim
|
||||
Claim,
|
||||
Menu
|
||||
};
|
||||
163
insurance_backend/routes/dataWarehouse.js
Normal file
163
insurance_backend/routes/dataWarehouse.js
Normal file
@@ -0,0 +1,163 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const dataWarehouseController = require('../controllers/dataWarehouseController');
|
||||
const { jwtAuth, checkPermission } = require('../middleware/auth');
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* tags:
|
||||
* name: DataWarehouse
|
||||
* description: 数据览仓相关接口
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/data-warehouse/overview:
|
||||
* get:
|
||||
* summary: 获取数据览仓概览数据
|
||||
* tags: [DataWarehouse]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功获取概览数据
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* code: { type: 'number' }
|
||||
* status: { type: 'string' }
|
||||
* data: { type: 'object' }
|
||||
* message: { type: 'string' }
|
||||
* timestamp: { type: 'string' }
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/overview', jwtAuth, checkPermission('data', 'read'),
|
||||
dataWarehouseController.getOverview
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/data-warehouse/insurance-type-distribution:
|
||||
* get:
|
||||
* summary: 获取保险类型分布数据
|
||||
* tags: [DataWarehouse]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功获取保险类型分布数据
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* code: { type: 'number' }
|
||||
* status: { type: 'string' }
|
||||
* data: { type: 'array' }
|
||||
* message: { type: 'string' }
|
||||
* timestamp: { type: 'string' }
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/insurance-type-distribution', jwtAuth, checkPermission('data', 'read'),
|
||||
dataWarehouseController.getInsuranceTypeDistribution
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/data-warehouse/application-status-distribution:
|
||||
* get:
|
||||
* summary: 获取申请状态分布数据
|
||||
* tags: [DataWarehouse]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功获取申请状态分布数据
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* code: { type: 'number' }
|
||||
* status: { type: 'string' }
|
||||
* data: { type: 'array' }
|
||||
* message: { type: 'string' }
|
||||
* timestamp: { type: 'string' }
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/application-status-distribution', jwtAuth, checkPermission('data', 'read'),
|
||||
dataWarehouseController.getApplicationStatusDistribution
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/data-warehouse/trend-data:
|
||||
* get:
|
||||
* summary: 获取近7天趋势数据
|
||||
* tags: [DataWarehouse]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功获取趋势数据
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* code: { type: 'number' }
|
||||
* status: { type: 'string' }
|
||||
* data: { type: 'array' }
|
||||
* message: { type: 'string' }
|
||||
* timestamp: { type: 'string' }
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/trend-data', jwtAuth, checkPermission('data', 'read'),
|
||||
dataWarehouseController.getTrendData
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/data-warehouse/claim-stats:
|
||||
* get:
|
||||
* summary: 获取赔付统计数据
|
||||
* tags: [DataWarehouse]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功获取赔付统计数据
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* code: { type: 'number' }
|
||||
* status: { type: 'string' }
|
||||
* data: { type: 'array' }
|
||||
* message: { type: 'string' }
|
||||
* timestamp: { type: 'string' }
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/claim-stats', jwtAuth, checkPermission('data', 'read'),
|
||||
dataWarehouseController.getClaimStats
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
119
insurance_backend/routes/menus.js
Normal file
119
insurance_backend/routes/menus.js
Normal file
@@ -0,0 +1,119 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const menuController = require('../controllers/menuController');
|
||||
const { jwtAuth } = require('../middleware/auth');
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* tags:
|
||||
* name: Menus
|
||||
* description: 菜单管理相关接口
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/menus/public:
|
||||
* get:
|
||||
* summary: 获取公开菜单列表(无需认证)
|
||||
* tags: [Menus]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功获取菜单列表
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* code:
|
||||
* type: integer
|
||||
* example: 200
|
||||
* status:
|
||||
* type: string
|
||||
* example: success
|
||||
* data:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/Menu'
|
||||
* message:
|
||||
* type: string
|
||||
* example: 获取菜单成功
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/public', menuController.getMenus);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/menus:
|
||||
* get:
|
||||
* summary: 获取当前用户的菜单列表
|
||||
* tags: [Menus]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功获取菜单列表
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* code:
|
||||
* type: integer
|
||||
* example: 200
|
||||
* status:
|
||||
* type: string
|
||||
* example: success
|
||||
* data:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/Menu'
|
||||
* message:
|
||||
* type: string
|
||||
* example: 获取菜单成功
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/', jwtAuth, menuController.getMenus);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/menus/all:
|
||||
* get:
|
||||
* summary: 获取所有菜单(包括非激活状态,仅管理员可用)
|
||||
* tags: [Menus]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功获取所有菜单
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* code:
|
||||
* type: integer
|
||||
* example: 200
|
||||
* status:
|
||||
* type: string
|
||||
* example: success
|
||||
* data:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/Menu'
|
||||
* message:
|
||||
* type: string
|
||||
* example: 获取所有菜单成功
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 没有权限
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/all', jwtAuth, menuController.getAllMenus);
|
||||
|
||||
module.exports = router;
|
||||
59
insurance_backend/scripts/init_menus.sql
Normal file
59
insurance_backend/scripts/init_menus.sql
Normal file
@@ -0,0 +1,59 @@
|
||||
-- 初始化菜单数据
|
||||
-- 注意:请确保menus表已经创建
|
||||
|
||||
-- 首先清空现有菜单数据
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
TRUNCATE TABLE menus;
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
-- 插入父级菜单
|
||||
INSERT INTO menus (name, key, path, icon, parent_id, component, `order`, status, show, created_at, updated_at)
|
||||
VALUES
|
||||
('仪表板', 'Dashboard', '/dashboard', 'DashboardOutlined', NULL, 'views/Dashboard.vue', 1, 'active', true, NOW(), NOW()),
|
||||
('数据揽仓', 'DataWarehouse', '/data-warehouse', 'DatabaseOutlined', NULL, '', 2, 'active', true, NOW(), NOW()),
|
||||
('监管任务', 'SupervisionTask', '/supervision-task', 'CheckCircleOutlined', NULL, '', 3, 'active', true, NOW(), NOW()),
|
||||
('待安装任务', 'PendingInstallationTask', '/pending-installation', 'ExclamationCircleOutlined', NULL, '', 4, 'active', true, NOW(), NOW()),
|
||||
('监管任务已结项', 'CompletedTask', '/completed-tasks', 'FileDoneOutlined', NULL, '', 5, 'active', true, NOW(), NOW()),
|
||||
('投保客户单', 'InsuredCustomers', '/insured-customers', 'ShopOutlined', NULL, '', 6, 'active', true, NOW(), NOW()),
|
||||
('生资保单', 'AgriculturalInsurance', '/agricultural-insurance', 'FileProtectOutlined', NULL, '', 7, 'active', true, NOW(), NOW()),
|
||||
('险种管理', 'InsuranceTypeManagement', '/insurance-types', 'MedicineBoxOutlined', NULL, '', 8, 'active', true, NOW(), NOW()),
|
||||
('客户理赔', 'CustomerClaims', '/customer-claims', 'AlertCircleOutlined', NULL, '', 9, 'active', true, NOW(), NOW()),
|
||||
('消息通知', 'Notifications', '/notifications', 'BellOutlined', NULL, '', 10, 'active', true, NOW(), NOW()),
|
||||
('子账号管理', 'UserManagement', '/users', 'UserAddOutlined', NULL, 'views/UserManagement.vue', 11, 'active', true, NOW(), NOW()),
|
||||
('系统设置', 'SystemSettings', '/system-settings', 'SettingOutlined', NULL, '', 12, 'active', true, NOW(), NOW()),
|
||||
('个人中心', 'UserProfile', '/profile', 'UserSwitchOutlined', NULL, '', 13, 'active', true, NOW(), NOW());
|
||||
|
||||
-- 插入子菜单
|
||||
-- 投保客户单的子菜单
|
||||
INSERT INTO menus (name, key, path, icon, parent_id, component, `order`, status, show, created_at, updated_at)
|
||||
SELECT
|
||||
'参保申请', 'ApplicationManagement', '/applications', 'FileTextOutlined', id, 'views/ApplicationManagement.vue', 1, 'active', true, NOW(), NOW()
|
||||
FROM menus
|
||||
WHERE `key` = 'InsuredCustomers';
|
||||
|
||||
-- 生资保单的子菜单
|
||||
INSERT INTO menus (name, key, path, icon, parent_id, component, `order`, status, show, created_at, updated_at)
|
||||
SELECT
|
||||
'生资保单列表', 'PolicyManagement', '/policies', 'FileDoneOutlined', id, 'views/PolicyManagement.vue', 1, 'active', true, NOW(), NOW()
|
||||
FROM menus
|
||||
WHERE `key` = 'AgriculturalInsurance';
|
||||
|
||||
-- 险种管理的子菜单
|
||||
INSERT INTO menus (name, key, path, icon, parent_id, component, `order`, status, show, created_at, updated_at)
|
||||
SELECT
|
||||
'险种管理', 'InsuranceTypeList', '/insurance-types', 'MedicineBoxOutlined', id, 'views/InsuranceTypeManagement.vue', 1, 'active', true, NOW(), NOW()
|
||||
FROM menus
|
||||
WHERE `key` = 'InsuranceTypeManagement';
|
||||
|
||||
-- 客户理赔的子菜单
|
||||
INSERT INTO menus (name, key, path, icon, parent_id, component, `order`, status, show, created_at, updated_at)
|
||||
SELECT
|
||||
'客户理赔', 'ClaimManagement', '/claims', 'SafetyCertificateOutlined', id, 'views/ClaimManagement.vue', 1, 'active', true, NOW(), NOW()
|
||||
FROM menus
|
||||
WHERE `key` = 'CustomerClaims';
|
||||
|
||||
-- 查询插入结果
|
||||
SELECT * FROM menus ORDER BY parent_id, `order`;
|
||||
|
||||
-- 输出插入的记录数
|
||||
SELECT CONCAT('成功插入 ', COUNT(*), ' 条菜单记录') AS result FROM menus;
|
||||
175
insurance_backend/scripts/initialize_menus.js
Normal file
175
insurance_backend/scripts/initialize_menus.js
Normal file
@@ -0,0 +1,175 @@
|
||||
// 初始化菜单表结构并插入数据
|
||||
const { sequelize } = require('../config/database');
|
||||
const { DataTypes } = require('sequelize');
|
||||
|
||||
// 定义Menu模型
|
||||
const Menu = sequelize.define('Menu', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
validate: {
|
||||
len: [2, 50]
|
||||
}
|
||||
},
|
||||
key: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
validate: {
|
||||
len: [2, 50]
|
||||
}
|
||||
},
|
||||
path: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
validate: {
|
||||
len: [1, 100]
|
||||
}
|
||||
},
|
||||
icon: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true
|
||||
},
|
||||
parent_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'menus',
|
||||
key: 'id'
|
||||
},
|
||||
defaultValue: null
|
||||
},
|
||||
component: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
},
|
||||
order: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('active', 'inactive'),
|
||||
defaultValue: 'active'
|
||||
},
|
||||
show: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: true
|
||||
}
|
||||
}, {
|
||||
tableName: 'menus',
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
indexes: [
|
||||
{ fields: ['key'] },
|
||||
{ fields: ['parent_id'] },
|
||||
{ fields: ['status'] },
|
||||
{ fields: ['order'] }
|
||||
]
|
||||
});
|
||||
|
||||
// 设置自关联
|
||||
Menu.hasMany(Menu, {
|
||||
as: 'children',
|
||||
foreignKey: 'parent_id'
|
||||
});
|
||||
|
||||
Menu.belongsTo(Menu, {
|
||||
as: 'parent',
|
||||
foreignKey: 'parent_id'
|
||||
});
|
||||
|
||||
// 初始化函数
|
||||
async function initializeMenus() {
|
||||
try {
|
||||
console.log('🔄 开始初始化菜单表结构...');
|
||||
|
||||
// 检查并创建表
|
||||
const tableExists = await sequelize.query(
|
||||
"SHOW TABLES LIKE 'menus'"
|
||||
);
|
||||
|
||||
if (tableExists[0].length === 0) {
|
||||
console.log('📝 menus表不存在,开始创建...');
|
||||
await Menu.sync({ force: true });
|
||||
console.log('✅ menus表创建成功');
|
||||
} else {
|
||||
console.log('ℹ️ menus表已存在,开始清空数据...');
|
||||
await sequelize.query('SET FOREIGN_KEY_CHECKS = 0');
|
||||
await sequelize.query('TRUNCATE TABLE menus');
|
||||
await sequelize.query('SET FOREIGN_KEY_CHECKS = 1');
|
||||
console.log('✅ menus表数据已清空');
|
||||
}
|
||||
|
||||
console.log('📊 开始插入菜单数据...');
|
||||
|
||||
// 插入父级菜单
|
||||
const parentMenus = await Menu.bulkCreate([
|
||||
{ name: '仪表板', key: 'Dashboard', path: '/dashboard', icon: 'DashboardOutlined', parent_id: null, component: 'views/Dashboard.vue', order: 1, status: 'active', show: true },
|
||||
{ name: '数据览仓', key: 'DataWarehouse', path: '/data-warehouse', icon: 'DatabaseOutlined', parent_id: null, component: '', order: 2, status: 'active', show: true },
|
||||
{ name: '监管任务', key: 'SupervisionTask', path: '/supervision-task', icon: 'CheckCircleOutlined', parent_id: null, component: '', order: 3, status: 'active', show: true },
|
||||
{ name: '待安装任务', key: 'PendingInstallationTask', path: '/pending-installation', icon: 'ExclamationCircleOutlined', parent_id: null, component: '', order: 4, status: 'active', show: true },
|
||||
{ name: '监管任务已结项', key: 'CompletedTask', path: '/completed-tasks', icon: 'FileDoneOutlined', parent_id: null, component: '', order: 5, status: 'active', show: true },
|
||||
{ name: '投保客户单', key: 'InsuredCustomers', path: '/insured-customers', icon: 'ShopOutlined', parent_id: null, component: '', order: 6, status: 'active', show: true },
|
||||
{ name: '生资保单', key: 'AgriculturalInsurance', path: '/agricultural-insurance', icon: 'FileProtectOutlined', parent_id: null, component: '', order: 7, status: 'active', show: true },
|
||||
{ name: '险种管理', key: 'InsuranceTypeManagement', path: '/insurance-types', icon: 'MedicineBoxOutlined', parent_id: null, component: '', order: 8, status: 'active', show: true },
|
||||
{ name: '客户理赔', key: 'CustomerClaims', path: '/customer-claims', icon: 'AlertCircleOutlined', parent_id: null, component: '', order: 9, status: 'active', show: true },
|
||||
{ name: '消息通知', key: 'Notifications', path: '/notifications', icon: 'BellOutlined', parent_id: null, component: '', order: 10, status: 'active', show: true },
|
||||
{ name: '子账号管理', key: 'UserManagement', path: '/users', icon: 'UserAddOutlined', parent_id: null, component: 'views/UserManagement.vue', order: 11, status: 'active', show: true },
|
||||
{ name: '系统设置', key: 'SystemSettings', path: '/system-settings', icon: 'SettingOutlined', parent_id: null, component: '', order: 12, status: 'active', show: true },
|
||||
{ name: '个人中心', key: 'UserProfile', path: '/profile', icon: 'UserSwitchOutlined', parent_id: null, component: '', order: 13, status: 'active', show: true }
|
||||
]);
|
||||
|
||||
console.log(`✅ 已插入 ${parentMenus.length} 条父级菜单数据`);
|
||||
|
||||
// 查找父级菜单ID
|
||||
const parentMenuMap = {};
|
||||
for (const menu of parentMenus) {
|
||||
parentMenuMap[menu.key] = menu.id;
|
||||
}
|
||||
|
||||
// 插入子菜单
|
||||
const subMenus = await Menu.bulkCreate([
|
||||
// 投保客户单的子菜单
|
||||
{ name: '参保申请', key: 'ApplicationManagement', path: '/applications', icon: 'FileTextOutlined', parent_id: parentMenuMap.InsuredCustomers, component: 'views/ApplicationManagement.vue', order: 1, status: 'active', show: true },
|
||||
|
||||
// 生资保单的子菜单
|
||||
{ name: '生资保单列表', key: 'PolicyManagement', path: '/policies', icon: 'FileDoneOutlined', parent_id: parentMenuMap.AgriculturalInsurance, component: 'views/PolicyManagement.vue', order: 1, status: 'active', show: true },
|
||||
|
||||
// 险种管理的子菜单
|
||||
{ name: '险种管理', key: 'InsuranceTypeList', path: '/insurance-types', icon: 'MedicineBoxOutlined', parent_id: parentMenuMap.InsuranceTypeManagement, component: 'views/InsuranceTypeManagement.vue', order: 1, status: 'active', show: true },
|
||||
|
||||
// 客户理赔的子菜单
|
||||
{ name: '客户理赔', key: 'ClaimManagement', path: '/claims', icon: 'SafetyCertificateOutlined', parent_id: parentMenuMap.CustomerClaims, component: 'views/ClaimManagement.vue', order: 1, status: 'active', show: true }
|
||||
]);
|
||||
|
||||
console.log(`✅ 已插入 ${subMenus.length} 条子菜单数据`);
|
||||
|
||||
// 查询所有菜单
|
||||
const allMenus = await Menu.findAll({
|
||||
order: [['parent_id', 'ASC'], ['order', 'ASC']],
|
||||
include: [{ model: Menu, as: 'children' }]
|
||||
});
|
||||
|
||||
console.log(`✅ 菜单数据初始化完成,共 ${allMenus.length} 条记录`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 菜单数据初始化失败:', error.message);
|
||||
throw error;
|
||||
} finally {
|
||||
// 关闭数据库连接
|
||||
await sequelize.close();
|
||||
console.log('🔌 数据库连接已关闭');
|
||||
}
|
||||
}
|
||||
|
||||
// 执行初始化
|
||||
initializeMenus().catch(err => {
|
||||
console.error('❌ 菜单数据初始化任务失败');
|
||||
process.exit(1);
|
||||
});
|
||||
225
insurance_backend/scripts/seed_data_warehouse.js
Normal file
225
insurance_backend/scripts/seed_data_warehouse.js
Normal file
@@ -0,0 +1,225 @@
|
||||
const { sequelize } = require('../config/database');
|
||||
const { DataTypes } = require('sequelize');
|
||||
const { User, Role, InsuranceType, InsuranceApplication, Policy, Claim } = require('../models');
|
||||
|
||||
// 创建随机数据的辅助函数
|
||||
const randomString = (length = 10, chars = 'abcdefghijklmnopqrstuvwxyz0123456789') => {
|
||||
let result = '';
|
||||
const charsLength = chars.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * charsLength));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const randomDate = (start, end) => {
|
||||
return new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime()));
|
||||
};
|
||||
|
||||
const randomNumber = (min, max) => {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
};
|
||||
|
||||
const randomDecimal = (min, max, decimals = 2) => {
|
||||
return Number((Math.random() * (max - min) + min).toFixed(decimals));
|
||||
};
|
||||
|
||||
const generateApplicationNo = () => {
|
||||
return `APP${Date.now()}${randomString(6).toUpperCase()}`;
|
||||
};
|
||||
|
||||
const generatePolicyNo = () => {
|
||||
return `POL${Date.now()}${randomString(6).toUpperCase()}`;
|
||||
};
|
||||
|
||||
const generateClaimNo = () => {
|
||||
return `CLAIM${Date.now()}${randomString(6).toUpperCase()}`;
|
||||
};
|
||||
|
||||
// 生成随机身份证号
|
||||
const generateIdCard = () => {
|
||||
// 简化版身份证号生成,真实场景需要更复杂的校验
|
||||
const areaCode = randomNumber(110000, 659004);
|
||||
const birthYear = randomNumber(1960, 2000);
|
||||
const birthMonth = String(randomNumber(1, 12)).padStart(2, '0');
|
||||
const birthDay = String(randomNumber(1, 28)).padStart(2, '0');
|
||||
const seq = String(randomNumber(100, 999)).padStart(3, '0');
|
||||
const checkDigit = Math.random() > 0.9 ? 'X' : randomNumber(0, 9);
|
||||
|
||||
return `${areaCode}${birthYear}${birthMonth}${birthDay}${seq}${checkDigit}`;
|
||||
};
|
||||
|
||||
// 生成随机手机号
|
||||
const generatePhone = () => {
|
||||
return `1${randomNumber(3, 9)}${randomString(9, '0123456789')}`;
|
||||
};
|
||||
|
||||
// 初始化测试数据
|
||||
async function seedData() {
|
||||
try {
|
||||
// 确保数据库连接正常
|
||||
await sequelize.authenticate();
|
||||
console.log('数据库连接成功');
|
||||
|
||||
// 1. 创建角色(如果不存在)
|
||||
let adminRole = await Role.findOne({ where: { name: 'admin' } });
|
||||
if (!adminRole) {
|
||||
adminRole = await Role.create({
|
||||
name: 'admin',
|
||||
description: '管理员角色'
|
||||
});
|
||||
}
|
||||
|
||||
// 2. 创建用户(如果不存在)
|
||||
let adminUser = await User.findOne({ where: { username: 'admin' } });
|
||||
if (!adminUser) {
|
||||
adminUser = await User.create({
|
||||
username: 'admin',
|
||||
password: 'admin123', // 实际应用中应该使用bcrypt加密
|
||||
email: 'admin@example.com',
|
||||
phone: '13800138000',
|
||||
role_id: adminRole.id,
|
||||
status: 1
|
||||
});
|
||||
}
|
||||
|
||||
// 3. 创建保险类型
|
||||
const insuranceTypes = [
|
||||
{
|
||||
name: '健康保险',
|
||||
description: '提供医疗费用报销和健康保障',
|
||||
coverage_amount_min: 10000.00,
|
||||
coverage_amount_max: 500000.00,
|
||||
premium_rate: 0.005,
|
||||
terms_years: 1,
|
||||
status: 1
|
||||
},
|
||||
{
|
||||
name: '人寿保险',
|
||||
description: '为家人提供经济保障',
|
||||
coverage_amount_min: 50000.00,
|
||||
coverage_amount_max: 1000000.00,
|
||||
premium_rate: 0.01,
|
||||
terms_years: 10,
|
||||
status: 1
|
||||
},
|
||||
{
|
||||
name: '财产保险',
|
||||
description: '保障个人财产安全',
|
||||
coverage_amount_min: 20000.00,
|
||||
coverage_amount_max: 200000.00,
|
||||
premium_rate: 0.003,
|
||||
terms_years: 1,
|
||||
status: 1
|
||||
},
|
||||
{
|
||||
name: '意外保险',
|
||||
description: '提供意外事故保障',
|
||||
coverage_amount_min: 50000.00,
|
||||
coverage_amount_max: 300000.00,
|
||||
premium_rate: 0.002,
|
||||
terms_years: 1,
|
||||
status: 1
|
||||
}
|
||||
];
|
||||
|
||||
// 清除现有保险类型并插入新数据
|
||||
await InsuranceType.destroy({ where: {} });
|
||||
const createdTypes = await InsuranceType.bulkCreate(insuranceTypes);
|
||||
console.log(`已创建 ${createdTypes.length} 种保险类型`);
|
||||
|
||||
// 4. 创建保险申请、保单和理赔数据
|
||||
const applications = [];
|
||||
const policies = [];
|
||||
const claims = [];
|
||||
|
||||
// 为每种保险类型创建多个申请
|
||||
for (let i = 0; i < 50; i++) {
|
||||
const type = createdTypes[randomNumber(0, createdTypes.length - 1)];
|
||||
const coverageAmount = randomDecimal(type.coverage_amount_min, type.coverage_amount_max);
|
||||
const premium = Number((coverageAmount * type.premium_rate).toFixed(2));
|
||||
|
||||
const application = {
|
||||
application_no: generateApplicationNo(),
|
||||
customer_name: `客户${i + 1}`,
|
||||
customer_id_card: generateIdCard(),
|
||||
customer_phone: generatePhone(),
|
||||
customer_address: `测试地址${i + 1}号`,
|
||||
insurance_type_id: type.id,
|
||||
coverage_amount: coverageAmount,
|
||||
premium: premium,
|
||||
application_date: randomDate(new Date(2023, 0, 1), new Date()),
|
||||
status: randomNumber(0, 3), // 0: 待审核, 1: 已批准, 2: 已拒绝, 3: 已撤销
|
||||
reviewer_id: adminUser.id,
|
||||
review_date: randomDate(new Date(2023, 0, 1), new Date()),
|
||||
rejection_reason: randomNumber(0, 2) === 0 ? '资料不完整' : null
|
||||
};
|
||||
applications.push(application);
|
||||
}
|
||||
|
||||
// 清除现有申请并插入新数据
|
||||
await InsuranceApplication.destroy({ where: {} });
|
||||
const createdApplications = await InsuranceApplication.bulkCreate(applications);
|
||||
console.log(`已创建 ${createdApplications.length} 个保险申请`);
|
||||
|
||||
// 为已批准的申请创建保单
|
||||
for (const app of createdApplications) {
|
||||
if (app.status === 1) { // 只有已批准的申请才有保单
|
||||
const policy = {
|
||||
policy_no: generatePolicyNo(),
|
||||
application_id: app.id,
|
||||
insurance_type_id: app.insurance_type_id,
|
||||
customer_id: adminUser.id, // 简化处理,实际应该关联真实客户
|
||||
coverage_amount: app.coverage_amount,
|
||||
premium: app.premium,
|
||||
start_date: randomDate(app.review_date, new Date()),
|
||||
end_date: new Date(app.review_date.getTime() + 365 * 24 * 60 * 60 * 1000),
|
||||
status: 1, // 1: 有效
|
||||
created_by: adminUser.id,
|
||||
created_at: app.review_date
|
||||
};
|
||||
policies.push(policy);
|
||||
}
|
||||
}
|
||||
|
||||
// 清除现有保单并插入新数据
|
||||
await Policy.destroy({ where: {} });
|
||||
const createdPolicies = await Policy.bulkCreate(policies);
|
||||
console.log(`已创建 ${createdPolicies.length} 个保单`);
|
||||
|
||||
// 为部分保单创建理赔
|
||||
for (const policy of createdPolicies) {
|
||||
if (randomNumber(0, 3) === 0) { // 约25%的保单会有理赔
|
||||
const claimAmount = randomDecimal(policy.coverage_amount * 0.1, policy.coverage_amount * 0.8);
|
||||
|
||||
const claim = {
|
||||
claim_no: generateClaimNo(),
|
||||
policy_id: policy.id,
|
||||
customer_id: policy.customer_id,
|
||||
claim_amount: claimAmount,
|
||||
claim_date: randomDate(policy.start_date, new Date()),
|
||||
status: randomNumber(0, 2), // 0: 待审核, 1: 已批准, 2: 已拒绝
|
||||
claim_reason: '意外事故',
|
||||
created_at: randomDate(policy.start_date, new Date()),
|
||||
updated_at: new Date()
|
||||
};
|
||||
claims.push(claim);
|
||||
}
|
||||
}
|
||||
|
||||
// 清除现有理赔并插入新数据
|
||||
await Claim.destroy({ where: {} });
|
||||
const createdClaims = await Claim.bulkCreate(claims);
|
||||
console.log(`已创建 ${createdClaims.length} 个理赔记录`);
|
||||
|
||||
console.log('数据览仓测试数据插入完成');
|
||||
} catch (error) {
|
||||
console.error('数据插入过程中发生错误:', error);
|
||||
} finally {
|
||||
// 关闭数据库连接
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
// 执行数据初始化
|
||||
seedData();
|
||||
62
insurance_backend/scripts/seed_menus.js
Normal file
62
insurance_backend/scripts/seed_menus.js
Normal file
@@ -0,0 +1,62 @@
|
||||
const mysql = require('mysql2/promise');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
require('dotenv').config();
|
||||
|
||||
/**
|
||||
* 初始化菜单数据脚本
|
||||
* 该脚本会读取并执行init_menus.sql文件,为数据库添加初始菜单数据
|
||||
*/
|
||||
|
||||
async function seedMenus() {
|
||||
let connection;
|
||||
|
||||
try {
|
||||
// 创建数据库连接
|
||||
connection = await mysql.createConnection({
|
||||
host: process.env.DB_HOST || '129.211.213.226',
|
||||
port: process.env.DB_PORT || 9527,
|
||||
user: process.env.DB_USER || 'root',
|
||||
password: process.env.DB_PASSWORD || 'aiotAiot123!',
|
||||
database: process.env.DB_NAME || 'insurance_data'
|
||||
});
|
||||
|
||||
console.log('✅ 数据库连接成功');
|
||||
|
||||
// 读取SQL脚本文件
|
||||
const sqlFilePath = path.join(__dirname, 'init_menus.sql');
|
||||
const sql = fs.readFileSync(sqlFilePath, 'utf8');
|
||||
|
||||
console.log('📄 读取SQL脚本成功');
|
||||
|
||||
// 执行SQL脚本
|
||||
const [results] = await connection.query(sql);
|
||||
|
||||
console.log('🚀 菜单数据初始化成功');
|
||||
|
||||
// 查询并显示已插入的菜单数量
|
||||
const [menusCount] = await connection.query('SELECT COUNT(*) as count FROM menus');
|
||||
console.log(`✅ 共插入 ${menusCount[0].count} 条菜单记录`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 菜单数据初始化失败:', error.message);
|
||||
throw error;
|
||||
} finally {
|
||||
// 关闭数据库连接
|
||||
if (connection) {
|
||||
await connection.end();
|
||||
console.log('🔌 数据库连接已关闭');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 执行脚本
|
||||
seedMenus()
|
||||
.then(() => {
|
||||
console.log('✨ 菜单数据初始化任务已完成');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch(() => {
|
||||
console.error('❌ 菜单数据初始化任务失败');
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -8,7 +8,7 @@ const { sequelize, testConnection } = require('../config/database');
|
||||
require('dotenv').config();
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3000;
|
||||
const PORT = process.env.PORT || 3002;
|
||||
|
||||
// 安全中间件
|
||||
app.use(helmet());
|
||||
@@ -49,6 +49,8 @@ app.use('/api/insurance-types', require('../routes/insuranceTypes'));
|
||||
app.use('/api/policies', require('../routes/policies'));
|
||||
app.use('/api/claims', require('../routes/claims'));
|
||||
app.use('/api/system', require('../routes/system'));
|
||||
app.use('/api/menus', require('../routes/menus'));
|
||||
app.use('/api/data-warehouse', require('../routes/dataWarehouse'));
|
||||
|
||||
// API文档路由
|
||||
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec, {
|
||||
|
||||
42
insurance_backend/update_admin_password.js
Normal file
42
insurance_backend/update_admin_password.js
Normal file
@@ -0,0 +1,42 @@
|
||||
const { sequelize } = require('./config/database');
|
||||
const bcrypt = require('bcrypt');
|
||||
|
||||
async function updateAdminPassword() {
|
||||
try {
|
||||
// 连接数据库
|
||||
await sequelize.authenticate();
|
||||
console.log('✅ 数据库连接成功');
|
||||
|
||||
// 密码为123456,使用bcrypt进行哈希处理
|
||||
const plainPassword = '123456';
|
||||
const hashedPassword = await bcrypt.hash(plainPassword, 12);
|
||||
console.log('🔑 密码哈希生成成功');
|
||||
|
||||
// 更新admin用户的密码
|
||||
const [updatedRows] = await sequelize.query(
|
||||
'UPDATE users SET password = :password WHERE username = :username',
|
||||
{
|
||||
replacements: {
|
||||
password: hashedPassword,
|
||||
username: 'admin'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (updatedRows > 0) {
|
||||
console.log('✅ admin用户密码已更新为: 123456');
|
||||
} else {
|
||||
console.log('⚠️ 未找到admin用户,请检查数据库');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 更新密码失败:', error.message);
|
||||
} finally {
|
||||
// 关闭数据库连接
|
||||
await sequelize.close();
|
||||
console.log('🔒 数据库连接已关闭');
|
||||
}
|
||||
}
|
||||
|
||||
// 执行脚本
|
||||
updateAdminPassword();
|
||||
Reference in New Issue
Block a user