xiaoyi
3 天以前 a59f15c41afa6ddf106c4bf9c163d9e322d314cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
<script lang="ts" setup>
import type { VbenFormSchema } from '@vben/common-ui';
import type { AuthApi } from '#/api/core/auth';
 
import { computed, ref } from 'vue';
 
import { AuthenticationLogin, Verification, z } from '@vben/common-ui';
import { isCaptchaEnable } from '@vben/hooks';
import { $t } from '@vben/locales';
 
import { checkCaptcha, getCaptcha } from '#/api/core/auth';
import { useAuthStore } from '#/store';
 
defineOptions({ name: 'Login' });
 
const authStore = useAuthStore();
const captchaEnable = isCaptchaEnable();
const loginRef = ref();
const verifyRef = ref();
const captchaType = 'blockPuzzle';
 
async function handleLogin(values: AuthApi.LoginParams) {
  if (captchaEnable) {
    verifyRef.value?.show();
    return;
  }
  await authStore.authLogin('username', values);
}
 
async function handleVerifySuccess({ captchaVerification }: { captchaVerification: string }) {
  try {
    await authStore.authLogin('username', {
      ...(await loginRef.value.getFormApi().getValues()),
      captchaVerification,
    });
  } catch (error) {
    console.error('验证码登录失败:', error);
  }
}
 
const formSchema = computed((): VbenFormSchema[] => [
  {
    component: 'VbenInput',
    componentProps: { placeholder: $t('authentication.usernameTip') },
    fieldName: 'username',
    label: $t('authentication.username'),
    rules: z.string().min(1, { message: $t('authentication.usernameTip') }).default(import.meta.env.VITE_APP_DEFAULT_USERNAME),
  },
  {
    component: 'VbenInputPassword',
    componentProps: { placeholder: $t('authentication.passwordTip') },
    fieldName: 'password',
    label: $t('authentication.password'),
    rules: z.string().min(1, { message: $t('authentication.passwordTip') }).default(import.meta.env.VITE_APP_DEFAULT_PASSWORD),
  },
]);
</script>
 
<template>
  <main class="mes-login-page">
    <section class="industrial-hero" aria-label="新疆新高电力科技有限公司">
      <div class="hero-grid" aria-hidden="true" />
      <div class="hero-glow hero-glow-one" aria-hidden="true" />
      <div class="hero-glow hero-glow-two" aria-hidden="true" />
 
      <header class="company-bar">
        <div class="company-brand">
          <img class="company-logo" src="/logos/xgdl.svg" alt="新高电力" width="48" height="48" />
          <div class="company-name">
            <strong>新高电力</strong>
            <span>新疆新高电力科技有限公司</span>
          </div>
        </div>
        <div class="system-mark"><span class="status-dot" /> INDUSTRIAL DIGITAL PLATFORM</div>
      </header>
 
      <div class="hero-content">
        <div class="hero-kicker"><span>MES · MANUFACTURING EXECUTION SYSTEM</span><i /></div>
        <h1>新高智造<br /><em>运营中心</em></h1>
        <p class="hero-subtitle">智能制造数字化管理平台</p>
 
        <div class="capability-list" aria-label="平台能力">
          <div class="capability-item"><span class="capability-index">01</span><span>生产数字化</span><b>PRODUCTION</b></div>
          <div class="capability-item"><span class="capability-index">02</span><span>设备智能化</span><b>EQUIPMENT</b></div>
          <div class="capability-item"><span class="capability-index">03</span><span>质量可追溯</span><b>QUALITY</b></div>
          <div class="capability-item"><span class="capability-index">04</span><span>数据智能决策</span><b>DECISION</b></div>
        </div>
      </div>
 
      <div class="factory-visual" aria-hidden="true">
        <div class="visual-label label-top">SMART FACTORY / XG POWER <span>01</span></div>
        <svg class="factory-svg" viewBox="0 0 920 330" fill="none" xmlns="http://www.w3.org/2000/svg" role="presentation">
          <path d="M0 286H920" stroke="rgba(190,218,238,.18)" />
          <path d="M0 302H920" stroke="rgba(190,218,238,.1)" />
          <path d="M82 284V178L196 112L310 178V284" fill="rgba(19,66,103,.64)" stroke="rgba(143,187,218,.48)" stroke-width="2" />
          <path d="M82 178H310M196 112V284" stroke="rgba(143,187,218,.38)" stroke-width="2" />
          <path d="M132 284V210H174V284M218 284V210H260V284" stroke="rgba(116,173,216,.46)" stroke-width="2" />
          <path d="M349 284V156L428 117L507 156V284" fill="rgba(17,60,96,.7)" stroke="rgba(143,187,218,.43)" stroke-width="2" />
          <path d="M349 156H507M428 117V284M384 284V214H414V284M445 284V214H475V284" stroke="rgba(143,187,218,.36)" stroke-width="2" />
          <path d="M563 284V190H718V284" fill="rgba(12,52,86,.72)" stroke="rgba(143,187,218,.4)" stroke-width="2" />
          <path d="M563 190L602 166H718L680 190" fill="rgba(34,88,128,.55)" stroke="rgba(143,187,218,.4)" stroke-width="2" />
          <rect x="593" y="211" width="46" height="73" fill="rgba(10,36,61,.62)" stroke="rgba(82,164,231,.7)" />
          <rect x="650" y="211" width="46" height="73" fill="rgba(10,36,61,.62)" stroke="rgba(82,164,231,.7)" />
          <path d="M616 217V277M673 217V277M600 234H632M657 234H689M600 250H632M657 250H689" stroke="rgba(112,196,255,.55)" stroke-width="2" />
          <path d="M776 284L823 90L870 284M800 190H846M793 221H853M786 253H860M805 162H841M813 130H833" stroke="rgba(142,191,224,.5)" stroke-width="2" />
          <path d="M823 90L790 65M823 90L856 65M790 65H856M720 122C756 94 786 78 823 78C860 78 879 94 920 122" stroke="rgba(83,173,236,.62)" stroke-width="2" />
          <path d="M18 244C140 213 233 251 326 224C443 190 532 218 638 181C750 142 810 169 920 130" stroke="#3C9EE8" stroke-width="2" stroke-dasharray="4 8" />
          <circle cx="638" cy="181" r="5" fill="#78C7FF" /><circle cx="823" cy="78" r="5" fill="#78C7FF" />
          <path d="M196 86V34M428 91V31M603 142V82" stroke="rgba(129,192,232,.28)" stroke-dasharray="3 5" />
        </svg>
        <div class="data-widget widget-left"><span>LINE STATUS</span><strong>ALL SYSTEMS NORMAL</strong><i /></div>
        <div class="data-widget widget-right"><span>REAL-TIME OUTPUT</span><strong>98.6%</strong><em>+2.4%</em></div>
      </div>
 
      <footer class="hero-footer"><span>系统版本 v3.2.0</span><span>运行区域 · 新疆生产建设兵团</span><span>© 2026 新高电力科技</span></footer>
    </section>
 
    <section class="login-side" aria-label="登录新高智造运营中心">
      <div class="login-side-inner">
        <div class="login-card">
          <div class="login-card-top"><span class="card-kicker">AUTHORIZED ACCESS</span><span class="lock-mark">SECURE</span></div>
          <AuthenticationLogin
            ref="loginRef"
            :form-schema="formSchema"
            :loading="authStore.loginLoading"
            :show-code-login="false"
            :show-qrcode-login="false"
            :show-third-party-login="false"
            :show-register="false"
            :show-forget-password="true"
            :show-doc-link="false"
            :show-remember-me="true"
            sub-title="统一连接生产、设备与质量数据"
            title="欢迎登录"
            @submit="handleLogin"
          />
          <div class="login-card-footer"><span>新高智造运营中心</span><span>企业内部系统</span></div>
        </div>
        <p class="security-caption"><span class="status-dot blue" /> 系统运行稳定 · 数据传输已加密</p>
      </div>
    </section>
 
    <Verification
      v-if="captchaEnable"
      ref="verifyRef"
      :captcha-type="captchaType"
      :check-captcha-api="checkCaptcha"
      :get-captcha-api="getCaptcha"
      :img-size="{ width: '400px', height: '200px' }"
      mode="pop"
      @on-success="handleVerifySuccess"
    />
  </main>
</template>
 
<style scoped>
.mes-login-page {
  --deep-blue: #061b33;
  --panel-blue: #0a2948;
  --tech-blue: #1677ff;
  --line-blue: rgb(151 201 235 / 24%);
  --text-blue: #dbeaf6;
  --ink: #12263d;
  --muted: #6d7b8c;
  --surface: #f5f7fa;
  display: grid;
  grid-template-columns: minmax(0, 58%) minmax(420px, 42%);
  min-height: 100dvh;
  overflow: hidden;
  color: var(--ink);
  background: var(--surface);
  font-family: "Avenir Next", "PingFang SC", "Microsoft YaHei", sans-serif;
}
 
.industrial-hero { position: relative; display: flex; min-height: 100dvh; flex-direction: column; padding: clamp(30px, 4vw, 64px) clamp(34px, 5.2vw, 100px) 26px; overflow: hidden; color: var(--text-blue); background: var(--deep-blue); isolation: isolate; }
.industrial-hero::before { position: absolute; inset: 0; z-index: -3; background: linear-gradient(125deg, rgb(6 27 51 / 100%) 0%, rgb(8 42 73 / 96%) 58%, rgb(5 24 45 / 98%) 100%); content: ''; }
.hero-grid { position: absolute; inset: 0; z-index: -2; opacity: .32; background-image: linear-gradient(rgb(150 206 241 / 9%) 1px, transparent 1px), linear-gradient(90deg, rgb(150 206 241 / 9%) 1px, transparent 1px); background-size: 44px 44px; mask-image: linear-gradient(to bottom, transparent 2%, black 24%, black 82%, transparent); }
.hero-glow { position: absolute; z-index: -1; border-radius: 50%; filter: blur(2px); pointer-events: none; }
.hero-glow-one { right: 12%; bottom: 17%; width: 410px; height: 410px; background: radial-gradient(circle, rgb(23 119 255 / 22%), transparent 70%); }
.hero-glow-two { left: 13%; top: 20%; width: 270px; height: 270px; background: radial-gradient(circle, rgb(56 145 220 / 15%), transparent 70%); }
.company-bar { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; }
.company-brand { display: flex; align-items: center; gap: 14px; }
.company-logo { flex: none; border-radius: 10px; }
.company-name { display: flex; flex-direction: column; gap: 5px; }
.company-name strong { color: #fff; font-size: 18px; font-weight: 650; letter-spacing: .08em; }
.company-name span { color: rgb(210 229 245 / 65%); font-size: 11px; letter-spacing: .06em; }
.system-mark, .hero-kicker, .visual-label, .data-widget, .hero-footer, .card-kicker, .lock-mark { font-family: "IBM Plex Mono", "SFMono-Regular", Consolas, monospace; }
.system-mark { display: flex; align-items: center; gap: 9px; padding-top: 7px; color: rgb(201 225 244 / 58%); font-size: 9px; letter-spacing: .14em; }
.status-dot { display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: #65c18c; box-shadow: 0 0 0 4px rgb(101 193 140 / 13%); }
.hero-content { position: relative; z-index: 1; margin: auto 0 5vh; }
.hero-kicker { display: flex; align-items: center; gap: 12px; color: rgb(183 215 237 / 64%); font-size: 10px; letter-spacing: .15em; }
.hero-kicker i { display: block; width: 42px; height: 1px; background: var(--tech-blue); }
.hero-content h1 { margin: 22px 0 12px; color: #fff; font-size: clamp(46px, 5.2vw, 82px); font-weight: 600; line-height: 1.08; letter-spacing: -.065em; }
.hero-content h1 em { color: #8ecbff; font-style: normal; }
.hero-subtitle { margin: 0; color: rgb(216 233 247 / 80%); font-size: clamp(16px, 1.4vw, 20px); letter-spacing: .12em; }
.capability-list { display: grid; grid-template-columns: repeat(4, minmax(100px, 1fr)); max-width: 660px; margin-top: 42px; border-top: 1px solid var(--line-blue); }
.capability-item { position: relative; display: grid; grid-template-columns: auto 1fr; gap: 7px 10px; padding: 15px 16px 0 0; border-right: 1px solid var(--line-blue); color: #e5f1fb; font-size: 14px; }
.capability-item:not(:first-child) { padding-left: 16px; }
.capability-item:last-child { border-right: 0; }
.capability-index { color: #63aef2; font-family: "IBM Plex Mono", monospace; font-size: 10px; }
.capability-item b { grid-column: 2; color: rgb(184 213 235 / 48%); font-family: "IBM Plex Mono", monospace; font-size: 8px; font-weight: 400; letter-spacing: .08em; }
.factory-visual { position: absolute; right: 2%; bottom: 64px; left: 2%; z-index: 0; opacity: .9; pointer-events: none; }
.factory-svg { display: block; width: 100%; height: auto; }
.visual-label { display: flex; justify-content: space-between; padding: 0 7% 6px; color: rgb(161 201 229 / 48%); font-size: 8px; letter-spacing: .12em; }
.visual-label span { color: #64b2ee; }
.data-widget { position: absolute; bottom: 48px; display: flex; flex-direction: column; gap: 5px; min-width: 142px; padding: 9px 11px; border: 1px solid rgb(128 188 229 / 27%); color: rgb(193 223 242 / 60%); background: rgb(6 28 50 / 74%); font-size: 8px; letter-spacing: .08em; }
.data-widget strong { color: #e4f2fd; font-size: 10px; font-weight: 500; }
.data-widget i { position: absolute; right: 10px; top: 11px; width: 5px; height: 5px; border-radius: 50%; background: #65c18c; }
.data-widget em { color: #70b9f5; font-size: 9px; font-style: normal; }
.widget-left { left: 8%; }.widget-right { right: 9%; }
.hero-footer { display: flex; justify-content: space-between; gap: 18px; margin-top: auto; color: rgb(182 211 232 / 47%); font-size: 8px; letter-spacing: .08em; }
.login-side { display: flex; align-items: center; justify-content: center; min-width: 0; padding: 52px clamp(30px, 6vw, 110px); background: var(--surface); }
.login-side-inner { width: min(100%, 430px); }
.login-card { padding: clamp(30px, 3.4vw, 48px); border: 1px solid rgb(6 27 51 / 8%); border-radius: 20px; background: #fff; box-shadow: 0 24px 70px rgb(6 27 51 / 10%), 0 4px 14px rgb(6 27 51 / 5%); }
.login-card-top, .login-card-footer { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.card-kicker, .lock-mark { color: #9aa8b7; font-size: 9px; letter-spacing: .1em; }.lock-mark { color: #5c9a76; }
.login-card :deep(.mb-7) { margin-bottom: 34px; }.login-card :deep(h2) { margin-top: 26px; color: var(--ink); font-size: 34px; font-weight: 650; letter-spacing: -.06em; }.login-card :deep(.text-muted-foreground) { display: block; margin-top: 9px; color: var(--muted); font-size: 13px; line-height: 1.6; }.login-card :deep(.grid) { gap: 17px; }.login-card :deep(label) { margin: 0; color: inherit; font-size: inherit; font-weight: inherit; }.login-card :deep([data-slot='checkbox'] + label) { margin-left: 8px; color: #51657a; font-size: 13px; font-weight: 500; line-height: 16px; }.login-card :deep(input) { min-height: 52px; border: 1px solid #d9e0e8; border-radius: 10px; color: var(--ink); background: #fff; box-shadow: none; font-family: inherit; font-size: 14px; transition: border-color .25s ease, box-shadow .25s ease, transform .25s ease; }.login-card :deep(input::placeholder) { color: #9aa7b6; }.login-card :deep(input:hover), .login-card :deep(input:focus) { border-color: var(--tech-blue); box-shadow: 0 0 0 4px rgb(22 119 255 / 10%); transform: translateY(-1px); }.login-card :deep([data-slot='input-group']) { border: 1px solid #d9e0e8; border-radius: 10px; background: #fff; }.login-card :deep([data-slot='input-group']:focus-within) { border-color: var(--tech-blue); box-shadow: 0 0 0 4px rgb(22 119 255 / 10%); }.login-card :deep([data-slot='input-group'] input) { border: 0; box-shadow: none; transform: none; }.login-card :deep(button:not([data-slot='checkbox'])) { min-height: 52px; border-radius: 10px; font-family: inherit; transition: transform .25s ease, background .25s ease, box-shadow .25s ease; }.login-card :deep(button[type='submit']) { margin-top: 10px; color: #fff; background: var(--tech-blue); font-size: 14px; font-weight: 650; letter-spacing: .05em; }.login-card :deep(button[type='submit']:hover) { background: #0f65db; box-shadow: 0 12px 24px rgb(22 119 255 / 22%); transform: translateY(-2px); }.login-card :deep(button[type='submit']:active) { transform: translateY(0) scale(.985); }.login-card :deep([data-slot='checkbox']) { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; flex: none; border: 1px solid #c2ccd6; border-radius: 4px; color: #fff; background: transparent; box-shadow: none; cursor: pointer; transition: border-color .2s ease, background .2s ease, box-shadow .2s ease; }.login-card :deep([data-slot='checkbox']:hover) { border-color: var(--tech-blue); }.login-card :deep([data-slot='checkbox'][data-state='checked']) { border-color: var(--tech-blue); background: var(--tech-blue); }.login-card :deep([data-slot='checkbox']:focus-visible) { box-shadow: 0 0 0 3px rgb(22 119 255 / 22%); outline: none; }.login-card-footer { margin-top: 30px; padding-top: 16px; border-top: 1px solid #e8edf2; color: #9aa7b6; font-size: 10px; }.security-caption { display: flex; align-items: center; justify-content: center; gap: 9px; margin: 18px 0 0; color: #8a98a8; font-size: 11px; }
@media (max-width: 1050px) { .mes-login-page { grid-template-columns: minmax(0, 55%) minmax(400px, 45%); }.industrial-hero { padding-inline: 42px; }.capability-item { font-size: 12px; }.capability-item b { font-size: 7px; } }
@media (max-width: 760px) { .mes-login-page { display: block; }.industrial-hero { min-height: 430px; padding: 28px 24px 22px; }.hero-content { margin: 66px 0 0; }.hero-content h1 { margin-top: 16px; font-size: 48px; }.capability-list { margin-top: 28px; }.capability-item { padding-top: 11px; font-size: 11px; }.capability-item:not(:first-child) { padding-left: 10px; }.capability-item b, .system-mark, .data-widget { display: none; }.factory-visual { bottom: 24px; opacity: .65; }.hero-footer { position: relative; z-index: 2; margin-top: auto; font-size: 7px; }.hero-footer span:nth-child(2) { display: none; }.login-side { padding: 38px 22px 48px; }.login-card { padding: 30px 24px; } }
@media (max-width: 400px) { .industrial-hero { min-height: 390px; padding-inline: 20px; }.company-logo { width: 40px; height: 40px; }.company-name strong { font-size: 16px; }.company-name span { font-size: 9px; }.hero-content h1 { font-size: 41px; }.capability-list { grid-template-columns: repeat(2, 1fr); gap: 10px 0; border-bottom: 1px solid var(--line-blue); padding-bottom: 10px; }.capability-item:nth-child(2) { border-right: 0; }.capability-item:nth-child(3) { padding-left: 0; }.login-side { padding-inline: 18px; } }
@media (prefers-reduced-motion: reduce) { .login-card :deep(input), .login-card :deep(button) { transition: none; } }
</style>