添加后台启动脚本和修改域名

This commit is contained in:
xuqiuyun
2025-11-17 09:18:31 +08:00
parent eca2040e5b
commit 8615549a6f
45 changed files with 15564 additions and 384 deletions

View File

@@ -1,4 +1,6 @@
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');
@@ -38,6 +40,77 @@ const getCategoryName = (cate) => {
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);
};
/**
* 获取栏舍、批次、品种和用途名称
*/
@@ -224,9 +297,12 @@ class IotCattleController {
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为用途名称
varieties: typeNames[cattle.varieties] || `品种ID:${cattle.varieties}`, // 映射iot_cattle.varieties为品种名称
cate: getCategoryName(cattle.cate), // 映射iot_cattle.cate为中文
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,
@@ -287,6 +363,13 @@ class IotCattleController {
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: [
@@ -309,20 +392,36 @@ class IotCattleController {
});
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: cattle.strain, // 映射iot_cattle.strain
varieties: cattle.varieties, // 映射iot_cattle.varieties单个记录不需要名称映射
cate: cattle.cate, // 映射iot_cattle.cate
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,
@@ -336,20 +435,34 @@ class IotCattleController {
weightCalculateTime: cattle.weightCalculateTime,
dayOfBirthday: cattle.dayOfBirthday,
farmName: `农场ID:${cattle.orgId}`, // 暂时显示ID后续可优化
penName: cattle.penId ? `栏舍ID:${cattle.penId}` : '未分配栏舍', // 暂时显示ID后续可优化
batchName: cattle.batchId === 0 ? '未分配批次' : `批次ID:${cattle.batchId}`, // 暂时显示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('获取牛只档案详情失败:', 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: '获取牛只档案详情失败',
@@ -500,23 +613,84 @@ class IotCattleController {
}
}
// 转换数据类型
// 转换数据类型,只更新实际提交的字段
const processedData = {};
if (updateData.earNumber) processedData.earNumber = parseInt(updateData.earNumber);
if (updateData.sex) processedData.sex = parseInt(updateData.sex);
if (updateData.strain) processedData.strain = parseInt(updateData.strain);
if (updateData.varieties) processedData.varieties = parseInt(updateData.varieties);
if (updateData.cate) processedData.cate = parseInt(updateData.cate);
if (updateData.birthWeight) processedData.birthWeight = parseFloat(updateData.birthWeight);
if (updateData.birthday) processedData.birthday = parseInt(updateData.birthday);
if (updateData.penId) processedData.penId = parseInt(updateData.penId);
if (updateData.intoTime) processedData.intoTime = parseInt(updateData.intoTime);
if (updateData.parity) processedData.parity = parseInt(updateData.parity);
if (updateData.source) processedData.source = parseInt(updateData.source);
if (updateData.sourceDay) processedData.sourceDay = parseInt(updateData.sourceDay);
if (updateData.sourceWeight) processedData.sourceWeight = parseFloat(updateData.sourceWeight);
if (updateData.orgId) processedData.orgId = parseInt(updateData.orgId);
if (updateData.batchId) processedData.batchId = parseInt(updateData.batchId);
// 辅助函数:安全转换为整数,如果转换失败则返回原值(不更新该字段)
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);
@@ -728,22 +902,280 @@ class IotCattleController {
});
}
// 这里需要添加Excel解析逻辑
// 由于没有安装xlsx库先返回模拟数据
const importedCount = 0;
const errors = [];
// 解析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);
// TODO: 实现Excel文件解析和数据库插入逻辑
// 1. 使用xlsx库解析Excel文件
// 2. 验证数据格式
// 3. 批量插入到数据库
// 4. 返回导入结果
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
message: `导入完成: 成功 ${importedCount} 条,失败 ${errors.length}`,
importedCount: importedCount,
errorCount: errors.length,
errors: errors,
successData: successData
});
} catch (error) {
@@ -763,55 +1195,65 @@ class IotCattleController {
try {
console.log('=== 下载牛只档案导入模板 ===');
// 创建模板数据 - 按照截图格式
// 创建模板数据 - 按照图片格式16列
const templateData = [
{
'耳标编号': '2105523006',
'性别': '1为公牛2为母牛',
'品': '1乳肉兼用',
'品种': '1:西藏高山牦牛2:宁夏牛',
'别': '1:犊牛,2:育成母牛,3:架子牛,4:青年牛,5:基础母牛,6:育肥牛',
'出生体重(kg)': '30',
'出生日期': '格式必须为2023-1-15',
'栏舍ID': '1',
'入栏时间': '2023-01-20',
'胎次': '0',
'来源': '1',
'来源天数': '5',
'来源体重': '35.5',
'当前体重': '450.0',
'事件': '正常',
'事件时间': '2023-01-20',
'泌乳天数': '0',
'精液编号': '',
'是否佩戴': '1',
'批次ID': '1'
'耳号': '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文件
// 使用ExportUtils生成Excel文件,按照图片中的列顺序
const ExportUtils = require('../utils/exportUtils');
const result = ExportUtils.exportToExcel(templateData, [
{ title: '耳标编号', dataIndex: '耳标编号', key: 'earNumber' },
{ title: '性别', dataIndex: '性别', key: 'sex' },
{ title: '品', dataIndex: '品', key: 'strain' },
{ title: '品种', dataIndex: '品种', key: 'varieties' },
{ title: '别', dataIndex: '别', key: 'cate' },
{ title: '出生体重(kg)', dataIndex: '出生体重(kg)', key: 'birthWeight' },
{ title: '出生日期', dataIndex: '出生日期', key: 'birthday' },
{ title: '栏舍ID', dataIndex: '栏舍ID', key: 'penId' },
{ title: '入栏时间', dataIndex: '入栏时间', key: 'intoTime' },
{ title: '胎次', dataIndex: '胎次', key: 'parity' },
{ title: '来源', dataIndex: '来源', key: 'source' },
{ title: '来源天数', dataIndex: '来源天数', key: 'sourceDay' },
{ title: '来源体重', dataIndex: '来源体重', key: 'sourceWeight' },
{ title: '当前体重', dataIndex: '当前体重', key: 'weight' },
{ title: '事件', dataIndex: '事件', key: 'event' },
{ title: '事件时间', dataIndex: '事件时间', key: 'eventTime' },
{ title: '泌乳天数', dataIndex: '泌乳天数', key: 'lactationDay' },
{ title: '精液编号', dataIndex: '精液编号', key: 'semenNum' },
{ title: '是否佩戴', dataIndex: '是否佩戴', key: 'isWear' },
{ title: '批次ID', dataIndex: '批次ID', key: 'batchId' }
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) {