修改养殖端小程序,保险前后端和小程序

This commit is contained in:
xuqiuyun
2025-09-19 18:13:07 +08:00
parent eb3c4604d3
commit 35db747d4f
89 changed files with 16231 additions and 1500 deletions

View File

@@ -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: {

View 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
};

View 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: '服务器内部错误'
});
}
};

View 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();

View 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. 完善菜单的国际化支持

View 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;

View File

@@ -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
};

View 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;

View 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;

View 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;

View 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);
});

View 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();

View 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);
});

View File

@@ -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, {

View 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();