Compare commits
12 Commits
0985744184
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 2734306690 | |||
| 543adc1ad6 | |||
| 7bcd8fb873 | |||
| ae87ddb56d | |||
| e9545c9be1 | |||
| e1f38fd995 | |||
| 2649ed048a | |||
| b368c172c5 | |||
| 93f66d844c | |||
| 7166d5049f | |||
| 908af8eaa5 | |||
| 091ba71069 |
@@ -3186,6 +3186,52 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/feed/recipes/generate-prioritized-stock/{pig_type_id}": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "根据指定的猪类型ID,优先使用有库存的原料,自动计算并创建一个配方。",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"饲料管理-配方"
|
||||
],
|
||||
"summary": "使用优先有库存原料的策略生成配方",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "猪类型ID",
|
||||
"name": "pig_type_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "业务码为201代表创建成功",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/controller.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/dto.GenerateRecipeResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/feed/recipes/{id}": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -3716,7 +3762,6 @@
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
7,
|
||||
-1,
|
||||
0,
|
||||
1,
|
||||
@@ -3726,12 +3771,12 @@
|
||||
5,
|
||||
-1,
|
||||
5,
|
||||
6
|
||||
6,
|
||||
7
|
||||
],
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"x-enum-varnames": [
|
||||
"_numLevels",
|
||||
"DebugLevel",
|
||||
"InfoLevel",
|
||||
"WarnLevel",
|
||||
@@ -3741,7 +3786,8 @@
|
||||
"FatalLevel",
|
||||
"_minLevel",
|
||||
"_maxLevel",
|
||||
"InvalidLevel"
|
||||
"InvalidLevel",
|
||||
"_numLevels"
|
||||
],
|
||||
"name": "level",
|
||||
"in": "query"
|
||||
@@ -10620,7 +10666,6 @@
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"enum": [
|
||||
7,
|
||||
-1,
|
||||
0,
|
||||
1,
|
||||
@@ -10630,10 +10675,10 @@
|
||||
5,
|
||||
-1,
|
||||
5,
|
||||
6
|
||||
6,
|
||||
7
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"_numLevels",
|
||||
"DebugLevel",
|
||||
"InfoLevel",
|
||||
"WarnLevel",
|
||||
@@ -10643,7 +10688,8 @@
|
||||
"FatalLevel",
|
||||
"_minLevel",
|
||||
"_maxLevel",
|
||||
"InvalidLevel"
|
||||
"InvalidLevel",
|
||||
"_numLevels"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -647,6 +647,15 @@ export const generateRecipeFromAllMaterials = (pigTypeId) => {
|
||||
return http.post(`/api/v1/feed/recipes/generate-from-all-materials/${pigTypeId}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据指定的猪类型ID,优先使用有库存的原料,自动计算并创建一个配方。
|
||||
* @param {number} pigTypeId - 猪类型ID
|
||||
* @returns {Promise<Response<GenerateRecipeResponse>>}
|
||||
*/
|
||||
export const generatePrioritizedStockRecipe = (pigTypeId) => {
|
||||
return http.post(`/api/v1/feed/recipes/generate-prioritized-stock/${pigTypeId}`);
|
||||
};
|
||||
|
||||
|
||||
export const FeedApi = {
|
||||
getNutrients,
|
||||
@@ -682,4 +691,5 @@ export const FeedApi = {
|
||||
updateRecipe,
|
||||
deleteRecipe,
|
||||
generateRecipeFromAllMaterials,
|
||||
generatePrioritizedStockRecipe,
|
||||
};
|
||||
|
||||
@@ -1,25 +1,29 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
title="一键生成配方"
|
||||
v-model="dialogVisible"
|
||||
width="500px"
|
||||
:before-close="handleCancel"
|
||||
title="一键生成配方"
|
||||
v-model="dialogVisible"
|
||||
width="500px"
|
||||
:before-close="handleCancel"
|
||||
>
|
||||
<el-form :model="form" ref="generateRecipeForm" label-width="100px">
|
||||
<el-form-item label="生成对象" prop="selectedPigType" :rules="[{ required: true, message: '请选择生成对象', trigger: 'change' }]">
|
||||
<el-form-item label="生成对象" prop="selectedPigType"
|
||||
:rules="[{ required: true, message: '请选择生成对象', trigger: 'change' }]">
|
||||
<el-select v-model="form.selectedPigType" placeholder="请选择猪品种-猪年龄阶段" style="width: 100%;">
|
||||
<el-option
|
||||
v-for="item in pigTypesOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
v-for="item in pigTypesOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="生成方式" prop="selectedGenerationMethod" :rules="[{ required: true, message: '请选择生成方式', trigger: 'change' }]">
|
||||
<el-form-item label="生成方式" prop="selectedGenerationMethod"
|
||||
:rules="[{ required: true, message: '请选择生成方式', trigger: 'change' }]">
|
||||
<el-select v-model="form.selectedGenerationMethod" placeholder="请选择生成方式" style="width: 100%;">
|
||||
<el-option label="使用系统中所有可用的原料" value="all_raw_materials"></el-option>
|
||||
<!-- 新增选项 -->
|
||||
<el-option label="优先使用有库存的原料" value="prefer_in_stock_materials"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@@ -34,9 +38,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive, watch, onMounted, computed } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { FeedApi } from '../../api/feed'; // 假设 FeedApi 包含生成配方接口
|
||||
import {ref, reactive, watch, onMounted, computed} from 'vue';
|
||||
import {ElMessage} from 'element-plus';
|
||||
import {FeedApi} from '../../api/feed'; // 假设 FeedApi 包含生成配方接口
|
||||
|
||||
export default {
|
||||
name: 'GenerateRecipeDialog',
|
||||
@@ -47,7 +51,7 @@ export default {
|
||||
},
|
||||
},
|
||||
emits: ['update:visible', 'success', 'cancel'],
|
||||
setup(props, { emit }) {
|
||||
setup(props, {emit}) {
|
||||
const generateRecipeForm = ref(null);
|
||||
const loading = ref(false);
|
||||
const pigTypesOptions = ref([]);
|
||||
@@ -68,12 +72,12 @@ export default {
|
||||
*/
|
||||
const fetchPigTypes = async () => {
|
||||
try {
|
||||
const response = await FeedApi.getPigTypes({ page: 1, page_size: 999 }); // 调用 FeedApi 中的 getPigTypes 方法获取猪类型列表
|
||||
const response = await FeedApi.getPigTypes({page: 1, page_size: 999}); // 调用 FeedApi 中的 getPigTypes 方法获取猪类型列表
|
||||
if (response.data && response.data.list) {
|
||||
pigTypesOptions.value = response.data.list.map(pigType => ({
|
||||
label: `${pigType.breed_name}-${pigType.age_stage_name}`,
|
||||
value: `${pigType.id}`, // 下拉框的值直接是 pigType 的 ID
|
||||
}));
|
||||
label: `${pigType.breed_name}-${pigType.age_stage_name}`,
|
||||
value: `${pigType.id}`, // 下拉框的值直接是 pigType 的 ID
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取猪种类失败:', error);
|
||||
@@ -92,10 +96,20 @@ export default {
|
||||
loading.value = true;
|
||||
try {
|
||||
const pigTypeId = parseInt(form.selectedPigType); // 获取选中的 pigType ID
|
||||
|
||||
// 调用一键生成配方的接口
|
||||
const response = await FeedApi.generateRecipeFromAllMaterials(pigTypeId);
|
||||
|
||||
let response = null; // 声明 response 变量
|
||||
|
||||
if (form.selectedGenerationMethod === 'all_raw_materials') {
|
||||
// 调用使用所有原料生成配方的接口
|
||||
response = await FeedApi.generateRecipeFromAllMaterials(pigTypeId);
|
||||
} else if (form.selectedGenerationMethod === 'prefer_in_stock_materials') {
|
||||
// 调用优先使用有库存原料生成配方的接口
|
||||
response = await FeedApi.generatePrioritizedStockRecipe(pigTypeId);
|
||||
} else {
|
||||
ElMessage.error('未知的生成方式');
|
||||
loading.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.data) {
|
||||
ElMessage.success('配方生成成功!');
|
||||
emit('success', response.data.name, response.data.description); // 传递配方名称和简介
|
||||
|
||||
@@ -199,12 +199,23 @@ export default {
|
||||
};
|
||||
|
||||
// 处理查看营养需求详情
|
||||
const handleViewNutrientRequirements = (pigType) => {
|
||||
currentNutrientRequirements.value = pigType.pig_nutrient_requirements || [];
|
||||
currentBreedName.value = pigType.breed_name;
|
||||
currentAgeStageName.value = pigType.age_stage_name;
|
||||
currentPigTypeId.value = pigType.id; // 设置当前的 pigType ID
|
||||
showNutrientDialog.value = true;
|
||||
const handleViewNutrientRequirements = async (pigType) => { // 添加 async
|
||||
try {
|
||||
// 强制重新获取该 pigType 的最新详情
|
||||
const response = await FeedApi.getPigTypeById(pigType.id);
|
||||
if (response.data) {
|
||||
currentNutrientRequirements.value = response.data.pig_nutrient_requirements || [];
|
||||
currentBreedName.value = response.data.breed_name;
|
||||
currentAgeStageName.value = response.data.age_stage_name;
|
||||
currentPigTypeId.value = response.data.id; // 设置当前的 pigType ID
|
||||
showNutrientDialog.value = true;
|
||||
} else {
|
||||
ElMessage.error('获取猪类型详情失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取猪类型详情失败:', error);
|
||||
ElMessage.error('获取猪类型详情失败');
|
||||
}
|
||||
};
|
||||
|
||||
const handleEdit = (row) => {
|
||||
|
||||
@@ -275,12 +275,23 @@ export default {
|
||||
};
|
||||
|
||||
// 处理查看营养需求详情
|
||||
const handleViewNutrientRequirements = (pigType) => {
|
||||
currentNutrientRequirements.value = pigType.pig_nutrient_requirements || [];
|
||||
currentBreedName.value = pigType.breed_name;
|
||||
currentAgeStageName.value = pigType.age_stage_name;
|
||||
currentPigTypeId.value = pigType.id; // 设置当前的 pigType ID
|
||||
showNutrientDialog.value = true;
|
||||
const handleViewNutrientRequirements = async (pigType) => { // 添加 async
|
||||
try {
|
||||
// 强制重新获取该 pigType 的最新详情
|
||||
const response = await FeedApi.getPigTypeById(pigType.id);
|
||||
if (response.data) {
|
||||
currentNutrientRequirements.value = response.data.pig_nutrient_requirements || [];
|
||||
currentBreedName.value = response.data.breed_name;
|
||||
currentAgeStageName.value = response.data.age_stage_name;
|
||||
currentPigTypeId.value = response.data.id; // 设置当前的 pigType ID
|
||||
showNutrientDialog.value = true;
|
||||
} else {
|
||||
ElMessage.error('获取猪类型详情失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取猪类型详情失败:', error);
|
||||
ElMessage.error('获取猪类型详情失败');
|
||||
}
|
||||
};
|
||||
|
||||
const handleEdit = (row) => {
|
||||
|
||||
@@ -132,7 +132,7 @@ export default {
|
||||
|
||||
try {
|
||||
// 调用API更新营养需求
|
||||
await FeedApi.updatePigNutrientRequirements(props.pigTypeId, { pig_nutrient_requirements: requirementsToSave });
|
||||
await FeedApi.updatePigTypeNutrientRequirements(props.pigTypeId, { nutrient_requirements: requirementsToSave });
|
||||
ElMessage.success('营养需求更新成功');
|
||||
emit('save'); // 通知父组件保存成功
|
||||
} catch (error) {
|
||||
|
||||
359
src/components/feed/RecipeCompareDialog.vue
Normal file
359
src/components/feed/RecipeCompareDialog.vue
Normal file
@@ -0,0 +1,359 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:model-value="visible"
|
||||
title="配方对比"
|
||||
@close="handleClose"
|
||||
width="80%"
|
||||
top="5vh"
|
||||
>
|
||||
<el-form :inline="true" class="compare-form">
|
||||
<el-form-item label="对比类型">
|
||||
<el-select v-model="compareType" placeholder="请选择对比类型" style="width: 200px;">
|
||||
<el-option label="配方" value="recipe"></el-option>
|
||||
<el-option label="猪的营养需求" value="pigType"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="compareType === 'recipe'" label="选择配方">
|
||||
<el-select v-model="selectedCompareRecipeId" filterable placeholder="请选择对比配方" style="width: 300px;">
|
||||
<el-option
|
||||
v-for="item in recipeList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
:disabled="item.id === currentRecipe.id"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="compareType === 'pigType'" label="选择猪类型">
|
||||
<el-select v-model="selectedPigTypeId" filterable placeholder="请选择猪类型" style="width: 300px;">
|
||||
<el-option
|
||||
v-for="item in pigTypeList"
|
||||
:key="item.id"
|
||||
:label="item.breed_name + ' - ' + item.age_stage_name"
|
||||
:value="item.id"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="startCompare" :disabled="!canCompare">开始对比</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-divider />
|
||||
|
||||
<div v-if="comparing" class="loading-spinner">
|
||||
<el-skeleton :rows="5" animated />
|
||||
</div>
|
||||
<div v-else-if="compareError" class="error-message">
|
||||
<el-alert
|
||||
title="对比数据加载失败"
|
||||
:description="compareError"
|
||||
type="error"
|
||||
show-icon
|
||||
@close="compareError = null"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="compareResult.length > 0" class="table-wrapper">
|
||||
<el-table :data="compareResult" style="width: 100%" border>
|
||||
<el-table-column label="营养素" fixed>
|
||||
<template #default="scope">
|
||||
<el-tooltip :content="nutrientsDescriptionMap[scope.row.nutrientName]" placement="top">
|
||||
<span>{{ scope.row.nutrientName }}</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="currentRecipe.name">
|
||||
<template #default="scope">
|
||||
<span :style="{ color: scope.row.currentRecipeValue > scope.row.compareRecipeValue ? 'green' : (scope.row.currentRecipeValue < scope.row.compareRecipeValue ? 'red' : 'inherit') }">
|
||||
{{ scope.row.currentRecipeValue !== undefined ? scope.row.currentRecipeValue.toFixed(2) : '-' }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<template v-if="compareType === 'recipe'">
|
||||
<el-table-column :label="compareRecipeName">
|
||||
<template #default="scope">
|
||||
<span :style="{ color: scope.row.compareRecipeValue > scope.row.currentRecipeValue ? 'green' : (scope.row.compareRecipeValue < scope.row.currentRecipeValue ? 'red' : 'inherit') }">
|
||||
{{ scope.row.compareRecipeValue !== undefined ? scope.row.compareRecipeValue.toFixed(2) : '-' }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
|
||||
<template v-else-if="compareType === 'pigType'">
|
||||
<el-table-column :label="pigTypeName + ' (下限)'">
|
||||
<template #default="scope">
|
||||
{{ scope.row.minRequirement !== undefined ? scope.row.minRequirement.toFixed(2) : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="pigTypeName + ' (上限)'">
|
||||
<template #default="scope">
|
||||
{{ scope.row.maxRequirement !== undefined ? scope.row.maxRequirement.toFixed(2) : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="是否达标" align="center">
|
||||
<template #default="scope">
|
||||
<el-icon v-if="scope.row.isMet" color="green"><Check /></el-icon>
|
||||
<el-icon v-else color="red"><Close /></el-icon>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
</el-table>
|
||||
</div>
|
||||
<div v-else class="no-data-message">
|
||||
<el-empty description="请选择对比项并点击开始对比" />
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="handleClose">关闭</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, watch, computed } from 'vue';
|
||||
import { getNutrients, getRecipes, getRecipeById, getPigTypes, getPigTypeById, getRawMaterialById } from '../../api/feed';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { Check, Close } from '@element-plus/icons-vue';
|
||||
|
||||
export default {
|
||||
name: 'RecipeCompareDialog',
|
||||
components: {
|
||||
Check,
|
||||
Close,
|
||||
},
|
||||
props: {
|
||||
visible: Boolean,
|
||||
currentRecipe: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ['update:visible'],
|
||||
setup(props, { emit }) {
|
||||
const compareType = ref('recipe'); // 'recipe' or 'pigType'
|
||||
const recipeList = ref([]);
|
||||
const selectedCompareRecipeId = ref(null);
|
||||
const pigTypeList = ref([]);
|
||||
const selectedPigTypeId = ref(null);
|
||||
const comparing = ref(false);
|
||||
const compareError = ref(null);
|
||||
const compareResult = ref([]);
|
||||
const compareRecipeName = ref('');
|
||||
const pigTypeName = ref('');
|
||||
const nutrientsDescriptionMap = ref({}); // 新增:存储营养素描述的映射
|
||||
|
||||
const handleClose = () => {
|
||||
emit('update:visible', false);
|
||||
// 重置状态
|
||||
compareType.value = 'recipe';
|
||||
selectedCompareRecipeId.value = null;
|
||||
selectedPigTypeId.value = null;
|
||||
compareResult.value = [];
|
||||
compareError.value = null;
|
||||
compareRecipeName.value = '';
|
||||
pigTypeName.value = '';
|
||||
};
|
||||
|
||||
const canCompare = computed(() => {
|
||||
if (compareType.value === 'recipe') {
|
||||
return selectedCompareRecipeId.value !== null;
|
||||
} else if (compareType.value === 'pigType') {
|
||||
return selectedPigTypeId.value !== null;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// 获取所有配方列表
|
||||
const fetchRecipeList = async () => {
|
||||
try {
|
||||
const response = await getRecipes({ page_size: 999 });
|
||||
recipeList.value = response.data.list || [];
|
||||
} catch (err) {
|
||||
ElMessage.error('获取配方列表失败: ' + (err.message || '未知错误'));
|
||||
}
|
||||
};
|
||||
|
||||
// 获取所有猪类型列表
|
||||
const fetchPigTypeList = async () => {
|
||||
try {
|
||||
const response = await getPigTypes({ page: 1, page_size: 999 });
|
||||
pigTypeList.value = response.data.list || [];
|
||||
} catch (err) {
|
||||
ElMessage.error('获取猪类型列表失败: ' + (err.message || '未知错误'));
|
||||
}
|
||||
};
|
||||
|
||||
// 新增:获取所有营养素列表及其描述
|
||||
const fetchNutrientsDescriptions = async () => {
|
||||
try {
|
||||
const response = await getNutrients({ page_size: 999 });
|
||||
nutrientsDescriptionMap.value = response.data.list.reduce((map, nutrient) => {
|
||||
map[nutrient.name] = nutrient.description;
|
||||
return map;
|
||||
}, {});
|
||||
} catch (err) {
|
||||
ElMessage.error('获取营养素描述失败: ' + (err.message || '未知错误'));
|
||||
}
|
||||
};
|
||||
|
||||
// 计算配方的营养成分汇总
|
||||
const calculateRecipeNutrientSummary = async (recipe) => {
|
||||
const summary = new Map();
|
||||
if (!recipe || !recipe.recipe_ingredients || recipe.recipe_ingredients.length === 0) {
|
||||
return summary;
|
||||
}
|
||||
|
||||
const rawMaterialPromises = recipe.recipe_ingredients.map(ing => getRawMaterialById(ing.raw_material_id));
|
||||
const rawMaterialResponses = await Promise.all(rawMaterialPromises);
|
||||
|
||||
const ingredientDetails = rawMaterialResponses.map((res, index) => ({
|
||||
...res.data,
|
||||
percentage: recipe.recipe_ingredients[index].percentage,
|
||||
}));
|
||||
|
||||
ingredientDetails.forEach(ing => {
|
||||
if (ing.raw_material_nutrients) {
|
||||
ing.raw_material_nutrients.forEach(nutrient => {
|
||||
const contribution = nutrient.value * (ing.percentage / 100);
|
||||
if (summary.has(nutrient.nutrient_name)) {
|
||||
summary.set(nutrient.nutrient_name, summary.get(nutrient.nutrient_name) + contribution);
|
||||
} else {
|
||||
summary.set(nutrient.nutrient_name, contribution);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return summary;
|
||||
};
|
||||
|
||||
const startCompare = async () => {
|
||||
comparing.value = true;
|
||||
compareError.value = null;
|
||||
compareResult.value = [];
|
||||
|
||||
try {
|
||||
const currentRecipeNutrients = await calculateRecipeNutrientSummary(props.currentRecipe);
|
||||
|
||||
if (compareType.value === 'recipe') {
|
||||
const compareRecipe = recipeList.value.find(r => r.id === selectedCompareRecipeId.value);
|
||||
if (!compareRecipe) {
|
||||
throw new Error('未找到对比配方');
|
||||
}
|
||||
compareRecipeName.value = compareRecipe.name;
|
||||
const otherRecipeDetails = await getRecipeById(selectedCompareRecipeId.value);
|
||||
const otherRecipeNutrients = await calculateRecipeNutrientSummary(otherRecipeDetails.data);
|
||||
|
||||
const allNutrientNames = new Set([...currentRecipeNutrients.keys(), ...otherRecipeNutrients.keys()]);
|
||||
|
||||
allNutrientNames.forEach(name => {
|
||||
const currentRecipeValueRaw = currentRecipeNutrients.has(name) ? currentRecipeNutrients.get(name) : undefined;
|
||||
const compareRecipeValueRaw = otherRecipeNutrients.has(name) ? otherRecipeNutrients.get(name) : undefined;
|
||||
|
||||
const currentRecipeValue = currentRecipeValueRaw !== undefined ? parseFloat(currentRecipeValueRaw.toFixed(2)) : undefined;
|
||||
const compareRecipeValue = compareRecipeValueRaw !== undefined ? parseFloat(compareRecipeValueRaw.toFixed(2)) : undefined;
|
||||
|
||||
compareResult.value.push({
|
||||
nutrientName: name,
|
||||
currentRecipeValue,
|
||||
compareRecipeValue,
|
||||
});
|
||||
});
|
||||
|
||||
} else if (compareType.value === 'pigType') {
|
||||
const pigType = pigTypeList.value.find(p => p.id === selectedPigTypeId.value);
|
||||
if (!pigType) {
|
||||
throw new Error('未找到猪类型');
|
||||
}
|
||||
pigTypeName.value = `${pigType.breed_name} - ${pigType.age_stage_name}`;
|
||||
const pigTypeDetails = await getPigTypeById(selectedPigTypeId.value);
|
||||
const pigNutrientRequirements = new Map();
|
||||
pigTypeDetails.data.pig_nutrient_requirements.forEach(req => {
|
||||
pigNutrientRequirements.set(req.nutrient_name, {
|
||||
min: req.min_requirement,
|
||||
max: req.max_requirement,
|
||||
});
|
||||
});
|
||||
|
||||
const allNutrientNames = new Set([...currentRecipeNutrients.keys(), ...pigNutrientRequirements.keys()]);
|
||||
|
||||
allNutrientNames.forEach(name => {
|
||||
const currentRecipeValueRaw = currentRecipeNutrients.has(name) ? currentRecipeNutrients.get(name) : undefined;
|
||||
const requirementRaw = pigNutrientRequirements.has(name) ? pigNutrientRequirements.get(name) : { min: undefined, max: undefined };
|
||||
|
||||
const currentRecipeValue = currentRecipeValueRaw !== undefined ? parseFloat(currentRecipeValueRaw.toFixed(2)) : undefined;
|
||||
const minRequirement = requirementRaw.min !== undefined ? parseFloat(requirementRaw.min.toFixed(2)) : undefined;
|
||||
const maxRequirement = requirementRaw.max !== undefined ? parseFloat(requirementRaw.max.toFixed(2)) : undefined;
|
||||
|
||||
const isMet = (minRequirement === undefined && maxRequirement === undefined) || // 如果猪没有这个营养素的需求,则认为达标
|
||||
(currentRecipeValue !== undefined &&
|
||||
(minRequirement === undefined || currentRecipeValue >= minRequirement) &&
|
||||
(maxRequirement === undefined || currentRecipeValue <= maxRequirement));
|
||||
|
||||
compareResult.value.push({
|
||||
nutrientName: name,
|
||||
currentRecipeValue,
|
||||
minRequirement: minRequirement,
|
||||
maxRequirement: maxRequirement,
|
||||
isMet: isMet,
|
||||
});
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("对比失败:", err);
|
||||
compareError.value = err.message || '未知错误';
|
||||
ElMessage.error('对比失败: ' + (err.message || '未知错误'));
|
||||
} finally {
|
||||
comparing.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
watch(() => props.visible, (newVal) => {
|
||||
if (newVal) {
|
||||
fetchRecipeList();
|
||||
fetchPigTypeList();
|
||||
fetchNutrientsDescriptions(); // 新增:在对话框显示时获取营养素描述
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
return {
|
||||
compareType,
|
||||
recipeList,
|
||||
selectedCompareRecipeId,
|
||||
pigTypeList,
|
||||
selectedPigTypeId,
|
||||
comparing,
|
||||
compareError,
|
||||
compareResult,
|
||||
compareRecipeName,
|
||||
pigTypeName,
|
||||
handleClose,
|
||||
canCompare,
|
||||
startCompare,
|
||||
nutrientsDescriptionMap, // 新增:返回 nutrientsDescriptionMap
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.compare-form {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.loading-spinner, .error-message, .no-data-message {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.dialog-footer {
|
||||
text-align: right;
|
||||
}
|
||||
.table-wrapper {
|
||||
margin: 0 auto; /* 水平居中 */
|
||||
max-width: 100%; /* 确保不超过父容器宽度 */
|
||||
}
|
||||
</style>
|
||||
0
src/components/feed/RecipeComparisonDialog.vue
Normal file
0
src/components/feed/RecipeComparisonDialog.vue
Normal file
@@ -65,12 +65,18 @@
|
||||
</el-tabs>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button v-if="!isEditing" @click="openCompareDialog">对比</el-button>
|
||||
<el-button v-if="!isEditing" @click="handleEdit">编辑配方</el-button>
|
||||
<el-button v-else @click="handleCancelEdit">取消</el-button>
|
||||
<el-button v-if="isEditing" type="primary" @click="handleSaveRecipe">保存</el-button>
|
||||
<el-button v-else @click="handleClose">关闭</el-button>
|
||||
</span>
|
||||
</template>
|
||||
<RecipeCompareDialog
|
||||
v-model:visible="compareDialogVisible"
|
||||
:current-recipe="recipe"
|
||||
@close="compareDialogVisible = false"
|
||||
/>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
@@ -79,6 +85,7 @@ import { ref, watch, nextTick, computed } from 'vue';
|
||||
import { getRawMaterialById, getRawMaterials, updateRecipe, getRecipeById } from '../../api/feed';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { Delete } from '@element-plus/icons-vue';
|
||||
import RecipeCompareDialog from './RecipeCompareDialog.vue'; // 引入对比组件
|
||||
|
||||
export default {
|
||||
name: 'RecipeDetailDialog',
|
||||
@@ -101,11 +108,16 @@ export default {
|
||||
const nutrientTableRef = ref(null);
|
||||
const allRawMaterials = ref([]); // 所有可用原料列表
|
||||
const newIngredientId = ref(null); // 待添加的新原料ID
|
||||
const compareDialogVisible = ref(false); // 控制对比对话框的显示
|
||||
|
||||
const handleClose = () => {
|
||||
emit('update:visible', false);
|
||||
};
|
||||
|
||||
const openCompareDialog = () => {
|
||||
compareDialogVisible.value = true;
|
||||
};
|
||||
|
||||
const calculateNutrientSummary = (ingredients) => {
|
||||
const summary = new Map();
|
||||
ingredients.forEach(ing => {
|
||||
@@ -213,11 +225,11 @@ export default {
|
||||
|
||||
// 保存配方
|
||||
const handleSaveRecipe = async () => {
|
||||
// 验证占比总和是否为100
|
||||
// 验证占比总和是否不超过100
|
||||
const totalPercentage = localIngredientDetails.value.reduce((sum, ing) => sum + ing.percentage, 0);
|
||||
|
||||
if (Math.abs(totalPercentage - 100) > 0.001) { // 允许浮点数误差
|
||||
ElMessage.error(`原料总占比必须为100%,当前为${totalPercentage.toFixed(2)}%`);
|
||||
if (totalPercentage > 100.001) { // 允许浮点数误差
|
||||
ElMessage.error(`原料总占比不能超过100%,当前为${totalPercentage.toFixed(2)}%`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -285,8 +297,14 @@ export default {
|
||||
newIngredientId,
|
||||
updateNutrientSummary,
|
||||
Delete, // 暴露 Delete 图标组件
|
||||
compareDialogVisible, // 暴露对比对话框的可见性
|
||||
openCompareDialog, // 暴露打开对比对话框的方法
|
||||
};
|
||||
},
|
||||
components: {
|
||||
Delete,
|
||||
RecipeCompareDialog, // 注册对比组件
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ export default {
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
try {
|
||||
const response = await getRecipes({ page: 1, page_size: 999 });
|
||||
const response = await getRecipes({ page: 1, page_size: 999, order_by: "id DESC" });
|
||||
this.recipes = response.data.list || [];
|
||||
} catch (err) {
|
||||
this.error = err.message || '未知错误';
|
||||
|
||||
Reference in New Issue
Block a user