var app = new Vue({
|
el: '#app',
|
data: {
|
// Sidebar Data
|
dateTreeData: [],
|
defaultProps: {
|
children: 'children',
|
label: 'title'
|
},
|
defaultExpandedKeys: [],
|
|
// Search & List Data
|
searchForm: {
|
day: '',
|
type: '',
|
deviceNo: '',
|
offset: 0,
|
limit: 200
|
},
|
deviceList: [],
|
loading: false,
|
|
// Enums
|
deviceEnums: {},
|
|
// Visualization State
|
visualizationVisible: false,
|
visDeviceType: '',
|
visDeviceNo: '',
|
logs: [],
|
isPlaying: false,
|
playbackSpeed: 1,
|
sliderValue: 0,
|
startTime: 0,
|
endTime: 0,
|
timer: null,
|
currentTime: 0,
|
lastTick: 0,
|
|
// Jump Time
|
jumpVisible: false,
|
jumpTime: null,
|
seekTargetTime: 0, // Target time we are trying to reach via loading
|
seekingOffset: false,
|
needToSeekOffset: false,
|
|
// Download State
|
downloadDialogVisible: false,
|
buildProgress: 0,
|
receiveProgress: 0,
|
downloadTimer: null
|
},
|
computed: {
|
filteredDeviceList() {
|
// Currently just returns the full list loaded for the day
|
return this.deviceList;
|
},
|
visualizationTitle() {
|
return `日志可视化 - ${this.visDeviceType} ${this.visDeviceNo} (${this.searchForm.day})`;
|
},
|
maxSliderValue() {
|
return Math.max(0, this.endTime - this.startTime);
|
},
|
currentTimeStr() {
|
if (!this.currentTime) return '';
|
var d = new Date(this.currentTime);
|
var Y = d.getFullYear() + '-';
|
var M = (d.getMonth() + 1 < 10 ? '0' + (d.getMonth() + 1) : d.getMonth() + 1) + '-';
|
var D = (d.getDate() < 10 ? '0' + d.getDate() : d.getDate()) + ' ';
|
var h = d.getHours().toString().padStart(2, '0');
|
var m = d.getMinutes().toString().padStart(2, '0');
|
var s = d.getSeconds().toString().padStart(2, '0');
|
var ms = d.getMilliseconds().toString().padStart(3, '0');
|
return Y + M + D + h + ':' + m + ':' + s + '.' + ms;
|
},
|
canDownload() {
|
return this.searchForm.day && this.searchForm.type && this.searchForm.deviceNo;
|
}
|
},
|
created() {
|
this.loadDeviceEnums();
|
this.loadDateTree();
|
},
|
methods: {
|
// --- Initialization ---
|
loadDeviceEnums() {
|
let that = this;
|
$.ajax({
|
url: baseUrl + "/deviceLog/enums/auth",
|
headers: {'token': localStorage.getItem('token')},
|
method: 'GET',
|
success: function (res) {
|
if (res.code === 200) {
|
that.deviceEnums = res.data || {};
|
}
|
}
|
});
|
},
|
|
// --- Date Tree ---
|
loadDateTree() {
|
let that = this;
|
$.ajax({
|
url: baseUrl + "/deviceLog/dates/auth",
|
headers: {'token': localStorage.getItem('token')},
|
method: 'GET',
|
success: function (res) {
|
if (res.code === 200) {
|
that.dateTreeData = that.buildMonthTree(res.data);
|
// Auto-expand current year/month if needed, or just root
|
if (that.dateTreeData.length > 0) {
|
that.defaultExpandedKeys = [that.dateTreeData[0].id];
|
}
|
} else if (res.code === 403) {
|
top.location.href = baseUrl + "/";
|
} else {
|
that.$message.error(res.msg || '加载日期失败');
|
}
|
}
|
});
|
},
|
buildMonthTree(data) {
|
var monthMap = {};
|
(data || []).forEach(function (y) {
|
(y.children || []).forEach(function (m) {
|
var month = m.title;
|
var arr = monthMap[month] || (monthMap[month] = []);
|
(m.children || []).forEach(function (d) {
|
arr.push({ title: d.title + '日', id: d.id, day: d.id });
|
});
|
});
|
});
|
var result = [];
|
Object.keys(monthMap).sort().reverse().forEach(function (month) {
|
result.push({ title: month + '月', id: month, children: monthMap[month] });
|
});
|
return result;
|
},
|
handleNodeClick(data) {
|
if (data.day && data.day.length === 8) {
|
this.searchForm.day = data.day;
|
this.loadDevices(data.day);
|
}
|
},
|
|
// --- Device List ---
|
loadDevices(day) {
|
this.loading = true;
|
this.deviceList = [];
|
let that = this;
|
$.ajax({
|
url: baseUrl + "/deviceLog/day/" + day + "/devices/auth",
|
headers: {'token': localStorage.getItem('token')},
|
method: 'GET',
|
success: function (res) {
|
that.loading = false;
|
if (res.code === 200) {
|
that.deviceList = res.data || [];
|
} else if (res.code === 403) {
|
top.location.href = baseUrl + "/";
|
} else {
|
that.$message.error(res.msg || '加载设备失败');
|
}
|
},
|
error: function() {
|
that.loading = false;
|
that.$message.error('请求失败');
|
}
|
});
|
},
|
|
// --- Download ---
|
handleBatchDownload() {
|
this.doDownload(this.searchForm.day, this.searchForm.type, this.searchForm.deviceNo);
|
},
|
downloadLog(deviceNo, type) {
|
this.doDownload(this.searchForm.day, type, deviceNo);
|
},
|
doDownload(day, type, deviceNo) {
|
if (!day) return this.$message.warning('请先选择日期');
|
if (!type) return this.$message.warning('请选择设备类型');
|
if (!deviceNo) return this.$message.warning('请输入设备编号');
|
|
let offset = this.searchForm.offset || 0;
|
let limit = this.searchForm.limit || 200;
|
let that = this;
|
|
$.ajax({
|
url: baseUrl + "/deviceLog/download/init/auth",
|
headers: {'token': localStorage.getItem('token')},
|
method: 'POST',
|
data: JSON.stringify({ day: day, type: type, deviceNo: deviceNo, offset: offset, limit: limit }),
|
dataType:'json',
|
contentType:'application/json;charset=UTF-8',
|
success: function (res) {
|
if (res.code !== 200) {
|
that.$message.error(res.msg || '初始化失败');
|
return;
|
}
|
var pid = res.data.progressId;
|
that.startDownloadProgress(pid);
|
that.performDownloadRequest(day, type, deviceNo, offset, limit, pid);
|
}
|
});
|
},
|
startDownloadProgress(pid) {
|
this.downloadDialogVisible = true;
|
this.buildProgress = 0;
|
this.receiveProgress = 0;
|
let that = this;
|
this.downloadTimer = setInterval(function(){
|
$.ajax({
|
url: baseUrl + '/deviceLog/download/progress/auth',
|
headers: {'token': localStorage.getItem('token')},
|
method: 'GET',
|
data: { id: pid },
|
success: function (p) {
|
if (p.code === 200) {
|
var percent = p.data.percent || 0;
|
that.buildProgress = percent;
|
}
|
}
|
});
|
}, 500);
|
},
|
performDownloadRequest(day, type, deviceNo, offset, limit, pid) {
|
let that = this;
|
$.ajax({
|
url: baseUrl + "/deviceLog/day/" + day + "/download/auth?type=" + encodeURIComponent(type) + "&deviceNo=" + encodeURIComponent(deviceNo) + "&offset=" + offset + "&limit=" + limit + "&progressId=" + encodeURIComponent(pid),
|
headers: {'token': localStorage.getItem('token')},
|
method: 'GET',
|
xhrFields: { responseType: 'blob' },
|
xhr: function(){
|
var xhr = new window.XMLHttpRequest();
|
xhr.onprogress = function(e){
|
if (e.lengthComputable && e.total > 0) {
|
var percent = Math.floor(e.loaded / e.total * 100);
|
that.receiveProgress = percent;
|
}
|
};
|
return xhr;
|
},
|
success: function (data, status, xhr) {
|
var disposition = xhr.getResponseHeader('Content-Disposition') || '';
|
var filename = type + '_' + deviceNo + '_' + day + '.zip';
|
var match = /filename=(.+)/.exec(disposition);
|
if (match && match[1]) {
|
filename = decodeURIComponent(match[1]);
|
}
|
that.buildProgress = 100;
|
that.receiveProgress = 100;
|
|
var blob = new Blob([data], {type: 'application/zip'});
|
var link = document.createElement('a');
|
var url = window.URL.createObjectURL(blob);
|
link.href = url;
|
link.download = filename;
|
document.body.appendChild(link);
|
link.click();
|
document.body.removeChild(link);
|
window.URL.revokeObjectURL(url);
|
|
clearInterval(that.downloadTimer);
|
setTimeout(() => { that.downloadDialogVisible = false; }, 1000);
|
},
|
error: function () {
|
clearInterval(that.downloadTimer);
|
that.downloadDialogVisible = false;
|
that.$message.error('下载失败或未找到日志');
|
}
|
});
|
},
|
|
// --- Visualization ---
|
visualizeLog(deviceNo, type) {
|
this.visDeviceType = type;
|
this.visDeviceNo = deviceNo;
|
this.visOffset = this.searchForm.offset || 0;
|
// Optimization: Load fewer files per request to speed up response
|
// searchForm.limit might be large (for download), so we force a small batch for visualization
|
this.visLimit = 2;
|
|
this.logs = [];
|
this.hasMoreLogs = true;
|
this.loadingLogs = false;
|
this.startTime = 0;
|
this.endTime = 0;
|
this.currentTime = 0;
|
this.sliderValue = 0;
|
this.isPlaying = false;
|
this.playbackSpeed = 1;
|
|
this.visualizationVisible = true;
|
this.loadMoreLogs();
|
},
|
loadMoreLogs() {
|
if (this.loadingLogs || !this.hasMoreLogs) return;
|
this.loadingLogs = true;
|
|
// Use Vue loading service if available, or element UI loading
|
let loadingInstance = null;
|
|
// Show loading if explicitly seeking (jumping far ahead) or normal load
|
if (this.seekTargetTime > 0) {
|
if (this.$loading) {
|
loadingInstance = this.$loading({
|
target: '.vis-container',
|
lock: true,
|
text: '正在跳转至目标时间 (加载中)...',
|
spinner: 'el-icon-loading',
|
background: 'rgba(255, 255, 255, 0.7)'
|
});
|
}
|
} else if (this.$loading && !this.isPlaying) {
|
loadingInstance = this.$loading({
|
target: '.vis-container',
|
lock: true,
|
text: '加载数据中...',
|
spinner: 'el-icon-loading',
|
background: 'rgba(255, 255, 255, 0.7)'
|
});
|
}
|
|
let that = this;
|
|
// If seeking and we have no idea where the target time is in terms of files,
|
// we should ask the server for the correct offset first!
|
if (this.seekTargetTime > 0 && this.visOffset === (this.searchForm.offset || 0)) {
|
// First time seeking or reset? No, this condition is tricky.
|
// Actually, if we are seeking, we can call the new /seek endpoint first.
|
// BUT, loadMoreLogs is recursive for seek. We need to be careful.
|
|
// Let's modify logic:
|
// If seekTargetTime is set, and we suspect it's far away (e.g. not in next batch),
|
// we should use the seek endpoint.
|
// For simplicity, let's ALWAYS try seek endpoint if seeking far ahead?
|
// Or just if we are seeking.
|
|
// However, loadMoreLogs is currently designed to just load NEXT batch.
|
// We should probably intercept the flow here.
|
}
|
|
// NEW LOGIC: If seeking, try to find offset first
|
if (this.seekTargetTime > 0 && this.needToSeekOffset && !this.seekingOffset) {
|
this.seekingOffset = true;
|
$.ajax({
|
url: baseUrl + "/deviceLog/day/" + this.searchForm.day + "/seek/auth",
|
headers: {'token': localStorage.getItem('token')},
|
data: { type: this.visDeviceType, deviceNo: this.visDeviceNo, timestamp: this.seekTargetTime },
|
success: function(res) {
|
if (res.code === 200) {
|
var targetOffset = res.data.offset;
|
// Update offset directly
|
that.visOffset = targetOffset;
|
// Clear logs because we jumped
|
that.logs = [];
|
that.seekingOffset = false;
|
that.needToSeekOffset = false;
|
|
// Now continue to load logs from this new offset
|
// We set seekTargetTime still > 0 so it will check if we arrived.
|
// But we need to call the actual load now.
|
// We recurse (but we need to reset loadingLogs flag first or it returns)
|
// that.loadingLogs = false; // Do not reset loadingLogs here as we are still "loading"
|
// that.loadMoreLogs(); // Recursive call is risky if not careful
|
|
// Better: call sequential load directly
|
that.loadMoreLogsSequential(loadingInstance);
|
} else {
|
// Fallback to sequential load if seek fails
|
that.seekingOffset = false;
|
that.needToSeekOffset = false;
|
that.loadMoreLogsSequential(loadingInstance);
|
}
|
},
|
error: function() {
|
that.seekingOffset = false;
|
that.needToSeekOffset = false;
|
that.loadMoreLogsSequential(loadingInstance);
|
}
|
});
|
return;
|
}
|
|
this.loadMoreLogsSequential(loadingInstance);
|
},
|
loadMoreLogsSequential(loadingInstance) {
|
let that = this;
|
let currentLimit = this.seekTargetTime > 0 ? 10 : this.visLimit;
|
|
$.ajax({
|
url: baseUrl + "/deviceLog/day/" + this.searchForm.day + "/preview/auth",
|
headers: {'token': localStorage.getItem('token')},
|
data: { type: this.visDeviceType, deviceNo: this.visDeviceNo, offset: this.visOffset, limit: currentLimit },
|
success: function(res) {
|
if (loadingInstance) loadingInstance.close();
|
that.loadingLogs = false;
|
if (res.code === 200) {
|
var newLogs = res.data || [];
|
|
if (newLogs.length === 0) {
|
that.hasMoreLogs = false;
|
if (that.seekTargetTime > 0) {
|
that.$message.warning('已到达日志末尾,无法到达目标时间');
|
that.seekTargetTime = 0;
|
} else {
|
if (that.logs.length === 0) that.$message.warning('没有找到日志数据');
|
else that.$message.info('数据已全部加载');
|
}
|
return;
|
}
|
|
// If we cleared logs (jumped), we need to set start time again maybe?
|
// If logs is empty, it means we jumped or initial load.
|
var isJump = that.logs.length === 0;
|
|
that.logs = that.logs.concat(newLogs);
|
that.visOffset += currentLimit;
|
|
if (that.logs.length > 0) {
|
if (isJump) {
|
// If we jumped, we need to ensure we don't break startTime if possible,
|
// OR we update startTime if it was 0.
|
// If we jumped to middle, startTime of the whole day is still 0?
|
// No, startTime usually is the beginning of the visualized session.
|
// If we jump, we might want to keep the "view" consistent?
|
// Actually, if we jump, we effectively discard previous logs.
|
// So the slider range might change?
|
// The user expects slider to represent the WHOLE day?
|
// Currently slider represents [startTime, endTime] of LOADED logs.
|
// If we jump, we might lose the "start".
|
// To support "Whole Day" slider, we need startTime of the FIRST log of the day.
|
// But we don't have that if we jump.
|
// For now, let's just update endTime.
|
// If it's a jump, we might need to adjust startTime if it's the first chunk we have.
|
if (that.startTime === 0) {
|
that.startTime = new Date(that.logs[0].createTime).getTime();
|
that.currentTime = that.startTime;
|
that.$nextTick(() => {
|
that.updateDeviceState(that.logs[0]);
|
});
|
}
|
} else {
|
// Normal load (initial or sequential)
|
// If initial load (startTime is 0)
|
if (that.startTime === 0) {
|
that.startTime = new Date(that.logs[0].createTime).getTime();
|
that.currentTime = that.startTime;
|
that.$nextTick(() => {
|
that.updateDeviceState(that.logs[0]);
|
});
|
}
|
}
|
|
// Update end time
|
that.endTime = new Date(that.logs[that.logs.length - 1].createTime).getTime();
|
|
// Handle Seek Logic
|
if (that.seekTargetTime > 0) {
|
// If we jumped, we should be close.
|
// Check if target is in current range
|
var lastLogTime = new Date(that.logs[that.logs.length - 1].createTime).getTime();
|
if (lastLogTime >= that.seekTargetTime) {
|
that.currentTime = that.seekTargetTime;
|
that.sliderValue = that.currentTime - that.startTime;
|
that.syncState();
|
that.seekTargetTime = 0;
|
that.$message.success('已跳转至目标时间');
|
} else {
|
// Still not there?
|
// If we used /seek, we should be there or very close.
|
// Maybe the file we found ends before target?
|
// We continue loading.
|
setTimeout(() => {
|
that.loadMoreLogs();
|
}, 50);
|
}
|
} else if (isJump) {
|
// If not seeking (just loaded via jump?), but we cleared logs...
|
// Wait, we only clear logs if seekTargetTime > 0 in the new logic.
|
// So this else is for normal load.
|
}
|
}
|
} else {
|
that.$message.error(res.msg);
|
that.seekTargetTime = 0;
|
}
|
},
|
error: function() {
|
if (loadingInstance) loadingInstance.close();
|
that.loadingLogs = false;
|
that.seekTargetTime = 0;
|
that.$message.error('请求失败');
|
}
|
});
|
},
|
handleVisualizationClose() {
|
this.pause();
|
this.visualizationVisible = false;
|
},
|
|
// --- Playback Logic ---
|
play() {
|
this.isPlaying = true;
|
this.lastTick = Date.now();
|
this.tick();
|
},
|
pause() {
|
this.isPlaying = false;
|
if (this.timer) cancelAnimationFrame(this.timer);
|
},
|
reset() {
|
this.pause();
|
this.currentTime = this.startTime;
|
this.sliderValue = 0;
|
if (this.logs.length > 0) {
|
this.updateDeviceState(this.logs[0]);
|
}
|
},
|
tick() {
|
if (!this.isPlaying) return;
|
var now = Date.now();
|
var delta = now - this.lastTick;
|
this.lastTick = now;
|
|
// Auto-load more logs if we are close to the end (prefetch)
|
if (this.hasMoreLogs && !this.loadingLogs) {
|
var idx = this.binarySearch(this.currentTime);
|
// If within last 20 frames
|
if (this.logs.length > 0 && (this.logs.length - 1 - idx) < 20) {
|
this.loadMoreLogs();
|
}
|
}
|
|
var nextTime = this.currentTime + delta * this.playbackSpeed;
|
if (nextTime >= this.endTime) {
|
if (this.hasMoreLogs) {
|
// Reached end of buffer, but more data available
|
// Clamp to endTime
|
nextTime = this.endTime;
|
|
// Ensure loading is triggered
|
if (!this.loadingLogs) {
|
this.loadMoreLogs();
|
}
|
|
// Update state but do NOT pause
|
this.currentTime = nextTime;
|
this.sliderValue = this.currentTime - this.startTime;
|
this.syncState();
|
|
// Continue loop to check again next frame
|
this.timer = requestAnimationFrame(this.tick);
|
return;
|
} else {
|
// Truly finished
|
nextTime = this.endTime;
|
this.currentTime = nextTime;
|
this.sliderValue = this.currentTime - this.startTime;
|
this.syncState();
|
this.pause();
|
return;
|
}
|
}
|
this.currentTime = nextTime;
|
this.sliderValue = this.currentTime - this.startTime;
|
|
this.syncState();
|
|
this.timer = requestAnimationFrame(this.tick);
|
},
|
sliderChange(val) {
|
this.currentTime = this.startTime + val;
|
this.syncState();
|
|
// If dragged near the end, load more
|
if (this.hasMoreLogs && !this.loadingLogs) {
|
var idx = this.binarySearch(this.currentTime);
|
if (this.logs.length > 0 && (this.logs.length - 1 - idx) < 20) {
|
this.loadMoreLogs();
|
}
|
}
|
},
|
sliderInput(val) {
|
this.currentTime = this.startTime + val;
|
this.syncState();
|
// If dragged near the end, load more
|
if (this.hasMoreLogs && !this.loadingLogs) {
|
var idx = this.binarySearch(this.currentTime);
|
if (this.logs.length > 0 && (this.logs.length - 1 - idx) < 20) {
|
this.loadMoreLogs();
|
}
|
}
|
},
|
syncState() {
|
var idx = this.binarySearch(this.currentTime);
|
if (idx >= 0) {
|
var targetLog = this.logs[idx];
|
this.updateDeviceState(targetLog);
|
}
|
},
|
binarySearch(time) {
|
let l = 0, r = this.logs.length - 1;
|
let ans = -1;
|
while (l <= r) {
|
let mid = Math.floor((l + r) / 2);
|
let logTime = new Date(this.logs[mid].createTime).getTime();
|
if (logTime <= time) {
|
ans = mid;
|
l = mid + 1;
|
} else {
|
r = mid - 1;
|
}
|
}
|
return ans;
|
},
|
updateDeviceState(logItem) {
|
if (!logItem || !logItem.wcsData) return;
|
try {
|
var protocol = JSON.parse(logItem.wcsData);
|
var list = [];
|
|
if (this.visDeviceType === 'Devp' && Array.isArray(protocol)) {
|
list = protocol.map(p => this.transformData(p, this.visDeviceType));
|
list.sort((a, b) => (a.stationId || 0) - (b.stationId || 0));
|
} else {
|
var data = this.transformData(protocol, this.visDeviceType);
|
list = [data];
|
}
|
|
var res = { code: 200, data: list };
|
|
if (this.$refs.card) {
|
if (this.visDeviceType === 'Crn') {
|
this.$refs.card.setCrnList(res);
|
} else if (this.visDeviceType === 'Rgv') {
|
this.$refs.card.setRgvList(res);
|
} else if (this.visDeviceType === 'DualCrn') {
|
this.$refs.card.setDualCrnList(res);
|
} else if (this.visDeviceType === 'Devp') {
|
this.$refs.card.setStationList(res);
|
}
|
}
|
} catch (e) {
|
console.error('Error parsing wcsData', e);
|
}
|
},
|
transformData(protocol, type) {
|
if (!protocol) return {};
|
|
// Enums from API
|
var CrnModeType = this.deviceEnums.CrnModeType || {};
|
var CrnStatusType = this.deviceEnums.CrnStatusType || {};
|
var CrnForkPosType = this.deviceEnums.CrnForkPosType || {};
|
var CrnLiftPosType = this.deviceEnums.CrnLiftPosType || {};
|
|
var DualCrnForkPosType = this.deviceEnums.DualCrnForkPosType || {};
|
var DualCrnLiftPosType = this.deviceEnums.DualCrnLiftPosType || {};
|
|
var RgvModeType = this.deviceEnums.RgvModeType || {};
|
var RgvStatusType = this.deviceEnums.RgvStatusType || {};
|
|
if (type === 'Crn') {
|
return {
|
crnNo: protocol.crnNo,
|
workNo: protocol.taskNo || 0,
|
mode: CrnModeType[protocol.mode] || '-',
|
status: CrnStatusType[protocol.status] || '-',
|
loading: protocol.loaded == 1 ? '有物' : '无物',
|
bay: protocol.bay,
|
lev: protocol.level,
|
forkOffset: CrnForkPosType[protocol.forkPos] || '-',
|
liftPos: CrnLiftPosType[protocol.liftPos] || '-',
|
walkPos: (protocol.walkPos == 1) ? '不在定位' : '在定位',
|
xspeed: protocol.xSpeed || 0,
|
yspeed: protocol.ySpeed || 0,
|
zspeed: protocol.zSpeed || 0,
|
xdistance: protocol.xDistance || 0,
|
ydistance: protocol.yDistance || 0,
|
warnCode: protocol.alarm,
|
deviceStatus: (protocol.alarm && protocol.alarm > 0) ? 'ERROR' :
|
((protocol.taskNo && protocol.taskNo > 0) ? 'WORKING' :
|
(protocol.mode == 3 ? 'AUTO' : 'OFFLINE'))
|
};
|
} else if (type === 'DualCrn') {
|
var vo = {
|
crnNo: protocol.crnNo,
|
taskNo: protocol.taskNo || 0,
|
taskNoTwo: protocol.taskNoTwo || 0,
|
mode: CrnModeType[protocol.mode] || '-',
|
status: CrnStatusType[protocol.status] || '-',
|
statusTwo: CrnStatusType[protocol.statusTwo] || '-',
|
loading: protocol.loaded == 1 ? '有物' : '无物',
|
loadingTwo: protocol.loadedTwo == 1 ? '有物' : '无物',
|
bay: protocol.bay,
|
lev: protocol.level,
|
forkOffset: DualCrnForkPosType[protocol.forkPos] || '-',
|
forkOffsetTwo: DualCrnForkPosType[protocol.forkPosTwo] || '-',
|
liftPos: DualCrnLiftPosType[protocol.liftPos] || '-',
|
walkPos: protocol.walkPos == 0 ? '在定位' : '不在定位',
|
taskReceive: protocol.taskReceive == 1 ? '接收' : '无任务',
|
taskReceiveTwo: protocol.taskReceiveTwo == 1 ? '接收' : '无任务',
|
xspeed: protocol.xSpeed,
|
yspeed: protocol.ySpeed,
|
zspeed: protocol.zSpeed,
|
xdistance: protocol.xDistance,
|
ydistance: protocol.yDistance,
|
warnCode: protocol.alarm
|
};
|
if (protocol.alarm && protocol.alarm > 0) vo.deviceStatus = 'ERROR';
|
else if ((protocol.taskNo && protocol.taskNo > 0) || (protocol.taskNoTwo && protocol.taskNoTwo > 0)) vo.deviceStatus = 'WORKING';
|
else if (protocol.mode == 3) vo.deviceStatus = 'AUTO';
|
else vo.deviceStatus = 'OFFLINE';
|
return vo;
|
} else if (type === 'Rgv') {
|
var vo = {
|
rgvNo: protocol.rgvNo,
|
taskNo: protocol.taskNo,
|
mode: RgvModeType[protocol.mode] || '',
|
status: RgvStatusType[protocol.status] || '',
|
loading: protocol.loaded == 1 ? '有物' : '无物',
|
trackSiteNo: protocol.rgvPos,
|
warnCode: protocol.alarm
|
};
|
|
var deviceStatus = "";
|
if (protocol.mode == 3) deviceStatus = "AUTO";
|
if (protocol.taskNo && protocol.taskNo > 0) deviceStatus = "WORKING";
|
if (protocol.alarm && protocol.alarm > 0) deviceStatus = "ERROR";
|
vo.deviceStatus = deviceStatus;
|
|
return vo;
|
} else if (type === 'Devp') {
|
return {
|
stationId: protocol.stationId,
|
taskNo: protocol.taskNo,
|
targetStaNo: protocol.targetStaNo,
|
autoing: protocol.autoing,
|
loading: protocol.loading,
|
inEnable: protocol.inEnable,
|
outEnable: protocol.outEnable,
|
emptyMk: protocol.emptyMk,
|
fullPlt: protocol.fullPlt,
|
runBlock: protocol.runBlock,
|
enableIn: protocol.enableIn,
|
palletHeight: protocol.palletHeight,
|
barcode: protocol.barcode,
|
weight: protocol.weight,
|
error: protocol.error,
|
errorMsg: protocol.errorMsg,
|
extend: protocol.extend
|
};
|
}
|
return protocol;
|
},
|
formatTooltip(val) {
|
var t = this.startTime + val;
|
var d = new Date(t);
|
var Y = d.getFullYear() + '-';
|
var M = (d.getMonth() + 1 < 10 ? '0' + (d.getMonth() + 1) : d.getMonth() + 1) + '-';
|
var D = (d.getDate() < 10 ? '0' + d.getDate() : d.getDate()) + ' ';
|
return Y + M + D + d.toLocaleTimeString() + '.' + d.getMilliseconds();
|
},
|
initJumpTime() {
|
if (this.currentTime > 0) {
|
this.jumpTime = new Date(this.currentTime);
|
} else if (this.startTime > 0) {
|
this.jumpTime = new Date(this.startTime);
|
} else {
|
// Try to parse from searchForm.day
|
if (this.searchForm.day && this.searchForm.day.length === 8) {
|
var y = this.searchForm.day.substring(0, 4);
|
var m = this.searchForm.day.substring(4, 6);
|
var d = this.searchForm.day.substring(6, 8);
|
// Default to 00:00:00 of that day
|
this.jumpTime = new Date(y + '/' + m + '/' + d + ' 00:00:00');
|
} else {
|
this.jumpTime = new Date();
|
}
|
}
|
},
|
confirmJump() {
|
if (!this.jumpTime) return;
|
|
// Construct target timestamp
|
// jumpTime from el-time-picker is a Date object (if not using value-format)
|
// or string/timestamp if using value-format.
|
// We didn't set value-format, so it should be Date object (default in ElementUI 2.x?)
|
// Actually, in default_api:Read above, I saw:
|
// <el-time-picker v-model="jumpTime" ... :picker-options="{ selectableRange: '00:00:00 - 23:59:59' }">
|
// Default v-model for el-time-picker is Date object.
|
|
let targetDate = this.jumpTime;
|
if (typeof targetDate === 'string' || typeof targetDate === 'number') {
|
targetDate = new Date(targetDate);
|
}
|
|
let baseDate = new Date(this.startTime > 0 ? this.startTime : Date.now());
|
|
baseDate.setHours(targetDate.getHours());
|
baseDate.setMinutes(targetDate.getMinutes());
|
baseDate.setSeconds(targetDate.getSeconds());
|
// Picker usually 0 ms
|
baseDate.setMilliseconds(0);
|
|
let targetTs = baseDate.getTime();
|
|
if (this.startTime > 0 && targetTs < this.startTime) {
|
targetTs = this.startTime;
|
}
|
|
// Check if beyond endTime
|
if (this.endTime > 0 && targetTs > this.endTime) {
|
// If we have more logs, we try to go as far as we can (endTime)
|
// and trigger loading
|
if (this.hasMoreLogs) {
|
this.seekTargetTime = targetTs;
|
this.needToSeekOffset = true;
|
// Trigger load immediately
|
if (!this.loadingLogs) {
|
this.loadMoreLogs();
|
} else {
|
// Already loading, just set the target and let callback handle it
|
}
|
this.jumpVisible = false;
|
return; // Don't update current time yet, wait for load
|
} else {
|
targetTs = this.endTime;
|
this.$message.warning('目标时间超出日志范围,已跳转至结束时间');
|
}
|
}
|
|
this.currentTime = targetTs;
|
this.sliderValue = this.currentTime - this.startTime;
|
this.syncState();
|
this.jumpVisible = false;
|
|
// Trigger load if needed
|
if (this.hasMoreLogs && !this.loadingLogs) {
|
// Force load check
|
this.loadMoreLogs();
|
}
|
}
|
}
|
});
|