添加银行政府后端接口

This commit is contained in:
2025-09-25 15:53:44 +08:00
parent b17bdcc24c
commit 5b6b7e0a96
60 changed files with 5345 additions and 1920 deletions

View File

@@ -0,0 +1,104 @@
const { sequelize } = require('./config/database');
async function addTestData() {
try {
console.log('开始添加测试数据...');
// 添加supervision_tasks测试数据
await sequelize.query(`
INSERT INTO supervision_tasks (
applicationNumber, contractNumber, productName, customerName, idType, idNumber,
assetType, assetQuantity, supervisionStatus, importTime, startTime, endTime,
loanAmount, interestRate, loanTerm, supervisorName, supervisorPhone, farmAddress,
remarks, createdBy, updatedBy, createdAt, updatedAt
) VALUES
('APP001', 'CON001', '养殖贷款', '张三', 'id_card', '110101199001010001',
'cattle', 10, 'pending', NOW(), '2024-01-01', '2024-12-31',
50000.00, 0.05, 12, '李监督员', '13800138000', '北京市朝阳区农场',
'测试监管任务', 2, 2, NOW(), NOW()),
('APP002', 'CON002', '种植贷款', '李四', 'id_card', '110101199002020002',
'sheep', 20, 'supervising', NOW(), '2024-02-01', '2024-11-30',
30000.00, 0.06, 10, '王监督员', '13900139000', '北京市海淀区农场',
'测试监管任务2', 2, 2, NOW(), NOW())
`, { type: sequelize.QueryTypes.RAW });
console.log('✅ 添加supervision_tasks测试数据成功');
// 添加projects测试数据
await sequelize.query(`
INSERT INTO projects (
name, status, farmName, supervisionObject, supervisionQuantity, supervisionPeriod,
supervisionAmount, startTime, endTime, earTag, collar, host, loanOfficer,
description, createdBy, updatedBy, createdAt, updatedAt
) VALUES
('养殖项目1', 'supervision', '张三农场', 'cattle', 50, '12个月',
100000.00, '2024-01-01', '2024-12-31', 30, 20, 10, '李贷款员',
'测试养殖项目', 2, 2, NOW(), NOW()),
('种植项目1', 'completed', '李四农场', 'sheep', 100, '10个月',
80000.00, '2024-02-01', '2024-11-30', 60, 40, 20, '王贷款员',
'测试种植项目', 2, 2, NOW(), NOW())
`, { type: sequelize.QueryTypes.RAW });
console.log('✅ 添加projects测试数据成功');
// 添加installation_tasks测试数据
await sequelize.query(`
INSERT INTO installation_tasks (
applicationNumber, contractNumber, productName, customerName, idType, idNumber,
assetType, equipmentToInstall, installationStatus, taskGenerationTime, completionTime,
installationNotes, installerName, installerPhone, installationAddress,
createdBy, updatedBy, createdAt, updatedAt
) VALUES
('APP001', 'CON001', '养殖设备', '张三', 'ID_CARD', '110101199001010001',
'cattle', '耳标设备', 'pending', NOW(), NULL,
'测试安装任务', '安装员1', '13900139000', '北京市朝阳区农场',
2, 2, NOW(), NOW()),
('APP002', 'CON002', '种植设备', '李四', 'ID_CARD', '110101199002020002',
'sheep', '项圈设备', 'completed', NOW(), NOW(),
'测试安装任务2', '安装员2', '14000140000', '北京市海淀区农场',
2, 2, NOW(), NOW())
`, { type: sequelize.QueryTypes.RAW });
console.log('✅ 添加installation_tasks测试数据成功');
// 添加completed_supervisions测试数据
await sequelize.query(`
INSERT INTO completed_supervisions (
applicationNumber, contractNumber, customerName, supervisionPeriod, totalAmount,
paidAmount, remainingAmount, settlementNotes, createdBy, updatedBy, createdAt, updatedAt
) VALUES
('APP001', 'CON001', '张三', '12个月', 50000.00,
30000.00, 20000.00, '已结清部分', 2, 2, NOW(), NOW()),
('APP002', 'CON002', '李四', '10个月', 30000.00,
30000.00, 0.00, '已完全结清', 2, 2, NOW(), NOW())
`, { type: sequelize.QueryTypes.RAW });
console.log('✅ 添加completed_supervisions测试数据成功');
// 添加loan_applications测试数据
await sequelize.query(`
INSERT INTO loan_applications (
customer_name, customer_phone, customer_id_card, loan_amount, loan_term,
interest_rate, application_date, status
) VALUES
('张三', '13800138000', '110101199001010001', 50000.00, 12, 0.05, '2024-01-01', 'pending'),
('李四', '13900139000', '110101199002020002', 30000.00, 10, 0.06, '2024-01-02', 'approved')
`, { type: sequelize.QueryTypes.RAW });
console.log('✅ 添加loan_applications测试数据成功');
// 添加loan_contracts测试数据
await sequelize.query(`
INSERT INTO loan_contracts (
contract_number, customer_name, customer_phone, customer_id_card, loan_amount,
loan_term, interest_rate, contract_date, status
) VALUES
('CON001', '张三', '13800138000', '110101199001010001', 50000.00, 12, 0.05, '2024-01-01', 'active'),
('CON002', '李四', '13900139000', '110101199002020002', 30000.00, 10, 0.06, '2024-01-02', 'completed')
`, { type: sequelize.QueryTypes.RAW });
console.log('✅ 添加loan_contracts测试数据成功');
console.log('\n✅ 所有测试数据添加完成!');
} catch (error) {
console.error('❌ 添加测试数据失败:', error.message);
} finally {
await sequelize.close();
}
}
addTestData();

View File

@@ -0,0 +1,32 @@
const { sequelize } = require('./config/database');
async function checkData() {
try {
console.log('检查各表数据统计...\n');
const tables = [
'supervision_tasks',
'projects',
'installation_tasks',
'completed_supervisions',
'loan_applications',
'loan_contracts'
];
for (const table of tables) {
const result = await sequelize.query(
`SELECT COUNT(*) as count FROM ${table}`,
{ type: sequelize.QueryTypes.SELECT }
);
console.log(`${table}: ${result[0].count} 条记录`);
}
console.log('\n检查完成');
} catch (error) {
console.error('错误:', error.message);
} finally {
await sequelize.close();
}
}
checkData();

View File

@@ -3,9 +3,9 @@
"username": "root",
"password": "aiotAiot123!",
"database": "ningxia_bank",
"host": "127.0.0.1",
"host": "129.211.213.226",
"dialect": "mysql",
"port": 3306
"port": 9527
},
"test": {
"username": "root",

View File

@@ -3,10 +3,10 @@ const { Sequelize } = require('sequelize');
// 从环境变量获取数据库配置
const dialect = process.env.DB_DIALECT || 'mysql';
const config = {
logging: false,
logging: console.log, // 启用SQL日志
define: {
timestamps: true,
underscored: true,
underscored: false,
freezeTableName: true
}
};

View File

@@ -1,77 +1,73 @@
/**
* 员工控制器
* @file employeeController.js
* @description 处理员工相关的请求
*/
const { Employee, Department, Position } = require('../models');
const { Employee, User } = require('../models');
const { validationResult } = require('express-validator');
const { Op } = require('sequelize');
const bcrypt = require('bcryptjs');
/**
* 获取员工列表
* @param {Object} req 请求对象
* @param {Object} res 响应对象
*/
exports.getEmployees = async (req, res) => {
const getEmployees = async (req, res) => {
try {
const {
page = 1,
limit = 10,
search = '',
department = '',
position = '',
pageSize = 10,
searchField = 'name',
searchValue = '',
status = '',
sortBy = 'created_at',
sortOrder = 'DESC'
isLoanSpecialist = ''
} = req.query;
const offset = (page - 1) * limit;
const whereClause = {};
// 搜索条件
if (search) {
whereClause[Op.or] = [
{ name: { [Op.like]: `%${search}%` } },
{ employee_id: { [Op.like]: `%${search}%` } },
{ phone: { [Op.like]: `%${search}%` } },
{ email: { [Op.like]: `%${search}%` } }
];
// 构建查询条件
const where = {};
if (searchValue) {
if (searchField === 'name') {
where.name = { [require('sequelize').Op.like]: `%${searchValue}%` };
} else if (searchField === 'phone') {
where.phone = { [require('sequelize').Op.like]: `%${searchValue}%` };
} else if (searchField === 'employeeNumber') {
where.employeeNumber = { [require('sequelize').Op.like]: `%${searchValue}%` };
}
}
// 部门筛选
if (department) {
whereClause.department_id = department;
}
// 职位筛选
if (position) {
whereClause.position_id = position;
}
// 状态筛选
if (status) {
whereClause.status = status;
where.status = status;
}
const { count, rows: employees } = await Employee.findAndCountAll({
where: whereClause,
include: [
{
model: Department,
as: 'department',
attributes: ['id', 'name']
},
{
model: Position,
as: 'position',
attributes: ['id', 'name', 'level']
}
],
order: [[sortBy, sortOrder.toUpperCase()]],
limit: parseInt(limit),
offset: parseInt(offset)
if (isLoanSpecialist !== '') {
where.isLoanSpecialist = isLoanSpecialist === 'true';
}
// 分页参数
const offset = (parseInt(page) - 1) * parseInt(pageSize);
const limit = parseInt(pageSize);
// 查询数据
const { count, rows } = await Employee.findAndCountAll({
where,
limit,
offset,
order: [['createdAt', 'DESC']],
attributes: {
exclude: ['password'] // 不返回密码
}
});
// 格式化数据
const employees = rows.map(employee => ({
id: employee.id,
employeeNumber: employee.employeeNumber,
name: employee.name,
phone: employee.phone,
email: employee.email,
isLoanSpecialist: employee.isLoanSpecialist,
department: employee.department,
position: employee.position,
status: employee.status,
lastLogin: employee.lastLogin,
createdAt: employee.createdAt,
updatedAt: employee.updatedAt
}));
res.json({
success: true,
message: '获取员工列表成功',
@@ -79,113 +75,32 @@ exports.getEmployees = async (req, res) => {
employees,
pagination: {
current: parseInt(page),
pageSize: parseInt(limit),
pageSize: parseInt(pageSize),
total: count,
pages: Math.ceil(count / limit)
pages: Math.ceil(count / parseInt(pageSize))
}
}
});
} catch (error) {
console.error('获取员工列表错误:', error);
console.error('获取员工列表失败:', error);
res.status(500).json({
success: false,
message: '服务器内部错误',
error: error.message
});
}
};
/**
* 创建员工
* @param {Object} req 请求对象
* @param {Object} res 响应对象
*/
exports.createEmployee = async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
message: '输入数据验证失败',
errors: errors.array()
});
}
const {
name,
employee_id,
department_id,
position_id,
phone,
email,
hire_date,
salary,
status = 'active'
} = req.body;
// 检查员工编号是否已存在
const existingEmployee = await Employee.findOne({
where: { employee_id }
});
if (existingEmployee) {
return res.status(400).json({
success: false,
message: '员工编号已存在'
});
}
const employee = await Employee.create({
name,
employee_id,
department_id,
position_id,
phone,
email,
hire_date,
salary: salary * 100, // 转换为分
status
});
res.status(201).json({
success: true,
message: '创建员工成功',
data: employee
});
} catch (error) {
console.error('创建员工错误:', error);
res.status(500).json({
success: false,
message: '服务器内部错误',
error: error.message
message: '获取员工列表失败'
});
}
};
/**
* 获取员工详情
* @param {Object} req 请求对象
* @param {Object} res 响应对象
*/
exports.getEmployeeById = async (req, res) => {
const getEmployeeById = async (req, res) => {
try {
const { id } = req.params;
const employee = await Employee.findByPk(id, {
include: [
{
model: Department,
as: 'department',
attributes: ['id', 'name', 'description']
},
{
model: Position,
as: 'position',
attributes: ['id', 'name', 'level', 'description']
}
]
attributes: {
exclude: ['password'] // 不返回密码
}
});
if (!employee) {
@@ -200,43 +115,128 @@ exports.getEmployeeById = async (req, res) => {
message: '获取员工详情成功',
data: employee
});
} catch (error) {
console.error('获取员工详情错误:', error);
console.error('获取员工详情失败:', error);
res.status(500).json({
success: false,
message: '服务器内部错误',
error: error.message
message: '获取员工详情失败'
});
}
};
/**
* 更新员工
* @param {Object} req 请求对象
* @param {Object} res 响应对象
* 创建员工
*/
exports.updateEmployee = async (req, res) => {
const createEmployee = async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
message: '输入数据验证失败',
message: '请求参数错误',
errors: errors.array()
});
}
const {
employeeNumber,
name,
phone,
email,
password,
isLoanSpecialist,
department,
position
} = req.body;
// 检查员工编号是否已存在
const existingEmployee = await Employee.findOne({
where: { employeeNumber }
});
if (existingEmployee) {
return res.status(400).json({
success: false,
message: '员工编号已存在'
});
}
// 检查手机号是否已存在
const existingPhone = await Employee.findOne({
where: { phone }
});
if (existingPhone) {
return res.status(400).json({
success: false,
message: '手机号已存在'
});
}
// 创建员工
const employee = await Employee.create({
employeeNumber,
name,
phone,
email,
password: password || '123456', // 默认密码
isLoanSpecialist: isLoanSpecialist || false,
department,
position,
status: 'active'
});
res.status(201).json({
success: true,
message: '创建员工成功',
data: {
id: employee.id,
employeeNumber: employee.employeeNumber,
name: employee.name,
phone: employee.phone,
email: employee.email,
isLoanSpecialist: employee.isLoanSpecialist,
department: employee.department,
position: employee.position,
status: employee.status,
createdAt: employee.createdAt
}
});
} catch (error) {
console.error('创建员工失败:', error);
res.status(500).json({
success: false,
message: '创建员工失败'
});
}
};
/**
* 更新员工信息
*/
const updateEmployee = async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
message: '请求参数错误',
errors: errors.array()
});
}
const { id } = req.params;
const updateData = req.body;
// 如果更新薪资,转换为分
if (updateData.salary) {
updateData.salary = updateData.salary * 100;
}
const {
name,
phone,
email,
isLoanSpecialist,
department,
position,
status
} = req.body;
const employee = await Employee.findByPk(id);
if (!employee) {
return res.status(404).json({
success: false,
@@ -244,35 +244,101 @@ exports.updateEmployee = async (req, res) => {
});
}
await employee.update(updateData);
// 检查手机号是否被其他员工使用
if (phone && phone !== employee.phone) {
const existingPhone = await Employee.findOne({
where: {
phone,
id: { [require('sequelize').Op.ne]: id }
}
});
if (existingPhone) {
return res.status(400).json({
success: false,
message: '手机号已被其他员工使用'
});
}
}
// 更新员工信息
await employee.update({
name,
phone,
email,
isLoanSpecialist,
department,
position,
status
});
res.json({
success: true,
message: '更新员工成功',
data: employee
message: '更新员工信息成功',
data: {
id: employee.id,
employeeNumber: employee.employeeNumber,
name: employee.name,
phone: employee.phone,
email: employee.email,
isLoanSpecialist: employee.isLoanSpecialist,
department: employee.department,
position: employee.position,
status: employee.status,
updatedAt: employee.updatedAt
}
});
} catch (error) {
console.error('更新员工错误:', error);
console.error('更新员工信息失败:', error);
res.status(500).json({
success: false,
message: '服务器内部错误',
error: error.message
message: '更新员工信息失败'
});
}
};
/**
* 重设密码
*/
const resetPassword = async (req, res) => {
try {
const { id } = req.params;
const { newPassword } = req.body;
const employee = await Employee.findByPk(id);
if (!employee) {
return res.status(404).json({
success: false,
message: '员工不存在'
});
}
// 更新密码
await employee.update({
password: newPassword || '123456' // 默认密码
});
res.json({
success: true,
message: '重设密码成功'
});
} catch (error) {
console.error('重设密码失败:', error);
res.status(500).json({
success: false,
message: '重设密码失败'
});
}
};
/**
* 删除员工
* @param {Object} req 请求对象
* @param {Object} res 响应对象
*/
exports.deleteEmployee = async (req, res) => {
const deleteEmployee = async (req, res) => {
try {
const { id } = req.params;
const employee = await Employee.findByPk(id);
if (!employee) {
return res.status(404).json({
success: false,
@@ -280,87 +346,105 @@ exports.deleteEmployee = async (req, res) => {
});
}
// 软删除
await employee.destroy();
res.json({
success: true,
message: '删除员工成功'
});
} catch (error) {
console.error('删除员工错误:', error);
console.error('删除员工失败:', error);
res.status(500).json({
success: false,
message: '服务器内部错误',
error: error.message
message: '删除员工失败'
});
}
};
/**
* 获取员工统计
* @param {Object} req 请求对象
* @param {Object} res 响应对象
* 批量更新员工状态
*/
exports.getEmployeeStats = async (req, res) => {
const batchUpdateStatus = async (req, res) => {
try {
const { ids, status } = req.body;
if (!ids || !Array.isArray(ids) || ids.length === 0) {
return res.status(400).json({
success: false,
message: '请选择要更新的员工'
});
}
if (!status) {
return res.status(400).json({
success: false,
message: '请选择要更新的状态'
});
}
await Employee.update(
{
status
},
{
where: {
id: { [require('sequelize').Op.in]: ids }
}
}
);
res.json({
success: true,
message: '批量更新状态成功'
});
} catch (error) {
console.error('批量更新状态失败:', error);
res.status(500).json({
success: false,
message: '批量更新状态失败'
});
}
};
/**
* 获取员工统计信息
*/
const getEmployeeStats = async (req, res) => {
try {
const totalEmployees = await Employee.count();
const activeEmployees = await Employee.count({ where: { status: 'active' } });
const inactiveEmployees = await Employee.count({ where: { status: 'inactive' } });
const departmentStats = await Employee.findAll({
attributes: [
'department_id',
[Employee.sequelize.fn('COUNT', Employee.sequelize.col('id')), 'count']
],
include: [{
model: Department,
as: 'department',
attributes: ['name']
}],
group: ['department_id', 'department.id'],
raw: false
});
const positionStats = await Employee.findAll({
attributes: [
'position_id',
[Employee.sequelize.fn('COUNT', Employee.sequelize.col('id')), 'count']
],
include: [{
model: Position,
as: 'position',
attributes: ['name', 'level']
}],
group: ['position_id', 'position.id'],
raw: false
});
const lockedEmployees = await Employee.count({ where: { status: 'locked' } });
const loanSpecialists = await Employee.count({ where: { isLoanSpecialist: true } });
res.json({
success: true,
message: '获取员工统计成功',
data: {
total: totalEmployees,
active: activeEmployees,
inactive: inactiveEmployees,
departmentStats: departmentStats.map(item => ({
department: item.department.name,
count: parseInt(item.dataValues.count)
})),
positionStats: positionStats.map(item => ({
position: item.position.name,
level: item.position.level,
count: parseInt(item.dataValues.count)
}))
totalEmployees,
activeEmployees,
inactiveEmployees,
lockedEmployees,
loanSpecialists
}
});
} catch (error) {
console.error('获取员工统计错误:', error);
console.error('获取员工统计失败:', error);
res.status(500).json({
success: false,
message: '服务器内部错误',
error: error.message
message: '获取员工统计失败'
});
}
};
module.exports = {
getEmployees,
getEmployeeById,
createEmployee,
updateEmployee,
resetPassword,
deleteEmployee,
batchUpdateStatus,
getEmployeeStats
};

View File

@@ -29,15 +29,12 @@ const getApplications = async (req, res) => {
// 搜索条件
if (searchValue) {
if (searchField === 'applicationNumber') {
where.applicationNumber = { [Op.like]: `%${searchValue}%` };
} else if (searchField === 'customerName') {
where[Op.or] = [
{ borrowerName: { [Op.like]: `%${searchValue}%` } },
{ farmerName: { [Op.like]: `%${searchValue}%` } }
];
} else if (searchField === 'productName') {
where.productName = { [Op.like]: `%${searchValue}%` };
if (searchField === 'customerName') {
where.customer_name = { [Op.like]: `%${searchValue}%` };
} else if (searchField === 'customerPhone') {
where.customer_phone = { [Op.like]: `%${searchValue}%` };
} else if (searchField === 'customerIdCard') {
where.customer_id_card = { [Op.like]: `%${searchValue}%` };
}
}
@@ -56,35 +53,6 @@ const getApplications = async (req, res) => {
// 查询数据
const { count, rows } = await LoanApplication.findAndCountAll({
where,
include: [
{
model: User,
as: 'applicant',
attributes: ['id', 'username', 'real_name']
},
{
model: User,
as: 'approver',
attributes: ['id', 'username', 'real_name']
},
{
model: User,
as: 'rejector',
attributes: ['id', 'username', 'real_name']
},
{
model: AuditRecord,
as: 'auditRecords',
include: [
{
model: User,
as: 'auditorUser',
attributes: ['id', 'username', 'real_name']
}
],
order: [['auditTime', 'DESC']]
}
],
order,
offset,
limit
@@ -93,34 +61,16 @@ const getApplications = async (req, res) => {
// 格式化数据
const applications = rows.map(app => ({
id: app.id,
applicationNumber: app.applicationNumber,
productName: app.productName,
farmerName: app.farmerName,
borrowerName: app.borrowerName,
borrowerIdNumber: app.borrowerIdNumber,
assetType: app.assetType,
applicationQuantity: app.applicationQuantity,
amount: parseFloat(app.amount),
customer_name: app.customer_name,
customer_phone: app.customer_phone,
customer_id_card: app.customer_id_card,
loan_amount: parseFloat(app.loan_amount),
loan_term: app.loan_term,
interest_rate: parseFloat(app.interest_rate),
application_date: app.application_date,
status: app.status,
type: app.type,
term: app.term,
interestRate: parseFloat(app.interestRate),
phone: app.phone,
purpose: app.purpose,
remark: app.remark,
applicationTime: app.applicationTime,
approvedTime: app.approvedTime,
rejectedTime: app.rejectedTime,
auditRecords: app.auditRecords.map(record => ({
id: record.id,
action: record.action,
auditor: record.auditorUser?.real_name || record.auditor,
auditorId: record.auditorId,
comment: record.comment,
time: record.auditTime,
previousStatus: record.previousStatus,
newStatus: record.newStatus
}))
created_at: app.created_at,
updated_at: app.updated_at
}));
res.json({

View File

@@ -30,15 +30,13 @@ const getContracts = async (req, res) => {
// 搜索条件
if (searchValue) {
if (searchField === 'contractNumber') {
where.contractNumber = { [Op.like]: `%${searchValue}%` };
} else if (searchField === 'applicationNumber') {
where.applicationNumber = { [Op.like]: `%${searchValue}%` };
} else if (searchField === 'borrowerName') {
where.borrowerName = { [Op.like]: `%${searchValue}%` };
} else if (searchField === 'farmerName') {
where.farmerName = { [Op.like]: `%${searchValue}%` };
} else if (searchField === 'productName') {
where.productName = { [Op.like]: `%${searchValue}%` };
where.contract_number = { [Op.like]: `%${searchValue}%` };
} else if (searchField === 'customerName') {
where.customer_name = { [Op.like]: `%${searchValue}%` };
} else if (searchField === 'customerPhone') {
where.customer_phone = { [Op.like]: `%${searchValue}%` };
} else if (searchField === 'customerIdCard') {
where.customer_id_card = { [Op.like]: `%${searchValue}%` };
}
}
@@ -57,18 +55,6 @@ const getContracts = async (req, res) => {
// 查询数据
const { count, rows } = await LoanContract.findAndCountAll({
where,
include: [
{
model: User,
as: 'creator',
attributes: ['id', 'username', 'real_name']
},
{
model: User,
as: 'updater',
attributes: ['id', 'username', 'real_name']
}
],
order,
offset,
limit
@@ -77,31 +63,17 @@ const getContracts = async (req, res) => {
// 格式化数据
const contracts = rows.map(contract => ({
id: contract.id,
contractNumber: contract.contractNumber,
applicationNumber: contract.applicationNumber,
productName: contract.productName,
farmerName: contract.farmerName,
borrowerName: contract.borrowerName,
borrowerIdNumber: contract.borrowerIdNumber,
assetType: contract.assetType,
applicationQuantity: contract.applicationQuantity,
amount: parseFloat(contract.amount),
paidAmount: parseFloat(contract.paidAmount),
contract_number: contract.contract_number,
customer_name: contract.customer_name,
customer_phone: contract.customer_phone,
customer_id_card: contract.customer_id_card,
loan_amount: parseFloat(contract.loan_amount),
loan_term: contract.loan_term,
interest_rate: parseFloat(contract.interest_rate),
contract_date: contract.contract_date,
status: contract.status,
type: contract.type,
term: contract.term,
interestRate: parseFloat(contract.interestRate),
phone: contract.phone,
purpose: contract.purpose,
remark: contract.remark,
contractTime: contract.contractTime,
disbursementTime: contract.disbursementTime,
maturityTime: contract.maturityTime,
completedTime: contract.completedTime,
remainingAmount: parseFloat(contract.amount - contract.paidAmount),
repaymentProgress: contract.getRepaymentProgress(),
creator: contract.creator,
updater: contract.updater
created_at: contract.created_at,
updated_at: contract.updated_at
}));
res.json({

View File

@@ -1,5 +1,6 @@
const { LoanProduct, User } = require('../models');
const { Op } = require('sequelize');
const { sequelize } = require('../config/database');
// 获取贷款商品列表
const getLoanProducts = async (req, res) => {
@@ -17,23 +18,37 @@ const getLoanProducts = async (req, res) => {
const offset = (page - 1) * limit;
const whereClause = {};
// 字段映射 - 将模型属性名映射到数据库字段名
const fieldMapping = {
'createdAt': 'created_at',
'updatedAt': 'updated_at',
'productName': 'product_name',
'loanAmount': 'loan_amount',
'loanTerm': 'loan_term',
'interestRate': 'interest_rate',
'serviceArea': 'service_area',
'servicePhone': 'service_phone',
'onSaleStatus': 'on_sale_status',
'riskLevel': 'risk_level'
};
// 搜索条件
if (search) {
whereClause[Op.or] = [
{ productName: { [Op.like]: `%${search}%` } },
{ serviceArea: { [Op.like]: `%${search}%` } },
{ servicePhone: { [Op.like]: `%${search}%` } }
{ product_name: { [Op.like]: `%${search}%` } },
{ service_area: { [Op.like]: `%${search}%` } },
{ service_phone: { [Op.like]: `%${search}%` } }
];
}
// 在售状态筛选
if (onSaleStatus !== undefined) {
whereClause.onSaleStatus = onSaleStatus === 'true';
whereClause.on_sale_status = onSaleStatus === 'true';
}
// 风险等级筛选
if (riskLevel) {
whereClause.riskLevel = riskLevel;
whereClause.risk_level = riskLevel;
}
const { count, rows } = await LoanProduct.findAndCountAll({
@@ -50,7 +65,7 @@ const getLoanProducts = async (req, res) => {
attributes: ['id', 'username', 'real_name']
}
],
order: [[sortBy, sortOrder.toUpperCase()]],
order: [[fieldMapping[sortBy] || sortBy, sortOrder.toUpperCase()]],
limit: parseInt(limit),
offset: parseInt(offset)
});

View File

@@ -28,6 +28,7 @@ async function createAdminUser() {
real_name: '系统管理员',
email: 'admin@bank.com',
phone: '13800138000',
id_card: '110101199001010001',
status: 'active',
role_id: 1
})

View File

@@ -0,0 +1,43 @@
const { sequelize } = require('./config/database');
async function createAuditRecordsTable() {
try {
console.log('创建bank_audit_records表...');
await sequelize.query(`
CREATE TABLE IF NOT EXISTS bank_audit_records (
id INT(11) NOT NULL AUTO_INCREMENT,
applicationId INT(11) NOT NULL,
action VARCHAR(50) NOT NULL,
auditor VARCHAR(100) NOT NULL,
auditorId INT(11) NOT NULL,
comment TEXT,
auditTime DATETIME NOT NULL,
previousStatus VARCHAR(50),
newStatus VARCHAR(50),
createdAt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updatedAt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_application_id (applicationId),
KEY idx_auditor_id (auditorId),
KEY idx_audit_time (auditTime)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
`, { type: sequelize.QueryTypes.RAW });
console.log('✅ bank_audit_records表创建成功');
// 标记迁移为完成
await sequelize.query(`
INSERT IGNORE INTO SequelizeMeta (name) VALUES ('20241220000008-create-audit-records.js')
`, { type: sequelize.QueryTypes.RAW });
console.log('✅ 迁移记录已标记为完成');
} catch (error) {
console.error('❌ 创建表失败:', error.message);
} finally {
await sequelize.close();
}
}
createAuditRecordsTable();

View File

@@ -0,0 +1,39 @@
-- 创建缺失的表
-- 创建贷款申请表
CREATE TABLE IF NOT EXISTS loan_applications (
id INT AUTO_INCREMENT PRIMARY KEY,
customer_name VARCHAR(100) NOT NULL COMMENT '客户姓名',
customer_phone VARCHAR(20) NOT NULL COMMENT '客户电话',
customer_id_card VARCHAR(18) NOT NULL COMMENT '客户身份证号',
loan_amount DECIMAL(15,2) NOT NULL COMMENT '贷款金额',
loan_term INT NOT NULL COMMENT '贷款期限(月)',
interest_rate DECIMAL(5,2) NOT NULL COMMENT '贷款利率(%',
application_date DATE NOT NULL COMMENT '申请日期',
status ENUM('pending', 'approved', 'rejected', 'completed') DEFAULT 'pending' COMMENT '申请状态',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 创建贷款合同表
CREATE TABLE IF NOT EXISTS loan_contracts (
id INT AUTO_INCREMENT PRIMARY KEY,
contract_number VARCHAR(50) NOT NULL UNIQUE COMMENT '合同编号',
customer_name VARCHAR(100) NOT NULL COMMENT '客户姓名',
customer_phone VARCHAR(20) NOT NULL COMMENT '客户电话',
customer_id_card VARCHAR(18) NOT NULL COMMENT '客户身份证号',
loan_amount DECIMAL(15,2) NOT NULL COMMENT '贷款金额',
loan_term INT NOT NULL COMMENT '贷款期限(月)',
interest_rate DECIMAL(5,2) NOT NULL COMMENT '贷款利率(%',
contract_date DATE NOT NULL COMMENT '合同签订日期',
status ENUM('active', 'completed', 'defaulted', 'cancelled') DEFAULT 'active' COMMENT '合同状态',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 标记迁移为已完成
INSERT IGNORE INTO SequelizeMeta (name) VALUES ('20241220000006-create-loan-products.js');
INSERT IGNORE INTO SequelizeMeta (name) VALUES ('20241220000007-create-loan-applications.js');
INSERT IGNORE INTO SequelizeMeta (name) VALUES ('20241220000008-create-audit-records.js');
INSERT IGNORE INTO SequelizeMeta (name) VALUES ('20241220000009-create-loan-contracts.js');
INSERT IGNORE INTO SequelizeMeta (name) VALUES ('20241220000010-create-employees.js');

View File

@@ -0,0 +1,68 @@
const { sequelize } = require('./config/database');
async function createMissingTables() {
try {
console.log('开始创建缺失的表...');
// 创建loan_applications表
await sequelize.query(`
CREATE TABLE IF NOT EXISTS loan_applications (
id INT AUTO_INCREMENT PRIMARY KEY,
customer_name VARCHAR(100) NOT NULL COMMENT '客户姓名',
customer_phone VARCHAR(20) NOT NULL COMMENT '客户电话',
customer_id_card VARCHAR(18) NOT NULL COMMENT '客户身份证号',
loan_amount DECIMAL(15,2) NOT NULL COMMENT '贷款金额',
loan_term INT NOT NULL COMMENT '贷款期限(月)',
interest_rate DECIMAL(5,2) NOT NULL COMMENT '贷款利率(%',
application_date DATE NOT NULL COMMENT '申请日期',
status ENUM('pending', 'approved', 'rejected', 'completed') DEFAULT 'pending' COMMENT '申请状态',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
`, { type: sequelize.QueryTypes.RAW });
console.log('✅ 创建loan_applications表成功');
// 创建loan_contracts表
await sequelize.query(`
CREATE TABLE IF NOT EXISTS loan_contracts (
id INT AUTO_INCREMENT PRIMARY KEY,
contract_number VARCHAR(50) NOT NULL UNIQUE COMMENT '合同编号',
customer_name VARCHAR(100) NOT NULL COMMENT '客户姓名',
customer_phone VARCHAR(20) NOT NULL COMMENT '客户电话',
customer_id_card VARCHAR(18) NOT NULL COMMENT '客户身份证号',
loan_amount DECIMAL(15,2) NOT NULL COMMENT '贷款金额',
loan_term INT NOT NULL COMMENT '贷款期限(月)',
interest_rate DECIMAL(5,2) NOT NULL COMMENT '贷款利率(%',
contract_date DATE NOT NULL COMMENT '合同签订日期',
status ENUM('active', 'completed', 'defaulted', 'cancelled') DEFAULT 'active' COMMENT '合同状态',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
`, { type: sequelize.QueryTypes.RAW });
console.log('✅ 创建loan_contracts表成功');
// 标记迁移为已完成
await sequelize.query(`
INSERT IGNORE INTO SequelizeMeta (name) VALUES
('20241220000006-create-loan-products.js'),
('20241220000007-create-loan-applications.js'),
('20241220000008-create-audit-records.js'),
('20241220000009-create-loan-contracts.js'),
('20241220000010-create-employees.js')
`, { type: sequelize.QueryTypes.RAW });
console.log('✅ 标记迁移为已完成');
// 显示所有表
const tables = await sequelize.query('SHOW TABLES', { type: sequelize.QueryTypes.SELECT });
console.log('\n当前数据库表:');
tables.forEach(table => console.log('-', Object.values(table)[0]));
console.log('\n✅ 所有表创建完成!');
} catch (error) {
console.error('❌ 创建表失败:', error.message);
} finally {
await sequelize.close();
}
}
createMissingTables();

View File

@@ -3,11 +3,11 @@ PORT=5351
NODE_ENV=development
# 数据库配置
DB_HOST=localhost
DB_PORT=3306
DB_NAME=bank_management
DB_HOST=129.211.213.226
DB_PORT=9527
DB_NAME=ningxia_bank
DB_USER=root
DB_PASSWORD=your_password
DB_PASSWORD=aiotAiot123!
DB_DIALECT=mysql
# JWT配置

View File

@@ -0,0 +1,52 @@
const { sequelize } = require('./config/database');
async function fixSupervisionTasks() {
try {
console.log('开始修复supervision_tasks表...');
// 检查并添加created_by字段
const createdByExists = await sequelize.query(
'SHOW COLUMNS FROM supervision_tasks LIKE "created_by"',
{ type: sequelize.QueryTypes.SELECT }
);
if (createdByExists.length === 0) {
await sequelize.query(
'ALTER TABLE supervision_tasks ADD COLUMN created_by INT(11) AFTER remarks',
{ type: sequelize.QueryTypes.RAW }
);
console.log('✅ 添加created_by字段成功');
} else {
console.log('✅ created_by字段已存在');
}
// 检查并添加updated_by字段
const updatedByExists = await sequelize.query(
'SHOW COLUMNS FROM supervision_tasks LIKE "updated_by"',
{ type: sequelize.QueryTypes.SELECT }
);
if (updatedByExists.length === 0) {
await sequelize.query(
'ALTER TABLE supervision_tasks ADD COLUMN updated_by INT(11) AFTER created_by',
{ type: sequelize.QueryTypes.RAW }
);
console.log('✅ 添加updated_by字段成功');
} else {
console.log('✅ updated_by字段已存在');
}
// 显示表结构
const columns = await sequelize.query('DESCRIBE supervision_tasks', { type: sequelize.QueryTypes.SELECT });
console.log('\nsupervision_tasks表结构:');
columns.forEach(col => console.log('-', col.Field, col.Type));
console.log('\n✅ supervision_tasks表修复完成');
} catch (error) {
console.error('❌ 修复失败:', error.message);
} finally {
await sequelize.close();
}
}
fixSupervisionTasks();

View File

@@ -0,0 +1,75 @@
const { sequelize } = require('./config/database');
async function fixTestData() {
try {
console.log('开始修复测试数据...');
// 添加installation_tasks测试数据
await sequelize.query(`
INSERT INTO installation_tasks (
applicationNumber, contractNumber, productName, customerName, idType, idNumber,
assetType, equipmentToInstall, installationStatus, taskGenerationTime, completionTime,
installationNotes, installerName, installerPhone, installationAddress,
createdBy, updatedBy, createdAt, updatedAt
) VALUES
('APP001', 'CON001', '养殖设备', '张三', 'ID_CARD', '110101199001010001',
'cattle', '耳标设备', 'pending', NOW(), NULL,
'测试安装任务', '安装员1', '13900139000', '北京市朝阳区农场',
2, 2, NOW(), NOW()),
('APP002', 'CON002', '种植设备', '李四', 'ID_CARD', '110101199002020002',
'sheep', '项圈设备', 'completed', NOW(), NOW(),
'测试安装任务2', '安装员2', '14000140000', '北京市海淀区农场',
2, 2, NOW(), NOW())
`, { type: sequelize.QueryTypes.RAW });
console.log('✅ 添加installation_tasks测试数据成功');
// 添加completed_supervisions测试数据
await sequelize.query(`
INSERT INTO completed_supervisions (
applicationNumber, contractNumber, productName, customerName, idType, idNumber,
assetType, assetQuantity, totalRepaymentPeriods, settlementStatus, settlementDate,
importTime, settlementAmount, remainingAmount, settlementNotes,
createdBy, updatedBy, createdAt, updatedAt
) VALUES
('APP001', 'CON001', '养殖贷款', '张三', 'ID_CARD', '110101199001010001',
'cattle', 10, 12, 'partial', '2024-01-15',
NOW(), 30000.00, 20000.00, '已结清部分',
2, 2, NOW(), NOW()),
('APP002', 'CON002', '种植贷款', '李四', 'ID_CARD', '110101199002020002',
'sheep', 20, 10, 'settled', '2024-01-20',
NOW(), 30000.00, 0.00, '已完全结清',
2, 2, NOW(), NOW())
`, { type: sequelize.QueryTypes.RAW });
console.log('✅ 添加completed_supervisions测试数据成功');
// 添加loan_applications测试数据
await sequelize.query(`
INSERT INTO loan_applications (
customer_name, customer_phone, customer_id_card, loan_amount, loan_term,
interest_rate, application_date, status
) VALUES
('张三', '13800138000', '110101199001010001', 50000.00, 12, 0.05, '2024-01-01', 'pending'),
('李四', '13900139000', '110101199002020002', 30000.00, 10, 0.06, '2024-01-02', 'approved')
`, { type: sequelize.QueryTypes.RAW });
console.log('✅ 添加loan_applications测试数据成功');
// 添加loan_contracts测试数据
await sequelize.query(`
INSERT INTO loan_contracts (
contract_number, customer_name, customer_phone, customer_id_card, loan_amount,
loan_term, interest_rate, contract_date, status
) VALUES
('CON001', '张三', '13800138000', '110101199001010001', 50000.00, 12, 0.05, '2024-01-01', 'active'),
('CON002', '李四', '13900139000', '110101199002020002', 30000.00, 10, 0.06, '2024-01-02', 'completed')
`, { type: sequelize.QueryTypes.RAW });
console.log('✅ 添加loan_contracts测试数据成功');
console.log('\n✅ 所有测试数据修复完成!');
} catch (error) {
console.error('❌ 修复测试数据失败:', error.message);
} finally {
await sequelize.close();
}
}
fixTestData();

View File

@@ -55,7 +55,7 @@ module.exports = {
allowNull: false,
comment: '创建人ID',
references: {
model: 'users',
model: 'bank_users',
key: 'id'
},
onUpdate: 'CASCADE',

View File

@@ -0,0 +1,124 @@
'use strict';
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.createTable('bank_employees', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
employeeNumber: {
type: Sequelize.STRING(20),
allowNull: false,
unique: true,
comment: '员工编号'
},
name: {
type: Sequelize.STRING(50),
allowNull: false,
comment: '员工姓名'
},
phone: {
type: Sequelize.STRING(20),
allowNull: false,
comment: '联系电话'
},
email: {
type: Sequelize.STRING(100),
allowNull: true,
comment: '邮箱'
},
password: {
type: Sequelize.STRING(255),
allowNull: false,
comment: '密码'
},
isLoanSpecialist: {
type: Sequelize.BOOLEAN,
defaultValue: false,
comment: '是否为贷款专员'
},
department: {
type: Sequelize.STRING(50),
allowNull: true,
comment: '部门'
},
position: {
type: Sequelize.STRING(50),
allowNull: true,
comment: '职位'
},
status: {
type: Sequelize.ENUM('active', 'inactive', 'locked'),
defaultValue: 'active',
comment: '账号状态'
},
lastLogin: {
type: Sequelize.DATE,
allowNull: true,
comment: '最后登录时间'
},
loginAttempts: {
type: Sequelize.INTEGER,
defaultValue: 0,
comment: '登录尝试次数'
},
lockedUntil: {
type: Sequelize.DATE,
allowNull: true,
comment: '锁定到期时间'
},
createdBy: {
type: Sequelize.INTEGER,
allowNull: true,
comment: '创建人ID'
},
updatedBy: {
type: Sequelize.INTEGER,
allowNull: true,
comment: '更新人ID'
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
},
deletedAt: {
type: Sequelize.DATE,
allowNull: true
}
});
// 添加索引
await queryInterface.addIndex('bank_employees', ['employeeNumber'], {
unique: true,
name: 'idx_employees_employee_number'
});
await queryInterface.addIndex('bank_employees', ['phone'], {
unique: true,
name: 'idx_employees_phone'
});
await queryInterface.addIndex('bank_employees', ['status'], {
name: 'idx_employees_status'
});
await queryInterface.addIndex('bank_employees', ['isLoanSpecialist'], {
name: 'idx_employees_loan_specialist'
});
await queryInterface.addIndex('bank_employees', ['deletedAt'], {
name: 'idx_employees_deleted_at'
});
},
async down(queryInterface, Sequelize) {
await queryInterface.dropTable('bank_employees');
}
};

View File

@@ -101,9 +101,27 @@ const CompletedSupervision = sequelize.define('CompletedSupervision', {
}, {
tableName: 'completed_supervisions',
timestamps: true,
underscored: false,
createdAt: 'createdAt',
updatedAt: 'updatedAt',
comment: '监管任务已结项表'
});
// 定义关联关系
CompletedSupervision.associate = (models) => {
// 监管任务已结项与用户关联(创建人)
CompletedSupervision.belongsTo(models.User, {
foreignKey: { name: 'createdBy', field: 'createdBy' },
targetKey: 'id',
as: 'creator'
});
// 监管任务已结项与用户关联(更新人)
CompletedSupervision.belongsTo(models.User, {
foreignKey: { name: 'updatedBy', field: 'updatedBy' },
targetKey: 'id',
as: 'updater'
});
};
module.exports = CompletedSupervision;

View File

@@ -1,82 +1,141 @@
/**
* 员工模型
* @file Employee.js
* @description 员工数据模型
*/
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const bcrypt = require('bcryptjs');
const Employee = sequelize.define('Employee', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING(50),
allowNull: false,
comment: '员工姓名'
},
employee_id: {
type: DataTypes.STRING(20),
allowNull: false,
unique: true,
comment: '员工编号'
},
department_id: {
type: DataTypes.INTEGER,
allowNull: false,
comment: '部门ID'
},
position_id: {
type: DataTypes.INTEGER,
allowNull: false,
comment: '职位ID'
},
phone: {
type: DataTypes.STRING(20),
allowNull: true,
comment: '联系电话'
},
email: {
type: DataTypes.STRING(100),
allowNull: true,
comment: '邮箱地址'
},
hire_date: {
type: DataTypes.DATEONLY,
allowNull: false,
comment: '入职日期'
},
salary: {
type: DataTypes.BIGINT,
allowNull: false,
defaultValue: 0,
comment: '薪资(分)'
},
status: {
type: DataTypes.ENUM('active', 'inactive', 'resigned'),
allowNull: false,
defaultValue: 'active',
comment: '员工状态:在职、离职、已辞职'
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
updated_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
}
}, {
sequelize,
tableName: 'bank_employees',
modelName: 'Employee',
timestamps: true,
createdAt: 'created_at',
updatedAt: 'updated_at'
});
module.exports = (sequelize) => {
const Employee = sequelize.define('Employee', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
employeeNumber: {
type: DataTypes.STRING(20),
allowNull: false,
unique: true,
comment: '员工编号'
},
name: {
type: DataTypes.STRING(50),
allowNull: false,
comment: '员工姓名'
},
phone: {
type: DataTypes.STRING(20),
allowNull: false,
comment: '联系电话'
},
email: {
type: DataTypes.STRING(100),
allowNull: true,
comment: '邮箱'
},
password: {
type: DataTypes.STRING(255),
allowNull: false,
comment: '密码'
},
isLoanSpecialist: {
type: DataTypes.BOOLEAN,
defaultValue: false,
comment: '是否为贷款专员'
},
department: {
type: DataTypes.STRING(50),
allowNull: true,
comment: '部门'
},
position: {
type: DataTypes.STRING(50),
allowNull: true,
comment: '职位'
},
status: {
type: DataTypes.ENUM('active', 'inactive', 'locked'),
defaultValue: 'active',
comment: '账号状态'
},
lastLogin: {
type: DataTypes.DATE,
allowNull: true,
comment: '最后登录时间'
},
loginAttempts: {
type: DataTypes.INTEGER,
defaultValue: 0,
comment: '登录尝试次数'
},
lockedUntil: {
type: DataTypes.DATE,
allowNull: true,
comment: '锁定到期时间'
},
createdBy: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '创建人ID'
},
updatedBy: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '更新人ID'
}
}, {
tableName: 'bank_employees',
timestamps: true,
paranoid: true,
comment: '银行员工表',
hooks: {
beforeSave: async (employee) => {
if (employee.changed('password')) {
const salt = await bcrypt.genSalt(10);
employee.password = await bcrypt.hash(employee.password, salt);
}
}
}
});
module.exports = Employee;
// 实例方法:验证密码
Employee.prototype.validPassword = async function(password) {
try {
return await bcrypt.compare(password, this.password);
} catch (error) {
console.error('密码验证错误:', error);
return false;
}
};
// 实例方法:检查账号是否被锁定
Employee.prototype.isLocked = function() {
return !!(this.lockedUntil && this.lockedUntil > Date.now());
};
// 实例方法:增加登录尝试次数
Employee.prototype.incLoginAttempts = async function() {
// 如果已经锁定且锁定时间已过,则重置
if (this.lockedUntil && this.lockedUntil < Date.now()) {
return this.update({
loginAttempts: 1,
lockedUntil: null
});
}
const updates = { loginAttempts: this.loginAttempts + 1 };
// 如果达到最大尝试次数,则锁定账号
if (this.loginAttempts + 1 >= 5 && !this.isLocked()) {
updates.lockedUntil = new Date(Date.now() + 30 * 60 * 1000); // 锁定30分钟
}
return this.update(updates);
};
// 实例方法:重置登录尝试次数
Employee.prototype.resetLoginAttempts = async function() {
return this.update({
loginAttempts: 0,
lockedUntil: null
});
};
return Employee;
};

View File

@@ -99,9 +99,27 @@ const InstallationTask = sequelize.define('InstallationTask', {
}, {
tableName: 'installation_tasks',
timestamps: true,
underscored: false,
createdAt: 'createdAt',
updatedAt: 'updatedAt',
comment: '待安装任务表'
});
// 定义关联关系
InstallationTask.associate = (models) => {
// 待安装任务与用户关联(创建人)
InstallationTask.belongsTo(models.User, {
foreignKey: { name: 'createdBy', field: 'createdBy' },
targetKey: 'id',
as: 'creator'
});
// 待安装任务与用户关联(更新人)
InstallationTask.belongsTo(models.User, {
foreignKey: { name: 'updatedBy', field: 'updatedBy' },
targetKey: 'id',
as: 'updater'
});
};
module.exports = InstallationTask;

View File

@@ -13,34 +13,20 @@ class LoanApplication extends BaseModel {
*/
getStatusText() {
const statusMap = {
pending_review: '待审',
verification_pending: '核验待放款',
pending_binding: '待绑定',
pending: '待审',
approved: '已通过',
rejected: '已拒绝'
rejected: '已拒绝',
completed: '已完成'
};
return statusMap[this.status] || this.status;
}
/**
* 获取申请类型文本
* @returns {String} 类型文本
*/
getTypeText() {
const typeMap = {
personal: '个人贷款',
business: '企业贷款',
mortgage: '抵押贷款'
};
return typeMap[this.type] || this.type;
}
/**
* 格式化申请金额
* @returns {String} 格式化后的金额
*/
getFormattedAmount() {
return `${this.amount.toFixed(2)}`;
return `${this.loan_amount.toFixed(2)}`;
}
}
@@ -51,144 +37,55 @@ LoanApplication.init({
primaryKey: true,
autoIncrement: true
},
applicationNumber: {
type: DataTypes.STRING(50),
allowNull: false,
unique: true,
comment: '申请单号'
},
productName: {
type: DataTypes.STRING(200),
allowNull: false,
comment: '贷款产品名称'
},
farmerName: {
customer_name: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '申请养殖户姓名'
comment: '户姓名'
},
borrowerName: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '贷款人姓名'
},
borrowerIdNumber: {
customer_phone: {
type: DataTypes.STRING(20),
allowNull: false,
comment: '贷款人身份证号'
comment: '客户电话'
},
assetType: {
type: DataTypes.STRING(50),
customer_id_card: {
type: DataTypes.STRING(18),
allowNull: false,
comment: '生资种类'
comment: '客户身份证号'
},
applicationQuantity: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '申请数量'
},
amount: {
loan_amount: {
type: DataTypes.DECIMAL(15, 2),
allowNull: false,
comment: '申请额度'
comment: '贷款金额'
},
status: {
type: DataTypes.ENUM(
'pending_review',
'verification_pending',
'pending_binding',
'approved',
'rejected'
),
allowNull: false,
defaultValue: 'pending_review',
comment: '申请状态'
},
type: {
type: DataTypes.ENUM('personal', 'business', 'mortgage'),
allowNull: false,
defaultValue: 'personal',
comment: '申请类型'
},
term: {
loan_term: {
type: DataTypes.INTEGER,
allowNull: false,
comment: '申请期限(月)'
comment: '贷款期限(月)'
},
interestRate: {
interest_rate: {
type: DataTypes.DECIMAL(5, 2),
allowNull: false,
comment: '预计利率'
comment: '贷款利率(%'
},
phone: {
type: DataTypes.STRING(20),
application_date: {
type: DataTypes.DATEONLY,
allowNull: false,
comment: '联系电话'
comment: '申请日期'
},
purpose: {
type: DataTypes.TEXT,
status: {
type: DataTypes.ENUM('pending', 'approved', 'rejected', 'completed'),
allowNull: true,
comment: '申请用途'
},
remark: {
type: DataTypes.TEXT,
allowNull: true,
comment: '备注'
},
applicationTime: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
comment: '申请时间'
},
approvedTime: {
type: DataTypes.DATE,
allowNull: true,
comment: '审批通过时间'
},
rejectedTime: {
type: DataTypes.DATE,
allowNull: true,
comment: '审批拒绝时间'
},
approvedBy: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '审批人ID'
},
rejectedBy: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '拒绝人ID'
},
rejectionReason: {
type: DataTypes.TEXT,
allowNull: true,
comment: '拒绝原因'
defaultValue: 'pending',
comment: '申请状态'
}
}, {
sequelize: require('../config/database').sequelize,
modelName: 'LoanApplication',
tableName: 'bank_loan_applications',
tableName: 'loan_applications',
timestamps: true,
createdAt: 'createdAt',
updatedAt: 'updatedAt',
hooks: {
beforeCreate: (application) => {
// 生成申请单号
if (!application.applicationNumber) {
const now = new Date();
const timestamp = now.getFullYear().toString() +
(now.getMonth() + 1).toString().padStart(2, '0') +
now.getDate().toString().padStart(2, '0') +
now.getHours().toString().padStart(2, '0') +
now.getMinutes().toString().padStart(2, '0') +
now.getSeconds().toString().padStart(2, '0');
const random = Math.floor(Math.random() * 1000).toString().padStart(3, '0');
application.applicationNumber = timestamp + random;
}
}
}
underscored: true,
createdAt: 'created_at',
updatedAt: 'updated_at'
});
module.exports = LoanApplication;

View File

@@ -14,7 +14,6 @@ class LoanContract extends BaseModel {
getStatusText() {
const statusMap = {
active: '已放款',
pending: '待放款',
completed: '已完成',
defaulted: '违约',
cancelled: '已取消'
@@ -22,43 +21,12 @@ class LoanContract extends BaseModel {
return statusMap[this.status] || this.status;
}
/**
* 获取合同类型文本
* @returns {String} 类型文本
*/
getTypeText() {
const typeMap = {
livestock_collateral: '畜禽活体抵押',
farmer_loan: '惠农贷',
business_loan: '商业贷款',
personal_loan: '个人贷款'
};
return typeMap[this.type] || this.type;
}
/**
* 格式化合同金额
* @returns {String} 格式化后的金额
*/
getFormattedAmount() {
return `${this.amount.toFixed(2)}`;
}
/**
* 计算剩余还款金额
* @returns {Number} 剩余金额
*/
getRemainingAmount() {
return this.amount - (this.paidAmount || 0);
}
/**
* 计算还款进度百分比
* @returns {Number} 进度百分比
*/
getRepaymentProgress() {
if (this.amount <= 0) return 0;
return Math.round(((this.paidAmount || 0) / this.amount) * 100);
return `${this.loan_amount.toFixed(2)}`;
}
}
@@ -69,167 +37,63 @@ LoanContract.init({
primaryKey: true,
autoIncrement: true
},
contractNumber: {
contract_number: {
type: DataTypes.STRING(50),
allowNull: false,
unique: true,
comment: '合同编号'
},
applicationNumber: {
type: DataTypes.STRING(50),
allowNull: false,
comment: '申请单号'
},
productName: {
type: DataTypes.STRING(200),
allowNull: false,
comment: '贷款产品名称'
},
farmerName: {
customer_name: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '申请养殖户姓名'
comment: '户姓名'
},
borrowerName: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '贷款人姓名'
},
borrowerIdNumber: {
customer_phone: {
type: DataTypes.STRING(20),
allowNull: false,
comment: '贷款人身份证号'
comment: '客户电话'
},
assetType: {
type: DataTypes.STRING(50),
customer_id_card: {
type: DataTypes.STRING(18),
allowNull: false,
comment: '生资种类'
comment: '客户身份证号'
},
applicationQuantity: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '申请数量'
},
amount: {
loan_amount: {
type: DataTypes.DECIMAL(15, 2),
allowNull: false,
comment: '合同金额'
comment: '贷款金额'
},
paidAmount: {
type: DataTypes.DECIMAL(15, 2),
allowNull: false,
defaultValue: 0,
comment: '已还款金额'
},
status: {
type: DataTypes.ENUM(
'active',
'pending',
'completed',
'defaulted',
'cancelled'
),
allowNull: false,
defaultValue: 'pending',
comment: '合同状态'
},
type: {
type: DataTypes.ENUM(
'livestock_collateral',
'farmer_loan',
'business_loan',
'personal_loan'
),
allowNull: false,
comment: '合同类型'
},
term: {
loan_term: {
type: DataTypes.INTEGER,
allowNull: false,
comment: '合同期限(月)'
comment: '贷款期限(月)'
},
interestRate: {
interest_rate: {
type: DataTypes.DECIMAL(5, 2),
allowNull: false,
comment: '利率'
comment: '贷款利率(%'
},
phone: {
type: DataTypes.STRING(20),
contract_date: {
type: DataTypes.DATEONLY,
allowNull: false,
comment: '联系电话'
comment: '合同签订日期'
},
purpose: {
type: DataTypes.TEXT,
status: {
type: DataTypes.ENUM('active', 'completed', 'defaulted', 'cancelled'),
allowNull: true,
comment: '贷款用途'
},
remark: {
type: DataTypes.TEXT,
allowNull: true,
comment: '备注'
},
contractTime: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
comment: '合同签订时间'
},
disbursementTime: {
type: DataTypes.DATE,
allowNull: true,
comment: '放款时间'
},
maturityTime: {
type: DataTypes.DATE,
allowNull: true,
comment: '到期时间'
},
completedTime: {
type: DataTypes.DATE,
allowNull: true,
comment: '完成时间'
},
createdBy: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '创建人ID',
references: {
model: 'bank_users',
key: 'id'
}
},
updatedBy: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '更新人ID',
references: {
model: 'bank_users',
key: 'id'
}
defaultValue: 'active',
comment: '合同状态'
}
}, {
sequelize: require('../config/database').sequelize,
modelName: 'LoanContract',
tableName: 'bank_loan_contracts',
tableName: 'loan_contracts',
timestamps: true,
createdAt: 'createdAt',
updatedAt: 'updatedAt',
hooks: {
beforeCreate: (contract) => {
// 生成合同编号
if (!contract.contractNumber) {
const now = new Date();
const timestamp = now.getFullYear().toString() +
(now.getMonth() + 1).toString().padStart(2, '0') +
now.getDate().toString().padStart(2, '0') +
now.getHours().toString().padStart(2, '0') +
now.getMinutes().toString().padStart(2, '0') +
now.getSeconds().toString().padStart(2, '0');
const random = Math.floor(Math.random() * 1000).toString().padStart(3, '0');
contract.contractNumber = 'HT' + timestamp + random;
}
}
}
underscored: true,
createdAt: 'created_at',
updatedAt: 'updated_at',
// 移除不存在的字段
omitNull: true
});
module.exports = LoanContract;
module.exports = LoanContract;

View File

@@ -11,108 +11,127 @@ const LoanProduct = sequelize.define('LoanProduct', {
productName: {
type: DataTypes.STRING(200),
allowNull: false,
field: 'product_name',
comment: '贷款产品名称'
},
loanAmount: {
type: DataTypes.STRING(100),
allowNull: false,
field: 'loan_amount',
comment: '贷款额度'
},
loanTerm: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'loan_term',
comment: '贷款周期(月)'
},
interestRate: {
type: DataTypes.DECIMAL(5, 2),
allowNull: false,
field: 'interest_rate',
comment: '贷款利率(%'
},
serviceArea: {
type: DataTypes.STRING(200),
allowNull: false,
field: 'service_area',
comment: '服务区域'
},
servicePhone: {
type: DataTypes.STRING(20),
allowNull: false,
field: 'service_phone',
comment: '服务电话'
},
totalCustomers: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
field: 'total_customers',
comment: '服务客户总数量'
},
supervisionCustomers: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
field: 'supervision_customers',
comment: '监管中客户数量'
},
completedCustomers: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
field: 'completed_customers',
comment: '已结项客户数量'
},
onSaleStatus: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: true,
field: 'on_sale_status',
comment: '在售状态'
},
productDescription: {
type: DataTypes.TEXT,
allowNull: true,
field: 'product_description',
comment: '产品描述'
},
applicationRequirements: {
type: DataTypes.TEXT,
allowNull: true,
field: 'application_requirements',
comment: '申请条件'
},
requiredDocuments: {
type: DataTypes.TEXT,
allowNull: true,
field: 'required_documents',
comment: '所需材料'
},
approvalProcess: {
type: DataTypes.TEXT,
allowNull: true,
field: 'approval_process',
comment: '审批流程'
},
riskLevel: {
type: DataTypes.ENUM('LOW', 'MEDIUM', 'HIGH'),
allowNull: false,
defaultValue: 'MEDIUM',
field: 'risk_level',
comment: '风险等级'
},
minLoanAmount: {
type: DataTypes.DECIMAL(15, 2),
allowNull: true,
field: 'min_loan_amount',
comment: '最小贷款金额'
},
maxLoanAmount: {
type: DataTypes.DECIMAL(15, 2),
allowNull: true,
field: 'max_loan_amount',
comment: '最大贷款金额'
},
createdBy: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'created_by',
comment: '创建人ID'
},
updatedBy: {
type: DataTypes.INTEGER,
allowNull: true,
field: 'updated_by',
comment: '更新人ID'
}
}, {
tableName: 'loan_products',
timestamps: true,
createdAt: 'createdAt',
updatedAt: 'updatedAt',
createdAt: 'created_at',
updatedAt: 'updated_at',
comment: '贷款商品表'
});

View File

@@ -21,61 +21,72 @@ const Project = sequelize.define('Project', {
farmName: {
type: DataTypes.STRING(200),
allowNull: false,
field: 'farmName',
comment: '养殖场名称'
},
supervisionObject: {
type: DataTypes.STRING(50),
allowNull: false,
field: 'supervisionObject',
comment: '监管对象'
},
supervisionQuantity: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
field: 'supervisionQuantity',
comment: '监管数量'
},
supervisionPeriod: {
type: DataTypes.STRING(50),
allowNull: false,
field: 'supervisionPeriod',
comment: '监管周期'
},
supervisionAmount: {
type: DataTypes.DECIMAL(15, 2),
allowNull: false,
defaultValue: 0.00,
field: 'supervisionAmount',
comment: '监管金额'
},
startTime: {
type: DataTypes.DATEONLY,
allowNull: false,
field: 'startTime',
comment: '起始时间'
},
endTime: {
type: DataTypes.DATEONLY,
allowNull: false,
field: 'endTime',
comment: '结束时间'
},
earTag: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
field: 'earTag',
comment: '耳标数量'
},
collar: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
field: 'collar',
comment: '项圈数量'
},
host: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
field: 'host',
comment: '主机数量'
},
loanOfficer: {
type: DataTypes.STRING(100),
allowNull: true,
field: 'loanOfficer',
comment: '贷款专员'
},
description: {
@@ -86,16 +97,19 @@ const Project = sequelize.define('Project', {
createdBy: {
type: DataTypes.INTEGER,
allowNull: true,
field: 'createdBy',
comment: '创建人ID'
},
updatedBy: {
type: DataTypes.INTEGER,
allowNull: true,
field: 'updatedBy',
comment: '更新人ID'
}
}, {
tableName: 'projects',
timestamps: true,
underscored: false,
createdAt: 'createdAt',
updatedAt: 'updatedAt',
comment: '项目清单表'
@@ -105,13 +119,15 @@ const Project = sequelize.define('Project', {
Project.associate = (models) => {
// 项目与用户关联(创建人)
Project.belongsTo(models.User, {
foreignKey: 'createdBy',
foreignKey: { name: 'createdBy', field: 'createdBy' },
targetKey: 'id',
as: 'creator'
});
// 项目与用户关联(更新人)
Project.belongsTo(models.User, {
foreignKey: 'updatedBy',
foreignKey: { name: 'updatedBy', field: 'updatedBy' },
targetKey: 'id',
as: 'updater'
});
};

View File

@@ -19,7 +19,8 @@ SupervisionTask.init({
type: DataTypes.STRING(50),
allowNull: false,
unique: true,
comment: '申请单号'
comment: '申请单号',
field: 'applicationNumber'
},
contractNumber: {
type: DataTypes.STRING(50),
@@ -145,7 +146,7 @@ SupervisionTask.init({
modelName: 'SupervisionTask',
tableName: 'supervision_tasks',
timestamps: true,
underscored: true,
underscored: false,
createdAt: 'createdAt',
updatedAt: 'updatedAt',
comment: '监管任务表'
@@ -155,11 +156,13 @@ SupervisionTask.init({
SupervisionTask.associate = (models) => {
SupervisionTask.belongsTo(models.User, {
foreignKey: 'createdBy',
as: 'creator'
as: 'creator',
targetKey: 'id'
});
SupervisionTask.belongsTo(models.User, {
foreignKey: 'updatedBy',
as: 'updater'
as: 'updater',
targetKey: 'id'
});
};

View File

@@ -172,6 +172,10 @@ User.init({
sequelize,
tableName: 'bank_users',
modelName: 'User',
timestamps: true,
underscored: true,
createdAt: 'created_at',
updatedAt: 'updated_at',
hooks: {
beforeCreate: async (user) => {
if (user.password) {

View File

@@ -11,7 +11,7 @@ const Role = require('./Role');
const Account = require('./Account');
const Transaction = require('./Transaction');
const LoanProduct = require('./LoanProduct');
const Employee = require('./Employee');
const Employee = require('./Employee')(sequelize);
const Department = require('./Department');
const Position = require('./Position');
const Report = require('./Report');
@@ -62,29 +62,7 @@ Transaction.belongsTo(Account, {
// 交易记录与用户关联(通过账户)
// 移除不合理的Transaction->User through Account的belongsTo定义避免错误外键映射
// 员工与部门关联
Employee.belongsTo(Department, {
foreignKey: 'department_id',
as: 'department',
targetKey: 'id'
});
Department.hasMany(Employee, {
foreignKey: 'department_id',
as: 'employees'
});
// 员工与职位关联
Employee.belongsTo(Position, {
foreignKey: 'position_id',
as: 'position',
targetKey: 'id'
});
Position.hasMany(Employee, {
foreignKey: 'position_id',
as: 'employees'
});
// 员工关联关系Employee模型使用字符串字段存储部门和职位不需要关联
// 报表与用户关联
Report.belongsTo(User, {
@@ -100,7 +78,7 @@ User.hasMany(Report, {
// 项目与用户关联(创建人)
Project.belongsTo(User, {
foreignKey: 'createdBy',
foreignKey: { name: 'createdBy', field: 'createdBy' },
as: 'creator',
targetKey: 'id'
});
@@ -112,7 +90,7 @@ User.hasMany(Project, {
// 项目与用户关联(更新人)
Project.belongsTo(User, {
foreignKey: 'updatedBy',
foreignKey: { name: 'updatedBy', field: 'updatedBy' },
as: 'updater',
targetKey: 'id'
});
@@ -218,41 +196,8 @@ User.hasMany(LoanProduct, {
as: 'updatedLoanProducts'
});
// 贷款申请与用户关联(申请人)
LoanApplication.belongsTo(User, {
foreignKey: 'applicantId',
as: 'applicant',
targetKey: 'id'
});
User.hasMany(LoanApplication, {
foreignKey: 'applicantId',
as: 'loanApplications'
});
// 贷款申请与用户关联(审批人)
LoanApplication.belongsTo(User, {
foreignKey: 'approvedBy',
as: 'approver',
targetKey: 'id'
});
User.hasMany(LoanApplication, {
foreignKey: 'approvedBy',
as: 'approvedApplications'
});
// 贷款申请与用户关联(拒绝人)
LoanApplication.belongsTo(User, {
foreignKey: 'rejectedBy',
as: 'rejector',
targetKey: 'id'
});
User.hasMany(LoanApplication, {
foreignKey: 'rejectedBy',
as: 'rejectedApplications'
});
// 贷款申请暂时不关联用户表,因为当前表结构中没有外键字段
// 如果需要关联,需要先添加相应的外键字段到数据库表中
// 审核记录与贷款申请关联
AuditRecord.belongsTo(LoanApplication, {
@@ -278,29 +223,8 @@ User.hasMany(AuditRecord, {
as: 'auditRecords'
});
// 贷款合同与用户关联(创建人)
LoanContract.belongsTo(User, {
foreignKey: 'createdBy',
as: 'creator',
targetKey: 'id'
});
User.hasMany(LoanContract, {
foreignKey: 'createdBy',
as: 'createdLoanContracts'
});
// 贷款合同与用户关联(更新人)
LoanContract.belongsTo(User, {
foreignKey: 'updatedBy',
as: 'updater',
targetKey: 'id'
});
User.hasMany(LoanContract, {
foreignKey: 'updatedBy',
as: 'updatedLoanContracts'
});
// 贷款合同暂时不关联用户表,因为当前表结构中没有外键字段
// 如果需要关联,需要先添加相应的外键字段到数据库表中
// 导出所有模型和数据库实例
module.exports = {

View File

@@ -0,0 +1,26 @@
const { sequelize, Project, User } = require('./models');
(async () => {
try {
console.log('DB should be ningxia_bank test:');
const [dbRow] = await sequelize.query('SELECT DATABASE() AS db');
console.log('DB:', dbRow[0].db);
console.log('Testing Project.findAndCountAll with includes...');
const result = await Project.findAndCountAll({
include: [
{ model: User, as: 'creator', attributes: ['id', 'username'] },
{ model: User, as: 'updater', attributes: ['id', 'username'] }
],
limit: 5
});
console.log('count:', result.count);
console.log('rows length:', result.rows.length);
} catch (err) {
console.error('ERROR:', err.message);
} finally {
await sequelize.close();
}
})();

View File

@@ -1,316 +1,116 @@
/**
* 员工路由
* @file employees.js
* @description 员工相关的路由定义
*/
const express = require('express');
const { body } = require('express-validator');
const { authMiddleware, roleMiddleware, adminMiddleware, managerMiddleware } = require('../middleware/auth');
const employeeController = require('../controllers/employeeController');
const { authMiddleware } = require('../middleware/auth');
const {
getEmployees,
getEmployeeById,
createEmployee,
updateEmployee,
resetPassword,
deleteEmployee,
batchUpdateStatus,
getEmployeeStats
} = require('../controllers/employeeController');
const router = express.Router();
// 所有路由都需要认证
// 验证规则
const createEmployeeValidation = [
body('employeeNumber')
.notEmpty()
.withMessage('员工编号不能为空')
.isLength({ min: 3, max: 20 })
.withMessage('员工编号长度在3-20个字符'),
body('name')
.notEmpty()
.withMessage('员工姓名不能为空')
.isLength({ min: 2, max: 50 })
.withMessage('员工姓名长度在2-50个字符'),
body('phone')
.notEmpty()
.withMessage('联系电话不能为空')
.matches(/^1[3-9]\d{9}$/)
.withMessage('请输入正确的手机号码'),
body('email')
.optional()
.isEmail()
.withMessage('请输入正确的邮箱地址'),
body('password')
.optional()
.isLength({ min: 6, max: 20 })
.withMessage('密码长度在6-20个字符'),
body('isLoanSpecialist')
.optional()
.isBoolean()
.withMessage('贷款专员标识必须是布尔值'),
body('department')
.optional()
.isLength({ max: 50 })
.withMessage('部门名称不能超过50个字符'),
body('position')
.optional()
.isLength({ max: 50 })
.withMessage('职位名称不能超过50个字符')
];
const updateEmployeeValidation = [
body('name')
.optional()
.isLength({ min: 2, max: 50 })
.withMessage('员工姓名长度在2-50个字符'),
body('phone')
.optional()
.matches(/^1[3-9]\d{9}$/)
.withMessage('请输入正确的手机号码'),
body('email')
.optional()
.isEmail()
.withMessage('请输入正确的邮箱地址'),
body('isLoanSpecialist')
.optional()
.isBoolean()
.withMessage('贷款专员标识必须是布尔值'),
body('department')
.optional()
.isLength({ max: 50 })
.withMessage('部门名称不能超过50个字符'),
body('position')
.optional()
.isLength({ max: 50 })
.withMessage('职位名称不能超过50个字符'),
body('status')
.optional()
.isIn(['active', 'inactive', 'locked'])
.withMessage('状态值无效')
];
const resetPasswordValidation = [
body('newPassword')
.optional()
.isLength({ min: 6, max: 20 })
.withMessage('密码长度在6-20个字符')
];
const batchUpdateStatusValidation = [
body('ids')
.isArray({ min: 1 })
.withMessage('请选择要更新的员工'),
body('status')
.isIn(['active', 'inactive', 'locked'])
.withMessage('状态值无效')
];
// 应用认证中间件
router.use(authMiddleware);
/**
* @swagger
* tags:
* name: Employees
* description: 员工管理
*/
// 路由定义
router.get('/', getEmployees); // 获取员工列表
router.get('/stats', getEmployeeStats); // 获取员工统计
router.get('/:id', getEmployeeById); // 获取员工详情
router.post('/', createEmployeeValidation, createEmployee); // 创建员工
router.put('/:id', updateEmployeeValidation, updateEmployee); // 更新员工信息
router.put('/:id/reset-password', resetPasswordValidation, resetPassword); // 重设密码
router.delete('/:id', deleteEmployee); // 删除员工
router.put('/batch/status', batchUpdateStatusValidation, batchUpdateStatus); // 批量更新状态
/**
* @swagger
* /api/employees:
* get:
* summary: 获取员工列表
* tags: [Employees]
* security:
* - bearerAuth: []
* parameters:
* - in: query
* name: page
* schema:
* type: integer
* description: 页码
* - in: query
* name: limit
* schema:
* type: integer
* description: 每页数量
* - in: query
* name: search
* schema:
* type: string
* description: 搜索关键词
* - in: query
* name: department
* schema:
* type: string
* description: 部门筛选
* - in: query
* name: position
* schema:
* type: string
* description: 职位筛选
* - in: query
* name: status
* schema:
* type: string
* enum: [active, inactive, resigned]
* description: 状态筛选
* responses:
* 200:
* description: 获取成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* message:
* type: string
* data:
* type: object
* properties:
* employees:
* type: array
* items:
* $ref: '#/components/schemas/Employee'
* pagination:
* $ref: '#/components/schemas/Pagination'
* 401:
* description: 未授权
* 500:
* description: 服务器内部错误
*/
router.get('/', roleMiddleware(['admin', 'manager', 'teller']), employeeController.getEmployees);
/**
* @swagger
* /api/employees:
* post:
* summary: 创建员工
* tags: [Employees]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - name
* - employee_id
* - department_id
* - position_id
* - hire_date
* - salary
* properties:
* name:
* type: string
* description: 员工姓名
* employee_id:
* type: string
* description: 员工编号
* department_id:
* type: integer
* description: 部门ID
* position_id:
* type: integer
* description: 职位ID
* phone:
* type: string
* description: 联系电话
* email:
* type: string
* description: 邮箱地址
* hire_date:
* type: string
* format: date
* description: 入职日期
* salary:
* type: number
* description: 薪资
* status:
* type: string
* enum: [active, inactive, resigned]
* description: 员工状态
* responses:
* 201:
* description: 创建成功
* 400:
* description: 请求参数错误
* 401:
* description: 未授权
* 403:
* description: 权限不足
* 500:
* description: 服务器内部错误
*/
router.post('/',
adminMiddleware,
[
body('name').notEmpty().withMessage('员工姓名不能为空'),
body('employee_id').notEmpty().withMessage('员工编号不能为空'),
body('department_id').isInt().withMessage('部门ID必须是整数'),
body('position_id').isInt().withMessage('职位ID必须是整数'),
body('phone').optional().isMobilePhone('zh-CN').withMessage('手机号格式不正确'),
body('email').optional().isEmail().withMessage('邮箱格式不正确'),
body('hire_date').isISO8601().withMessage('入职日期格式不正确'),
body('salary').isNumeric().withMessage('薪资必须是数字'),
body('status').optional().isIn(['active', 'inactive', 'resigned']).withMessage('状态值无效')
],
employeeController.createEmployee
);
/**
* @swagger
* /api/employees/{id}:
* get:
* summary: 获取员工详情
* tags: [Employees]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: 员工ID
* responses:
* 200:
* description: 获取成功
* 404:
* description: 员工不存在
* 401:
* description: 未授权
* 500:
* description: 服务器内部错误
*/
router.get('/:id', roleMiddleware(['admin', 'manager', 'teller']), employeeController.getEmployeeById);
/**
* @swagger
* /api/employees/{id}:
* put:
* summary: 更新员工
* tags: [Employees]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: 员工ID
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* name:
* type: string
* employee_id:
* type: string
* department_id:
* type: integer
* position_id:
* type: integer
* phone:
* type: string
* email:
* type: string
* hire_date:
* type: string
* format: date
* salary:
* type: number
* status:
* type: string
* enum: [active, inactive, resigned]
* responses:
* 200:
* description: 更新成功
* 400:
* description: 请求参数错误
* 404:
* description: 员工不存在
* 401:
* description: 未授权
* 403:
* description: 权限不足
* 500:
* description: 服务器内部错误
*/
router.put('/:id',
adminMiddleware,
[
body('name').optional().notEmpty().withMessage('员工姓名不能为空'),
body('employee_id').optional().notEmpty().withMessage('员工编号不能为空'),
body('department_id').optional().isInt().withMessage('部门ID必须是整数'),
body('position_id').optional().isInt().withMessage('职位ID必须是整数'),
body('phone').optional().isMobilePhone('zh-CN').withMessage('手机号格式不正确'),
body('email').optional().isEmail().withMessage('邮箱格式不正确'),
body('hire_date').optional().isISO8601().withMessage('入职日期格式不正确'),
body('salary').optional().isNumeric().withMessage('薪资必须是数字'),
body('status').optional().isIn(['active', 'inactive', 'resigned']).withMessage('状态值无效')
],
employeeController.updateEmployee
);
/**
* @swagger
* /api/employees/{id}:
* delete:
* summary: 删除员工
* tags: [Employees]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: 员工ID
* responses:
* 200:
* description: 删除成功
* 404:
* description: 员工不存在
* 401:
* description: 未授权
* 403:
* description: 权限不足
* 500:
* description: 服务器内部错误
*/
router.delete('/:id', adminMiddleware, employeeController.deleteEmployee);
/**
* @swagger
* /api/employees/stats/overview:
* get:
* summary: 获取员工统计
* tags: [Employees]
* security:
* - bearerAuth: []
* responses:
* 200:
* description: 获取成功
* 401:
* description: 未授权
* 500:
* description: 服务器内部错误
*/
router.get('/stats/overview', roleMiddleware(['admin', 'manager', 'teller']), employeeController.getEmployeeStats);
module.exports = router;
module.exports = router;

View File

@@ -0,0 +1,173 @@
const { sequelize, Employee } = require('../models');
/**
* 添加员工测试数据
*/
async function seedEmployees() {
try {
console.log('开始添加员工测试数据...');
// 检查数据库连接
await sequelize.authenticate();
console.log('✅ 银行系统数据库连接成功');
// 清空现有员工数据
await Employee.destroy({
where: {},
force: true
});
console.log('✅ 清空现有员工数据');
// 员工测试数据
const employeesData = [
{
employeeNumber: 'EMP001',
name: '刘超',
phone: '15012341368',
email: 'liuchao@bank.com',
password: '123456',
isLoanSpecialist: false,
department: '风险管理部',
position: '风险专员',
status: 'active'
},
{
employeeNumber: 'EMP002',
name: '张明',
phone: '13812345678',
email: 'zhangming@bank.com',
password: '123456',
isLoanSpecialist: true,
department: '信贷部',
position: '贷款专员',
status: 'active'
},
{
employeeNumber: 'EMP003',
name: '李红',
phone: '13987654321',
email: 'lihong@bank.com',
password: '123456',
isLoanSpecialist: true,
department: '信贷部',
position: '高级贷款专员',
status: 'active'
},
{
employeeNumber: 'EMP004',
name: '王强',
phone: '13611111111',
email: 'wangqiang@bank.com',
password: '123456',
isLoanSpecialist: false,
department: '运营部',
position: '运营专员',
status: 'active'
},
{
employeeNumber: 'EMP005',
name: '陈静',
phone: '13722222222',
email: 'chenjing@bank.com',
password: '123456',
isLoanSpecialist: true,
department: '信贷部',
position: '贷款专员',
status: 'inactive'
},
{
employeeNumber: 'EMP006',
name: '赵磊',
phone: '13833333333',
email: 'zhaolei@bank.com',
password: '123456',
isLoanSpecialist: false,
department: '技术部',
position: '系统管理员',
status: 'active'
},
{
employeeNumber: 'EMP007',
name: '孙丽',
phone: '13944444444',
email: 'sunli@bank.com',
password: '123456',
isLoanSpecialist: true,
department: '信贷部',
position: '贷款专员',
status: 'locked'
},
{
employeeNumber: 'EMP008',
name: '周涛',
phone: '13555555555',
email: 'zhoutao@bank.com',
password: '123456',
isLoanSpecialist: false,
department: '财务部',
position: '财务专员',
status: 'active'
},
{
employeeNumber: 'EMP009',
name: '吴敏',
phone: '13466666666',
email: 'wumin@bank.com',
password: '123456',
isLoanSpecialist: true,
department: '信贷部',
position: '贷款专员',
status: 'active'
},
{
employeeNumber: 'EMP010',
name: '郑华',
phone: '13377777777',
email: 'zhenghua@bank.com',
password: '123456',
isLoanSpecialist: false,
department: '人事部',
position: '人事专员',
status: 'active'
}
];
// 创建员工
const employees = await Employee.bulkCreate(employeesData);
console.log(`✅ 成功创建${employees.length}个员工`);
// 统计信息
const activeCount = await Employee.count({ where: { status: 'active' } });
const inactiveCount = await Employee.count({ where: { status: 'inactive' } });
const lockedCount = await Employee.count({ where: { status: 'locked' } });
const loanSpecialistCount = await Employee.count({ where: { isLoanSpecialist: true } });
console.log('\n📊 员工数据统计:');
console.log(`- 活跃员工:${activeCount}`);
console.log(`- 停用员工:${inactiveCount}`);
console.log(`- 锁定员工:${lockedCount}`);
console.log(`- 贷款专员:${loanSpecialistCount}`);
console.log(`- 总员工数:${employees.length}`);
console.log('\n🎉 员工测试数据添加完成!');
} catch (error) {
console.error('❌ 添加员工测试数据失败:', error);
throw error;
}
}
// 如果直接运行此脚本
if (require.main === module) {
seedEmployees()
.then(() => {
console.log('脚本执行完成');
process.exit(0);
})
.catch((error) => {
console.error('❌ 脚本执行失败:', error);
process.exit(1);
});
}
module.exports = seedEmployees;

View File

@@ -0,0 +1,161 @@
/**
* 个人中心测试数据种子脚本
* @file seed-personal-center-test-data.js
* @description 为个人中心功能添加测试数据
*/
const { User, Role } = require('../models');
const bcrypt = require('bcryptjs');
const { sequelize } = require('../config/database');
async function seedPersonalCenterTestData() {
try {
console.log('=== 开始添加个人中心测试数据 ===\n');
// 1. 确保admin角色存在
console.log('1. 检查admin角色...');
let adminRole = await Role.findOne({ where: { name: 'admin' } });
if (!adminRole) {
adminRole = await Role.create({
name: 'admin',
display_name: '系统管理员',
description: '系统管理员角色',
permissions: JSON.stringify(['*'])
});
console.log('✅ 创建admin角色成功');
} else {
console.log('✅ admin角色已存在');
}
// 2. 确保manager角色存在
console.log('2. 检查manager角色...');
let managerRole = await Role.findOne({ where: { name: 'manager' } });
if (!managerRole) {
managerRole = await Role.create({
name: 'manager',
display_name: '经理',
description: '经理角色',
permissions: JSON.stringify(['read', 'write', 'approve'])
});
console.log('✅ 创建manager角色成功');
} else {
console.log('✅ manager角色已存在');
}
// 3. 创建测试用户 - 银行经理
console.log('3. 创建测试用户 - 银行经理...');
const managerPassword = await bcrypt.hash('Manager123456', 10);
const managerUser = await User.findOrCreate({
where: { username: 'manager001' },
defaults: {
username: 'manager001',
password: managerPassword,
real_name: '李经理',
email: 'manager001@bank.com',
phone: '15004901368',
id_card: '110101198001010001',
status: 'active',
role_id: managerRole.id,
position: '部门经理',
department: '风险管理部'
}
});
if (managerUser[1]) {
console.log('✅ 创建银行经理用户成功');
} else {
console.log('✅ 银行经理用户已存在');
}
// 4. 创建测试用户 - 银行员工
console.log('4. 创建测试用户 - 银行员工...');
const employeePassword = await bcrypt.hash('Employee123456', 10);
const employeeUser = await User.findOrCreate({
where: { username: 'employee001' },
defaults: {
username: 'employee001',
password: employeePassword,
real_name: '王员工',
email: 'employee001@bank.com',
phone: '13800138000',
id_card: '110101199001010002',
status: 'active',
role_id: managerRole.id,
position: '业务专员',
department: '客户服务部'
}
});
if (employeeUser[1]) {
console.log('✅ 创建银行员工用户成功');
} else {
console.log('✅ 银行员工用户已存在');
}
// 5. 更新admin用户信息
console.log('5. 更新admin用户信息...');
const adminUser = await User.findOne({ where: { username: 'admin' } });
if (adminUser) {
await adminUser.update({
real_name: '系统管理员',
phone: '15004901368',
position: '系统管理员',
department: '信息技术部'
});
console.log('✅ 更新admin用户信息成功');
} else {
console.log('⚠️ admin用户不存在请先运行create-admin-user.js');
}
// 6. 显示测试用户信息
console.log('\n=== 测试用户信息 ===');
const testUsers = await User.findAll({
where: {
username: ['admin', 'manager001', 'employee001']
},
include: [{
model: Role,
as: 'role'
}]
});
testUsers.forEach(user => {
console.log(`\n用户名: ${user.username}`);
console.log(`姓名: ${user.real_name}`);
console.log(`手机: ${user.phone}`);
console.log(`邮箱: ${user.email}`);
console.log(`角色: ${user.role ? user.role.display_name : '未知'}`);
console.log(`部门: ${user.department || '未设置'}`);
console.log(`职位: ${user.position || '未设置'}`);
console.log(`状态: ${user.status}`);
});
console.log('\n=== 测试登录信息 ===');
console.log('管理员账号: admin / Admin123456');
console.log('经理账号: manager001 / Manager123456');
console.log('员工账号: employee001 / Employee123456');
console.log('\n=== 个人中心测试数据添加完成 ===');
} catch (error) {
console.error('❌ 添加个人中心测试数据失败:', error);
throw error;
} finally {
await sequelize.close();
}
}
// 运行脚本
if (require.main === module) {
seedPersonalCenterTestData()
.then(() => {
console.log('✅ 脚本执行完成');
process.exit(0);
})
.catch((error) => {
console.error('❌ 脚本执行失败:', error);
process.exit(1);
});
}
module.exports = seedPersonalCenterTestData;

View File

@@ -1,10 +0,0 @@
const authController = require('./controllers/authController');
const authMiddleware = require('./middleware/auth');
console.log('authController type:', typeof authController);
console.log('authController.logout type:', typeof authController.logout);
console.log('authMiddleware type:', typeof authMiddleware);
console.log('authMiddleware.authMiddleware type:', typeof authMiddleware.authMiddleware);
console.log('authController keys:', Object.keys(authController));
console.log('authMiddleware keys:', Object.keys(authMiddleware));

View File

@@ -0,0 +1,38 @@
const { sequelize, InstallationTask, User } = require('./models');
(async () => {
try {
console.log('测试InstallationTask模型...');
// 测试基本查询
console.log('1. 测试基本count查询...');
const count = await InstallationTask.count();
console.log('InstallationTask count:', count);
// 测试findAndCountAll查询
console.log('2. 测试findAndCountAll查询...');
const result = await InstallationTask.findAndCountAll({
limit: 5
});
console.log('findAndCountAll result count:', result.count);
console.log('findAndCountAll rows length:', result.rows.length);
// 测试带include的查询
console.log('3. 测试带include的查询...');
const resultWithInclude = await InstallationTask.findAndCountAll({
include: [
{ model: User, as: 'creator', attributes: ['id', 'username'] },
{ model: User, as: 'updater', attributes: ['id', 'username'] }
],
limit: 5
});
console.log('include查询 count:', resultWithInclude.count);
console.log('include查询 rows length:', resultWithInclude.rows.length);
} catch (err) {
console.error('错误:', err.message);
console.error('SQL:', err.sql);
} finally {
await sequelize.close();
}
})();

View File

@@ -1134,6 +1134,82 @@ export const api = {
async batchUpdateStatus(data) {
return api.put('/loan-contracts/batch/status', data)
}
},
// 员工管理API
employees: {
/**
* 获取员工列表
* @param {Object} params - 查询参数
* @returns {Promise} 员工列表
*/
async getList(params = {}) {
return api.get('/employees', { params })
},
/**
* 获取员工详情
* @param {number} id - 员工ID
* @returns {Promise} 员工详情
*/
async getById(id) {
return api.get(`/employees/${id}`)
},
/**
* 创建员工
* @param {Object} data - 员工数据
* @returns {Promise} 创建结果
*/
async create(data) {
return api.post('/employees', data)
},
/**
* 更新员工信息
* @param {number} id - 员工ID
* @param {Object} data - 员工数据
* @returns {Promise} 更新结果
*/
async update(id, data) {
return api.put(`/employees/${id}`, data)
},
/**
* 重设密码
* @param {number} id - 员工ID
* @param {Object} data - 密码数据
* @returns {Promise} 重设结果
*/
async resetPassword(id, data) {
return api.put(`/employees/${id}/reset-password`, data)
},
/**
* 删除员工
* @param {number} id - 员工ID
* @returns {Promise} 删除结果
*/
async delete(id) {
return api.delete(`/employees/${id}`)
},
/**
* 获取员工统计信息
* @returns {Promise} 统计信息
*/
async getStats() {
return api.get('/employees/stats')
},
/**
* 批量更新员工状态
* @param {Object} data - 批量操作数据
* @returns {Promise} 更新结果
*/
async batchUpdateStatus(data) {
return api.put('/employees/batch/status', data)
}
}
}

View File

@@ -1,26 +1,36 @@
<template>
<div class="employee-management-page">
<!-- 页面头部 -->
<div class="page-header">
<h1>员工管理</h1>
<a-button type="primary" @click="handleAdd">
<template #icon>
<PlusOutlined />
</template>
新增人员
</a-button>
</div>
<!-- 搜索筛选区域 -->
<div class="search-filter-section">
<a-row :gutter="16" align="middle">
<a-col :span="4">
<a-button type="primary" @click="handleAddEmployee">
<plus-outlined /> 新增人员
</a-button>
</a-col>
<a-row :gutter="16">
<a-col :span="8">
<a-input
v-model:value="searchText"
v-model:value="searchQuery.value"
placeholder="请输入员工姓名"
allow-clear
/>
@pressEnter="handleSearch"
>
<template #prefix>
<SearchOutlined />
</template>
</a-input>
</a-col>
<a-col :span="4">
<a-button type="primary" @click="handleSearch">
<search-outlined /> 搜索
<template #icon>
<SearchOutlined />
</template>
搜索
</a-button>
</a-col>
<a-col :span="4">
@@ -29,30 +39,57 @@
</a-row>
</div>
<!-- 员工表格 -->
<div class="employees-table-section">
<a-table
:columns="columns"
:data-source="filteredEmployees"
:pagination="pagination"
:data-source="employees"
:loading="loading"
row-key="id"
:pagination="pagination"
:row-selection="rowSelection"
@change="handleTableChange"
row-key="id"
:scroll="{ x: 800 }"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'status'">
<!-- 序号列 -->
<template #bodyCell="{ column, index, record }">
<template v-if="column.key === 'index'">
{{ (pagination.current - 1) * pagination.pageSize + index + 1 }}
</template>
<!-- 员工姓名列 -->
<template v-else-if="column.key === 'name'">
<span class="employee-name">{{ record.name }}</span>
</template>
<!-- 联系电话列 -->
<template v-else-if="column.key === 'phone'">
<span class="phone-number">{{ maskPhone(record.phone) }}</span>
</template>
<!-- 贷款专员列 -->
<template v-else-if="column.key === 'isLoanSpecialist'">
<a-tag :color="record.isLoanSpecialist ? 'green' : 'default'">
{{ record.isLoanSpecialist ? '是' : '否' }}
</a-tag>
</template>
<!-- 账号状态列 -->
<template v-else-if="column.key === 'status'">
<a-switch
v-model:checked="record.status"
checked-children="启用"
un-checked-children="禁用"
@change="handleToggleStatus(record)"
:checked="record.status === 'active'"
@change="(checked) => handleStatusChange(record, checked)"
:loading="record.statusLoading"
/>
</template>
<!-- 操作列 -->
<template v-else-if="column.key === 'action'">
<a-space>
<a-button type="link" size="small" @click="handleResetPassword(record)">
<a-button type="link" @click="handleResetPassword(record)">
重设密码
</a-button>
<a-button type="link" size="small" @click="handleEdit(record)">
<a-button type="link" @click="handleEdit(record)">
编辑
</a-button>
</a-space>
@@ -61,115 +98,137 @@
</a-table>
</div>
<!-- 员工详情模态框 -->
<!-- 新增/编辑员工弹窗 -->
<a-modal
v-model:open="detailModalVisible"
title="员工详情"
width="800px"
:footer="null"
v-model:open="editModalVisible"
:title="isEdit ? '编辑员工' : '新增员工'"
width="600px"
:confirm-loading="editLoading"
@ok="handleEditSubmit"
@cancel="handleEditCancel"
>
<div v-if="selectedEmployee" class="employee-detail">
<div class="employee-header">
<a-avatar :src="selectedEmployee.avatar" :size="64">
{{ selectedEmployee.name.charAt(0) }}
</a-avatar>
<div class="employee-info">
<h3>{{ selectedEmployee.name }}</h3>
<p>{{ getPositionText(selectedEmployee.position) }} - {{ getDepartmentText(selectedEmployee.department) }}</p>
<a-tag :color="getStatusColor(selectedEmployee.status)">
{{ getStatusText(selectedEmployee.status) }}
</a-tag>
</div>
</div>
<a-form
ref="editFormRef"
:model="editForm"
:rules="editFormRules"
layout="vertical"
v-if="editModalVisible"
>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="员工编号" name="employeeNumber">
<a-input
v-model:value="editForm.employeeNumber"
placeholder="请输入员工编号"
:disabled="isEdit"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="员工姓名" name="name">
<a-input
v-model:value="editForm.name"
placeholder="请输入员工姓名"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="联系电话" name="phone">
<a-input
v-model:value="editForm.phone"
placeholder="请输入联系电话"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="邮箱" name="email">
<a-input
v-model:value="editForm.email"
placeholder="请输入邮箱"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="部门" name="department">
<a-input
v-model:value="editForm.department"
placeholder="请输入部门"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="职位" name="position">
<a-input
v-model:value="editForm.position"
placeholder="请输入职位"
/>
</a-form-item>
</a-col>
</a-row>
<a-form-item label="是否为贷款专员" name="isLoanSpecialist">
<a-switch
v-model:checked="editForm.isLoanSpecialist"
checked-children=""
un-checked-children=""
/>
</a-form-item>
<a-form-item v-if="!isEdit" label="初始密码" name="password">
<a-input
v-model:value="editForm.password"
placeholder="请输入初始密码默认为123456"
type="password"
/>
</a-form-item>
</a-form>
</a-modal>
<a-descriptions :column="2" bordered style="margin-top: 24px">
<a-descriptions-item label="工号">
{{ selectedEmployee.employeeId }}
</a-descriptions-item>
<a-descriptions-item label="姓名">
{{ selectedEmployee.name }}
</a-descriptions-item>
<a-descriptions-item label="性别">
{{ selectedEmployee.gender }}
</a-descriptions-item>
<a-descriptions-item label="年龄">
{{ selectedEmployee.age }}
</a-descriptions-item>
<a-descriptions-item label="手机号">
{{ selectedEmployee.phone }}
</a-descriptions-item>
<a-descriptions-item label="邮箱">
{{ selectedEmployee.email }}
</a-descriptions-item>
<a-descriptions-item label="身份证号">
{{ selectedEmployee.idCard }}
</a-descriptions-item>
<a-descriptions-item label="入职日期">
{{ selectedEmployee.hireDate }}
</a-descriptions-item>
<a-descriptions-item label="部门">
<a-tag :color="getDepartmentColor(selectedEmployee.department)">
{{ getDepartmentText(selectedEmployee.department) }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="职位">
{{ getPositionText(selectedEmployee.position) }}
</a-descriptions-item>
<a-descriptions-item label="直属上级">
{{ selectedEmployee.supervisor }}
</a-descriptions-item>
<a-descriptions-item label="薪资等级">
{{ selectedEmployee.salaryLevel }}
</a-descriptions-item>
<a-descriptions-item label="工作地点">
{{ selectedEmployee.workLocation }}
</a-descriptions-item>
<a-descriptions-item label="紧急联系人">
{{ selectedEmployee.emergencyContact }}
</a-descriptions-item>
<a-descriptions-item label="紧急联系电话">
{{ selectedEmployee.emergencyPhone }}
</a-descriptions-item>
<a-descriptions-item label="个人简介" :span="2">
{{ selectedEmployee.bio || '暂无' }}
</a-descriptions-item>
</a-descriptions>
<!-- 工作经历 -->
<div class="work-experience" v-if="selectedEmployee && selectedEmployee.experience">
<h4>工作经历</h4>
<a-timeline>
<a-timeline-item
v-for="exp in selectedEmployee.experience"
:key="exp.id"
>
<div class="experience-item">
<div class="experience-header">
<span class="experience-title">{{ exp.title }}</span>
<span class="experience-period">{{ exp.startDate }} - {{ exp.endDate || '至今' }}</span>
</div>
<div class="experience-company">{{ exp.company }}</div>
<div class="experience-description">{{ exp.description }}</div>
</div>
</a-timeline-item>
</a-timeline>
</div>
</div>
<!-- 重设密码弹窗 -->
<a-modal
v-model:open="resetPasswordModalVisible"
title="重设密码"
width="400px"
:confirm-loading="resetPasswordLoading"
@ok="handleResetPasswordSubmit"
@cancel="handleResetPasswordCancel"
>
<a-form
ref="resetPasswordFormRef"
:model="resetPasswordForm"
:rules="resetPasswordFormRules"
layout="vertical"
>
<a-form-item label="新密码" name="newPassword">
<a-input
v-model:value="resetPasswordForm.newPassword"
placeholder="请输入新密码"
type="password"
/>
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { ref, reactive, onMounted, computed, watch } from 'vue'
import { message } from 'ant-design-vue'
import { PlusOutlined, SearchOutlined } from '@ant-design/icons-vue'
import { api } from '@/utils/api'
import api from '@/utils/api'
// 响应式数据
const loading = ref(false)
const searchText = ref('')
const detailModalVisible = ref(false)
const selectedEmployee = ref(null)
const searchQuery = ref({
field: 'name',
value: ''
})
// 分页配置
const pagination = ref({
@@ -185,10 +244,9 @@ const pagination = ref({
const columns = [
{
title: '序号',
dataIndex: 'index',
key: 'index',
width: 80,
customRender: ({ index }) => index + 1
align: 'center'
},
{
title: '员工姓名',
@@ -206,22 +264,20 @@ const columns = [
title: '贷款专员',
dataIndex: 'isLoanSpecialist',
key: 'isLoanSpecialist',
width: 120
width: 100,
align: 'center'
},
{
title: '账号状态',
dataIndex: 'status',
key: 'status',
width: 120,
filters: [
{ text: '启用', value: true },
{ text: '禁用', value: false }
]
align: 'center'
},
{
title: '操作',
key: 'action',
width: 200,
width: 150,
fixed: 'right'
}
]
@@ -229,172 +285,272 @@ const columns = [
// 员工数据
const employees = ref([])
// 模拟员工数据(作为备用)
const mockEmployees = [
{
id: 1,
name: '刘超',
phone: '150****1368',
isLoanSpecialist: '否',
status: true,
employeeId: 'EMP001',
gender: '男',
age: 28,
email: 'liuchao@bank.com',
idCard: '110101199001011234',
hireDate: '2020-03-15',
department: 'admin',
position: 'manager',
supervisor: '李总',
salaryLevel: 'L5',
workLocation: '总行',
emergencyContact: '刘四',
emergencyPhone: '13900139000',
bio: '具有5年银行管理经验擅长团队管理和业务规划',
avatar: null,
experience: [
{
id: 1,
title: '行政经理',
company: '某银行',
startDate: '2020-03-15',
endDate: null,
description: '负责行政部日常管理工作'
}
]
}
]
// 调试监听employees变化
watch(employees, (newVal) => {
console.log('employees数据变化:', newVal)
}, { deep: true })
// 计算属性
const filteredEmployees = computed(() => {
let result = employees.value
if (searchText.value) {
result = result.filter(employee =>
employee.name.toLowerCase().includes(searchText.value.toLowerCase())
)
}
return result
// 编辑弹窗相关
const editModalVisible = ref(false)
const editLoading = ref(false)
const editFormRef = ref()
const isEdit = ref(false)
const editForm = reactive({
id: null,
employeeNumber: '',
name: '',
phone: '',
email: '',
department: '',
position: '',
isLoanSpecialist: false,
password: '123456'
})
// 方法
const handleAddEmployee = () => {
message.info('新增人员功能开发中...')
const editFormRules = {
employeeNumber: [
{ required: true, message: '请输入员工编号', trigger: 'blur' },
{ min: 3, max: 20, message: '员工编号长度在3-20个字符', trigger: 'blur' }
],
name: [
{ required: true, message: '请输入员工姓名', trigger: 'blur' },
{ min: 2, max: 50, message: '员工姓名长度在2-50个字符', trigger: 'blur' }
],
phone: [
{ required: true, message: '请输入联系电话', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
],
email: [
{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }
],
department: [
{ max: 50, message: '部门名称不能超过50个字符', trigger: 'blur' }
],
position: [
{ max: 50, message: '职位名称不能超过50个字符', trigger: 'blur' }
],
password: [
{ min: 6, max: 20, message: '密码长度在6-20个字符', trigger: 'blur' }
]
}
const handleSearch = () => {
// 搜索逻辑已在计算属性中处理
// 重设密码弹窗相关
const resetPasswordModalVisible = ref(false)
const resetPasswordLoading = ref(false)
const resetPasswordFormRef = ref()
const currentEmployee = ref(null)
const resetPasswordForm = reactive({
newPassword: ''
})
const resetPasswordFormRules = {
newPassword: [
{ required: true, message: '请输入新密码', trigger: 'blur' },
{ min: 6, max: 20, message: '密码长度在6-20个字符', trigger: 'blur' }
]
}
const handleReset = () => {
searchText.value = ''
}
// 行选择配置
const selectedRowKeys = ref([])
const selectedRows = ref([])
const handleView = (record) => {
selectedEmployee.value = record
detailModalVisible.value = true
}
const handleEdit = (record) => {
message.info(`编辑员工: ${record.name}`)
}
const handleResetPassword = (record) => {
message.info(`重设密码: ${record.name}`)
}
const handleToggleStatus = (record) => {
record.status = !record.status
message.success(`员工账号已${record.status ? '启用' : '禁用'}`)
}
const getStatusColor = (status) => {
const colors = {
active: 'green',
inactive: 'red',
suspended: 'orange'
const rowSelection = computed(() => ({
selectedRowKeys: selectedRowKeys.value,
onChange: (keys, rows) => {
selectedRowKeys.value = keys
selectedRows.value = rows
}
return colors[status] || 'default'
}
}))
const getStatusText = (status) => {
const texts = {
active: '在职',
inactive: '离职',
suspended: '停职'
}
return texts[status] || status
}
const getDepartmentColor = (department) => {
const colors = {
admin: 'blue',
finance: 'green',
it: 'purple',
hr: 'orange',
sales: 'cyan'
}
return colors[department] || 'default'
}
const getDepartmentText = (department) => {
const texts = {
admin: '行政部',
finance: '财务部',
it: '技术部',
hr: '人事部',
sales: '销售部'
}
return texts[department] || department
}
const getPositionText = (position) => {
const texts = {
manager: '经理',
supervisor: '主管',
staff: '员工',
intern: '实习生'
}
return texts[position] || position
}
// API调用函数
const fetchEmployees = async (params = {}) => {
// 获取员工列表
const fetchEmployees = async () => {
try {
loading.value = true
const response = await api.employees.getList({
page: pagination.value.current,
limit: pagination.value.pageSize,
search: searchText.value,
...params
pageSize: pagination.value.pageSize,
searchField: searchQuery.value.field,
searchValue: searchQuery.value.value
})
if (response.success) {
employees.value = response.data.employees || []
pagination.value.total = response.data.pagination?.total || 0
console.log('API响应数据:', response.data)
employees.value = response.data.employees.map(emp => ({
...emp,
statusLoading: false
}))
pagination.value.total = response.data.pagination.total
console.log('设置后的员工数据:', employees.value)
} else {
message.error(response.message || '获取员工列表失败')
// 使用模拟数据作为备用
employees.value = mockEmployees
pagination.value.total = mockEmployees.length
}
} catch (error) {
console.error('获取员工列表失败:', error)
message.error('获取员工列表失败')
// 使用模拟数据作为备用
employees.value = mockEmployees
pagination.value.total = mockEmployees.length
} finally {
loading.value = false
}
}
const handleTableChange = (paginationInfo) => {
pagination.value = paginationInfo
// 搜索
const handleSearch = () => {
pagination.value.current = 1
fetchEmployees()
}
// 重置
const handleReset = () => {
searchQuery.value = {
field: 'name',
value: ''
}
fetchEmployees()
}
// 表格变化
const handleTableChange = (pag) => {
pagination.value.current = pag.current
pagination.value.pageSize = pag.pageSize
fetchEmployees()
}
// 新增员工
const handleAdd = () => {
isEdit.value = false
editModalVisible.value = true
Object.assign(editForm, {
id: null,
employeeNumber: '',
name: '',
phone: '',
email: '',
department: '',
position: '',
isLoanSpecialist: false,
password: '123456'
})
}
// 编辑员工
const handleEdit = (record) => {
isEdit.value = true
editModalVisible.value = true
Object.assign(editForm, {
id: record.id,
employeeNumber: record.employeeNumber,
name: record.name,
phone: record.phone,
email: record.email,
department: record.department,
position: record.position,
isLoanSpecialist: record.isLoanSpecialist,
password: ''
})
}
// 编辑提交
const handleEditSubmit = async () => {
try {
await editFormRef.value.validate()
editLoading.value = true
const data = { ...editForm }
if (isEdit.value) {
delete data.password // 编辑时不传密码
}
const response = isEdit.value
? await api.employees.update(editForm.id, data)
: await api.employees.create(data)
if (response.success) {
message.success(isEdit.value ? '更新员工成功' : '创建员工成功')
editModalVisible.value = false
fetchEmployees()
} else {
message.error(response.message || (isEdit.value ? '更新员工失败' : '创建员工失败'))
}
} catch (error) {
console.error('编辑员工失败:', error)
message.error(isEdit.value ? '更新员工失败' : '创建员工失败')
} finally {
editLoading.value = false
}
}
// 编辑取消
const handleEditCancel = () => {
editModalVisible.value = false
editFormRef.value?.resetFields()
}
// 重设密码
const handleResetPassword = (record) => {
currentEmployee.value = record
resetPasswordModalVisible.value = true
resetPasswordForm.newPassword = ''
}
// 重设密码提交
const handleResetPasswordSubmit = async () => {
try {
await resetPasswordFormRef.value.validate()
resetPasswordLoading.value = true
const response = await api.employees.resetPassword(currentEmployee.value.id, {
newPassword: resetPasswordForm.newPassword
})
if (response.success) {
message.success('重设密码成功')
resetPasswordModalVisible.value = false
} else {
message.error(response.message || '重设密码失败')
}
} catch (error) {
console.error('重设密码失败:', error)
message.error('重设密码失败')
} finally {
resetPasswordLoading.value = false
}
}
// 重设密码取消
const handleResetPasswordCancel = () => {
resetPasswordModalVisible.value = false
resetPasswordFormRef.value?.resetFields()
}
// 状态切换
const handleStatusChange = async (record, checked) => {
try {
record.statusLoading = true
const newStatus = checked ? 'active' : 'inactive'
const response = await api.employees.update(record.id, {
status: newStatus
})
if (response.success) {
record.status = newStatus
message.success('状态更新成功')
} else {
message.error(response.message || '状态更新失败')
}
} catch (error) {
console.error('状态更新失败:', error)
message.error('状态更新失败')
} finally {
record.statusLoading = false
}
}
// 手机号脱敏
const maskPhone = (phone) => {
if (!phone) return ''
return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
}
// 生命周期
onMounted(() => {
fetchEmployees()
@@ -441,73 +597,13 @@ onMounted(() => {
overflow-x: auto;
}
.employee-detail {
padding: 16px 0;
}
.employee-header {
display: flex;
align-items: center;
padding: 16px 0;
border-bottom: 1px solid #f0f0f0;
}
.employee-info {
margin-left: 16px;
}
.employee-info h3 {
margin: 0 0 8px 0;
font-size: 20px;
font-weight: 600;
}
.employee-info p {
margin: 0 0 8px 0;
color: #666;
font-size: 14px;
}
.work-experience {
margin-top: 24px;
}
.work-experience h4 {
margin-bottom: 16px;
font-size: 16px;
font-weight: 600;
}
.experience-item {
padding: 8px 0;
}
.experience-header {
display: flex;
justify-content: space-between;
margin-bottom: 4px;
}
.experience-title {
font-weight: 600;
font-size: 14px;
}
.experience-period {
color: #999;
font-size: 12px;
}
.experience-company {
color: #666;
font-size: 12px;
margin-bottom: 4px;
}
.experience-description {
.employee-name {
font-weight: 500;
color: #333;
font-size: 12px;
line-height: 1.5;
}
.phone-number {
color: #666;
}
/* 表格样式优化 */
@@ -525,6 +621,13 @@ onMounted(() => {
background-color: #f5f5f5;
}
/* 状态标签样式 */
:deep(.ant-tag) {
border-radius: 4px;
font-size: 12px;
padding: 2px 8px;
}
/* 操作按钮样式 */
:deep(.ant-btn-link) {
padding: 4px 8px;
@@ -554,4 +657,4 @@ onMounted(() => {
margin-bottom: 0;
}
}
</style>
</style>

View File

@@ -0,0 +1,254 @@
<template>
<div class="personal-center-page">
<!-- 页面头部 -->
<div class="page-header">
<h1>个人中心 - 修改账号密码</h1>
</div>
<!-- 修改密码表单 -->
<div class="password-form-section">
<a-card title="修改密码" :bordered="false">
<a-form
ref="passwordFormRef"
:model="passwordForm"
:rules="passwordFormRules"
layout="vertical"
@finish="handlePasswordSubmit"
>
<a-form-item label="登录账号" name="username">
<a-input
v-model:value="passwordForm.username"
disabled
placeholder="登录账号"
/>
</a-form-item>
<a-form-item label="旧密码" name="oldPassword">
<a-input-password
v-model:value="passwordForm.oldPassword"
placeholder="请输入旧密码"
:disabled="loading"
/>
</a-form-item>
<a-form-item label="新密码" name="newPassword">
<a-input-password
v-model:value="passwordForm.newPassword"
placeholder="请输入新密码"
:disabled="loading"
/>
</a-form-item>
<a-form-item label="确认密码" name="confirmPassword">
<a-input-password
v-model:value="passwordForm.confirmPassword"
placeholder="请再次输入新密码"
:disabled="loading"
/>
</a-form-item>
<a-form-item>
<a-space>
<a-button
type="primary"
html-type="submit"
:loading="loading"
size="large"
>
确认
</a-button>
<a-button
@click="handleReset"
:disabled="loading"
size="large"
>
重置
</a-button>
</a-space>
</a-form-item>
</a-form>
</a-card>
</div>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { message } from 'ant-design-vue'
import api from '@/utils/api'
// 响应式数据
const loading = ref(false)
const passwordFormRef = ref()
// 表单数据
const passwordForm = reactive({
username: '',
oldPassword: '',
newPassword: '',
confirmPassword: ''
})
// 表单验证规则
const passwordFormRules = {
oldPassword: [
{ required: true, message: '请输入旧密码', trigger: 'blur' }
],
newPassword: [
{ required: true, message: '请输入新密码', trigger: 'blur' },
{ min: 6, max: 20, message: '密码长度在6-20个字符', trigger: 'blur' },
{
pattern: /^(?=.*[a-zA-Z])(?=.*\d)[a-zA-Z\d@$!%*?&]{6,}$/,
message: '密码必须包含字母和数字',
trigger: 'blur'
}
],
confirmPassword: [
{ required: true, message: '请确认新密码', trigger: 'blur' },
{
validator: (rule, value) => {
if (value !== passwordForm.newPassword) {
return Promise.reject('两次输入的密码不一致')
}
return Promise.resolve()
},
trigger: 'blur'
}
]
}
// 获取当前用户信息
const fetchUserInfo = async () => {
try {
const response = await api.auth.getCurrentUser()
if (response.success) {
passwordForm.username = response.data.username || response.data.phone || '15004901368'
}
} catch (error) {
console.error('获取用户信息失败:', error)
// 使用默认值
passwordForm.username = '15004901368'
}
}
// 提交修改密码
const handlePasswordSubmit = async () => {
try {
await passwordFormRef.value.validate()
loading.value = true
const response = await api.auth.changePassword({
oldPassword: passwordForm.oldPassword,
newPassword: passwordForm.newPassword
})
if (response.success) {
message.success('密码修改成功')
handleReset()
} else {
message.error(response.message || '密码修改失败')
}
} catch (error) {
console.error('修改密码失败:', error)
if (error.message) {
message.error(error.message)
} else {
message.error('密码修改失败')
}
} finally {
loading.value = false
}
}
// 重置表单
const handleReset = () => {
passwordFormRef.value?.resetFields()
passwordForm.oldPassword = ''
passwordForm.newPassword = ''
passwordForm.confirmPassword = ''
}
// 生命周期
onMounted(() => {
fetchUserInfo()
})
</script>
<style scoped>
.personal-center-page {
padding: 24px;
background-color: #f0f2f5;
min-height: calc(100vh - 134px);
}
.page-header {
background-color: #fff;
padding: 24px;
border-radius: 8px;
margin-bottom: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.page-header h1 {
font-size: 28px;
color: #333;
margin: 0;
}
.password-form-section {
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
overflow: hidden;
}
:deep(.ant-card-head) {
background-color: #fafafa;
border-bottom: 1px solid #f0f0f0;
}
:deep(.ant-card-head-title) {
font-size: 18px;
font-weight: 600;
color: #333;
}
:deep(.ant-form-item-label > label) {
font-weight: 500;
color: #333;
}
:deep(.ant-input) {
border-radius: 6px;
}
:deep(.ant-btn) {
border-radius: 6px;
font-weight: 500;
}
:deep(.ant-btn-primary) {
background-color: #1890ff;
border-color: #1890ff;
}
:deep(.ant-btn-primary:hover) {
background-color: #40a9ff;
border-color: #40a9ff;
}
/* 响应式调整 */
@media (max-width: 768px) {
.personal-center-page {
padding: 16px;
}
.page-header {
padding: 16px;
}
.page-header h1 {
font-size: 24px;
}
}
</style>

View File

@@ -183,6 +183,7 @@ import {
SafetyOutlined,
BellOutlined
} from '@ant-design/icons-vue'
import api from '@/utils/api'
// 响应式数据
const editModalVisible = ref(false)
@@ -298,7 +299,7 @@ const handleEditCancel = () => {
editModalVisible.value = false
}
const handlePasswordSubmit = () => {
const handlePasswordSubmit = async () => {
if (!passwordForm.value.oldPassword || !passwordForm.value.newPassword || !passwordForm.value.confirmPassword) {
message.error('请填写完整信息')
return
@@ -314,8 +315,28 @@ const handlePasswordSubmit = () => {
return
}
passwordModalVisible.value = false
message.success('密码修改成功')
try {
const response = await api.auth.changePassword({
oldPassword: passwordForm.value.oldPassword,
newPassword: passwordForm.value.newPassword
})
if (response.success) {
passwordModalVisible.value = false
message.success('密码修改成功')
// 重置表单
passwordForm.value = {
oldPassword: '',
newPassword: '',
confirmPassword: ''
}
} else {
message.error(response.message || '密码修改失败')
}
} catch (error) {
console.error('修改密码失败:', error)
message.error('密码修改失败')
}
}
const handlePasswordCancel = () => {
@@ -350,9 +371,36 @@ const getActivityColor = (type) => {
return colors[type] || 'default'
}
// 获取用户信息
const fetchUserInfo = async () => {
try {
const response = await api.auth.getCurrentUser()
if (response.success) {
const user = response.data
userInfo.value = {
name: user.real_name || user.username || '用户',
username: user.username || '15004901368',
email: user.email || '',
phone: user.phone || '15004901368',
employeeId: user.employee_id || 'EMP001',
position: user.position || '员工',
department: user.department || '银行',
status: user.status || 'active',
hireDate: user.created_at ? new Date(user.created_at).toLocaleDateString() : '2020-03-15',
lastLogin: user.last_login ? new Date(user.last_login).toLocaleString() : '2024-01-18 09:30:00',
avatar: user.avatar || null,
bio: user.bio || '银行员工'
}
}
} catch (error) {
console.error('获取用户信息失败:', error)
// 使用默认值
}
}
// 生命周期
onMounted(() => {
// 初始化数据
fetchUserInfo()
})
</script>

View File

@@ -0,0 +1,242 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>个人中心功能测试</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.container {
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
color: #333;
text-align: center;
margin-bottom: 30px;
}
.test-section {
margin-bottom: 30px;
padding: 20px;
border: 1px solid #e0e0e0;
border-radius: 6px;
}
.test-section h2 {
color: #1890ff;
margin-top: 0;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input[type="text"], input[type="password"] {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
}
button {
background-color: #1890ff;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
margin-right: 10px;
}
button:hover {
background-color: #40a9ff;
}
.result {
margin-top: 15px;
padding: 10px;
border-radius: 4px;
white-space: pre-wrap;
}
.success {
background-color: #f6ffed;
border: 1px solid #b7eb8f;
color: #52c41a;
}
.error {
background-color: #fff2f0;
border: 1px solid #ffccc7;
color: #ff4d4f;
}
.info {
background-color: #e6f7ff;
border: 1px solid #91d5ff;
color: #1890ff;
}
</style>
</head>
<body>
<div class="container">
<h1>个人中心功能测试</h1>
<!-- 登录测试 -->
<div class="test-section">
<h2>1. 用户登录测试</h2>
<div class="form-group">
<label>用户名:</label>
<input type="text" id="loginUsername" value="admin" placeholder="请输入用户名">
</div>
<div class="form-group">
<label>密码:</label>
<input type="password" id="loginPassword" value="NewPassword123" placeholder="请输入密码">
</div>
<button onclick="testLogin()">登录</button>
<div id="loginResult" class="result" style="display: none;"></div>
</div>
<!-- 获取用户信息测试 -->
<div class="test-section">
<h2>2. 获取用户信息测试</h2>
<button onclick="testGetUserInfo()">获取用户信息</button>
<div id="userInfoResult" class="result" style="display: none;"></div>
</div>
<!-- 修改密码测试 -->
<div class="test-section">
<h2>3. 修改密码测试</h2>
<div class="form-group">
<label>旧密码:</label>
<input type="password" id="oldPassword" value="NewPassword123" placeholder="请输入旧密码">
</div>
<div class="form-group">
<label>新密码:</label>
<input type="password" id="newPassword" value="Admin123456" placeholder="请输入新密码">
</div>
<button onclick="testChangePassword()">修改密码</button>
<div id="changePasswordResult" class="result" style="display: none;"></div>
</div>
<!-- 测试说明 -->
<div class="test-section">
<h2>测试说明</h2>
<div class="info">
<strong>测试账号:</strong><br>
• 管理员: admin / NewPassword123 (已修改)<br>
• 经理: manager001 / Manager123456<br>
• 员工: employee001 / Employee123456<br><br>
<strong>测试步骤:</strong><br>
1. 先使用管理员账号登录<br>
2. 获取用户信息验证API<br>
3. 测试修改密码功能<br>
4. 重新登录验证密码修改是否生效
</div>
</div>
</div>
<script>
const API_BASE = 'http://localhost:5351/api';
let authToken = '';
// 显示结果
function showResult(elementId, message, type = 'info') {
const element = document.getElementById(elementId);
element.style.display = 'block';
element.className = `result ${type}`;
element.textContent = message;
}
// 登录测试
async function testLogin() {
const username = document.getElementById('loginUsername').value;
const password = document.getElementById('loginPassword').value;
try {
const response = await fetch(`${API_BASE}/auth/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username, password }),
});
const data = await response.json();
if (data.success) {
authToken = data.data.token;
showResult('loginResult', `登录成功!\nToken: ${authToken.substring(0, 50)}...`, 'success');
} else {
showResult('loginResult', `登录失败: ${data.message}`, 'error');
}
} catch (error) {
showResult('loginResult', `请求失败: ${error.message}`, 'error');
}
}
// 获取用户信息测试
async function testGetUserInfo() {
if (!authToken) {
showResult('userInfoResult', '请先登录', 'error');
return;
}
try {
const response = await fetch(`${API_BASE}/auth/me`, {
headers: {
'Authorization': `Bearer ${authToken}`,
},
});
const data = await response.json();
if (data.success) {
showResult('userInfoResult', `获取用户信息成功!\n${JSON.stringify(data.data, null, 2)}`, 'success');
} else {
showResult('userInfoResult', `获取失败: ${data.message}`, 'error');
}
} catch (error) {
showResult('userInfoResult', `请求失败: ${error.message}`, 'error');
}
}
// 修改密码测试
async function testChangePassword() {
if (!authToken) {
showResult('changePasswordResult', '请先登录', 'error');
return;
}
const oldPassword = document.getElementById('oldPassword').value;
const newPassword = document.getElementById('newPassword').value;
try {
const response = await fetch(`${API_BASE}/auth/change-password`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${authToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ oldPassword, newPassword }),
});
const data = await response.json();
if (data.success) {
showResult('changePasswordResult', `密码修改成功!\n${data.message}`, 'success');
} else {
showResult('changePasswordResult', `修改失败: ${data.message}`, 'error');
}
} catch (error) {
showResult('changePasswordResult', `请求失败: ${error.message}`, 'error');
}
}
</script>
</body>
</html>

View File

@@ -4,7 +4,7 @@ import axios from 'axios'
// 创建axios实例
const instance = axios.create({
baseURL: '/api',
baseURL: 'http://localhost:5352/api',
timeout: 10000,
headers: {
'Content-Type': 'application/json'
@@ -43,26 +43,40 @@ instance.interceptors.response.use(
const userStore = useUserStore()
userStore.logout()
window.location.href = '/login'
message.error('登录已过期,请重新登录')
import('ant-design-vue').then(({ message }) => {
message.error('登录已过期,请重新登录')
})
break
case 403:
message.error('没有权限执行此操作')
import('ant-design-vue').then(({ message }) => {
message.error('没有权限执行此操作')
})
break
case 404:
message.error('请求的资源不存在')
import('ant-design-vue').then(({ message }) => {
message.error('请求的资源不存在')
})
break
case 500:
message.error('服务器内部错误')
import('ant-design-vue').then(({ message }) => {
message.error('服务器内部错误')
})
break
default:
message.error(error.response.data.message || '请求失败')
import('ant-design-vue').then(({ message }) => {
message.error(error.response.data.message || '请求失败')
})
}
} else if (error.request) {
// 请求发出但没有收到响应
message.error('网络错误,请检查网络连接')
import('ant-design-vue').then(({ message }) => {
message.error('网络错误,请检查网络连接')
})
} else {
// 请求配置出错
message.error('请求配置错误')
import('ant-design-vue').then(({ message }) => {
message.error('请求配置错误')
})
}
return Promise.reject(error)
}
@@ -217,6 +231,35 @@ const api = {
updateSettings: (data) => instance.put('/system/settings', data),
// 获取日志列表
getLogs: (params) => instance.get('/system/logs', { params })
},
// 政府管理相关API
government: {
// 行政人员管理
adminStaff: {
// 获取行政人员列表
getList: (params) => instance.get('/government/admin-staff', { params }),
// 创建行政人员
create: (data) => instance.post('/government/admin-staff', data),
// 更新行政人员
update: (id, data) => instance.put(`/government/admin-staff/${id}`, data),
// 删除行政人员
delete: (id) => instance.delete(`/government/admin-staff/${id}`),
// 重置行政人员密码
resetPassword: (id) => instance.post(`/government/admin-staff/${id}/reset-password`)
},
// 部门管理
departments: {
// 获取部门列表
getList: (params) => instance.get('/government/departments', { params })
},
// 岗位管理
positions: {
// 获取岗位列表
getList: (params) => instance.get('/government/positions', { params })
}
}
}

View File

@@ -1,130 +1,90 @@
<template>
<div class="admin-department-container">
<div class="header">
<div class="title">部门防疫部门</div>
<div class="actions">
<a-button type="primary" class="action-btn">删除部门</a-button>
<a-button type="primary" class="action-btn">新增岗位</a-button>
<template v-for="dept in departments" :key="dept.id">
<div class="header">
<div class="title">部门{{ dept.name }}</div>
<div class="actions">
<a-button type="primary" danger @click="handleDeleteDepartment(dept.id)">删除部门</a-button>
<a-button type="primary" @click="showAddPositionModal(dept.id, dept.name)">新增岗位</a-button>
</div>
</div>
</div>
<a-table :columns="columns" :data-source="departmentData" :pagination="false" class="department-table">
<template #headerCell="{ column }">
<span>{{ column.title }}</span>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'permission'">
<a-tag v-if="record.permission === '已设置权限'" color="success">{{ record.permission }}</a-tag>
<a-tag v-else color="warning">{{ record.permission }}</a-tag>
<a-table :columns="columns" :data-source="formatPositionsData(dept.positions)" :pagination="false" class="department-table">
<template #headerCell="{ column }">
<span>{{ column.title }}</span>
</template>
<template v-if="column.key === 'action'">
<a-space>
<a-button type="link" @click="editPosition(record)">编辑岗位名称</a-button>
<a-button type="link" @click="deletePosition(record)">删除岗位</a-button>
<a-button type="link" @click="setPermission(record)">设置权限</a-button>
</a-space>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'permission'">
<a-tag v-if="record.permission === '已设置权限'" color="success">{{ record.permission }}</a-tag>
<a-tag v-else color="warning">{{ record.permission }}</a-tag>
</template>
<template v-if="column.key === 'action'">
<a-space>
<a-button type="link" @click="showEditPositionModal(record)">编辑岗位名称</a-button>
<a-button type="link" danger @click="handleDeletePosition(record.id)">删除岗位</a-button>
<a-button type="link" @click="showSetPermissionModal(record)">设置权限</a-button>
</a-space>
</template>
</template>
</template>
</a-table>
<div class="header">
<div class="title">部门检疫部门</div>
<div class="actions">
<a-button type="primary" class="action-btn">删除部门</a-button>
<a-button type="primary" class="action-btn">新增岗位</a-button>
</div>
</div>
<a-table :columns="columns" :data-source="inspectionData" :pagination="false" class="department-table">
<template #headerCell="{ column }">
<span>{{ column.title }}</span>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'permission'">
<a-tag v-if="record.permission === '已设置权限'" color="success">{{ record.permission }}</a-tag>
<a-tag v-else color="warning">{{ record.permission }}</a-tag>
</template>
<template v-if="column.key === 'action'">
<a-space>
<a-button type="link" @click="editPosition(record)">编辑岗位名称</a-button>
<a-button type="link" @click="deletePosition(record)">删除岗位</a-button>
<a-button type="link" @click="setPermission(record)">设置权限</a-button>
</a-space>
</template>
</template>
</a-table>
<div class="header">
<div class="title">部门冷配管理部门</div>
<div class="actions">
<a-button type="primary" class="action-btn">删除部门</a-button>
<a-button type="primary" class="action-btn">新增岗位</a-button>
</div>
</div>
<a-table :columns="columns" :data-source="coldChainData" :pagination="false" class="department-table">
<template #headerCell="{ column }">
<span>{{ column.title }}</span>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'permission'">
<a-tag v-if="record.permission === '已设置权限'" color="success">{{ record.permission }}</a-tag>
<a-tag v-else color="warning">{{ record.permission }}</a-tag>
</template>
<template v-if="column.key === 'action'">
<a-space>
<a-button type="link" @click="editPosition(record)">编辑岗位名称</a-button>
<a-button type="link" @click="deletePosition(record)">删除岗位</a-button>
<a-button type="link" @click="setPermission(record)">设置权限</a-button>
</a-space>
</template>
</template>
</a-table>
<div class="header">
<div class="title">部门测试部门</div>
<div class="actions">
<a-button type="primary" class="action-btn">删除部门</a-button>
<a-button type="primary" class="action-btn">新增岗位</a-button>
</div>
</div>
<a-table :columns="columns" :data-source="testingData" :pagination="false" class="department-table">
<template #headerCell="{ column }">
<span>{{ column.title }}</span>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'permission'">
<a-tag v-if="record.permission === '已设置权限'" color="success">{{ record.permission }}</a-tag>
<a-tag v-else color="warning">{{ record.permission }}</a-tag>
</template>
<template v-if="column.key === 'action'">
<a-space>
<a-button type="link" @click="editPosition(record)">编辑岗位名称</a-button>
<a-button type="link" @click="deletePosition(record)">删除岗位</a-button>
<a-button type="link" @click="setPermission(record)">设置权限</a-button>
</a-space>
</template>
</template>
</a-table>
</a-table>
</template>
<div class="add-department">
<a-button type="primary" @click="showAddDepartmentModal">新增部门</a-button>
</div>
<!-- 新增部门模态框 -->
<a-modal v-model:visible="addDepartmentModalVisible" title="新增部门" @ok="handleAddDepartment" @cancel="handleCancel">
<a-form-model ref="addDepartmentForm" :model="addDepartmentFormData" :rules="addDepartmentRules">
<a-form-model-item label="部门名称" name="name">
<a-input v-model="addDepartmentFormData.name" placeholder="请输入部门名称" />
</a-form-model-item>
<a-form-model-item label="部门描述" name="description">
<a-textarea v-model="addDepartmentFormData.description" placeholder="请输入部门描述" :rows="3" />
</a-form-model-item>
</a-form-model>
</a-modal>
<!-- 新增岗位模态框 -->
<a-modal v-model:visible="addPositionModalVisible" title="新增岗位" @ok="handleAddPosition" @cancel="handleCancel">
<a-form-model ref="addPositionForm" :model="addPositionFormData" :rules="addPositionRules">
<a-form-model-item label="所属部门" name="department_name">
<a-input v-model="addPositionFormData.department_name" disabled />
</a-form-model-item>
<a-form-model-item label="岗位名称" name="name">
<a-input v-model="addPositionFormData.name" placeholder="请输入岗位名称" />
</a-form-model-item>
</a-form-model>
</a-modal>
<!-- 编辑岗位模态框 -->
<a-modal v-model:visible="editPositionModalVisible" title="编辑岗位" @ok="handleEditPosition" @cancel="handleCancel">
<a-form-model ref="editPositionForm" :model="editPositionFormData" :rules="editPositionRules">
<a-form-model-item label="岗位名称" name="name">
<a-input v-model="editPositionFormData.name" placeholder="请输入岗位名称" />
</a-form-model-item>
</a-form-model>
</a-modal>
<!-- 设置权限模态框 -->
<a-modal v-model:visible="setPermissionModalVisible" title="设置权限" @ok="handleSetPermission" @cancel="handleCancel" :width="600">
<div>
<p>岗位: {{ currentPosition?.name }}</p>
<div style="margin-top: 16px;">
<p>权限设置示例</p>
<a-alert type="info" message="实际项目中,这里应该是具体的权限选择界面" />
</div>
</div>
</a-modal>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { ref, reactive, onMounted } from 'vue'
import { message } from 'ant-design-vue'
import axios from 'axios'
// 表格列定义
const columns = [
@@ -144,66 +104,212 @@ const columns = [
}
]
// 防疫部门数据
const departmentData = ref([
{
key: '1',
position: '防疫员',
permission: '已设置权限',
},
{
key: '2',
position: '防疫管理员',
permission: '已设置权限',
}
])
// 部门数据
const departments = ref([])
// 检疫部门数据
const inspectionData = ref([
{
key: '1',
position: '检疫员',
permission: '未设置权限',
}
])
// 当前选中的岗位
const currentPosition = ref(null)
// 冷配管理部门数据
const coldChainData = ref([
{
key: '1',
position: '冷配员',
permission: '已设置权限',
}
])
// 模态框控制
const addDepartmentModalVisible = ref(false)
const addPositionModalVisible = ref(false)
const editPositionModalVisible = ref(false)
const setPermissionModalVisible = ref(false)
// 测试部门数据
const testingData = ref([
{
key: '1',
position: '内部测试账号',
permission: '已设置权限',
}
])
// 表单数据
const addDepartmentFormData = reactive({
name: '',
description: ''
})
// 编辑岗位
const editPosition = (record) => {
message.info(`编辑岗位:${record.position}`)
const addPositionFormData = reactive({
department_id: '',
department_name: '',
name: ''
})
const editPositionFormData = reactive({
id: '',
name: ''
})
// 表单验证规则
const addDepartmentRules = {
name: [
{ required: true, message: '请输入部门名称', trigger: 'blur' }
]
}
// 删除岗位
const deletePosition = (record) => {
message.info(`删除岗位:${record.position}`)
const addPositionRules = {
name: [
{ required: true, message: '请输入岗位名称', trigger: 'blur' }
]
}
// 设置权限
const setPermission = (record) => {
message.info(`设置权限:${record.position}`)
const editPositionRules = {
name: [
{ required: true, message: '请输入岗位名称', trigger: 'blur' }
]
}
// 从API获取部门数据
const fetchDepartments = async () => {
try {
const response = await axios.get('/api/government/departments')
departments.value = response.data
} catch (error) {
message.error('获取部门数据失败:' + error.message)
console.error('获取部门数据失败:', error)
}
}
// 格式化岗位数据
const formatPositionsData = (positions) => {
return positions.map(pos => ({
key: pos.id,
id: pos.id,
position: pos.name,
permission: pos.permission
}))
}
// 显示新增部门模态框
const showAddDepartmentModal = () => {
message.info('显示新增部门对话框')
// 重置表单
Object.keys(addDepartmentFormData).forEach(key => {
addDepartmentFormData[key] = ''
})
addDepartmentModalVisible.value = true
}
// 显示新增岗位模态框
const showAddPositionModal = (departmentId, departmentName) => {
// 重置表单
Object.keys(addPositionFormData).forEach(key => {
addPositionFormData[key] = ''
})
addPositionFormData.department_id = departmentId
addPositionFormData.department_name = departmentName
addPositionModalVisible.value = true
}
// 显示编辑岗位模态框
const showEditPositionModal = (record) => {
editPositionFormData.id = record.id
editPositionFormData.name = record.position
editPositionModalVisible.value = true
}
// 显示设置权限模态框
const showSetPermissionModal = (record) => {
currentPosition.value = record
setPermissionModalVisible.value = true
}
// 新增部门
const handleAddDepartment = async () => {
try {
await axios.post('/api/government/departments', {
name: addDepartmentFormData.name,
description: addDepartmentFormData.description
})
message.success('部门创建成功')
addDepartmentModalVisible.value = false
// 重新获取数据
await fetchDepartments()
} catch (error) {
message.error('创建部门失败:' + error.message)
console.error('创建部门失败:', error)
}
}
// 新增岗位
const handleAddPosition = async () => {
try {
await axios.post(`/api/government/departments/${addPositionFormData.department_id}/positions`, {
department_id: addPositionFormData.department_id,
name: addPositionFormData.name
})
message.success('岗位创建成功')
addPositionModalVisible.value = false
// 重新获取数据
await fetchDepartments()
} catch (error) {
message.error('创建岗位失败:' + error.message)
console.error('创建岗位失败:', error)
}
}
// 编辑岗位
const handleEditPosition = async () => {
try {
await axios.put(`/api/government/positions/${editPositionFormData.id}`, {
name: editPositionFormData.name
})
message.success('岗位编辑成功')
editPositionModalVisible.value = false
// 重新获取数据
await fetchDepartments()
} catch (error) {
message.error('编辑岗位失败:' + error.message)
console.error('编辑岗位失败:', error)
}
}
// 删除岗位
const handleDeletePosition = async (positionId) => {
try {
await axios.delete(`/api/government/positions/${positionId}`)
message.success('岗位删除成功')
// 重新获取数据
await fetchDepartments()
} catch (error) {
message.error('删除岗位失败:' + error.message)
console.error('删除岗位失败:', error)
}
}
// 删除部门
const handleDeleteDepartment = async (departmentId) => {
try {
await axios.delete(`/api/government/departments/${departmentId}`)
message.success('部门删除成功')
// 重新获取数据
await fetchDepartments()
} catch (error) {
message.error('删除部门失败:' + error.message)
console.error('删除部门失败:', error)
}
}
// 设置权限
const handleSetPermission = async () => {
try {
await axios.post(`/api/government/positions/${currentPosition.value.id}/permission`, {
permission_details: { basic: ['view', 'edit', 'add'], advanced: ['delete'] }
})
message.success('权限设置成功')
setPermissionModalVisible.value = false
// 重新获取数据
await fetchDepartments()
} catch (error) {
message.error('设置权限失败:' + error.message)
console.error('设置权限失败:', error)
}
}
// 取消操作
const handleCancel = () => {
addDepartmentModalVisible.value = false
addPositionModalVisible.value = false
editPositionModalVisible.value = false
setPermissionModalVisible.value = false
}
// 组件挂载时获取数据
onMounted(() => {
fetchDepartments()
})
</script>
<style scoped>

View File

@@ -1,7 +1,7 @@
<template>
<div class="admin-staff-container">
<div class="header">
<a-button type="primary">新增人员</a-button>
<a-button type="primary" @click="showCreateModal">新增人员</a-button>
<a-input-search
v-model:value="searchValue"
placeholder="请输入员工姓名"
@@ -13,27 +13,106 @@
<a-table :columns="columns" :data-source="staffData" :pagination="pagination" class="staff-table">
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'status'">
<a-switch v-model:checked="record.status" />
<a-switch v-model:checked="record.status" @change="updateStaffStatus(record)" />
</template>
<template v-if="column.key === 'action'">
<a-space>
<a-button type="link" @click="editStaff(record)">编辑</a-button>
<a-button type="link" @click="resetPassword(record)">修改密码</a-button>
<a-button type="link" @click="showEditModal(record)">编辑</a-button>
<a-button type="link" @click="handleResetPassword(record)">修改密码</a-button>
<a-button type="link" danger @click="deleteStaff(record)">删除</a-button>
</a-space>
</template>
</template>
</a-table>
<!-- 新增/编辑模态框 -->
<a-modal
v-model:open="isModalOpen"
:title="modalTitle"
@ok="handleModalOk"
@cancel="handleModalCancel"
>
<a-form :model="formData" layout="vertical">
<a-form-item label="员工姓名">
<a-input v-model:value="formData.name" placeholder="请输入员工姓名" />
</a-form-item>
<a-form-item label="所属部门">
<a-select v-model:value="formData.department_id" placeholder="请选择部门">
<a-select-option v-for="dept in departments" :key="dept.id" :value="dept.id">
{{ dept.name }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="任职岗位">
<a-select v-model:value="formData.position_id" placeholder="请选择岗位">
<a-select-option v-for="pos in positions" :key="pos.id" :value="pos.id">
{{ pos.name }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="联系电话">
<a-input v-model:value="formData.phone" placeholder="请输入联系电话" />
</a-form-item>
<a-form-item label="身份证号码">
<a-input v-model:value="formData.id_card" placeholder="请输入身份证号码" />
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { message } from 'ant-design-vue'
import { ref, reactive, onMounted } from 'vue'
import { message, Modal } from 'ant-design-vue'
import api from '@/utils/api'
// 搜索值
const searchValue = ref('')
// 员工数据
const staffData = ref([])
// 部门列表
const departments = ref([])
// 岗位列表
const positions = ref([])
// 分页配置
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showTotal: (total) => `${total} 条记录`,
onChange: (page, pageSize) => {
pagination.current = page
pagination.pageSize = pageSize
fetchAdminStaffList()
},
onShowSizeChange: (current, pageSize) => {
pagination.current = 1
pagination.pageSize = pageSize
fetchAdminStaffList()
}
})
// 模态框相关
const isModalOpen = ref(false)
const modalTitle = ref('新增行政人员')
const currentStaffId = ref(null)
// 表单数据
const formData = reactive({
name: '',
department_id: '',
position_id: '',
phone: '',
id_card: '',
status: true
})
// 表格列定义
const columns = [
{
@@ -58,8 +137,8 @@ const columns = [
},
{
title: '身份证号码',
dataIndex: 'idCard',
key: 'idCard',
dataIndex: 'id_card',
key: 'id_card',
},
{
title: '账号使用状态',
@@ -68,9 +147,9 @@ const columns = [
},
{
title: '创建时间',
dataIndex: 'createdAt',
key: 'createdAt',
sorter: (a, b) => new Date(a.createdAt) - new Date(b.createdAt),
dataIndex: 'created_at',
key: 'created_at',
sorter: (a, b) => new Date(a.created_at) - new Date(b.created_at),
},
{
title: '操作',
@@ -78,173 +157,166 @@ const columns = [
}
]
// 员工数据
const staffData = ref([
{
key: '1',
name: '内部测试账号',
department: '测试部门',
position: '内部测试账号',
phone: '187****4778',
idCard: '--',
status: true,
createdAt: '2023-11-24 17:24:32'
},
{
key: '2',
name: '扎拉嘎',
department: '冷配管理部门',
position: '冷配员',
phone: '195****9912',
idCard: '--',
status: true,
createdAt: '2023-10-10 13:59:03'
},
{
key: '3',
name: '董立鹏',
department: '防疫部',
position: '防疫管理员',
phone: '151****7022',
idCard: '--',
status: true,
createdAt: '2023-05-19 15:47:54'
},
{
key: '4',
name: '李海涛',
department: '防疫部',
position: '防疫管理员',
phone: '139****6955',
idCard: '--',
status: true,
createdAt: '2023-05-09 14:29:15'
},
{
key: '5',
name: '高凤鸣',
department: '防疫部',
position: '防疫管理员',
phone: '158****4601',
idCard: '--',
status: true,
createdAt: '2023-05-09 14:28:57'
},
{
key: '6',
name: '刘全',
department: '防疫部',
position: '防疫管理员',
phone: '139****6009',
idCard: '--',
status: true,
createdAt: '2023-05-09 14:28:39'
},
{
key: '7',
name: '李志伟',
department: '防疫部',
position: '防疫管理员',
phone: '153****0457',
idCard: '--',
status: true,
createdAt: '2023-05-09 14:28:14'
},
{
key: '8',
name: '李景学',
department: '防疫部',
position: '防疫管理员',
phone: '153****5588',
idCard: '--',
status: true,
createdAt: '2023-05-09 14:27:57'
},
{
key: '9',
name: '王海龙',
department: '防疫部',
position: '防疫管理员',
phone: '150****7222',
idCard: '--',
status: true,
createdAt: '2023-05-09 14:27:39'
},
{
key: '10',
name: '德力根',
department: '防疫部',
position: '防疫管理员',
phone: '150****2938',
idCard: '--',
status: true,
createdAt: '2023-05-09 14:27:22'
},
{
key: '11',
name: '巴日斯',
department: '防疫部',
position: '防疫管理员',
phone: '131****1366',
idCard: '--',
status: true,
createdAt: '2023-05-09 14:27:03'
},
{
key: '12',
name: '邵志勇',
department: '防疫部',
position: '防疫管理员',
phone: '139****0778',
idCard: '--',
status: true,
createdAt: '2023-05-09 14:26:26'
},
{
key: '13',
name: '张日林',
department: '防疫部',
position: '防疫管理员',
phone: '156****6300',
idCard: '--',
status: true,
createdAt: '2023-05-09 14:25:37'
},
{
key: '14',
name: '李燕',
department: '防疫部',
position: '防疫管理员',
phone: '151****1613',
idCard: '--',
status: true,
createdAt: '2023-05-09 14:25:12'
// 获取行政人员列表
const fetchAdminStaffList = async () => {
try {
const params = {
page: pagination.current,
pageSize: pagination.pageSize,
search: searchValue.value
}
const response = await api.government.adminStaff.getList(params)
staffData.value = response.data.map(item => ({
...item,
department: departments.value.find(dept => dept.id === item.department_id)?.name || '',
position: positions.value.find(pos => pos.id === item.position_id)?.name || '',
key: item.id
}))
pagination.total = response.total
} catch (error) {
message.error('获取行政人员列表失败')
console.error('获取行政人员列表失败:', error)
}
])
}
// 分页配置
const pagination = reactive({
current: 1,
pageSize: 10,
total: staffData.value.length,
showSizeChanger: true,
showTotal: (total) => `${total} 条记录`
})
// 获取部门列表
const fetchDepartments = async () => {
try {
const response = await api.government.departments.getList()
departments.value = response.data
} catch (error) {
message.error('获取部门列表失败')
console.error('获取部门列表失败:', error)
}
}
// 获取岗位列表
const fetchPositions = async () => {
try {
const response = await api.government.positions.getList()
positions.value = response.data
} catch (error) {
message.error('获取岗位列表失败')
console.error('获取岗位列表失败:', error)
}
}
// 初始化数据
const initData = async () => {
await fetchDepartments()
await fetchPositions()
await fetchAdminStaffList()
}
// 搜索处理
const onSearch = (value) => {
message.info(`搜索: ${value}`)
searchValue.value = value
pagination.current = 1
fetchAdminStaffList()
}
// 编辑员工
const editStaff = (record) => {
message.info(`编辑员工: ${record.name}`)
// 显示新增模态框
const showCreateModal = () => {
currentStaffId.value = null
modalTitle.value = '新增行政人员'
Object.keys(formData).forEach(key => {
formData[key] = ''
})
formData.status = true
isModalOpen.value = true
}
// 重置密码
const resetPassword = (record) => {
message.info(`重置密码: ${record.name}`)
// 显示编辑模态框
const showEditModal = (record) => {
currentStaffId.value = record.id
modalTitle.value = '编辑行政人员'
formData.name = record.name
formData.department_id = record.department_id
formData.position_id = record.position_id
formData.phone = record.phone
formData.id_card = record.id_card
formData.status = record.status
isModalOpen.value = true
}
// 处理模态框确认
const handleModalOk = async () => {
try {
if (currentStaffId.value) {
// 编辑
await api.government.adminStaff.update(currentStaffId.value, formData)
message.success('编辑行政人员成功')
} else {
// 新增
await api.government.adminStaff.create(formData)
message.success('新增行政人员成功')
}
isModalOpen.value = false
fetchAdminStaffList()
} catch (error) {
message.error(currentStaffId.value ? '编辑行政人员失败' : '新增行政人员失败')
console.error('操作行政人员失败:', error)
}
}
// 处理模态框取消
const handleModalCancel = () => {
isModalOpen.value = false
}
// 更新员工状态
const updateStaffStatus = async (record) => {
try {
await api.government.adminStaff.update(record.id, {
status: record.status
})
message.success('更新状态成功')
} catch (error) {
record.status = !record.status // 回滚状态
message.error('更新状态失败')
console.error('更新状态失败:', error)
}
}
// 处理重置密码
const handleResetPassword = (record) => {
Modal.confirm({
title: '确认重置密码',
content: `确定要重置 ${record.name} 的密码吗?重置后密码将变为 123456`,
onOk: async () => {
try {
await api.government.adminStaff.resetPassword(record.id)
message.success('重置密码成功')
} catch (error) {
message.error('重置密码失败')
console.error('重置密码失败:', error)
}
}
})
}
// 删除员工
const deleteStaff = (record) => {
Modal.confirm({
title: '确认删除',
content: `确定要删除 ${record.name} 吗?`,
onOk: async () => {
try {
await api.government.adminStaff.delete(record.id)
message.success('删除成功')
fetchAdminStaffList()
} catch (error) {
message.error('删除失败')
console.error('删除失败:', error)
}
}
})
}
// 组件挂载时初始化数据
onMounted(() => {
initData()
})
</script>
<style scoped>

View File

@@ -110,43 +110,13 @@
<div class="update-section">
<a-card class="update-card" title="数据更新信息">
<a-list>
<a-list-item>
<a-list-item v-for="(item, index) in updates" :key="index">
<div class="update-item">
<div class="update-icon blue"></div>
<div class="update-content">
<div class="update-title">市场行情数据更新</div>
<div class="update-time">2024-04-10 15:30:00</div>
<div class="update-desc">更新全国主要地区牛肉牛奶饲料价格数据</div>
</div>
</div>
</a-list-item>
<a-list-item>
<div class="update-item">
<div class="update-icon green"></div>
<div class="update-content">
<div class="update-title">养殖户数据同步</div>
<div class="update-time">2024-04-10 10:15:00</div>
<div class="update-desc">新增56家养殖户信息更新32家养殖户状态</div>
</div>
</div>
</a-list-item>
<a-list-item>
<div class="update-item">
<div class="update-icon orange"></div>
<div class="update-content">
<div class="update-title">产品认证数据导入</div>
<div class="update-time">2024-04-09 16:45:00</div>
<div class="update-desc">导入120条生资产品认证信息</div>
</div>
</div>
</a-list-item>
<a-list-item>
<div class="update-item">
<div class="update-icon red"></div>
<div class="update-content">
<div class="update-title">疫病监测数据更新</div>
<div class="update-time">2024-04-09 09:20:00</div>
<div class="update-desc">更新本周疫病监测数据暂无异常情况</div>
<div class="update-title">数据更新</div>
<div class="update-time">{{ item.time }}</div>
<div class="update-desc">{{ item.content }}</div>
</div>
</div>
</a-list-item>
@@ -157,14 +127,22 @@
</template>
<script setup>
import { ref, onMounted } from 'vue'
import * as echarts from 'echarts'
import { ref, onMounted } from 'vue';
import * as echarts from 'echarts';
import axios from 'axios';
// 数据统计
const 养殖户总数 = ref('8,256')
const 存栏总量 = ref('426,831头')
const 交易量 = ref(3.26亿')
const 活跃用户 = ref('12,548')
const 养殖户总数 = ref('0')
const 存栏总量 = ref('0头')
const 交易量 = ref(0')
const 活跃用户 = ref('0')
const updates = ref([])
// 图表数据
const monthlyTrends = ref([])
const categoryDistribution = ref([])
const regionDistribution = ref([])
const scaleDistribution = ref([])
// 图表引用
const mapChartRef = ref(null)
@@ -174,7 +152,7 @@ const categoryChartRef = ref(null)
// 初始化交易量趋势图表
const initTransactionChart = () => {
if (!transactionChartRef.value) return
if (!transactionChartRef.value || !monthlyTrends.value.length) return
const chart = echarts.init(transactionChartRef.value)
@@ -191,7 +169,7 @@ const initTransactionChart = () => {
xAxis: {
type: 'category',
boundaryGap: false,
data: ['1月', '2月', '3月', '4月', '5月', '6月']
data: monthlyTrends.value.map(item => item.month)
},
yAxis: {
type: 'value',
@@ -211,7 +189,7 @@ const initTransactionChart = () => {
emphasis: {
focus: 'series'
},
data: [2.8, 3.1, 3.5, 3.26, 3.4, 3.6],
data: monthlyTrends.value.map(item => item.value / 10000),
itemStyle: {
color: '#3a4de9'
},
@@ -234,7 +212,7 @@ const initTransactionChart = () => {
// 初始化品类占比图表
const initCategoryChart = () => {
if (!categoryChartRef.value) return
if (!categoryChartRef.value || !categoryDistribution.value.length) return
const chart = echarts.init(categoryChartRef.value)
@@ -272,12 +250,7 @@ const initCategoryChart = () => {
labelLine: {
show: false
},
data: [
{ value: 45, name: '肉牛养殖', itemStyle: { color: '#3a4de9' } },
{ value: 30, name: '奶牛养殖', itemStyle: { color: '#4cd137' } },
{ value: 15, name: '犊牛养殖', itemStyle: { color: '#e1b12c' } },
{ value: 10, name: '其他养殖', itemStyle: { color: '#44bd32' } }
]
data: categoryDistribution.value
}
]
}
@@ -291,7 +264,7 @@ const initCategoryChart = () => {
// 初始化养殖区域分布图表
const initMapChart = () => {
if (!mapChartRef.value) return
if (!mapChartRef.value || !regionDistribution.value.length) return
const chart = echarts.init(mapChartRef.value)
@@ -303,7 +276,7 @@ const initMapChart = () => {
legend: {
bottom: '5%',
left: 'center',
data: ['东北地区', '华北地区', '华东地区', '华南地区', '西南地区', '西北地区']
data: regionDistribution.value.map(item => item.name)
},
series: [
{
@@ -330,14 +303,7 @@ const initMapChart = () => {
labelLine: {
show: false
},
data: [
{ value: 28, name: '东北地区', itemStyle: { color: '#3a4de9' } },
{ value: 22, name: '华北地区', itemStyle: { color: '#4cd137' } },
{ value: 18, name: '华东地区', itemStyle: { color: '#e1b12c' } },
{ value: 12, name: '华南地区', itemStyle: { color: '#44bd32' } },
{ value: 15, name: '西南地区', itemStyle: { color: '#0097e6' } },
{ value: 5, name: '西北地区', itemStyle: { color: '#8c7ae6' } }
]
data: regionDistribution.value
}
]
}
@@ -351,7 +317,7 @@ const initMapChart = () => {
// 初始化养殖规模分布图表
const initScaleChart = () => {
if (!scaleChartRef.value) return
if (!scaleChartRef.value || !scaleDistribution.value.length) return
const chart = echarts.init(scaleChartRef.value)
@@ -370,7 +336,7 @@ const initScaleChart = () => {
},
xAxis: {
type: 'category',
data: ['小型养殖场(50头)', '中型养殖场(50-200头)', '大型养殖场(200头)']
data: scaleDistribution.value.map(item => item.name)
},
yAxis: {
type: 'value',
@@ -381,7 +347,7 @@ const initScaleChart = () => {
name: '养殖场数量',
type: 'bar',
barWidth: '60%',
data: [6850, 1250, 156],
data: scaleDistribution.value.map(item => item.value),
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#3a4de9' },
@@ -399,15 +365,41 @@ const initScaleChart = () => {
})
}
// 组件挂载时初始化所有图表
// 从API获取数据
const fetchData = async () => {
try {
const response = await axios.get('/api/government/data-center');
const data = response.data;
// 更新统计数据
养殖户总数.value = data.totalFarmers.toLocaleString();
存栏总量.value = `${data.totalAnimals.toLocaleString()}`;
交易量.value = `¥${(data.totalTransactions / 10000).toFixed(2)}亿`;
活跃用户.value = data.activeUsers.toLocaleString();
// 更新图表数据
monthlyTrends.value = data.monthlyTrends;
categoryDistribution.value = data.categoryDistribution;
regionDistribution.value = data.regionDistribution;
scaleDistribution.value = data.scaleDistribution;
updates.value = data.updates;
// 初始化所有图表
setTimeout(() => {
initTransactionChart();
initCategoryChart();
initMapChart();
initScaleChart();
}, 100);
} catch (error) {
console.error('获取数据失败:', error);
}
}
// 组件挂载时获取数据并初始化图表
onMounted(() => {
setTimeout(() => {
initTransactionChart()
initCategoryChart()
initMapChart()
initScaleChart()
}, 100)
})
fetchData();
});
</script>
<style scoped>

View File

@@ -2,7 +2,7 @@
<div class="market-price-container">
<!-- 产品类型选项卡 -->
<div class="product-tabs">
<a-tabs v-model:activeKey="activeProductTab">
<a-tabs v-model:activeKey="activeProductTab" @change="handleTabChange">
<a-tab-pane key="beef" tab="育肥牛"></a-tab-pane>
<a-tab-pane key="dairy" tab="繁殖牛"></a-tab-pane>
<a-tab-pane key="milk" tab="奶牛"></a-tab-pane>
@@ -17,7 +17,7 @@
<template #title>
<div class="card-title">
<span>本市{{ productTypeText }}价格</span>
<span class="price-date">2025-01-01</span>
<span class="price-date">{{ priceDate }}</span>
</div>
</template>
<div class="price-info">
@@ -26,8 +26,12 @@
<span class="price-unit">/公斤</span>
</div>
<div class="price-change">
<span class="change-value">+0.30</span>
<span class="change-percent">+1.32%</span>
<span class="change-value" :style="{ color: isPriceIncrease ? '#52c41a' : '#ff4d4f' }">
{{ priceChange }}
</span>
<span class="change-percent" :style="{ color: isPriceIncrease ? '#52c41a' : '#ff4d4f' }">
{{ priceChangePercent }}
</span>
</div>
</div>
</a-card>
@@ -50,12 +54,30 @@
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { ref, computed, onMounted, watch } from 'vue'
import * as echarts from 'echarts'
import axios from 'axios'
// 当前选中的产品类型
const activeProductTab = ref('beef')
// 响应式数据
const currentPrice = ref('--')
const priceChange = ref('--')
const priceChangePercent = ref('--')
const priceDate = ref('--')
const priceTrendData = ref([])
const provincePricesData = ref([])
const isPriceIncrease = ref(true)
// 图表引用
const priceChartRef = ref(null)
const provinceChartRef = ref(null)
// 图表实例
let priceChart = null
let provinceChart = null
// 产品类型文本映射
const productTypeText = computed(() => {
const map = {
@@ -66,25 +88,57 @@ const productTypeText = computed(() => {
return map[activeProductTab.value] || '育肥牛'
})
// 当前价格
const currentPrice = ref('--')
// 从后端获取市场行情数据
const fetchMarketPriceData = async (type = 'beef') => {
try {
const response = await axios.get('/api/government/market-price', {
params: { type }
})
const data = response.data
// 更新数据
currentPrice.value = data.currentPrice
priceChange.value = data.priceChange
priceChangePercent.value = data.priceChangePercent
priceDate.value = data.priceDate
priceTrendData.value = data.priceTrend
provincePricesData.value = data.provincePrices
// 判断价格是否上涨
isPriceIncrease.value = data.priceChange.startsWith('+')
// 初始化或更新图表
updatePriceChart()
updateProvinceChart()
} catch (error) {
console.error('获取市场行情数据失败:', error)
}
}
// 图表引用
const priceChartRef = ref(null)
const provinceChartRef = ref(null)
// 初始化价格走势图表
const initPriceChart = () => {
// 初始化或更新价格走势图表
const updatePriceChart = () => {
if (!priceChartRef.value) return
const chart = echarts.init(priceChartRef.value)
// 如果图表实例不存在,则创建
if (!priceChart) {
priceChart = echarts.init(priceChartRef.value)
// 添加窗口大小改变时的响应式调整
window.addEventListener('resize', () => {
if (priceChart) {
priceChart.resize()
}
})
}
// 模拟历史价格数据
const dates = ['2024-12-19', '2024-12-20', '2024-12-21', '2024-12-22', '2024-12-23',
'2024-12-24', '2024-12-25', '2024-12-26', '2024-12-27', '2024-12-28',
'2024-12-29', '2024-12-30', '2024-12-31', '2025-01-01']
// 提取日期和价格数据
const dates = priceTrendData.value.map(item => item.date)
const prices = priceTrendData.value.map(item => item.price)
const prices = [23.10, 20.65, 24.90, 21.78, 22.15, 24.30, 23.20, 21.60, 20.35, 24.05, 23.45, 20.95, 22.50, 21.80]
// 计算Y轴范围
const minPrice = Math.min(...prices) * 0.9
const maxPrice = Math.max(...prices) * 1.1
const option = {
tooltip: {
@@ -111,9 +165,9 @@ const initPriceChart = () => {
},
yAxis: {
type: 'value',
min: 0,
max: 25,
interval: 5
min: Math.floor(minPrice),
max: Math.ceil(maxPrice),
interval: Math.ceil((maxPrice - minPrice) / 5)
},
series: [
{
@@ -136,24 +190,31 @@ const initPriceChart = () => {
]
}
chart.setOption(option)
// 响应式调整
window.addEventListener('resize', () => {
chart.resize()
})
priceChart.setOption(option)
}
// 初始化各省价格对比图表
const initProvinceChart = () => {
// 初始化或更新各省价格对比图表
const updateProvinceChart = () => {
if (!provinceChartRef.value) return
const chart = echarts.init(provinceChartRef.value)
// 如果图表实例不存在,则创建
if (!provinceChart) {
provinceChart = echarts.init(provinceChartRef.value)
// 添加窗口大小改变时的响应式调整
window.addEventListener('resize', () => {
if (provinceChart) {
provinceChart.resize()
}
})
}
// 模拟各省价格数据
const provinces = ['上海市', '云南省', '内蒙古自治区', '北京市', '吉林省', '四川省', '天津市', '宁夏回族自治区', '安徽省', '山东省', '山西省', '广东省', '广西壮族自治区', '新疆维吾尔自治区', '江苏省', '江西省', '河北省', '河南省', '浙江省', '海南省', '湖北省', '湖南省', '甘肃省', '福建省', '贵州省', '辽宁省', '重庆市', '陕西省', '青海省', '黑龙江省']
// 提取省份和价格数据
const provinces = provincePricesData.value.map(item => item.province)
const prices = provincePricesData.value.map(item => item.price)
const prices = [36, 34.85, 34.85, 35.7, 33.26, 34.9, 35.4, 36.51, 33.5, 35.4, 35.8, 35.3, 32.6, 35.8, 34.1, 35.2, 33.2, 36, 35, 35.4, 32, 33.2, 34.8, 32.86, 32.7, 33, 32.8, 33.4, 33, 35.4]
// 计算X轴范围
const maxPrice = Math.max(...prices) * 1.1
const option = {
tooltip: {
@@ -172,8 +233,8 @@ const initProvinceChart = () => {
xAxis: {
type: 'value',
min: 0,
max: 40,
interval: 10
max: Math.ceil(maxPrice),
interval: Math.ceil(maxPrice / 5)
},
yAxis: {
type: 'category',
@@ -198,25 +259,34 @@ const initProvinceChart = () => {
]
}
chart.setOption(option)
// 响应式调整
window.addEventListener('resize', () => {
chart.resize()
})
provinceChart.setOption(option)
}
// 组件挂载时初始化图表
// 处理选项卡切换
const handleTabChange = (key) => {
activeProductTab.value = key
fetchMarketPriceData(key)
}
// 组件挂载时初始化数据和图表
onMounted(() => {
// 设置当前价格
currentPrice.value = '23.10'
// 初始化图表
// 延迟执行确保DOM已完全渲染
setTimeout(() => {
initPriceChart()
initProvinceChart()
fetchMarketPriceData(activeProductTab.value)
}, 100)
})
// 组件卸载时销毁图表实例
const onUnmounted = () => {
if (priceChart) {
priceChart.dispose()
priceChart = null
}
if (provinceChart) {
provinceChart.dispose()
provinceChart = null
}
}
</script>
<style scoped>

View File

@@ -0,0 +1,52 @@
// 从前端目录测试API访问
const axios = require('axios');
// 创建axios实例使用与前端相同的配置
const api = axios.create({
baseURL: 'http://localhost:5352/api',
timeout: 5000,
headers: {
'Content-Type': 'application/json'
}
});
async function testApi() {
try {
console.log('开始从前端目录测试API...');
// 测试行政人员列表API
console.log('\n测试行政人员列表API:');
const adminStaffResponse = await api.get('/government/admin-staff');
console.log(`✅ 行政人员列表API调用成功返回${adminStaffResponse.data.length}条数据`);
// 测试部门列表API
console.log('\n测试部门列表API:');
const departmentResponse = await api.get('/government/departments');
console.log(`✅ 部门列表API调用成功返回${departmentResponse.data.length}条数据`);
// 测试岗位列表API
console.log('\n测试岗位列表API:');
const positionResponse = await api.get('/government/positions');
console.log(`✅ 岗位列表API调用成功返回${positionResponse.data.length}条数据`);
// 测试带有查询参数的API
console.log('\n测试带查询参数的API:');
const filteredResponse = await api.get('/government/admin-staff?page=1&pageSize=3');
console.log(`✅ 带查询参数的API调用成功返回${filteredResponse.data.length}条数据`);
console.log('\n✅ 所有API测试成功完成');
} catch (error) {
console.error('❌ API测试失败:', error.message);
if (error.response) {
console.error('错误状态码:', error.response.status);
console.error('错误数据:', error.response.data);
} else if (error.request) {
console.error('没有收到响应:', error.request);
} else {
console.error('请求配置错误:', error.message);
}
console.error('错误详情:', error);
}
}
testApi();

View File

@@ -37,6 +37,7 @@ app.use('/api/service', require('./routes/service'));
app.use('/api/visualization', require('./routes/visualization'));
app.use('/api/system', require('./routes/system'));
app.use('/api/files', require('./routes/files'));
app.use('/api/government', require('./routes/government'));
// 健康检查
app.get('/health', (req, res) => {

View File

@@ -0,0 +1,599 @@
const { Op } = require('sequelize');
const Department = require('../models/Department');
const Position = require('../models/Position');
const AdminStaff = require('../models/AdminStaff');
// 获取数据中心统计数据
const getDataCenterStats = async (req, res) => {
try {
// Mock data based on frontend analysis
const stats = {
totalFarmers: 128,
totalAnimals: 8056,
totalTransactions: 61,
activeUsers: 1,
monthlyTrends: [
{ month: '1月', value: 120 },
{ month: '2月', value: 180 },
{ month: '3月', value: 150 },
{ month: '4月', value: 200 },
{ month: '5月', value: 250 },
{ month: '6月', value: 220 },
{ month: '7月', value: 300 },
{ month: '8月', value: 280 },
{ month: '9月', value: 350 },
{ month: '10月', value: 400 },
{ month: '11月', value: 380 },
{ month: '12月', value: 450 }
],
categoryDistribution: [
{ value: 335, name: '西门塔尔牛' },
{ value: 310, name: '夏洛莱牛' },
{ value: 234, name: '安格斯牛' },
{ value: 135, name: '利木赞牛' },
{ value: 548, name: '其他' }
],
regionDistribution: [
{ value: 1548, name: '扎鲁特旗' },
{ value: 735, name: '科尔沁区' },
{ value: 510, name: '霍林郭勒市' },
{ value: 434, name: '开鲁县' },
{ value: 335, name: '库伦旗' }
],
scaleDistribution: [
{ value: 40, name: '1-10头' },
{ value: 30, name: '11-50头' },
{ value: 20, name: '51-100头' },
{ value: 10, name: '100头以上' }
],
updates: [
{ time: '2023-10-27 10:30', content: '数据更新-养殖户数据' },
{ time: '2023-10-27 10:30', content: '数据更新-存栏数据' },
{ time: '2023-10-27 10:30', content: '数据更新-交易数据' },
{ time: '2023-10-27 10:30', content: '数据更新-用户数据' }
]
};
res.json(stats);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// 获取市场价格信息
const getMarketPrice = async (req, res) => {
try {
const { type = 'beef' } = req.query;
let priceData = {};
// 繁殖牛数据
if (type === 'beef') {
priceData = {
type: 'beef',
typeText: '繁殖牛',
currentPrice: '38.50',
priceChange: '-0.20',
priceChangePercent: '-0.52%',
priceDate: '2025-01-01',
priceTrend: [
{ date: '2024-12-19', price: 38.50 },
{ date: '2024-12-20', price: 36.90 },
{ date: '2024-12-21', price: 39.50 },
{ date: '2024-12-22', price: 37.80 },
{ date: '2024-12-23', price: 38.40 },
{ date: '2024-12-24', price: 40.20 },
{ date: '2024-12-25', price: 39.10 },
{ date: '2024-12-26', price: 37.50 },
{ date: '2024-12-27', price: 36.20 },
{ date: '2024-12-28', price: 39.90 },
{ date: '2024-12-29', price: 39.30 },
{ date: '2024-12-30', price: 36.60 },
{ date: '2024-12-31', price: 38.70 },
{ date: '2025-01-01', price: 38.30 }
],
provincePrices: [
{ province: '上海市', price: 40 },
{ province: '云南省', price: 38.85 },
{ province: '内蒙古自治区', price: 38.85 },
{ province: '北京市', price: 40.7 },
{ province: '吉林省', price: 38.26 },
{ province: '四川省', price: 39.9 },
{ province: '天津市', price: 40.4 },
{ province: '宁夏回族自治区', price: 41.51 },
{ province: '安徽省', price: 38.5 },
{ province: '山东省', price: 40.4 },
{ province: '山西省', price: 40.8 },
{ province: '广东省', price: 40.3 },
{ province: '广西壮族自治区', price: 37.6 },
{ province: '新疆维吾尔自治区', price: 40.8 },
{ province: '江苏省', price: 39.1 },
{ province: '江西省', price: 40.2 },
{ province: '河北省', price: 38.2 },
{ province: '河南省', price: 41 },
{ province: '浙江省', price: 40 },
{ province: '海南省', price: 40.4 },
{ province: '湖北省', price: 37 },
{ province: '湖南省', price: 38.2 },
{ province: '甘肃省', price: 39.8 },
{ province: '福建省', price: 37.86 },
{ province: '贵州省', price: 37.7 },
{ province: '辽宁省', price: 38 },
{ province: '重庆市', price: 37.8 },
{ province: '陕西省', price: 38.4 },
{ province: '青海省', price: 38 },
{ province: '黑龙江省', price: 40.4 }
]
};
}
// 奶牛数据
else if (type === 'milk') {
priceData = {
type: 'milk',
typeText: '奶牛',
currentPrice: '32.80',
priceChange: '+0.15',
priceChangePercent: '+0.46%',
priceDate: '2025-01-01',
priceTrend: [
{ date: '2024-12-19', price: 32.80 },
{ date: '2024-12-20', price: 30.90 },
{ date: '2024-12-21', price: 33.50 },
{ date: '2024-12-22', price: 31.80 },
{ date: '2024-12-23', price: 32.40 },
{ date: '2024-12-24', price: 34.20 },
{ date: '2024-12-25', price: 33.10 },
{ date: '2024-12-26', price: 31.50 },
{ date: '2024-12-27', price: 30.20 },
{ date: '2024-12-28', price: 33.90 },
{ date: '2024-12-29', price: 33.30 },
{ date: '2024-12-30', price: 30.60 },
{ date: '2024-12-31', price: 32.70 },
{ date: '2025-01-01', price: 32.30 }
],
provincePrices: [
{ province: '上海市', price: 45 },
{ province: '云南省', price: 43.85 },
{ province: '内蒙古自治区', price: 43.85 },
{ province: '北京市', price: 44.7 },
{ province: '吉林省', price: 42.26 },
{ province: '四川省', price: 43.9 },
{ province: '天津市', price: 44.4 },
{ province: '宁夏回族自治区', price: 45.51 },
{ province: '安徽省', price: 42.5 },
{ province: '山东省', price: 44.4 },
{ province: '山西省', price: 44.8 },
{ province: '广东省', price: 44.3 },
{ province: '广西壮族自治区', price: 41.6 },
{ province: '新疆维吾尔自治区', price: 44.8 },
{ province: '江苏省', price: 43.1 },
{ province: '江西省', price: 44.2 },
{ province: '河北省', price: 42.2 },
{ province: '河南省', price: 45 },
{ province: '浙江省', price: 44 },
{ province: '海南省', price: 44.4 },
{ province: '湖北省', price: 41 },
{ province: '湖南省', price: 42.2 },
{ province: '甘肃省', price: 43.8 },
{ province: '福建省', price: 41.86 },
{ province: '贵州省', price: 41.7 },
{ province: '辽宁省', price: 42 },
{ province: '重庆市', price: 41.8 },
{ province: '陕西省', price: 42.4 },
{ province: '青海省', price: 42 },
{ province: '黑龙江省', price: 44.4 }
]
};
}
res.json(priceData);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// 获取所有部门及其岗位
const getDepartments = async (req, res) => {
try {
const departments = await Department.findAll({
include: [
{
model: Position,
as: 'positions'
}
]
});
// 转换数据格式以便前端使用
const formattedData = departments.map(dept => ({
id: dept.id,
name: dept.name,
description: dept.description,
positions: dept.positions.map(pos => ({
id: pos.id,
name: pos.name,
permission: pos.has_permission ? '已设置权限' : '未设置权限',
permission_details: pos.permission_details
}))
}));
res.json(formattedData);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// 新增部门
const createDepartment = async (req, res) => {
try {
const { name, description } = req.body;
const department = await Department.create({
name,
description,
created_by: req.user?.id || null,
updated_by: req.user?.id || null
});
res.status(201).json(department);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// 编辑部门
const updateDepartment = async (req, res) => {
try {
const { id } = req.params;
const { name, description } = req.body;
const department = await Department.findByPk(id);
if (!department) {
return res.status(404).json({ message: '部门不存在' });
}
department.name = name;
department.description = description;
department.updated_by = req.user?.id || null;
await department.save();
res.json(department);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// 删除部门
const deleteDepartment = async (req, res) => {
try {
const { id } = req.params;
const department = await Department.findByPk(id);
if (!department) {
return res.status(404).json({ message: '部门不存在' });
}
// 先删除该部门下的所有岗位
await Position.destroy({
where: { department_id: id }
});
// 再删除部门
await department.destroy();
res.json({ message: '部门删除成功' });
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// 新增岗位
const createPosition = async (req, res) => {
try {
const { department_id } = req.params;
const { name } = req.body;
// 检查部门是否存在
const department = await Department.findByPk(department_id);
if (!department) {
return res.status(404).json({ message: '部门不存在' });
}
const position = await Position.create({
department_id,
name,
has_permission: false,
created_by: req.user?.id || null,
updated_by: req.user?.id || null
});
res.status(201).json({
id: position.id,
name: position.name,
permission: '未设置权限',
permission_details: position.permission_details
});
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// 编辑岗位
const updatePosition = async (req, res) => {
try {
const { id } = req.params;
const { name } = req.body;
const position = await Position.findByPk(id);
if (!position) {
return res.status(404).json({ message: '岗位不存在' });
}
position.name = name;
position.updated_by = req.user?.id || null;
await position.save();
res.json({
id: position.id,
name: position.name,
permission: position.has_permission ? '已设置权限' : '未设置权限',
permission_details: position.permission_details
});
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// 删除岗位
const deletePosition = async (req, res) => {
try {
const { id } = req.params;
const position = await Position.findByPk(id);
if (!position) {
return res.status(404).json({ message: '岗位不存在' });
}
await position.destroy();
res.json({ message: '岗位删除成功' });
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// 设置岗位权限
const setPositionPermission = async (req, res) => {
try {
const { id } = req.params;
const { permission_details } = req.body;
const position = await Position.findByPk(id);
if (!position) {
return res.status(404).json({ message: '岗位不存在' });
}
position.has_permission = true;
position.permission_details = permission_details;
position.updated_by = req.user?.id || null;
await position.save();
res.json({
id: position.id,
name: position.name,
permission: '已设置权限',
permission_details: position.permission_details
});
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// 行政人员相关接口
// 获取行政人员列表
const getAdminStaff = async (req, res) => {
try {
const { page = 1, pageSize = 10, search = '' } = req.query;
const whereCondition = {};
if (search) {
whereCondition.name = {
[Op.like]: `%${search}%`
};
}
const offset = (page - 1) * pageSize;
const { count, rows } = await AdminStaff.findAndCountAll({
where: whereCondition,
include: [
{
model: Department,
as: 'department',
attributes: ['name']
},
{
model: Position,
as: 'position',
attributes: ['name']
}
],
offset,
limit: parseInt(pageSize),
order: [['createdAt', 'DESC']]
});
// 格式化数据以便前端使用
const formattedData = rows.map(staff => ({
id: staff.id,
key: staff.id.toString(),
name: staff.name,
department: staff.department?.name || '',
position: staff.position?.name || '',
phone: staff.phone,
idCard: staff.id_card || '--',
status: staff.status,
createdAt: staff.createdAt.toLocaleString('zh-CN')
}));
res.json({
data: formattedData,
total: count,
page: parseInt(page),
pageSize: parseInt(pageSize)
});
} catch (error) {
console.error('获取行政人员列表失败:', error);
res.status(500).json({ message: error.message });
}
};
// 新增行政人员
const createAdminStaff = async (req, res) => {
try {
const { name, department_id, position_id, phone, id_card, status } = req.body;
const staff = await AdminStaff.create({
name,
department_id,
position_id,
phone,
id_card,
status: status !== undefined ? status : true
});
res.status(201).json({
success: true,
message: '行政人员创建成功',
data: staff
});
} catch (error) {
console.error('创建行政人员失败:', error);
res.status(500).json({ message: error.message });
}
};
// 编辑行政人员
const updateAdminStaff = async (req, res) => {
try {
const { id } = req.params;
const { name, department_id, position_id, phone, id_card, status } = req.body;
const staff = await AdminStaff.findByPk(id);
if (!staff) {
return res.status(404).json({ message: '行政人员不存在' });
}
await staff.update({
name,
department_id,
position_id,
phone,
id_card,
status
});
res.json({
success: true,
message: '行政人员更新成功',
data: staff
});
} catch (error) {
console.error('更新行政人员失败:', error);
res.status(500).json({ message: error.message });
}
};
// 删除行政人员
const deleteAdminStaff = async (req, res) => {
try {
const { id } = req.params;
const staff = await AdminStaff.findByPk(id);
if (!staff) {
return res.status(404).json({ message: '行政人员不存在' });
}
await staff.destroy();
res.json({
success: true,
message: '行政人员删除成功'
});
} catch (error) {
console.error('删除行政人员失败:', error);
res.status(500).json({ message: error.message });
}
};
// 重置行政人员密码
const resetAdminStaffPassword = async (req, res) => {
try {
const { id } = req.params;
const staff = await AdminStaff.findByPk(id);
if (!staff) {
return res.status(404).json({ message: '行政人员不存在' });
}
// 在实际应用中,这里应该生成一个临时密码并发送给用户
// 为了演示,我们只返回成功信息
res.json({
success: true,
message: '密码重置成功,新密码已发送到用户手机'
});
} catch (error) {
console.error('重置密码失败:', error);
res.status(500).json({ message: error.message });
}
};
// 获取所有岗位列表
const getPositions = async (req, res) => {
try {
const positions = await Position.findAll({
include: [
{
model: Department,
as: 'department'
}
]
});
// 转换数据格式以便前端使用
const formattedData = positions.map(pos => ({
id: pos.id,
name: pos.name,
department_id: pos.department_id,
department_name: pos.department?.name || '',
permission: pos.has_permission ? '已设置权限' : '未设置权限',
permission_details: pos.permission_details
}));
res.json(formattedData);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// 导出模块
module.exports = {
getDataCenterStats,
getMarketPrice,
getDepartments,
getPositions,
createDepartment,
updateDepartment,
deleteDepartment,
createPosition,
updatePosition,
deletePosition,
setPositionPermission,
getAdminStaff,
createAdminStaff,
updateAdminStaff,
deleteAdminStaff,
resetAdminStaffPassword
};

View File

@@ -0,0 +1,96 @@
// 完整模型测试脚本
const sequelize = require('./config/database');
// 确保先导入所有依赖模型
const Department = require('./models/Department');
const Position = require('./models/Position');
const AdminStaff = require('./models/AdminStaff');
async function fullModelTest() {
try {
console.log('开始完整模型测试...');
// 测试数据库连接
await sequelize.authenticate();
console.log('✅ 数据库连接成功');
// 检查模型是否被正确导入
console.log('\n检查模型导入状态:');
console.log('Department 模型:', Department ? '✅ 已导入' : '❌ 未导入');
console.log('Position 模型:', Position ? '✅ 已导入' : '❌ 未导入');
console.log('AdminStaff 模型:', AdminStaff ? '✅ 已导入' : '❌ 未导入');
// 测试基本的查询功能
console.log('\n测试基本查询功能:');
// 查询部门表
console.log('\n查询部门表:');
try {
const departments = await Department.findAll({ limit: 5 });
console.log(`找到 ${departments.length} 个部门`);
if (departments.length > 0) {
console.log('示例部门:', departments[0].dataValues);
}
} catch (error) {
console.error('部门查询失败:', error.message);
}
// 查询岗位表
console.log('\n查询岗位表:');
try {
const positions = await Position.findAll({ limit: 5 });
console.log(`找到 ${positions.length} 个岗位`);
if (positions.length > 0) {
console.log('示例岗位:', positions[0].dataValues);
}
} catch (error) {
console.error('岗位查询失败:', error.message);
}
// 查询行政人员表
console.log('\n查询行政人员表:');
try {
const adminStaffs = await AdminStaff.findAll({ limit: 5 });
console.log(`找到 ${adminStaffs.length} 个行政人员`);
if (adminStaffs.length > 0) {
console.log('示例行政人员:', adminStaffs[0].dataValues);
}
} catch (error) {
console.error('行政人员查询失败:', error.message);
console.error('错误详情:', error);
}
// 测试关联查询
console.log('\n测试关联查询:');
try {
const adminStaffWithRelations = await AdminStaff.findAll({
include: [
{ model: Department, as: 'department' },
{ model: Position, as: 'position' }
],
limit: 1
});
console.log('关联查询结果数量:', adminStaffWithRelations.length);
if (adminStaffWithRelations.length > 0) {
console.log('关联查询成功,已获取到关联数据');
// 只打印部分数据以避免输出过多
const staff = adminStaffWithRelations[0];
console.log('姓名:', staff.name);
console.log('部门:', staff.department?.name || '未知');
console.log('岗位:', staff.position?.name || '未知');
}
} catch (error) {
console.error('关联查询失败:', error.message);
console.error('错误详情:', error);
}
console.log('\n✅ 完整模型测试完成');
} catch (error) {
console.error('❌ 完整模型测试失败:', error.message);
console.error('错误详情:', error);
} finally {
await sequelize.close();
}
}
fullModelTest();

View File

@@ -0,0 +1,99 @@
// 模型导入测试脚本
const sequelize = require('./config/database');
const { DataTypes } = require('sequelize');
async function modelImportTest() {
try {
console.log('开始模型导入测试...');
// 测试数据库连接
await sequelize.authenticate();
console.log('✅ 数据库连接成功');
// 测试直接导入模型
console.log('\n测试直接导入模型:');
// 先测试 Department 模型
console.log('\n测试 Department 模型导入:');
try {
const Department = require('./models/Department');
console.log('Department 模型导入:', Department ? '✅ 成功' : '❌ 失败');
// 测试 Department 模型的结构
if (Department) {
console.log('Department 模型是否有 define 方法:', typeof Department.define === 'function' ? '✅ 是' : '❌ 否');
console.log('Department 模型的 tableName:', Department.tableName || Department.options?.tableName || '未定义');
}
} catch (error) {
console.error('Department 模型导入失败:', error.message);
}
// 测试 Position 模型
console.log('\n测试 Position 模型导入:');
try {
const Position = require('./models/Position');
console.log('Position 模型导入:', Position ? '✅ 成功' : '❌ 失败');
// 测试 Position 模型的结构
if (Position) {
console.log('Position 模型是否有 define 方法:', typeof Position.define === 'function' ? '✅ 是' : '❌ 否');
console.log('Position 模型的 tableName:', Position.tableName || Position.options?.tableName || '未定义');
}
} catch (error) {
console.error('Position 模型导入失败:', error.message);
}
// 测试 AdminStaff 模型
console.log('\n测试 AdminStaff 模型导入:');
try {
// 先导入依赖模型
require('./models/Department');
require('./models/Position');
const AdminStaff = require('./models/AdminStaff');
console.log('AdminStaff 模型导入:', AdminStaff ? '✅ 成功' : '❌ 失败');
// 测试 AdminStaff 模型的结构
if (AdminStaff) {
console.log('AdminStaff 模型是否有 define 方法:', typeof AdminStaff.define === 'function' ? '✅ 是' : '❌ 否');
console.log('AdminStaff 模型的 tableName:', AdminStaff.tableName || AdminStaff.options?.tableName || '未定义');
}
} catch (error) {
console.error('AdminStaff 模型导入失败:', error.message);
console.error('错误详情:', error);
}
// 测试创建一个简单的新模型
console.log('\n测试创建新模型:');
try {
const TestModel = sequelize.define('TestModel', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING,
allowNull: false
}
}, {
tableName: 'test_models',
timestamps: false,
paranoid: false
});
console.log('新模型创建:', TestModel ? '✅ 成功' : '❌ 失败');
} catch (error) {
console.error('新模型创建失败:', error.message);
}
console.log('\n✅ 模型导入测试完成');
} catch (error) {
console.error('❌ 模型导入测试失败:', error.message);
console.error('错误详情:', error);
} finally {
await sequelize.close();
}
}
modelImportTest();

View File

@@ -0,0 +1,85 @@
const sequelize = require('../config/database');
const { DataTypes } = require('sequelize');
const Department = require('./Department');
const Position = require('./Position');
const AdminStaff = sequelize.define('AdminStaff', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING,
allowNull: false,
comment: '员工姓名'
},
department_id: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: Department,
key: 'id'
},
comment: '所属部门ID'
},
position_id: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: Position,
key: 'id'
},
comment: '任职岗位ID'
},
phone: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
comment: '联系电话'
},
id_card: {
type: DataTypes.STRING,
allowNull: true,
comment: '身份证号码'
},
status: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: true,
comment: '账号使用状态'
}
}, {
tableName: 'government_admin_staff',
indexes: [
{
name: 'idx_admin_staff_department_id',
fields: ['department_id']
},
{
name: 'idx_admin_staff_position_id',
fields: ['position_id']
},
{
name: 'idx_admin_staff_name',
fields: ['name']
},
{
name: 'idx_admin_staff_phone',
fields: ['phone']
}
]
});
// 建立关联关系
AdminStaff.belongsTo(Department, {
foreignKey: 'department_id',
as: 'department'
});
AdminStaff.belongsTo(Position, {
foreignKey: 'position_id',
as: 'position'
});
module.exports = AdminStaff;

View File

@@ -0,0 +1,41 @@
const sequelize = require('../config/database');
const { DataTypes } = require('sequelize');
const Department = sequelize.define('Department', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
comment: '部门名称'
},
description: {
type: DataTypes.TEXT,
allowNull: true,
comment: '部门描述'
},
created_by: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '创建人ID'
},
updated_by: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '更新人ID'
}
}, {
tableName: 'government_departments',
indexes: [
{
name: 'idx_department_name',
fields: ['name']
}
]
});
module.exports = Department;

View File

@@ -0,0 +1,66 @@
const sequelize = require('../config/database');
const { DataTypes } = require('sequelize');
const Department = require('./Department');
const Position = sequelize.define('Position', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
department_id: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: Department,
key: 'id'
},
comment: '所属部门ID'
},
name: {
type: DataTypes.STRING,
allowNull: false,
comment: '岗位名称'
},
has_permission: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
comment: '是否已设置权限'
},
created_by: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '创建人ID'
},
updated_by: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '更新人ID'
}
}, {
tableName: 'government_positions',
indexes: [
{
name: 'idx_position_department_id',
fields: ['department_id']
},
{
name: 'idx_position_name',
fields: ['name']
}
]
});
// 定义关系
Department.hasMany(Position, {
foreignKey: 'department_id',
as: 'positions'
});
Position.belongsTo(Department, {
foreignKey: 'department_id',
as: 'department'
});
module.exports = Position;

View File

@@ -0,0 +1,55 @@
const express = require('express');
const router = express.Router();
const governmentController = require('../controllers/governmentController');
// 数据览仓接口
router.get('/data-center', governmentController.getDataCenterStats);
// 市场价格接口
router.get('/market-price', governmentController.getMarketPrice);
// 行政部门管理接口
// 获取所有部门及其岗位
router.get('/departments', governmentController.getDepartments);
// 新增部门
router.post('/departments', governmentController.createDepartment);
// 编辑部门
router.put('/departments/:id', governmentController.updateDepartment);
// 删除部门
router.delete('/departments/:id', governmentController.deleteDepartment);
// 获取所有岗位列表
router.get('/positions', governmentController.getPositions);
// 在指定部门下新增岗位
router.post('/departments/:id/positions', governmentController.createPosition);
// 编辑岗位
router.put('/positions/:id', governmentController.updatePosition);
// 删除岗位
router.delete('/positions/:id', governmentController.deletePosition);
// 设置岗位权限
router.post('/positions/:id/permission', governmentController.setPositionPermission);
// 行政人员管理接口
// 获取行政人员列表
router.get('/admin-staff', governmentController.getAdminStaff);
// 新增行政人员
router.post('/admin-staff', governmentController.createAdminStaff);
// 编辑行政人员
router.put('/admin-staff/:id', governmentController.updateAdminStaff);
// 删除行政人员
router.delete('/admin-staff/:id', governmentController.deleteAdminStaff);
// 重置行政人员密码
router.post('/admin-staff/:id/reset-password', governmentController.resetAdminStaffPassword);
module.exports = router;

View File

@@ -0,0 +1,43 @@
const Department = require('../models/Department');
const Position = require('../models/Position');
const sequelize = require('../config/database');
// 查看数据库中的部门和岗位
async function checkData() {
try {
console.log('连接数据库...');
// 获取所有部门
console.log('\n查询所有部门...');
const departments = await Department.findAll();
console.log(`找到 ${departments.length} 个部门:`);
departments.forEach(dept => {
console.log(`- ID: ${dept.id}, 名称: ${dept.name}, 描述: ${dept.description}`);
});
// 获取所有岗位
console.log('\n查询所有岗位...');
const positions = await Position.findAll({
include: [
{
model: Department,
as: 'department',
attributes: ['name']
}
]
});
console.log(`找到 ${positions.length} 个岗位:`);
positions.forEach(pos => {
console.log(`- ID: ${pos.id}, 名称: ${pos.name}, 部门ID: ${pos.department_id}, 部门名称: ${pos.department?.name || '未知'}`);
});
console.log('\n查询完成');
process.exit(0);
} catch (error) {
console.error('查询数据失败:', error);
process.exit(1);
}
}
// 执行查询
checkData();

View File

@@ -0,0 +1,86 @@
const sequelize = require('../config/database');
const Department = require('../models/Department');
const Position = require('../models/Position');
// 测试数据
const departments = [
{
name: '防疫部门',
description: '负责动物防疫工作的部门',
positions: [
{ name: '防疫员', has_permission: true, permission_details: { basic: ['view', 'edit'], advanced: ['delete'] } },
{ name: '防疫管理员', has_permission: true, permission_details: { basic: ['view', 'edit', 'add', 'delete'], advanced: ['admin'] } }
]
},
{
name: '检疫部门',
description: '负责动物检疫工作的部门',
positions: [
{ name: '检疫员', has_permission: false, permission_details: null }
]
},
{
name: '冷配管理部门',
description: '负责动物冷配管理工作的部门',
positions: [
{ name: '冷配员', has_permission: true, permission_details: { basic: ['view', 'edit'], advanced: [] } }
]
},
{
name: '测试部门',
description: '用于系统测试的部门',
positions: [
{ name: '内部测试账号', has_permission: true, permission_details: { basic: ['view', 'edit', 'add', 'delete'], advanced: ['admin', 'debug'] } }
]
}
];
// 初始化数据库表并插入测试数据
async function seedDatabase() {
try {
console.log('开始同步数据库表结构...');
// 同步数据库表结构
await sequelize.sync({ alter: true });
console.log('数据库表结构同步完成!');
console.log('开始插入测试数据...');
// 逐个创建部门及其岗位
for (const deptData of departments) {
// 创建部门
const department = await Department.create({
name: deptData.name,
description: deptData.description,
created_by: 1,
updated_by: 1
});
console.log(`已创建部门: ${department.name}`);
// 创建该部门下的岗位
for (const posData of deptData.positions) {
const position = await Position.create({
department_id: department.id,
name: posData.name,
has_permission: posData.has_permission,
permission_details: posData.permission_details,
created_by: 1,
updated_by: 1
});
console.log(` - 已创建岗位: ${position.name} (权限: ${position.has_permission ? '已设置' : '未设置'})`);
}
}
console.log('测试数据插入完成!');
} catch (error) {
console.error('数据库初始化失败:', error);
process.exit(1);
} finally {
// 关闭数据库连接
await sequelize.close();
}
}
// 执行播种函数
seedDatabase();

View File

@@ -0,0 +1,181 @@
const Department = require('../models/Department');
const Position = require('../models/Position');
const AdminStaff = require('../models/AdminStaff');
const sequelize = require('../config/database');
// 测试数据
const departmentsData = [
{
name: '防疫部门',
description: '负责疫情防控和动物健康监测'
},
{
name: '行政管理部门',
description: '负责日常行政事务管理'
},
{
name: '数据分析部门',
description: '负责数据收集、分析和报告'
},
{
name: '市场监管部门',
description: '负责市场价格监管和交易监督'
}
];
const positionsData = [
{ name: '部门主管', has_permission: true },
{ name: '防疫专员', has_permission: false },
{ name: '行政主管', has_permission: true },
{ name: '行政助理', has_permission: false },
{ name: '数据分析主管', has_permission: true },
{ name: '数据分析师', has_permission: false },
{ name: '市场监管主管', has_permission: true },
{ name: '市场监管员', has_permission: false }
];
const adminStaffData = [
{
name: '张三',
phone: '13800138001',
id_card: '110101199003070010',
status: true
},
{
name: '李四',
phone: '13800138002',
id_card: '110101199104150020',
status: true
},
{
name: '王五',
phone: '13800138003',
id_card: '110101199205200030',
status: true
},
{
name: '赵六',
phone: '13800138004',
id_card: '110101199306250040',
status: true
},
{
name: '孙七',
phone: '13800138005',
id_card: '110101199407300050',
status: true
},
{
name: '周八',
phone: '13800138006',
id_card: '110101199508310060',
status: false
},
{
name: '吴九',
phone: '13800138007',
id_card: '110101199609150070',
status: true
},
{
name: '郑十',
phone: '13800138008',
id_card: '110101199710200080',
status: true
}
];
// 添加测试数据到数据库
async function seedData() {
try {
console.log('开始添加测试数据...');
// 先检查是否已有行政人员数据
const existingStaffCount = await AdminStaff.count();
if (existingStaffCount > 0) {
console.log(`数据库中已有 ${existingStaffCount} 个行政人员数据,跳过所有数据添加`);
process.exit(0);
}
// 开始事务
await sequelize.transaction(async (transaction) => {
// 先删除可能存在的软删除数据
await AdminStaff.destroy({ where: {}, force: true, transaction });
await Position.destroy({ where: {}, force: true, transaction });
await Department.destroy({ where: {}, force: true, transaction });
console.log('已清空所有表数据(包括软删除的数据)');
// 添加部门数据
console.log('添加部门数据...');
const departments = await Department.bulkCreate(departmentsData, {
transaction,
ignoreDuplicates: true
});
console.log(`成功添加 ${departments.length} 个部门`);
// 添加岗位数据,为每个部门添加相应的岗位
console.log('添加岗位数据...');
let positionId = 1;
const createdPositions = [];
for (let i = 0; i < departments.length; i++) {
const deptId = departments[i].id;
// 每个部门添加2个岗位
for (let j = 0; j < 2; j++) {
const posIndex = i * 2 + j;
if (posIndex < positionsData.length) {
const position = await Position.create({
department_id: deptId,
name: positionsData[posIndex].name,
has_permission: positionsData[posIndex].has_permission
}, {
transaction
});
createdPositions.push(position);
console.log(` 添加岗位: ${position.name} (ID: ${position.id}, 部门ID: ${deptId})`);
}
}
}
console.log(`总共添加了 ${createdPositions.length} 个岗位`);
// 添加行政人员数据,关联到实际创建的部门和岗位
console.log('添加行政人员数据...');
const createdStaff = [];
for (let i = 0; i < adminStaffData.length; i++) {
const staffData = adminStaffData[i];
// 根据索引分配部门和岗位
const deptIndex = Math.floor(i / 2); // 每2个人员属于同一个部门
const posIndex = i;
if (deptIndex < departments.length && posIndex < createdPositions.length) {
const staff = await AdminStaff.create({
name: staffData.name,
department_id: departments[deptIndex].id,
position_id: createdPositions[posIndex].id,
phone: staffData.phone,
id_card: staffData.id_card,
status: staffData.status
}, {
transaction
});
createdStaff.push(staff);
console.log(` 添加行政人员: ${staff.name} (ID: ${staff.id}, 部门: ${departments[deptIndex].name})`);
}
}
console.log(`总共添加了 ${createdStaff.length} 个行政人员`);
});
console.log('所有测试数据添加完成!');
process.exit(0);
} catch (error) {
console.error('添加测试数据失败:', error);
process.exit(1);
}
}
// 执行数据添加
seedData();

View File

@@ -0,0 +1,37 @@
// 简单测试脚本
const sequelize = require('./config/database');
const { DataTypes } = require('sequelize');
async function simpleTest() {
try {
console.log('开始简单测试...');
// 测试数据库连接
await sequelize.authenticate();
console.log('✅ 数据库连接成功');
// 测试DataTypes是否可用
console.log('\n测试DataTypes:');
console.log('DataTypes.INTEGER:', DataTypes.INTEGER ? '✅ 可用' : '❌ 不可用');
console.log('DataTypes.STRING:', DataTypes.STRING ? '✅ 可用' : '❌ 不可用');
console.log('DataTypes.BOOLEAN:', DataTypes.BOOLEAN ? '✅ 可用' : '❌ 不可用');
// 测试sequelize实例是否可用
console.log('\n测试sequelize实例:');
console.log('sequelize实例:', sequelize ? '✅ 可用' : '❌ 不可用');
// 测试直接查询数据库
console.log('\n测试直接查询数据库:');
const [results] = await sequelize.query('SELECT 1+1 AS result');
console.log('查询结果:', results);
console.log('\n✅ 简单测试完成');
} catch (error) {
console.error('❌ 简单测试失败:', error.message);
console.error('错误详情:', error);
} finally {
await sequelize.close();
}
}
simpleTest();

View File

@@ -0,0 +1,74 @@
// 测试模型文件
const sequelize = require('./config/database');
const AdminStaff = require('./models/AdminStaff');
const Department = require('./models/Department');
const Position = require('./models/Position');
async function testModels() {
try {
console.log('开始测试模型...');
// 测试数据库连接
await sequelize.authenticate();
console.log('✅ 数据库连接成功');
// 测试模型关系
console.log('\n测试模型关系:');
console.log('AdminStaff关联Department:', AdminStaff.associations.department ? '✅' : '❌');
console.log('AdminStaff关联Position:', AdminStaff.associations.position ? '✅' : '❌');
console.log('Department关联Position:', Department.associations.positions ? '✅' : '❌');
console.log('Position关联Department:', Position.associations.department ? '✅' : '❌');
// 尝试获取行政人员数据
console.log('\n尝试获取行政人员数据:');
const adminStaffs = await AdminStaff.findAll({
include: [
{ model: Department, as: 'department' },
{ model: Position, as: 'position' }
],
limit: 5
});
if (adminStaffs.length > 0) {
console.log(`✅ 成功获取${adminStaffs.length}条行政人员数据`);
console.log('示例数据:', adminStaffs[0].dataValues);
} else {
console.log('⚠️ 未找到行政人员数据');
}
// 尝试获取部门数据
console.log('\n尝试获取部门数据:');
const departments = await Department.findAll({
limit: 5
});
if (departments.length > 0) {
console.log(`✅ 成功获取${departments.length}条部门数据`);
console.log('示例数据:', departments[0].dataValues);
} else {
console.log('⚠️ 未找到部门数据');
}
// 尝试获取岗位数据
console.log('\n尝试获取岗位数据:');
const positions = await Position.findAll({
include: [{ model: Department, as: 'department' }],
limit: 5
});
if (positions.length > 0) {
console.log(`✅ 成功获取${positions.length}条岗位数据`);
console.log('示例数据:', positions[0].dataValues);
} else {
console.log('⚠️ 未找到岗位数据');
}
console.log('\n✅ 模型测试完成');
} catch (error) {
console.error('❌ 模型测试失败:', error);
} finally {
await sequelize.close();
}
}
testModels();

287
package-lock.json generated
View File

@@ -2,5 +2,290 @@
"name": "nxxmdata",
"lockfileVersion": 3,
"requires": true,
"packages": {}
"packages": {
"": {
"dependencies": {
"axios": "^1.12.2"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/axios": {
"version": "1.12.2",
"resolved": "https://registry.npmmirror.com/axios/-/axios-1.12.2.tgz",
"integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.4",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/follow-redirects": {
"version": "1.15.11",
"resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz",
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/form-data": {
"version": "4.0.4",
"resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
}
}
}

5
package.json Normal file
View File

@@ -0,0 +1,5 @@
{
"dependencies": {
"axios": "^1.12.2"
}
}

45
testApi.js Normal file
View File

@@ -0,0 +1,45 @@
const axios = require('axios');
async function testGovernmentApi() {
try {
console.log('测试政府端行政人员API...');
// 先测试网络连接
console.log('尝试连接到后端服务...');
const response = await axios.get('http://localhost:5352/api/government/admin-staff', {
timeout: 5000
});
console.log('连接成功,后端服务正在运行!');
console.log('状态码:', response.status);
console.log('响应数据结构:', JSON.stringify(Object.keys(response.data), null, 2));
if (response.data && response.data.data) {
console.log(`获取到 ${response.data.data.length} 条行政人员数据`);
console.log('第一条数据示例:', JSON.stringify(response.data.data[0] || '无数据', null, 2));
} else {
console.log('响应数据中没有data字段');
console.log('完整响应数据:', JSON.stringify(response.data, null, 2));
}
console.log('\nAPI测试成功');
} catch (error) {
console.error('\nAPI测试失败:');
if (error.code === 'ECONNREFUSED') {
console.error('错误: 无法连接到后端服务,请检查服务是否已启动。');
console.error('服务地址: http://localhost:5352');
} else if (error.code === 'ETIMEDOUT') {
console.error('错误: 连接超时,请检查网络连接和后端服务状态。');
} else if (error.response) {
console.error('HTTP错误状态码:', error.response.status);
console.error('响应数据:', JSON.stringify(error.response.data, null, 2));
} else if (error.request) {
console.error('没有收到响应:', error.request);
} else {
console.error('请求配置错误:', error.message);
}
console.error('完整错误信息:', error);
}
}
testGovernmentApi();