From 48c1de18235020edff108339ed1d12bade8a2b90 Mon Sep 17 00:00:00 2001
From: Junjie <DELL@qq.com>
Date: 星期一, 08 十二月 2025 16:37:02 +0800
Subject: [PATCH] #

---
 src/main/java/com/zy/asrs/controller/DeviceLogController.java |  338 ++++++++++++++++++++++++++++
 src/main/webapp/static/js/deviceLogs/deviceLogs.js            |  228 +++++++++++++++++++
 src/main/webapp/views/deviceLogs/deviceLogs.html              |   96 ++++++++
 3 files changed, 662 insertions(+), 0 deletions(-)

diff --git a/src/main/java/com/zy/asrs/controller/DeviceLogController.java b/src/main/java/com/zy/asrs/controller/DeviceLogController.java
new file mode 100644
index 0000000..d5537e9
--- /dev/null
+++ b/src/main/java/com/zy/asrs/controller/DeviceLogController.java
@@ -0,0 +1,338 @@
+package com.zy.asrs.controller;
+
+import com.core.annotations.ManagerAuth;
+import com.core.common.Cools;
+import com.core.common.R;
+import com.zy.common.web.BaseController;
+import com.zy.core.enums.SlaveType;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletResponse;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+@RestController
+public class DeviceLogController extends BaseController {
+
+    @Value("${deviceLogStorage.loggingPath}")
+    private String loggingPath;
+
+    private static class ProgressInfo {
+        long totalRaw;
+        long processedRaw;
+        int totalCount;
+        int processedCount;
+        boolean finished;
+    }
+
+    private static final Map<String, ProgressInfo> DOWNLOAD_PROGRESS = new ConcurrentHashMap<>();
+
+    @RequestMapping(value = "/deviceLog/dates/auth")
+    @ManagerAuth
+    public R dates() {
+        try {
+            Path baseDir = Paths.get(loggingPath);
+            if (!Files.exists(baseDir)) {
+                return R.ok(new ArrayList<>());
+            }
+            List<String> days = Files.list(baseDir)
+                    .filter(Files::isDirectory)
+                    .map(p -> p.getFileName().toString())
+                    .filter(name -> name.length() == 8 && name.chars().allMatch(Character::isDigit))
+                    .sorted()
+                    .collect(Collectors.toList());
+            Map<String, Map<String, List<String>>> grouped = new LinkedHashMap<>();
+            for (String day : days) {
+                String year = day.substring(0, 4);
+                String month = day.substring(4, 6);
+                grouped.computeIfAbsent(year, k -> new LinkedHashMap<>())
+                        .computeIfAbsent(month, k -> new ArrayList<>())
+                        .add(day);
+            }
+            List<Map<String, Object>> tree = new ArrayList<>();
+            for (Map.Entry<String, Map<String, List<String>>> yEntry : grouped.entrySet()) {
+                Map<String, Object> yNode = new HashMap<>();
+                yNode.put("title", yEntry.getKey());
+                yNode.put("id", yEntry.getKey());
+                List<Map<String, Object>> mChildren = new ArrayList<>();
+                for (Map.Entry<String, List<String>> mEntry : yEntry.getValue().entrySet()) {
+                    Map<String, Object> mNode = new HashMap<>();
+                    mNode.put("title", mEntry.getKey());
+                    mNode.put("id", yEntry.getKey() + "-" + mEntry.getKey());
+                    List<Map<String, Object>> dChildren = new ArrayList<>();
+                    for (String d : mEntry.getValue()) {
+                        Map<String, Object> dNode = new HashMap<>();
+                        dNode.put("title", d.substring(6, 8));
+                        dNode.put("id", d);
+                        dNode.put("day", d);
+                        dChildren.add(dNode);
+                    }
+                    mNode.put("children", dChildren);
+                    mChildren.add(mNode);
+                }
+                yNode.put("children", mChildren);
+                tree.add(yNode);
+            }
+            return R.ok(tree);
+        } catch (Exception e) {
+            return R.error("璇诲彇鏃ユ湡澶辫触");
+        }
+    }
+
+    @RequestMapping(value = "/deviceLog/day/{day}/devices/auth")
+    @ManagerAuth
+    public R devices(@PathVariable("day") String day) {
+        try {
+            if (day == null || day.length() != 8 || !day.chars().allMatch(Character::isDigit)) {
+                return R.error("鏃ユ湡鏍煎紡閿欒");
+            }
+            Path dayDir = Paths.get(loggingPath, day);
+            if (!Files.exists(dayDir) || !Files.isDirectory(dayDir)) {
+                return R.ok(new ArrayList<>());
+            }
+            List<Path> files = Files.list(dayDir)
+                    .filter(p -> !Files.isDirectory(p) && p.getFileName().toString().endsWith(".log"))
+                    .collect(Collectors.toList());
+            Map<String, Map<String, Object>> deviceMap = new HashMap<>();
+            for (Path p : files) {
+                String name = p.getFileName().toString();
+                String[] parts = name.split("_");
+                if (parts.length < 4) {
+                    continue;
+                }
+                String deviceNo = parts[1];
+                String type = parts[0];
+                Map<String, Object> info = deviceMap.computeIfAbsent(deviceNo, k -> {
+                    Map<String, Object> map = new HashMap<>();
+                    map.put("deviceNo", deviceNo);
+                    map.put("types", new HashSet<String>());
+                    map.put("fileCount", 0);
+                    return map;
+                });
+                ((Set<String>) info.get("types")).add(type);
+                info.put("fileCount", ((Integer) info.get("fileCount")) + 1);
+            }
+            List<Map<String, Object>> res = deviceMap.values().stream().map(m -> {
+                Map<String, Object> x = new HashMap<>();
+                x.put("deviceNo", m.get("deviceNo"));
+                x.put("types", ((Set<String>) m.get("types")).stream().collect(Collectors.toList()));
+                x.put("fileCount", m.get("fileCount"));
+                return x;
+            }).collect(Collectors.toList());
+            return R.ok(res);
+        } catch (Exception e) {
+            return R.error("璇诲彇璁惧鍒楄〃澶辫触");
+        }
+    }
+
+    @RequestMapping(value = "/deviceLog/day/{day}/download/auth")
+    @ManagerAuth
+    public void download(@PathVariable("day") String day,
+                         @RequestParam("type") String type,
+                         @RequestParam("deviceNo") String deviceNo,
+                         @RequestParam(value = "offset", required = false) Integer offset,
+                         @RequestParam(value = "limit", required = false) Integer limit,
+                         @RequestParam(value = "progressId", required = false) String progressId,
+                         HttpServletResponse response) {
+        try {
+            String dayClean = day == null ? null : day.replaceAll("\\D", "");
+            if (dayClean == null || dayClean.length() != 8 || !dayClean.chars().allMatch(Character::isDigit)) {
+                response.setStatus(400);
+                return;
+            }
+            if (type == null || SlaveType.findInstance(type) == null) {
+                response.setStatus(400);
+                return;
+            }
+            if (deviceNo == null || !deviceNo.chars().allMatch(Character::isDigit)) {
+                response.setStatus(400);
+                return;
+            }
+            Path dayDir = Paths.get(loggingPath, dayClean);
+            if (!Files.exists(dayDir) || !Files.isDirectory(dayDir)) {
+                response.setStatus(404);
+                return;
+            }
+            List<Path> files = Files.list(dayDir)
+                    .filter(p -> {
+                        String name = p.getFileName().toString();
+                        String prefix = type + "_" + deviceNo + "_" + dayClean + "_";
+                        return name.endsWith(".log") && name.startsWith(prefix);
+                    }).collect(Collectors.toList());
+            // 鎺掑簭锛堟寜鏂囦欢涓殑绱㈠紩鍙烽�掑锛�
+            String prefix = type + "_" + deviceNo + "_" + dayClean + "_";
+            files.sort(Comparator.comparingInt(p -> {
+                String n = p.getFileName().toString();
+                try {
+                    String suf = n.substring(prefix.length(), n.length() - 4);
+                    return Integer.parseInt(suf);
+                } catch (Exception e) {
+                    return Integer.MAX_VALUE;
+                }
+            }));
+            int from = offset == null || offset < 0 ? 0 : offset;
+            int max = limit == null || limit <= 0 ? 200 : limit;
+            int to = Math.min(files.size(), from + max);
+            if (from >= files.size()) {
+                response.setStatus(404);
+                return;
+            }
+            files = files.subList(from, to);
+            if (files.isEmpty()) {
+                response.setStatus(404);
+                return;
+            }
+            ProgressInfo info;
+            String id = progressId;
+            if (Cools.isEmpty(id)) {
+                id = UUID.randomUUID().toString();
+            }
+            List<Path> finalFiles = files;
+            info = DOWNLOAD_PROGRESS.computeIfAbsent(id, k -> {
+                ProgressInfo x = new ProgressInfo();
+                x.totalCount = finalFiles.size();
+                long sum = 0L;
+                for (Path f : finalFiles) {
+                    try { sum += Files.size(f); } catch (Exception ignored) {}
+                }
+                x.totalRaw = sum;
+                x.processedRaw = 0L;
+                x.processedCount = 0;
+                x.finished = false;
+                return x;
+            });
+            response.reset();
+            response.setContentType("application/zip");
+            String filename = type + "_" + deviceNo + "_" + dayClean + ".zip";
+            response.setHeader("Content-Disposition", "attachment; filename=" + filename);
+            long totalRawSize = 0L;
+            for (Path f : files) {
+                try { totalRawSize += Files.size(f); } catch (Exception ignored) {}
+            }
+            response.setHeader("X-Total-Size", String.valueOf(totalRawSize));
+            response.setHeader("X-File-Count", String.valueOf(files.size()));
+            response.setHeader("X-Progress-Id", id);
+            try (java.util.zip.ZipOutputStream zos = new java.util.zip.ZipOutputStream(response.getOutputStream())) {
+                for (Path f : files) {
+                    java.util.zip.ZipEntry entry = new java.util.zip.ZipEntry(f.getFileName().toString());
+                    zos.putNextEntry(entry);
+                    Files.copy(f, zos);
+                    zos.closeEntry();
+                    try {
+                        info.processedRaw += Files.size(f);
+                    } catch (Exception ignored) {}
+                    info.processedCount += 1;
+                }
+                zos.finish();
+                info.finished = true;
+            }
+        } catch (Exception e) {
+            try { response.setStatus(500); } catch (Exception ignore) {}
+        }
+    }
+
+    @RequestMapping(value = "/deviceLog/download/init/auth")
+    @ManagerAuth
+    public R init(@org.springframework.web.bind.annotation.RequestBody com.alibaba.fastjson.JSONObject param) {
+        try {
+            String day = param.getString("day");
+            String type = param.getString("type");
+            String deviceNo = param.getString("deviceNo");
+            Integer offset = param.getInteger("offset");
+            Integer limit = param.getInteger("limit");
+            String dayClean = Cools.isEmpty(day) ? null : day.replaceAll("\\D", "");
+            if (Cools.isEmpty(dayClean) || dayClean.length() != 8 || !dayClean.chars().allMatch(Character::isDigit)) {
+                return R.error("鏃ユ湡鏍煎紡閿欒");
+            }
+            if (Cools.isEmpty(type) || SlaveType.findInstance(type) == null) {
+                return R.error("璁惧绫诲瀷閿欒");
+            }
+            if (Cools.isEmpty(deviceNo) || !deviceNo.chars().allMatch(Character::isDigit)) {
+                return R.error("璁惧缂栧彿閿欒");
+            }
+            Path dayDir = Paths.get(loggingPath, dayClean);
+            if (!Files.exists(dayDir) || !Files.isDirectory(dayDir)) {
+                return R.error("褰撴棩鐩綍涓嶅瓨鍦�");
+            }
+            List<Path> files = Files.list(dayDir)
+                    .filter(p -> {
+                        String name = p.getFileName().toString();
+                        String prefix = type + "_" + deviceNo + "_" + dayClean + "_";
+                        return name.endsWith(".log") && name.startsWith(prefix);
+                    }).collect(Collectors.toList());
+            String prefix = type + "_" + deviceNo + "_" + dayClean + "_";
+            files.sort(Comparator.comparingInt(p -> {
+                String n = p.getFileName().toString();
+                try {
+                    String suf = n.substring(prefix.length(), n.length() - 4);
+                    return Integer.parseInt(suf);
+                } catch (Exception e) {
+                    return Integer.MAX_VALUE;
+                }
+            }));
+            int from = offset == null || offset < 0 ? 0 : offset;
+            int max = limit == null || limit <= 0 ? 200 : limit;
+            int to = Math.min(files.size(), from + max);
+            if (from >= files.size()) {
+                return R.error("璧峰搴忓彿瓒呭嚭鑼冨洿");
+            }
+            files = files.subList(from, to);
+            String id = UUID.randomUUID().toString();
+            ProgressInfo info = new ProgressInfo();
+            info.totalCount = files.size();
+            long sum = 0L;
+            for (Path f : files) {
+                try { sum += Files.size(f); } catch (Exception ignored) {}
+            }
+            info.totalRaw = sum;
+            info.processedRaw = 0L;
+            info.processedCount = 0;
+            info.finished = false;
+            DOWNLOAD_PROGRESS.put(id, info);
+            Map<String, Object> res = new HashMap<>();
+            res.put("progressId", id);
+            res.put("totalSize", info.totalRaw);
+            res.put("fileCount", info.totalCount);
+            return R.ok(res);
+        } catch (Exception e) {
+            return R.error("鍒濆鍖栧け璐�");
+        }
+    }
+
+    @RequestMapping(value = "/deviceLog/download/progress/auth")
+    @ManagerAuth
+    public R progress(String id) {
+        ProgressInfo info = DOWNLOAD_PROGRESS.get(id);
+        if (info == null) {
+            return R.error("鏃犳晥杩涘害");
+        }
+        long total = info.totalRaw;
+        long done = info.processedRaw;
+        int percent;
+        if (info.finished) {
+            percent = 100;
+        } else if (total > 0) {
+            percent = (int) Math.min(99, (done * 100L) / total);
+        } else if (info.totalCount > 0) {
+            percent = (int) Math.min(99, (info.processedCount * 100L) / info.totalCount);
+        } else {
+            percent = 0;
+        }
+        Map<String, Object> res = new HashMap<>();
+        res.put("percent", percent);
+        res.put("processedSize", done);
+        res.put("totalSize", total);
+        res.put("processedCount", info.processedCount);
+        res.put("totalCount", info.totalCount);
+        res.put("finished", info.finished);
+        return R.ok(res);
+    }
+}
diff --git a/src/main/webapp/static/js/deviceLogs/deviceLogs.js b/src/main/webapp/static/js/deviceLogs/deviceLogs.js
new file mode 100644
index 0000000..31246f0
--- /dev/null
+++ b/src/main/webapp/static/js/deviceLogs/deviceLogs.js
@@ -0,0 +1,228 @@
+layui.use(['tree', 'layer', 'form', 'element'], function() {
+    var tree = layui.tree;
+    var $ = layui.jquery;
+    var layer = layui.layer;
+    var form = layui.form;
+    var element = layui.element;
+
+    var currentDay = null;
+
+    function 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 });
+                });
+            });
+        });
+        var result = [];
+        Object.keys(monthMap).sort().forEach(function (month) {
+            result.push({ title: month + '鏈�', id: month, children: monthMap[month] });
+        });
+        return result;
+    }
+
+    function loadDateTree() {
+        $.ajax({
+            url: baseUrl + "/deviceLog/dates/auth",
+            headers: {'token': localStorage.getItem('token')},
+            method: 'GET',
+            beforeSend: function () {
+                layer.load(1, {shade: [0.1,'#fff']});
+            },
+            success: function (res) {
+                layer.closeAll('loading');
+                if (res.code === 200) {
+                    var monthTree = buildMonthTree(res.data);
+                    tree.render({
+                        elem: '#date-tree',
+                        id: 'dateTree',
+                        data: monthTree,
+                        click: function(obj){
+                            var node = obj.data;
+                            if (node.id && node.id.length === 8) {
+                                currentDay = node.id;
+                                $('#selected-day').val(currentDay);
+                                loadDevices(currentDay);
+                            }
+                        }
+                    });
+                } else if (res.code === 403) {
+                    top.location.href = baseUrl + "/";
+                } else {
+                    layer.msg(res.msg || '鍔犺浇鏃ユ湡澶辫触', {icon: 2});
+                }
+            }
+        });
+    }
+
+    function loadDevices(day) {
+        $('#device-list').html('');
+        $.ajax({
+            url: baseUrl + "/deviceLog/day/" + day + "/devices/auth",
+            headers: {'token': localStorage.getItem('token')},
+            method: 'GET',
+            beforeSend: function () {
+                layer.load(1, {shade: [0.1,'#fff']});
+            },
+            success: function (res) {
+                layer.closeAll('loading');
+                if (res.code === 200) {
+                    if (!res.data || res.data.length === 0) {
+                        $('#device-list').html('<div class="layui-text">褰撴棩鏈壘鍒拌澶囨棩蹇�</div>');
+                        return;
+                    }
+                    var html = '';
+                    res.data.forEach(function(item){
+                        var types = item.types || [];
+                        var typeBtns = '';
+                        types.forEach(function(t){
+                            typeBtns += '<button class="layui-btn layui-btn-xs" data-type="' + t + '" data-device-no="' + item.deviceNo + '">涓嬭浇(' + t + ')</button>';
+                        });
+                        html += '<div class="layui-col-xs12" style="margin-bottom:8px;">' +
+                            '<div class="layui-card">' +
+                            '<div class="layui-card-body">' +
+                            '<span>璁惧缂栧彿锛�<b>' + item.deviceNo + '</b></span>' +
+                            '<span style="margin-left:20px;">绫诲瀷锛�' + types.join(',') + '</span>' +
+                            '<span style="margin-left:20px;">鏂囦欢鏁帮細' + item.fileCount + '</span>' +
+                            '<span style="margin-left:20px;">' + typeBtns + '</span>' +
+                            '</div>' +
+                            '</div>' +
+                            '</div>';
+                    });
+                    $('#device-list').html(html);
+                } else if (res.code === 403) {
+                    top.location.href = baseUrl + "/";
+                } else {
+                    layer.msg(res.msg || '鍔犺浇璁惧澶辫触', {icon: 2});
+                }
+            }
+        });
+    }
+
+    function downloadDeviceLog(day, type, deviceNo) {
+        if (!day) {
+            layer.msg('璇峰厛閫夋嫨鏃ユ湡', {icon: 2});
+            return;
+        }
+        if (!type) {
+            layer.msg('璇烽�夋嫨璁惧绫诲瀷', {icon: 2});
+            return;
+        }
+        if (!deviceNo) {
+            layer.msg('璇疯緭鍏ヨ澶囩紪鍙�', {icon: 2});
+            return;
+        }
+        var offsetVal = parseInt($('#file-offset').val());
+        var limitVal = parseInt($('#file-limit').val());
+        var offset = isNaN(offsetVal) || offsetVal < 0 ? 0 : offsetVal;
+        var limit = isNaN(limitVal) || limitVal <= 0 ? 200 : limitVal;
+        $.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) {
+                    layer.msg(res.msg || '鍒濆鍖栧け璐�', {icon: 2});
+                    return;
+                }
+                var pid = res.data.progressId;
+                var progressIndex = layer.open({
+                    type: 1,
+                    title: '涓嬭浇涓�',
+                    area: ['520px', '200px'],
+                    content: '<div style="padding:16px;">' +
+                        '<div class="layui-text" style="margin-bottom:15px;">鍘嬬缉鐢熸垚杩涘害</div>' +
+                        '<div class="layui-progress" lay-showPercent="true" lay-filter="buildProgress">' +
+                        '<div class="layui-progress-bar" style="width:0%"><span class="layui-progress-text">0%</span></div>' +
+                        '</div>' +
+                        '<div class="layui-text" style="margin:12px 0 15px;">涓嬭浇鎺ユ敹杩涘害</div>' +
+                        '<div class="layui-progress" lay-showPercent="true" lay-filter="receiveProgress">' +
+                        '<div class="layui-progress-bar" style="width:0%"><span class="layui-progress-text">0%</span></div>' +
+                        '</div>' +
+                        '</div>'
+                });
+                var timer = 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;
+                                element.progress('buildProgress', percent + '%');
+                                // 闅愯棌瀹炴椂澶у皬锛屼笉鏇存柊鏂囧瓧
+                            }
+                        }
+                    });
+                }, 500);
+
+                $.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){
+                            var percent = 0;
+                            if (e.lengthComputable && e.total > 0) {
+                                percent = Math.floor(e.loaded / e.total * 100);
+                                element.progress('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]);
+                        }
+                        element.progress('buildProgress', '100%');
+                        element.progress('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(timer);
+                        setTimeout(function(){ layer.close(progressIndex); }, 300);
+                    },
+                    error: function () {
+                        clearInterval(timer);
+                        layer.close(progressIndex);
+                        layer.msg('涓嬭浇澶辫触鎴栨湭鎵惧埌鏃ュ織', {icon: 2});
+                    }
+                });
+            }
+        });
+    }
+
+    $(document).on('click', '#download-btn', function () {
+        downloadDeviceLog(currentDay, $('#device-type-input').val(), $('#device-no-input').val());
+    });
+
+    $(document).on('click', '#device-list .layui-btn', function () {
+        var deviceNo = $(this).attr('data-device-no');
+        var type = $(this).attr('data-type');
+        downloadDeviceLog(currentDay, type, deviceNo);
+    });
+
+    loadDateTree();
+    limit();
+});
+
diff --git a/src/main/webapp/views/deviceLogs/deviceLogs.html b/src/main/webapp/views/deviceLogs/deviceLogs.html
new file mode 100644
index 0000000..c09cc42
--- /dev/null
+++ b/src/main/webapp/views/deviceLogs/deviceLogs.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="utf-8">
+    <title>璁惧鏃ュ織</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="../../static/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="../../static/css/admin.css?v=318" media="all">
+    <link rel="stylesheet" href="../../static/css/cool.css" media="all">
+</head>
+<body>
+
+<div class="layui-fluid">
+    <div class="layui-row">
+        <div class="layui-col-md3">
+            <div class="layui-card">
+                <div class="layui-card-header">鏃ユ湡</div>
+                <div class="layui-card-body">
+                    <div id="date-tree"></div>
+                </div>
+            </div>
+        </div>
+        <div class="layui-col-md9">
+            <div class="layui-card">
+                <div class="layui-card-header">鏃ュ織涓嬭浇</div>
+                <div class="layui-card-body">
+                    <form class="layui-form toolbar" id="search-box">
+                        <div class="layui-form-item">
+                            <div class="layui-inline">
+                                <label class="layui-form-label">閫変腑鏃ユ湡锛�</label>
+                                <div class="layui-input-inline">
+                                    <input id="selected-day" class="layui-input" type="text" placeholder="yyyyMMdd" readonly>
+                                </div>
+                            </div>
+                            <div class="layui-inline">
+                                <label class="layui-form-label">璁惧绫诲瀷锛�</label>
+                                <div class="layui-input-inline">
+                                    <select id="device-type-input" class="layui-input">
+                                        <option value="">璇烽�夋嫨</option>
+                                        <option value="Crn">Crn</option>
+                                        <option value="Devp">Devp</option>
+                                        <option value="Rgv">Rgv</option>
+                                    </select>
+                                </div>
+                            </div>
+                            <div class="layui-inline">
+                                <label class="layui-form-label">璁惧缂栧彿锛�</label>
+                                <div class="layui-input-inline">
+                                    <input id="device-no-input" class="layui-input" type="text" placeholder="璇疯緭鍏ヨ澶囩紪鍙�">
+                                </div>
+                            </div>
+                            <div class="layui-inline">
+                                <label class="layui-form-label">璧峰搴忓彿锛�</label>
+                                <div class="layui-input-inline">
+                                    <input id="file-offset" class="layui-input" type="text" placeholder="榛樿0">
+                                </div>
+                            </div>
+                            <div class="layui-inline">
+                                <label class="layui-form-label">鏈�澶ф枃浠舵暟锛�</label>
+                                <div class="layui-input-inline">
+                                    <input id="file-limit" class="layui-input" type="text" placeholder="榛樿200">
+                                </div>
+                            </div>
+                            <div class="layui-inline">
+                                <button id="download-btn" type="button" class="layui-btn layui-btn-normal">涓嬭浇</button>
+                            </div>
+                        </div>
+                    </form>
+
+                    <hr class="layui-bg-gray">
+
+                    <div class="layui-row">
+                        <div class="layui-col-xs12">
+                            <div class="layui-card">
+                                <div class="layui-card-header">璇ユ棩璁惧鍒楄〃</div>
+                                <div class="layui-card-body">
+                                    <div id="device-list" class="layui-row"></div>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<script type="text/javascript" src="../../static/js/jquery/jquery-3.3.1.min.js"></script>
+<script type="text/javascript" src="../../static/layui/layui.js" charset="utf-8"></script>
+<script type="text/javascript" src="../../static/js/common.js" charset="utf-8"></script>
+<script type="text/javascript" src="../../static/js/cool.js" charset="utf-8"></script>
+<script type="text/javascript" src="../../static/js/deviceLogs/deviceLogs.js" charset="utf-8"></script>
+</body>
+</html>

--
Gitblit v1.9.1