784 lines
28 KiB
HTML
784 lines
28 KiB
HTML
<!DOCTYPE HTML>
|
||
<html lang="zh-cn">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>log view</title>
|
||
|
||
<link href="resources/plugins/bootstrap-3.3.0/css/bootstrap.min.css" rel="stylesheet"/>
|
||
<link href="resources/plugins/material-design-iconic-font-2.2.0/css/material-design-iconic-font.min.css" rel="stylesheet"/>
|
||
<link href="resources/plugins/bootstrap-table-1.17.0/bootstrap-table.min.css" rel="stylesheet"/>
|
||
<link href="resources/plugins/waves-0.7.5/waves.min.css" rel="stylesheet"/>
|
||
<link href="resources/plugins/jquery-confirm/jquery-confirm.min.css" rel="stylesheet"/>
|
||
<link href="resources/css/common.css" rel="stylesheet"/>
|
||
<style>
|
||
.img-max-height-lg {
|
||
max-height: 240px !important;
|
||
}
|
||
.img-max-height-sm {
|
||
max-height: 320px !important;
|
||
}
|
||
.progress-text {
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
display: inline-block;
|
||
color: #000; /* 默认颜色为黑色 */
|
||
transition: color 0.3s; /* 平滑过渡 */
|
||
white-space: nowrap; /* 防止文字换行 */
|
||
font-size: 14px; /* 可以根据需要调整字体大小 */
|
||
z-index: 1; /* 确保文字在进度条之上 */
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="main">
|
||
<div id="toolbar">
|
||
<a style="color: red;font-size: 1.2em;" class="waves-effect waves-button" href="javascript:;" onclick="clealogAction()"><i class="zmdi zmdi-delete"></i><span class="lang" key="UISTR_ALLLOG_DELETE">clean all logs</span></a>
|
||
<a style="font-size: 1.2em;" id="savelogs" class="waves-effect waves-button" href="javascript:;" onclick="generateAndDownloadCsv()"><i class="zmdi zmdi-download"></i><span class="lang" key="UISTR_SAVE_LOGS">save logs as csv</span></a>
|
||
<a style="font-size: 1.2em;" id="uploadlogs" class="waves-effect waves-button" href="javascript:;" onclick="uploadlogs()"><i class="zmdi zmdi-upload"></i><span class="lang" key="UISTR_UPLOAD_LOGS">upload log from CSV</span></a>
|
||
<a style="font-size: 1.2em;" id="saveasafd" class="waves-effect waves-button" href="javascript:;" onclick="generateAndDownloadAFD()"><i class="zmdi zmdi-download"></i><span>Save logs as AFD</span></a>
|
||
<!-- 添加说明 -->
|
||
<a style="color: red;font-size: 1.2em; display: block; margin-top: 0px;" class="waves-effect waves-button"><i class="zmdi zmdi-info-outline"></i><span class="lang" key="UISTR_LOGS_NOTE">logs note</span></a>
|
||
</div>
|
||
<table id="table"></table>
|
||
</div>
|
||
|
||
<!-- 选择时间栏 -->
|
||
<div id="createDialog" class="crudDialog" hidden>
|
||
<form>
|
||
<div class="form-group">
|
||
<label for="inputuserid" class="lang" key="UISTR_ENROLL_ID">user id</label>
|
||
<input id="inputuserid" class="form-control" type="text" oninput="validateEnrollid(this)">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="inputstartdate" class="lang" key="UISTR_START">start</label>
|
||
<input id="inputstartdate" type="date" class="form-control">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="inputenddate"class="lang" key="UISTR_END">end</label>
|
||
<input id="inputenddate" type="date" class="form-control">
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<!-- 添加模态窗口 -->
|
||
<div class="modal fade" id="uploadModal" tabindex="-1" role="dialog" aria-labelledby="uploadModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog" role="document">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="uploadModalLabel">Upload Logs</h5>
|
||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||
<span aria-hidden="true">×</span>
|
||
</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p><span id="uploaded" class="lang" key="UISTR_UPLOADED">Uploaded</span></p>
|
||
<div class="progress">
|
||
<div class="progress-bar" role="progressbar" style="width: 0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
|
||
<span class="progress-text">0%</span>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="lang btn btn-secondary" data-dismiss="modal" key="UISTR_CLOSE">Close</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div id="resetDialog" class="crudDialog" hidden>
|
||
<form>
|
||
</form>
|
||
</div>
|
||
<script src="resources/plugins/jquery.1.12.4.min.js"></script>
|
||
<script src="resources/plugins/bootstrap-3.3.0/js/bootstrap.min.js"></script>
|
||
<script src="resources/plugins/bootstrap-table-1.17.0/bootstrap-table.min.js"></script>
|
||
<script src="resources/plugins/waves-0.7.5/waves.min.js"></script>
|
||
<script src="resources/plugins/jquery-confirm/jquery-confirm.min.js"></script>
|
||
|
||
<script src="resources/plugins/jquery.cookie.js"></script>
|
||
<script src="resources/js/notify.js"></script>
|
||
<script src="resources/js/lang.js"></script>
|
||
<script src="resources/js/common.js"></script>
|
||
<script src="resources/js/admin.js"></script>
|
||
<script>
|
||
let deviceid = $.cookie("deviceid") || "1";
|
||
let DI_USERID_FORMAT = parseInt($.cookie("USERID_FORMAT")) || 0;
|
||
let fromDate = null;
|
||
let toDate = null;
|
||
let enrollIdstr = null;
|
||
let poithide = $.cookie("manufacturer") !== undefined && $.cookie("manufacturer")==="Piot Robotics";
|
||
check_permission();
|
||
lang_load().then(() => {
|
||
getfromtodate();
|
||
changetitle('time',lang.UISTR_TIME_SETTING);
|
||
changetitle('id',lang.UISTR_ENROLL_ID);
|
||
changetitle('name',lang.UISTR_UserName);
|
||
changetitle('mode',lang.UISTR_VERIFY_mode);
|
||
changetitle('event',lang.UISTR_EVENT);
|
||
changetitle('note',lang.UISTR_NOTE||'note');
|
||
changetitle('inout',lang.UISTR_INOUT||'inout');
|
||
changetitle('photo',lang.UISTR_Photo||'Photo');
|
||
changetitle('photouri',lang.UISTR_Photo_Url||'Photo');
|
||
changeSearchPlaceholder();
|
||
if (!$.cookie("CNPJ")) {
|
||
const element = document.getElementById("saveasafd");
|
||
if (element) {
|
||
element.remove();
|
||
}
|
||
}
|
||
}).catch(error => {
|
||
});
|
||
function convertEnrollId(strid) {
|
||
if (typeof strid === 'number') {
|
||
return strid > 0xFFFFFFFF ? strid.toString() : strid;
|
||
}
|
||
if (typeof strid === 'string' && /^\d+$/.test(strid) && DI_USERID_FORMAT != 2) {
|
||
const num = Number(strid);
|
||
return Number.isSafeInteger(num) ? num : strid;
|
||
}
|
||
return strid;
|
||
}
|
||
function formatNoteContent(note) {
|
||
if (note === null || note === undefined || note === "") return "";
|
||
if (typeof note === 'object') {
|
||
return Object.entries(note)
|
||
.map(([key, value]) => `${key}:${value}`)
|
||
.join(',');
|
||
}
|
||
if (typeof note === 'string') {
|
||
try {
|
||
const parsed = JSON.parse(note);
|
||
return Object.entries(parsed)
|
||
.map(([key, value]) => `${key}:${value}`)
|
||
.join(',');
|
||
} catch (e) {
|
||
return note;
|
||
}
|
||
}
|
||
return String(note);
|
||
}
|
||
function get_log(index = 0,fromDate, toDate, enrollIdstr) {
|
||
let send = new Object();
|
||
send["password"] = $.cookie("pwd");
|
||
send["cmd"] = 'getlog';
|
||
send["index"]=index;
|
||
if (enrollIdstr?.trim()) {
|
||
send["enrollid"] = convertEnrollId(enrollIdstr);
|
||
}
|
||
if (fromDate) {
|
||
send["from"] = fromDate;
|
||
}
|
||
if (toDate) {
|
||
send["to"] = toDate;
|
||
}
|
||
console.log(send);
|
||
$.ajax({
|
||
url: debugserver+'/api',
|
||
type: 'POST',
|
||
contentType: "application/json",
|
||
dataType: "json",
|
||
data: JSON.stringify(send),
|
||
beforeSend: function() {
|
||
},
|
||
success: function(json) {
|
||
console.log("done", json);
|
||
let records = json.record;
|
||
let transformedData = [];
|
||
for (let record of records) {
|
||
let transformedRecord = {
|
||
id: record.enrollid,
|
||
name: record.name || "",
|
||
time: record.time || "",
|
||
mode: record.mode || "",
|
||
inout: record.inout || "",
|
||
event: record.event || "",
|
||
note: formatNoteContent(record.note),
|
||
photo: record.photourl ? 1 : 0,
|
||
photouri:record.photourl || ""
|
||
};
|
||
transformedData.push(transformedRecord);
|
||
}
|
||
$table.bootstrapTable('append', transformedData);
|
||
if (json.count > json.to) {
|
||
var nindex=json.to+1;
|
||
get_log(nindex);
|
||
}
|
||
},
|
||
error: function(error) {
|
||
console.log("error::::", error);
|
||
}
|
||
});
|
||
}
|
||
function getfromtodate() {
|
||
$.confirm({
|
||
type: 'blue',
|
||
animationSpeed: 300,
|
||
title: lang.UISTR_VIEWLOG,
|
||
content: $('#createDialog').html(),
|
||
buttons: {
|
||
confirm: {
|
||
text:lang.UISTR_YES_OK,
|
||
btnClass: 'waves-effect waves-button',
|
||
action: function () {
|
||
fromDate = this.$content.find('#inputstartdate').val();
|
||
toDate = this.$content.find('#inputenddate').val();
|
||
enrollIdstr = this.$content.find('#inputuserid').val();
|
||
get_log(0,fromDate, toDate, enrollIdstr);
|
||
}
|
||
}
|
||
}
|
||
});
|
||
}
|
||
var $table = $('#table');
|
||
$(function() {
|
||
$(document).on('focus', 'input[type="text"]', function() {
|
||
$(this).parent().find('label').addClass('active');
|
||
}).on('blur', 'input[type="text"]', function() {
|
||
if ($(this).val() == '') {
|
||
$(this).parent().find('label').removeClass('active');
|
||
}
|
||
});
|
||
// bootstrap table初始化
|
||
// http://bootstrap-table.wenzhixin.net.cn/zh-cn/documentation/
|
||
$table.bootstrapTable({
|
||
data:[],
|
||
height: getHeight(),
|
||
striped: true,
|
||
search: true,
|
||
searchOnEnterKey: true,
|
||
showRefresh: true,
|
||
//showToggle: true,
|
||
showColumns: true,
|
||
minimumCountColumns: 2,
|
||
//showPaginationSwitch: true,
|
||
clickToSelect: true,
|
||
detailView: true,
|
||
detailFormatter: 'detailFormatter',
|
||
pagination: true,
|
||
pageSize: 15,
|
||
pageList: [10, 15, 30, 50, 100],
|
||
paginationLoop: false,
|
||
classes: 'table table-hover table-no-bordered',
|
||
//sidePagination: 'server',
|
||
//silentSort: false,
|
||
smartDisplay: false,
|
||
idField: 'time',
|
||
sortName: 'time',
|
||
sortOrder: 'desc',
|
||
escape: true,
|
||
searchOnEnterKey: true,
|
||
maintainSelected: true,
|
||
toolbar: '#toolbar',
|
||
columns: [
|
||
{field: 'time', title: 'date time', sortable: true, halign: 'center', width: '15%',align: 'center'},
|
||
{field: 'id', title: 'user id', sortable: true, halign: 'center', width: '15%',align: 'center'},
|
||
{field: 'name', title: 'name', sortable: true, halign: 'center', width: '15%',align: 'center'},
|
||
{field: 'mode', title: 'verify mode', sortable: true, halign: 'center', width: '10%',align: 'center'},
|
||
{field: 'inout', title: 'inout', sortable: true, halign: 'center', width: '10%',align: 'center'},
|
||
{field: 'event', title: 'event', sortable: true, halign: 'center', width: '10%',align: 'center'},
|
||
{field: 'note', title: 'note', sortable: true, halign: 'center', width: '20%',align: 'center'},
|
||
{field: 'photo', title: 'photo', sortable: true, halign: 'center', width: '10%',align: 'center', formatter: 'actionfaceFormatter'},
|
||
{field: 'photouri', title: 'image', visible: false}
|
||
]
|
||
}).on('all.bs.table', function (e, name, args) {
|
||
$('[data-toggle="tooltip"]').tooltip();
|
||
$('[data-toggle="popover"]').popover();
|
||
});
|
||
});
|
||
|
||
$table.on('refresh.bs.table', function () {
|
||
$table.bootstrapTable('removeAll');
|
||
get_log(0,fromDate, toDate, enrollIdstr);
|
||
});
|
||
|
||
function validateEnrollid(inputElement) {
|
||
let maxValue, maxLength;
|
||
switch(DI_USERID_FORMAT) {
|
||
case 0:
|
||
inputElement.value = inputElement.value.replace(/\D/g, '');
|
||
maxValue = 99999999;
|
||
maxLength = 8;
|
||
break;
|
||
|
||
case 1:
|
||
inputElement.value = inputElement.value.replace(/[^0-9]/g, '');
|
||
maxValue = BigInt('9223372036854775807');
|
||
maxLength = 19;
|
||
break;
|
||
case 2:
|
||
maxLength = 32;
|
||
break;
|
||
}
|
||
if (inputElement.value.length > maxLength) {
|
||
inputElement.value = inputElement.value.slice(0, maxLength);
|
||
}
|
||
if (DI_USERID_FORMAT !== 2) {
|
||
const numericValue = DI_USERID_FORMAT === 1 ?
|
||
BigInt(inputElement.value || 0) :
|
||
parseInt(inputElement.value || 0, 10);
|
||
|
||
if (numericValue > maxValue) {
|
||
inputElement.value = maxValue.toString();
|
||
}
|
||
}
|
||
}
|
||
function generateAndDownloadCsv() {
|
||
const data = $table.bootstrapTable('getData');
|
||
const header = "No\tTMNo\tEnNo\tName\tEvent\tMode\tDateTime\r\n";
|
||
const csvContent = "data:text/csv;charset=utf-8,"
|
||
+ header
|
||
+ data.map((row, index) => `${index + 1}\t${deviceid}\t${row.id}\t${row.name}\t${row.inout}\t${row.mode}\t${row.time}`).join("\r\n");
|
||
|
||
const encodedUri = encodeURI(csvContent);
|
||
const link = document.createElement("a");
|
||
let paddedDeviceId = String(deviceid).padStart(3, '0');
|
||
link.setAttribute("href", encodedUri);
|
||
link.setAttribute("download", `alog_${paddedDeviceId}.CSV`);
|
||
document.body.appendChild(link);
|
||
link.click();
|
||
document.body.removeChild(link);
|
||
}
|
||
|
||
function updateProgressBar(total, uploaded,isdownload=false) {
|
||
const progressBar = $('.progress-bar');
|
||
var percentage = (uploaded / total) * 100;
|
||
progressBar.css('width', `${percentage}%`).attr('aria-valuenow', percentage);
|
||
|
||
if(isdownload)
|
||
$('#uploaded').text(lang.UISTR_DOWNLOADED+': '+uploaded+' / '+total);
|
||
else
|
||
$('#uploaded').text(lang.UISTR_UPLOADED+': '+uploaded+' / '+total);
|
||
|
||
// 更新进度条中的文字
|
||
const progressText = $('.progress-text');
|
||
progressText.text(`${Math.round(percentage)}%`);
|
||
|
||
// 根据进度调整文字颜色
|
||
if (percentage > 52) {
|
||
progressText.css('color', 'white');
|
||
} else {
|
||
progressText.css('color', 'black');
|
||
}
|
||
}
|
||
function uploadlogs() {
|
||
const fileInput = document.createElement('input');
|
||
fileInput.type = 'file';
|
||
fileInput.accept = ".TXT, .CSV";
|
||
fileInput.onchange = function(event) {
|
||
const file = event.target.files[0];
|
||
if (file) {
|
||
const reader = new FileReader();
|
||
reader.onload = function(e) {
|
||
const csvContent = e.target.result;
|
||
processDataFromCSV(csvContent);
|
||
};
|
||
reader.readAsText(file);
|
||
}
|
||
};
|
||
fileInput.click();
|
||
}
|
||
function processDataFromCSV(csvContent) {
|
||
var allRows = [];
|
||
var lines = csvContent.split('\n');
|
||
for (var i = 0; i < lines.length; i++) {
|
||
if (i === 0) continue;
|
||
var columns = lines[i].split('\t');
|
||
if (columns.length >= 6) {
|
||
var row = {
|
||
id: convertEnrollId(columns[2]),
|
||
event: Number(columns[4]),
|
||
mode: Number(columns[5]),
|
||
time: columns[6].replace(/\r/g, '')
|
||
};
|
||
allRows.push(row);
|
||
}
|
||
}
|
||
const totalRows = allRows.length;
|
||
let currentIndex = 0;
|
||
|
||
function batchProcessRecords(startIndex, endIndex) {
|
||
const batch = allRows.slice(startIndex, endIndex);
|
||
set_logs(batch).then(() => {
|
||
currentIndex = endIndex;
|
||
updateProgressBar(totalRows, currentIndex);
|
||
if (currentIndex < totalRows) {
|
||
// 计算下次发送的数据量
|
||
const nextBatchSize = Math.min(500, Math.ceil(totalRows / 50));
|
||
const nextEndIndex = Math.min(currentIndex + nextBatchSize, totalRows);
|
||
batchProcessRecords(currentIndex, nextEndIndex);
|
||
} else {
|
||
$('#uploadModal').modal('hide');
|
||
onUploadSuccess();
|
||
}
|
||
}).catch(error => {
|
||
console.error('Error setting user info:', error);
|
||
alert('An error occurred while processing the records.');
|
||
$('#uploadModal').modal('hide');
|
||
});
|
||
}
|
||
|
||
// 初始化模态框
|
||
$('#uploadModalLabel').text(lang.UISTR_UPLOAD_LOGS);
|
||
$('#uploadModal').modal('show');
|
||
updateProgressBar(totalRows, currentIndex);
|
||
|
||
// 初始化处理
|
||
const firstBatchSize = Math.min(500, Math.ceil(totalRows / 50));
|
||
const firstEndIndex = Math.min(firstBatchSize, totalRows);
|
||
batchProcessRecords(0, firstEndIndex);
|
||
}
|
||
function set_logs(recordsArray) {
|
||
return new Promise((resolve, reject) => {
|
||
let send = new Object();
|
||
send["password"] = $.cookie("pwd");
|
||
send["cmd"] = 'setlogs';
|
||
send["count"] = recordsArray.length;
|
||
send["records"] = recordsArray;
|
||
console.log(send);
|
||
$.ajax({
|
||
url: debugserver + '/api',
|
||
type: 'POST',
|
||
contentType: "application/json",
|
||
dataType: "json",
|
||
data: JSON.stringify(send),
|
||
beforeSend: function () { },
|
||
success: function (json) {
|
||
if (json.ret === "setlogs" && json.result === true) {
|
||
resolve();
|
||
} else {
|
||
reject(json);
|
||
}
|
||
},
|
||
error: function (error) {
|
||
reject(error);
|
||
}
|
||
});
|
||
});
|
||
}
|
||
function changetitle(field, newTitle) {
|
||
var columns = $table.bootstrapTable('getOptions').columns[0];
|
||
for (var i = 0; i < columns.length; i++) {
|
||
if (columns[i].field === field) {
|
||
columns[i].title = newTitle;
|
||
break;
|
||
}
|
||
}
|
||
$table.bootstrapTable('refreshOptions', {columns: columns});
|
||
}
|
||
function changeSearchPlaceholder() {
|
||
const options = $table.bootstrapTable('getOptions');
|
||
options.formatSearch = () => lang.UISTR_USER_FIND || 'Search...';
|
||
options.formatRefresh = () => lang.UISTR_Refresh || 'Refresh';
|
||
options.formatRefreshButton = (icon, text) =>
|
||
`<i class="${icon}"></i> ${text || lang.UISTR_Refresh || 'Refresh'}`;
|
||
options.formatColumns = () => lang.UISTR_Columns || 'Columns'; // [4](@ref)
|
||
options.formatColumnsButton = (icon, text) =>
|
||
`<i class="${icon}"></i> ${text || lang.UISTR_Columns || 'Columns'}`;
|
||
options.formatToggle = () => lang.UISTR_Toggle || 'Toggle'; // 悬浮提示(Tooltip)
|
||
options.formatToggleButton = (icon, text) =>
|
||
`<i class="${icon}"></i> ${text || lang.UISTR_Toggle || 'Toggle'}`; // 按钮文本
|
||
options.formatNoMatches = () => lang.UISTR_NoMatches || 'No matching records found';
|
||
options.formatShowingRows = (from, to, total) => {
|
||
const template = lang.UISTR_ShowingRows
|
||
|| 'Showing #1 to #2 of #3 rows';
|
||
return template
|
||
.replace(/#1/g, from)
|
||
.replace(/#2/g, to)
|
||
.replace(/#3/g, total);
|
||
};
|
||
options.formatRecordsPerPage = (pageSize) => {
|
||
const template = lang.UISTR_RecordsPerPage
|
||
|| '#3 rows per page';
|
||
return template.replace(/#3/g, pageSize);
|
||
};
|
||
|
||
// 强制刷新配置
|
||
$table.bootstrapTable('refreshOptions', options);
|
||
}
|
||
function actionfaceFormatter(value, row, index) {
|
||
return value ? `<i class="glyphicon glyphicon-picture"></i>` : '';
|
||
}
|
||
function detailFormatter(index, row) {
|
||
var html = [];
|
||
var detailsHtml = [];
|
||
// Manually add the information you want to display
|
||
detailsHtml.push('<p><b>'+lang.UISTR_ENROLL_ID+':</b>' + row.id + '</p>');
|
||
detailsHtml.push('<p><b>'+lang.UISTR_UserName+':</b>' + row.name + '</p>');
|
||
detailsHtml.push('<p><b>'+lang.UISTR_TIME_SETTING+':</b>' + row.time + '</p>');
|
||
detailsHtml.push('<p><b>'+lang.UISTR_VERIFY_mode+':</b>' + row.mode + '</p>');
|
||
if(!poithide)
|
||
{
|
||
detailsHtml.push('<p><b>inout:</b>' + row.inout + '</p>');
|
||
detailsHtml.push('<p><b>'+lang.UISTR_EVENT+':</b>' + row.event + '</p>');
|
||
detailsHtml.push('<p><b>note:</b>' + row.note + '</p>');
|
||
}
|
||
var imageUrl = '';
|
||
if(row.photouri)
|
||
imageUrl = debugserver+row.photouri;
|
||
|
||
// Use Flexbox for responsive alignment
|
||
html.push('<div class="row d-flex flex-column flex-sm-row">'); // Use flex-column for stacking on small screens, and flex-row for larger screens
|
||
|
||
html.push(' <div class="col-12 col-sm-8">'); // Adjusted width for the first column
|
||
html.push(' ' + detailsHtml.join(''));
|
||
html.push(' </div>');
|
||
|
||
if (imageUrl && imageUrl.trim()) {
|
||
// Ensure the image is always on the left on small screens
|
||
html.push(' <div class="col-12 col-sm-4 order-first order-sm-0">'); // Use order-first for small screens, and remove it for larger screens
|
||
html.push(' <a href="' + imageUrl+'?t=' + new Date().getTime() + '" target="_blank">'); // Added link to open image in new tab
|
||
html.push(' <img src="' + imageUrl+'?t=' + new Date().getTime() + '" alt="User Image" class="img-max-height-lg img-max-height-sm">');
|
||
html.push(' </a>');
|
||
html.push(' </div>');
|
||
}
|
||
|
||
|
||
|
||
html.push('</div>');
|
||
|
||
return html.join('');
|
||
}
|
||
function clealogAction() {
|
||
$.confirm({
|
||
type: 'dark',
|
||
animationSpeed: 300,
|
||
title: window.lang.UISTR_ALLLOG_DELETE,
|
||
content: $('#resetDialog').html(),
|
||
buttons: {
|
||
confirm: {
|
||
text: lang.UISTR_YES_OK,
|
||
btnClass: 'waves-effect waves-button',
|
||
action: function () {
|
||
let send = new Object()
|
||
send["password"] = $.cookie("pwd")
|
||
send["cmd"] = 'cleanlog'
|
||
console.log(send)
|
||
$.ajax({
|
||
url: debugserver+'/api',
|
||
type: 'POST',
|
||
async: false,
|
||
contentType:"application/json",
|
||
dataType: "json",
|
||
data: JSON.stringify(send),
|
||
beforeSend: function() {
|
||
|
||
},
|
||
success: function(json){
|
||
console.log(json)
|
||
$.alert(lang.UISTR_SUCCESSFUL);
|
||
$table.bootstrapTable('removeAll');
|
||
get_log(0,fromDate, toDate, enrollIdstr);
|
||
onUploadSuccess();
|
||
},
|
||
error: function(error){
|
||
console.log("reset error::::",error);
|
||
notify(lang.UISTR_ERRNONESUCCESS);
|
||
},
|
||
always: function () {
|
||
}
|
||
})
|
||
}
|
||
},
|
||
cancel:{
|
||
text: lang.UISTR_NO_ESC,
|
||
btnClass: 'waves-effect waves-button',
|
||
action: function () {
|
||
}
|
||
}
|
||
}
|
||
});
|
||
}
|
||
$(document).ready(function() {
|
||
$('#uploadModal').on('hidden.bs.modal', function () {
|
||
$table.bootstrapTable('removeAll');
|
||
get_log(0,fromDate, toDate, enrollIdstr);
|
||
});
|
||
|
||
if(poithide)
|
||
{
|
||
const hideColumns = ['inout', 'event', 'note', 'photo', 'photouri'];
|
||
hideColumns.forEach(col => {
|
||
$table.bootstrapTable('hideColumn', col);
|
||
});
|
||
}
|
||
});
|
||
// 严格兼容C++的CRC16算法
|
||
function crc16_modbus(data) {
|
||
let crc = 0xFFFF;
|
||
for (let i = 0; i < data.length; i++) {
|
||
crc ^= data.charCodeAt(i);
|
||
for (let j = 0; j < 8; j++) {
|
||
const lsb = crc & 0x0001;
|
||
crc = (crc >> 1) & 0x7FFF;
|
||
if (lsb) crc ^= 0xA001;
|
||
}
|
||
}
|
||
return crc.toString(16).toUpperCase().padStart(4, '0');
|
||
}
|
||
|
||
// 严格字段格式化(支持对齐方式)
|
||
function formatField(value, length, { numeric = false, alignLeft = false } = {}) {
|
||
const str = String(value);
|
||
const padded = numeric ?
|
||
str.padStart(length, '0') :
|
||
(alignLeft ? str.padEnd(length, ' ') : str.padStart(length, ' '));
|
||
return padded.slice(0, length);
|
||
}
|
||
|
||
|
||
// 设备序列号生成(严格遵循C++格式)
|
||
function generateEquipmentSerial(serial) {
|
||
return [
|
||
'00049', // 厂商编号(5位)
|
||
'00907', // 型号编号(5位)
|
||
'0', // 版本号(1位)
|
||
serial.padStart(6, '0') // 序列号(6位)
|
||
].join('').padEnd(17, ' ');
|
||
}
|
||
function formatAFDDateTime(input) {
|
||
// Step 1: 统一转换为标准时间字符串
|
||
let timeStr;
|
||
if (typeof input === 'number') {
|
||
// 时间戳处理(兼容Date.now())
|
||
const d = new Date(input);
|
||
timeStr = [
|
||
d.getFullYear(),
|
||
(d.getMonth() + 1).toString().padStart(2, '0'),
|
||
d.getDate().toString().padStart(2, '0')
|
||
].join('-') + ' ' + [
|
||
d.getHours().toString().padStart(2, '0'),
|
||
d.getMinutes().toString().padStart(2, '0'),
|
||
d.getSeconds().toString().padStart(2, '0')
|
||
].join(':');
|
||
} else if (typeof input === 'string') {
|
||
// 字符串直接使用
|
||
timeStr = input;
|
||
} else {
|
||
throw new Error('输入类型错误,应为时间戳或YYYY-MM-DD HH:mm:ss字符串');
|
||
}
|
||
|
||
// Step 2: 格式校验(更严格的正则表达式)
|
||
if (!/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(timeStr)) {
|
||
throw new Error(`时间格式错误,应为YYYY-MM-DD HH:mm:ss,当前输入:${timeStr}`);
|
||
}
|
||
|
||
// Step 3: 构建AFD格式
|
||
const [datePart, timePart] = timeStr.split(' ');
|
||
const [year, month, day] = datePart.split('-');
|
||
const [hour, minute, second] = timePart.split(':');
|
||
|
||
// 生成带分隔符的日期和时间[6,7](@ref)
|
||
const formattedDate = `${year}-${month}-${day}`;
|
||
const formattedTime = `${hour}:${minute}:${second}`;
|
||
|
||
// 拼接并填充至24位[9,10](@ref)
|
||
return `${formattedDate}T${formattedTime}-0300`.padEnd(24, ' ');
|
||
}
|
||
function buildTrailer(counts) {
|
||
return [
|
||
'999999999', // 固定头
|
||
formatField(counts[2] || 0, 9, { numeric: true }), // 类型2计数
|
||
formatField(counts[3] || 0, 9, { numeric: true }), // 类型3计数
|
||
formatField(0, 9, { numeric: true }), // 类型4
|
||
formatField(0, 9, { numeric: true }), // 类型5
|
||
formatField(0, 9, { numeric: true }), // 类型6
|
||
formatField(0, 9, { numeric: true }), // 类型7
|
||
'9' // 结束标识
|
||
].join('');
|
||
}
|
||
function generateAndDownloadAFD() {
|
||
const data = $table.bootstrapTable('getData');
|
||
const config = {
|
||
serialNumber: $.cookie("devicesn") || "000001",
|
||
cnpj: $.cookie("CNPJ")||"",
|
||
companyName: $.cookie("companyname") || "",
|
||
manufacturerCNPJ: "44880091000172"
|
||
};
|
||
// 构建文件名(严格长度控制)
|
||
const fileName = `AFD${
|
||
formatField(config.serialNumber,17)
|
||
}${
|
||
formatField(config.cnpj, 14, {numeric: true})
|
||
}REP.txt`;
|
||
|
||
// 生成Header记录(字段位置严格对齐)
|
||
const headerFields = [
|
||
'000000000', // 001-009
|
||
'1', // 010
|
||
'1', // 011
|
||
formatField(config.cnpj, 14, {numeric: true}), // 012-025
|
||
formatField(' ', 14), // 026-039
|
||
formatField(config.companyName, 150, {alignLeft: true}), //040-189
|
||
formatField(config.serialNumber, 17),
|
||
formatField(data[data.length-1].time.split('T')[0], 10), //217-226
|
||
formatField(data[0].time.split('T')[0], 10), //207-216
|
||
formatAFDDateTime(Date.now()), //227-250
|
||
'003', //251-253
|
||
'1', //254
|
||
config.manufacturerCNPJ, //255-268
|
||
formatField('E8', 30, {alignLeft: true}) //269-298
|
||
].join('');
|
||
|
||
const headerLine = headerFields +crc16_modbus(headerFields);
|
||
|
||
// 处理数据记录(严格类型判断)
|
||
let counts = {2:0, 3:0};
|
||
const records = data
|
||
.slice() // 创建副本避免修改原数组
|
||
.sort((a, b) => new Date(a.time) - new Date(b.time)) // 按时间升序排序(最旧在前)
|
||
.map((row, index) => {
|
||
const isType2 = row.id === '999999999999' && row.mode ===255;
|
||
let baseStr;
|
||
|
||
if (isType2) {
|
||
// Type2记录:9位序号 + "2" + 24位时间 + 固定CPF + "1" + 备注
|
||
baseStr = [
|
||
formatField(index + 1, 9, {numeric: true}), // 9位序号
|
||
'2',
|
||
formatAFDDateTime(row.time).slice(0, 24), // 严格24位
|
||
'26571383063 ', // 固定14位
|
||
'1',
|
||
formatField(row.note, 150, {alignLeft: true})// 左对齐备注
|
||
].join('');
|
||
counts[2]++;
|
||
} else {
|
||
// Type3记录:9位序号 + "3" + 24位时间 + 12位CPF
|
||
baseStr = [
|
||
formatField(index + 1, 9, {numeric: true}), // 9位序号
|
||
'3',
|
||
formatAFDDateTime(row.time).slice(0, 24), // 24位时间
|
||
formatField(row.id, 11, {alignLeft: true})
|
||
].join('');
|
||
counts[3]++;
|
||
}
|
||
|
||
// 计算CRC并拼接记录
|
||
const crc = crc16_modbus(baseStr);
|
||
return `${baseStr} ${crc}`;
|
||
});
|
||
const trailerLine = buildTrailer(counts);
|
||
// 构建完整文件内容
|
||
const fullContent = [headerLine, ...records, trailerLine].join('\r\n');
|
||
|
||
const encoder = new TextEncoder();
|
||
const encoded = encoder.encode(fullContent);
|
||
|
||
// 文件下载
|
||
const blob = new Blob([encoded], {type: 'text/plain;charset=iso-8859-1'});
|
||
const link = document.createElement('a');
|
||
link.href = URL.createObjectURL(blob);
|
||
link.download = fileName;
|
||
document.body.appendChild(link);
|
||
link.click();
|
||
document.body.removeChild(link);
|
||
}
|
||
function onUploadSuccess() {
|
||
const homeChannel = new BroadcastChannel('home_refresh_channel');
|
||
homeChannel.postMessage({type: 'refresh_home'});
|
||
homeChannel.close();
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|