#
Junjie
6 天以前 2e7dbd705fc82e8db74b073e55af938d67d8c19f
src/main/webapp/views/login.html
@@ -260,27 +260,57 @@
        }
        .login-submit {
            width: 100%;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            font-size: 16px;
            font-weight: 600;
            border-radius: 16px;
            margin-top: 6px;
            box-shadow: 0 14px 24px rgba(46, 115, 223, 0.28);
        }
        .login-actions {
            display: flex;
            flex-direction: column;
            gap: 12px;
            margin-top: 6px;
        }
        .login-actions .el-button {
            width: 100%;
            margin-left: 0;
            box-sizing: border-box;
        }
        .login-passkey {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            border-radius: 16px;
            border-color: rgba(71, 110, 162, 0.24);
            color: #26496a;
            background: rgba(245, 249, 255, 0.96);
        }
        .login-passkey-tip {
            margin-top: 12px;
            color: #7b8c9d;
            font-size: 12px;
            line-height: 1.7;
        }
        .tools-dialog .el-dialog,
        .text-dialog .el-dialog,
        .upload-dialog .el-dialog {
        .upload-dialog .el-dialog,
        .mfa-dialog .el-dialog {
            border-radius: 20px;
            overflow: hidden;
        }
        .tools-dialog .el-dialog__header,
        .text-dialog .el-dialog__header,
        .upload-dialog .el-dialog__header {
        .upload-dialog .el-dialog__header,
        .mfa-dialog .el-dialog__header {
            padding: 18px 20px 12px;
            border-bottom: 1px solid rgba(222, 230, 239, 0.92);
            background: #f8fbff;
@@ -288,14 +318,16 @@
        .tools-dialog .el-dialog__title,
        .text-dialog .el-dialog__title,
        .upload-dialog .el-dialog__title {
        .upload-dialog .el-dialog__title,
        .mfa-dialog .el-dialog__title {
            font-weight: 700;
            color: #243447;
        }
        .tools-dialog .el-dialog__body,
        .text-dialog .el-dialog__body,
        .upload-dialog .el-dialog__body {
        .upload-dialog .el-dialog__body,
        .mfa-dialog .el-dialog__body {
            padding: 18px 20px 20px;
        }
@@ -357,6 +389,33 @@
            display: inline-flex;
            align-items: center;
            justify-content: center;
        }
        .mfa-tip {
            color: #6f7f92;
            font-size: 13px;
            line-height: 1.7;
            margin-bottom: 12px;
        }
        .mfa-account {
            margin-bottom: 14px;
            padding: 12px 14px;
            border-radius: 14px;
            background: rgba(244, 248, 253, 0.92);
            color: #42576d;
            font-size: 13px;
        }
        .mfa-account strong {
            color: #1e3956;
        }
        .mfa-footer {
            display: flex;
            justify-content: flex-end;
            gap: 10px;
            margin-top: 14px;
        }
        @media (max-width: 640px) {
@@ -423,40 +482,37 @@
    <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>
                <el-form ref="loginForm" class="login-form" :model="loginForm" :rules="loginRules" :validate-on-rule-change="false" @submit.native.prevent>
                    <el-form-item prop="mobile">
                        <el-input v-model.trim="loginForm.mobile" :placeholder="text('login.username', '账号')" clearable @keyup.enter.native="handleLogin">
                            <i slot="prefix" class="el-input__icon el-icon-user"></i>
@@ -467,15 +523,23 @@
                            <i slot="prefix" class="el-input__icon el-icon-lock"></i>
                        </el-input>
                    </el-form-item>
                    <el-button class="login-submit" type="primary" :loading="loginLoading" @click="handleLogin">
                        {{ text('login.submit', '登录') }}
                    </el-button>
                    <div class="login-actions">
                        <el-button class="login-submit" type="primary" :loading="loginLoading" @click="handleLogin">
                            {{ text('login.submit', '登录') }}
                        </el-button>
                        <el-button class="login-passkey" plain :loading="passkeyLoading" @click="handlePasskeyLogin">
                            {{ text('login.passkey.submit', '通行密钥登录') }}
                        </el-button>
                    </div>
                    <div class="login-passkey-tip">
                        {{ text('login.passkey.tip', '支持使用设备生物识别或安全密钥登录。可先输入账号缩小凭证范围,留空则尝试发现式登录。') }}
                    </div>
                </el-form>
            </div>
        </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">
@@ -494,31 +558,53 @@
        </div>
    </el-dialog>
    <el-dialog
        class="mfa-dialog"
        :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">{{ 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" :validate-on-rule-change="false" label-width="82px" size="small" @submit.native.prevent>
            <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">{{ text('login.mfa.cancel', '取消') }}</el-button>
            <el-button type="primary" :loading="mfaLoading" @click="handleMfaLogin">{{ text('login.mfa.submit', '验证并登录') }}</el-button>
        </div>
    </el-dialog>
    <el-dialog class="text-dialog" :title="textDialog.title" :visible.sync="textDialogVisible" width="720px" append-to-body>
        <div class="dialog-text-label">{{ textDialog.label }}</div>
        <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>
</body>
<script type="text/javascript" src="../static/js/jquery/jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="../static/js/tools/md5.js"></script>
<script type="text/javascript" src="../static/js/common.js?v=20260309_i18n_fix1"></script>
<script type="text/javascript" src="../static/js/common.js"></script>
<script type="text/javascript" src="../static/js/webauthn-utils.js"></script>
<script type="text/javascript" src="../static/vue/js/vue.min.js"></script>
<script type="text/javascript" src="../static/vue/element/element.js"></script>
<script type="text/javascript" src="../static/js/login/login.js?v=20260310_login_vue"></script>
<script type="text/javascript" src="../static/js/login/login.js?v=20260311_login_passkey"></script>
</html>