添加 IntelliJ IDEA 项目配置文件

This commit is contained in:
ylweng
2025-09-02 21:59:27 +08:00
parent 59cfe620fe
commit 501c218a83
56 changed files with 11886 additions and 126 deletions

194
backend/routes/auth.js Normal file
View File

@@ -0,0 +1,194 @@
const express = require('express')
const bcrypt = require('bcryptjs')
const jwt = require('jsonwebtoken')
const Joi = require('joi')
const router = express.Router()
// 模拟用户数据
const users = [
{
id: 1,
username: 'admin',
email: 'admin@example.com',
password: '$2a$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
role: 'admin',
status: 'active'
},
{
id: 2,
username: 'buyer',
email: 'buyer@example.com',
password: '$2a$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
role: 'buyer',
status: 'active'
},
{
id: 3,
username: 'trader',
email: 'trader@example.com',
password: '$2a$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
role: 'trader',
status: 'active'
}
]
// 登录参数验证
const loginSchema = Joi.object({
username: Joi.string().min(2).max(50).required(),
password: Joi.string().min(6).max(100).required()
})
// 生成JWT token
const generateToken = (user) => {
return jwt.sign(
{
id: user.id,
username: user.username,
role: user.role
},
process.env.JWT_SECRET || 'niumall-secret-key',
{ expiresIn: process.env.JWT_EXPIRES_IN || '24h' }
)
}
// 用户登录
router.post('/login', async (req, res) => {
try {
// 参数验证
const { error, value } = loginSchema.validate(req.body)
if (error) {
return res.status(400).json({
success: false,
message: '参数验证失败',
details: error.details[0].message
})
}
const { username, password } = value
// 查找用户
const user = users.find(u => u.username === username || u.email === username)
if (!user) {
return res.status(401).json({
success: false,
message: '用户名或密码错误'
})
}
// 验证密码
const isPasswordValid = await bcrypt.compare(password, user.password)
if (!isPasswordValid) {
return res.status(401).json({
success: false,
message: '用户名或密码错误'
})
}
// 检查用户状态
if (user.status !== 'active') {
return res.status(403).json({
success: false,
message: '账户已被禁用,请联系管理员'
})
}
// 生成token
const token = generateToken(user)
res.json({
success: true,
message: '登录成功',
data: {
access_token: token,
token_type: 'Bearer',
expires_in: 86400, // 24小时
user: {
id: user.id,
username: user.username,
email: user.email,
role: user.role,
status: user.status
}
}
})
} catch (error) {
console.error('登录失败:', error)
res.status(500).json({
success: false,
message: '登录失败,请稍后重试'
})
}
})
// 获取当前用户信息
router.get('/me', authenticateToken, (req, res) => {
const user = users.find(u => u.id === req.user.id)
if (!user) {
return res.status(404).json({
success: false,
message: '用户不存在'
})
}
res.json({
success: true,
data: {
user: {
id: user.id,
username: user.username,
email: user.email,
role: user.role,
status: user.status
},
permissions: getUserPermissions(user.role)
}
})
})
// 用户登出
router.post('/logout', authenticateToken, (req, res) => {
// 在实际项目中可以将token加入黑名单
res.json({
success: true,
message: '登出成功'
})
})
// JWT token验证中间件
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization']
const token = authHeader && authHeader.split(' ')[1]
if (!token) {
return res.status(401).json({
success: false,
message: '访问令牌缺失'
})
}
jwt.verify(token, process.env.JWT_SECRET || 'niumall-secret-key', (err, user) => {
if (err) {
return res.status(403).json({
success: false,
message: '访问令牌无效或已过期'
})
}
req.user = user
next()
})
}
// 获取用户权限
function getUserPermissions(role) {
const permissions = {
admin: ['*'], // 管理员拥有所有权限
buyer: ['order:read', 'order:create', 'order:update', 'supplier:read'],
trader: ['order:read', 'order:update', 'supplier:read', 'supplier:create', 'supplier:update', 'transport:read'],
supplier: ['order:read', 'quality:read', 'quality:create', 'quality:update'],
driver: ['transport:read', 'transport:update']
}
return permissions[role] || []
}
module.exports = router

490
backend/routes/finance.js Normal file
View File

@@ -0,0 +1,490 @@
const express = require('express');
const router = express.Router();
const Joi = require('joi');
// 模拟财务数据
let settlements = [
{
id: 1,
orderId: 1,
settlementCode: 'SET001',
supplierName: '山东优质牲畜合作社',
buyerName: '北京肉类加工有限公司',
cattleCount: 50,
unitPrice: 25000,
totalAmount: 1250000,
paymentMethod: 'bank_transfer',
paymentStatus: 'paid',
settlementDate: '2024-01-20',
paymentDate: '2024-01-22',
invoiceNumber: 'INV001',
invoiceStatus: 'issued',
taxAmount: 125000,
actualPayment: 1125000,
bankAccount: '1234567890123456789',
bankName: '中国农业银行',
createdAt: new Date('2024-01-20'),
updatedAt: new Date('2024-01-22')
},
{
id: 2,
orderId: 2,
settlementCode: 'SET002',
supplierName: '内蒙古草原牲畜有限公司',
buyerName: '天津屠宰加工厂',
cattleCount: 80,
unitPrice: 24000,
totalAmount: 1920000,
paymentMethod: 'cash',
paymentStatus: 'pending',
settlementDate: '2024-01-25',
paymentDate: null,
invoiceNumber: 'INV002',
invoiceStatus: 'pending',
taxAmount: 192000,
actualPayment: 1728000,
bankAccount: '9876543210987654321',
bankName: '中国建设银行',
createdAt: new Date('2024-01-25'),
updatedAt: new Date('2024-01-25')
}
];
let payments = [
{
id: 1,
settlementId: 1,
paymentCode: 'PAY001',
amount: 1125000,
paymentMethod: 'bank_transfer',
status: 'success',
transactionId: 'TXN20240122001',
paidAt: '2024-01-22T10:30:00Z',
createdAt: new Date('2024-01-22T10:30:00Z')
}
];
// 验证schemas
const settlementCreateSchema = Joi.object({
orderId: Joi.number().integer().required(),
cattleCount: Joi.number().integer().min(1).required(),
unitPrice: Joi.number().min(0).required(),
paymentMethod: Joi.string().valid('bank_transfer', 'cash', 'check', 'online').required(),
settlementDate: Joi.date().iso().required(),
invoiceNumber: Joi.string().min(3).max(50)
});
const paymentCreateSchema = Joi.object({
settlementId: Joi.number().integer().required(),
amount: Joi.number().min(0).required(),
paymentMethod: Joi.string().valid('bank_transfer', 'cash', 'check', 'online').required(),
transactionId: Joi.string().max(100)
});
// 获取结算列表
router.get('/settlements', (req, res) => {
try {
const {
page = 1,
pageSize = 20,
keyword,
paymentStatus,
startDate,
endDate
} = req.query;
let filteredSettlements = [...settlements];
// 关键词搜索
if (keyword) {
filteredSettlements = filteredSettlements.filter(settlement =>
settlement.settlementCode.includes(keyword) ||
settlement.supplierName.includes(keyword) ||
settlement.buyerName.includes(keyword)
);
}
// 支付状态筛选
if (paymentStatus) {
filteredSettlements = filteredSettlements.filter(settlement => settlement.paymentStatus === paymentStatus);
}
// 时间范围筛选
if (startDate) {
filteredSettlements = filteredSettlements.filter(settlement =>
new Date(settlement.settlementDate) >= new Date(startDate)
);
}
if (endDate) {
filteredSettlements = filteredSettlements.filter(settlement =>
new Date(settlement.settlementDate) <= new Date(endDate)
);
}
// 分页处理
const startIndex = (page - 1) * pageSize;
const endIndex = startIndex + parseInt(pageSize);
const paginatedSettlements = filteredSettlements.slice(startIndex, endIndex);
res.json({
success: true,
data: {
list: paginatedSettlements,
pagination: {
page: parseInt(page),
pageSize: parseInt(pageSize),
total: filteredSettlements.length,
totalPages: Math.ceil(filteredSettlements.length / pageSize)
}
}
});
} catch (error) {
res.status(500).json({
success: false,
message: '获取结算列表失败',
error: error.message
});
}
});
// 获取结算详情
router.get('/settlements/:id', (req, res) => {
try {
const { id } = req.params;
const settlement = settlements.find(s => s.id === parseInt(id));
if (!settlement) {
return res.status(404).json({
success: false,
message: '结算记录不存在'
});
}
// 获取相关支付记录
const relatedPayments = payments.filter(p => p.settlementId === settlement.id);
res.json({
success: true,
data: {
...settlement,
payments: relatedPayments
}
});
} catch (error) {
res.status(500).json({
success: false,
message: '获取结算详情失败',
error: error.message
});
}
});
// 创建结算记录
router.post('/settlements', (req, res) => {
try {
const { error, value } = settlementCreateSchema.validate(req.body);
if (error) {
return res.status(400).json({
success: false,
message: '参数验证失败',
errors: error.details.map(detail => detail.message)
});
}
const settlementCode = `SET${String(Date.now()).slice(-6)}`;
const totalAmount = value.cattleCount * value.unitPrice;
const taxAmount = totalAmount * 0.1; // 假设税率10%
const actualPayment = totalAmount - taxAmount;
const newSettlement = {
id: Math.max(...settlements.map(s => s.id)) + 1,
...value,
settlementCode,
totalAmount,
taxAmount,
actualPayment,
paymentStatus: 'pending',
paymentDate: null,
invoiceStatus: 'pending',
supplierName: '供应商名称', // 实际应从订单获取
buyerName: '采购商名称', // 实际应从订单获取
bankAccount: '',
bankName: '',
createdAt: new Date(),
updatedAt: new Date()
};
settlements.push(newSettlement);
res.status(201).json({
success: true,
message: '结算记录创建成功',
data: newSettlement
});
} catch (error) {
res.status(500).json({
success: false,
message: '创建结算记录失败',
error: error.message
});
}
});
// 更新结算状态
router.put('/settlements/:id/status', (req, res) => {
try {
const { id } = req.params;
const { paymentStatus, invoiceStatus } = req.body;
const settlementIndex = settlements.findIndex(s => s.id === parseInt(id));
if (settlementIndex === -1) {
return res.status(404).json({
success: false,
message: '结算记录不存在'
});
}
if (paymentStatus) {
settlements[settlementIndex].paymentStatus = paymentStatus;
if (paymentStatus === 'paid') {
settlements[settlementIndex].paymentDate = new Date().toISOString().split('T')[0];
}
}
if (invoiceStatus) {
settlements[settlementIndex].invoiceStatus = invoiceStatus;
}
settlements[settlementIndex].updatedAt = new Date();
res.json({
success: true,
message: '结算状态更新成功',
data: settlements[settlementIndex]
});
} catch (error) {
res.status(500).json({
success: false,
message: '更新结算状态失败',
error: error.message
});
}
});
// 获取支付记录列表
router.get('/payments', (req, res) => {
try {
const {
page = 1,
pageSize = 20,
settlementId,
status
} = req.query;
let filteredPayments = [...payments];
// 按结算单筛选
if (settlementId) {
filteredPayments = filteredPayments.filter(payment => payment.settlementId === parseInt(settlementId));
}
// 按状态筛选
if (status) {
filteredPayments = filteredPayments.filter(payment => payment.status === status);
}
// 分页处理
const startIndex = (page - 1) * pageSize;
const endIndex = startIndex + parseInt(pageSize);
const paginatedPayments = filteredPayments.slice(startIndex, endIndex);
res.json({
success: true,
data: {
list: paginatedPayments,
pagination: {
page: parseInt(page),
pageSize: parseInt(pageSize),
total: filteredPayments.length,
totalPages: Math.ceil(filteredPayments.length / pageSize)
}
}
});
} catch (error) {
res.status(500).json({
success: false,
message: '获取支付记录失败',
error: error.message
});
}
});
// 创建支付记录
router.post('/payments', (req, res) => {
try {
const { error, value } = paymentCreateSchema.validate(req.body);
if (error) {
return res.status(400).json({
success: false,
message: '参数验证失败',
errors: error.details.map(detail => detail.message)
});
}
const paymentCode = `PAY${String(Date.now()).slice(-6)}`;
const newPayment = {
id: Math.max(...payments.map(p => p.id)) + 1,
...value,
paymentCode,
status: 'processing',
paidAt: null,
createdAt: new Date()
};
payments.push(newPayment);
// 模拟支付处理
setTimeout(() => {
const paymentIndex = payments.findIndex(p => p.id === newPayment.id);
if (paymentIndex !== -1) {
payments[paymentIndex].status = 'success';
payments[paymentIndex].paidAt = new Date().toISOString();
payments[paymentIndex].transactionId = `TXN${Date.now()}`;
// 更新对应结算单状态
const settlementIndex = settlements.findIndex(s => s.id === value.settlementId);
if (settlementIndex !== -1) {
settlements[settlementIndex].paymentStatus = 'paid';
settlements[settlementIndex].paymentDate = new Date().toISOString().split('T')[0];
settlements[settlementIndex].updatedAt = new Date();
}
}
}, 3000); // 3秒后处理完成
res.status(201).json({
success: true,
message: '支付申请已提交',
data: newPayment
});
} catch (error) {
res.status(500).json({
success: false,
message: '创建支付记录失败',
error: error.message
});
}
});
// 获取财务统计
router.get('/stats/overview', (req, res) => {
try {
const totalSettlements = settlements.length;
const paidCount = settlements.filter(s => s.paymentStatus === 'paid').length;
const pendingCount = settlements.filter(s => s.paymentStatus === 'pending').length;
const totalAmount = settlements.reduce((sum, s) => sum + s.totalAmount, 0);
const paidAmount = settlements
.filter(s => s.paymentStatus === 'paid')
.reduce((sum, s) => sum + s.actualPayment, 0);
const pendingAmount = settlements
.filter(s => s.paymentStatus === 'pending')
.reduce((sum, s) => sum + s.actualPayment, 0);
const totalTaxAmount = settlements.reduce((sum, s) => sum + s.taxAmount, 0);
// 本月统计
const currentMonth = new Date().getMonth();
const currentYear = new Date().getFullYear();
const monthlySettlements = settlements.filter(s => {
const settleDate = new Date(s.settlementDate);
return settleDate.getMonth() === currentMonth && settleDate.getFullYear() === currentYear;
});
const monthlyAmount = monthlySettlements.reduce((sum, s) => sum + s.totalAmount, 0);
res.json({
success: true,
data: {
totalSettlements,
paidCount,
pendingCount,
totalAmount,
paidAmount,
pendingAmount,
totalTaxAmount,
monthlyAmount,
paymentRate: totalSettlements > 0 ? Math.round((paidCount / totalSettlements) * 100) : 0
}
});
} catch (error) {
res.status(500).json({
success: false,
message: '获取财务统计失败',
error: error.message
});
}
});
// 获取财务报表
router.get('/reports/monthly', (req, res) => {
try {
const { year = new Date().getFullYear(), month } = req.query;
let targetSettlements = settlements;
// 筛选指定年份
targetSettlements = targetSettlements.filter(s => {
const settleDate = new Date(s.settlementDate);
return settleDate.getFullYear() === parseInt(year);
});
// 如果指定了月份,进一步筛选
if (month) {
targetSettlements = targetSettlements.filter(s => {
const settleDate = new Date(s.settlementDate);
return settleDate.getMonth() === parseInt(month) - 1;
});
}
// 按月份分组统计
const monthlyStats = {};
for (let i = 1; i <= 12; i++) {
monthlyStats[i] = {
month: i,
settlementCount: 0,
totalAmount: 0,
paidAmount: 0,
pendingAmount: 0
};
}
targetSettlements.forEach(settlement => {
const settleMonth = new Date(settlement.settlementDate).getMonth() + 1;
monthlyStats[settleMonth].settlementCount++;
monthlyStats[settleMonth].totalAmount += settlement.totalAmount;
if (settlement.paymentStatus === 'paid') {
monthlyStats[settleMonth].paidAmount += settlement.actualPayment;
} else {
monthlyStats[settleMonth].pendingAmount += settlement.actualPayment;
}
});
res.json({
success: true,
data: {
year: parseInt(year),
monthlyStats: Object.values(monthlyStats)
}
});
} catch (error) {
res.status(500).json({
success: false,
message: '获取财务报表失败',
error: error.message
});
}
});
module.exports = router;

539
backend/routes/orders.js Normal file
View File

@@ -0,0 +1,539 @@
const express = require('express')
const Joi = require('joi')
const router = express.Router()
// 模拟订单数据
let orders = [
{
id: 1,
orderNo: 'ORD20240101001',
buyerId: 2,
buyerName: '山东养殖场',
supplierId: 3,
supplierName: '河北供应商',
traderId: 1,
traderName: '北京贸易公司',
cattleBreed: '西门塔尔',
cattleCount: 50,
expectedWeight: 25000,
actualWeight: 24800,
unitPrice: 28.5,
totalAmount: 712500,
paidAmount: 200000,
remainingAmount: 512500,
status: 'shipping',
deliveryAddress: '山东省济南市某养殖场',
expectedDeliveryDate: '2024-01-15',
actualDeliveryDate: null,
notes: '优质西门塔尔牛',
createdAt: '2024-01-10T00:00:00Z',
updatedAt: '2024-01-12T00:00:00Z'
},
{
id: 2,
orderNo: 'ORD20240101002',
buyerId: 2,
buyerName: '山东养殖场',
supplierId: 4,
supplierName: '内蒙古牧场',
traderId: 1,
traderName: '北京贸易公司',
cattleBreed: '安格斯',
cattleCount: 30,
expectedWeight: 18000,
actualWeight: 18200,
unitPrice: 30.0,
totalAmount: 540000,
paidAmount: 540000,
remainingAmount: 0,
status: 'completed',
deliveryAddress: '山东省济南市某养殖场',
expectedDeliveryDate: '2024-01-08',
actualDeliveryDate: '2024-01-08',
notes: '',
createdAt: '2024-01-05T00:00:00Z',
updatedAt: '2024-01-08T00:00:00Z'
}
]
// 订单状态枚举
const ORDER_STATUS = {
PENDING: 'pending',
CONFIRMED: 'confirmed',
PREPARING: 'preparing',
SHIPPING: 'shipping',
DELIVERED: 'delivered',
ACCEPTED: 'accepted',
COMPLETED: 'completed',
CANCELLED: 'cancelled',
REFUNDED: 'refunded'
}
// 验证模式
const createOrderSchema = Joi.object({
buyerId: Joi.number().integer().positive().required(),
supplierId: Joi.number().integer().positive().required(),
traderId: Joi.number().integer().positive(),
cattleBreed: Joi.string().min(1).max(50).required(),
cattleCount: Joi.number().integer().positive().required(),
expectedWeight: Joi.number().positive().required(),
unitPrice: Joi.number().positive().required(),
deliveryAddress: Joi.string().min(1).max(200).required(),
expectedDeliveryDate: Joi.date().iso().required(),
notes: Joi.string().max(500).allow('')
})
const updateOrderSchema = Joi.object({
cattleBreed: Joi.string().min(1).max(50),
cattleCount: Joi.number().integer().positive(),
expectedWeight: Joi.number().positive(),
actualWeight: Joi.number().positive(),
unitPrice: Joi.number().positive(),
deliveryAddress: Joi.string().min(1).max(200),
expectedDeliveryDate: Joi.date().iso(),
actualDeliveryDate: Joi.date().iso(),
notes: Joi.string().max(500).allow(''),
status: Joi.string().valid(...Object.values(ORDER_STATUS))
})
// 获取订单列表
router.get('/', (req, res) => {
try {
const {
page = 1,
pageSize = 20,
orderNo,
buyerId,
supplierId,
status,
startDate,
endDate
} = req.query
let filteredOrders = [...orders]
// 订单号搜索
if (orderNo) {
filteredOrders = filteredOrders.filter(order =>
order.orderNo.includes(orderNo)
)
}
// 买方筛选
if (buyerId) {
filteredOrders = filteredOrders.filter(order =>
order.buyerId === parseInt(buyerId)
)
}
// 供应商筛选
if (supplierId) {
filteredOrders = filteredOrders.filter(order =>
order.supplierId === parseInt(supplierId)
)
}
// 状态筛选
if (status) {
filteredOrders = filteredOrders.filter(order => order.status === status)
}
// 日期范围筛选
if (startDate) {
filteredOrders = filteredOrders.filter(order =>
new Date(order.createdAt) >= new Date(startDate)
)
}
if (endDate) {
filteredOrders = filteredOrders.filter(order =>
new Date(order.createdAt) <= new Date(endDate)
)
}
// 分页
const total = filteredOrders.length
const startIndex = (page - 1) * pageSize
const endIndex = startIndex + parseInt(pageSize)
const paginatedOrders = filteredOrders.slice(startIndex, endIndex)
res.json({
success: true,
data: {
items: paginatedOrders,
total: total,
page: parseInt(page),
pageSize: parseInt(pageSize),
totalPages: Math.ceil(total / pageSize)
}
})
} catch (error) {
res.status(500).json({
success: false,
message: '获取订单列表失败'
})
}
})
// 获取订单详情
router.get('/:id', (req, res) => {
try {
const { id } = req.params
const order = orders.find(o => o.id === parseInt(id))
if (!order) {
return res.status(404).json({
success: false,
message: '订单不存在'
})
}
res.json({
success: true,
data: order
})
} catch (error) {
res.status(500).json({
success: false,
message: '获取订单详情失败'
})
}
})
// 创建订单
router.post('/', (req, res) => {
try {
// 参数验证
const { error, value } = createOrderSchema.validate(req.body)
if (error) {
return res.status(400).json({
success: false,
message: '参数验证失败',
details: error.details[0].message
})
}
const {
buyerId,
supplierId,
traderId,
cattleBreed,
cattleCount,
expectedWeight,
unitPrice,
deliveryAddress,
expectedDeliveryDate,
notes
} = value
// 生成订单号
const orderNo = `ORD${new Date().toISOString().slice(0, 10).replace(/-/g, '')}${String(orders.length + 1).padStart(3, '0')}`
// 计算总金额
const totalAmount = expectedWeight * unitPrice
// 创建新订单
const newOrder = {
id: Math.max(...orders.map(o => o.id)) + 1,
orderNo,
buyerId,
buyerName: '买方名称', // 实际项目中需要从数据库获取
supplierId,
supplierName: '供应商名称', // 实际项目中需要从数据库获取
traderId: traderId || null,
traderName: traderId ? '贸易商名称' : null,
cattleBreed,
cattleCount,
expectedWeight,
actualWeight: null,
unitPrice,
totalAmount,
paidAmount: 0,
remainingAmount: totalAmount,
status: ORDER_STATUS.PENDING,
deliveryAddress,
expectedDeliveryDate,
actualDeliveryDate: null,
notes: notes || '',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
}
orders.push(newOrder)
res.status(201).json({
success: true,
message: '订单创建成功',
data: newOrder
})
} catch (error) {
res.status(500).json({
success: false,
message: '创建订单失败'
})
}
})
// 更新订单
router.put('/:id', (req, res) => {
try {
const { id } = req.params
const orderIndex = orders.findIndex(o => o.id === parseInt(id))
if (orderIndex === -1) {
return res.status(404).json({
success: false,
message: '订单不存在'
})
}
// 参数验证
const { error, value } = updateOrderSchema.validate(req.body)
if (error) {
return res.status(400).json({
success: false,
message: '参数验证失败',
details: error.details[0].message
})
}
// 更新订单信息
orders[orderIndex] = {
...orders[orderIndex],
...value,
updatedAt: new Date().toISOString()
}
// 如果更新了实际重量,重新计算总金额
if (value.actualWeight && orders[orderIndex].unitPrice) {
orders[orderIndex].totalAmount = value.actualWeight * orders[orderIndex].unitPrice
orders[orderIndex].remainingAmount = orders[orderIndex].totalAmount - orders[orderIndex].paidAmount
}
res.json({
success: true,
message: '订单更新成功',
data: orders[orderIndex]
})
} catch (error) {
res.status(500).json({
success: false,
message: '更新订单失败'
})
}
})
// 删除订单
router.delete('/:id', (req, res) => {
try {
const { id } = req.params
const orderIndex = orders.findIndex(o => o.id === parseInt(id))
if (orderIndex === -1) {
return res.status(404).json({
success: false,
message: '订单不存在'
})
}
orders.splice(orderIndex, 1)
res.json({
success: true,
message: '订单删除成功'
})
} catch (error) {
res.status(500).json({
success: false,
message: '删除订单失败'
})
}
})
// 确认订单
router.put('/:id/confirm', (req, res) => {
try {
const { id } = req.params
const orderIndex = orders.findIndex(o => o.id === parseInt(id))
if (orderIndex === -1) {
return res.status(404).json({
success: false,
message: '订单不存在'
})
}
if (orders[orderIndex].status !== ORDER_STATUS.PENDING) {
return res.status(400).json({
success: false,
message: '只有待确认的订单才能确认'
})
}
orders[orderIndex].status = ORDER_STATUS.CONFIRMED
orders[orderIndex].updatedAt = new Date().toISOString()
res.json({
success: true,
message: '订单确认成功',
data: orders[orderIndex]
})
} catch (error) {
res.status(500).json({
success: false,
message: '确认订单失败'
})
}
})
// 取消订单
router.put('/:id/cancel', (req, res) => {
try {
const { id } = req.params
const { reason } = req.body
const orderIndex = orders.findIndex(o => o.id === parseInt(id))
if (orderIndex === -1) {
return res.status(404).json({
success: false,
message: '订单不存在'
})
}
orders[orderIndex].status = ORDER_STATUS.CANCELLED
orders[orderIndex].notes = reason ? `取消原因: ${reason}` : '订单已取消'
orders[orderIndex].updatedAt = new Date().toISOString()
res.json({
success: true,
message: '订单取消成功',
data: orders[orderIndex]
})
} catch (error) {
res.status(500).json({
success: false,
message: '取消订单失败'
})
}
})
// 订单验收
router.put('/:id/accept', (req, res) => {
try {
const { id } = req.params
const { actualWeight, notes } = req.body
const orderIndex = orders.findIndex(o => o.id === parseInt(id))
if (orderIndex === -1) {
return res.status(404).json({
success: false,
message: '订单不存在'
})
}
if (!actualWeight || actualWeight <= 0) {
return res.status(400).json({
success: false,
message: '请提供有效的实际重量'
})
}
orders[orderIndex].status = ORDER_STATUS.ACCEPTED
orders[orderIndex].actualWeight = actualWeight
orders[orderIndex].totalAmount = actualWeight * orders[orderIndex].unitPrice
orders[orderIndex].remainingAmount = orders[orderIndex].totalAmount - orders[orderIndex].paidAmount
orders[orderIndex].actualDeliveryDate = new Date().toISOString()
if (notes) {
orders[orderIndex].notes = notes
}
orders[orderIndex].updatedAt = new Date().toISOString()
res.json({
success: true,
message: '订单验收成功',
data: orders[orderIndex]
})
} catch (error) {
res.status(500).json({
success: false,
message: '订单验收失败'
})
}
})
// 完成订单
router.put('/:id/complete', (req, res) => {
try {
const { id } = req.params
const orderIndex = orders.findIndex(o => o.id === parseInt(id))
if (orderIndex === -1) {
return res.status(404).json({
success: false,
message: '订单不存在'
})
}
orders[orderIndex].status = ORDER_STATUS.COMPLETED
orders[orderIndex].paidAmount = orders[orderIndex].totalAmount
orders[orderIndex].remainingAmount = 0
orders[orderIndex].updatedAt = new Date().toISOString()
res.json({
success: true,
message: '订单完成成功',
data: orders[orderIndex]
})
} catch (error) {
res.status(500).json({
success: false,
message: '完成订单失败'
})
}
})
// 获取订单统计数据
router.get('/statistics', (req, res) => {
try {
const { startDate, endDate } = req.query
let filteredOrders = [...orders]
if (startDate) {
filteredOrders = filteredOrders.filter(order =>
new Date(order.createdAt) >= new Date(startDate)
)
}
if (endDate) {
filteredOrders = filteredOrders.filter(order =>
new Date(order.createdAt) <= new Date(endDate)
)
}
const statistics = {
totalOrders: filteredOrders.length,
completedOrders: filteredOrders.filter(o => o.status === ORDER_STATUS.COMPLETED).length,
pendingOrders: filteredOrders.filter(o => o.status === ORDER_STATUS.PENDING).length,
cancelledOrders: filteredOrders.filter(o => o.status === ORDER_STATUS.CANCELLED).length,
totalAmount: filteredOrders.reduce((sum, order) => sum + order.totalAmount, 0),
totalCattle: filteredOrders.reduce((sum, order) => sum + order.cattleCount, 0),
statusDistribution: Object.values(ORDER_STATUS).reduce((acc, status) => {
acc[status] = filteredOrders.filter(o => o.status === status).length
return acc
}, {})
}
res.json({
success: true,
data: statistics
})
} catch (error) {
res.status(500).json({
success: false,
message: '获取订单统计失败'
})
}
})
module.exports = router

548
backend/routes/quality.js Normal file
View File

@@ -0,0 +1,548 @@
const express = require('express');
const router = express.Router();
const Joi = require('joi');
// 模拟质量检测数据
let qualityRecords = [
{
id: 1,
orderId: 1,
inspectionCode: 'QC001',
inspectorName: '张检验员',
inspectionDate: '2024-01-15',
inspectionLocation: '山东省济南市历城区牲畜养殖基地',
cattleCount: 50,
samplingCount: 5,
inspectionType: 'pre_transport',
healthStatus: 'healthy',
quarantineCertificate: 'QC001_certificate.pdf',
vaccineRecords: [
{
vaccineName: '口蹄疫疫苗',
vaccineDate: '2024-01-01',
batchNumber: 'VAC20240101'
}
],
diseaseTests: [
{
testName: '布鲁氏菌病检测',
result: 'negative',
testDate: '2024-01-10'
},
{
testName: '结核病检测',
result: 'negative',
testDate: '2024-01-10'
}
],
weightCheck: {
averageWeight: 450,
weightRange: '420-480',
weightVariance: 15
},
qualityGrade: 'A',
qualityScore: 95,
issues: [],
recommendations: [
'建议继续保持当前饲养标准',
'注意观察牲畜健康状况'
],
photos: [
'inspection_001_1.jpg',
'inspection_001_2.jpg'
],
status: 'passed',
createdAt: new Date('2024-01-15'),
updatedAt: new Date('2024-01-15')
},
{
id: 2,
orderId: 2,
inspectionCode: 'QC002',
inspectorName: '李检验员',
inspectionDate: '2024-01-16',
inspectionLocation: '内蒙古呼和浩特市草原牧场',
cattleCount: 80,
samplingCount: 8,
inspectionType: 'pre_transport',
healthStatus: 'healthy',
quarantineCertificate: 'QC002_certificate.pdf',
vaccineRecords: [
{
vaccineName: '口蹄疫疫苗',
vaccineDate: '2023-12-15',
batchNumber: 'VAC20231215'
}
],
diseaseTests: [
{
testName: '布鲁氏菌病检测',
result: 'negative',
testDate: '2024-01-12'
}
],
weightCheck: {
averageWeight: 480,
weightRange: '450-520',
weightVariance: 20
},
qualityGrade: 'A',
qualityScore: 92,
issues: [
{
type: 'minor',
description: '个别牲畜体重偏轻',
solution: '加强营养补充'
}
],
recommendations: [
'对体重偏轻的牲畜进行重点关注',
'适当调整饲料配比'
],
photos: [
'inspection_002_1.jpg',
'inspection_002_2.jpg',
'inspection_002_3.jpg'
],
status: 'passed',
createdAt: new Date('2024-01-16'),
updatedAt: new Date('2024-01-16')
}
];
// 验证schemas
const inspectionCreateSchema = Joi.object({
orderId: Joi.number().integer().required(),
inspectorName: Joi.string().min(2).max(50).required(),
inspectionDate: Joi.date().iso().required(),
inspectionLocation: Joi.string().min(5).max(200).required(),
cattleCount: Joi.number().integer().min(1).required(),
samplingCount: Joi.number().integer().min(1).required(),
inspectionType: Joi.string().valid('pre_transport', 'during_transport', 'post_transport', 'arrival').required()
});
const qualityResultSchema = Joi.object({
healthStatus: Joi.string().valid('healthy', 'sick', 'quarantine').required(),
qualityGrade: Joi.string().valid('A+', 'A', 'B+', 'B', 'C', 'D').required(),
qualityScore: Joi.number().min(0).max(100).required(),
weightCheck: Joi.object({
averageWeight: Joi.number().min(0),
weightRange: Joi.string(),
weightVariance: Joi.number().min(0)
}),
diseaseTests: Joi.array().items(Joi.object({
testName: Joi.string().required(),
result: Joi.string().valid('positive', 'negative', 'inconclusive').required(),
testDate: Joi.date().iso().required()
})),
issues: Joi.array().items(Joi.object({
type: Joi.string().valid('critical', 'major', 'minor').required(),
description: Joi.string().required(),
solution: Joi.string()
})),
recommendations: Joi.array().items(Joi.string())
});
// 获取质量检测列表
router.get('/', (req, res) => {
try {
const {
page = 1,
pageSize = 20,
keyword,
inspectionType,
qualityGrade,
status,
startDate,
endDate
} = req.query;
let filteredRecords = [...qualityRecords];
// 关键词搜索
if (keyword) {
filteredRecords = filteredRecords.filter(record =>
record.inspectionCode.includes(keyword) ||
record.inspectorName.includes(keyword) ||
record.inspectionLocation.includes(keyword)
);
}
// 检测类型筛选
if (inspectionType) {
filteredRecords = filteredRecords.filter(record => record.inspectionType === inspectionType);
}
// 质量等级筛选
if (qualityGrade) {
filteredRecords = filteredRecords.filter(record => record.qualityGrade === qualityGrade);
}
// 状态筛选
if (status) {
filteredRecords = filteredRecords.filter(record => record.status === status);
}
// 时间范围筛选
if (startDate) {
filteredRecords = filteredRecords.filter(record =>
new Date(record.inspectionDate) >= new Date(startDate)
);
}
if (endDate) {
filteredRecords = filteredRecords.filter(record =>
new Date(record.inspectionDate) <= new Date(endDate)
);
}
// 分页处理
const startIndex = (page - 1) * pageSize;
const endIndex = startIndex + parseInt(pageSize);
const paginatedRecords = filteredRecords.slice(startIndex, endIndex);
res.json({
success: true,
data: {
list: paginatedRecords,
pagination: {
page: parseInt(page),
pageSize: parseInt(pageSize),
total: filteredRecords.length,
totalPages: Math.ceil(filteredRecords.length / pageSize)
}
}
});
} catch (error) {
res.status(500).json({
success: false,
message: '获取质量检测列表失败',
error: error.message
});
}
});
// 获取质量检测详情
router.get('/:id', (req, res) => {
try {
const { id } = req.params;
const record = qualityRecords.find(r => r.id === parseInt(id));
if (!record) {
return res.status(404).json({
success: false,
message: '质量检测记录不存在'
});
}
res.json({
success: true,
data: record
});
} catch (error) {
res.status(500).json({
success: false,
message: '获取质量检测详情失败',
error: error.message
});
}
});
// 创建质量检测记录
router.post('/', (req, res) => {
try {
const { error, value } = inspectionCreateSchema.validate(req.body);
if (error) {
return res.status(400).json({
success: false,
message: '参数验证失败',
errors: error.details.map(detail => detail.message)
});
}
const inspectionCode = `QC${String(Date.now()).slice(-6)}`;
const newRecord = {
id: Math.max(...qualityRecords.map(r => r.id)) + 1,
...value,
inspectionCode,
healthStatus: 'pending',
quarantineCertificate: '',
vaccineRecords: [],
diseaseTests: [],
weightCheck: null,
qualityGrade: '',
qualityScore: 0,
issues: [],
recommendations: [],
photos: [],
status: 'pending',
createdAt: new Date(),
updatedAt: new Date()
};
qualityRecords.push(newRecord);
res.status(201).json({
success: true,
message: '质量检测记录创建成功',
data: newRecord
});
} catch (error) {
res.status(500).json({
success: false,
message: '创建质量检测记录失败',
error: error.message
});
}
});
// 更新质量检测结果
router.put('/:id/result', (req, res) => {
try {
const { id } = req.params;
const { error, value } = qualityResultSchema.validate(req.body);
if (error) {
return res.status(400).json({
success: false,
message: '参数验证失败',
errors: error.details.map(detail => detail.message)
});
}
const recordIndex = qualityRecords.findIndex(r => r.id === parseInt(id));
if (recordIndex === -1) {
return res.status(404).json({
success: false,
message: '质量检测记录不存在'
});
}
// 根据检测结果确定状态
let status = 'passed';
if (value.healthStatus === 'sick' || value.qualityScore < 60) {
status = 'failed';
} else if (value.healthStatus === 'quarantine' || value.issues.some(issue => issue.type === 'critical')) {
status = 'quarantine';
}
qualityRecords[recordIndex] = {
...qualityRecords[recordIndex],
...value,
status,
updatedAt: new Date()
};
res.json({
success: true,
message: '质量检测结果更新成功',
data: qualityRecords[recordIndex]
});
} catch (error) {
res.status(500).json({
success: false,
message: '更新质量检测结果失败',
error: error.message
});
}
});
// 上传检测照片
router.post('/:id/photos', (req, res) => {
try {
const { id } = req.params;
const { photos } = req.body;
if (!Array.isArray(photos) || photos.length === 0) {
return res.status(400).json({
success: false,
message: '照片列表不能为空'
});
}
const recordIndex = qualityRecords.findIndex(r => r.id === parseInt(id));
if (recordIndex === -1) {
return res.status(404).json({
success: false,
message: '质量检测记录不存在'
});
}
qualityRecords[recordIndex].photos = [...qualityRecords[recordIndex].photos, ...photos];
qualityRecords[recordIndex].updatedAt = new Date();
res.json({
success: true,
message: '照片上传成功',
data: {
photos: qualityRecords[recordIndex].photos
}
});
} catch (error) {
res.status(500).json({
success: false,
message: '上传照片失败',
error: error.message
});
}
});
// 获取质量统计
router.get('/stats/overview', (req, res) => {
try {
const totalInspections = qualityRecords.length;
const passedCount = qualityRecords.filter(r => r.status === 'passed').length;
const failedCount = qualityRecords.filter(r => r.status === 'failed').length;
const quarantineCount = qualityRecords.filter(r => r.status === 'quarantine').length;
const pendingCount = qualityRecords.filter(r => r.status === 'pending').length;
// 平均质量分数
const completedRecords = qualityRecords.filter(r => r.qualityScore > 0);
const averageScore = completedRecords.length > 0
? completedRecords.reduce((sum, r) => sum + r.qualityScore, 0) / completedRecords.length
: 0;
// 质量等级分布
const gradeDistribution = qualityRecords
.filter(r => r.qualityGrade)
.reduce((dist, record) => {
dist[record.qualityGrade] = (dist[record.qualityGrade] || 0) + 1;
return dist;
}, {});
// 检测类型分布
const typeDistribution = qualityRecords.reduce((dist, record) => {
dist[record.inspectionType] = (dist[record.inspectionType] || 0) + 1;
return dist;
}, {});
// 合格率
const passRate = totalInspections > 0 ? Math.round((passedCount / totalInspections) * 100) : 0;
res.json({
success: true,
data: {
totalInspections,
passedCount,
failedCount,
quarantineCount,
pendingCount,
averageScore: Math.round(averageScore * 10) / 10,
passRate,
gradeDistribution,
typeDistribution
}
});
} catch (error) {
res.status(500).json({
success: false,
message: '获取质量统计失败',
error: error.message
});
}
});
// 获取质量趋势报告
router.get('/reports/trend', (req, res) => {
try {
const { period = 'month' } = req.query;
// 按时间分组统计
const now = new Date();
const trends = [];
if (period === 'month') {
// 最近12个月
for (let i = 11; i >= 0; i--) {
const date = new Date(now.getFullYear(), now.getMonth() - i, 1);
const monthRecords = qualityRecords.filter(r => {
const recordDate = new Date(r.inspectionDate);
return recordDate.getMonth() === date.getMonth() &&
recordDate.getFullYear() === date.getFullYear();
});
const passed = monthRecords.filter(r => r.status === 'passed').length;
const total = monthRecords.length;
const averageScore = monthRecords.length > 0
? monthRecords.reduce((sum, r) => sum + (r.qualityScore || 0), 0) / monthRecords.length
: 0;
trends.push({
period: `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`,
totalInspections: total,
passedCount: passed,
passRate: total > 0 ? Math.round((passed / total) * 100) : 0,
averageScore: Math.round(averageScore * 10) / 10
});
}
}
res.json({
success: true,
data: {
period,
trends
}
});
} catch (error) {
res.status(500).json({
success: false,
message: '获取质量趋势报告失败',
error: error.message
});
}
});
// 获取检测标准配置
router.get('/standards', (req, res) => {
try {
const standards = {
weightStandards: {
cattle: {
min: 400,
max: 600,
optimal: 500
}
},
healthRequirements: [
{
name: '口蹄疫疫苗',
required: true,
validityDays: 365
},
{
name: '布鲁氏菌病检测',
required: true,
validityDays: 30
},
{
name: '结核病检测',
required: true,
validityDays: 30
}
],
gradingCriteria: {
'A+': { minScore: 95, description: '优质级' },
'A': { minScore: 85, description: '良好级' },
'B+': { minScore: 75, description: '合格级' },
'B': { minScore: 65, description: '基本合格级' },
'C': { minScore: 50, description: '待改进级' },
'D': { minScore: 0, description: '不合格级' }
}
};
res.json({
success: true,
data: standards
});
} catch (error) {
res.status(500).json({
success: false,
message: '获取检测标准失败',
error: error.message
});
}
});
module.exports = router;

406
backend/routes/suppliers.js Normal file
View File

@@ -0,0 +1,406 @@
const express = require('express');
const router = express.Router();
const Joi = require('joi');
// 模拟供应商数据
let suppliers = [
{
id: 1,
name: '山东优质牲畜合作社',
code: 'SUP001',
contact: '李经理',
phone: '15888888888',
address: '山东省济南市历城区牲畜养殖基地',
businessLicense: 'SUP001_license.pdf',
qualificationLevel: 'A',
certifications: ['动物防疫合格证', '饲料生产许可证'],
cattleTypes: ['肉牛', '奶牛'],
capacity: 5000,
rating: 4.8,
cooperationStartDate: '2022-01-15',
status: 'active',
region: 'east',
createdAt: new Date('2022-01-15'),
updatedAt: new Date('2024-01-15')
},
{
id: 2,
name: '内蒙古草原牲畜有限公司',
code: 'SUP002',
contact: '王总',
phone: '13999999999',
address: '内蒙古呼和浩特市草原牧场',
businessLicense: 'SUP002_license.pdf',
qualificationLevel: 'A+',
certifications: ['有机认证', '绿色食品认证'],
cattleTypes: ['草原牛', '黄牛'],
capacity: 8000,
rating: 4.9,
cooperationStartDate: '2021-08-20',
status: 'active',
region: 'north',
createdAt: new Date('2021-08-20'),
updatedAt: new Date('2024-01-20')
},
{
id: 3,
name: '四川高原牲畜养殖场',
code: 'SUP003',
contact: '张场长',
phone: '18777777777',
address: '四川省成都市高原养殖区',
businessLicense: 'SUP003_license.pdf',
qualificationLevel: 'B+',
certifications: ['无公害产品认证'],
cattleTypes: ['高原牛'],
capacity: 3000,
rating: 4.5,
cooperationStartDate: '2022-06-10',
status: 'active',
region: 'southwest',
createdAt: new Date('2022-06-10'),
updatedAt: new Date('2024-01-10')
}
];
// 验证schemas
const supplierCreateSchema = Joi.object({
name: Joi.string().min(2).max(100).required(),
code: Joi.string().min(3).max(20).required(),
contact: Joi.string().min(2).max(50).required(),
phone: Joi.string().pattern(/^1[3-9]\d{9}$/).required(),
address: Joi.string().min(5).max(200).required(),
qualificationLevel: Joi.string().valid('A+', 'A', 'B+', 'B', 'C').required(),
cattleTypes: Joi.array().items(Joi.string()).min(1).required(),
capacity: Joi.number().integer().min(1).required(),
region: Joi.string().valid('north', 'south', 'east', 'west', 'northeast', 'northwest', 'southeast', 'southwest', 'central').required()
});
const supplierUpdateSchema = Joi.object({
name: Joi.string().min(2).max(100),
contact: Joi.string().min(2).max(50),
phone: Joi.string().pattern(/^1[3-9]\d{9}$/),
address: Joi.string().min(5).max(200),
qualificationLevel: Joi.string().valid('A+', 'A', 'B+', 'B', 'C'),
cattleTypes: Joi.array().items(Joi.string()).min(1),
capacity: Joi.number().integer().min(1),
region: Joi.string().valid('north', 'south', 'east', 'west', 'northeast', 'northwest', 'southeast', 'southwest', 'central'),
status: Joi.string().valid('active', 'inactive', 'suspended')
});
// 获取供应商列表
router.get('/', (req, res) => {
try {
const {
page = 1,
pageSize = 20,
keyword,
region,
qualificationLevel,
status = 'active'
} = req.query;
let filteredSuppliers = [...suppliers];
// 关键词搜索
if (keyword) {
filteredSuppliers = filteredSuppliers.filter(supplier =>
supplier.name.includes(keyword) ||
supplier.code.includes(keyword) ||
supplier.contact.includes(keyword)
);
}
// 区域筛选
if (region) {
filteredSuppliers = filteredSuppliers.filter(supplier => supplier.region === region);
}
// 资质等级筛选
if (qualificationLevel) {
filteredSuppliers = filteredSuppliers.filter(supplier => supplier.qualificationLevel === qualificationLevel);
}
// 状态筛选
if (status) {
filteredSuppliers = filteredSuppliers.filter(supplier => supplier.status === status);
}
// 分页处理
const startIndex = (page - 1) * pageSize;
const endIndex = startIndex + parseInt(pageSize);
const paginatedSuppliers = filteredSuppliers.slice(startIndex, endIndex);
res.json({
success: true,
data: {
list: paginatedSuppliers,
pagination: {
page: parseInt(page),
pageSize: parseInt(pageSize),
total: filteredSuppliers.length,
totalPages: Math.ceil(filteredSuppliers.length / pageSize)
}
}
});
} catch (error) {
res.status(500).json({
success: false,
message: '获取供应商列表失败',
error: error.message
});
}
});
// 获取供应商详情
router.get('/:id', (req, res) => {
try {
const { id } = req.params;
const supplier = suppliers.find(s => s.id === parseInt(id));
if (!supplier) {
return res.status(404).json({
success: false,
message: '供应商不存在'
});
}
res.json({
success: true,
data: supplier
});
} catch (error) {
res.status(500).json({
success: false,
message: '获取供应商详情失败',
error: error.message
});
}
});
// 创建供应商
router.post('/', (req, res) => {
try {
const { error, value } = supplierCreateSchema.validate(req.body);
if (error) {
return res.status(400).json({
success: false,
message: '参数验证失败',
errors: error.details.map(detail => detail.message)
});
}
// 检查编码是否重复
const existingSupplier = suppliers.find(s => s.code === value.code);
if (existingSupplier) {
return res.status(400).json({
success: false,
message: '供应商编码已存在'
});
}
const newSupplier = {
id: Math.max(...suppliers.map(s => s.id)) + 1,
...value,
businessLicense: '',
certifications: [],
rating: 0,
cooperationStartDate: new Date().toISOString().split('T')[0],
status: 'active',
createdAt: new Date(),
updatedAt: new Date()
};
suppliers.push(newSupplier);
res.status(201).json({
success: true,
message: '供应商创建成功',
data: newSupplier
});
} catch (error) {
res.status(500).json({
success: false,
message: '创建供应商失败',
error: error.message
});
}
});
// 更新供应商
router.put('/:id', (req, res) => {
try {
const { id } = req.params;
const { error, value } = supplierUpdateSchema.validate(req.body);
if (error) {
return res.status(400).json({
success: false,
message: '参数验证失败',
errors: error.details.map(detail => detail.message)
});
}
const supplierIndex = suppliers.findIndex(s => s.id === parseInt(id));
if (supplierIndex === -1) {
return res.status(404).json({
success: false,
message: '供应商不存在'
});
}
suppliers[supplierIndex] = {
...suppliers[supplierIndex],
...value,
updatedAt: new Date()
};
res.json({
success: true,
message: '供应商更新成功',
data: suppliers[supplierIndex]
});
} catch (error) {
res.status(500).json({
success: false,
message: '更新供应商失败',
error: error.message
});
}
});
// 删除供应商
router.delete('/:id', (req, res) => {
try {
const { id } = req.params;
const supplierIndex = suppliers.findIndex(s => s.id === parseInt(id));
if (supplierIndex === -1) {
return res.status(404).json({
success: false,
message: '供应商不存在'
});
}
suppliers.splice(supplierIndex, 1);
res.json({
success: true,
message: '供应商删除成功'
});
} catch (error) {
res.status(500).json({
success: false,
message: '删除供应商失败',
error: error.message
});
}
});
// 获取供应商统计信息
router.get('/stats/overview', (req, res) => {
try {
const totalSuppliers = suppliers.length;
const activeSuppliers = suppliers.filter(s => s.status === 'active').length;
const averageRating = suppliers.reduce((sum, s) => sum + s.rating, 0) / totalSuppliers;
const totalCapacity = suppliers.reduce((sum, s) => sum + s.capacity, 0);
// 按等级统计
const levelStats = suppliers.reduce((stats, supplier) => {
stats[supplier.qualificationLevel] = (stats[supplier.qualificationLevel] || 0) + 1;
return stats;
}, {});
// 按区域统计
const regionStats = suppliers.reduce((stats, supplier) => {
stats[supplier.region] = (stats[supplier.region] || 0) + 1;
return stats;
}, {});
res.json({
success: true,
data: {
totalSuppliers,
activeSuppliers,
averageRating: Math.round(averageRating * 10) / 10,
totalCapacity,
levelStats,
regionStats
}
});
} catch (error) {
res.status(500).json({
success: false,
message: '获取供应商统计信息失败',
error: error.message
});
}
});
// 批量操作
router.post('/batch', (req, res) => {
try {
const { action, ids } = req.body;
if (!action || !Array.isArray(ids) || ids.length === 0) {
return res.status(400).json({
success: false,
message: '参数错误'
});
}
let affectedCount = 0;
switch (action) {
case 'activate':
suppliers.forEach(supplier => {
if (ids.includes(supplier.id)) {
supplier.status = 'active';
supplier.updatedAt = new Date();
affectedCount++;
}
});
break;
case 'deactivate':
suppliers.forEach(supplier => {
if (ids.includes(supplier.id)) {
supplier.status = 'inactive';
supplier.updatedAt = new Date();
affectedCount++;
}
});
break;
case 'delete':
suppliers = suppliers.filter(supplier => {
if (ids.includes(supplier.id)) {
affectedCount++;
return false;
}
return true;
});
break;
default:
return res.status(400).json({
success: false,
message: '不支持的操作类型'
});
}
res.json({
success: true,
message: `批量${action}成功`,
data: { affectedCount }
});
} catch (error) {
res.status(500).json({
success: false,
message: '批量操作失败',
error: error.message
});
}
});
module.exports = router;

467
backend/routes/transport.js Normal file
View File

@@ -0,0 +1,467 @@
const express = require('express');
const router = express.Router();
const Joi = require('joi');
// 模拟运输数据
let transports = [
{
id: 1,
orderId: 1,
transportCode: 'TRP001',
driverName: '张师傅',
driverPhone: '13800001111',
vehicleNumber: '鲁A12345',
vehicleType: '厢式货车',
startLocation: '山东省济南市历城区牲畜养殖基地',
endLocation: '北京市朝阳区肉类加工厂',
plannedDepartureTime: '2024-01-15T08:00:00Z',
actualDepartureTime: '2024-01-15T08:30:00Z',
estimatedArrivalTime: '2024-01-15T18:00:00Z',
actualArrivalTime: null,
distance: 450,
status: 'in_transit',
currentLocation: {
lat: 36.8012,
lng: 117.1120,
address: '山东省济南市天桥区',
updateTime: '2024-01-15T14:30:00Z'
},
route: [
{ lat: 36.6512, lng: 117.1201, time: '2024-01-15T08:30:00Z' },
{ lat: 36.7012, lng: 117.1001, time: '2024-01-15T10:30:00Z' },
{ lat: 36.8012, lng: 117.1120, time: '2024-01-15T14:30:00Z' }
],
cattleCount: 50,
temperature: 18,
humidity: 65,
alerts: [],
createdAt: new Date('2024-01-15T08:00:00Z'),
updatedAt: new Date('2024-01-15T14:30:00Z')
},
{
id: 2,
orderId: 2,
transportCode: 'TRP002',
driverName: '李师傅',
driverPhone: '13800002222',
vehicleNumber: '蒙B67890',
vehicleType: '专用运牛车',
startLocation: '内蒙古呼和浩特市草原牧场',
endLocation: '天津市滨海新区屠宰场',
plannedDepartureTime: '2024-01-16T06:00:00Z',
actualDepartureTime: '2024-01-16T06:15:00Z',
estimatedArrivalTime: '2024-01-16T20:00:00Z',
actualArrivalTime: '2024-01-16T19:45:00Z',
distance: 680,
status: 'completed',
currentLocation: {
lat: 39.3434,
lng: 117.3616,
address: '天津市滨海新区',
updateTime: '2024-01-16T19:45:00Z'
},
route: [
{ lat: 40.8420, lng: 111.7520, time: '2024-01-16T06:15:00Z' },
{ lat: 40.1420, lng: 114.7520, time: '2024-01-16T12:15:00Z' },
{ lat: 39.3434, lng: 117.3616, time: '2024-01-16T19:45:00Z' }
],
cattleCount: 80,
temperature: 15,
humidity: 70,
alerts: [
{
type: 'temperature',
message: '车厢温度偏高',
time: '2024-01-16T14:30:00Z',
resolved: true
}
],
createdAt: new Date('2024-01-16T06:00:00Z'),
updatedAt: new Date('2024-01-16T19:45:00Z')
}
];
// 验证schemas
const transportCreateSchema = Joi.object({
orderId: Joi.number().integer().required(),
driverName: Joi.string().min(2).max(50).required(),
driverPhone: Joi.string().pattern(/^1[3-9]\d{9}$/).required(),
vehicleNumber: Joi.string().min(5).max(20).required(),
vehicleType: Joi.string().min(2).max(50).required(),
startLocation: Joi.string().min(5).max(200).required(),
endLocation: Joi.string().min(5).max(200).required(),
plannedDepartureTime: Joi.date().iso().required(),
estimatedArrivalTime: Joi.date().iso().required(),
distance: Joi.number().min(1).required(),
cattleCount: Joi.number().integer().min(1).required()
});
const locationUpdateSchema = Joi.object({
lat: Joi.number().min(-90).max(90).required(),
lng: Joi.number().min(-180).max(180).required(),
address: Joi.string().max(200),
temperature: Joi.number().min(-50).max(50),
humidity: Joi.number().min(0).max(100)
});
const statusUpdateSchema = Joi.object({
status: Joi.string().valid('pending', 'loading', 'in_transit', 'arrived', 'completed', 'cancelled').required(),
actualTime: Joi.date().iso()
});
// 获取运输列表
router.get('/', (req, res) => {
try {
const {
page = 1,
pageSize = 20,
keyword,
status,
startDate,
endDate
} = req.query;
let filteredTransports = [...transports];
// 关键词搜索
if (keyword) {
filteredTransports = filteredTransports.filter(transport =>
transport.transportCode.includes(keyword) ||
transport.driverName.includes(keyword) ||
transport.vehicleNumber.includes(keyword)
);
}
// 状态筛选
if (status) {
filteredTransports = filteredTransports.filter(transport => transport.status === status);
}
// 时间范围筛选
if (startDate) {
filteredTransports = filteredTransports.filter(transport =>
new Date(transport.plannedDepartureTime) >= new Date(startDate)
);
}
if (endDate) {
filteredTransports = filteredTransports.filter(transport =>
new Date(transport.plannedDepartureTime) <= new Date(endDate)
);
}
// 分页处理
const startIndex = (page - 1) * pageSize;
const endIndex = startIndex + parseInt(pageSize);
const paginatedTransports = filteredTransports.slice(startIndex, endIndex);
res.json({
success: true,
data: {
list: paginatedTransports,
pagination: {
page: parseInt(page),
pageSize: parseInt(pageSize),
total: filteredTransports.length,
totalPages: Math.ceil(filteredTransports.length / pageSize)
}
}
});
} catch (error) {
res.status(500).json({
success: false,
message: '获取运输列表失败',
error: error.message
});
}
});
// 获取运输详情
router.get('/:id', (req, res) => {
try {
const { id } = req.params;
const transport = transports.find(t => t.id === parseInt(id));
if (!transport) {
return res.status(404).json({
success: false,
message: '运输记录不存在'
});
}
res.json({
success: true,
data: transport
});
} catch (error) {
res.status(500).json({
success: false,
message: '获取运输详情失败',
error: error.message
});
}
});
// 创建运输任务
router.post('/', (req, res) => {
try {
const { error, value } = transportCreateSchema.validate(req.body);
if (error) {
return res.status(400).json({
success: false,
message: '参数验证失败',
errors: error.details.map(detail => detail.message)
});
}
const transportCode = `TRP${String(Date.now()).slice(-6)}`;
const newTransport = {
id: Math.max(...transports.map(t => t.id)) + 1,
...value,
transportCode,
actualDepartureTime: null,
actualArrivalTime: null,
status: 'pending',
currentLocation: null,
route: [],
temperature: null,
humidity: null,
alerts: [],
createdAt: new Date(),
updatedAt: new Date()
};
transports.push(newTransport);
res.status(201).json({
success: true,
message: '运输任务创建成功',
data: newTransport
});
} catch (error) {
res.status(500).json({
success: false,
message: '创建运输任务失败',
error: error.message
});
}
});
// 更新位置信息
router.post('/:id/location', (req, res) => {
try {
const { id } = req.params;
const { error, value } = locationUpdateSchema.validate(req.body);
if (error) {
return res.status(400).json({
success: false,
message: '参数验证失败',
errors: error.details.map(detail => detail.message)
});
}
const transportIndex = transports.findIndex(t => t.id === parseInt(id));
if (transportIndex === -1) {
return res.status(404).json({
success: false,
message: '运输记录不存在'
});
}
const currentTime = new Date();
const locationData = {
...value,
updateTime: currentTime.toISOString()
};
// 更新当前位置
transports[transportIndex].currentLocation = locationData;
// 添加到路径轨迹
transports[transportIndex].route.push({
lat: value.lat,
lng: value.lng,
time: currentTime.toISOString()
});
// 更新温度和湿度
if (value.temperature !== undefined) {
transports[transportIndex].temperature = value.temperature;
}
if (value.humidity !== undefined) {
transports[transportIndex].humidity = value.humidity;
}
transports[transportIndex].updatedAt = currentTime;
res.json({
success: true,
message: '位置更新成功',
data: transports[transportIndex]
});
} catch (error) {
res.status(500).json({
success: false,
message: '更新位置失败',
error: error.message
});
}
});
// 更新运输状态
router.put('/:id/status', (req, res) => {
try {
const { id } = req.params;
const { error, value } = statusUpdateSchema.validate(req.body);
if (error) {
return res.status(400).json({
success: false,
message: '参数验证失败',
errors: error.details.map(detail => detail.message)
});
}
const transportIndex = transports.findIndex(t => t.id === parseInt(id));
if (transportIndex === -1) {
return res.status(404).json({
success: false,
message: '运输记录不存在'
});
}
const currentTime = new Date();
transports[transportIndex].status = value.status;
// 根据状态更新实际时间
if (value.status === 'in_transit' && !transports[transportIndex].actualDepartureTime) {
transports[transportIndex].actualDepartureTime = value.actualTime || currentTime.toISOString();
} else if (value.status === 'completed' && !transports[transportIndex].actualArrivalTime) {
transports[transportIndex].actualArrivalTime = value.actualTime || currentTime.toISOString();
}
transports[transportIndex].updatedAt = currentTime;
res.json({
success: true,
message: '状态更新成功',
data: transports[transportIndex]
});
} catch (error) {
res.status(500).json({
success: false,
message: '更新状态失败',
error: error.message
});
}
});
// 获取运输统计
router.get('/stats/overview', (req, res) => {
try {
const totalTransports = transports.length;
const inTransitCount = transports.filter(t => t.status === 'in_transit').length;
const completedCount = transports.filter(t => t.status === 'completed').length;
const pendingCount = transports.filter(t => t.status === 'pending').length;
// 平均运输时间(已完成的订单)
const completedTransports = transports.filter(t => t.status === 'completed' && t.actualDepartureTime && t.actualArrivalTime);
const averageTransitTime = completedTransports.length > 0
? completedTransports.reduce((sum, t) => {
const departureTime = new Date(t.actualDepartureTime);
const arrivalTime = new Date(t.actualArrivalTime);
return sum + (arrivalTime - departureTime);
}, 0) / completedTransports.length / (1000 * 60 * 60) // 转换为小时
: 0;
// 总运输距离
const totalDistance = transports.reduce((sum, t) => sum + t.distance, 0);
// 总运输牲畜数量
const totalCattleCount = transports.reduce((sum, t) => sum + t.cattleCount, 0);
res.json({
success: true,
data: {
totalTransports,
inTransitCount,
completedCount,
pendingCount,
averageTransitTime: Math.round(averageTransitTime * 10) / 10,
totalDistance,
totalCattleCount
}
});
} catch (error) {
res.status(500).json({
success: false,
message: '获取运输统计失败',
error: error.message
});
}
});
// 获取实时运输地图数据
router.get('/map/realtime', (req, res) => {
try {
const activeTransports = transports
.filter(t => t.status === 'in_transit' && t.currentLocation)
.map(t => ({
id: t.id,
transportCode: t.transportCode,
driverName: t.driverName,
vehicleNumber: t.vehicleNumber,
currentLocation: t.currentLocation,
destination: t.endLocation,
cattleCount: t.cattleCount,
estimatedArrivalTime: t.estimatedArrivalTime
}));
res.json({
success: true,
data: activeTransports
});
} catch (error) {
res.status(500).json({
success: false,
message: '获取实时地图数据失败',
error: error.message
});
}
});
// 获取运输轨迹
router.get('/:id/route', (req, res) => {
try {
const { id } = req.params;
const transport = transports.find(t => t.id === parseInt(id));
if (!transport) {
return res.status(404).json({
success: false,
message: '运输记录不存在'
});
}
res.json({
success: true,
data: {
transportCode: transport.transportCode,
startLocation: transport.startLocation,
endLocation: transport.endLocation,
route: transport.route,
currentLocation: transport.currentLocation
}
});
} catch (error) {
res.status(500).json({
success: false,
message: '获取运输轨迹失败',
error: error.message
});
}
});
module.exports = router;

359
backend/routes/users.js Normal file
View File

@@ -0,0 +1,359 @@
const express = require('express')
const bcrypt = require('bcryptjs')
const Joi = require('joi')
const router = express.Router()
// 模拟用户数据
let users = [
{
id: 1,
username: 'admin',
email: 'admin@example.com',
phone: '13800138000',
role: 'admin',
status: 'active',
createdAt: '2024-01-01T00:00:00Z',
updatedAt: '2024-01-01T00:00:00Z'
},
{
id: 2,
username: 'buyer01',
email: 'buyer01@example.com',
phone: '13800138001',
role: 'buyer',
status: 'active',
createdAt: '2024-01-02T00:00:00Z',
updatedAt: '2024-01-02T00:00:00Z'
},
{
id: 3,
username: 'supplier01',
email: 'supplier01@example.com',
phone: '13800138002',
role: 'supplier',
status: 'inactive',
createdAt: '2024-01-03T00:00:00Z',
updatedAt: '2024-01-03T00:00:00Z'
}
]
// 验证模式
const createUserSchema = Joi.object({
username: Joi.string().min(2).max(50).required(),
email: Joi.string().email().required(),
phone: Joi.string().pattern(/^1[3-9]\d{9}$/).allow(''),
password: Joi.string().min(6).max(100).required(),
role: Joi.string().valid('admin', 'buyer', 'trader', 'supplier', 'driver').required(),
status: Joi.string().valid('active', 'inactive').default('active')
})
const updateUserSchema = Joi.object({
username: Joi.string().min(2).max(50),
email: Joi.string().email(),
phone: Joi.string().pattern(/^1[3-9]\d{9}$/).allow(''),
role: Joi.string().valid('admin', 'buyer', 'trader', 'supplier', 'driver'),
status: Joi.string().valid('active', 'inactive', 'banned')
})
// 获取用户列表
router.get('/', (req, res) => {
try {
const { page = 1, pageSize = 20, keyword, role, status } = req.query
let filteredUsers = [...users]
// 关键词搜索
if (keyword) {
filteredUsers = filteredUsers.filter(user =>
user.username.includes(keyword) ||
user.email.includes(keyword)
)
}
// 角色筛选
if (role) {
filteredUsers = filteredUsers.filter(user => user.role === role)
}
// 状态筛选
if (status) {
filteredUsers = filteredUsers.filter(user => user.status === status)
}
// 分页
const total = filteredUsers.length
const startIndex = (page - 1) * pageSize
const endIndex = startIndex + parseInt(pageSize)
const paginatedUsers = filteredUsers.slice(startIndex, endIndex)
res.json({
success: true,
data: {
items: paginatedUsers,
total: total,
page: parseInt(page),
pageSize: parseInt(pageSize),
totalPages: Math.ceil(total / pageSize)
}
})
} catch (error) {
res.status(500).json({
success: false,
message: '获取用户列表失败'
})
}
})
// 获取用户详情
router.get('/:id', (req, res) => {
try {
const { id } = req.params
const user = users.find(u => u.id === parseInt(id))
if (!user) {
return res.status(404).json({
success: false,
message: '用户不存在'
})
}
res.json({
success: true,
data: user
})
} catch (error) {
res.status(500).json({
success: false,
message: '获取用户详情失败'
})
}
})
// 创建用户
router.post('/', async (req, res) => {
try {
// 参数验证
const { error, value } = createUserSchema.validate(req.body)
if (error) {
return res.status(400).json({
success: false,
message: '参数验证失败',
details: error.details[0].message
})
}
const { username, email, phone, password, role, status } = value
// 检查用户名是否已存在
if (users.find(u => u.username === username)) {
return res.status(400).json({
success: false,
message: '用户名已存在'
})
}
// 检查邮箱是否已存在
if (users.find(u => u.email === email)) {
return res.status(400).json({
success: false,
message: '邮箱已存在'
})
}
// 创建新用户
const newUser = {
id: Math.max(...users.map(u => u.id)) + 1,
username,
email,
phone: phone || '',
role,
status,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
}
users.push(newUser)
res.status(201).json({
success: true,
message: '用户创建成功',
data: newUser
})
} catch (error) {
res.status(500).json({
success: false,
message: '创建用户失败'
})
}
})
// 更新用户
router.put('/:id', (req, res) => {
try {
const { id } = req.params
const userIndex = users.findIndex(u => u.id === parseInt(id))
if (userIndex === -1) {
return res.status(404).json({
success: false,
message: '用户不存在'
})
}
// 参数验证
const { error, value } = updateUserSchema.validate(req.body)
if (error) {
return res.status(400).json({
success: false,
message: '参数验证失败',
details: error.details[0].message
})
}
// 更新用户信息
users[userIndex] = {
...users[userIndex],
...value,
updatedAt: new Date().toISOString()
}
res.json({
success: true,
message: '用户更新成功',
data: users[userIndex]
})
} catch (error) {
res.status(500).json({
success: false,
message: '更新用户失败'
})
}
})
// 删除用户
router.delete('/:id', (req, res) => {
try {
const { id } = req.params
const userIndex = users.findIndex(u => u.id === parseInt(id))
if (userIndex === -1) {
return res.status(404).json({
success: false,
message: '用户不存在'
})
}
users.splice(userIndex, 1)
res.json({
success: true,
message: '用户删除成功'
})
} catch (error) {
res.status(500).json({
success: false,
message: '删除用户失败'
})
}
})
// 批量删除用户
router.delete('/batch', (req, res) => {
try {
const { ids } = req.body
if (!Array.isArray(ids) || ids.length === 0) {
return res.status(400).json({
success: false,
message: '请提供有效的用户ID列表'
})
}
users = users.filter(user => !ids.includes(user.id))
res.json({
success: true,
message: `成功删除 ${ids.length} 个用户`
})
} catch (error) {
res.status(500).json({
success: false,
message: '批量删除用户失败'
})
}
})
// 重置用户密码
router.put('/:id/password', async (req, res) => {
try {
const { id } = req.params
const { password } = req.body
const userIndex = users.findIndex(u => u.id === parseInt(id))
if (userIndex === -1) {
return res.status(404).json({
success: false,
message: '用户不存在'
})
}
if (!password || password.length < 6) {
return res.status(400).json({
success: false,
message: '密码长度不能少于6位'
})
}
// 在实际项目中,这里会对密码进行加密
users[userIndex].updatedAt = new Date().toISOString()
res.json({
success: true,
message: '密码重置成功'
})
} catch (error) {
res.status(500).json({
success: false,
message: '重置密码失败'
})
}
})
// 更新用户状态
router.put('/:id/status', (req, res) => {
try {
const { id } = req.params
const { status } = req.body
const userIndex = users.findIndex(u => u.id === parseInt(id))
if (userIndex === -1) {
return res.status(404).json({
success: false,
message: '用户不存在'
})
}
if (!['active', 'inactive', 'banned'].includes(status)) {
return res.status(400).json({
success: false,
message: '无效的用户状态'
})
}
users[userIndex].status = status
users[userIndex].updatedAt = new Date().toISOString()
res.json({
success: true,
message: '用户状态更新成功',
data: users[userIndex]
})
} catch (error) {
res.status(500).json({
success: false,
message: '更新用户状态失败'
})
}
})
module.exports = router