Compare commits
	
		
			2 Commits
		
	
	
		
			edfc641f93
			...
			accbb1a9f6
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| accbb1a9f6 | |||
| d38feb938d | 
| @@ -4765,6 +4765,9 @@ | |||||||
|         "capacity": { |         "capacity": { | ||||||
|           "type": "integer" |           "type": "integer" | ||||||
|         }, |         }, | ||||||
|  |         "current_pig_count": { | ||||||
|  |           "type": "integer" | ||||||
|  |         }, | ||||||
|         "house_id": { |         "house_id": { | ||||||
|           "type": "integer" |           "type": "integer" | ||||||
|         }, |         }, | ||||||
| @@ -4898,6 +4901,14 @@ | |||||||
|           "description": "创建时间", |           "description": "创建时间", | ||||||
|           "type": "string" |           "type": "string" | ||||||
|         }, |         }, | ||||||
|  |         "currentTotalPigsInPens": { | ||||||
|  |           "description": "当前存栏总数", | ||||||
|  |           "type": "integer" | ||||||
|  |         }, | ||||||
|  |         "currentTotalQuantity": { | ||||||
|  |           "description": "当前总数", | ||||||
|  |           "type": "integer" | ||||||
|  |         }, | ||||||
|         "end_date": { |         "end_date": { | ||||||
|           "description": "批次结束日期", |           "description": "批次结束日期", | ||||||
|           "type": "string" |           "type": "string" | ||||||
|   | |||||||
							
								
								
									
										216
									
								
								src/components/PigBatchForm.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								src/components/PigBatchForm.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,216 @@ | |||||||
|  | <template> | ||||||
|  |   <el-dialog | ||||||
|  |       :model-value="visible" | ||||||
|  |       :title="title" | ||||||
|  |       @close="handleClose" | ||||||
|  |       :close-on-click-modal="false" | ||||||
|  |       width="600px" | ||||||
|  |   > | ||||||
|  |     <el-form | ||||||
|  |         ref="formRef" | ||||||
|  |         :model="formData" | ||||||
|  |         :rules="rules" | ||||||
|  |         label-width="120px" | ||||||
|  |         @submit.prevent | ||||||
|  |     > | ||||||
|  |       <el-form-item label="批次编号" prop="batch_number"> | ||||||
|  |         <el-input v-model="formData.batch_number" placeholder="请输入批次编号"/> | ||||||
|  |       </el-form-item> | ||||||
|  |       <el-form-item label="初始数量" prop="initial_count"> | ||||||
|  |         <el-input-number v-model="formData.initial_count" :min="1" controls-position="right" style="width: 100%"/> | ||||||
|  |       </el-form-item> | ||||||
|  |       <el-form-item label="来源类型" prop="origin_type"> | ||||||
|  |         <el-select v-model="formData.origin_type" placeholder="请选择来源类型" style="width: 100%"> | ||||||
|  |           <el-option v-for="item in originTypeOptions" :key="item" :label="item" :value="item"/> | ||||||
|  |         </el-select> | ||||||
|  |       </el-form-item> | ||||||
|  |       <el-form-item label="开始日期" prop="start_date"> | ||||||
|  |         <el-date-picker | ||||||
|  |             v-model="formData.start_date" | ||||||
|  |             type="datetime" | ||||||
|  |             placeholder="选择开始日期和时间" | ||||||
|  |             value-format="YYYY-MM-DD HH:mm:ss" | ||||||
|  |             style="width: 100%" | ||||||
|  |         /> | ||||||
|  |       </el-form-item> | ||||||
|  |       <el-form-item label="批次状态" prop="status"> | ||||||
|  |         <el-select v-model="formData.status" placeholder="请选择批次状态" style="width: 100%"> | ||||||
|  |           <el-option v-for="item in batchStatusOptions" :key="item" :label="item" :value="item"/> | ||||||
|  |         </el-select> | ||||||
|  |       </el-form-item> | ||||||
|  |     </el-form> | ||||||
|  |  | ||||||
|  |     <template #footer> | ||||||
|  |       <span class="dialog-footer"> | ||||||
|  |         <el-button @click="handleClose">取消</el-button> | ||||||
|  |         <el-button type="primary" @click="handleSubmit" :loading="loading">确定</el-button> | ||||||
|  |       </span> | ||||||
|  |     </template> | ||||||
|  |   </el-dialog> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script> | ||||||
|  | import {ref, reactive, watch, computed, nextTick} from 'vue'; | ||||||
|  | import {ElMessage} from 'element-plus'; | ||||||
|  | import {createPigBatch, updatePigBatch} from '@/api/pigBatch.js'; | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |   name: 'PigBatchForm', | ||||||
|  |   props: { | ||||||
|  |     visible: { | ||||||
|  |       type: Boolean, | ||||||
|  |       default: false | ||||||
|  |     }, | ||||||
|  |     batchData: { | ||||||
|  |       type: Object, | ||||||
|  |       default: () => ({}) | ||||||
|  |     }, | ||||||
|  |     isEdit: { | ||||||
|  |       type: Boolean, | ||||||
|  |       default: false | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   emits: ['update:visible', 'success', 'cancel'], | ||||||
|  |   setup(props, {emit}) { | ||||||
|  |     const formRef = ref(null); | ||||||
|  |     const loading = ref(false); | ||||||
|  |  | ||||||
|  |     const originTypeOptions = ["自繁", "外购"]; | ||||||
|  |     const batchStatusOptions = ["保育", "生长", "育肥", "待售", "已出售", "已归档"]; | ||||||
|  |  | ||||||
|  |     const initialFormData = () => ({ | ||||||
|  |       id: null, | ||||||
|  |       batch_number: '', | ||||||
|  |       initial_count: 1, | ||||||
|  |       origin_type: '自繁', | ||||||
|  |       start_date: '', | ||||||
|  |       status: '保育', | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     const formData = reactive(initialFormData()); | ||||||
|  |  | ||||||
|  |     const rules = { | ||||||
|  |       batch_number: [ | ||||||
|  |         {required: true, message: '请输入批次编号', trigger: 'blur'} | ||||||
|  |       ], | ||||||
|  |       initial_count: [ | ||||||
|  |         {required: true, message: '请输入初始数量', trigger: 'blur'}, | ||||||
|  |         {type: 'integer', min: 1, message: '初始数量必须是大于0的整数', trigger: 'blur'} | ||||||
|  |       ], | ||||||
|  |       origin_type: [ | ||||||
|  |         {required: true, message: '请选择来源类型', trigger: 'change'} | ||||||
|  |       ], | ||||||
|  |       start_date: [ | ||||||
|  |         {required: true, message: '请选择开始日期', trigger: 'change'} | ||||||
|  |       ], | ||||||
|  |       status: [ | ||||||
|  |         {required: true, message: '请选择批次状态', trigger: 'change'} | ||||||
|  |       ] | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     const title = computed(() => (props.isEdit ? '编辑猪群' : '添加猪群')); | ||||||
|  |  | ||||||
|  |     // ✅ 修复:东八区 RFC3339 转换函数 | ||||||
|  |     const toRFC3339 = (dateStr) => { | ||||||
|  |       if (!dateStr) return null; | ||||||
|  |       // "2025-10-23 14:30:00" → Date 对象(东八区) | ||||||
|  |       const date = new Date(dateStr.replace(/ /, 'T') + '+08:00'); | ||||||
|  |       if (isNaN(date.getTime())) return null; | ||||||
|  |       return date.toISOString().slice(0, -1) + '+08:00'; // "2025-10-23T14:30:00+08:00" | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     // ✅ 修复:RFC3339 转显示格式 | ||||||
|  |     const fromRFC3339 = (rfc3339Str) => { | ||||||
|  |       if (!rfc3339Str) return ''; | ||||||
|  |       const date = new Date(rfc3339Str); | ||||||
|  |       if (isNaN(date.getTime())) return ''; | ||||||
|  |       const year = date.getFullYear(); | ||||||
|  |       const month = String(date.getMonth() + 1).padStart(2, '0'); | ||||||
|  |       const day = String(date.getDate()).padStart(2, '0'); | ||||||
|  |       const hours = String(date.getHours()).padStart(2, '0'); | ||||||
|  |       const minutes = String(date.getMinutes()).padStart(2, '0'); | ||||||
|  |       const seconds = String(date.getSeconds()).padStart(2, '0'); | ||||||
|  |       return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     const handleClose = () => { | ||||||
|  |       emit('update:visible', false); | ||||||
|  |       emit('cancel'); | ||||||
|  |       nextTick(() => { | ||||||
|  |         formRef.value?.resetFields(); | ||||||
|  |         Object.assign(formData, initialFormData()); | ||||||
|  |       }); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     const handleSubmit = async () => { | ||||||
|  |       if (!formRef.value) return; | ||||||
|  |       await formRef.value.validate(async (valid) => { | ||||||
|  |         if (valid) { | ||||||
|  |           loading.value = true; | ||||||
|  |           try { | ||||||
|  |             let response; | ||||||
|  |             const submitData = {...formData}; | ||||||
|  |             delete submitData.id; | ||||||
|  |  | ||||||
|  |             // ✅ 修复:正确转东八区 RFC3339 | ||||||
|  |             submitData.start_date = toRFC3339(formData.start_date); | ||||||
|  |             console.log('提交的日期:', submitData.start_date); // 调试用 | ||||||
|  |  | ||||||
|  |             if (props.isEdit) { | ||||||
|  |               response = await updatePigBatch(props.batchData.id, submitData); | ||||||
|  |             } else { | ||||||
|  |               response = await createPigBatch(submitData); | ||||||
|  |             } | ||||||
|  |             ElMessage.success((props.isEdit ? '更新' : '创建') + '猪群成功'); | ||||||
|  |             emit('success', response.data); | ||||||
|  |             handleClose(); | ||||||
|  |           } catch (error) { | ||||||
|  |             console.error('提交错误:', error); | ||||||
|  |             ElMessage.error((props.isEdit ? '更新' : '创建') + '猪群失败: ' + (error.message || '未知错误')); | ||||||
|  |           } finally { | ||||||
|  |             loading.value = false; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     watch(() => props.visible, (newVal) => { | ||||||
|  |       if (newVal) { | ||||||
|  |         Object.assign(formData, initialFormData()); | ||||||
|  |         if (props.isEdit && props.batchData) { | ||||||
|  |           // ✅ 修复:正确转换编辑时的日期 | ||||||
|  |           formData.id = props.batchData.id; | ||||||
|  |           formData.batch_number = props.batchData.batch_number || ''; | ||||||
|  |           formData.initial_count = props.batchData.initial_count || 1; | ||||||
|  |           formData.origin_type = props.batchData.origin_type || '自繁'; | ||||||
|  |           formData.status = props.batchData.status || '保育'; | ||||||
|  |           formData.start_date = fromRFC3339(props.batchData.start_date); | ||||||
|  |         } | ||||||
|  |         nextTick(() => { | ||||||
|  |           formRef.value?.clearValidate(); | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     return { | ||||||
|  |       formRef, | ||||||
|  |       loading, | ||||||
|  |       formData, | ||||||
|  |       rules, | ||||||
|  |       title, | ||||||
|  |       originTypeOptions, | ||||||
|  |       batchStatusOptions, | ||||||
|  |       handleClose, | ||||||
|  |       handleSubmit | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style scoped> | ||||||
|  | .dialog-footer { | ||||||
|  |   display: flex; | ||||||
|  |   justify-content: flex-end; | ||||||
|  |   gap: 10px; | ||||||
|  | } | ||||||
|  | </style> | ||||||
							
								
								
									
										137
									
								
								src/components/PigBatchList.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								src/components/PigBatchList.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,137 @@ | |||||||
|  | <template> | ||||||
|  |   <div class="pig-batch-list"> | ||||||
|  |     <div v-for="batch in pigBatches" :key="batch.id" class="pig-batch-item"> | ||||||
|  |       <div class="batch-header" @click="toggleExpand(batch)"> | ||||||
|  |         <div class="batch-info"> | ||||||
|  |           <span>批次编号: {{ batch.batch_number }}</span> | ||||||
|  |           <span>状态: {{ batch.status }}</span> | ||||||
|  |           <span>初始数量: {{ batch.initial_count }}</span> | ||||||
|  |         </div> | ||||||
|  |         <div class="batch-actions"> | ||||||
|  |           <el-button size="small" type="primary" @click.stop="emitAddPen(batch)">增加猪栏</el-button> | ||||||
|  |           <el-button size="small" @click.stop="emitEditBatch(batch)">编辑</el-button> | ||||||
|  |           <el-button size="small" type="danger" @click.stop="emitDeleteBatch(batch)">删除</el-button> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |       <div v-if="batch.isExpanded" class="batch-content"> | ||||||
|  |         <div v-if="batch.pens && batch.pens.length > 0" class="pig-pen-list"> | ||||||
|  |           <PigPenInfoCard | ||||||
|  |             v-for="pen in batch.pens" | ||||||
|  |             :key="pen.id" | ||||||
|  |             :pen="pen" | ||||||
|  |             @edit="emitEditPen" | ||||||
|  |             @delete="emitDeletePen" | ||||||
|  |           /> | ||||||
|  |         </div> | ||||||
|  |         <div v-else class="no-pens-message"> | ||||||
|  |           <p>该猪群下没有猪栏信息。</p> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script> | ||||||
|  | import PigPenInfoCard from './PigPenInfoCard.vue'; | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |   name: 'PigBatchList', | ||||||
|  |   components: { | ||||||
|  |     PigPenInfoCard | ||||||
|  |   }, | ||||||
|  |   props: { | ||||||
|  |     pigBatches: { | ||||||
|  |       type: Array, | ||||||
|  |       required: true | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   emits: ['edit-batch', 'delete-batch', 'add-pen', 'edit-pen', 'delete-pen'], | ||||||
|  |   methods: { | ||||||
|  |     toggleExpand(batch) { | ||||||
|  |       batch.isExpanded = !batch.isExpanded; | ||||||
|  |     }, | ||||||
|  |     // 猪群操作 | ||||||
|  |     emitAddPen(batch) { | ||||||
|  |       this.$emit('add-pen', batch); | ||||||
|  |     }, | ||||||
|  |     emitEditBatch(batch) { | ||||||
|  |       this.$emit('edit-batch', batch); | ||||||
|  |     }, | ||||||
|  |     emitDeleteBatch(batch) { | ||||||
|  |       this.$emit('delete-batch', batch); | ||||||
|  |     }, | ||||||
|  |     // 猪栏操作 | ||||||
|  |     emitEditPen(pen) { | ||||||
|  |       this.$emit('edit-pen', pen); | ||||||
|  |     }, | ||||||
|  |     emitDeletePen(pen) { | ||||||
|  |       this.$emit('delete-pen', pen); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style scoped> | ||||||
|  | .pig-batch-list { | ||||||
|  |   width: 100%; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .pig-batch-item { | ||||||
|  |   border: 1px solid #eee; | ||||||
|  |   border-radius: 4px; | ||||||
|  |   margin-bottom: 16px; | ||||||
|  |   overflow: hidden; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .batch-header { | ||||||
|  |   display: flex; | ||||||
|  |   justify-content: space-between; | ||||||
|  |   align-items: center; | ||||||
|  |   padding: 16px; | ||||||
|  |   cursor: pointer; | ||||||
|  |   background-color: #f9f9f9; | ||||||
|  |   transition: background-color 0.3s; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .batch-header:hover { | ||||||
|  |   background-color: #f0f0f0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .batch-info { | ||||||
|  |   display: flex; | ||||||
|  |   flex-direction: column; | ||||||
|  |   gap: 8px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .batch-info span:first-child { | ||||||
|  |   font-weight: bold; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .batch-info span { | ||||||
|  |   margin-right: 20px; | ||||||
|  |   font-size: 14px; | ||||||
|  |   color: #606266; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .batch-actions { | ||||||
|  |   display: flex; | ||||||
|  |   gap: 10px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .batch-content { | ||||||
|  |   padding: 16px; | ||||||
|  |   border-top: 1px solid #eee; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .pig-pen-list { | ||||||
|  |   display: flex; | ||||||
|  |   flex-wrap: wrap; | ||||||
|  |   gap: 16px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .no-pens-message { | ||||||
|  |   color: #909399; | ||||||
|  |   text-align: center; | ||||||
|  |   padding: 20px 0; | ||||||
|  | } | ||||||
|  | </style> | ||||||
| @@ -4,7 +4,9 @@ | |||||||
|       <div class="title">猪栏: {{ pen.pen_number }}</div> |       <div class="title">猪栏: {{ pen.pen_number }}</div> | ||||||
|       <div class="info-item">状态: <el-tag size="small" :type="statusType">{{ pen.status || '未知' }}</el-tag></div> |       <div class="info-item">状态: <el-tag size="small" :type="statusType">{{ pen.status || '未知' }}</el-tag></div> | ||||||
|       <div class="info-item">容量: {{ pen.capacity }}</div> |       <div class="info-item">容量: {{ pen.capacity }}</div> | ||||||
|  |       <div class="info-item">存栏: {{ pen.current_pig_count || 0 }}</div> | ||||||
|       <div class="info-item">批次: {{ pen.batch_number || '未分配' }}</div> |       <div class="info-item">批次: {{ pen.batch_number || '未分配' }}</div> | ||||||
|  |       <div class="info-item">猪舍: {{ pen.house_name || '未知' }}</div> | ||||||
|     </div> |     </div> | ||||||
|     <div class="actions-section"> |     <div class="actions-section"> | ||||||
|       <el-button size="small" @click="emitEdit">编辑</el-button> |       <el-button size="small" @click="emitEdit">编辑</el-button> | ||||||
| @@ -67,7 +69,7 @@ export default { | |||||||
|   flex-direction: column; |   flex-direction: column; | ||||||
|   justify-content: space-between; |   justify-content: space-between; | ||||||
|   width: 200px; /* 适当加宽以容纳更多信息 */ |   width: 200px; /* 适当加宽以容纳更多信息 */ | ||||||
|   height: 240px; |   height: 260px; /* 调整高度以适应更多信息 */ | ||||||
|   background-color: #fff; |   background-color: #fff; | ||||||
|   box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); |   box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); | ||||||
|   transition: box-shadow 0.3s; |   transition: box-shadow 0.3s; | ||||||
| @@ -80,7 +82,8 @@ export default { | |||||||
| .info-section { | .info-section { | ||||||
|   display: flex; |   display: flex; | ||||||
|   flex-direction: column; |   flex-direction: column; | ||||||
|   gap: 12px; /* 增加信息项间距 */ |   gap: 10px; /* 调整信息项间距 */ | ||||||
|  |   margin-bottom: 10px; | ||||||
| } | } | ||||||
|  |  | ||||||
| .info-section .title { | .info-section .title { | ||||||
|   | |||||||
| @@ -48,6 +48,10 @@ | |||||||
|             <el-icon><Tickets /></el-icon> |             <el-icon><Tickets /></el-icon> | ||||||
|             <template #title>栏舍管理</template> |             <template #title>栏舍管理</template> | ||||||
|           </el-menu-item> |           </el-menu-item> | ||||||
|  |           <el-menu-item index="/pms/batch-management"> | ||||||
|  |             <el-icon><Management /></el-icon> | ||||||
|  |             <template #title>猪群管理</template> | ||||||
|  |           </el-menu-item> | ||||||
|         </el-sub-menu> |         </el-sub-menu> | ||||||
|  |  | ||||||
|         <el-menu-item index="/plans"> |         <el-menu-item index="/plans"> | ||||||
| @@ -179,14 +183,14 @@ import { ref, computed, onMounted, onUnmounted } from 'vue'; | |||||||
| import { useRoute, useRouter } from 'vue-router'; | import { useRoute, useRouter } from 'vue-router'; | ||||||
| import {  | import {  | ||||||
|   House, Monitor, Calendar, ArrowDown, Menu, Fold, Expand, Setting, Tickets, DataAnalysis, Document, Food, |   House, Monitor, Calendar, ArrowDown, Menu, Fold, Expand, Setting, Tickets, DataAnalysis, Document, Food, | ||||||
|   FirstAidKit, Clock, Files, ShoppingCart, SoldOut, Warning, Switch, List, Shop, Coin, DataLine, Finished, User, ScaleToOriginal, OfficeBuilding |   FirstAidKit, Clock, Files, ShoppingCart, SoldOut, Warning, Switch, List, Shop, Coin, DataLine, Finished, User, ScaleToOriginal, OfficeBuilding, Management | ||||||
| } from '@element-plus/icons-vue'; | } from '@element-plus/icons-vue'; | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|   name: 'MainLayout', |   name: 'MainLayout', | ||||||
|   components: { |   components: { | ||||||
|     House, Monitor, Calendar, ArrowDown, Menu, Fold, Expand, Setting, Tickets, DataAnalysis, Document, Food, |     House, Monitor, Calendar, ArrowDown, Menu, Fold, Expand, Setting, Tickets, DataAnalysis, Document, Food, | ||||||
|     FirstAidKit, Clock, Files, ShoppingCart, SoldOut, Warning, Switch, List, Shop, Coin, DataLine, Finished, User, ScaleToOriginal, OfficeBuilding |     FirstAidKit, Clock, Files, ShoppingCart, SoldOut, Warning, Switch, List, Shop, Coin, DataLine, Finished, User, ScaleToOriginal, OfficeBuilding, Management | ||||||
|   }, |   }, | ||||||
|   setup() { |   setup() { | ||||||
|     const route = useRoute(); |     const route = useRoute(); | ||||||
| @@ -224,6 +228,7 @@ export default { | |||||||
|         '/devices': '设备管理', |         '/devices': '设备管理', | ||||||
|         '/device-templates': '设备模板管理', |         '/device-templates': '设备模板管理', | ||||||
|         '/pms/farm-management': '栏舍管理', |         '/pms/farm-management': '栏舍管理', | ||||||
|  |         '/pms/batch-management': '猪群管理', | ||||||
|         '/plans': '计划管理', |         '/plans': '计划管理', | ||||||
|         '/monitor/device-command-logs': '设备命令日志', |         '/monitor/device-command-logs': '设备命令日志', | ||||||
|         '/monitor/feed-usage-records': '饲料使用记录', |         '/monitor/feed-usage-records': '饲料使用记录', | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ import PlanList from './views/plan/PlanList.vue'; | |||||||
| import LoginForm from './views/home/LoginForm.vue'; | import LoginForm from './views/home/LoginForm.vue'; | ||||||
| import DeviceTemplateList from './views/device/DeviceTemplateList.vue'; | import DeviceTemplateList from './views/device/DeviceTemplateList.vue'; | ||||||
| import PigFarmManagementView from './views/pms/PigFarmManagementView.vue'; // 导入栏舍管理视图 | import PigFarmManagementView from './views/pms/PigFarmManagementView.vue'; // 导入栏舍管理视图 | ||||||
|  | import PigBatchManagementView from './views/pms/PigBatchManagementView.vue'; // 导入猪群管理视图 | ||||||
|  |  | ||||||
| // --- 统一导入所有监控视图 --- | // --- 统一导入所有监控视图 --- | ||||||
| import DeviceCommandLogView from './views/monitor/DeviceCommandLogView.vue'; | import DeviceCommandLogView from './views/monitor/DeviceCommandLogView.vue'; | ||||||
| @@ -42,7 +43,8 @@ const routes = [ | |||||||
|     {path: '/device-templates', component: DeviceTemplateList, meta: {requiresAuth: true}}, |     {path: '/device-templates', component: DeviceTemplateList, meta: {requiresAuth: true}}, | ||||||
|     {path: '/plans', component: PlanList, meta: {requiresAuth: true}}, |     {path: '/plans', component: PlanList, meta: {requiresAuth: true}}, | ||||||
|     {path: '/login', component: LoginForm}, |     {path: '/login', component: LoginForm}, | ||||||
|     {path: '/pms/farm-management', name: 'PigFarmManagement', component: PigFarmManagementView, meta: { requiresAuth: true }}, |     {path: '/pms/farm-management', name: 'PigFarmManagement', component: PigFarmManagementView, meta: { requiresAuth: true, title: '栏舍管理' }}, | ||||||
|  |     {path: '/pms/batch-management', name: 'PigBatchManagement', component: PigBatchManagementView, meta: { requiresAuth: true, title: '猪群管理' }}, | ||||||
|  |  | ||||||
|     // --- 统一注册所有监控路由 --- |     // --- 统一注册所有监控路由 --- | ||||||
|     {path: '/monitor/device-command-logs', component: DeviceCommandLogView, meta: {requiresAuth: true}}, |     {path: '/monitor/device-command-logs', component: DeviceCommandLogView, meta: {requiresAuth: true}}, | ||||||
|   | |||||||
							
								
								
									
										323
									
								
								src/views/pms/PigBatchManagementView.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										323
									
								
								src/views/pms/PigBatchManagementView.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,323 @@ | |||||||
|  | <template> | ||||||
|  |   <div class="pig-batch-management"> | ||||||
|  |     <el-card> | ||||||
|  |       <template #header> | ||||||
|  |         <div class="card-header"> | ||||||
|  |           <div class="title-container"> | ||||||
|  |             <h2 class="page-title">猪群管理</h2> | ||||||
|  |             <el-button type="text" @click="loadData" class="refresh-btn" title="刷新列表"> | ||||||
|  |               <el-icon :size="20"><Refresh /></el-icon> | ||||||
|  |             </el-button> | ||||||
|  |           </div> | ||||||
|  |           <el-button type="primary" @click="handleAddBatch">添加猪群</el-button> | ||||||
|  |         </div> | ||||||
|  |       </template> | ||||||
|  |  | ||||||
|  |       <!-- 加载状态 --> | ||||||
|  |       <div v-if="loading" class="loading"> | ||||||
|  |         <el-skeleton animated /> | ||||||
|  |       </div> | ||||||
|  |  | ||||||
|  |       <!-- 错误状态 --> | ||||||
|  |       <div v-else-if="error" class="error"> | ||||||
|  |         <el-alert | ||||||
|  |           title="获取猪群数据失败" | ||||||
|  |           :description="error" | ||||||
|  |           type="error" | ||||||
|  |           show-icon | ||||||
|  |           closable | ||||||
|  |           @close="error = null" | ||||||
|  |         /> | ||||||
|  |         <el-button type="primary" @click="loadData" class="retry-btn">重新加载</el-button> | ||||||
|  |       </div> | ||||||
|  |  | ||||||
|  |       <!-- 数据展示或空状态 --> | ||||||
|  |       <div v-else> | ||||||
|  |         <PigBatchList  | ||||||
|  |           v-if="pigBatchesData.length > 0"  | ||||||
|  |           :pigBatches="pigBatchesData"  | ||||||
|  |           @edit-batch="handleEditBatch"  | ||||||
|  |           @delete-batch="handleDeleteBatch"  | ||||||
|  |           @add-pen="handleAddPen" | ||||||
|  |           @edit-pen="handleEditPen" | ||||||
|  |           @delete-pen="handleDeletePen" | ||||||
|  |         /> | ||||||
|  |         <el-empty v-else description="暂无数据" /> | ||||||
|  |       </div> | ||||||
|  |  | ||||||
|  |     </el-card> | ||||||
|  |  | ||||||
|  |     <!-- 猪群表单对话框 --> | ||||||
|  |     <PigBatchForm | ||||||
|  |       v-model:visible="batchDialogVisible" | ||||||
|  |       :batch-data="currentBatch" | ||||||
|  |       :is-edit="isEditBatch" | ||||||
|  |       @success="handleBatchSuccess" | ||||||
|  |       @cancel="batchDialogVisible = false" | ||||||
|  |     /> | ||||||
|  |  | ||||||
|  |     <!-- 猪栏表单对话框 (用于在猪群管理中添加/编辑猪栏) --> | ||||||
|  |     <PenForm  | ||||||
|  |       v-model:visible="penDialogVisible" | ||||||
|  |       :pen-data="currentPen" | ||||||
|  |       :is-edit="isEditPen" | ||||||
|  |       @success="handlePenSuccess" | ||||||
|  |       @cancel="penDialogVisible = false" | ||||||
|  |     /> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script> | ||||||
|  | import { getPigBatches, deletePigBatch } from '@/api/pigBatch.js'; | ||||||
|  | import { getPens, deletePen } from '@/api/pen.js'; | ||||||
|  | import { getPigHouses } from '@/api/pigHouse.js'; | ||||||
|  | import PigBatchList from '@/components/PigBatchList.vue'; | ||||||
|  | import PigBatchForm from '@/components/PigBatchForm.vue'; | ||||||
|  | import PenForm from '@/components/PenForm.vue'; // 引入 PenForm | ||||||
|  | import { Refresh } from '@element-plus/icons-vue'; | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |   name: 'PigBatchManagementView', | ||||||
|  |   components: { | ||||||
|  |     PigBatchList, | ||||||
|  |     PigBatchForm, | ||||||
|  |     PenForm, | ||||||
|  |     Refresh | ||||||
|  |   }, | ||||||
|  |   data() { | ||||||
|  |     return { | ||||||
|  |       pigBatchesData: [], | ||||||
|  |       loading: false, | ||||||
|  |       error: null, | ||||||
|  |       // 猪群表单状态 | ||||||
|  |       batchDialogVisible: false, | ||||||
|  |       isEditBatch: false, | ||||||
|  |       currentBatch: {}, | ||||||
|  |       // 猪栏表单状态 | ||||||
|  |       penDialogVisible: false, | ||||||
|  |       isEditPen: false, | ||||||
|  |       currentPen: {}, | ||||||
|  |       // 辅助映射 | ||||||
|  |       houseMap: new Map(), // 用于猪栏显示猪舍名称 | ||||||
|  |     }; | ||||||
|  |   }, | ||||||
|  |   async mounted() { | ||||||
|  |     await this.loadData(); | ||||||
|  |   }, | ||||||
|  |   methods: { | ||||||
|  |     async loadData() { | ||||||
|  |       this.loading = true; | ||||||
|  |       this.error = null; | ||||||
|  |       try { | ||||||
|  |         const [batchesResponse, pensResponse, housesResponse] = await Promise.all([ | ||||||
|  |           getPigBatches(), | ||||||
|  |           getPens(), | ||||||
|  |           getPigHouses(), | ||||||
|  |         ]); | ||||||
|  |          | ||||||
|  |         const batches = Array.isArray(batchesResponse.data) ? batchesResponse.data : []; | ||||||
|  |         const pens = Array.isArray(pensResponse.data) ? pensResponse.data : []; | ||||||
|  |         const houses = Array.isArray(housesResponse.data) ? housesResponse.data : []; | ||||||
|  |  | ||||||
|  |         // 更新 houseMap | ||||||
|  |         this.houseMap = new Map(houses.map(h => [h.id, h.name])); | ||||||
|  |         this.pigBatchesData = this.assembleData(batches, pens); | ||||||
|  |  | ||||||
|  |       } catch (err) { | ||||||
|  |         this.error = err.message || '无法获取数据'; | ||||||
|  |         console.error('Failed to fetch pig batch data:', err); | ||||||
|  |       } finally { | ||||||
|  |         this.loading = false; | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     assembleData(batches, pens) { | ||||||
|  |       const batchMap = new Map(); | ||||||
|  |       batches.forEach(batch => { | ||||||
|  |         const existingBatch = this.pigBatchesData.find(b => b.id === batch.id); | ||||||
|  |         batchMap.set(batch.id, { ...batch, pens: [], isExpanded: existingBatch ? existingBatch.isExpanded : false }); | ||||||
|  |       }); | ||||||
|  |  | ||||||
|  |       for (const pen of pens) { | ||||||
|  |         if (pen.pig_batch_id && batchMap.has(pen.pig_batch_id)) { | ||||||
|  |           const penWithDetails = { | ||||||
|  |             ...pen, | ||||||
|  |             house_name: this.houseMap.get(pen.house_id) || '未知猪舍' | ||||||
|  |           }; | ||||||
|  |           batchMap.get(pen.pig_batch_id).pens.push(penWithDetails); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       return Array.from(batchMap.values()); | ||||||
|  |     }, | ||||||
|  |     // --- 猪群操作 --- | ||||||
|  |     handleAddBatch() { | ||||||
|  |       this.currentBatch = {}; | ||||||
|  |       this.isEditBatch = false; | ||||||
|  |       this.batchDialogVisible = true; | ||||||
|  |     }, | ||||||
|  |     handleEditBatch(batch) { | ||||||
|  |       this.currentBatch = { ...batch }; | ||||||
|  |       this.isEditBatch = true; | ||||||
|  |       this.batchDialogVisible = true; | ||||||
|  |     }, | ||||||
|  |     async handleDeleteBatch(batch) { | ||||||
|  |       try { | ||||||
|  |         await this.$confirm(`确认删除猪群 "${batch.batch_number}" 吗?`, '提示', { | ||||||
|  |           confirmButtonText: '确定', | ||||||
|  |           cancelButtonText: '取消', | ||||||
|  |           type: 'warning' | ||||||
|  |         }); | ||||||
|  |         await deletePigBatch(batch.id); | ||||||
|  |         this.$message.success('删除成功'); | ||||||
|  |         // 本地更新数据 | ||||||
|  |         this.pigBatchesData = this.pigBatchesData.filter(b => b.id !== batch.id); | ||||||
|  |       } catch (err) { | ||||||
|  |         if (err !== 'cancel') { | ||||||
|  |           this.$message.error('删除失败: ' + (err.message || '未知错误')); | ||||||
|  |           console.error('Failed to delete pig batch:', err); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     handleBatchSuccess(newBatchData) { | ||||||
|  |       this.batchDialogVisible = false; | ||||||
|  |       // 为新批次数据添加 pens 和 isExpanded 状态 | ||||||
|  |       const processedBatchData = { ...newBatchData, pens: [], isExpanded: false }; | ||||||
|  |  | ||||||
|  |       if (this.isEditBatch) { | ||||||
|  |         // 更新现有猪群 | ||||||
|  |         const index = this.pigBatchesData.findIndex(b => b.id === processedBatchData.id); | ||||||
|  |         if (index !== -1) { | ||||||
|  |           // 保留 pens 和 isExpanded 状态 | ||||||
|  |           const existingBatch = this.pigBatchesData[index]; | ||||||
|  |           this.pigBatchesData[index] = { ...processedBatchData, pens: existingBatch.pens, isExpanded: existingBatch.isExpanded }; | ||||||
|  |         } | ||||||
|  |         this.$message.success('猪群更新成功'); | ||||||
|  |       } else { | ||||||
|  |         // 添加新猪群 | ||||||
|  |         this.pigBatchesData.push(processedBatchData); | ||||||
|  |         this.$message.success('猪群添加成功'); | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     // --- 猪栏操作 (在猪群管理中) --- | ||||||
|  |     handleAddPen(batch) { | ||||||
|  |       this.currentPen = { pig_batch_id: batch.id }; // 预设猪栏所属批次 | ||||||
|  |       this.isEditPen = false; | ||||||
|  |       this.penDialogVisible = true; | ||||||
|  |     }, | ||||||
|  |     handleEditPen(pen) { | ||||||
|  |       this.currentPen = { ...pen }; | ||||||
|  |       this.isEditPen = true; | ||||||
|  |       this.penDialogVisible = true; | ||||||
|  |     }, | ||||||
|  |     async handleDeletePen(pen) { | ||||||
|  |       try { | ||||||
|  |         await this.$confirm(`确认删除猪栏 "${pen.pen_number}" 吗?`, '提示', { | ||||||
|  |           confirmButtonText: '确定', | ||||||
|  |           cancelButtonText: '取消', | ||||||
|  |           type: 'warning' | ||||||
|  |         }); | ||||||
|  |         await deletePen(pen.id); | ||||||
|  |         this.$message.success('删除成功'); | ||||||
|  |         // 本地更新数据 | ||||||
|  |         const batch = this.pigBatchesData.find(b => b.id === pen.pig_batch_id); | ||||||
|  |         if (batch) { | ||||||
|  |           batch.pens = batch.pens.filter(p => p.id !== pen.id); | ||||||
|  |         } | ||||||
|  |       } catch (err) { | ||||||
|  |         if (err !== 'cancel') { | ||||||
|  |           this.$message.error('删除失败: ' + (err.message || '未知错误')); | ||||||
|  |           console.error('Failed to delete pen:', err); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     handlePenSuccess(newPenData) { | ||||||
|  |       this.penDialogVisible = false; | ||||||
|  |       // 为新猪栏数据添加 house_name 和 batch_number | ||||||
|  |       const penWithDetails = { | ||||||
|  |         ...newPenData, | ||||||
|  |         house_name: this.houseMap.get(newPenData.house_id) || '未知猪舍', | ||||||
|  |         batch_number: this.pigBatchesData.find(b => b.id === newPenData.pig_batch_id)?.batch_number || null | ||||||
|  |       }; | ||||||
|  |  | ||||||
|  |       const batch = this.pigBatchesData.find(b => b.id === penWithDetails.pig_batch_id); | ||||||
|  |       if (batch) { | ||||||
|  |         if (this.isEditPen) { | ||||||
|  |           // 更新现有猪栏 | ||||||
|  |           const index = batch.pens.findIndex(p => p.id === penWithDetails.id); | ||||||
|  |           if (index !== -1) { | ||||||
|  |             batch.pens[index] = penWithDetails; | ||||||
|  |           } | ||||||
|  |           this.$message.success('猪栏更新成功'); | ||||||
|  |         } else { | ||||||
|  |           // 添加新猪栏 | ||||||
|  |           batch.pens.push(penWithDetails); | ||||||
|  |           this.$message.success('猪栏添加成功'); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style scoped> | ||||||
|  | /* 样式与 PigFarmManagementView 保持一致 */ | ||||||
|  | .pig-batch-management { | ||||||
|  |   padding: 20px; | ||||||
|  |   max-width: 1200px; | ||||||
|  |   margin: 0 auto; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .card-header { | ||||||
|  |   display: flex; | ||||||
|  |   justify-content: space-between; | ||||||
|  |   align-items: center; | ||||||
|  |   padding: 15px 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .title-container { | ||||||
|  |   display: flex; | ||||||
|  |   align-items: center; | ||||||
|  |   gap: 5px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .page-title { | ||||||
|  |   margin: 0; | ||||||
|  |   font-size: 1.5rem; | ||||||
|  |   font-weight: bold; | ||||||
|  |   line-height: 1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .refresh-btn { | ||||||
|  |   color: black; | ||||||
|  |   background-color: transparent; | ||||||
|  |   padding: 0; | ||||||
|  |   width: 24px; | ||||||
|  |   height: 24px; | ||||||
|  |   display: flex; | ||||||
|  |   align-items: center; | ||||||
|  |   justify-content: center; | ||||||
|  |   border: none; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .loading { | ||||||
|  |   padding: 20px 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .error { | ||||||
|  |   padding: 20px 0; | ||||||
|  |   text-align: center; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .retry-btn { | ||||||
|  |   margin-top: 15px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @media (max-width: 768px) { | ||||||
|  |   .pig-batch-management { | ||||||
|  |     padding: 10px; | ||||||
|  |   } | ||||||
|  |   .card-header { | ||||||
|  |     flex-direction: column; | ||||||
|  |     gap: 15px; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | </style> | ||||||
		Reference in New Issue
	
	Block a user