自动化立体仓库 - WMS系统
lty
9 小时以前 01fa93b5dd9f20286ef8f22515caf55307f76a9d
src/main/webapp/views/login.html
@@ -46,6 +46,178 @@
            text-align: center;
        }
        .license-entry-text {
            width: 80%;
            margin: 0 auto 12px;
            color: #5e5e5e;
            font-size: 12px;
            line-height: 1.7;
            text-align: left;
        }
        .system-tool-shell {
            background: #ffffff;
            border-radius: 22px;
            overflow: hidden;
        }
        .system-tool-header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 14px 20px;
            border-bottom: 1px solid #dbe4ef;
        }
        .system-tool-title {
            color: #243447;
            font-size: 18px;
            font-weight: 700;
        }
        .system-tool-close {
            color: #97a5b4;
            font-size: 24px;
            line-height: 1;
            cursor: pointer;
            user-select: none;
        }
        .system-tool-body {
            padding: 18px 20px 22px;
            background: #ffffff;
        }
        .system-tool-group + .system-tool-group {
            margin-top: 18px;
            padding-top: 18px;
            border-top: 1px solid #e8eef5;
        }
        .system-tool-group-title {
            color: #34495e;
            font-size: 14px;
            font-weight: 700;
            margin-bottom: 14px;
        }
        .system-tool-actions {
            display: flex;
            flex-wrap: wrap;
            gap: 12px 18px;
        }
        .system-tool-btn {
            min-width: 104px;
            height: 34px;
            padding: 0 18px;
            border: 1px solid #d7dfea;
            border-radius: 4px;
            background: #ffffff;
            color: #4d5d6d;
            font-size: 14px;
            cursor: pointer;
            box-sizing: border-box;
        }
        .system-tool-btn.primary {
            color: #ffffff;
            background: #5da8ff;
            border-color: #5da8ff;
        }
        .system-tool-btn.secondary {
            color: #7eb4ef;
            background: #d9ecff;
            border-color: #aed4ff;
        }
        .system-tool-desc {
            margin-top: 12px;
            color: #9aa8b6;
            font-size: 12px;
            line-height: 1.7;
        }
        .system-dialog-shell {
            background: #ffffff;
            border-radius: 18px;
            overflow: hidden;
        }
        .system-dialog-header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 14px 18px;
            border-bottom: 1px solid #e1e8f0;
            background: #f8fbff;
        }
        .system-dialog-title {
            color: #243447;
            font-size: 16px;
            font-weight: 700;
        }
        .system-dialog-close {
            color: #97a5b4;
            font-size: 22px;
            line-height: 1;
            cursor: pointer;
            user-select: none;
        }
        .system-dialog-body {
            padding: 18px;
            background: #ffffff;
        }
        .system-dialog-label {
            color: #43576b;
            font-size: 13px;
            font-weight: 700;
            margin-bottom: 8px;
        }
        .system-dialog-tip {
            margin-bottom: 10px;
            color: #8c9aac;
            font-size: 12px;
            line-height: 1.7;
        }
        .system-dialog-textarea {
            width: 100%;
            min-height: 220px;
            resize: none;
            border: 1px solid #d7dfea;
            border-radius: 8px;
            padding: 10px 12px;
            box-sizing: border-box;
            color: #4d5d6d;
            background: #fbfdff;
            font-size: 13px;
            line-height: 1.7;
        }
        .system-dialog-footer {
            display: flex;
            justify-content: flex-end;
            gap: 10px;
            margin-top: 14px;
        }
        body .system-tool-popup {
            border-radius: 22px !important;
            overflow: hidden !important;
        }
        body .system-tool-popup .layui-layer-content {
            overflow: hidden !important;
            background: transparent;
        }
    </style>
</head>
<body>
@@ -90,10 +262,8 @@
                <button class="login100-form-btn login-btn">Login</button>
            </div>
            <div class="container-login100-form-btn p-t-10" style="display: none;margin-top: 50px;" id="updateLicense">
                <form enctype="multipart/form-data" style="display: none;">
                    <input id="license" type="file" name="file">
                </form>
                <button class="login100-form-btn" id="submitLicense">更新许可证</button>
                <div class="license-entry-text" id="licenseEntryMessage">系统当前未检测到可用许可证,请打开系统工具处理许可证。</div>
                <button class="login100-form-btn" id="openLicenseTool" type="button">系统工具</button>
            </div>
        </div>
    </div>
@@ -115,10 +285,458 @@
        if (oldPass) {
            $('#password').val(oldPass);
        }
        autoShowLicenseTool();
    })
    window.onload = function () {
        document.getElementById("username").focus();
    }
    var licenseToolState = {
        toolLayerIndex: null,
        textLayerIndex: null,
        uploadLayerIndex: null
    };
    function getQueryValue(name) {
        var query = window.location.search.substring(1).split("&");
        for (var i = 0; i < query.length; i++) {
            var item = query[i].split("=");
            if (item[0] === name) {
                return decodeURIComponent(item[1] || "");
            }
        }
        return "";
    }
    function autoShowLicenseTool() {
        var needOpen = getQueryValue("license") === "invalid" || sessionStorage.getItem("licensePanelAutoOpen") === "1";
        if (!needOpen) {
            return;
        }
        verifyLicenseAvailability(function (isValid) {
            if (isValid) {
                clearLicensePromptState();
                hideLicenseEntry();
                clearLicenseQueryFlag();
                return;
            }
            var message = sessionStorage.getItem("licenseInvalidReason") || "系统当前未检测到可用许可证,请先导入新的许可证。";
            showLicenseEntry(message);
            openLicenseTool(message);
        });
    }
    function showLicenseEntry(message) {
        $("#updateLicense").show();
        if (message) {
            $("#licenseEntryMessage").text(message);
        }
    }
    function hideLicenseEntry() {
        $("#updateLicense").hide();
    }
    function clearLicensePromptState() {
        sessionStorage.removeItem("licensePanelAutoOpen");
        sessionStorage.removeItem("licenseInvalidReason");
    }
    function clearLicenseQueryFlag() {
        if (window.history && window.history.replaceState && getQueryValue("license") === "invalid") {
            window.history.replaceState(null, document.title, window.location.pathname);
        }
    }
    function verifyLicenseAvailability(callback) {
        $.ajax({
            url: baseUrl + "/license/getLicenseDays",
            method: 'GET',
            success: function (res) {
                if (res.code == 200) {
                    var days = Number(getResponseValue(res));
                    callback(!isNaN(days) && days >= 0);
                    return;
                }
                callback(false);
            },
            error: function () {
                callback(false);
            }
        });
    }
    function escapeHtml(text) {
        return String(text || "")
            .replace(/&/g, "&amp;")
            .replace(/</g, "&lt;")
            .replace(/>/g, "&gt;")
            .replace(/\"/g, "&quot;")
            .replace(/'/g, "&#39;");
    }
    function buildLicenseToolContent() {
        return ''
            + '<div class="system-tool-shell">'
            + '  <div class="system-tool-header">'
            + '    <div class="system-tool-title">系统工具</div>'
            + '    <span class="system-tool-close" id="toolClose">×</span>'
            + '  </div>'
            + '  <div class="system-tool-body">'
            + '    <div class="system-tool-group">'
            + '      <div class="system-tool-group-title">推荐操作</div>'
            + '      <div class="system-tool-actions">'
            + '        <button type="button" class="system-tool-btn primary" id="toolRequestCode">获取请求码</button>'
            + '        <button type="button" class="system-tool-btn secondary" id="toolActivate">一键激活</button>'
            + '      </div>'
            + '      <div class="system-tool-desc">优先使用“获取请求码”和“一键激活”完成许可证申请与激活。</div>'
            + '    </div>'
            + '    <div class="system-tool-group">'
            + '      <div class="system-tool-group-title">其他工具</div>'
            + '      <div class="system-tool-actions">'
            + '        <button type="button" class="system-tool-btn" id="toolProjectName">获取项目名称</button>'
            + '        <button type="button" class="system-tool-btn" id="toolServerInfo">获取系统配置</button>'
            + '        <button type="button" class="system-tool-btn" id="toolInputLicense">录入许可证</button>'
            + '      </div>'
            + '    </div>'
            + '  </div>'
            + '</div>';
    }
    function openLicenseTool(message) {
        var toolMessage = message || $("#licenseEntryMessage").text() || "系统当前未检测到可用许可证,请先导入新的许可证。";
        var toolWidth = Math.min($(window).width() - 32, 760);
        showLicenseEntry(toolMessage);
        if (licenseToolState.toolLayerIndex !== null) {
            layer.close(licenseToolState.toolLayerIndex);
        }
        licenseToolState.toolLayerIndex = layer.open({
            type: 1,
            title: false,
            closeBtn: 0,
            area: [Math.min(toolWidth, 560) + 'px', 'auto'],
            shadeClose: false,
            skin: 'system-tool-popup',
            content: buildLicenseToolContent(),
            success: function (layero, index) {
                licenseToolState.toolLayerIndex = index;
                bindLicenseToolEvents(layero);
            },
            end: function () {
                licenseToolState.toolLayerIndex = null;
            }
        });
    }
    function bindLicenseToolEvents(layero) {
        layero.find("#toolClose").on("click", function () {
            layer.close(licenseToolState.toolLayerIndex);
        });
        layero.find("#toolRequestCode").on("click", function () {
            requestCode();
        });
        layero.find("#toolActivate").on("click", function () {
            activateLicense();
        });
        layero.find("#toolProjectName").on("click", function () {
            getProjectName();
        });
        layero.find("#toolServerInfo").on("click", function () {
            getServerInfo();
        });
        layero.find("#toolInputLicense").on("click", function () {
            openLicenseInputDialog();
        });
    }
    function getResponseValue(res) {
        if (!res) {
            return "";
        }
        if (typeof res.data !== "undefined" && res.data !== null && res.data !== "") {
            return res.data;
        }
        if (typeof res.msg !== "undefined" && res.msg !== null && res.msg !== "") {
            return res.msg;
        }
        return "";
    }
    function openTextDialog(title, label, text, tip) {
        var prettyText = "";
        try {
            prettyText = typeof text === "string" ? text : JSON.stringify(text, null, 2);
        } catch (e) {
            prettyText = String(text || "");
        }
        if (licenseToolState.textLayerIndex !== null) {
            layer.close(licenseToolState.textLayerIndex);
        }
        licenseToolState.textLayerIndex = layer.open({
            type: 1,
            title: false,
            closeBtn: 0,
            area: [Math.min($(window).width() - 32, 720) + 'px', 'auto'],
            shadeClose: false,
            skin: 'system-tool-popup',
            content: ''
                + '<div class="system-dialog-shell">'
                + '  <div class="system-dialog-header">'
                + '    <div class="system-dialog-title">' + escapeHtml(title) + '</div>'
                + '    <span class="system-dialog-close" id="systemTextClose">×</span>'
                + '  </div>'
                + '  <div class="system-dialog-body">'
                + '    <div class="system-dialog-label">' + escapeHtml(label) + '</div>'
                + (tip ? '<div class="system-dialog-tip">' + escapeHtml(tip) + '</div>' : '')
                + '    <textarea class="system-dialog-textarea" id="systemDialogText" readonly></textarea>'
                + '    <div class="system-dialog-footer">'
                + '      <button type="button" class="system-tool-btn" id="systemTextCloseBtn">关闭</button>'
                + '      <button type="button" class="system-tool-btn primary" id="systemTextCopy">复制</button>'
                + '    </div>'
                + '  </div>'
                + '</div>',
            success: function (layero, index) {
                licenseToolState.textLayerIndex = index;
                layero.find("#systemDialogText").val(prettyText);
                layero.find("#systemTextClose, #systemTextCloseBtn").on("click", function () {
                    layer.close(index);
                });
                layero.find("#systemTextCopy").on("click", function () {
                    copyRequestCodeText(prettyText);
                });
            },
            end: function () {
                licenseToolState.textLayerIndex = null;
            }
        });
    }
    function openLicenseInputDialog() {
        if (licenseToolState.uploadLayerIndex !== null) {
            layer.close(licenseToolState.uploadLayerIndex);
        }
        licenseToolState.uploadLayerIndex = layer.open({
            type: 1,
            title: false,
            closeBtn: 0,
            area: [Math.min($(window).width() - 32, 760) + 'px', 'auto'],
            shadeClose: false,
            skin: 'system-tool-popup',
            content: ''
                + '<div class="system-dialog-shell">'
                + '  <div class="system-dialog-header">'
                + '    <div class="system-dialog-title">录入许可证</div>'
                + '    <span class="system-dialog-close" id="licenseInputClose">×</span>'
                + '  </div>'
                + '  <div class="system-dialog-body">'
                + '    <div class="system-dialog-label">许可证 Base64</div>'
                + '    <div class="system-dialog-tip">将许可证服务端返回的 license 字段完整粘贴到这里。</div>'
                + '    <textarea class="system-dialog-textarea" id="licenseInputValue"></textarea>'
                + '    <div class="system-dialog-footer">'
                + '      <button type="button" class="system-tool-btn" id="licenseInputCancel">取消</button>'
                + '      <button type="button" class="system-tool-btn primary" id="licenseInputSubmit">提交</button>'
                + '    </div>'
                + '  </div>'
                + '</div>',
            success: function (layero, index) {
                licenseToolState.uploadLayerIndex = index;
                layero.find("#licenseInputClose, #licenseInputCancel").on("click", function () {
                    layer.close(index);
                });
                layero.find("#licenseInputSubmit").on("click", function () {
                    submitLicense(layero.find("#licenseInputValue").val());
                });
            },
            end: function () {
                licenseToolState.uploadLayerIndex = null;
            }
        });
    }
    function requestCode() {
        fetchRequestCode(function (value, errorMsg) {
            if (value) {
                openTextDialog("获取请求码", "请求码", value, "请求码中已包含项目名称,直接发给许可证服务端即可。");
                return;
            }
            layer.msg(errorMsg || '获取请求码失败', {time: 2000});
        });
    }
    function fetchRequestCode(callback) {
        $.ajax({
            url: baseUrl + "/license/getRequestCode",
            method: 'GET',
            success: function (res) {
                var value = getResponseValue(res);
                if (res.code == 200) {
                    callback(value ? String(value) : "", "");
                    return;
                }
                callback("", res.msg || '获取请求码失败');
            },
            error: function (xhr) {
                callback("", xhr && xhr.status === 404 ? "获取请求码接口不存在" : "获取请求码失败");
            }
        })
    }
    function getProjectName() {
        $.ajax({
            url: baseUrl + "/license/getProjectName",
            method: 'GET',
            success: function (res) {
                var value = getResponseValue(res);
                if (res.code == 200 && value) {
                    layer.alert(escapeHtml(String(value)), {
                        title: '项目名称'
                    });
                    return;
                }
                layer.msg(res.msg || '获取项目名称失败', {time: 2000});
            },
            error: function () {
                layer.msg('获取项目名称失败', {time: 2000});
            }
        })
    }
    function getServerInfo() {
        $.ajax({
            url: baseUrl + "/license/getServerInfos",
            method: 'GET',
            success: function (res) {
                openTextDialog("获取系统配置", "系统配置信息", res, "新许可证模式下可用于排查当前节点硬件绑定信息。");
            },
            error: function () {
                fallbackServerInfoFromRequestCode();
            }
        })
    }
    function fallbackServerInfoFromRequestCode() {
        fetchRequestCode(function (requestCode, errorMsg) {
            if (!requestCode) {
                layer.msg(errorMsg || '获取系统配置信息失败', {time: 2000});
                return;
            }
            try {
                var decoded = decodeRequestCodePayload(requestCode);
                var node = (((decoded || {}).licenseBind || {}).nodes || [])[0] || {};
                var serverInfo = {
                    subject: decoded.subject || "",
                    nodeId: node.nodeId || "",
                    ipAddress: node.ipAddress || [],
                    macAddress: node.macAddress || [],
                    cpuSerial: node.cpuSerial || "",
                    mainBoardSerial: node.mainBoardSerial || ""
                };
                openTextDialog("获取系统配置", "系统配置信息", serverInfo, "当前接口不可用,已按请求码解析出本机节点配置。");
            } catch (e) {
                layer.msg('获取系统配置信息失败', {time: 2000});
            }
        });
    }
    function decodeRequestCodePayload(requestCode) {
        var normalized = String(requestCode || "").replace(/\s+/g, "");
        if (!normalized) {
            return {};
        }
        var binary = window.atob(normalized);
        var bytes = [];
        for (var i = 0; i < binary.length; i++) {
            bytes.push("%" + ("00" + binary.charCodeAt(i).toString(16)).slice(-2));
        }
        return JSON.parse(decodeURIComponent(bytes.join("")));
    }
    function activateLicense() {
        layer.confirm('确定执行一键激活吗?', {
            title: '提示'
        }, function (index) {
            layer.close(index);
            $.ajax({
                url: baseUrl + "/license/activate",
                method: 'POST',
                success: function (res) {
                    if (res.code == 200) {
                        layer.msg('激活成功', {time: 1500});
                        return;
                    }
                    layer.msg(res.msg || '激活失败', {time: 2000});
                },
                error: function () {
                    layer.msg('激活失败', {time: 2000});
                }
            })
        });
    }
    function submitLicense(licenseBase64) {
        if (!licenseBase64 || !licenseBase64.trim()) {
            layer.msg('许可证内容不能为空', {time: 1500});
            return;
        }
        $.ajax({
            url: baseUrl + "/license/updateLicense",
            headers: {'Content-Type': 'application/json'},
            data: JSON.stringify({license: licenseBase64.trim()}),
            method: 'POST',
            success: function (res) {
                if (res.code == 200) {
                    clearLicensePromptState();
                    clearLicenseQueryFlag();
                    if (licenseToolState.uploadLayerIndex !== null) {
                        layer.close(licenseToolState.uploadLayerIndex);
                    }
                    layer.msg('许可证更新成功', {time: 1000}, function () {
                        parent.location.reload();
                    });
                    return;
                }
                layer.msg(res.msg || '许可证更新失败', {time: 2000});
            },
            error: function (xhr) {
                if (xhr && xhr.status === 403) {
                    layer.msg('许可证录入接口被拦截,请重启服务后重试', {time: 2500});
                    return;
                }
                layer.msg('许可证录入失败', {time: 2000});
            }
        });
    }
    function copyRequestCodeText(text) {
        if (!text) {
            layer.msg('请求码为空', {time: 1500});
            return;
        }
        if (navigator.clipboard && window.isSecureContext) {
            navigator.clipboard.writeText(text).then(function () {
                layer.msg('请求码已复制', {time: 1200});
            }, function () {
                fallbackCopy(text);
            });
            return;
        }
        fallbackCopy(text);
    }
    function fallbackCopy(text) {
        var copyInput = $("<textarea>").val(text).css({
            position: "fixed",
            top: "-1000px"
        }).appendTo("body");
        copyInput[0].select();
        try {
            document.execCommand("copy");
            layer.msg('请求码已复制', {time: 1200});
        } catch (e) {
            layer.msg('复制失败,请手动复制', {time: 1500});
        }
        copyInput.remove();
    }
    $(document).on('click', '.login-btn', function () {
@@ -140,6 +758,8 @@
            method: 'POST',
            success: function (res) {
                if (res.code === 200) {
                    clearLicensePromptState();
                    clearLicenseQueryFlag();
                    localStorage.setItem("token", res.data.token);
                    localStorage.setItem("username", res.data.username);
                    window.location.href = "index.html";
@@ -151,7 +771,8 @@
                    layer.tips(res.msg, '#password', {tips: [4, '#ff0000']});
                } else if (res.code == 20001) {
                    layer.tips(res.msg, '.login-btn', {tips: [3, '#ff0000']});
                    $("#updateLicense").show()
                    showLicenseEntry(res.msg)
                    openLicenseTool(res.msg)
                } else {
                    layer.tips(res.msg, '.login-btn', {tips: [3, '#ff0000']});
                }
@@ -165,37 +786,8 @@
        }
    });
    //更新许可证
    $("#submitLicense").on("click", () => {
        $("#license").click()
    })
    //上传并更新许可证
    $("#license").on("change", (evt) => {
        var files = evt.target.files;
        let formData = new FormData();
        formData.append("file", files[0])
        $.ajax({
            url: baseUrl + "/license/updateLicense",
            headers: {'token': localStorage.getItem('token')},
            data: formData,
            method: 'POST',
            cache: false,
            processData: false,
            contentType: false,
            success: function (res) {
                if (res.code == 200) {
                    layer.msg('更新成功', {time: 1000}, () => {
                        parent.location.reload()
                    });
                } else {
                    layer.msg(res.msg, {time: 2000}, () => {
                        parent.location.reload()
                    })
                }
            }
        })
    $("#openLicenseTool").on("click", () => {
        openLicenseTool($("#licenseEntryMessage").text());
    })
</script>
</body>