#
Junjie
昨天 ab6dd7555377d0d5fe3a78149c0cdb98e2f15275
#
4个文件已修改
236 ■■■■ 已修改文件
src/main/resources/i18n/en-US/messages.properties 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/i18n/zh-CN/messages.properties 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/static/js/login/login.js 103 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/login.html 53 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/i18n/en-US/messages.properties
@@ -16,6 +16,17 @@
common.workPage=Work Page
common.businessPage=Business Page
login.title=WCS System V3.0
login.subtitle=Please enter your account and password to access the system.
login.hero.title=The WCS keeps device scheduling, task execution, and on-site monitoring within the same business workflow.
login.hero.subtitle=The Warehouse Control System is built for automated AS/RS execution, coordinating cranes, RGVs, conveyor stations, task commands, location status, and log tracing through unified dispatch and visualization. It helps warehouse operations stay stable, traceable, and collaborative. Zhejiang Zhongyang Warehouse Technology Co., Ltd. focuses on automated AS/RS and intelligent intralogistics systems, covering solution design, software control, equipment integration, and project delivery.
login.hero.metric.dispatch.title=Dispatch
login.hero.metric.dispatch.desc=Unified orchestration of on-site execution tasks
login.hero.metric.trace.title=Traceability
login.hero.metric.trace.desc=End-to-end traceability across jobs, devices, and logs
login.hero.metric.integration.title=Integration
login.hero.metric.integration.desc=Integrates WMS, devices, and business rules
login.hero.company.name=Zhejiang Zhongyang Warehouse Technology Co., Ltd.
login.hero.company.solution=Automated AS/RS and intelligent logistics system solutions
login.username=Account
login.password=Password
login.submit=Sign In
@@ -37,6 +48,35 @@
login.serverInfo.title=System Configuration
login.serverInfo.label=System Configuration
login.serverInfo.tip=Legacy projects can still use this hardware JSON to apply for a license.
login.dialog.close=Close
login.mfa.title=MFA Verification
login.mfa.tip=Account and password verified. Enter the 6-digit code from your authenticator app to continue signing in.
login.mfa.currentAccount=Current account:
login.mfa.codeLabel=Verification Code
login.mfa.codePlaceholder=Please enter the 6-digit code
login.mfa.cancel=Cancel
login.mfa.submit=Verify and Sign In
login.license.label=License Base64
login.license.tip=Paste the full license field returned by the license service here.
login.license.submit=Submit
login.license.success=License updated successfully
login.validation.usernameRequired=Please enter the account
login.validation.passwordRequired=Please enter the password
login.validation.mfaRequired=Please enter the 6-digit verification code
login.validation.mfaInvalid=Please enter a 6-digit numeric verification code
login.error.loginFailed=Login failed
login.error.mfaTicketExpired=The login ticket has expired. Please sign in again
login.error.mfaFailed=Verification failed
login.error.requestCodeFailed=Failed to get request code
login.error.serverInfoFailed=Failed to get system configuration
login.error.licenseEmpty=License content cannot be empty
login.error.licenseUpdateFailed=Failed to update license
login.error.licenseImportFailed=Failed to import license
login.activate.confirm=Are you sure you want to activate now?
login.activate.success=Activation successful
login.activate.failed=Activation failed
login.projectName.title=Project Name
login.error.projectNameFailed=Failed to get project name
index.searchMenu=Search menu
index.noMatchedMenu=No matching menus
index.noAvailableMenu=No available menus for current account
src/main/resources/i18n/zh-CN/messages.properties
@@ -16,6 +16,17 @@
common.workPage=工作页面
common.businessPage=业务页面
login.title=WCS系统V3.0
login.subtitle=请输入账号和密码进入系统。
login.hero.title=WCS系统让设备调度、任务执行与现场监控保持在同一套业务链路中。
login.hero.subtitle=Warehouse Control System 面向自动化立体仓库现场执行层,围绕堆垛机、RGV、输送站台、任务指令、库位状态和日志追踪进行统一调度与可视化管理,帮助仓储系统实现稳定、可追溯、可联动的作业控制。浙江中扬立库技术有限公司长期专注于自动化立体仓库与智能物流系统建设,覆盖方案设计、软件控制、设备集成与项目实施交付。
login.hero.metric.dispatch.title=调度
login.hero.metric.dispatch.desc=统一编排现场执行任务
login.hero.metric.trace.title=追溯
login.hero.metric.trace.desc=作业、设备、日志全链路留痕
login.hero.metric.integration.title=集成
login.hero.metric.integration.desc=对接WMS、设备与业务规则
login.hero.company.name=浙江中扬立库技术有限公司
login.hero.company.solution=自动化立体仓库与智能物流系统解决方案
login.username=账号
login.password=密码
login.submit=登录
@@ -37,6 +48,35 @@
login.serverInfo.title=获取系统配置
login.serverInfo.label=系统配置信息
login.serverInfo.tip=老项目仍可继续使用这份硬件信息 JSON 申请许可证。
login.dialog.close=关闭
login.mfa.title=MFA二次验证
login.mfa.tip=账号密码已通过,请输入身份验证器中的 6 位动态验证码后继续登录。
login.mfa.currentAccount=当前账号:
login.mfa.codeLabel=验证码
login.mfa.codePlaceholder=请输入6位动态码
login.mfa.cancel=取消
login.mfa.submit=验证并登录
login.license.label=许可证 Base64
login.license.tip=将许可证服务端返回的 license 字段完整粘贴到这里。
login.license.submit=提交
login.license.success=许可证更新成功
login.validation.usernameRequired=请输入账号
login.validation.passwordRequired=请输入密码
login.validation.mfaRequired=请输入6位验证码
login.validation.mfaInvalid=请输入6位数字验证码
login.error.loginFailed=登录失败
login.error.mfaTicketExpired=登录票据已失效,请重新登录
login.error.mfaFailed=验证失败
login.error.requestCodeFailed=获取请求码失败
login.error.serverInfoFailed=获取系统配置信息失败
login.error.licenseEmpty=许可证内容不能为空
login.error.licenseUpdateFailed=许可证更新失败
login.error.licenseImportFailed=许可证录入失败
login.activate.confirm=确定执行一键激活吗?
login.activate.success=激活成功
login.activate.failed=激活失败
login.projectName.title=项目名称
login.error.projectNameFailed=获取项目名称失败
index.searchMenu=搜索菜单
index.noMatchedMenu=没有匹配菜单
index.noAvailableMenu=当前账号没有可用菜单
src/main/webapp/static/js/login/login.js
@@ -69,19 +69,50 @@
                var localeTick = this.localeTick;
                void localeTick;
                if (window.WCS_I18N && typeof window.WCS_I18N.t === "function") {
                    return window.WCS_I18N.t(key);
                    var translated = window.WCS_I18N.t(key);
                    if (translated && translated !== key) {
                        return translated;
                    }
                }
                return fallback || key;
            },
            refreshRuleMessages: function () {
                var vm = this;
                vm.loginRules = {
                    mobile: [
                        { required: true, message: vm.text("login.validation.usernameRequired", "请输入账号"), trigger: "blur" }
                    ],
                    password: [
                        { required: true, message: vm.text("login.validation.passwordRequired", "请输入密码"), trigger: "blur" }
                    ]
                };
                vm.mfaRules = {
                    code: [
                        { required: true, message: vm.text("login.validation.mfaRequired", "请输入6位验证码"), trigger: "blur" },
                        {
                            validator: function (rule, value, callback) {
                                if (!/^\d{6}$/.test(String(value || "").trim())) {
                                    callback(new Error(vm.text("login.validation.mfaInvalid", "请输入6位数字验证码")));
                                    return;
                                }
                                callback();
                            },
                            trigger: "blur"
                        }
                    ]
                };
            },
            initLanguageSwitch: function () {
                var vm = this;
                if (!window.WCS_I18N || typeof window.WCS_I18N.onReady !== "function") {
                    vm.refreshRuleMessages();
                    return;
                }
                window.WCS_I18N.onReady(function (i18n) {
                    vm.localeOptions = i18n.getLocaleOptions();
                    vm.currentLocale = i18n.getLocale();
                    document.title = i18n.t("login.title");
                    vm.refreshRuleMessages();
                    vm.localeTick++;
                });
            },
@@ -138,13 +169,13 @@
                                vm.openMfaDialog(payload);
                                return;
                            }
                            vm.finishLogin(payload);
                            return;
                        }
                        vm.$message.error(res.msg || "登录失败");
                        vm.finishLogin(payload);
                        return;
                    }
                        vm.$message.error(res.msg || vm.text("login.error.loginFailed", "登录失败"));
                    },
                    error: function () {
                        vm.$message.error("登录失败");
                        vm.$message.error(vm.text("login.error.loginFailed", "登录失败"));
                    },
                    complete: function () {
                        vm.loginLoading = false;
@@ -192,7 +223,7 @@
            submitMfaLogin: function () {
                var vm = this;
                if (!vm.mfaPending.ticket) {
                    vm.$message.error("登录票据已失效,请重新登录");
                    vm.$message.error(vm.text("login.error.mfaTicketExpired", "登录票据已失效,请重新登录"));
                    vm.closeMfaDialog();
                    return;
                }
@@ -209,10 +240,10 @@
                            vm.finishLogin(res.data || {});
                            return;
                        }
                        vm.$message.error(res.msg || "验证失败");
                        vm.$message.error(res.msg || vm.text("login.error.mfaFailed", "验证失败"));
                    },
                    error: function () {
                        vm.$message.error("验证失败");
                        vm.$message.error(vm.text("login.error.mfaFailed", "验证失败"));
                    },
                    complete: function () {
                        vm.mfaLoading = false;
@@ -250,9 +281,9 @@
                    textarea.select();
                    document.execCommand("copy");
                    document.body.removeChild(textarea);
                    this.$message.success("已复制到剪贴板");
                    this.$message.success(this.text("login.dialog.copied", "已复制到剪贴板"));
                } catch (err) {
                    this.$message.error("复制失败");
                    this.$message.error(this.text("login.dialog.copyFailed", "复制失败"));
                }
            },
            copyText: function () {
@@ -260,7 +291,7 @@
                var text = vm.textDialog.text || "";
                if (navigator.clipboard && navigator.clipboard.writeText) {
                    navigator.clipboard.writeText(text).then(function () {
                        vm.$message.success("已复制到剪贴板");
                        vm.$message.success(vm.text("login.dialog.copied", "已复制到剪贴板"));
                    }).catch(function () {
                        vm.fallbackCopy(text);
                    });
@@ -275,13 +306,18 @@
                    method: "GET",
                    success: function (res) {
                        if (Number(res.code) === 200) {
                            vm.openTextDialog("获取请求码", "请求码", res.msg || "", "请求码中已包含项目名称,直接发给许可证服务端即可。");
                            vm.openTextDialog(
                                vm.text("login.requestCode.title", "获取请求码"),
                                vm.text("login.requestCode.label", "请求码"),
                                res.msg || "",
                                vm.text("login.requestCode.tip", "请求码中已包含项目名称,直接发给许可证服务端即可。")
                            );
                            return;
                        }
                        vm.$message.error(res.msg || "获取请求码失败");
                        vm.$message.error(res.msg || vm.text("login.error.requestCodeFailed", "获取请求码失败"));
                    },
                    error: function () {
                        vm.$message.error("获取请求码失败");
                        vm.$message.error(vm.text("login.error.requestCodeFailed", "获取请求码失败"));
                    }
                });
            },
@@ -291,17 +327,22 @@
                    url: baseUrl + "/license/getServerInfos",
                    method: "GET",
                    success: function (res) {
                        vm.openTextDialog("获取系统配置", "系统配置信息", res, "老项目仍可继续使用这份硬件信息 JSON 申请许可证。");
                        vm.openTextDialog(
                            vm.text("login.serverInfo.title", "获取系统配置"),
                            vm.text("login.serverInfo.label", "系统配置信息"),
                            res,
                            vm.text("login.serverInfo.tip", "老项目仍可继续使用这份硬件信息 JSON 申请许可证。")
                        );
                    },
                    error: function () {
                        vm.$message.error("获取系统配置信息失败");
                        vm.$message.error(vm.text("login.error.serverInfoFailed", "获取系统配置信息失败"));
                    }
                });
            },
            submitLicense: function () {
                var vm = this;
                if (!vm.licenseBase64) {
                    vm.$message.warning("许可证内容不能为空");
                    vm.$message.warning(vm.text("login.error.licenseEmpty", "许可证内容不能为空"));
                    return;
                }
                ajaxJson({
@@ -313,35 +354,35 @@
                        if (Number(res.code) === 200) {
                            vm.uploadDialogVisible = false;
                            vm.licenseBase64 = "";
                            vm.$message.success("许可证更新成功");
                            vm.$message.success(vm.text("login.license.success", "许可证更新成功"));
                            return;
                        }
                        vm.$message.error(res.msg || "许可证更新失败");
                        vm.$message.error(res.msg || vm.text("login.error.licenseUpdateFailed", "许可证更新失败"));
                    },
                    error: function () {
                        vm.$message.error("许可证录入失败");
                        vm.$message.error(vm.text("login.error.licenseImportFailed", "许可证录入失败"));
                    }
                });
            },
            activateLicense: function () {
                var vm = this;
                vm.$confirm("确定执行一键激活吗?", "提示", {
                vm.$confirm(vm.text("login.activate.confirm", "确定执行一键激活吗?"), vm.text("common.prompt", "提示"), {
                    type: "warning",
                    confirmButtonText: "确定",
                    cancelButtonText: "取消"
                    confirmButtonText: vm.text("common.ok", "确定"),
                    cancelButtonText: vm.text("login.mfa.cancel", "取消")
                }).then(function () {
                    ajaxJson({
                        url: baseUrl + "/license/activate",
                        method: "POST",
                        success: function (res) {
                            if (Number(res.code) === 200) {
                                vm.$message.success("激活成功");
                                vm.$message.success(vm.text("login.activate.success", "激活成功"));
                                return;
                            }
                            vm.$message.error(res.msg || "激活失败");
                            vm.$message.error(res.msg || vm.text("login.activate.failed", "激活失败"));
                        },
                        error: function () {
                            vm.$message.error("激活失败");
                            vm.$message.error(vm.text("login.activate.failed", "激活失败"));
                        }
                    });
                }).catch(function () {
@@ -354,15 +395,15 @@
                    method: "GET",
                    success: function (res) {
                        if (Number(res.code) === 200) {
                            vm.$alert(res.msg || "", "项目名称", {
                                confirmButtonText: "确定"
                            vm.$alert(res.msg || "", vm.text("login.projectName.title", "项目名称"), {
                                confirmButtonText: vm.text("common.ok", "确定")
                            });
                            return;
                        }
                        vm.$message.error(res.msg || "获取项目名称失败");
                        vm.$message.error(res.msg || vm.text("login.error.projectNameFailed", "获取项目名称失败"));
                    },
                    error: function () {
                        vm.$message.error("获取项目名称失败");
                        vm.$message.error(vm.text("login.error.projectNameFailed", "获取项目名称失败"));
                    }
                });
            }
src/main/webapp/views/login.html
@@ -454,37 +454,34 @@
    <div class="login-layout">
        <section class="hero-panel animate__animated animate__fadeInLeft">
            <div class="brand-chip">Zoneyung WCS</div>
            <div class="hero-title">WCS系统让设备调度、任务执行与现场监控保持在同一套业务链路中。</div>
            <div class="hero-title">{{ text('login.hero.title', 'WCS系统让设备调度、任务执行与现场监控保持在同一套业务链路中。') }}</div>
            <div class="hero-subtitle">
                Warehouse Control System 面向自动化立体仓库现场执行层,围绕堆垛机、RGV、输送站台、任务指令、
                库位状态和日志追踪进行统一调度与可视化管理,帮助仓储系统实现稳定、可追溯、可联动的作业控制。
                浙江中扬立库技术有限公司长期专注于自动化立体仓库与智能物流系统建设,覆盖方案设计、软件控制、
                设备集成与项目实施交付。
                {{ text('login.hero.subtitle', 'Warehouse Control System 面向自动化立体仓库现场执行层,围绕堆垛机、RGV、输送站台、任务指令、库位状态和日志追踪进行统一调度与可视化管理,帮助仓储系统实现稳定、可追溯、可联动的作业控制。浙江中扬立库技术有限公司长期专注于自动化立体仓库与智能物流系统建设,覆盖方案设计、软件控制、设备集成与项目实施交付。') }}
            </div>
            <div class="hero-metrics">
                <div class="metric-card">
                    <div class="metric-value">调度</div>
                    <div class="metric-label">统一编排现场执行任务</div>
                    <div class="metric-value">{{ text('login.hero.metric.dispatch.title', '调度') }}</div>
                    <div class="metric-label">{{ text('login.hero.metric.dispatch.desc', '统一编排现场执行任务') }}</div>
                </div>
                <div class="metric-card">
                    <div class="metric-value">追溯</div>
                    <div class="metric-label">作业、设备、日志全链路留痕</div>
                    <div class="metric-value">{{ text('login.hero.metric.trace.title', '追溯') }}</div>
                    <div class="metric-label">{{ text('login.hero.metric.trace.desc', '作业、设备、日志全链路留痕') }}</div>
                </div>
                <div class="metric-card">
                    <div class="metric-value">集成</div>
                    <div class="metric-label">对接WMS、设备与业务规则</div>
                    <div class="metric-value">{{ text('login.hero.metric.integration.title', '集成') }}</div>
                    <div class="metric-label">{{ text('login.hero.metric.integration.desc', '对接WMS、设备与业务规则') }}</div>
                </div>
            </div>
            <div class="hero-footer">
                <span>浙江中扬立库技术有限公司</span>
                <span>自动化立体仓库与智能物流系统解决方案</span>
                <span>{{ text('login.hero.company.name', '浙江中扬立库技术有限公司') }}</span>
                <span>{{ text('login.hero.company.solution', '自动化立体仓库与智能物流系统解决方案') }}</span>
            </div>
        </section>
        <section class="login-card animate__animated animate__fadeInUp">
            <div class="login-head">
                <h1 class="login-title" @click="handleTitleClick">{{ text('login.title', 'WCS系统V3.0') }}</h1>
                <div class="login-subtitle">请输入账号和密码进入系统。</div>
                <div class="login-subtitle">{{ text('login.subtitle', '请输入账号和密码进入系统。') }}</div>
            </div>
            <div class="login-body">
                <el-form ref="loginForm" class="login-form" :model="loginForm" :rules="loginRules" @submit.native.prevent>
@@ -506,7 +503,7 @@
        </section>
    </div>
    <el-dialog class="tools-dialog" title="系统工具" :visible.sync="toolsDialogVisible" width="560px" :close-on-click-modal="true" append-to-body>
    <el-dialog class="tools-dialog" :title="text('login.tools.title', '系统工具')" :visible.sync="toolsDialogVisible" width="560px" :close-on-click-modal="true" append-to-body>
        <div class="tool-group">
            <div class="tool-title">{{ text('login.tools.recommended', '推荐操作') }}</div>
            <div class="tool-actions">
@@ -527,22 +524,22 @@
    <el-dialog
        class="mfa-dialog"
        title="MFA二次验证"
        :title="text('login.mfa.title', 'MFA二次验证')"
        :visible.sync="mfaDialogVisible"
        width="420px"
        :close-on-click-modal="false"
        @close="closeMfaDialog"
        append-to-body>
        <div class="mfa-tip">账号密码已通过,请输入身份验证器中的 6 位动态验证码后继续登录。</div>
        <div class="mfa-account">当前账号:<strong>{{ mfaPending.username || loginForm.mobile || '--' }}</strong></div>
        <div class="mfa-tip">{{ text('login.mfa.tip', '账号密码已通过,请输入身份验证器中的 6 位动态验证码后继续登录。') }}</div>
        <div class="mfa-account">{{ text('login.mfa.currentAccount', '当前账号:') }}<strong>{{ mfaPending.username || loginForm.mobile || '--' }}</strong></div>
        <el-form ref="mfaForm" :model="mfaForm" :rules="mfaRules" label-width="82px" size="small" @submit.native.prevent>
            <el-form-item label="验证码" prop="code">
                <el-input v-model.trim="mfaForm.code" maxlength="6" placeholder="请输入6位动态码" @keyup.enter.native="handleMfaLogin"></el-input>
            <el-form-item :label="text('login.mfa.codeLabel', '验证码')" prop="code">
                <el-input v-model.trim="mfaForm.code" maxlength="6" :placeholder="text('login.mfa.codePlaceholder', '请输入6位动态码')" @keyup.enter.native="handleMfaLogin"></el-input>
            </el-form-item>
        </el-form>
        <div class="mfa-footer">
            <el-button @click="closeMfaDialog">取消</el-button>
            <el-button type="primary" :loading="mfaLoading" @click="handleMfaLogin">验证并登录</el-button>
            <el-button @click="closeMfaDialog">{{ text('login.mfa.cancel', '取消') }}</el-button>
            <el-button type="primary" :loading="mfaLoading" @click="handleMfaLogin">{{ text('login.mfa.submit', '验证并登录') }}</el-button>
        </div>
    </el-dialog>
@@ -551,18 +548,18 @@
        <div v-if="textDialog.tip" class="dialog-text-tip">{{ textDialog.tip }}</div>
        <el-input v-model="textDialog.text" type="textarea" :rows="10" readonly></el-input>
        <div class="text-footer">
            <el-button @click="textDialogVisible = false">关闭</el-button>
            <el-button @click="textDialogVisible = false">{{ text('login.dialog.close', '关闭') }}</el-button>
            <el-button type="primary" @click="copyText">{{ text('copy', '复制') }}</el-button>
        </div>
    </el-dialog>
    <el-dialog class="upload-dialog" title="录入许可证" :visible.sync="uploadDialogVisible" width="760px" append-to-body>
        <div class="dialog-text-label">许可证 Base64</div>
        <div class="dialog-text-tip">将许可证服务端返回的 license 字段完整粘贴到这里。</div>
    <el-dialog class="upload-dialog" :title="text('login.tools.uploadLicense', '录入许可证')" :visible.sync="uploadDialogVisible" width="760px" append-to-body>
        <div class="dialog-text-label">{{ text('login.license.label', '许可证 Base64') }}</div>
        <div class="dialog-text-tip">{{ text('login.license.tip', '将许可证服务端返回的 license 字段完整粘贴到这里。') }}</div>
        <el-input v-model.trim="licenseBase64" type="textarea" :rows="10"></el-input>
        <div class="upload-footer">
            <el-button @click="uploadDialogVisible = false">取消</el-button>
            <el-button type="primary" @click="submitLicense">提交</el-button>
            <el-button @click="uploadDialogVisible = false">{{ text('login.mfa.cancel', '取消') }}</el-button>
            <el-button type="primary" @click="submitLicense">{{ text('login.license.submit', '提交') }}</el-button>
        </div>
    </el-dialog>
</div>