Compare commits
2 Commits
6387a5798b
...
5fb1808943
| Author | SHA1 | Date | |
|---|---|---|---|
| 5fb1808943 | |||
| 17ae47fa68 |
@@ -3140,6 +3140,52 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/feed/recipes/generate-from-all-materials/{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}": {
|
"/api/v1/feed/recipes/{id}": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -3664,7 +3710,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"enum": [
|
"enum": [
|
||||||
7,
|
|
||||||
-1,
|
-1,
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
@@ -3674,12 +3719,12 @@
|
|||||||
5,
|
5,
|
||||||
-1,
|
-1,
|
||||||
5,
|
5,
|
||||||
6
|
6,
|
||||||
|
7
|
||||||
],
|
],
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int32",
|
"format": "int32",
|
||||||
"x-enum-varnames": [
|
"x-enum-varnames": [
|
||||||
"_numLevels",
|
|
||||||
"DebugLevel",
|
"DebugLevel",
|
||||||
"InfoLevel",
|
"InfoLevel",
|
||||||
"WarnLevel",
|
"WarnLevel",
|
||||||
@@ -3689,7 +3734,8 @@
|
|||||||
"FatalLevel",
|
"FatalLevel",
|
||||||
"_minLevel",
|
"_minLevel",
|
||||||
"_maxLevel",
|
"_maxLevel",
|
||||||
"InvalidLevel"
|
"InvalidLevel",
|
||||||
|
"_numLevels"
|
||||||
],
|
],
|
||||||
"name": "level",
|
"name": "level",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
@@ -7434,6 +7480,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.GenerateRecipeResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"description": {
|
||||||
|
"description": "新生成的配方描述",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"description": "新生成的配方ID",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"description": "新生成的配方名称",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.HistoricalAlarmDTO": {
|
"dto.HistoricalAlarmDTO": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -10518,7 +10581,6 @@
|
|||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int32",
|
"format": "int32",
|
||||||
"enum": [
|
"enum": [
|
||||||
7,
|
|
||||||
-1,
|
-1,
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
@@ -10528,10 +10590,10 @@
|
|||||||
5,
|
5,
|
||||||
-1,
|
-1,
|
||||||
5,
|
5,
|
||||||
6
|
6,
|
||||||
|
7
|
||||||
],
|
],
|
||||||
"x-enum-varnames": [
|
"x-enum-varnames": [
|
||||||
"_numLevels",
|
|
||||||
"DebugLevel",
|
"DebugLevel",
|
||||||
"InfoLevel",
|
"InfoLevel",
|
||||||
"WarnLevel",
|
"WarnLevel",
|
||||||
@@ -10541,7 +10603,8 @@
|
|||||||
"FatalLevel",
|
"FatalLevel",
|
||||||
"_minLevel",
|
"_minLevel",
|
||||||
"_maxLevel",
|
"_maxLevel",
|
||||||
"InvalidLevel"
|
"InvalidLevel",
|
||||||
|
"_numLevels"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -317,6 +317,13 @@ import {PaginationDTO, Response} from '../enums';
|
|||||||
* @property {Array<RecipeIngredientDto>} [recipe_ingredients] - 配方原料组成
|
* @property {Array<RecipeIngredientDto>} [recipe_ingredients] - 配方原料组成
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} GenerateRecipeResponse
|
||||||
|
* @property {number} id - 新生成的配方ID
|
||||||
|
* @property {string} name - 新生成的配方名称
|
||||||
|
* @property {string} description - 新生成的配方描述
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
// --- API Functions ---
|
// --- API Functions ---
|
||||||
|
|
||||||
@@ -628,6 +635,15 @@ export const deleteRecipe = (id) => {
|
|||||||
return http.delete(`/api/v1/feed/recipes/${id}`);
|
return http.delete(`/api/v1/feed/recipes/${id}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用系统中所有可用的原料一键生成配方
|
||||||
|
* @param {number} pigTypeId - 猪类型ID
|
||||||
|
* @returns {Promise<Response<GenerateRecipeResponse>>}
|
||||||
|
*/
|
||||||
|
export const generateRecipeFromAllMaterials = (pigTypeId) => {
|
||||||
|
return http.post(`/api/v1/feed/recipes/generate-from-all-materials/${pigTypeId}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export const FeedApi = {
|
export const FeedApi = {
|
||||||
getNutrients,
|
getNutrients,
|
||||||
@@ -662,4 +678,5 @@ export const FeedApi = {
|
|||||||
getRecipeById,
|
getRecipeById,
|
||||||
updateRecipe,
|
updateRecipe,
|
||||||
deleteRecipe,
|
deleteRecipe,
|
||||||
|
generateRecipeFromAllMaterials,
|
||||||
};
|
};
|
||||||
|
|||||||
164
src/components/feed/GenerateRecipeDialog.vue
Normal file
164
src/components/feed/GenerateRecipeDialog.vue
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
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-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"
|
||||||
|
></el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<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-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="handleCancel">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleGenerate" :loading="loading">生成</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { ref, reactive, watch, onMounted, computed } from 'vue';
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
import { FeedApi } from '../../api/feed'; // 假设 FeedApi 包含生成配方接口
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'GenerateRecipeDialog',
|
||||||
|
props: {
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ['update:visible', 'success', 'cancel'],
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const generateRecipeForm = ref(null);
|
||||||
|
const loading = ref(false);
|
||||||
|
const pigTypesOptions = ref([]);
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
selectedPigType: '',
|
||||||
|
selectedGenerationMethod: 'all_raw_materials', // 默认选中
|
||||||
|
});
|
||||||
|
|
||||||
|
// 计算属性,用于控制 dialog 的显示
|
||||||
|
const dialogVisible = computed({
|
||||||
|
get: () => props.visible,
|
||||||
|
set: (val) => emit('update:visible', val),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取猪种类数据并格式化为下拉选项
|
||||||
|
*/
|
||||||
|
const fetchPigTypes = async () => {
|
||||||
|
try {
|
||||||
|
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
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取猪种类失败:', error);
|
||||||
|
ElMessage.error('获取猪种类失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理生成配方逻辑
|
||||||
|
*/
|
||||||
|
const handleGenerate = async () => {
|
||||||
|
if (!generateRecipeForm.value) return;
|
||||||
|
|
||||||
|
generateRecipeForm.value.validate(async (valid) => {
|
||||||
|
if (valid) {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const pigTypeId = parseInt(form.selectedPigType); // 获取选中的 pigType ID
|
||||||
|
|
||||||
|
// 调用一键生成配方的接口
|
||||||
|
const response = await FeedApi.generateRecipeFromAllMaterials(pigTypeId);
|
||||||
|
|
||||||
|
if (response.data) {
|
||||||
|
ElMessage.success('配方生成成功!');
|
||||||
|
emit('success', response.data.name, response.data.description); // 传递配方名称和简介
|
||||||
|
dialogVisible.value = false; // 关闭弹窗
|
||||||
|
} else {
|
||||||
|
ElMessage.error('配方生成失败:未知错误');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('一键生成配方失败:', error);
|
||||||
|
ElMessage.error('一键生成配方失败: ' + (error.response?.data?.message || error.message || '未知错误'));
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ElMessage.warning('请检查表单填写');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理取消操作
|
||||||
|
*/
|
||||||
|
const handleCancel = () => {
|
||||||
|
dialogVisible.value = false;
|
||||||
|
emit('cancel');
|
||||||
|
// 重置表单字段
|
||||||
|
if (generateRecipeForm.value) {
|
||||||
|
generateRecipeForm.value.resetFields();
|
||||||
|
}
|
||||||
|
form.selectedPigType = ''; // 手动清空,因为 resetFields 不会清空未绑定 prop 的字段
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听 visible 变化,当弹窗打开时加载数据
|
||||||
|
watch(() => props.visible, (newVal) => {
|
||||||
|
if (newVal) {
|
||||||
|
fetchPigTypes();
|
||||||
|
// 每次打开时重置表单
|
||||||
|
if (generateRecipeForm.value) {
|
||||||
|
generateRecipeForm.value.resetFields();
|
||||||
|
}
|
||||||
|
form.selectedPigType = '';
|
||||||
|
form.selectedGenerationMethod = 'all_raw_materials';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 首次加载时也获取一次,以防万一
|
||||||
|
// fetchPigTypes(); // 移到 watch 中,确保每次打开弹窗都刷新数据
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
generateRecipeForm,
|
||||||
|
dialogVisible,
|
||||||
|
form,
|
||||||
|
pigTypesOptions,
|
||||||
|
loading,
|
||||||
|
handleGenerate,
|
||||||
|
handleCancel,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
<el-table-column prop="name" label="原料名称" />
|
<el-table-column prop="name" label="原料名称" />
|
||||||
<el-table-column prop="percentage" label="占比">
|
<el-table-column prop="percentage" label="占比">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
{{ (scope.row.percentage * 100).toFixed(2) }}%
|
{{ (scope.row.percentage * 100).toFixed(4) }}%
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
<el-table-column prop="name" label="原料名称" />
|
<el-table-column prop="name" label="原料名称" />
|
||||||
<el-table-column label="占比">
|
<el-table-column label="占比">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-input-number v-model="scope.row.percentage" :min="0" :max="1" :step="0.01" :precision="2" @change="updateNutrientSummary"></el-input-number>
|
<el-input-number v-model="scope.row.percentage" :min="0" :max="1" :step="0.0001" :precision="4" @change="updateNutrientSummary"></el-input-number>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作">
|
<el-table-column label="操作">
|
||||||
|
|||||||
@@ -9,7 +9,10 @@
|
|||||||
<el-icon :size="20"><Refresh /></el-icon>
|
<el-icon :size="20"><Refresh /></el-icon>
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<el-button type="primary" @click="addRecipe">新增配方</el-button>
|
<div>
|
||||||
|
<el-button type="primary" @click="addRecipe">新增配方</el-button>
|
||||||
|
<el-button type="success" @click="openGenerateRecipeDialog">一键生成配方</el-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -55,6 +58,13 @@
|
|||||||
v-model:visible="detailDialogVisible"
|
v-model:visible="detailDialogVisible"
|
||||||
:recipe="selectedRecipe"
|
:recipe="selectedRecipe"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- 一键生成配方对话框 -->
|
||||||
|
<GenerateRecipeDialog
|
||||||
|
v-model:visible="generateRecipeDialogVisible"
|
||||||
|
@success="onGenerateRecipeSuccess"
|
||||||
|
@cancel="generateRecipeDialogVisible = false"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -65,6 +75,7 @@ import RecipeTable from '../../components/feed/RecipeTable.vue';
|
|||||||
import RecipeForm from '../../components/feed/RecipeForm.vue';
|
import RecipeForm from '../../components/feed/RecipeForm.vue';
|
||||||
import RecipeDetailDialog from '../../components/feed/RecipeDetailDialog.vue';
|
import RecipeDetailDialog from '../../components/feed/RecipeDetailDialog.vue';
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
|
import GenerateRecipeDialog from '../../components/feed/GenerateRecipeDialog.vue'; // 引入新的组件
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'RecipeList',
|
name: 'RecipeList',
|
||||||
@@ -72,7 +83,8 @@ export default {
|
|||||||
RecipeTable,
|
RecipeTable,
|
||||||
RecipeForm,
|
RecipeForm,
|
||||||
RecipeDetailDialog,
|
RecipeDetailDialog,
|
||||||
Refresh
|
Refresh,
|
||||||
|
GenerateRecipeDialog, // 注册新的组件
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -84,6 +96,7 @@ export default {
|
|||||||
isEdit: false,
|
isEdit: false,
|
||||||
detailDialogVisible: false,
|
detailDialogVisible: false,
|
||||||
selectedRecipe: null,
|
selectedRecipe: null,
|
||||||
|
generateRecipeDialogVisible: false, // 控制一键生成配方弹窗的显示
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
@@ -141,6 +154,17 @@ export default {
|
|||||||
handleShowDetails(recipe) {
|
handleShowDetails(recipe) {
|
||||||
this.selectedRecipe = recipe;
|
this.selectedRecipe = recipe;
|
||||||
this.detailDialogVisible = true;
|
this.detailDialogVisible = true;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 打开一键生成配方对话框
|
||||||
|
*/
|
||||||
|
openGenerateRecipeDialog() {
|
||||||
|
this.generateRecipeDialogVisible = true;
|
||||||
|
},
|
||||||
|
onGenerateRecipeSuccess(recipeName, recipeDescription) {
|
||||||
|
ElMessage.success(`配方 "${recipeName}" 生成成功: ${recipeDescription}`);
|
||||||
|
this.generateRecipeDialogVisible = false;
|
||||||
|
this.loadRecipes(); // 刷新配方列表
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user