Compare commits
	
		
			11 Commits
		
	
	
		
			e341e53fe2
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 0ff82657ab | |||
| fff309f56b | |||
| d3700c8835 | |||
| ea44c9caea | |||
| ab8ea6977f | |||
| d5f91b9b12 | |||
| a8d9b033f3 | |||
| e1fb3055bb | |||
| 6d452394aa | |||
| 62801326cb | |||
| ee2f3e12d2 | 
| @@ -970,6 +970,149 @@ | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "/api/v1/monitor/notifications": { | ||||
|       "get": { | ||||
|         "security": [ | ||||
|           { | ||||
|             "BearerAuth": [] | ||||
|           } | ||||
|         ], | ||||
|         "description": "根据提供的过滤条件,分页获取通知列表", | ||||
|         "produces": [ | ||||
|           "application/json" | ||||
|         ], | ||||
|         "tags": [ | ||||
|           "数据监控" | ||||
|         ], | ||||
|         "summary": "批量查询通知", | ||||
|         "parameters": [ | ||||
|           { | ||||
|             "type": "string", | ||||
|             "name": "end_time", | ||||
|             "in": "query" | ||||
|           }, | ||||
|           { | ||||
|             "enum": [ | ||||
|               7, | ||||
|               -1, | ||||
|               0, | ||||
|               1, | ||||
|               2, | ||||
|               3, | ||||
|               4, | ||||
|               5, | ||||
|               -1, | ||||
|               5, | ||||
|               6 | ||||
|             ], | ||||
|             "type": "integer", | ||||
|             "format": "int32", | ||||
|             "x-enum-varnames": [ | ||||
|               "_numLevels", | ||||
|               "DebugLevel", | ||||
|               "InfoLevel", | ||||
|               "WarnLevel", | ||||
|               "ErrorLevel", | ||||
|               "DPanicLevel", | ||||
|               "PanicLevel", | ||||
|               "FatalLevel", | ||||
|               "_minLevel", | ||||
|               "_maxLevel", | ||||
|               "InvalidLevel" | ||||
|             ], | ||||
|             "name": "level", | ||||
|             "in": "query" | ||||
|           }, | ||||
|           { | ||||
|             "enum": [ | ||||
|               "邮件", | ||||
|               "企业微信", | ||||
|               "飞书", | ||||
|               "日志" | ||||
|             ], | ||||
|             "type": "string", | ||||
|             "x-enum-varnames": [ | ||||
|               "NotifierTypeSMTP", | ||||
|               "NotifierTypeWeChat", | ||||
|               "NotifierTypeLark", | ||||
|               "NotifierTypeLog" | ||||
|             ], | ||||
|             "name": "notifier_type", | ||||
|             "in": "query" | ||||
|           }, | ||||
|           { | ||||
|             "type": "string", | ||||
|             "name": "order_by", | ||||
|             "in": "query" | ||||
|           }, | ||||
|           { | ||||
|             "type": "integer", | ||||
|             "name": "page", | ||||
|             "in": "query" | ||||
|           }, | ||||
|           { | ||||
|             "type": "integer", | ||||
|             "name": "pageSize", | ||||
|             "in": "query" | ||||
|           }, | ||||
|           { | ||||
|             "type": "string", | ||||
|             "name": "start_time", | ||||
|             "in": "query" | ||||
|           }, | ||||
|           { | ||||
|             "enum": [ | ||||
|               "发送成功", | ||||
|               "发送失败", | ||||
|               "已跳过" | ||||
|             ], | ||||
|             "type": "string", | ||||
|             "x-enum-comments": { | ||||
|               "NotificationStatusFailed": "通知发送失败", | ||||
|               "NotificationStatusSkipped": "通知因某些原因被跳过(例如:用户未配置联系方式)", | ||||
|               "NotificationStatusSuccess": "通知已成功发送" | ||||
|             }, | ||||
|             "x-enum-descriptions": [ | ||||
|               "通知已成功发送", | ||||
|               "通知发送失败", | ||||
|               "通知因某些原因被跳过(例如:用户未配置联系方式)" | ||||
|             ], | ||||
|             "x-enum-varnames": [ | ||||
|               "NotificationStatusSuccess", | ||||
|               "NotificationStatusFailed", | ||||
|               "NotificationStatusSkipped" | ||||
|             ], | ||||
|             "name": "status", | ||||
|             "in": "query" | ||||
|           }, | ||||
|           { | ||||
|             "type": "integer", | ||||
|             "name": "user_id", | ||||
|             "in": "query" | ||||
|           } | ||||
|         ], | ||||
|         "responses": { | ||||
|           "200": { | ||||
|             "description": "OK", | ||||
|             "schema": { | ||||
|               "allOf": [ | ||||
|                 { | ||||
|                   "$ref": "#/definitions/controller.Response" | ||||
|                 }, | ||||
|                 { | ||||
|                   "type": "object", | ||||
|                   "properties": { | ||||
|                     "data": { | ||||
|                       "$ref": "#/definitions/dto.ListNotificationResponse" | ||||
|                     } | ||||
|                   } | ||||
|                 } | ||||
|               ] | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "/api/v1/monitor/pending-collections": { | ||||
|       "get": { | ||||
|         "security": [ | ||||
| @@ -3452,7 +3595,7 @@ | ||||
|             "BearerAuth": [] | ||||
|           } | ||||
|         ], | ||||
|         "description": "获取所有计划的列表", | ||||
|         "description": "获取所有计划的列表,支持按类型过滤和分页", | ||||
|         "produces": [ | ||||
|           "application/json" | ||||
|         ], | ||||
| @@ -3460,6 +3603,36 @@ | ||||
|           "计划管理" | ||||
|         ], | ||||
|         "summary": "获取计划列表", | ||||
|         "parameters": [ | ||||
|           { | ||||
|             "type": "integer", | ||||
|             "description": "页码", | ||||
|             "name": "page", | ||||
|             "in": "query" | ||||
|           }, | ||||
|           { | ||||
|             "type": "integer", | ||||
|             "description": "每页大小", | ||||
|             "name": "pageSize", | ||||
|             "in": "query" | ||||
|           }, | ||||
|           { | ||||
|             "enum": [ | ||||
|               "所有任务", | ||||
|               "自定义任务", | ||||
|               "系统任务" | ||||
|             ], | ||||
|             "type": "string", | ||||
|             "x-enum-varnames": [ | ||||
|               "PlanTypeFilterAll", | ||||
|               "PlanTypeFilterCustom", | ||||
|               "PlanTypeFilterSystem" | ||||
|             ], | ||||
|             "description": "计划类型", | ||||
|             "name": "planType", | ||||
|             "in": "query" | ||||
|           } | ||||
|         ], | ||||
|         "responses": { | ||||
|           "200": { | ||||
|             "description": "业务码为200代表成功获取列表", | ||||
| @@ -3472,10 +3645,7 @@ | ||||
|                   "type": "object", | ||||
|                   "properties": { | ||||
|                     "data": { | ||||
|                       "type": "array", | ||||
|                       "items": { | ||||
|                         "$ref": "#/definitions/dto.PlanResponse" | ||||
|                       } | ||||
|                       "$ref": "#/definitions/dto.ListPlansResponse" | ||||
|                     } | ||||
|                   } | ||||
|                 } | ||||
| @@ -3585,7 +3755,7 @@ | ||||
|             "BearerAuth": [] | ||||
|           } | ||||
|         ], | ||||
|         "description": "根据计划ID更新计划的详细信息。", | ||||
|         "description": "根据计划ID更新计划的详细信息。系统计划不允许修改。", | ||||
|         "consumes": [ | ||||
|           "application/json" | ||||
|         ], | ||||
| @@ -3641,7 +3811,7 @@ | ||||
|             "BearerAuth": [] | ||||
|           } | ||||
|         ], | ||||
|         "description": "根据计划ID删除计划。(软删除)", | ||||
|         "description": "根据计划ID删除计划。(软删除)系统计划不允许删除。", | ||||
|         "produces": [ | ||||
|           "application/json" | ||||
|         ], | ||||
| @@ -3675,7 +3845,7 @@ | ||||
|             "BearerAuth": [] | ||||
|           } | ||||
|         ], | ||||
|         "description": "根据计划ID启动一个计划的执行。", | ||||
|         "description": "根据计划ID启动一个计划的执行。系统计划不允许手动启动。", | ||||
|         "produces": [ | ||||
|           "application/json" | ||||
|         ], | ||||
| @@ -3709,7 +3879,7 @@ | ||||
|             "BearerAuth": [] | ||||
|           } | ||||
|         ], | ||||
|         "description": "根据计划ID停止一个正在执行的计划。", | ||||
|         "description": "根据计划ID停止一个正在执行的计划。系统计划不能被停止。", | ||||
|         "produces": [ | ||||
|           "application/json" | ||||
|         ], | ||||
| @@ -3918,6 +4088,64 @@ | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "/api/v1/users/{id}/notifications/test": { | ||||
|       "post": { | ||||
|         "security": [ | ||||
|           { | ||||
|             "BearerAuth": [] | ||||
|           } | ||||
|         ], | ||||
|         "description": "为指定用户发送一条特定渠道的测试消息,以验证其配置是否正确。", | ||||
|         "consumes": [ | ||||
|           "application/json" | ||||
|         ], | ||||
|         "produces": [ | ||||
|           "application/json" | ||||
|         ], | ||||
|         "tags": [ | ||||
|           "用户管理" | ||||
|         ], | ||||
|         "summary": "发送测试通知", | ||||
|         "parameters": [ | ||||
|           { | ||||
|             "type": "integer", | ||||
|             "description": "用户ID", | ||||
|             "name": "id", | ||||
|             "in": "path", | ||||
|             "required": true | ||||
|           }, | ||||
|           { | ||||
|             "description": "请求体", | ||||
|             "name": "body", | ||||
|             "in": "body", | ||||
|             "required": true, | ||||
|             "schema": { | ||||
|               "$ref": "#/definitions/dto.SendTestNotificationRequest" | ||||
|             } | ||||
|           } | ||||
|         ], | ||||
|         "responses": { | ||||
|           "200": { | ||||
|             "description": "成功响应", | ||||
|             "schema": { | ||||
|               "allOf": [ | ||||
|                 { | ||||
|                   "$ref": "#/definitions/controller.Response" | ||||
|                 }, | ||||
|                 { | ||||
|                   "type": "object", | ||||
|                   "properties": { | ||||
|                     "data": { | ||||
|                       "type": "string" | ||||
|                     } | ||||
|                   } | ||||
|                 } | ||||
|               ] | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   "definitions": { | ||||
| @@ -3948,6 +4176,7 @@ | ||||
|         2001, | ||||
|         4000, | ||||
|         4001, | ||||
|         4003, | ||||
|         4004, | ||||
|         4009, | ||||
|         5000, | ||||
| @@ -3957,6 +4186,7 @@ | ||||
|         "CodeBadRequest": "请求参数错误", | ||||
|         "CodeConflict": "资源冲突", | ||||
|         "CodeCreated": "创建成功", | ||||
|         "CodeForbidden": "禁止访问", | ||||
|         "CodeInternalError": "服务器内部错误", | ||||
|         "CodeNotFound": "资源未找到", | ||||
|         "CodeServiceUnavailable": "服务不可用", | ||||
| @@ -3968,6 +4198,7 @@ | ||||
|         "创建成功", | ||||
|         "请求参数错误", | ||||
|         "未授权", | ||||
|         "禁止访问", | ||||
|         "资源未找到", | ||||
|         "资源冲突", | ||||
|         "服务器内部错误", | ||||
| @@ -3978,6 +4209,7 @@ | ||||
|         "CodeCreated", | ||||
|         "CodeBadRequest", | ||||
|         "CodeUnauthorized", | ||||
|         "CodeForbidden", | ||||
|         "CodeNotFound", | ||||
|         "CodeConflict", | ||||
|         "CodeInternalError", | ||||
| @@ -4426,6 +4658,20 @@ | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "dto.ListNotificationResponse": { | ||||
|       "type": "object", | ||||
|       "properties": { | ||||
|         "list": { | ||||
|           "type": "array", | ||||
|           "items": { | ||||
|             "$ref": "#/definitions/dto.NotificationDTO" | ||||
|           } | ||||
|         }, | ||||
|         "pagination": { | ||||
|           "$ref": "#/definitions/dto.PaginationDTO" | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "dto.ListPendingCollectionResponse": { | ||||
|       "type": "object", | ||||
|       "properties": { | ||||
| @@ -4524,6 +4770,21 @@ | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "dto.ListPlansResponse": { | ||||
|       "type": "object", | ||||
|       "properties": { | ||||
|         "plans": { | ||||
|           "type": "array", | ||||
|           "items": { | ||||
|             "$ref": "#/definitions/dto.PlanResponse" | ||||
|           } | ||||
|         }, | ||||
|         "total": { | ||||
|           "type": "integer", | ||||
|           "example": 100 | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "dto.ListRawMaterialPurchaseResponse": { | ||||
|       "type": "object", | ||||
|       "properties": { | ||||
| @@ -4734,6 +4995,47 @@ | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "dto.NotificationDTO": { | ||||
|       "type": "object", | ||||
|       "properties": { | ||||
|         "alarm_timestamp": { | ||||
|           "type": "string" | ||||
|         }, | ||||
|         "created_at": { | ||||
|           "type": "string" | ||||
|         }, | ||||
|         "error_message": { | ||||
|           "type": "string" | ||||
|         }, | ||||
|         "id": { | ||||
|           "type": "integer" | ||||
|         }, | ||||
|         "level": { | ||||
|           "$ref": "#/definitions/zapcore.Level" | ||||
|         }, | ||||
|         "message": { | ||||
|           "type": "string" | ||||
|         }, | ||||
|         "notifier_type": { | ||||
|           "$ref": "#/definitions/notify.NotifierType" | ||||
|         }, | ||||
|         "status": { | ||||
|           "$ref": "#/definitions/models.NotificationStatus" | ||||
|         }, | ||||
|         "title": { | ||||
|           "type": "string" | ||||
|         }, | ||||
|         "to_address": { | ||||
|           "type": "string" | ||||
|         }, | ||||
|         "updated_at": { | ||||
|           "type": "string" | ||||
|         }, | ||||
|         "user_id": { | ||||
|           "type": "integer" | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "dto.PaginationDTO": { | ||||
|       "type": "object", | ||||
|       "properties": { | ||||
| @@ -5178,6 +5480,9 @@ | ||||
|         "plan_id": { | ||||
|           "type": "integer" | ||||
|         }, | ||||
|         "plan_name": { | ||||
|           "type": "string" | ||||
|         }, | ||||
|         "started_at": { | ||||
|           "type": "string" | ||||
|         }, | ||||
| @@ -5232,6 +5537,14 @@ | ||||
|           "type": "string", | ||||
|           "example": "猪舍温度控制计划" | ||||
|         }, | ||||
|         "plan_type": { | ||||
|           "allOf": [ | ||||
|             { | ||||
|               "$ref": "#/definitions/models.PlanType" | ||||
|             } | ||||
|           ], | ||||
|           "example": "自定义任务" | ||||
|         }, | ||||
|         "status": { | ||||
|           "allOf": [ | ||||
|             { | ||||
| @@ -5586,6 +5899,22 @@ | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "dto.SendTestNotificationRequest": { | ||||
|       "type": "object", | ||||
|       "required": [ | ||||
|         "type" | ||||
|       ], | ||||
|       "properties": { | ||||
|         "type": { | ||||
|           "description": "Type 指定要测试的通知渠道", | ||||
|           "allOf": [ | ||||
|             { | ||||
|               "$ref": "#/definitions/notify.NotifierType" | ||||
|             } | ||||
|           ] | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "dto.SensorDataDTO": { | ||||
|       "type": "object", | ||||
|       "properties": { | ||||
| @@ -6196,6 +6525,29 @@ | ||||
|         "ReasonTypeHealthCare" | ||||
|       ] | ||||
|     }, | ||||
|     "models.NotificationStatus": { | ||||
|       "type": "string", | ||||
|       "enum": [ | ||||
|         "发送成功", | ||||
|         "发送失败", | ||||
|         "已跳过" | ||||
|       ], | ||||
|       "x-enum-comments": { | ||||
|         "NotificationStatusFailed": "通知发送失败", | ||||
|         "NotificationStatusSkipped": "通知因某些原因被跳过(例如:用户未配置联系方式)", | ||||
|         "NotificationStatusSuccess": "通知已成功发送" | ||||
|       }, | ||||
|       "x-enum-descriptions": [ | ||||
|         "通知已成功发送", | ||||
|         "通知发送失败", | ||||
|         "通知因某些原因被跳过(例如:用户未配置联系方式)" | ||||
|       ], | ||||
|       "x-enum-varnames": [ | ||||
|         "NotificationStatusSuccess", | ||||
|         "NotificationStatusFailed", | ||||
|         "NotificationStatusSkipped" | ||||
|       ] | ||||
|     }, | ||||
|     "models.PenStatus": { | ||||
|       "type": "string", | ||||
|       "enum": [ | ||||
| @@ -6437,6 +6789,17 @@ | ||||
|         "PlanStatusFailed" | ||||
|       ] | ||||
|     }, | ||||
|     "models.PlanType": { | ||||
|       "type": "string", | ||||
|       "enum": [ | ||||
|         "自定义任务", | ||||
|         "系统任务" | ||||
|       ], | ||||
|       "x-enum-varnames": [ | ||||
|         "PlanTypeCustom", | ||||
|         "PlanTypeSystem" | ||||
|       ] | ||||
|     }, | ||||
|     "models.SensorType": { | ||||
|       "type": "string", | ||||
|       "enum": [ | ||||
| @@ -6492,22 +6855,26 @@ | ||||
|       "enum": [ | ||||
|         "计划分析", | ||||
|         "等待", | ||||
|         "下料" | ||||
|         "下料", | ||||
|         "全量采集" | ||||
|       ], | ||||
|       "x-enum-comments": { | ||||
|         "TaskPlanAnalysis": "解析Plan的Task列表并添加到待执行队列的特殊任务", | ||||
|         "TaskTypeFullCollection": "新增的全量采集任务", | ||||
|         "TaskTypeReleaseFeedWeight": "下料口释放指定重量任务", | ||||
|         "TaskTypeWaiting": "等待任务" | ||||
|       }, | ||||
|       "x-enum-descriptions": [ | ||||
|         "解析Plan的Task列表并添加到待执行队列的特殊任务", | ||||
|         "等待任务", | ||||
|         "下料口释放指定重量任务" | ||||
|         "下料口释放指定重量任务", | ||||
|         "新增的全量采集任务" | ||||
|       ], | ||||
|       "x-enum-varnames": [ | ||||
|         "TaskPlanAnalysis", | ||||
|         "TaskTypeWaiting", | ||||
|         "TaskTypeReleaseFeedWeight" | ||||
|         "TaskTypeReleaseFeedWeight", | ||||
|         "TaskTypeFullCollection" | ||||
|       ] | ||||
|     }, | ||||
|     "models.ValueDescriptor": { | ||||
| @@ -6525,6 +6892,64 @@ | ||||
|           "$ref": "#/definitions/models.SensorType" | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "notify.NotifierType": { | ||||
|       "type": "string", | ||||
|       "enum": [ | ||||
|         "邮件", | ||||
|         "企业微信", | ||||
|         "飞书", | ||||
|         "日志" | ||||
|       ], | ||||
|       "x-enum-varnames": [ | ||||
|         "NotifierTypeSMTP", | ||||
|         "NotifierTypeWeChat", | ||||
|         "NotifierTypeLark", | ||||
|         "NotifierTypeLog" | ||||
|       ] | ||||
|     }, | ||||
|     "repository.PlanTypeFilter": { | ||||
|       "type": "string", | ||||
|       "enum": [ | ||||
|         "所有任务", | ||||
|         "自定义任务", | ||||
|         "系统任务" | ||||
|       ], | ||||
|       "x-enum-varnames": [ | ||||
|         "PlanTypeFilterAll", | ||||
|         "PlanTypeFilterCustom", | ||||
|         "PlanTypeFilterSystem" | ||||
|       ] | ||||
|     }, | ||||
|     "zapcore.Level": { | ||||
|       "type": "integer", | ||||
|       "format": "int32", | ||||
|       "enum": [ | ||||
|         7, | ||||
|         -1, | ||||
|         0, | ||||
|         1, | ||||
|         2, | ||||
|         3, | ||||
|         4, | ||||
|         5, | ||||
|         -1, | ||||
|         5, | ||||
|         6 | ||||
|       ], | ||||
|       "x-enum-varnames": [ | ||||
|         "_numLevels", | ||||
|         "DebugLevel", | ||||
|         "InfoLevel", | ||||
|         "WarnLevel", | ||||
|         "ErrorLevel", | ||||
|         "DPanicLevel", | ||||
|         "PanicLevel", | ||||
|         "FatalLevel", | ||||
|         "_minLevel", | ||||
|         "_maxLevel", | ||||
|         "InvalidLevel" | ||||
|       ] | ||||
|     } | ||||
|   }, | ||||
|   "securityDefinitions": { | ||||
|   | ||||
| @@ -121,6 +121,49 @@ import http from '../utils/http'; | ||||
|  * @property {number} [operator_id] | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * @typedef {('邮件'|'企业微信'|'飞书'|'日志')} NotifierType | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * @typedef {('发送成功'|'发送失败'|'已跳过')} NotificationStatus | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * @typedef {object} NotificationDTO | ||||
|  * @property {number} id | ||||
|  * @property {number} user_id | ||||
|  * @property {NotifierType} notifier_type | ||||
|  * @property {string} to_address | ||||
|  * @property {string} title | ||||
|  * @property {string} message | ||||
|  * @property {number} level - 日志级别, 见 ZapcoreLevel 枚举 | ||||
|  * @property {string} alarm_timestamp | ||||
|  * @property {NotificationStatus} status | ||||
|  * @property {string} error_message | ||||
|  * @property {string} created_at | ||||
|  * @property {string} updated_at | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * @typedef {object} ListNotificationResponse | ||||
|  * @property {Array<NotificationDTO>} list | ||||
|  * @property {PaginationDTO} pagination | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * @typedef {object} NotificationsParams | ||||
|  * @property {number} [page] | ||||
|  * @property {number} [pageSize] | ||||
|  * @property {string} [order_by] | ||||
|  * @property {string} [start_time] | ||||
|  * @property {string} [end_time] | ||||
|  * @property {number} [level] - 日志级别, 见 ZapcoreLevel 枚举 | ||||
|  * @property {NotifierType} [notifier_type] | ||||
|  * @property {NotificationStatus} [status] | ||||
|  * @property {number} [user_id] | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * @typedef {('等待中'|'已完成'|'已超时')} PendingCollectionStatus | ||||
|  */ | ||||
| @@ -606,6 +649,22 @@ import http from '../utils/http'; | ||||
|  * @property {number} [operator_id] | ||||
|  */ | ||||
|  | ||||
| // --- Enums --- | ||||
|  | ||||
| /** | ||||
|  * 日志级别, 对应后端的 zapcore.Level | ||||
|  * @enum {number} | ||||
|  */ | ||||
| export const ZapcoreLevel = { | ||||
|   Debug: -1, | ||||
|   Info: 0, | ||||
|   Warn: 1, | ||||
|   Error: 2, | ||||
|   DPanic: 3, | ||||
|   Panic: 4, | ||||
|   Fatal: 5, | ||||
|   Invalid: 6, | ||||
| }; | ||||
|  | ||||
| // --- Functions --- | ||||
|  | ||||
| @@ -647,6 +706,16 @@ export const getMedicationLogs = async (params) => { | ||||
|   return processResponse(responseData); | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * 批量查询通知 | ||||
|  * @param {NotificationsParams} params - 查询参数 | ||||
|  * @returns {Promise<{list: Array<NotificationDTO>, total: number}>} | ||||
|  */ | ||||
| export const getNotifications = async (params) => { | ||||
|   const responseData = await http.get('/api/v1/monitor/notifications', { params }); | ||||
|   return processResponse(responseData); | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * 获取待采集请求列表 | ||||
|  * @param {PendingCollectionsParams} params - 查询参数 | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import http from '../utils/http'; | ||||
|  | ||||
| /** | ||||
|  * @typedef {('计划分析'|'等待'|'下料')} TaskType | ||||
|  * @typedef {('计划分析'|'等待'|'下料'|'全量采集')} TaskType | ||||
|  */ | ||||
|  | ||||
| /** | ||||
| @@ -67,6 +67,10 @@ import http from '../utils/http'; | ||||
|  * @typedef {('子计划'|'任务')} PlanContentType | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * @typedef {('自定义任务'|'系统任务')} PlanType | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * @typedef {object} PlanResponse | ||||
|  * @property {number} id | ||||
| @@ -78,17 +82,41 @@ import http from '../utils/http'; | ||||
|  * @property {number} execute_count | ||||
|  * @property {PlanStatus} status | ||||
|  * @property {PlanContentType} content_type | ||||
|  * @property {PlanType} plan_type | ||||
|  * @property {Array<TaskResponse>} tasks | ||||
|  * @property {Array<SubPlanResponse>} sub_plans | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * @typedef {object} ListPlansResponse | ||||
|  * @property {Array<PlanResponse>} plans | ||||
|  * @property {number} total | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * @typedef {object} PlanExecutionLogDTO | ||||
|  * @property {string} created_at | ||||
|  * @property {string} ended_at | ||||
|  * @property {string} error | ||||
|  * @property {number} id | ||||
|  * @property {number} plan_id | ||||
|  * @property {string} plan_name | ||||
|  * @property {string} started_at | ||||
|  * @property {string} status | ||||
|  * @property {string} updated_at | ||||
|  */ | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * 获取所有计划的列表 | ||||
|  * @returns {Promise<Array<PlanResponse>>} | ||||
|  * @param {object} params - 查询参数 | ||||
|  * @param {number} [params.page] - 页码 | ||||
|  * @param {number} [params.pageSize] - 每页大小 | ||||
|  * @param {('所有任务'|'自定义任务'|'系统任务')} [params.planType] - 计划类型 | ||||
|  * @returns {Promise<ListPlansResponse>} | ||||
|  */ | ||||
| const getPlans = () => { | ||||
|   return http.get('/api/v1/plans'); | ||||
| const getPlans = (params) => { | ||||
|   return http.get('/api/v1/plans', { params }); | ||||
| }; | ||||
|  | ||||
| /** | ||||
| @@ -110,7 +138,7 @@ const getPlanById = (id) => { | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * 根据计划ID更新计划的详细信息 | ||||
|  * 根据计划ID更新计划的详细信息。系统计划不允许修改。 | ||||
|  * @param {number} id - 计划ID | ||||
|  * @param {UpdatePlanRequest} planData - 更新后的计划信息 | ||||
|  * @returns {Promise<PlanResponse>} | ||||
| @@ -120,7 +148,7 @@ const updatePlan = (id, planData) => { | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * 根据计划ID删除计划(软删除) | ||||
|  * 根据计划ID删除计划。(软删除)系统计划不允许删除。 | ||||
|  * @param {number} id - 计划ID | ||||
|  * @returns {Promise<*>} | ||||
|  */ | ||||
| @@ -129,7 +157,7 @@ const deletePlan = (id) => { | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * 根据计划ID启动一个计划的执行 | ||||
|  * 根据计划ID启动一个计划的执行。系统计划不允许手动启动。 | ||||
|  * @param {number} id - 计划ID | ||||
|  * @returns {Promise<*>} | ||||
|  */ | ||||
| @@ -138,7 +166,7 @@ const startPlan = (id) => { | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * 根据计划ID停止一个正在执行的计划 | ||||
|  * 根据计划ID停止一个正在执行的计划。系统计划不能被停止。 | ||||
|  * @param {number} id - 计划ID | ||||
|  * @returns {Promise<*>} | ||||
|  */ | ||||
|   | ||||
| @@ -71,6 +71,15 @@ import http from '../utils/http'; | ||||
|  * @property {string} [username] | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * @typedef {('邮件'|'企业微信'|'飞书'|'日志')} NotifierType | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * @typedef {object} SendTestNotificationRequest | ||||
|  * @property {NotifierType} type - Type 指定要测试的通知渠道 | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * 创建一个新用户 | ||||
|  * @param {CreateUserRequest} userData - 用户信息 | ||||
| @@ -99,8 +108,19 @@ const getUserHistory = (id, params) => { | ||||
|   return http.get(`/api/v1/users/${id}/history`, { params }); | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * 发送测试通知 | ||||
|  * @param {number} id - 用户ID | ||||
|  * @param {SendTestNotificationRequest} data - 请求体 | ||||
|  * @returns {Promise<string>} | ||||
|  */ | ||||
| const sendTestNotification = (id, data) => { | ||||
|   return http.post(`/api/v1/users/${id}/notifications/test`, data); | ||||
| }; | ||||
|  | ||||
| export const UserApi = { | ||||
|   createUser, | ||||
|   login, | ||||
|   getUserHistory, | ||||
|   sendTestNotification, | ||||
| }; | ||||
|   | ||||
| @@ -29,11 +29,35 @@ | ||||
|           </div> | ||||
|         </div> | ||||
|         <div class="batch-actions"> | ||||
|           <el-button size="small" type="primary" @click.stop="showAddPenDialog(batch)" :disabled="!batch.is_active"> | ||||
|             增加猪栏 | ||||
|           </el-button> | ||||
|           <el-button size="small" @click.stop="emitEditBatch(batch)">编辑</el-button> | ||||
|           <el-button size="small" type="danger" @click.stop="emitDeleteBatch(batch)">删除</el-button> | ||||
|           <el-dropdown trigger="click" class="batch-dropdown"> | ||||
|             <el-button type="primary" size="small"> | ||||
|               管理猪群<el-icon class="el-icon--right"><arrow-down /></el-icon> | ||||
|             </el-button> | ||||
|             <template #dropdown> | ||||
|               <el-dropdown-menu> | ||||
|                 <el-dropdown-item @click="showAddPenDialog(batch)" :disabled="!batch.is_active">增加猪栏</el-dropdown-item> | ||||
|                 <el-dropdown-item @click="emitEditBatch(batch)">编辑</el-dropdown-item> | ||||
|                 <el-dropdown-item @click="emitDeleteBatch(batch)" divided>删除</el-dropdown-item> | ||||
|               </el-dropdown-menu> | ||||
|             </template> | ||||
|           </el-dropdown> | ||||
|           <el-dropdown trigger="click" class="batch-dropdown"> | ||||
|             <el-button type="success" size="small"> | ||||
|               调栏<el-icon class="el-icon--right"><arrow-down /></el-icon> | ||||
|             </el-button> | ||||
|             <template #dropdown> | ||||
|               <el-dropdown-menu> | ||||
|                 <el-dropdown-item  | ||||
|                   @click="emitTransferPigs(batch)" | ||||
|                   :disabled="!batch.is_active || !batch.pens || batch.pens.length < 2" | ||||
|                 >群内调栏</el-dropdown-item> | ||||
|                 <el-dropdown-item  | ||||
|                   @click="emitTransferPigsAcrossBatches(batch)" | ||||
|                   :disabled="!batch.is_active || !batch.pens || batch.pens.length === 0" | ||||
|                 >跨群调栏</el-dropdown-item> | ||||
|               </el-dropdown-menu> | ||||
|             </template> | ||||
|           </el-dropdown> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div v-if="batch.isExpanded" class="batch-content"> | ||||
| @@ -43,6 +67,7 @@ | ||||
|               :key="pen.id" | ||||
|               :pen="pen" | ||||
|               :isBatchActive="batch.is_active" | ||||
|               :batchUnassignedPigCount="batch.unassigned_pig_count" | ||||
|               @allocate-pigs="showAllocatePigsDialog($event, batch)" | ||||
|               @remove="emitRemovePen" | ||||
|           /> | ||||
| @@ -73,8 +98,7 @@ | ||||
|  | ||||
|     <!-- 分配猪只对话框 --> | ||||
|     <AllocatePigsDialog | ||||
|         v-if="allocatePigsDialogVisible" | ||||
|         :visible.sync="allocatePigsDialogVisible" | ||||
|         v-model:visible="allocatePigsDialogVisible" | ||||
|         :unassigned-pig-count="currentBatch ? currentBatch.unassigned_pig_count : 0" | ||||
|         :pen-id="selectedPenForAllocation ? selectedPenForAllocation.id : 0" | ||||
|         @confirm="handleAllocatePigs" | ||||
| @@ -87,12 +111,14 @@ import PigBatchPenCard from './PigBatchPenCard.vue'; | ||||
| import AllocatePigsDialog from './AllocatePigsDialog.vue'; | ||||
| import {getAllPens, getAllPigHouses, movePigsIntoPen} from '../api/pigBatch'; | ||||
| import {formatRFC3339} from '../utils/format'; // 导入格式化函数 | ||||
| import { ArrowDown } from '@element-plus/icons-vue'; // 导入 ArrowDown 图标 | ||||
|  | ||||
| export default { | ||||
|   name: 'PigBatchList', | ||||
|   components: { | ||||
|     PigBatchPenCard, | ||||
|     AllocatePigsDialog | ||||
|     AllocatePigsDialog, | ||||
|     ArrowDown // 注册 ArrowDown 图标 | ||||
|   }, | ||||
|   props: { | ||||
|     pigBatches: { | ||||
| @@ -100,7 +126,7 @@ export default { | ||||
|       required: true | ||||
|     } | ||||
|   }, | ||||
|   emits: ['edit-batch', 'delete-batch', 'add-pen', 'remove-pen', 'assign-pen-to-batch', 'reload-data'], | ||||
|   emits: ['edit-batch', 'delete-batch', 'add-pen', 'remove-pen', 'assign-pen-to-batch', 'reload-data', 'transfer-pigs', 'transfer-pigs-across-batches'], | ||||
|   data() { | ||||
|     return { | ||||
|       addPenDialogVisible: false, | ||||
| @@ -129,9 +155,12 @@ export default { | ||||
|         // Create a map for quick lookup of house names by ID | ||||
|         const houseMap = new Map(houses.map(house => [house.id, house.name])); | ||||
|  | ||||
|         this.availablePens = pens.map(pen => ({ | ||||
|         // Filter for pens that are not assigned to any batch | ||||
|         const unassignedPens = pens.filter(pen => !pen.pig_batch_id); | ||||
|  | ||||
|         this.availablePens = unassignedPens.map(pen => ({ | ||||
|           id: pen.id, | ||||
|           label: `${pen.pen_number}(${houseMap.get(pen.house_id) || '未知猪舍'})` // Changed format here | ||||
|           label: `${pen.pen_number} (${houseMap.get(pen.house_id) || '未知猪舍'})` | ||||
|         })); | ||||
|       } catch (error) { | ||||
|         console.error("Error fetching pens or houses:", error); | ||||
| @@ -178,6 +207,12 @@ export default { | ||||
|     emitDeleteBatch(batch) { | ||||
|       this.$emit('delete-batch', batch); | ||||
|     }, | ||||
|     emitTransferPigs(batch) { | ||||
|       this.$emit('transfer-pigs', batch); | ||||
|     }, | ||||
|     emitTransferPigsAcrossBatches(batch) { | ||||
|       this.$emit('transfer-pigs-across-batches', batch); | ||||
|     }, | ||||
|     // 猪栏操作 | ||||
|     emitRemovePen(pen) { | ||||
|       this.$emit('remove-pen', pen); | ||||
| @@ -238,6 +273,10 @@ export default { | ||||
|   gap: 10px; | ||||
| } | ||||
|  | ||||
| .batch-dropdown { | ||||
|   margin-left: 10px; /* 为下拉菜单添加左边距 */ | ||||
| } | ||||
|  | ||||
| .batch-content { | ||||
|   padding: 16px; | ||||
|   border-top: 1px solid #eee; | ||||
|   | ||||
| @@ -7,10 +7,10 @@ | ||||
|       <div class="info-item">猪舍: {{ pen.house_name || '未知' }}</div> | ||||
|       <div class="info-item border-left">容量: {{ pen.capacity }}</div> | ||||
|       <div class="info-item">批次: {{ pen.batch_number || '未分配' }}</div> | ||||
|       <div class="info-item border-left">存栏: {{ pen.current_pig_count || 0 }}</div> | ||||
|       <div class="info-item border-left">存栏: <span :class="{'over-capacity': pen.current_pig_count > pen.capacity}">{{ pen.current_pig_count || 0 }}</span></div> | ||||
|     </div> | ||||
|     <div class="actions-section"> | ||||
|       <el-button size="small" @click="emitAllocatePigs" :disabled="!isBatchActive">分配猪只</el-button> | ||||
|       <el-button size="small" @click="emitAllocatePigs" :disabled="!isBatchActive || batchUnassignedPigCount <= 0">分配猪只</el-button> | ||||
|       <el-button size="small" type="danger" @click="emitRemove" :disabled="!isBatchActive || pen.current_pig_count > 0">移除</el-button> | ||||
|     </div> | ||||
|   </div> | ||||
| @@ -29,6 +29,10 @@ export default { | ||||
|     isBatchActive: { | ||||
|       type: Boolean, | ||||
|       default: true // 默认活跃,以防万一没有传递 | ||||
|     }, | ||||
|     batchUnassignedPigCount: { | ||||
|       type: Number, | ||||
|       default: 0 | ||||
|     } | ||||
|   }, | ||||
|   emits: ['remove', 'allocate-pigs'], | ||||
| @@ -124,4 +128,8 @@ export default { | ||||
|   width: 100%; | ||||
|   margin: 0; | ||||
| } | ||||
|  | ||||
| .over-capacity { | ||||
|   color: red; | ||||
| } | ||||
| </style> | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|       <div class="info-item">猪舍: {{ pen.house_name || '未知' }}</div> | ||||
|       <div class="info-item border-left">容量: {{ pen.capacity }}</div> | ||||
|       <div class="info-item">批次: {{ pen.batch_number || '未分配' }}</div> | ||||
|       <div class="info-item border-left">存栏: {{ pen.current_pig_count || 0 }}</div> | ||||
|       <div class="info-item border-left">存栏: <span :class="{'over-capacity': pen.current_pig_count > pen.capacity}">{{ pen.current_pig_count || 0 }}</span></div> | ||||
|     </div> | ||||
|     <div class="actions-section"> | ||||
|       <el-button size="small" @click="emitEdit">编辑</el-button> | ||||
| @@ -120,4 +120,8 @@ export default { | ||||
|   width: 100%; | ||||
|   margin: 0; | ||||
| } | ||||
|  | ||||
| .over-capacity { | ||||
|   color: red; | ||||
| } | ||||
| </style> | ||||
|   | ||||
| @@ -20,9 +20,9 @@ | ||||
|             <span>{{ plan.name }} - 内容</span> | ||||
|             <div> | ||||
|               <template v-if="!isSubPlan"> | ||||
|                 <el-button class="button" type="primary" @click="savePlanContent" v-if="isEditingContent">保存</el-button> | ||||
|                 <el-button class="button" type="danger" @click="cancelEdit" v-if="isEditingContent">取消</el-button> | ||||
|                 <el-button class="button" @click="enterEditMode" v-else>编辑内容</el-button> | ||||
|                 <el-button class="button" type="primary" @click="savePlanContent" v-if="isEditingContent" :disabled="plan.plan_type === '系统任务'">保存</el-button> | ||||
|                 <el-button class="button" type="danger" @click="cancelEdit" v-if="isEditingContent" :disabled="plan.plan_type === '系统任务'">取消</el-button> | ||||
|                 <el-button class="button" @click="enterEditMode" v-else :disabled="plan.plan_type === '系统任务'">编辑内容</el-button> | ||||
|               </template> | ||||
|  | ||||
|               <!-- Dynamic Add Buttons --> | ||||
| @@ -32,12 +32,14 @@ | ||||
|                   type="primary" | ||||
|                   size="small" | ||||
|                   @click="showAddSubPlanDialog" | ||||
|                   :disabled="plan.plan_type === '系统任务'" | ||||
|                 >增加子计划</el-button> | ||||
|                 <el-button | ||||
|                   v-if="plan.content_type === 'tasks' || !plan.content_type" | ||||
|                   type="primary" | ||||
|                   size="small" | ||||
|                   @click="showTaskEditorDialog()" | ||||
|                   :disabled="plan.plan_type === '系统任务'" | ||||
|                 >增加子任务</el-button> | ||||
|               </template> | ||||
|             </div> | ||||
| @@ -61,8 +63,8 @@ | ||||
|                   延时: {{ task.parameters.delay_duration }} 秒 | ||||
|                 </p> | ||||
|                 <el-button-group v-if="isEditingContent"> | ||||
|                   <el-button type="primary" size="small" @click="editTask(task)">编辑</el-button> | ||||
|                   <el-button type="danger" size="small" @click="deleteTask(task)">删除</el-button> | ||||
|                   <el-button type="primary" size="small" @click="editTask(task)" :disabled="plan.plan_type === '系统任务'">编辑</el-button> | ||||
|                   <el-button type="danger" size="small" @click="deleteTask(task)" :disabled="plan.plan_type === '系统任务'">删除</el-button> | ||||
|                 </el-button-group> | ||||
|               </el-card> | ||||
|             </el-timeline-item> | ||||
| @@ -80,7 +82,7 @@ | ||||
|                   <!-- Pass child_plan_id to recursive PlanDetail --> | ||||
|                   <plan-detail :plan-id="subPlan.child_plan_id" :is-sub-plan="true" /> | ||||
|                   <el-button-group v-if="isEditingContent" class="sub-plan-actions"> | ||||
|                     <el-button type="danger" size="small" @click="deleteSubPlan(subPlan)">删除</el-button> | ||||
|                     <el-button type="danger" size="small" @click="deleteSubPlan(subPlan)" :disabled="plan.plan_type === '系统任务'">删除</el-button> | ||||
|                   </el-button-group> | ||||
|                 </div> | ||||
|               </el-card> | ||||
| @@ -135,16 +137,16 @@ | ||||
|     > | ||||
|       <el-form :model="currentTaskForm" ref="taskFormRef" :rules="taskFormRules" label-width="100px"> | ||||
|         <el-form-item label="任务类型" prop="type"> | ||||
|           <el-select v-model="currentTaskForm.type" placeholder="请选择任务类型" style="width: 100%;" :disabled="isEditingTask"> | ||||
|           <el-select v-model="currentTaskForm.type" placeholder="请选择任务类型" style="width: 100%;" :disabled="isEditingTask || plan.plan_type === '系统任务'"> | ||||
|             <!-- Only Delay Task for now --> | ||||
|             <el-option label="延时任务" value="delay_task"></el-option> | ||||
|           </el-select> | ||||
|         </el-form-item> | ||||
|         <el-form-item label="任务名称" prop="name"> | ||||
|           <el-input v-model="currentTaskForm.name" placeholder="请输入任务名称"></el-input> | ||||
|           <el-input v-model="currentTaskForm.name" placeholder="请输入任务名称" :disabled="plan.plan_type === '系统任务'"></el-input> | ||||
|         </el-form-item> | ||||
|         <el-form-item label="任务描述" prop="description"> | ||||
|           <el-input type="textarea" v-model="currentTaskForm.description" placeholder="请输入任务描述"></el-input> | ||||
|           <el-input type="textarea" v-model="currentTaskForm.description" placeholder="请输入任务描述" :disabled="plan.plan_type === '系统任务'"></el-input> | ||||
|         </el-form-item> | ||||
|         <!-- Dynamic task component for specific parameters --> | ||||
|         <template v-if="currentTaskForm.type === 'delay_task'"> | ||||
| @@ -153,6 +155,7 @@ | ||||
|             @update:parameters="val => currentTaskForm.parameters = val" | ||||
|             prop-path="parameters.delay_duration" | ||||
|             :is-editing="true"  | ||||
|             :disabled="plan.plan_type === '系统任务'" | ||||
|           /> | ||||
|         </template> | ||||
|         <!-- More task types can be rendered here --> | ||||
| @@ -160,7 +163,7 @@ | ||||
|       <template #footer> | ||||
|         <span class="dialog-footer"> | ||||
|           <el-button @click="taskEditorDialogVisible = false">取消</el-button> | ||||
|           <el-button type="primary" @click="confirmTaskEdit">确定</el-button> | ||||
|           <el-button type="primary" @click="confirmTaskEdit" :disabled="plan.plan_type === '系统任务'">确定</el-button> | ||||
|         </span> | ||||
|       </template> | ||||
|     </el-dialog> | ||||
| @@ -355,7 +358,7 @@ export default { | ||||
|     }, | ||||
|     async fetchAvailablePlans() { | ||||
|       try { | ||||
|         const response = await apiClient.plans.getPlans(); // 更正此处 | ||||
|         const response = await apiClient.plans.getPlans({ planType: '自定义任务' });  | ||||
|         this.availablePlans = response.data.plans.filter(p => | ||||
|           p.id !== this.planId | ||||
|         ); | ||||
| @@ -483,7 +486,8 @@ export default { | ||||
|             } else { | ||||
|               ElMessage.error('未找到要编辑的任务'); | ||||
|             } | ||||
|           } else { | ||||
|           } | ||||
|           else { | ||||
|             // Add a new task | ||||
|             const newTask = { | ||||
|               id: Date.now(),  | ||||
|   | ||||
							
								
								
									
										204
									
								
								src/components/TransferPigsAcrossBatchesModal.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										204
									
								
								src/components/TransferPigsAcrossBatchesModal.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,204 @@ | ||||
| <template> | ||||
|   <el-dialog | ||||
|     title="跨群调栏" | ||||
|     :model-value="visible" | ||||
|     @update:model-value="$emit('update:visible', $event)" | ||||
|     width="40%" | ||||
|     @close="resetForm" | ||||
|   > | ||||
|     <el-form ref="form" :model="form" :rules="rules" label-width="120px"> | ||||
|       <el-form-item label="源猪群批次"> | ||||
|         <span>{{ batch.batch_number }}</span> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="源猪栏" prop="fromPenID"> | ||||
|         <el-select v-model="form.fromPenID" placeholder="请选择源猪栏" style="width: 100%;"> | ||||
|           <el-option | ||||
|             v-for="pen in sourcePens" | ||||
|             :key="pen.id" | ||||
|             :label="`${pen.pen_number} (存栏: ${pen.current_pig_count})`" | ||||
|             :value="pen.id" | ||||
|           ></el-option> | ||||
|         </el-select> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="目标猪群批次" prop="destBatchID"> | ||||
|         <el-select v-model="form.destBatchID" placeholder="请选择目标猪群" style="width: 100%;" @change="onDestBatchChange"> | ||||
|           <el-option | ||||
|             v-for="b in availableBatches" | ||||
|             :key="b.id" | ||||
|             :label="b.batch_number" | ||||
|             :value="b.id" | ||||
|           ></el-option> | ||||
|         </el-select> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="目标猪栏" prop="toPenID"> | ||||
|         <el-select v-model="form.toPenID" placeholder="请选择目标猪栏" style="width: 100%;"> | ||||
|           <el-option | ||||
|             v-for="pen in destinationPens" | ||||
|             :key="pen.id" | ||||
|             :label="`${pen.pen_number} (容量: ${pen.capacity})`" | ||||
|             :value="pen.id" | ||||
|           ></el-option> | ||||
|         </el-select> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="调栏数量" prop="quantity"> | ||||
|         <el-input-number v-model="form.quantity" :min="1" :max="maxTransferQuantity" /> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="备注" prop="remarks"> | ||||
|         <el-input v-model="form.remarks" type="textarea" :rows="2" placeholder="请输入备注" /> | ||||
|       </el-form-item> | ||||
|     </el-form> | ||||
|     <template #footer> | ||||
|       <span class="dialog-footer"> | ||||
|         <el-button @click="$emit('update:visible', false)">取 消</el-button> | ||||
|         <el-button type="primary" @click="handleConfirm">确 定</el-button> | ||||
|       </span> | ||||
|     </template> | ||||
|   </el-dialog> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { getPigBatches, transferPigsAcrossBatches } from '@/api/pigBatch'; | ||||
| import { getPens } from '@/api/pen'; | ||||
|  | ||||
| export default { | ||||
|   name: 'TransferPigsAcrossBatchesModal', | ||||
|   props: { | ||||
|     visible: { | ||||
|       type: Boolean, | ||||
|       required: true | ||||
|     }, | ||||
|     batch: { | ||||
|       type: Object, | ||||
|       required: true // 源猪群批次信息 | ||||
|     } | ||||
|   }, | ||||
|   emits: ['update:visible', 'success'], | ||||
|   data() { | ||||
|     return { | ||||
|       form: { | ||||
|         fromPenID: null, | ||||
|         destBatchID: null, | ||||
|         toPenID: null, | ||||
|         quantity: 1, | ||||
|         remarks: '' | ||||
|       }, | ||||
|       rules: { | ||||
|         fromPenID: [{ required: true, message: '请选择源猪栏', trigger: 'change' }], | ||||
|         destBatchID: [{ required: true, message: '请选择目标猪群批次', trigger: 'change' }], | ||||
|         toPenID: [{ required: true, message: '请选择目标猪栏', trigger: 'change' }], | ||||
|         quantity: [ | ||||
|           { required: true, message: '请输入调栏数量', trigger: 'blur' }, | ||||
|           { type: 'integer', message: '请输入整数', trigger: 'blur' }, | ||||
|           { validator: this.validateQuantity, trigger: 'blur' } | ||||
|         ] | ||||
|       }, | ||||
|       sourcePens: [], // 源猪群的猪栏列表 | ||||
|       availableBatches: [], // 可用的目标猪群列表 | ||||
|       destinationPens: [], // 目标猪群的猪栏列表 | ||||
|       maxTransferQuantity: 1 // 最大可调栏数量 | ||||
|     }; | ||||
|   }, | ||||
|   watch: { | ||||
|     visible(newVal) { | ||||
|       if (newVal) { | ||||
|         this.initData(); | ||||
|       } | ||||
|     }, | ||||
|     'form.fromPenID': function(newVal) { | ||||
|       if (newVal) { | ||||
|         const selectedPen = this.sourcePens.find(pen => pen.id === newVal); | ||||
|         this.maxTransferQuantity = selectedPen ? selectedPen.current_pig_count : 1; | ||||
|         // 如果当前数量大于最大值,重置数量 | ||||
|         if (this.form.quantity > this.maxTransferQuantity) { | ||||
|           this.form.quantity = this.maxTransferQuantity; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     async initData() { | ||||
|       this.resetForm(); | ||||
|       this.sourcePens = this.batch.pens.filter(pen => pen.current_pig_count > 0); // 过滤掉没有猪的猪栏 | ||||
|       await this.fetchAvailableBatchesAndPens(); | ||||
|     }, | ||||
|     async fetchAvailableBatchesAndPens() { | ||||
|       try { | ||||
|         const [batchesResponse, pensResponse] = await Promise.all([ | ||||
|           getPigBatches({ is_active: true }), // 获取所有活跃猪群 | ||||
|           getPens() // 获取所有猪栏 | ||||
|         ]); | ||||
|  | ||||
|         const allBatches = batchesResponse.data || []; | ||||
|         const allPens = pensResponse.data || []; | ||||
|  | ||||
|         // 过滤掉源猪群自身,以及非活跃的猪群 | ||||
|         this.availableBatches = allBatches.filter(b => b.id !== this.batch.id && b.is_active); | ||||
|  | ||||
|         // 将所有猪栏按批次ID分组,方便后续查找 | ||||
|         const pensByBatch = allPens.reduce((acc, pen) => { | ||||
|           if (pen.pig_batch_id) { | ||||
|             if (!acc[pen.pig_batch_id]) { | ||||
|               acc[pen.pig_batch_id] = []; | ||||
|             } | ||||
|             acc[pen.pig_batch_id].push(pen); | ||||
|           } | ||||
|           return acc; | ||||
|         }, {}); | ||||
|         this.pensByBatch = pensByBatch; // 存储起来,以便 onDestBatchChange 使用 | ||||
|  | ||||
|       } catch (error) { | ||||
|         console.error("Error fetching batches or pens:", error); | ||||
|         this.$message.error("获取猪群或猪栏信息失败"); | ||||
|       } | ||||
|     }, | ||||
|     onDestBatchChange(batchId) { | ||||
|       this.form.toPenID = null; // 重置目标猪栏选择 | ||||
|       // 允许选择目标批次下的所有猪栏,包括已满的 | ||||
|       this.destinationPens = (this.pensByBatch[batchId] || []); | ||||
|     }, | ||||
|     validateQuantity(rule, value, callback) { | ||||
|       if (value > this.maxTransferQuantity) { | ||||
|         callback(new Error(`调栏数量不能超过源猪栏存栏数量 (${this.maxTransferQuantity})`)); | ||||
|       } else { | ||||
|         callback(); | ||||
|       } | ||||
|     }, | ||||
|     handleConfirm() { | ||||
|       this.$refs.form.validate(async valid => { | ||||
|         if (valid) { | ||||
|           try { | ||||
|             await transferPigsAcrossBatches(this.batch.id, { | ||||
|               fromPenID: this.form.fromPenID, | ||||
|               destBatchID: this.form.destBatchID, | ||||
|               toPenID: this.form.toPenID, | ||||
|               quantity: this.form.quantity, | ||||
|               remarks: this.form.remarks | ||||
|             }); | ||||
|             this.$message.success('跨群调栏成功'); | ||||
|             this.$emit('success'); | ||||
|             this.$emit('update:visible', false); | ||||
|           } catch (error) { | ||||
|             console.error('Error transferring pigs across batches:', error); | ||||
|             this.$message.error('跨群调栏失败: ' + (error.response?.data?.message || error.message || '未知错误')); | ||||
|           } | ||||
|         } | ||||
|       }); | ||||
|     }, | ||||
|     resetForm() { | ||||
|       this.$refs.form?.resetFields(); | ||||
|       this.form.fromPenID = null; | ||||
|       this.form.destBatchID = null; | ||||
|       this.form.toPenID = null; | ||||
|       this.form.quantity = 1; | ||||
|       this.form.remarks = ''; | ||||
|       this.sourcePens = []; | ||||
|       this.availableBatches = []; | ||||
|       this.destinationPens = []; | ||||
|       this.maxTransferQuantity = 1; | ||||
|     } | ||||
|   } | ||||
| }; | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| </style> | ||||
							
								
								
									
										161
									
								
								src/components/TransferPigsModal.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								src/components/TransferPigsModal.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,161 @@ | ||||
| <template> | ||||
|   <el-dialog | ||||
|     title="群内调栏" | ||||
|     :model-value="visible" | ||||
|     @update:model-value="$emit('update:visible', $event)" | ||||
|     width="500px" | ||||
|     :before-close="handleClose" | ||||
|   > | ||||
|     <el-form ref="formRef" :model="form" :rules="rules" label-width="100px"> | ||||
|       <el-form-item label="调出猪栏" prop="fromPenID"> | ||||
|         <el-select v-model="form.fromPenID" placeholder="请选择调出猪栏" style="width: 100%;" @change="handleFromPenChange"> | ||||
|           <el-option | ||||
|             v-for="pen in sourcePens" | ||||
|             :key="pen.id" | ||||
|             :label="`${pen.pen_number} (存栏: ${pen.current_pig_count})`" | ||||
|             :value="pen.id" | ||||
|             :disabled="pen.current_pig_count === 0" | ||||
|           /> | ||||
|         </el-select> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="调入猪栏" prop="toPenID"> | ||||
|         <el-select v-model="form.toPenID" placeholder="请选择调入猪栏" style="width: 100%;" :disabled="!form.fromPenID"> | ||||
|           <el-option | ||||
|             v-for="pen in destinationPens" | ||||
|             :key="pen.id" | ||||
|             :label="`${pen.pen_number} (存栏: ${pen.current_pig_count})`" | ||||
|             :value="pen.id" | ||||
|           /> | ||||
|         </el-select> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="调栏数量" prop="quantity"> | ||||
|         <el-input-number | ||||
|           v-model="form.quantity" | ||||
|           :min="1" | ||||
|           :max="maxQuantity" | ||||
|           :disabled="!form.fromPenID" | ||||
|           placeholder="请输入数量" | ||||
|           style="width: 100%;" | ||||
|         /> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="备注" prop="remarks"> | ||||
|         <el-input v-model="form.remarks" type="textarea" placeholder="请输入备注" /> | ||||
|       </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 { transferPigsWithinBatch } from '@/api/pigBatch.js'; | ||||
|  | ||||
| export default { | ||||
|   name: 'TransferPigsModal', | ||||
|   props: { | ||||
|     visible: { | ||||
|       type: Boolean, | ||||
|       default: false, | ||||
|     }, | ||||
|     batch: { | ||||
|       type: Object, | ||||
|       required: true, | ||||
|     }, | ||||
|   }, | ||||
|   emits: ['update:visible', 'success'], | ||||
|   data() { | ||||
|     return { | ||||
|       loading: false, | ||||
|       form: { | ||||
|         fromPenID: null, | ||||
|         toPenID: null, | ||||
|         quantity: 1, | ||||
|         remarks: '', | ||||
|       }, | ||||
|       rules: { | ||||
|         fromPenID: [{ required: true, message: '请选择调出猪栏', trigger: 'change' }], | ||||
|         toPenID: [{ required: true, message: '请选择调入猪栏', trigger: 'change' }], | ||||
|         quantity: [{ required: true, message: '请输入调栏数量', trigger: 'blur' }], | ||||
|       }, | ||||
|     }; | ||||
|   }, | ||||
|   computed: { | ||||
|     sourcePens() { | ||||
|       return this.batch.pens || []; | ||||
|     }, | ||||
|     destinationPens() { | ||||
|       if (!this.form.fromPenID) return []; | ||||
|       return this.batch.pens.filter(pen => pen.id !== this.form.fromPenID); | ||||
|     }, | ||||
|     maxQuantity() { | ||||
|       if (!this.form.fromPenID) return 1; | ||||
|       const selectedPen = this.sourcePens.find(p => p.id === this.form.fromPenID); | ||||
|       return selectedPen ? selectedPen.current_pig_count : 1; | ||||
|     }, | ||||
|   }, | ||||
|   watch: { | ||||
|     visible(newVal) { | ||||
|       if (newVal) { | ||||
|         this.resetForm(); | ||||
|       } | ||||
|     }, | ||||
|   }, | ||||
|   methods: { | ||||
|     handleFromPenChange(penId) { | ||||
|       this.form.toPenID = null; | ||||
|       this.form.quantity = 1; | ||||
|       const selectedPen = this.sourcePens.find(p => p.id === penId); | ||||
|       if (selectedPen && selectedPen.current_pig_count === 0) { | ||||
|         this.$message.warning('该猪栏没有猪,无法调出。'); | ||||
|         this.form.fromPenID = null; | ||||
|       } | ||||
|     }, | ||||
|     handleSubmit() { | ||||
|       this.$refs.formRef.validate(async (valid) => { | ||||
|         if (valid) { | ||||
|           if (this.form.quantity > this.maxQuantity) { | ||||
|             this.$message.error('调栏数量不能超过当前存栏量'); | ||||
|             return; | ||||
|           } | ||||
|           this.loading = true; | ||||
|           try { | ||||
|             await transferPigsWithinBatch(this.batch.id, this.form); | ||||
|             this.$message.success('调栏成功'); | ||||
|             this.$emit('success'); | ||||
|             this.handleClose(); | ||||
|           } catch (error) { | ||||
|             this.$message.error('调栏失败: ' + (error.response?.data?.message || error.message)); | ||||
|             console.error('Failed to transfer pigs:', error); | ||||
|           } finally { | ||||
|             this.loading = false; | ||||
|           } | ||||
|         } | ||||
|       }); | ||||
|     }, | ||||
|     handleClose() { | ||||
|       this.$emit('update:visible', false); | ||||
|     }, | ||||
|     resetForm() { | ||||
|       if (this.$refs.formRef) { | ||||
|         this.$refs.formRef.resetFields(); | ||||
|       } | ||||
|       this.form = { | ||||
|         fromPenID: null, | ||||
|         toPenID: null, | ||||
|         quantity: 1, | ||||
|         remarks: '', | ||||
|       }; | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| .dialog-footer { | ||||
|   text-align: right; | ||||
| } | ||||
| </style> | ||||
| @@ -77,6 +77,10 @@ | ||||
|             <el-icon><FirstAidKit /></el-icon> | ||||
|             <template #title>用药记录</template> | ||||
|           </el-menu-item> | ||||
|           <el-menu-item index="/monitor/notifications"> | ||||
|             <el-icon><Bell /></el-icon> | ||||
|             <template #title>通知记录</template> | ||||
|           </el-menu-item> | ||||
|           <el-menu-item index="/monitor/pending-collections"> | ||||
|             <el-icon><Clock /></el-icon> | ||||
|             <template #title>待采集请求</template> | ||||
| @@ -183,14 +187,14 @@ import { ref, computed, onMounted, onUnmounted } from 'vue'; | ||||
| import { useRoute, useRouter } from 'vue-router'; | ||||
| import {  | ||||
|   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, Management | ||||
|   FirstAidKit, Clock, Files, ShoppingCart, SoldOut, Warning, Switch, List, Shop, Coin, DataLine, Finished, User, ScaleToOriginal, OfficeBuilding, Management, Bell | ||||
| } from '@element-plus/icons-vue'; | ||||
|  | ||||
| export default { | ||||
|   name: 'MainLayout', | ||||
|   components: { | ||||
|     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, Management | ||||
|     FirstAidKit, Clock, Files, ShoppingCart, SoldOut, Warning, Switch, List, Shop, Coin, DataLine, Finished, User, ScaleToOriginal, OfficeBuilding, Management, Bell | ||||
|   }, | ||||
|   setup() { | ||||
|     const route = useRoute(); | ||||
| @@ -233,6 +237,7 @@ export default { | ||||
|         '/monitor/device-command-logs': '设备命令日志', | ||||
|         '/monitor/feed-usage-records': '饲料使用记录', | ||||
|         '/monitor/medication-logs': '用药记录', | ||||
|         '/monitor/notifications': '通知记录', | ||||
|         '/monitor/pending-collections': '待采集请求', | ||||
|         '/monitor/pig-batch-logs': '猪批次日志', | ||||
|         '/monitor/pig-purchases': '猪只采购记录', | ||||
|   | ||||
| @@ -17,6 +17,7 @@ import PigBatchManagementView from './views/pms/PigBatchManagementView.vue'; // | ||||
| import DeviceCommandLogView from './views/monitor/DeviceCommandLogView.vue'; | ||||
| import FeedUsageRecordsView from './views/monitor/FeedUsageRecordsView.vue'; | ||||
| import MedicationLogsView from './views/monitor/MedicationLogsView.vue'; | ||||
| import NotificationLogView from './views/monitor/NotificationLogView.vue'; | ||||
| import PendingCollectionsView from './views/monitor/PendingCollectionsView.vue'; | ||||
| import PigBatchLogsView from './views/monitor/PigBatchLogsView.vue'; | ||||
| import PigPurchasesView from './views/monitor/PigPurchasesView.vue'; | ||||
| @@ -50,6 +51,7 @@ const routes = [ | ||||
|     {path: '/monitor/device-command-logs', component: DeviceCommandLogView, meta: {requiresAuth: true}}, | ||||
|     {path: '/monitor/feed-usage-records', component: FeedUsageRecordsView, meta: {requiresAuth: true}}, | ||||
|     {path: '/monitor/medication-logs', component: MedicationLogsView, meta: {requiresAuth: true}}, | ||||
|     {path: '/monitor/notifications', component: NotificationLogView, meta: {requiresAuth: true}}, | ||||
|     {path: '/monitor/pending-collections', component: PendingCollectionsView, meta: {requiresAuth: true}}, | ||||
|     {path: '/monitor/pig-batch-logs', component: PigBatchLogsView, meta: {requiresAuth: true}}, | ||||
|     {path: '/monitor/pig-purchases', component: PigPurchasesView, meta: {requiresAuth: true}}, | ||||
|   | ||||
| @@ -53,9 +53,7 @@ http.interceptors.response.use( | ||||
|       if (error.response.status === 401) { | ||||
|         // 清除token并重定向到登录页 | ||||
|         localStorage.removeItem('jwt_token'); | ||||
|         // 这里需要访问router,但http.js是纯工具文件,不应直接依赖Vue Router实例 | ||||
|         // 可以在main.js的全局错误处理或组件中处理401错误 | ||||
|         // 例如:window.location.href = '/login'; | ||||
|         window.location.href = '/login'; | ||||
|       } | ||||
|     } else if (error.request) { | ||||
|       // 请求发出但没有收到响应 | ||||
|   | ||||
							
								
								
									
										111
									
								
								src/views/monitor/NotificationLogView.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								src/views/monitor/NotificationLogView.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,111 @@ | ||||
| <template> | ||||
|   <div class="notification-log-view"> | ||||
|     <GenericMonitorList | ||||
|       :fetchData="fetchNotifications" | ||||
|       :columnsConfig="notificationLogColumns" | ||||
|     /> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script setup> | ||||
| import GenericMonitorList from '../../components/GenericMonitorList.vue'; | ||||
| import { getNotifications, ZapcoreLevel } from '../../api/monitor.js'; | ||||
| import { formatRFC3339 } from '../../utils/format.js'; | ||||
|  | ||||
| // 适配通用组件的 fetchData prop | ||||
| const fetchNotifications = async (params) => { | ||||
|   return await getNotifications(params); | ||||
| }; | ||||
|  | ||||
| // 定义表格的列 | ||||
| const notificationLogColumns = [ | ||||
|   { | ||||
|     title: '用户ID', | ||||
|     dataIndex: 'user_id', | ||||
|     key: 'user_id', | ||||
|     sorter: true, | ||||
|     filterType: 'number', | ||||
|     minWidth: 100, | ||||
|   }, | ||||
|   { | ||||
|     title: '通知渠道', | ||||
|     dataIndex: 'notifier_type', | ||||
|     key: 'notifier_type', | ||||
|     filterType: 'select', | ||||
|     filterOptions: [ | ||||
|       { value: '邮件', text: '邮件' }, | ||||
|       { value: '企业微信', text: '企业微信' }, | ||||
|       { value: '飞书', text: '飞书' }, | ||||
|       { value: '日志', text: '日志' }, | ||||
|     ], | ||||
|     minWidth: 120, | ||||
|   }, | ||||
|   { | ||||
|     title: '目标地址', | ||||
|     dataIndex: 'to_address', | ||||
|     key: 'to_address', | ||||
|     minWidth: 200, | ||||
|   }, | ||||
|   { | ||||
|     title: '标题', | ||||
|     dataIndex: 'title', | ||||
|     key: 'title', | ||||
|     minWidth: 200, | ||||
|   }, | ||||
|   { | ||||
|     title: '消息', | ||||
|     dataIndex: 'message', | ||||
|     key: 'message', | ||||
|     minWidth: 300, | ||||
|   }, | ||||
|   { | ||||
|     title: '日志级别', | ||||
|     dataIndex: 'level', | ||||
|     key: 'level', | ||||
|     sorter: true, | ||||
|     filterType: 'select', | ||||
|     filterOptions: Object.entries(ZapcoreLevel).map(([text, value]) => ({ value, text })), | ||||
|     minWidth: 110, | ||||
|   }, | ||||
|   { | ||||
|     title: '告警时间', | ||||
|     dataIndex: 'alarm_timestamp', | ||||
|     key: 'alarm_timestamp', | ||||
|     sorter: true, | ||||
|     formatter: (row, column, cellValue) => formatRFC3339(cellValue), | ||||
|     minWidth: 180, | ||||
|   }, | ||||
|   { | ||||
|     title: '状态', | ||||
|     dataIndex: 'status', | ||||
|     key: 'status', | ||||
|     filterType: 'select', | ||||
|     filterOptions: [ | ||||
|       { value: '发送成功', text: '发送成功' }, | ||||
|       { value: '发送失败', text: '发送失败' }, | ||||
|       { value: '已跳过', text: '已跳过' }, | ||||
|     ], | ||||
|     minWidth: 120, | ||||
|   }, | ||||
|   { | ||||
|     title: '错误信息', | ||||
|     dataIndex: 'error_message', | ||||
|     key: 'error_message', | ||||
|     minWidth: 200, | ||||
|   }, | ||||
|   { | ||||
|     title: '创建时间', | ||||
|     dataIndex: 'created_at', | ||||
|     key: 'created_at', | ||||
|     sorter: true, | ||||
|     formatter: (row, column, cellValue) => formatRFC3339(cellValue), | ||||
|     minWidth: 180, | ||||
|   }, | ||||
| ]; | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| .notification-log-view { | ||||
|   /* 视图容器样式 */ | ||||
| } | ||||
| </style> | ||||
| @@ -34,6 +34,12 @@ const planExecutionLogColumns = [ | ||||
|     sorter: true, | ||||
|     minWidth: 120, | ||||
|   }, | ||||
|   { | ||||
|     title: '计划名称', | ||||
|     dataIndex: 'plan_name', | ||||
|     key: 'plan_name', | ||||
|     minWidth: 150, | ||||
|   }, | ||||
|   { | ||||
|     title: '状态', | ||||
|     dataIndex: 'status', | ||||
|   | ||||
| @@ -9,7 +9,15 @@ | ||||
|               <el-icon :size="20"><Refresh /></el-icon> | ||||
|             </el-button> | ||||
|           </div> | ||||
|           <el-button type="primary" @click="addPlan">添加计划</el-button> | ||||
|            | ||||
|           <div class="filter-and-add"> | ||||
|             <el-select v-model="planTypeFilter" placeholder="选择计划类型" @change="loadPlans" style="width: 150px; margin-right: 10px;"> | ||||
|               <el-option label="所有任务" value="所有任务"></el-option> | ||||
|               <el-option label="自定义任务" value="自定义任务"></el-option> | ||||
|               <el-option label="系统任务" value="系统任务"></el-option> | ||||
|             </el-select> | ||||
|             <el-button type="primary" @click="addPlan">添加计划</el-button> | ||||
|           </div> | ||||
|         </div> | ||||
|       </template> | ||||
|        | ||||
| @@ -65,17 +73,18 @@ | ||||
|         </el-table-column> | ||||
|         <el-table-column label="操作" width="280"> | ||||
|           <template #default="scope"> | ||||
|             <el-button size="small" @click="editPlan(scope.row)">编辑</el-button> | ||||
|             <el-button size="small" @click="editPlan(scope.row)" :disabled="scope.row.plan_type === '系统任务'">编辑</el-button> | ||||
|             <el-button size="small" @click="showDetails(scope.row)">详情</el-button> | ||||
|             <el-button  | ||||
|               size="small"  | ||||
|               :type="scope.row.status === 1 ? 'warning' : 'primary'" | ||||
|               @click="scope.row.status === 1 ? stopPlan(scope.row) : startPlan(scope.row)" | ||||
|               :loading="stoppingPlanId === scope.row.id || startingPlanId === scope.row.id" | ||||
|               :disabled="scope.row.plan_type === '系统任务'" | ||||
|             > | ||||
|               {{ scope.row.status === 1 ? '停止' : '启动' }} | ||||
|             </el-button> | ||||
|             <el-button size="small" type="danger" @click="deletePlan(scope.row)">删除</el-button> | ||||
|             <el-button size="small" type="danger" @click="deletePlan(scope.row)" :disabled="scope.row.plan_type === '系统任务'">删除</el-button> | ||||
|           </template> | ||||
|         </el-table-column> | ||||
|       </el-table> | ||||
| @@ -145,7 +154,8 @@ export default { | ||||
|       }, | ||||
|       selectedPlanIdForDetails: null, // 当前要查看详情的计划ID | ||||
|       startingPlanId: null, | ||||
|       stoppingPlanId: null | ||||
|       stoppingPlanId: null, | ||||
|       planTypeFilter: '自定义任务', // 新增:计划类型筛选,默认自定义任务 | ||||
|     }; | ||||
|   }, | ||||
|   async mounted() { | ||||
| @@ -158,7 +168,7 @@ export default { | ||||
|       this.error = null; | ||||
|        | ||||
|       try { | ||||
|         const response = await apiClient.plans.getPlans(); // 更正此处 | ||||
|         const response = await apiClient.plans.getPlans({ planType: this.planTypeFilter }); // 传递 planTypeFilter | ||||
|         let fetchedPlans = response.data?.plans || []; | ||||
|         // Default sort by ID ascending | ||||
|         fetchedPlans.sort((a, b) => a.id - b.id); | ||||
| @@ -326,6 +336,10 @@ export default { | ||||
|     handlePlanCancel() { | ||||
|       this.dialogVisible = false; | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     // 监听 planTypeFilter 变化,重新加载计划列表 | ||||
|     planTypeFilter: 'loadPlans' | ||||
|   } | ||||
| }; | ||||
| </script> | ||||
| @@ -388,6 +402,11 @@ export default { | ||||
|   height: 20px; | ||||
| } | ||||
|  | ||||
| .filter-and-add { | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
| } | ||||
|  | ||||
| @media (max-width: 768px) { | ||||
|   .plan-list { | ||||
|     padding: 10px; | ||||
| @@ -397,4 +416,4 @@ export default { | ||||
|     gap: 15px; | ||||
|   } | ||||
| } | ||||
| </style> | ||||
| </style> | ||||
|   | ||||
| @@ -42,6 +42,8 @@ | ||||
|           @modify-pig-count-pen="handleModifyPigCountPen" | ||||
|           @remove-pen="handleRemovePen" | ||||
|           @assign-pen-to-batch="handleAssignPenToBatch" | ||||
|           @transfer-pigs="handleTransferPigs" | ||||
|           @transfer-pigs-across-batches="handleTransferPigsAcrossBatches" | ||||
|           @reload-data="loadData" | ||||
|         /> | ||||
|         <el-empty v-else description="暂无数据" /> | ||||
| @@ -66,6 +68,21 @@ | ||||
|       @success="handlePenSuccess" | ||||
|       @cancel="penDialogVisible = false" | ||||
|     /> | ||||
|  | ||||
|     <!-- 群内调栏对话框 --> | ||||
|     <TransferPigsModal | ||||
|       v-if="transferDialogVisible" | ||||
|       v-model:visible="transferDialogVisible" | ||||
|       :batch="currentBatchForTransfer" | ||||
|       @success="handleTransferSuccess" | ||||
|     /> | ||||
|  | ||||
|     <!-- 跨群调栏对话框 --> | ||||
|     <TransferPigsAcrossBatchesModal | ||||
|       v-model:visible="transferAcrossBatchesDialogVisible" | ||||
|       :batch="currentBatchForTransferAcrossBatches" | ||||
|       @success="handleTransferAcrossBatchesSuccess" | ||||
|     /> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| @@ -76,6 +93,8 @@ import { getPigHouses } from '@/api/pigHouse.js'; | ||||
| import PigBatchList from '@/components/PigBatchList.vue'; | ||||
| import PigBatchForm from '@/components/PigBatchForm.vue'; | ||||
| import PenForm from '@/components/PenForm.vue'; | ||||
| import TransferPigsModal from '@/components/TransferPigsModal.vue'; | ||||
| import TransferPigsAcrossBatchesModal from '@/components/TransferPigsAcrossBatchesModal.vue'; | ||||
| import { Refresh } from '@element-plus/icons-vue'; | ||||
|  | ||||
| export default { | ||||
| @@ -84,6 +103,8 @@ export default { | ||||
|     PigBatchList, | ||||
|     PigBatchForm, | ||||
|     PenForm, | ||||
|     TransferPigsModal, | ||||
|     TransferPigsAcrossBatchesModal, | ||||
|     Refresh | ||||
|   }, | ||||
|   data() { | ||||
| @@ -99,6 +120,12 @@ export default { | ||||
|       penDialogVisible: false, | ||||
|       isEditPen: false, | ||||
|       currentPen: {}, | ||||
|       // 调栏模态框状态 | ||||
|       transferDialogVisible: false, | ||||
|       currentBatchForTransfer: {}, | ||||
|       // 跨群调栏模态框状态 | ||||
|       transferAcrossBatchesDialogVisible: false, | ||||
|       currentBatchForTransferAcrossBatches: {}, | ||||
|       // 辅助映射 | ||||
|       houseMap: new Map(), // 用于猪栏显示猪舍名称 | ||||
|     }; | ||||
| @@ -208,6 +235,11 @@ export default { | ||||
|       } | ||||
|     }, | ||||
|     // --- 猪栏操作 (在猪群管理中) --- | ||||
|     handleAddPen(house) { | ||||
|       this.currentPen = { ...house }; // 修正:这里应该是传入house对象,而不是pen对象 | ||||
|       this.isEditPen = false; | ||||
|       this.penDialogVisible = true; | ||||
|     }, | ||||
|     handleModifyPigCountPen(pen) { | ||||
|       this.currentPen = { ...pen }; | ||||
|       this.isEditPen = true; | ||||
| @@ -264,6 +296,25 @@ export default { | ||||
|         this.$message.error('分配猪栏失败: ' + (error.response?.data?.message || error.message || '未知错误')); | ||||
|         console.error('Failed to assign pen to batch:', error); | ||||
|       } | ||||
|     }, | ||||
|     // --- 调栏操作 --- | ||||
|     handleTransferPigs(batch) { | ||||
|       this.currentBatchForTransfer = batch; | ||||
|       this.transferDialogVisible = true; | ||||
|     }, | ||||
|     handleTransferSuccess() { | ||||
|       this.transferDialogVisible = false; | ||||
|       this.loadData(); // 重新加载数据以反映变化 | ||||
|     }, | ||||
|     handleTransferPigsAcrossBatches(batch) { | ||||
|       console.log('handleTransferPigsAcrossBatches called with batch:', batch); | ||||
|       this.currentBatchForTransferAcrossBatches = batch; | ||||
|       this.transferAcrossBatchesDialogVisible = true; | ||||
|       console.log('transferAcrossBatchesDialogVisible set to:', this.transferAcrossBatchesDialogVisible); | ||||
|     }, | ||||
|     handleTransferAcrossBatchesSuccess() { | ||||
|       this.transferAcrossBatchesDialogVisible = false; | ||||
|       this.loadData(); // 重新加载数据以反映变化 | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user