feat(同步服务): 添加实例同步功能并优化更新逻辑
添加实例同步API接口和前端刷新按钮
优化实例同步服务中的更新逻辑,确保配置更新后实例能正确重启
修复输送线任务中条码读取方式,改为使用标准接口
添加相关状态文件用于跟踪同步和代理状态
前端添加刷新按钮调用同步API
后端同步服务现在会正确处理已存在实例的更新
修正条码读取方式以避免潜在的空字符问题
添加状态跟踪文件用于监控系统运行状态
| | |
| | | CommonConveyorLine conveyorLine = (CommonConveyorLine)device; |
| | | |
| | | DeviceProDTO? devicePro = conveyorLine.DeviceProDTOs.FirstOrDefault(x => x.DeviceProParamName == nameof(ConveyorLineDBNameNew.Barcode) && x.DeviceChildCode == sourceLineNo); |
| | | ConveyorLineTaskCommandNew command = conveyorLine.ReadCustomer<ConveyorLineTaskCommandNew>(sourceLineNo); // æµè¯ç¨ |
| | | //var barcode = conveyorLine.GetValue<ConveyorLineDBNameNew, string>(ConveyorLineDBNameNew.Barcode, sourceLineNo); |
| | | stock.SourcePalletNo = string.IsNullOrEmpty(command.Barcode.Replace("\0", "").ToString()) ? string.Empty : command.Barcode.Replace("\0", "").ToString(); |
| | | //ConveyorLineTaskCommandNew command = conveyorLine.ReadCustomer<ConveyorLineTaskCommandNew>(sourceLineNo); // æµè¯ç¨ |
| | | var barcode = conveyorLine.GetValue<ConveyorLineDBNameNew, string>(ConveyorLineDBNameNew.Barcode, sourceLineNo); |
| | | stock.SourcePalletNo = string.IsNullOrEmpty(barcode) ? string.Empty : barcode; |
| | | } |
| | | } |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | {"t":0,"agent":"ae21cf2","agent_type":"explore","event":"agent_start","parent_mode":"none"} |
| | | {"t":0,"agent":"ae21cf2","agent_type":"explore","event":"agent_stop","success":true,"duration_ms":129137} |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | { |
| | | "lastSentAt": "2026-04-15T07:13:52.473Z" |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | { |
| | | "updatedAt": "2026-04-15T06:44:32.119Z", |
| | | "missions": [ |
| | | { |
| | | "id": "session:584f6639-de9a-47e8-a9e4-f92fac6305be:none", |
| | | "source": "session", |
| | | "name": "none", |
| | | "objective": "Session mission", |
| | | "createdAt": "2026-04-15T06:42:22.982Z", |
| | | "updatedAt": "2026-04-15T06:44:32.119Z", |
| | | "status": "done", |
| | | "workerCount": 1, |
| | | "taskCounts": { |
| | | "total": 1, |
| | | "pending": 0, |
| | | "blocked": 0, |
| | | "inProgress": 0, |
| | | "completed": 1, |
| | | "failed": 0 |
| | | }, |
| | | "agents": [ |
| | | { |
| | | "name": "explore:ae21cf2", |
| | | "role": "explore", |
| | | "ownership": "ae21cf28e45f297ff", |
| | | "status": "done", |
| | | "currentStep": null, |
| | | "latestUpdate": "completed", |
| | | "completedSummary": null, |
| | | "updatedAt": "2026-04-15T06:44:32.119Z" |
| | | } |
| | | ], |
| | | "timeline": [ |
| | | { |
| | | "id": "session-start:ae21cf28e45f297ff:2026-04-15T06:42:22.982Z", |
| | | "at": "2026-04-15T06:42:22.982Z", |
| | | "kind": "update", |
| | | "agent": "explore:ae21cf2", |
| | | "detail": "started explore:ae21cf2", |
| | | "sourceKey": "session-start:ae21cf28e45f297ff" |
| | | }, |
| | | { |
| | | "id": "session-stop:ae21cf28e45f297ff:2026-04-15T06:44:32.119Z", |
| | | "at": "2026-04-15T06:44:32.119Z", |
| | | "kind": "completion", |
| | | "agent": "explore:ae21cf2", |
| | | "detail": "completed", |
| | | "sourceKey": "session-stop:ae21cf28e45f297ff" |
| | | } |
| | | ] |
| | | } |
| | | ] |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | { |
| | | "agents": [ |
| | | { |
| | | "agent_id": "ae21cf28e45f297ff", |
| | | "agent_type": "explore", |
| | | "started_at": "2026-04-15T06:42:22.982Z", |
| | | "parent_mode": "none", |
| | | "status": "completed", |
| | | "completed_at": "2026-04-15T06:44:32.119Z", |
| | | "duration_ms": 129137 |
| | | } |
| | | ], |
| | | "total_spawned": 1, |
| | | "total_completed": 1, |
| | | "total_failed": 0, |
| | | "last_updated": "2026-04-15T06:44:32.224Z" |
| | | } |
| | |
| | | // 4.4 åå»ºææ´æ°å®ä¾ |
| | | if (_instanceManager.InstanceExists(device.DeviceCode)) |
| | | { |
| | | _logger.LogInformation("å®ä¾ {DeviceCode} å·²åå¨ï¼è·³è¿å建", device.DeviceCode); |
| | | // å®ä¾å·²åå¨ï¼æ´æ°é
ç½® |
| | | var instance = _instanceManager.GetInstance(device.DeviceCode); |
| | | if (instance != null) |
| | | { |
| | | var oldState = instance.GetState(); |
| | | var wasRunning = oldState.Status == InstanceStatus.Running; |
| | | |
| | | // 忢æ§å®ä¾ |
| | | await _instanceManager.StopInstanceAsync(device.DeviceCode); |
| | | |
| | | // å 餿§å®ä¾ï¼ä¿çé
ç½®ï¼ |
| | | await _instanceManager.DeleteInstanceAsync(device.DeviceCode, deleteConfig: false); |
| | | |
| | | // éæ°å建å®ä¾ |
| | | var newInstance = await _instanceManager.CreateInstanceAsync(config); |
| | | if (wasRunning) |
| | | { |
| | | await _instanceManager.StartInstanceAsync(device.DeviceCode); |
| | | } |
| | | _logger.LogInformation("å·²æ´æ°å®ä¾ {DeviceCode} (端å£:{Port})", device.DeviceCode, device.DevicePort); |
| | | } |
| | | } |
| | | else |
| | | { |
| | |
| | | } |
| | | } |
| | | |
| | | // 鿰忥å®ä¾ï¼ä»æ°æ®åºéæ°è·åï¼ |
| | | export async function syncInstances(): Promise<{ message: string; lastSyncTime: string }> { |
| | | const response = await api.post<{ message: string; lastSyncTime: string }>('/Sync/SyncInstances') |
| | | return response.data |
| | | } |
| | | |
| | | // è·å䏿¬¡åæ¥æ¶é´ |
| | | export async function getLastSyncTime(): Promise<{ lastSyncTime: string | null }> { |
| | | const response = await api.get<{ lastSyncTime: string | null }>('/Sync/LastSyncTime') |
| | | return response.data |
| | | } |
| | | |
| | | // è·åæºæ¢°ææå¡ç«¯è¿è¡ç¶æï¼å
å«å¤å®ä¾åæ¥æ¶æ¶æ¯æ¥å¿ï¼ |
| | | export async function getRobotClientStatus(): Promise<RobotClientStatusResponse> { |
| | | const response = await api.get<RobotClientStatusResponse>('/RobotClients/status') |
| | |
| | | <p class="text-muted">管çåçæ§ S7 PLC 模æå¨å®ä¾</p> |
| | | </div> |
| | | <div class="header-right"> |
| | | <el-button type="default" @click="handleRefresh"> |
| | | <el-icon><Refresh /></el-icon> |
| | | éæ°è·åå®ä¾ |
| | | </el-button> |
| | | <el-button type="primary" class="create-btn" @click="$router.push('/create')"> |
| | | <el-icon><Plus /></el-icon> |
| | | å建å®ä¾ |
| | |
| | | import { onMounted, onUnmounted, ref } from 'vue' |
| | | import { storeToRefs } from 'pinia' |
| | | import { useInstancesStore } from '../stores/instances' |
| | | import api from '../api' |
| | | import { syncInstances } from '../api' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { |
| | | Cpu, |
| | | Plus, |
| | | Loading, |
| | | Refresh, |
| | | User, |
| | | VideoPause, |
| | | VideoPlay, |
| | |
| | | store.stopAutoRefresh() |
| | | }) |
| | | |
| | | async function handleRefresh() { |
| | | try { |
| | | await syncInstances() |
| | | await store.loadInstances() |
| | | ElMessage.success('已鿰è·åå®ä¾å表') |
| | | } catch (err) { |
| | | console.error('åæ¥å¤±è´¥:', err) |
| | | ElMessage.error('åæ¥å¤±è´¥ï¼è¯·æ¥çæ§å¶å°') |
| | | } |
| | | } |
| | | |
| | | async function handleStart(id: string) { |
| | | try { |
| | | await ElMessageBox.confirm(`ç¡®å®è¦å¯å¨å®ä¾ "${id}" åï¼`, '确认', { |