Compare commits

...

4 Commits

Author SHA1 Message Date
eb55f170fa 优化展示 2025-11-22 19:39:35 +08:00
f7f95b4241 实现功能 2025-11-22 19:30:24 +08:00
55a3dbd1d5 猪类型增删改查界面 2025-11-22 19:22:21 +08:00
f44dc710c9 优化展示 2025-11-22 18:36:02 +08:00
4 changed files with 353 additions and 30 deletions

View File

@@ -1,8 +1,8 @@
<template>
<div class="nutrient-editor">
<el-form label-width="200px" style="max-height: 300px; overflow-y: auto;">
<el-form label-width="250px" style="max-height: 400px; overflow-y: auto;">
<el-form-item v-for="(nutrient, index) in localNutrients" :key="index" :label="nutrient.nutrient_name">
<el-input-number v-model="nutrient.value" :min="0" controls-position="right" style="width: 150px;"></el-input-number>
<el-input-number v-model="nutrient.value" :min="0" controls-position="right" style="width: 200px;"></el-input-number>
<el-button type="danger" @click="removeNutrient(index)" style="margin-left: 10px;">
<el-icon><Delete /></el-icon>
</el-button>
@@ -10,7 +10,7 @@
</el-form>
<div class="add-nutrient-section">
<el-select v-model="newNutrientId" placeholder="选择要添加的营养素" filterable style="width: 250px;">
<el-select v-model="newNutrientId" placeholder="选择要添加的营养素" filterable style="flex-grow: 1;">
<el-option v-for="item in availableNutrients" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
<el-button type="primary" @click="addNutrient" style="margin-left: 10px;">添加</el-button>

View File

@@ -53,27 +53,45 @@
<el-divider></el-divider>
<!-- 下半段该品种下的年龄阶段简介 -->
<h4 style="margin-bottom: 16px;">该品种下的年龄阶段简介</h4>
<el-table
:data="props.row.pig_types"
border
style="width: 100%; margin-top: 10px;"
>
<el-table-column prop="age_stage_name" label="年龄阶段"></el-table-column>
<el-table-column prop="description" label="描述"></el-table-column>
<el-table-column prop="min_days" label="最小天数"></el-table-column>
<el-table-column prop="max_days" label="最大天数"></el-table-column>
<el-table-column prop="min_weight" label="最小体重(kg)" :formatter="weightFormatter"></el-table-column>
<el-table-column prop="max_weight" label="最大体重(kg)" :formatter="weightFormatter"></el-table-column>
<el-table-column prop="daily_gain_weight" label="日增重(kg)" :formatter="weightFormatter"></el-table-column>
<el-table-column prop="daily_feed_intake" label="日采食量(kg)" :formatter="weightFormatter"></el-table-column>
<!-- 新增营养需求列 -->
<el-table-column label="营养需求" width="120">
<template #default="scope">
<el-button type="text" @click="handleViewNutrientRequirements(scope.row)">查看详情</el-button>
</template>
</el-table-column>
</el-table>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
<h4>该品种下的年龄阶段简介</h4>
<el-button type="primary" size="small" @click="handleAddPigType(props.row.id)">添加年龄阶段</el-button>
</div>
<template v-if="props.row.pig_types && props.row.pig_types.length > 0">
<el-table
:data="props.row.pig_types"
border
style="width: 100%; margin-top: 10px;"
>
<el-table-column prop="age_stage_name" label="年龄阶段"></el-table-column>
<el-table-column prop="description" label="描述"></el-table-column>
<el-table-column prop="min_days" label="最小天数"></el-table-column>
<el-table-column prop="max_days" label="最大天数"></el-table-column>
<el-table-column prop="min_weight" label="最小体重(kg)" :formatter="weightFormatter"></el-table-column>
<el-table-column prop="max_weight" label="最大体重(kg)" :formatter="weightFormatter"></el-table-column>
<el-table-column prop="daily_gain_weight" label="日增重(kg)" :formatter="weightFormatter"></el-table-column>
<el-table-column prop="daily_feed_intake" label="日采食量(kg)" :formatter="weightFormatter"></el-table-column>
<!-- 新增营养需求列 -->
<el-table-column label="营养需求" width="120">
<template #default="scope">
<el-button type="text" @click="handleViewNutrientRequirements(scope.row)">查看详情</el-button>
</template>
</el-table-column>
<!-- 新增操作列 -->
<el-table-column label="操作" width="150">
<template #default="scope">
<el-button size="small" @click="handleEditPigType(scope.row)">编辑</el-button>
<el-button size="small" type="danger" @click="handleDeletePigType(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</template>
<template v-else>
<el-empty description="暂无年龄阶段数据">
<el-button type="primary" @click="handleAddPigType(props.row.id)">点击添加首个年龄阶段</el-button>
</el-empty>
</template>
</div>
</template>
</el-table-column>
@@ -112,6 +130,24 @@
@refresh="handleNutrientRequirementsRefresh"
></PigNutrientRequirementsDisplay>
</el-dialog>
<!-- 新增年龄阶段编辑弹窗 -->
<el-dialog
v-model="showPigTypeDialog"
:title="isEditingPigType ? '编辑年龄阶段' : '添加年龄阶段'"
width="600px"
:close-on-click-modal="false"
>
<PigTypeEditor
v-if="showPigTypeDialog"
:initialData="currentPigType"
:breedId="currentBreedIdForPigType"
:isEditing="isEditingPigType"
:existingAgeStageIds="getExistingAgeStageIds(currentBreedIdForPigType)"
@save="handlePigTypeSave"
@cancel="handlePigTypeCancel"
></PigTypeEditor>
</el-dialog>
</div>
</template>
@@ -120,12 +156,14 @@ import {ref, onMounted} from 'vue';
import {FeedApi} from '../../api/feed';
import {ElMessageBox, ElMessage} from 'element-plus';
import PigNutrientRequirementsDisplay from './PigNutrientRequirementsDisplay.vue'; // 导入新的组件
import PigTypeEditor from './PigTypeEditor.vue'; // 导入年龄阶段编辑器组件
export default {
name: 'PigBreedTable',
emits: ['edit'], // 声明触发的事件
components: {
PigNutrientRequirementsDisplay, // 注册组件
PigTypeEditor, // 注册年龄阶段编辑器组件
},
setup(props, { emit }) {
const mainTable = ref(null); // el-table 的引用
@@ -140,6 +178,11 @@ export default {
const currentBreedName = ref('');
const currentAgeStageName = ref('');
const currentPigTypeId = ref(null); // 新增:用于传递给营养需求编辑器的 pigType ID
// 年龄阶段编辑弹窗相关
const showPigTypeDialog = ref(false);
const currentPigType = ref({}); // 当前编辑的年龄阶段数据
const currentBreedIdForPigType = ref(null); // 当前操作的品种ID
const isEditingPigType = ref(false); // 是否是编辑模式
const pagination = ref({
page: 1,
@@ -279,6 +322,103 @@ export default {
showNutrientDialog.value = false;
};
// 获取当前品种已有的年龄阶段ID列表
const getExistingAgeStageIds = (breedId) => {
const breed = tableData.value.find(item => item.id === breedId);
return breed && breed.pig_types ? breed.pig_types.map(pt => pt.age_stage_id) : [];
};
// 处理添加年龄阶段
const handleAddPigType = (breedId) => {
isEditingPigType.value = false;
currentPigType.value = { // 初始化新年龄阶段的数据
age_stage_id: null, // age_stage_id 默认为 null由下拉框选择
description: '',
min_days: 0,
max_days: 0,
min_weight: 0,
max_weight: 0,
daily_gain_weight: 0,
daily_feed_intake: 0,
};
currentBreedIdForPigType.value = breedId;
showPigTypeDialog.value = true;
};
// 处理编辑年龄阶段
const handleEditPigType = (pigType) => {
isEditingPigType.value = true;
currentPigType.value = { ...pigType }; // 复制一份数据进行编辑
currentBreedIdForPigType.value = pigType.breed_id;
showPigTypeDialog.value = true;
};
// 处理删除年龄阶段
const handleDeletePigType = (pigType) => {
ElMessageBox.confirm(
`确定要删除年龄阶段 "${pigType.age_stage_name}" 吗?`,
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
).then(async () => {
try {
await FeedApi.deletePigType(pigType.id);
ElMessage.success('年龄阶段删除成功');
// 刷新当前品种的 pig_types 数据
const breedIdToRefresh = pigType.breed_id;
if (breedIdToRefresh) {
try {
const response = await FeedApi.getPigTypes({ breed_id: breedIdToRefresh, page: 1, page_size: 999 });
if (response.data && response.data.list) {
const sortedPigTypes = response.data.list.sort((a, b) => a.age_stage_id - b.age_stage_id);
const index = tableData.value.findIndex(item => item.id === breedIdToRefresh);
if (index !== -1) {
tableData.value[index].pig_types = sortedPigTypes;
}
}
} catch (error) {
console.error('刷新该品种下的猪类型失败:', error);
ElMessage.error('刷新猪类型失败');
}
}
} catch (error) {
ElMessage.error('删除失败: ' + (error.message || '未知错误'));
}
}).catch(() => {
// 用户取消操作
});
};
// 年龄阶段编辑器保存后的回调
const handlePigTypeSave = async () => {
showPigTypeDialog.value = false;
// 刷新当前品种的 pig_types 数据
const breedIdToRefresh = currentBreedIdForPigType.value;
if (breedIdToRefresh) {
try {
const response = await FeedApi.getPigTypes({ breed_id: breedIdToRefresh, page: 1, page_size: 999 });
if (response.data && response.data.list) {
const sortedPigTypes = response.data.list.sort((a, b) => a.age_stage_id - b.age_stage_id);
const index = tableData.value.findIndex(item => item.id === breedIdToRefresh);
if (index !== -1) {
tableData.value[index].pig_types = sortedPigTypes;
}
}
} catch (error) {
console.error('刷新该品种下的猪类型失败:', error);
ElMessage.error('刷新猪类型失败');
}
}
};
// 年龄阶段编辑器取消后的回调
const handlePigTypeCancel = () => {
showPigTypeDialog.value = false;
};
onMounted(() => {
fetchPigBreeds();
});
@@ -295,6 +435,10 @@ export default {
currentBreedName,
currentAgeStageName,
currentPigTypeId,
showPigTypeDialog,
currentPigType,
currentBreedIdForPigType,
isEditingPigType,
handleSearch,
handleSizeChange,
handleCurrentChange,
@@ -305,7 +449,13 @@ export default {
handleEdit,
handleDelete,
fetchPigBreeds, // 将方法暴露出去
handleAddPigType,
handleEditPigType,
handleDeletePigType,
getExistingAgeStageIds, // 暴露给模板
handleNutrientRequirementsRefresh, // 暴露给模板
handlePigTypeSave,
handlePigTypeCancel,
};
},
};

View File

@@ -1,6 +1,6 @@
<template>
<div class="pig-nutrient-requirements-editor">
<el-form style="max-height: 300px; overflow-y: auto;">
<el-form label-width="250px" style="max-height: 300px; overflow-y: auto;">
<el-form-item v-for="(req, index) in localNutrientRequirements" :key="req.nutrient_id" :label="req.nutrient_name">
<div class="nutrient-input-group">
<el-input-number
@@ -30,7 +30,7 @@
</el-form>
<div class="add-nutrient-section">
<el-select v-model="newNutrientId" placeholder="选择要添加的营养素" filterable style="width: 250px;">
<el-select v-model="newNutrientId" placeholder="选择要添加的营养素" filterable style="flex-grow: 1;">
<el-option v-for="item in availableNutrients" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
<el-button type="primary" @click="addNutrientRequirement" style="margin-left: 10px;">添加</el-button>
@@ -167,14 +167,12 @@ export default {
}
/* 确保 el-form-item 的标签不换行 */
.el-form-item {
display: flex; /* 使用 flex 布局 */
/* 移除 display: flex; 因为 label-width 会处理对齐 */
}
.el-form-item__label {
white-space: nowrap; /* 强制不换行 */
flex-shrink: 0; /* 防止收缩 */
/* 移除 flex-shrink: 0; 和 min-width: 150px; 因为 label-width 会处理宽度 */
text-align: right; /* 标签右对齐 */
/* 可以根据需要调整一个最小宽度,但通常 flex-shrink: 0 配合内容自适应就足够 */
min-width: 150px; /* 示例:给一个最小宽度,确保对齐效果 */
}
.nutrient-input-group {
display: flex;

View File

@@ -0,0 +1,175 @@
<template>
<div class="pig-type-editor">
<el-form :model="formData" ref="formRef" label-width="120px">
<el-form-item label="年龄阶段" prop="age_stage_id" :rules="[{ required: true, message: '请选择年龄阶段', trigger: 'change' }]">
<el-select v-model="formData.age_stage_id" placeholder="请选择年龄阶段" filterable style="width: 100%;">
<el-option
v-for="item in ageStages"
:key="item.id"
:label="item.name"
:value="item.id"
:disabled="!isEditing && existingAgeStageIds.includes(item.id)"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input type="textarea" v-model="formData.description"></el-input>
</el-form-item>
<el-form-item label="最小天数" prop="min_days" :rules="[{ required: true, message: '请输入最小天数', trigger: 'blur' }]">
<el-input-number v-model="formData.min_days" :min="0" controls-position="right"></el-input-number>
</el-form-item>
<el-form-item label="最大天数" prop="max_days" :rules="[{ required: true, message: '请输入最大天数', trigger: 'blur' }]">
<el-input-number v-model="formData.max_days" :min="0" controls-position="right"></el-input-number>
</el-form-item>
<el-form-item label="最小体重(kg)" prop="min_weight">
<el-input-number v-model="formData.min_weight" :min="0" :step="0.01" :precision="2" controls-position="right"></el-input-number>
</el-form-item>
<el-form-item label="最大体重(kg)" prop="max_weight">
<el-input-number v-model="formData.max_weight" :min="0" :step="0.01" :precision="2" controls-position="right"></el-input-number>
</el-form-item>
<el-form-item label="日增重(kg)" prop="daily_gain_weight">
<el-input-number v-model="formData.daily_gain_weight" :min="0" :step="0.01" :precision="2" controls-position="right"></el-input-number>
</el-form-item>
<el-form-item label="日采食量(kg)" prop="daily_feed_intake">
<el-input-number v-model="formData.daily_feed_intake" :min="0" :step="0.01" :precision="2" controls-position="right"></el-input-number>
</el-form-item>
</el-form>
<div class="dialog-footer">
<el-button @click="handleCancel">取消</el-button>
<el-button type="primary" @click="handleSave">保存</el-button>
</div>
</div>
</template>
<script>
import { ref, watch, onMounted } from 'vue';
import { ElMessage } from 'element-plus';
import { FeedApi } from '../../api/feed';
export default {
name: 'PigTypeEditor',
props: {
initialData: {
type: Object,
default: () => ({
age_stage_id: null, // 确保默认值是 null
}),
},
breedId: {
type: Number,
required: true,
},
// 新增一个 prop 来接收当前品种已有的 age_stage_id 列表
// 用于在添加模式下禁用已选择的年龄阶段
existingAgeStageIds: {
type: Array,
default: () => [],
},
// 新增一个 prop 来指示是否是编辑模式
isEditing: {
type: Boolean,
default: false,
},
},
emits: ['save', 'cancel'],
setup(props, { emit }) {
const formRef = ref(null);
const formData = ref({});
const ageStages = ref([]); // 用于存储所有年龄阶段列表
watch(() => props.initialData, (newData) => {
// 复制一份数据避免直接修改props
formData.value = { ...newData };
// 如果是添加模式age_stage_id 应该为 null
if (!props.isEditing) {
formData.value.age_stage_id = null;
}
// 将克转换为公斤显示
if (formData.value.min_weight !== undefined && formData.value.min_weight !== null) formData.value.min_weight /= 1000;
if (formData.value.max_weight !== undefined && formData.value.max_weight !== null) formData.value.max_weight /= 1000;
if (formData.value.daily_gain_weight !== undefined && formData.value.daily_gain_weight !== null) formData.value.daily_gain_weight /= 1000;
if (formData.value.daily_feed_intake !== undefined && formData.value.daily_feed_intake !== null) formData.value.daily_feed_intake /= 1000;
// 确保所有重量字段都有默认值,避免 NaN
formData.value.min_weight = formData.value.min_weight || 0;
formData.value.max_weight = formData.value.max_weight || 0;
formData.value.daily_gain_weight = formData.value.daily_gain_weight || 0;
formData.value.daily_feed_intake = formData.value.daily_feed_intake || 0;
}, { immediate: true, deep: true });
const handleSave = () => {
formRef.value.validate(async (valid) => {
if (valid) {
try {
const dataToSave = {
breed_id: props.breedId,
age_stage_id: formData.value.age_stage_id,
description: formData.value.description,
min_days: formData.value.min_days,
max_days: formData.value.max_days,
min_weight: formData.value.min_weight * 1000, // Convert kg to g
max_weight: formData.value.max_weight * 1000, // Convert kg to g
daily_gain_weight: formData.value.daily_gain_weight * 1000, // Convert kg to g
daily_feed_intake: formData.value.daily_feed_intake * 1000, // Convert kg to g
};
if (formData.value.id) {
// 编辑现有年龄阶段
await FeedApi.updatePigType(formData.value.id, dataToSave);
ElMessage.success('年龄阶段更新成功');
} else {
// 添加新年龄阶段
await FeedApi.createPigType(dataToSave);
ElMessage.success('年龄阶段添加成功');
}
emit('save');
} catch (error) {
ElMessage.error('操作失败: ' + (error.message || '未知错误'));
}
}
});
};
const handleCancel = () => {
emit('cancel');
};
// 获取所有年龄阶段列表
const fetchAgeStages = async () => {
try {
const response = await FeedApi.getPigAgeStages({ page: 1, page_size: 999 }); // 明确传递 page: 1
if (response.data && response.data.list) {
ageStages.value = response.data.list;
}
} catch (error) {
ElMessage.error('获取年龄阶段列表失败');
}
};
onMounted(() => {
fetchAgeStages();
});
return {
formRef,
formData,
ageStages,
handleSave,
handleCancel,
};
},
};
</script>
<style scoped>
.pig-type-editor {
padding: 20px;
}
.dialog-footer {
margin-top: 20px;
text-align: right;
}
</style>