1299 lines
43 KiB
JavaScript
1299 lines
43 KiB
JavaScript
const { Op } = require('sequelize');
|
||
const XLSX = require('xlsx');
|
||
const path = require('path');
|
||
const IotCattle = require('../models/IotCattle');
|
||
const Farm = require('../models/Farm');
|
||
const CattlePen = require('../models/CattlePen');
|
||
const CattleBatch = require('../models/CattleBatch');
|
||
const CattleType = require('../models/CattleType');
|
||
const CattleUser = require('../models/CattleUser');
|
||
|
||
/**
|
||
* 计算月龄(基于birthday时间戳)
|
||
*/
|
||
const calculateAgeInMonths = (birthday) => {
|
||
if (!birthday) return 0;
|
||
|
||
const now = Math.floor(Date.now() / 1000); // 当前时间戳(秒)
|
||
const birthTimestamp = parseInt(birthday);
|
||
|
||
if (isNaN(birthTimestamp)) return 0;
|
||
|
||
const ageInSeconds = now - birthTimestamp;
|
||
const ageInMonths = Math.floor(ageInSeconds / (30 * 24 * 60 * 60)); // 按30天一个月计算
|
||
|
||
return Math.max(0, ageInMonths);
|
||
};
|
||
|
||
/**
|
||
* 类别中文映射(与前端保持一致)
|
||
*/
|
||
const getCategoryName = (cate) => {
|
||
const categoryMap = {
|
||
1: '犊牛',
|
||
2: '育成母牛',
|
||
3: '架子牛',
|
||
4: '青年牛',
|
||
5: '基础母牛',
|
||
6: '育肥牛'
|
||
};
|
||
return categoryMap[cate] || '未知';
|
||
};
|
||
|
||
/**
|
||
* 类别名称到ID的映射(用于导入)
|
||
*/
|
||
const getCategoryId = (name) => {
|
||
const categoryMap = {
|
||
'犊牛': 1,
|
||
'育成母牛': 2,
|
||
'架子牛': 3,
|
||
'青年牛': 4,
|
||
'基础母牛': 5,
|
||
'育肥牛': 6
|
||
};
|
||
return categoryMap[name] || null;
|
||
};
|
||
|
||
/**
|
||
* 性别名称到ID的映射(用于导入)
|
||
*/
|
||
const getSexId = (name) => {
|
||
const sexMap = {
|
||
'公': 1,
|
||
'公牛': 1,
|
||
'母': 2,
|
||
'母牛': 2
|
||
};
|
||
return sexMap[name] || null;
|
||
};
|
||
|
||
/**
|
||
* 来源名称到ID的映射(用于导入)
|
||
*/
|
||
const getSourceId = (name) => {
|
||
const sourceMap = {
|
||
'购买': 1,
|
||
'自繁': 2,
|
||
'放生': 3,
|
||
'合作社': 4,
|
||
'入股': 5
|
||
};
|
||
return sourceMap[name] || null;
|
||
};
|
||
|
||
/**
|
||
* 血统纯度名称到ID的映射(用于导入)
|
||
*/
|
||
const getDescentId = (name) => {
|
||
const descentMap = {
|
||
'纯血': 1,
|
||
'纯种': 1,
|
||
'杂交': 2,
|
||
'杂交一代': 2,
|
||
'杂交二代': 3,
|
||
'杂交三代': 4
|
||
};
|
||
return descentMap[name] || null;
|
||
};
|
||
|
||
/**
|
||
* 日期字符串转换为时间戳(秒)
|
||
*/
|
||
const dateToTimestamp = (dateStr) => {
|
||
if (!dateStr) return null;
|
||
|
||
// 支持多种日期格式:2023-01-01, 2023/01/01, 2023-1-1
|
||
const date = new Date(dateStr);
|
||
if (isNaN(date.getTime())) {
|
||
return null;
|
||
}
|
||
return Math.floor(date.getTime() / 1000);
|
||
};
|
||
|
||
/**
|
||
* 获取栏舍、批次、品种和用途名称
|
||
*/
|
||
const getPenBatchTypeAndUserNames = async (cattleList) => {
|
||
// 获取所有唯一的栏舍ID、批次ID、品种ID和品系ID(用途)
|
||
const penIds = [...new Set(cattleList.map(cattle => cattle.penId).filter(id => id))];
|
||
const batchIds = [...new Set(cattleList.map(cattle => cattle.batchId).filter(id => id && id > 0))];
|
||
const typeIds = [...new Set(cattleList.map(cattle => cattle.varieties).filter(id => id))];
|
||
const strainIds = [...new Set(cattleList.map(cattle => cattle.strain).filter(id => id))];
|
||
|
||
// 查询栏舍名称
|
||
const penNames = {};
|
||
if (penIds.length > 0) {
|
||
const pens = await CattlePen.findAll({
|
||
where: { id: penIds },
|
||
attributes: ['id', 'name']
|
||
});
|
||
pens.forEach(pen => {
|
||
penNames[pen.id] = pen.name;
|
||
});
|
||
}
|
||
|
||
// 查询批次名称
|
||
const batchNames = {};
|
||
if (batchIds.length > 0) {
|
||
const batches = await CattleBatch.findAll({
|
||
where: { id: batchIds },
|
||
attributes: ['id', 'name']
|
||
});
|
||
batches.forEach(batch => {
|
||
batchNames[batch.id] = batch.name;
|
||
});
|
||
}
|
||
|
||
// 查询品种名称
|
||
const typeNames = {};
|
||
if (typeIds.length > 0) {
|
||
const types = await CattleType.findAll({
|
||
where: { id: typeIds },
|
||
attributes: ['id', 'name']
|
||
});
|
||
types.forEach(type => {
|
||
typeNames[type.id] = type.name;
|
||
});
|
||
}
|
||
|
||
// 查询用途名称(基于strain字段)
|
||
const userNames = {};
|
||
if (strainIds.length > 0) {
|
||
const users = await CattleUser.findAll({
|
||
where: { id: strainIds },
|
||
attributes: ['id', 'name']
|
||
});
|
||
users.forEach(user => {
|
||
userNames[user.id] = user.name;
|
||
});
|
||
}
|
||
|
||
return { penNames, batchNames, typeNames, userNames };
|
||
};
|
||
|
||
/**
|
||
* 牛只档案控制器 - 基于iot_cattle表
|
||
*/
|
||
class IotCattleController {
|
||
/**
|
||
* 获取牛只档案列表
|
||
*/
|
||
async getCattleArchives(req, res) {
|
||
try {
|
||
const {
|
||
page = 1,
|
||
pageSize = 10,
|
||
search = '',
|
||
farmId = '',
|
||
penId = '',
|
||
batchId = ''
|
||
} = req.query;
|
||
|
||
console.log('=== 后端接收搜索请求 ===');
|
||
console.log('请求时间:', new Date().toISOString());
|
||
console.log('请求参数:', { page, pageSize, search, farmId, penId, batchId });
|
||
console.log('请求来源:', req.ip);
|
||
console.log('User-Agent:', req.get('User-Agent'));
|
||
|
||
const offset = (page - 1) * pageSize;
|
||
const whereConditions = {};
|
||
|
||
// 搜索条件
|
||
if (search) {
|
||
// 尝试将搜索词转换为数字,如果成功则按数字搜索,否则按字符串搜索
|
||
const searchNumber = parseInt(search);
|
||
if (!isNaN(searchNumber)) {
|
||
// 数字搜索:精确匹配耳号
|
||
whereConditions[Op.or] = [
|
||
{ earNumber: searchNumber },
|
||
{ strain: { [Op.like]: `%${search}%` } }
|
||
];
|
||
} else {
|
||
// 字符串搜索:模糊匹配
|
||
whereConditions[Op.or] = [
|
||
{ strain: { [Op.like]: `%${search}%` } }
|
||
];
|
||
}
|
||
console.log('=== 搜索条件构建 ===');
|
||
console.log('搜索关键词:', search);
|
||
console.log('搜索条件对象:', JSON.stringify(whereConditions, null, 2));
|
||
}
|
||
|
||
// 农场筛选
|
||
if (farmId) {
|
||
whereConditions.orgId = farmId;
|
||
console.log('添加农场筛选条件:', farmId);
|
||
}
|
||
|
||
// 栏舍筛选
|
||
if (penId) {
|
||
whereConditions.penId = penId;
|
||
console.log('添加栏舍筛选条件:', penId);
|
||
}
|
||
|
||
// 批次筛选
|
||
if (batchId) {
|
||
whereConditions.batchId = batchId;
|
||
console.log('添加批次筛选条件:', batchId);
|
||
}
|
||
|
||
console.log('=== 最终查询条件 ===');
|
||
console.log('完整查询条件:', JSON.stringify(whereConditions, null, 2));
|
||
console.log('分页参数:', { offset, limit: pageSize });
|
||
|
||
// 先获取总数
|
||
console.log('=== 开始数据库查询 ===');
|
||
console.log('查询时间:', new Date().toISOString());
|
||
|
||
const countStartTime = Date.now();
|
||
const totalCount = await IotCattle.count({
|
||
where: whereConditions
|
||
});
|
||
const countEndTime = Date.now();
|
||
|
||
console.log('=== 总数查询完成 ===');
|
||
console.log('查询耗时:', countEndTime - countStartTime, 'ms');
|
||
console.log('总记录数:', totalCount);
|
||
|
||
// 获取分页数据
|
||
const dataStartTime = Date.now();
|
||
const rows = await IotCattle.findAll({
|
||
where: whereConditions,
|
||
attributes: [
|
||
'id', 'earNumber', 'sex', 'strain', 'varieties', 'cate',
|
||
'birthWeight', 'birthday', 'penId', 'batchId', 'orgId',
|
||
'weight', 'parity', 'weightCalculateTime', 'dayOfBirthday',
|
||
'intoTime', 'source', 'sourceDay', 'sourceWeight',
|
||
'event', 'eventTime', 'lactationDay', 'semenNum', 'isWear',
|
||
'imgs', 'isEleAuth', 'isQuaAuth', 'isDelete', 'isOut',
|
||
'createUid', 'createTime', 'algebra', 'colour', 'infoWeight',
|
||
'descent', 'isVaccin', 'isInsemination', 'isInsure', 'isMortgage',
|
||
'updateTime', 'breedBullTime', 'sixWeight', 'eighteenWeight',
|
||
'twelveDayWeight', 'eighteenDayWeight', 'xxivDayWeight',
|
||
'semenBreedImgs', 'sellStatus'
|
||
],
|
||
limit: parseInt(pageSize),
|
||
offset: parseInt(offset),
|
||
order: [['id', 'ASC']] // 升序排序
|
||
});
|
||
const dataEndTime = Date.now();
|
||
|
||
console.log('=== 数据查询完成 ===');
|
||
console.log('查询耗时:', dataEndTime - dataStartTime, 'ms');
|
||
console.log('查询到记录数:', rows.length);
|
||
console.log('记录详情:', rows.map(row => ({
|
||
id: row.id,
|
||
earNumber: row.earNumber,
|
||
sex: row.sex,
|
||
varieties: row.varieties
|
||
})));
|
||
|
||
// 获取栏舍、批次、品种和用途名称
|
||
const { penNames, batchNames, typeNames, userNames } = await getPenBatchTypeAndUserNames(rows);
|
||
|
||
// 格式化数据(基于iot_cattle表字段映射)
|
||
const formattedData = rows.map(cattle => ({
|
||
id: cattle.id,
|
||
earNumber: cattle.earNumber, // 映射iot_cattle.ear_number
|
||
sex: cattle.sex, // 映射iot_cattle.sex
|
||
strain: userNames[cattle.strain] || `品系ID:${cattle.strain}`, // 映射iot_cattle.strain为用途名称(用于显示)
|
||
strainId: cattle.strain, // 原始ID(用于编辑和提交)
|
||
varieties: typeNames[cattle.varieties] || `品种ID:${cattle.varieties}`, // 映射iot_cattle.varieties为品种名称(用于显示)
|
||
varietiesId: cattle.varieties, // 原始ID(用于编辑和提交)
|
||
cate: getCategoryName(cattle.cate), // 映射iot_cattle.cate为中文(用于显示)
|
||
cateId: cattle.cate, // 原始ID(用于编辑和提交)
|
||
birthWeight: cattle.birthWeight, // 映射iot_cattle.birth_weight
|
||
birthday: cattle.birthday, // 映射iot_cattle.birthday
|
||
intoTime: cattle.intoTime,
|
||
parity: cattle.parity,
|
||
source: cattle.source,
|
||
sourceDay: cattle.sourceDay,
|
||
sourceWeight: cattle.sourceWeight,
|
||
ageInMonths: calculateAgeInMonths(cattle.birthday), // 从iot_cattle.birthday计算月龄
|
||
physiologicalStage: cattle.parity || 0, // 使用parity作为生理阶段
|
||
currentWeight: cattle.weight || 0, // 使用weight作为当前体重
|
||
weightCalculateTime: cattle.weightCalculateTime,
|
||
dayOfBirthday: cattle.dayOfBirthday,
|
||
farmName: `农场ID:${cattle.orgId}`, // 暂时显示ID,后续可优化
|
||
penName: cattle.penId ? (penNames[cattle.penId] || `栏舍ID:${cattle.penId}`) : '未分配栏舍', // 映射栏舍名称
|
||
batchName: cattle.batchId === 0 ? '未分配批次' : (batchNames[cattle.batchId] || `批次ID:${cattle.batchId}`), // 映射批次名称
|
||
farmId: cattle.orgId, // 映射iot_cattle.org_id
|
||
penId: cattle.penId, // 映射iot_cattle.pen_id
|
||
batchId: cattle.batchId // 映射iot_cattle.batch_id
|
||
}));
|
||
|
||
console.log('=== 数据格式化完成 ===');
|
||
console.log('格式化后数据条数:', formattedData.length);
|
||
console.log('格式化后数据示例:', formattedData.slice(0, 2));
|
||
|
||
const responseData = {
|
||
success: true,
|
||
data: {
|
||
list: formattedData,
|
||
pagination: {
|
||
current: parseInt(page),
|
||
pageSize: parseInt(pageSize),
|
||
total: totalCount,
|
||
pages: Math.ceil(totalCount / parseInt(pageSize))
|
||
}
|
||
},
|
||
message: '获取牛只档案列表成功'
|
||
};
|
||
|
||
console.log('=== 准备返回响应 ===');
|
||
console.log('响应时间:', new Date().toISOString());
|
||
console.log('响应数据大小:', JSON.stringify(responseData).length, 'bytes');
|
||
console.log('分页信息:', responseData.data.pagination);
|
||
|
||
res.json(responseData);
|
||
} catch (error) {
|
||
console.error('获取牛只档案列表失败:', error);
|
||
res.status(500).json({
|
||
success: false,
|
||
message: '获取牛只档案列表失败',
|
||
error: error.message
|
||
});
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取单个牛只档案详情
|
||
*/
|
||
async getCattleArchiveById(req, res) {
|
||
try {
|
||
const { id } = req.params;
|
||
|
||
console.log('=== 获取牛只档案详情 ===');
|
||
console.log('请求时间:', new Date().toISOString());
|
||
console.log('档案ID:', id);
|
||
console.log('请求来源:', req.ip);
|
||
console.log('用户信息:', req.user ? { id: req.user.id, username: req.user.username } : '未登录');
|
||
console.log('User-Agent:', req.get('User-Agent'));
|
||
|
||
const cattle = await IotCattle.findByPk(id, {
|
||
include: [
|
||
{
|
||
model: Farm,
|
||
as: 'farm',
|
||
attributes: ['id', 'name', 'location']
|
||
},
|
||
{
|
||
model: CattlePen,
|
||
as: 'pen',
|
||
attributes: ['id', 'name', 'code']
|
||
},
|
||
{
|
||
model: CattleBatch,
|
||
as: 'batch',
|
||
attributes: ['id', 'name', 'code']
|
||
}
|
||
]
|
||
});
|
||
|
||
if (!cattle) {
|
||
console.log('牛只档案不存在,ID:', id);
|
||
return res.status(404).json({
|
||
success: false,
|
||
message: '牛只档案不存在'
|
||
});
|
||
}
|
||
|
||
console.log('找到牛只档案:', {
|
||
id: cattle.id,
|
||
earNumber: cattle.earNumber,
|
||
orgId: cattle.orgId,
|
||
penId: cattle.penId,
|
||
batchId: cattle.batchId
|
||
});
|
||
|
||
// 获取栏舍、批次、品种和用途名称
|
||
const cattleList = [cattle];
|
||
const { penNames, batchNames, typeNames, userNames } = await getPenBatchTypeAndUserNames(cattleList);
|
||
|
||
// 格式化数据(基于iot_cattle表字段映射)
|
||
const formattedData = {
|
||
id: cattle.id,
|
||
earNumber: cattle.earNumber, // 映射iot_cattle.ear_number
|
||
sex: cattle.sex, // 映射iot_cattle.sex
|
||
strain: userNames[cattle.strain] || `品系ID:${cattle.strain}`, // 映射iot_cattle.strain为用途名称(用于显示)
|
||
strainId: cattle.strain, // 原始ID(用于编辑和提交)
|
||
varieties: typeNames[cattle.varieties] || `品种ID:${cattle.varieties}`, // 映射iot_cattle.varieties为品种名称(用于显示)
|
||
varietiesId: cattle.varieties, // 原始ID(用于编辑和提交)
|
||
cate: getCategoryName(cattle.cate), // 映射iot_cattle.cate为中文(用于显示)
|
||
cateId: cattle.cate, // 原始ID(用于编辑和提交)
|
||
birthWeight: cattle.birthWeight, // 映射iot_cattle.birth_weight
|
||
birthday: cattle.birthday, // 映射iot_cattle.birthday
|
||
intoTime: cattle.intoTime,
|
||
parity: cattle.parity,
|
||
source: cattle.source,
|
||
sourceDay: cattle.sourceDay,
|
||
sourceWeight: cattle.sourceWeight,
|
||
ageInMonths: calculateAgeInMonths(cattle.birthday), // 从iot_cattle.birthday计算月龄
|
||
physiologicalStage: cattle.parity || 0, // 使用parity作为生理阶段
|
||
currentWeight: cattle.weight || 0, // 使用weight作为当前体重
|
||
weightCalculateTime: cattle.weightCalculateTime,
|
||
dayOfBirthday: cattle.dayOfBirthday,
|
||
farmName: `农场ID:${cattle.orgId}`, // 暂时显示ID,后续可优化
|
||
penName: cattle.penId ? (penNames[cattle.penId] || `栏舍ID:${cattle.penId}`) : '未分配栏舍', // 映射栏舍名称
|
||
batchName: cattle.batchId === 0 ? '未分配批次' : (batchNames[cattle.batchId] || `批次ID:${cattle.batchId}`), // 映射批次名称
|
||
farmId: cattle.orgId, // 映射iot_cattle.org_id
|
||
penId: cattle.penId, // 映射iot_cattle.pen_id
|
||
batchId: cattle.batchId // 映射iot_cattle.batch_id
|
||
};
|
||
|
||
console.log('=== 返回格式化后的数据 ===');
|
||
console.log('格式化数据示例:', {
|
||
id: formattedData.id,
|
||
earNumber: formattedData.earNumber,
|
||
farmId: formattedData.farmId,
|
||
penId: formattedData.penId,
|
||
batchId: formattedData.batchId
|
||
});
|
||
|
||
res.json({
|
||
success: true,
|
||
data: formattedData,
|
||
message: '获取牛只档案详情成功'
|
||
});
|
||
} catch (error) {
|
||
console.error('=== 获取牛只档案详情失败 ===');
|
||
console.error('错误时间:', new Date().toISOString());
|
||
console.error('档案ID:', req.params.id);
|
||
console.error('错误信息:', error.message);
|
||
console.error('错误堆栈:', error.stack);
|
||
|
||
res.status(500).json({
|
||
success: false,
|
||
message: '获取牛只档案详情失败',
|
||
error: error.message
|
||
});
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 创建牛只档案
|
||
*/
|
||
async createCattleArchive(req, res) {
|
||
try {
|
||
const {
|
||
earNumber,
|
||
sex,
|
||
strain,
|
||
varieties,
|
||
cate,
|
||
birthWeight,
|
||
birthday,
|
||
penId,
|
||
intoTime,
|
||
parity,
|
||
source,
|
||
sourceDay,
|
||
sourceWeight,
|
||
orgId,
|
||
batchId
|
||
} = req.body;
|
||
|
||
// 验证必填字段
|
||
if (!earNumber || !sex || !strain || !varieties || !cate || !birthWeight || !birthday || !orgId) {
|
||
return res.status(400).json({
|
||
success: false,
|
||
message: '缺少必填字段'
|
||
});
|
||
}
|
||
|
||
// 检查耳标号是否已存在
|
||
const existingCattle = await IotCattle.findOne({
|
||
where: { earNumber: earNumber }
|
||
});
|
||
|
||
if (existingCattle) {
|
||
return res.status(400).json({
|
||
success: false,
|
||
message: '耳标号已存在'
|
||
});
|
||
}
|
||
|
||
const cattleData = {
|
||
earNumber: parseInt(earNumber),
|
||
sex: parseInt(sex),
|
||
strain: parseInt(strain),
|
||
varieties: parseInt(varieties),
|
||
cate: parseInt(cate),
|
||
birthWeight: parseFloat(birthWeight),
|
||
birthday: parseInt(birthday),
|
||
penId: penId ? parseInt(penId) : 0,
|
||
intoTime: intoTime ? parseInt(intoTime) : 0,
|
||
parity: parity ? parseInt(parity) : 0,
|
||
source: source ? parseInt(source) : 0,
|
||
sourceDay: sourceDay ? parseInt(sourceDay) : 0,
|
||
sourceWeight: sourceWeight ? parseFloat(sourceWeight) : 0,
|
||
weight: req.body.currentWeight ? parseFloat(req.body.currentWeight) : 0,
|
||
event: req.body.event || 1,
|
||
eventTime: req.body.eventTime || Math.floor(Date.now() / 1000),
|
||
lactationDay: req.body.lactationDay || 0,
|
||
semenNum: req.body.semenNum || '',
|
||
isWear: req.body.isWear || 0,
|
||
imgs: req.body.imgs || '',
|
||
isEleAuth: req.body.isEleAuth || 0,
|
||
isQuaAuth: req.body.isQuaAuth || 0,
|
||
isDelete: 0,
|
||
isOut: 0,
|
||
createUid: req.user ? req.user.id : 1,
|
||
createTime: Math.floor(Date.now() / 1000),
|
||
algebra: req.body.algebra || 0,
|
||
colour: req.body.colour || '',
|
||
infoWeight: req.body.infoWeight ? parseFloat(req.body.infoWeight) : 0,
|
||
descent: req.body.descent || 0,
|
||
isVaccin: req.body.isVaccin || 0,
|
||
isInsemination: req.body.isInsemination || 0,
|
||
isInsure: req.body.isInsure || 0,
|
||
isMortgage: req.body.isMortgage || 0,
|
||
updateTime: Math.floor(Date.now() / 1000),
|
||
breedBullTime: req.body.breedBullTime || 0,
|
||
level: req.body.level || 0,
|
||
sixWeight: req.body.sixWeight ? parseFloat(req.body.sixWeight) : 0,
|
||
eighteenWeight: req.body.eighteenWeight ? parseFloat(req.body.eighteenWeight) : 0,
|
||
twelveDayWeight: req.body.twelveDayWeight ? parseFloat(req.body.twelveDayWeight) : 0,
|
||
eighteenDayWeight: req.body.eighteenDayWeight ? parseFloat(req.body.eighteenDayWeight) : 0,
|
||
xxivDayWeight: req.body.xxivDayWeight ? parseFloat(req.body.xxivDayWeight) : 0,
|
||
semenBreedImgs: req.body.semenBreedImgs || '',
|
||
sellStatus: req.body.sellStatus || 100,
|
||
orgId: parseInt(orgId),
|
||
batchId: batchId ? parseInt(batchId) : 0
|
||
};
|
||
|
||
const cattle = await IotCattle.create(cattleData);
|
||
|
||
res.status(201).json({
|
||
success: true,
|
||
data: cattle,
|
||
message: '创建牛只档案成功'
|
||
});
|
||
} catch (error) {
|
||
console.error('创建牛只档案失败:', error);
|
||
res.status(500).json({
|
||
success: false,
|
||
message: '创建牛只档案失败',
|
||
error: error.message
|
||
});
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 更新牛只档案
|
||
*/
|
||
async updateCattleArchive(req, res) {
|
||
try {
|
||
const { id } = req.params;
|
||
const updateData = req.body;
|
||
|
||
const cattle = await IotCattle.findByPk(id);
|
||
if (!cattle) {
|
||
return res.status(404).json({
|
||
success: false,
|
||
message: '牛只档案不存在'
|
||
});
|
||
}
|
||
|
||
// 如果更新耳标号,检查是否重复
|
||
if (updateData.earNumber && updateData.earNumber !== cattle.earNumber) {
|
||
const existingCattle = await IotCattle.findOne({
|
||
where: {
|
||
earNumber: updateData.earNumber,
|
||
id: { [Op.ne]: id }
|
||
}
|
||
});
|
||
|
||
if (existingCattle) {
|
||
return res.status(400).json({
|
||
success: false,
|
||
message: '耳标号已存在'
|
||
});
|
||
}
|
||
}
|
||
|
||
// 转换数据类型,只更新实际提交的字段
|
||
const processedData = {};
|
||
|
||
// 辅助函数:安全转换为整数,如果转换失败则返回原值(不更新该字段)
|
||
const safeParseInt = (value) => {
|
||
if (value === null || value === undefined || value === '') return undefined;
|
||
const parsed = parseInt(value);
|
||
return isNaN(parsed) ? undefined : parsed;
|
||
};
|
||
|
||
// 辅助函数:安全转换为浮点数,如果转换失败则返回原值(不更新该字段)
|
||
const safeParseFloat = (value) => {
|
||
if (value === null || value === undefined || value === '') return undefined;
|
||
const parsed = parseFloat(value);
|
||
return isNaN(parsed) ? undefined : parsed;
|
||
};
|
||
|
||
// 只更新实际提交的字段,如果字段值无效则跳过(保持原有值)
|
||
if (updateData.hasOwnProperty('earNumber')) {
|
||
const parsed = safeParseInt(updateData.earNumber);
|
||
if (parsed !== undefined) processedData.earNumber = parsed;
|
||
}
|
||
if (updateData.hasOwnProperty('sex')) {
|
||
const parsed = safeParseInt(updateData.sex);
|
||
if (parsed !== undefined) processedData.sex = parsed;
|
||
}
|
||
if (updateData.hasOwnProperty('strain')) {
|
||
const parsed = safeParseInt(updateData.strain);
|
||
if (parsed !== undefined) processedData.strain = parsed;
|
||
}
|
||
if (updateData.hasOwnProperty('varieties')) {
|
||
const parsed = safeParseInt(updateData.varieties);
|
||
if (parsed !== undefined) processedData.varieties = parsed;
|
||
}
|
||
if (updateData.hasOwnProperty('cate')) {
|
||
const parsed = safeParseInt(updateData.cate);
|
||
if (parsed !== undefined) processedData.cate = parsed;
|
||
}
|
||
if (updateData.hasOwnProperty('birthWeight')) {
|
||
const parsed = safeParseFloat(updateData.birthWeight);
|
||
if (parsed !== undefined) processedData.birthWeight = parsed;
|
||
}
|
||
if (updateData.hasOwnProperty('birthday')) {
|
||
const parsed = safeParseInt(updateData.birthday);
|
||
if (parsed !== undefined) processedData.birthday = parsed;
|
||
}
|
||
if (updateData.hasOwnProperty('penId')) {
|
||
const parsed = safeParseInt(updateData.penId);
|
||
if (parsed !== undefined) processedData.penId = parsed;
|
||
}
|
||
if (updateData.hasOwnProperty('intoTime')) {
|
||
const parsed = safeParseInt(updateData.intoTime);
|
||
if (parsed !== undefined) processedData.intoTime = parsed;
|
||
}
|
||
if (updateData.hasOwnProperty('parity')) {
|
||
const parsed = safeParseInt(updateData.parity);
|
||
if (parsed !== undefined) processedData.parity = parsed;
|
||
}
|
||
if (updateData.hasOwnProperty('source')) {
|
||
const parsed = safeParseInt(updateData.source);
|
||
if (parsed !== undefined) processedData.source = parsed;
|
||
}
|
||
if (updateData.hasOwnProperty('sourceDay')) {
|
||
const parsed = safeParseInt(updateData.sourceDay);
|
||
if (parsed !== undefined) processedData.sourceDay = parsed;
|
||
}
|
||
if (updateData.hasOwnProperty('sourceWeight')) {
|
||
const parsed = safeParseFloat(updateData.sourceWeight);
|
||
if (parsed !== undefined) processedData.sourceWeight = parsed;
|
||
}
|
||
if (updateData.hasOwnProperty('orgId')) {
|
||
const parsed = safeParseInt(updateData.orgId);
|
||
if (parsed !== undefined) processedData.orgId = parsed;
|
||
}
|
||
if (updateData.hasOwnProperty('batchId')) {
|
||
const parsed = safeParseInt(updateData.batchId);
|
||
if (parsed !== undefined) processedData.batchId = parsed;
|
||
}
|
||
|
||
await cattle.update(processedData);
|
||
|
||
res.json({
|
||
success: true,
|
||
data: cattle,
|
||
message: '更新牛只档案成功'
|
||
});
|
||
} catch (error) {
|
||
console.error('更新牛只档案失败:', error);
|
||
res.status(500).json({
|
||
success: false,
|
||
message: '更新牛只档案失败',
|
||
error: error.message
|
||
});
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 删除牛只档案
|
||
*/
|
||
async deleteCattleArchive(req, res) {
|
||
try {
|
||
const { id } = req.params;
|
||
|
||
const cattle = await IotCattle.findByPk(id);
|
||
if (!cattle) {
|
||
return res.status(404).json({
|
||
success: false,
|
||
message: '牛只档案不存在'
|
||
});
|
||
}
|
||
|
||
await cattle.destroy();
|
||
|
||
res.json({
|
||
success: true,
|
||
message: '删除牛只档案成功'
|
||
});
|
||
} catch (error) {
|
||
console.error('删除牛只档案失败:', error);
|
||
res.status(500).json({
|
||
success: false,
|
||
message: '删除牛只档案失败',
|
||
error: error.message
|
||
});
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 批量删除牛只档案
|
||
*/
|
||
async batchDeleteCattleArchives(req, res) {
|
||
try {
|
||
const { ids } = req.body;
|
||
|
||
if (!ids || !Array.isArray(ids) || ids.length === 0) {
|
||
return res.status(400).json({
|
||
success: false,
|
||
message: '请选择要删除的牛只档案'
|
||
});
|
||
}
|
||
|
||
const deletedCount = await IotCattle.destroy({
|
||
where: {
|
||
id: {
|
||
[Op.in]: ids
|
||
}
|
||
}
|
||
});
|
||
|
||
res.json({
|
||
success: true,
|
||
data: { deletedCount },
|
||
message: `成功删除 ${deletedCount} 个牛只档案`
|
||
});
|
||
} catch (error) {
|
||
console.error('批量删除牛只档案失败:', error);
|
||
res.status(500).json({
|
||
success: false,
|
||
message: '批量删除牛只档案失败',
|
||
error: error.message
|
||
});
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取农场列表(用于下拉选择)
|
||
*/
|
||
async getFarms(req, res) {
|
||
try {
|
||
const farms = await Farm.findAll({
|
||
attributes: ['id', 'name', 'location'],
|
||
order: [['name', 'ASC']]
|
||
});
|
||
|
||
res.json({
|
||
success: true,
|
||
data: farms,
|
||
message: '获取农场列表成功'
|
||
});
|
||
} catch (error) {
|
||
console.error('获取农场列表失败:', error);
|
||
res.status(500).json({
|
||
success: false,
|
||
message: '获取农场列表失败',
|
||
error: error.message
|
||
});
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取栏舍列表(用于下拉选择)
|
||
*/
|
||
async getPens(req, res) {
|
||
try {
|
||
const { farmId } = req.query;
|
||
const where = {};
|
||
|
||
if (farmId) {
|
||
where.farmId = farmId;
|
||
}
|
||
|
||
const pens = await CattlePen.findAll({
|
||
where,
|
||
attributes: ['id', 'name', 'code', 'farmId'],
|
||
order: [['name', 'ASC']]
|
||
});
|
||
|
||
res.json({
|
||
success: true,
|
||
data: pens,
|
||
message: '获取栏舍列表成功'
|
||
});
|
||
} catch (error) {
|
||
console.error('获取栏舍列表失败:', error);
|
||
res.status(500).json({
|
||
success: false,
|
||
message: '获取栏舍列表失败',
|
||
error: error.message
|
||
});
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取批次列表(用于下拉选择)
|
||
*/
|
||
async getBatches(req, res) {
|
||
try {
|
||
const { farmId } = req.query;
|
||
const where = {};
|
||
|
||
if (farmId) {
|
||
where.farmId = farmId;
|
||
}
|
||
|
||
const batches = await CattleBatch.findAll({
|
||
where,
|
||
attributes: ['id', 'name', 'code', 'farmId'],
|
||
order: [['name', 'ASC']]
|
||
});
|
||
|
||
res.json({
|
||
success: true,
|
||
data: batches,
|
||
message: '获取批次列表成功'
|
||
});
|
||
} catch (error) {
|
||
console.error('获取批次列表失败:', error);
|
||
res.status(500).json({
|
||
success: false,
|
||
message: '获取批次列表失败',
|
||
error: error.message
|
||
});
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 导入牛只档案数据
|
||
*/
|
||
async importCattleArchives(req, res) {
|
||
try {
|
||
console.log('=== 开始导入牛只档案数据 ===');
|
||
|
||
if (!req.file) {
|
||
return res.status(400).json({
|
||
success: false,
|
||
message: '请选择要导入的文件'
|
||
});
|
||
}
|
||
|
||
const file = req.file;
|
||
console.log('上传文件信息:', {
|
||
originalname: file.originalname,
|
||
mimetype: file.mimetype,
|
||
size: file.size
|
||
});
|
||
|
||
// 检查文件类型
|
||
const allowedTypes = [
|
||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||
'application/vnd.ms-excel'
|
||
];
|
||
|
||
if (!allowedTypes.includes(file.mimetype)) {
|
||
return res.status(400).json({
|
||
success: false,
|
||
message: '请上传Excel文件(.xlsx或.xls格式)'
|
||
});
|
||
}
|
||
|
||
// 解析Excel文件
|
||
const workbook = XLSX.readFile(file.path);
|
||
const sheetName = workbook.SheetNames[0];
|
||
const worksheet = workbook.Sheets[sheetName];
|
||
const data = XLSX.utils.sheet_to_json(worksheet);
|
||
|
||
console.log(`解析到 ${data.length} 行数据`);
|
||
|
||
if (data.length === 0) {
|
||
return res.status(400).json({
|
||
success: false,
|
||
message: 'Excel文件中没有数据'
|
||
});
|
||
}
|
||
|
||
// 获取所有品种和品系(品类)的映射关系
|
||
const cattleTypes = await CattleType.findAll({ attributes: ['id', 'name'] });
|
||
const typeNameToId = {};
|
||
cattleTypes.forEach(type => {
|
||
typeNameToId[type.name] = type.id;
|
||
});
|
||
|
||
const cattleUsers = await CattleUser.findAll({ attributes: ['id', 'name'] });
|
||
const userNameToId = {};
|
||
cattleUsers.forEach(user => {
|
||
userNameToId[user.name] = user.id;
|
||
});
|
||
|
||
// 获取所有栏舍和批次的映射关系
|
||
const pens = await CattlePen.findAll({ attributes: ['id', 'name'] });
|
||
const penNameToId = {};
|
||
pens.forEach(pen => {
|
||
penNameToId[pen.name] = pen.id;
|
||
});
|
||
|
||
const batches = await CattleBatch.findAll({ attributes: ['id', 'name'] });
|
||
const batchNameToId = {};
|
||
batches.forEach(batch => {
|
||
batchNameToId[batch.name] = batch.id;
|
||
});
|
||
|
||
// 获取默认农场ID(从请求中获取或使用第一个农场)
|
||
let defaultOrgId = req.body.orgId || req.query.orgId;
|
||
if (!defaultOrgId) {
|
||
const firstFarm = await Farm.findOne({ order: [['id', 'ASC']] });
|
||
defaultOrgId = firstFarm ? firstFarm.id : null;
|
||
}
|
||
|
||
if (!defaultOrgId) {
|
||
return res.status(400).json({
|
||
success: false,
|
||
message: '请指定所属农场'
|
||
});
|
||
}
|
||
|
||
const errors = [];
|
||
const successData = [];
|
||
|
||
// 处理每一行数据
|
||
for (let i = 0; i < data.length; i++) {
|
||
const row = data[i];
|
||
const rowNum = i + 2; // Excel行号(从2开始,第1行是表头)
|
||
|
||
try {
|
||
// 验证必填字段
|
||
if (!row['耳号']) {
|
||
errors.push({ row: rowNum, field: '耳号', message: '耳号不能为空' });
|
||
continue;
|
||
}
|
||
|
||
// 映射字段
|
||
const earNumber = String(row['耳号']).trim();
|
||
|
||
// 检查耳号是否已存在
|
||
const existingCattle = await IotCattle.findOne({
|
||
where: { earNumber: parseInt(earNumber) }
|
||
});
|
||
|
||
if (existingCattle) {
|
||
errors.push({ row: rowNum, field: '耳号', message: `耳号 ${earNumber} 已存在` });
|
||
continue;
|
||
}
|
||
|
||
// 品类(strain)- 从名称查找ID(必填)
|
||
const strainName = row['品类'] ? String(row['品类']).trim() : '';
|
||
if (!strainName) {
|
||
errors.push({ row: rowNum, field: '品类', message: '品类不能为空' });
|
||
continue;
|
||
}
|
||
const strainId = userNameToId[strainName];
|
||
if (!strainId) {
|
||
errors.push({ row: rowNum, field: '品类', message: `品类 "${strainName}" 不存在` });
|
||
continue;
|
||
}
|
||
|
||
// 品种(varieties)- 从名称查找ID(必填)
|
||
const varietiesName = row['品种'] ? String(row['品种']).trim() : '';
|
||
if (!varietiesName) {
|
||
errors.push({ row: rowNum, field: '品种', message: '品种不能为空' });
|
||
continue;
|
||
}
|
||
const varietiesId = typeNameToId[varietiesName];
|
||
if (!varietiesId) {
|
||
errors.push({ row: rowNum, field: '品种', message: `品种 "${varietiesName}" 不存在` });
|
||
continue;
|
||
}
|
||
|
||
// 生理阶段(cate)(必填)
|
||
const cateName = row['生理阶段'] ? String(row['生理阶段']).trim() : '';
|
||
if (!cateName) {
|
||
errors.push({ row: rowNum, field: '生理阶段', message: '生理阶段不能为空' });
|
||
continue;
|
||
}
|
||
const cateId = getCategoryId(cateName);
|
||
if (!cateId) {
|
||
errors.push({ row: rowNum, field: '生理阶段', message: `生理阶段 "${cateName}" 无效` });
|
||
continue;
|
||
}
|
||
|
||
// 性别(sex)(必填)
|
||
const sexName = row['性别'] ? String(row['性别']).trim() : '';
|
||
if (!sexName) {
|
||
errors.push({ row: rowNum, field: '性别', message: '性别不能为空' });
|
||
continue;
|
||
}
|
||
const sexId = getSexId(sexName);
|
||
if (!sexId) {
|
||
errors.push({ row: rowNum, field: '性别', message: `性别 "${sexName}" 无效,应为"公"或"母"` });
|
||
continue;
|
||
}
|
||
|
||
// 来源(source)(必填)
|
||
const sourceName = row['来源'] ? String(row['来源']).trim() : '';
|
||
if (!sourceName) {
|
||
errors.push({ row: rowNum, field: '来源', message: '来源不能为空' });
|
||
continue;
|
||
}
|
||
const sourceId = getSourceId(sourceName);
|
||
if (!sourceId) {
|
||
errors.push({ row: rowNum, field: '来源', message: `来源 "${sourceName}" 无效` });
|
||
continue;
|
||
}
|
||
|
||
// 血统纯度(descent)
|
||
const descentName = row['血统纯度'] ? String(row['血统纯度']).trim() : '';
|
||
const descentId = descentName ? getDescentId(descentName) : 0;
|
||
|
||
// 栏舍(penId)- 从名称查找ID
|
||
const penName = row['栏舍'] ? String(row['栏舍']).trim() : '';
|
||
const penId = penName ? (penNameToId[penName] || null) : null;
|
||
|
||
// 所属批次(batchId)- 从名称查找ID
|
||
const batchName = row['所属批次'] ? String(row['所属批次']).trim() : '';
|
||
const batchId = batchName ? (batchNameToId[batchName] || null) : null;
|
||
|
||
// 已产胎次(parity)
|
||
const parity = row['已产胎次'] ? parseInt(row['已产胎次']) || 0 : 0;
|
||
|
||
// 出生日期(birthday)(必填)
|
||
const birthdayStr = row['出生日期(格式必须为2023-01-01)'] || row['出生日期'] || '';
|
||
if (!birthdayStr) {
|
||
errors.push({ row: rowNum, field: '出生日期', message: '出生日期不能为空' });
|
||
continue;
|
||
}
|
||
const birthday = dateToTimestamp(birthdayStr);
|
||
if (!birthday) {
|
||
errors.push({ row: rowNum, field: '出生日期', message: `出生日期格式错误: "${birthdayStr}",格式应为:2023-01-01` });
|
||
continue;
|
||
}
|
||
|
||
// 现估重(weight)(必填)
|
||
const currentWeightStr = row['现估重(公斤)'] || row['现估重'] || '';
|
||
if (!currentWeightStr) {
|
||
errors.push({ row: rowNum, field: '现估重(公斤)', message: '现估重(公斤)不能为空' });
|
||
continue;
|
||
}
|
||
const currentWeight = parseFloat(currentWeightStr);
|
||
if (isNaN(currentWeight) || currentWeight < 0) {
|
||
errors.push({ row: rowNum, field: '现估重(公斤)', message: `现估重(公斤)格式错误: "${currentWeightStr}"` });
|
||
continue;
|
||
}
|
||
|
||
// 代数(algebra)
|
||
const algebra = row['代数'] ? parseInt(row['代数']) || 0 : 0;
|
||
|
||
// 入场日期(intoTime)
|
||
const intoTimeStr = row['入场日期(格式必须为2023-01-01)'] || row['入场日期'] || '';
|
||
const intoTime = intoTimeStr ? dateToTimestamp(intoTimeStr) : null;
|
||
if (intoTimeStr && !intoTime) {
|
||
errors.push({ row: rowNum, field: '入场日期', message: `入场日期格式错误: "${intoTimeStr}"` });
|
||
continue;
|
||
}
|
||
|
||
// 出生体重(birthWeight)
|
||
const birthWeight = row['出生体重'] ? parseFloat(row['出生体重']) || 0 : 0;
|
||
|
||
// 冻精编号(semenNum)
|
||
const semenNum = row['冻精编号'] ? String(row['冻精编号']).trim() : '';
|
||
|
||
// 构建插入数据
|
||
const cattleData = {
|
||
orgId: parseInt(defaultOrgId),
|
||
earNumber: parseInt(earNumber),
|
||
sex: sexId,
|
||
strain: strainId || 0,
|
||
varieties: varietiesId || 0,
|
||
cate: cateId || 0,
|
||
birthWeight: birthWeight,
|
||
birthday: birthday || 0,
|
||
penId: penId || 0,
|
||
intoTime: intoTime || 0,
|
||
parity: parity,
|
||
source: sourceId || 0,
|
||
sourceDay: 0,
|
||
sourceWeight: 0,
|
||
weight: currentWeight,
|
||
event: 1,
|
||
eventTime: Math.floor(Date.now() / 1000),
|
||
lactationDay: 0,
|
||
semenNum: semenNum,
|
||
isWear: 0,
|
||
imgs: '',
|
||
isEleAuth: 0,
|
||
isQuaAuth: 0,
|
||
isDelete: 0,
|
||
isOut: 0,
|
||
createUid: req.user ? req.user.id : 1,
|
||
createTime: Math.floor(Date.now() / 1000),
|
||
algebra: algebra,
|
||
colour: '',
|
||
infoWeight: 0,
|
||
descent: descentId || 0,
|
||
isVaccin: 0,
|
||
isInsemination: 0,
|
||
isInsure: 0,
|
||
isMortgage: 0,
|
||
updateTime: Math.floor(Date.now() / 1000),
|
||
breedBullTime: 0,
|
||
level: 0,
|
||
sixWeight: 0,
|
||
eighteenWeight: 0,
|
||
twelveDayWeight: 0,
|
||
eighteenDayWeight: 0,
|
||
xxivDayWeight: 0,
|
||
semenBreedImgs: '',
|
||
sellStatus: 100,
|
||
batchId: batchId || 0
|
||
};
|
||
|
||
// 插入数据库
|
||
await IotCattle.create(cattleData);
|
||
successData.push({ row: rowNum, earNumber: earNumber });
|
||
|
||
} catch (error) {
|
||
console.error(`处理第 ${rowNum} 行数据失败:`, error);
|
||
errors.push({
|
||
row: rowNum,
|
||
field: '数据',
|
||
message: `处理失败: ${error.message}`
|
||
});
|
||
}
|
||
}
|
||
|
||
const importedCount = successData.length;
|
||
|
||
console.log(`导入完成: 成功 ${importedCount} 条,失败 ${errors.length} 条`);
|
||
|
||
res.json({
|
||
success: true,
|
||
message: `导入完成: 成功 ${importedCount} 条,失败 ${errors.length} 条`,
|
||
importedCount: importedCount,
|
||
errorCount: errors.length,
|
||
errors: errors,
|
||
successData: successData
|
||
});
|
||
|
||
} catch (error) {
|
||
console.error('导入牛只档案数据失败:', error);
|
||
res.status(500).json({
|
||
success: false,
|
||
message: '导入失败',
|
||
error: error.message
|
||
});
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 下载导入模板
|
||
*/
|
||
async downloadImportTemplate(req, res) {
|
||
try {
|
||
console.log('=== 下载牛只档案导入模板 ===');
|
||
|
||
// 创建模板数据 - 按照图片格式(16列)
|
||
const templateData = [
|
||
{
|
||
'耳号': '202308301035',
|
||
'品类': '肉用型牛',
|
||
'品种': '蒙古牛',
|
||
'生理阶段': '犊牛',
|
||
'性别': '公',
|
||
'血统纯度': '纯血',
|
||
'栏舍': '牛舍-20230819',
|
||
'所属批次': '230508357',
|
||
'已产胎次': '0',
|
||
'来源': '购买',
|
||
'现估重(公斤)': '50',
|
||
'代数': '0',
|
||
'出生日期(格式必须为2023-01-01)': '2023-08-30',
|
||
'入场日期(格式必须为2023-01-01)': '2023-08-30',
|
||
'出生体重': '50.00',
|
||
'冻精编号': '51568'
|
||
},
|
||
{
|
||
'耳号': '202308301036',
|
||
'品类': '肉用型牛',
|
||
'品种': '蒙古牛',
|
||
'生理阶段': '犊牛',
|
||
'性别': '母',
|
||
'血统纯度': '杂交',
|
||
'栏舍': '牛舍-20230819',
|
||
'所属批次': '230508357',
|
||
'已产胎次': '1',
|
||
'来源': '购买',
|
||
'现估重(公斤)': '50',
|
||
'代数': '1',
|
||
'出生日期(格式必须为2023-01-01)': '2023-08-30',
|
||
'入场日期(格式必须为2023-01-01)': '2023-08-30',
|
||
'出生体重': '45.00',
|
||
'冻精编号': '51568'
|
||
}
|
||
];
|
||
|
||
// 使用ExportUtils生成Excel文件,按照图片中的列顺序
|
||
const ExportUtils = require('../utils/exportUtils');
|
||
const result = await ExportUtils.exportToExcelWithStyle(templateData, [
|
||
{ title: '耳号', dataIndex: '耳号', key: 'earNumber', width: 15, required: true },
|
||
{ title: '品类', dataIndex: '品类', key: 'strain', width: 12, required: true },
|
||
{ title: '品种', dataIndex: '品种', key: 'varieties', width: 12, required: true },
|
||
{ title: '生理阶段', dataIndex: '生理阶段', key: 'cate', width: 12, required: true },
|
||
{ title: '性别', dataIndex: '性别', key: 'sex', width: 8, required: true },
|
||
{ title: '血统纯度', dataIndex: '血统纯度', key: 'descent', width: 12, required: false },
|
||
{ title: '栏舍', dataIndex: '栏舍', key: 'penName', width: 15, required: false },
|
||
{ title: '所属批次', dataIndex: '所属批次', key: 'batchName', width: 15, required: false },
|
||
{ title: '已产胎次', dataIndex: '已产胎次', key: 'parity', width: 10, required: false },
|
||
{ title: '来源', dataIndex: '来源', key: 'source', width: 10, required: true },
|
||
{ title: '现估重(公斤)', dataIndex: '现估重(公斤)', key: 'currentWeight', width: 12, required: true },
|
||
{ title: '代数', dataIndex: '代数', key: 'algebra', width: 8, required: false },
|
||
{ title: '出生日期(格式必须为2023-01-01)', dataIndex: '出生日期(格式必须为2023-01-01)', key: 'birthday', width: 25, required: true },
|
||
{ title: '入场日期(格式必须为2023-01-01)', dataIndex: '入场日期(格式必须为2023-01-01)', key: 'intoTime', width: 25, required: false },
|
||
{ title: '出生体重', dataIndex: '出生体重', key: 'birthWeight', width: 12, required: false },
|
||
{ title: '冻精编号', dataIndex: '冻精编号', key: 'semenNum', width: 12, required: false }
|
||
], '牛只档案导入模板');
|
||
|
||
if (result.success) {
|
||
// 使用Express的res.download方法
|
||
res.download(result.filePath, '牛只档案导入模板.xlsx', (err) => {
|
||
if (err) {
|
||
console.error('文件下载失败:', err);
|
||
if (!res.headersSent) {
|
||
res.status(500).json({
|
||
success: false,
|
||
message: '文件下载失败',
|
||
error: err.message
|
||
});
|
||
}
|
||
} else {
|
||
// 下载成功后删除临时文件
|
||
const fs = require('fs');
|
||
fs.unlink(result.filePath, (err) => {
|
||
if (err) console.error('删除临时文件失败:', err);
|
||
});
|
||
}
|
||
});
|
||
} else {
|
||
res.status(500).json({
|
||
success: false,
|
||
message: '生成模板文件失败',
|
||
error: result.message
|
||
});
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('下载导入模板失败:', error);
|
||
res.status(500).json({
|
||
success: false,
|
||
message: '下载模板失败',
|
||
error: error.message
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
module.exports = new IotCattleController();
|