Files
pig-farm-controller-fe/src/components/feed/PigBreedTable.vue
2025-11-25 22:37:00 +08:00

465 lines
17 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div>
<!-- 搜索区域 -->
<div style="margin-bottom: 20px; display: flex; align-items: center; gap: 10px;">
<el-input
v-model="searchKeyword"
placeholder="请输入品种名称"
clearable
style="width: 300px;"
@keyup.enter="handleSearch"
></el-input>
<el-button type="primary" @click="handleSearch">搜索</el-button>
</div>
<!-- 品种列表 -->
<el-table
ref="mainTable"
:data="tableData"
style="width: 100%"
v-loading="loading"
row-key="id"
:expand-row-keys="expandRowKeys"
@expand-change="handleExpandChange"
@row-click="handleRowClick"
>
<el-table-column type="expand">
<template #default="props">
<div style="padding: 10px 20px;">
<!-- 品种的完整信息 -->
<el-descriptions
class="margin-top"
title="品种详细信息"
:column="2"
border
>
<el-descriptions-item label="描述" :span="2">
{{ props.row.description || '无' }}
</el-descriptions-item>
<el-descriptions-item label="外貌特征">
{{ props.row.appearance_features || '无' }}
</el-descriptions-item>
<el-descriptions-item label="父母信息">
{{ props.row.parent_info || '无' }}
</el-descriptions-item>
<el-descriptions-item label="品种优点">
{{ props.row.breed_advantages || '无' }}
</el-descriptions-item>
<el-descriptions-item label="品种缺点">
{{ props.row.breed_disadvantages || '无' }}
</el-descriptions-item>
</el-descriptions>
<el-divider></el-divider>
<!-- 下半段该品种下的年龄阶段简介 -->
<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>
<el-table-column prop="name" label="品种名称" width="150"></el-table-column>
<el-table-column prop="description" label="描述"></el-table-column>
<el-table-column label="操作" width="180">
<template #default="scope">
<el-button size="small" @click="handleEdit(scope.row)">编辑</el-button>
<el-button size="small" type="danger" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
style="margin-top: 20px;"
:current-page="pagination.page"
:page-size="pagination.page_size"
:total="pagination.total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
></el-pagination>
<!-- 营养需求详情弹窗 -->
<el-dialog
v-model="showNutrientDialog"
:title="`品种 ${currentBreedName} - 年龄阶段 ${currentAgeStageName} 营养需求`"
width="700px"
:close-on-click-modal="false"
>
<PigNutrientRequirementsDisplay
v-if="showNutrientDialog"
:nutrientRequirements="currentNutrientRequirements"
:pigTypeId="currentPigTypeId"
@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>
<script>
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 的引用
const tableData = ref([]);
const loading = ref(false);
const searchKeyword = ref('');
const expandRowKeys = ref([]); // 用于控制展开行
// 营养需求弹窗相关
const showNutrientDialog = ref(false);
const currentNutrientRequirements = ref([]);
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,
page_size: 10,
total: 0,
});
const fetchPigBreeds = async () => {
loading.value = true;
try {
const params = {
page: pagination.value.page,
page_size: pagination.value.page_size,
order_by: 'id ASC', // 默认按 ID 升序排序
};
if (searchKeyword.value) {
params.name = searchKeyword.value; // 按名称模糊查询
}
const response = await FeedApi.getPigBreeds(params);
if (response.data) {
tableData.value = response.data.list;
pagination.value.total = response.data.pagination.total;
}
} catch (error) {
console.error('获取品种列表失败:', error);
ElMessage.error('获取品种列表失败');
} finally {
loading.value = false;
}
};
const handleSearch = () => {
pagination.value.page = 1;
fetchPigBreeds();
};
const handleSizeChange = (size) => {
pagination.value.page_size = size;
fetchPigBreeds();
};
const handleCurrentChange = (page) => {
pagination.value.page = page;
fetchPigBreeds();
};
// 处理行展开/折叠事件
const handleExpandChange = async (row, expandedRows) => {
const isExpanded = expandedRows.some(r => r.id === row.id);
// 优化:仅在展开时且数据未加载时才请求
// 注意PigBreedResponse 默认不包含 pig_types 字段,需要单独请求
if (isExpanded && (!row.pig_types || row.pig_types.length === 0)) {
try {
// 调用 getPigTypes 接口,按 breed_id 筛选
const response = await FeedApi.getPigTypes({ breed_id: row.id, page: 1, page_size: 999 }); // 获取所有相关猪类型
if (response.data && response.data.list) {
// 对获取到的猪类型列表按 age_stage_id 进行排序
const sortedPigTypes = response.data.list.sort((a, b) => a.age_stage_id - b.age_stage_id);
const index = tableData.value.findIndex(item => item.id === row.id);
if (index !== -1) {
tableData.value[index].pig_types = sortedPigTypes;
}
}
} catch (error) {
console.error('获取该品种下的猪类型失败:', error);
ElMessage.error('获取猪类型失败');
}
}
};
// 处理行点击事件
const handleRowClick = (row, column) => {
// 如果点击的是操作列,则不执行任何操作
if (column.label === '操作') {
return;
}
// 否则,切换行的展开状态
mainTable.value.toggleRowExpansion(row);
};
// 将克转换为公斤并格式化,只返回数值
const weightFormatter = (row, column, cellValue) => {
if (typeof cellValue === 'number') {
return (cellValue / 1000).toFixed(2);
}
return cellValue;
};
// 处理查看营养需求详情
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 handleEdit = (row) => {
emit('edit', row); // 触发 edit 事件,并传递当前行数据
};
const handleDelete = (row) => {
ElMessageBox.confirm(
`确定要删除品种 "${row.name}" 吗?`,
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
).then(async () => {
try {
await FeedApi.deletePigBreed(row.id);
ElMessage.success('删除成功');
await fetchPigBreeds();
} catch (error) {
ElMessage.error('删除失败: ' + (error.message || '未知错误'));
}
}).catch(() => {
// 用户取消操作
});
};
// 处理营养需求编辑后的刷新事件
const handleNutrientRequirementsRefresh = async () => {
// 重新获取当前展开行的 pig_types 数据
const expandedRow = tableData.value.find(item => expandRowKeys.value.includes(item.id));
if (expandedRow) {
// 重新调用 handleExpandChange 来刷新该行的 pig_types 数据
// 确保传入的 expandedRows 包含当前行,以便触发数据加载逻辑
await handleExpandChange(expandedRow, [expandedRow]);
}
// 关闭弹窗
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();
});
return {
mainTable, // 暴露出 ref
tableData,
loading,
searchKeyword,
expandRowKeys,
pagination,
showNutrientDialog,
currentNutrientRequirements,
currentBreedName,
currentAgeStageName,
currentPigTypeId,
showPigTypeDialog,
currentPigType,
currentBreedIdForPigType,
isEditingPigType,
handleSearch,
handleSizeChange,
handleCurrentChange,
handleExpandChange,
handleRowClick, // 暴露出点击处理方法
weightFormatter, // 暴露给模板
handleViewNutrientRequirements, // 暴露给模板
handleEdit,
handleDelete,
fetchPigBreeds, // 将方法暴露出去
handleAddPigType,
handleEditPigType,
handleDeletePigType,
getExistingAgeStageIds, // 暴露给模板
handleNutrientRequirementsRefresh, // 暴露给模板
handlePigTypeSave,
handlePigTypeCancel,
};
},
};
</script>
<style scoped>
</style>