HanvonAttendanceService/device-api-dump/logview.html

784 lines
28 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!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">&times;</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>