2026-08-05 02:07:59 +00:00
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
|
<html lang="zh-CN">
|
|
|
|
|
|
<head>
|
2026-08-05 14:17:37 +08:00
|
|
|
|
<meta charset="utf-8">
|
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
|
|
|
|
<title>登录 · iAOP 云美工业AI优化平台</title>
|
|
|
|
|
|
<style>
|
|
|
|
|
|
/* iAOP 登录页(Ant Design Pro 风格重构,2026-08)。
|
|
|
|
|
|
* 布局对齐 Ant Design Pro 登录页:顶部品牌区 + 居中白卡 + 底部版权。
|
|
|
|
|
|
* 认证走 shared/session.js「后端优先 + 本地账号降级」双轨。 */
|
|
|
|
|
|
:root {
|
|
|
|
|
|
--ant-primary: #1677ff;
|
|
|
|
|
|
--ant-primary-hover: #4096ff;
|
|
|
|
|
|
--ant-text: rgba(0, 0, 0, 0.88);
|
|
|
|
|
|
--ant-text-secondary: rgba(0, 0, 0, 0.65);
|
|
|
|
|
|
--ant-text-tertiary: rgba(0, 0, 0, 0.45);
|
|
|
|
|
|
--ant-border: #d9d9d9;
|
|
|
|
|
|
--ant-split: rgba(5, 5, 5, 0.06);
|
|
|
|
|
|
--ant-bg-layout: #f0f2f5;
|
|
|
|
|
|
--ant-error: #ff4d4f;
|
|
|
|
|
|
--ant-success: #52c41a;
|
|
|
|
|
|
--ant-radius: 6px;
|
|
|
|
|
|
--ant-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
|
|
|
|
|
"Helvetica Neue", Arial, "PingFang SC", "Hiragino Sans GB",
|
|
|
|
|
|
"Microsoft YaHei", "Noto Sans", sans-serif;
|
|
|
|
|
|
}
|
|
|
|
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
|
|
body {
|
|
|
|
|
|
font-family: var(--ant-font);
|
|
|
|
|
|
background: var(--ant-bg-layout);
|
|
|
|
|
|
color: var(--ant-text);
|
|
|
|
|
|
min-height: 100vh;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
-webkit-font-smoothing: antialiased;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ---- 品牌区(AntD Pro 登录页:logo + 标题 + slogan 居中) ------------- */
|
|
|
|
|
|
.brand {
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
padding: 72px 24px 24px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.brand .logo-row {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.brand .logo-badge {
|
|
|
|
|
|
width: 44px; height: 44px;
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
background: var(--ant-primary);
|
|
|
|
|
|
color: #fff; font-size: 20px; font-weight: 600;
|
|
|
|
|
|
display: inline-flex; align-items: center; justify-content: center;
|
|
|
|
|
|
box-shadow: 0 4px 12px rgba(22, 119, 255, 0.35);
|
|
|
|
|
|
}
|
|
|
|
|
|
.brand h1 { font-size: 28px; font-weight: 600; letter-spacing: 0.5px; }
|
|
|
|
|
|
.brand .slogan {
|
|
|
|
|
|
margin-top: 12px;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
color: var(--ant-text-tertiary);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ---- 登录卡片 ---------------------------------------------------------- */
|
|
|
|
|
|
main { flex: 1; display: flex; justify-content: center; padding: 16px 24px 48px; }
|
|
|
|
|
|
.login-card {
|
|
|
|
|
|
width: 368px;
|
|
|
|
|
|
max-width: 100%;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
box-shadow: 0 1px 2px 0 rgba(0,0,0,.03), 0 1px 6px -1px rgba(0,0,0,.02),
|
|
|
|
|
|
0 2px 4px 0 rgba(0,0,0,.02);
|
|
|
|
|
|
padding: 32px 32px 24px;
|
|
|
|
|
|
align-self: flex-start;
|
|
|
|
|
|
}
|
|
|
|
|
|
.login-card h2 {
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
padding-bottom: 12px;
|
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
border-bottom: 1px solid var(--ant-split);
|
|
|
|
|
|
color: var(--ant-text);
|
|
|
|
|
|
}
|
|
|
|
|
|
.login-card h2 .active-tab { color: var(--ant-primary); }
|
|
|
|
|
|
|
|
|
|
|
|
.alert {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
padding: 8px 12px;
|
|
|
|
|
|
border-radius: var(--ant-radius);
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
|
}
|
|
|
|
|
|
.alert.show { display: block; }
|
|
|
|
|
|
.alert-error { color: #cf1322; background: #fff2f0; border: 1px solid #ffccc7; }
|
|
|
|
|
|
.alert-ok { color: #237804; background: #f6ffed; border: 1px solid #b7eb8f; }
|
|
|
|
|
|
|
|
|
|
|
|
.field { margin-bottom: 20px; }
|
|
|
|
|
|
.field label {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
color: var(--ant-text-secondary);
|
|
|
|
|
|
margin-bottom: 6px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.field .input-wrap { position: relative; }
|
|
|
|
|
|
.field .input-icon {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
left: 11px; top: 50%;
|
|
|
|
|
|
transform: translateY(-50%);
|
|
|
|
|
|
color: var(--ant-text-tertiary);
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
.field input {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 40px;
|
|
|
|
|
|
padding: 4px 40px 4px 34px;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
border: 1px solid var(--ant-border);
|
|
|
|
|
|
border-radius: var(--ant-radius);
|
|
|
|
|
|
transition: border-color .2s, box-shadow .2s;
|
|
|
|
|
|
}
|
|
|
|
|
|
.field input:hover { border-color: var(--ant-primary-hover); }
|
|
|
|
|
|
.field input:focus {
|
|
|
|
|
|
border-color: var(--ant-primary);
|
|
|
|
|
|
outline: none;
|
|
|
|
|
|
box-shadow: 0 0 0 2px rgba(22, 119, 255, .1);
|
|
|
|
|
|
}
|
|
|
|
|
|
.field input[aria-invalid="true"] { border-color: var(--ant-error); }
|
|
|
|
|
|
.field .pwd-toggle {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
right: 4px; top: 50%;
|
|
|
|
|
|
transform: translateY(-50%);
|
|
|
|
|
|
border: none; background: none;
|
|
|
|
|
|
cursor: pointer; font-size: 14px;
|
|
|
|
|
|
padding: 6px 8px;
|
|
|
|
|
|
color: var(--ant-text-tertiary);
|
|
|
|
|
|
}
|
|
|
|
|
|
.field .pwd-toggle:hover { color: var(--ant-primary); }
|
|
|
|
|
|
|
|
|
|
|
|
.btn-login {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 40px;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
border-radius: var(--ant-radius);
|
|
|
|
|
|
background: var(--ant-primary);
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transition: background .2s;
|
|
|
|
|
|
font-family: inherit;
|
|
|
|
|
|
}
|
|
|
|
|
|
.btn-login:hover { background: var(--ant-primary-hover); }
|
|
|
|
|
|
.btn-login:disabled { opacity: .6; cursor: not-allowed; }
|
|
|
|
|
|
|
|
|
|
|
|
.btn-sso {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 36px;
|
|
|
|
|
|
margin-top: 12px;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
border: 1px solid var(--ant-border);
|
|
|
|
|
|
border-radius: var(--ant-radius);
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
color: var(--ant-text-secondary);
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
font-family: inherit;
|
|
|
|
|
|
}
|
|
|
|
|
|
.btn-sso:hover { color: var(--ant-primary); border-color: var(--ant-primary); }
|
|
|
|
|
|
|
|
|
|
|
|
/* ---- 演示账号提示(本地降级模式) --------------------------------------- */
|
|
|
|
|
|
.demo-accounts {
|
|
|
|
|
|
margin-top: 20px;
|
|
|
|
|
|
padding: 12px;
|
|
|
|
|
|
background: #fafafa;
|
|
|
|
|
|
border: 1px solid var(--ant-split);
|
|
|
|
|
|
border-radius: var(--ant-radius);
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: var(--ant-text-tertiary);
|
|
|
|
|
|
line-height: 1.8;
|
|
|
|
|
|
}
|
|
|
|
|
|
.demo-accounts summary { cursor: pointer; color: var(--ant-text-secondary); }
|
|
|
|
|
|
.demo-accounts code {
|
|
|
|
|
|
background: rgba(0,0,0,.04);
|
|
|
|
|
|
padding: 1px 6px;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: var(--ant-text-secondary);
|
|
|
|
|
|
}
|
|
|
|
|
|
.demo-accounts .fill-link {
|
|
|
|
|
|
color: var(--ant-primary);
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
margin-left: 6px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ---- 页脚 --------------------------------------------------------------- */
|
|
|
|
|
|
footer {
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
padding: 24px;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: var(--ant-text-tertiary);
|
|
|
|
|
|
line-height: 1.8;
|
|
|
|
|
|
}
|
|
|
|
|
|
footer .links a {
|
|
|
|
|
|
color: var(--ant-text-tertiary);
|
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
|
margin: 0 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
footer .links a:hover { color: var(--ant-primary); }
|
|
|
|
|
|
</style>
|
2026-08-05 02:07:59 +00:00
|
|
|
|
</head>
|
|
|
|
|
|
<body>
|
|
|
|
|
|
<!--
|
2026-08-05 14:17:37 +08:00
|
|
|
|
iAOP 登录页(issue #150/#134 + 2026-08 Ant Design Pro 风格重构)。
|
|
|
|
|
|
认证双轨(shared/session.js):
|
|
|
|
|
|
· 后端 core/auth 可达 → POST /auth/login(HMAC 会话,HttpOnly cookie);
|
|
|
|
|
|
· 纯静态部署 → UserStore 本地账号(首次访问自动播种演示账号)。
|
|
|
|
|
|
角色三级(PRD 8.2):readonly 只读 / engineer 可配置 / admin 可发布回滚,
|
|
|
|
|
|
登录后门户按角色过滤可见模块。
|
2026-08-05 02:07:59 +00:00
|
|
|
|
-->
|
2026-08-05 14:17:37 +08:00
|
|
|
|
<div class="brand">
|
|
|
|
|
|
<div class="logo-row">
|
|
|
|
|
|
<span class="logo-badge">iA</span>
|
|
|
|
|
|
<h1>iAOP 云美工业AI优化平台</h1>
|
2026-08-05 02:07:59 +00:00
|
|
|
|
</div>
|
2026-08-05 14:17:37 +08:00
|
|
|
|
<div class="slogan">iAOP-Core 通用内核 · Template-Ti(海绵钛)一期 · 配置化驾驶舱 / 模板配置台 / LLM 助手</div>
|
|
|
|
|
|
</div>
|
2026-08-05 02:07:59 +00:00
|
|
|
|
|
2026-08-05 14:17:37 +08:00
|
|
|
|
<main>
|
|
|
|
|
|
<form class="login-card" id="loginForm" autocomplete="on" novalidate>
|
|
|
|
|
|
<h2><span class="active-tab">账号密码登录</span></h2>
|
2026-08-05 02:07:59 +00:00
|
|
|
|
|
2026-08-05 14:17:37 +08:00
|
|
|
|
<div class="alert" id="alert" role="alert" aria-live="polite"></div>
|
2026-08-05 02:07:59 +00:00
|
|
|
|
|
2026-08-05 14:17:37 +08:00
|
|
|
|
<div class="field">
|
|
|
|
|
|
<label for="username">用户名</label>
|
|
|
|
|
|
<div class="input-wrap">
|
|
|
|
|
|
<span class="input-icon">👤</span>
|
|
|
|
|
|
<input id="username" name="username" type="text" required
|
|
|
|
|
|
autocomplete="username" placeholder="请输入用户名" maxlength="64">
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="field">
|
|
|
|
|
|
<label for="password">密码</label>
|
|
|
|
|
|
<div class="input-wrap">
|
|
|
|
|
|
<span class="input-icon">🔒</span>
|
|
|
|
|
|
<input id="password" name="password" type="password" required
|
|
|
|
|
|
autocomplete="current-password" placeholder="请输入密码" minlength="8">
|
|
|
|
|
|
<button type="button" class="pwd-toggle" id="togglePwd"
|
|
|
|
|
|
title="显示/隐藏密码" aria-label="显示或隐藏密码">👁</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-08-05 02:07:59 +00:00
|
|
|
|
|
2026-08-05 14:17:37 +08:00
|
|
|
|
<button type="submit" class="btn-login" id="loginBtn">登 录</button>
|
2026-08-05 10:37:26 +08:00
|
|
|
|
|
2026-08-05 14:17:37 +08:00
|
|
|
|
<!-- issue #152:OIDC SSO 双轨入口——配置了 issuer/client_id 时显示 -->
|
|
|
|
|
|
<button type="button" class="btn-sso" id="ssoBtn" hidden>企业 SSO 登录(OIDC)</button>
|
2026-08-05 02:07:59 +00:00
|
|
|
|
|
2026-08-05 14:17:37 +08:00
|
|
|
|
<details class="demo-accounts">
|
|
|
|
|
|
<summary>演示账号(纯静态部署·本地账号模式)</summary>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
管理员 <code>admin / Admin@12345</code><span class="fill-link" data-u="admin" data-p="Admin@12345">填入</span><br>
|
|
|
|
|
|
工程师 <code>engineer / Engineer@12</code><span class="fill-link" data-u="engineer" data-p="Engineer@12">填入</span><br>
|
|
|
|
|
|
只读 <code>viewer / Viewer@1234</code><span class="fill-link" data-u="viewer" data-p="Viewer@1234">填入</span><br>
|
|
|
|
|
|
不同角色登录后可见模块不同;生产环境请立即改密。
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</details>
|
|
|
|
|
|
</form>
|
|
|
|
|
|
</main>
|
|
|
|
|
|
|
|
|
|
|
|
<footer>
|
|
|
|
|
|
<div class="links">
|
|
|
|
|
|
<a href="../cockpit/">驾驶舱</a>
|
|
|
|
|
|
<a href="../chat/assistant.html">对话助手</a>
|
2026-08-05 15:54:14 +08:00
|
|
|
|
<a href="../mobile/">移动端</a>
|
2026-08-05 02:07:59 +00:00
|
|
|
|
</div>
|
2026-08-05 14:17:37 +08:00
|
|
|
|
iAOP-Core v1.0 · 认证后端 core/auth(PBKDF2-HMAC-SHA256)· 部署底座 K8s/Helm
|
|
|
|
|
|
</footer>
|
2026-08-05 02:07:59 +00:00
|
|
|
|
|
2026-08-05 10:37:26 +08:00
|
|
|
|
<script src="user_store.js"></script>
|
2026-08-05 14:17:37 +08:00
|
|
|
|
<script src="../shared/session.js"></script>
|
2026-08-05 10:37:26 +08:00
|
|
|
|
<script>
|
2026-08-05 14:17:37 +08:00
|
|
|
|
(function () {
|
|
|
|
|
|
"use strict";
|
|
|
|
|
|
function $(id) { return document.getElementById(id); }
|
|
|
|
|
|
|
|
|
|
|
|
function showAlert(msg, kind) {
|
|
|
|
|
|
var el = $("alert");
|
|
|
|
|
|
el.textContent = msg || "";
|
|
|
|
|
|
el.className = "alert show " + (kind === "ok" ? "alert-ok" : "alert-error");
|
|
|
|
|
|
}
|
|
|
|
|
|
function clearAlert() { $("alert").className = "alert"; }
|
|
|
|
|
|
|
|
|
|
|
|
function nextUrl() {
|
2026-08-05 22:43:47 +08:00
|
|
|
|
// 登录后默认进 SPA 外壳(无门户页;外壳默认加载驾驶舱)
|
|
|
|
|
|
return new URLSearchParams(location.search).get("next") || "../index.html";
|
2026-08-05 14:17:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
|
|
|
|
// 首次访问播种本地演示账号(幂等;后端模式不影响)
|
|
|
|
|
|
UserStore.seedDefaults().catch(function () {});
|
|
|
|
|
|
|
|
|
|
|
|
// 已登录直接跳转
|
|
|
|
|
|
IAOP_SESSION.currentUser().then(function (u) {
|
|
|
|
|
|
if (u) location.href = nextUrl();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
$("loginForm").addEventListener("submit", function (e) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
var u = $("username").value.trim();
|
|
|
|
|
|
var p = $("password").value;
|
|
|
|
|
|
$("username").removeAttribute("aria-invalid");
|
|
|
|
|
|
$("password").removeAttribute("aria-invalid");
|
|
|
|
|
|
if (!u) {
|
|
|
|
|
|
$("username").setAttribute("aria-invalid", "true");
|
|
|
|
|
|
showAlert("请输入用户名", "error");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!p || p.length < 8) {
|
|
|
|
|
|
$("password").setAttribute("aria-invalid", "true");
|
|
|
|
|
|
showAlert("密码不少于 8 位", "error");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
$("loginBtn").disabled = true;
|
|
|
|
|
|
clearAlert();
|
|
|
|
|
|
IAOP_SESSION.login(u, p).then(function (user) {
|
|
|
|
|
|
showAlert("登录成功,欢迎 " + user.username + "(" +
|
|
|
|
|
|
(IAOP_SESSION.ROLE_LABELS[user.role] || user.role) + ")", "ok");
|
|
|
|
|
|
setTimeout(function () { location.href = nextUrl(); }, 400);
|
|
|
|
|
|
}).catch(function (err) {
|
|
|
|
|
|
$("password").setAttribute("aria-invalid", "true");
|
|
|
|
|
|
showAlert(err.message || "登录失败", "error");
|
|
|
|
|
|
}).finally(function () {
|
|
|
|
|
|
$("loginBtn").disabled = false;
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
$("togglePwd").addEventListener("click", function () {
|
|
|
|
|
|
var pwd = $("password");
|
|
|
|
|
|
pwd.type = pwd.type === "password" ? "text" : "password";
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 演示账号一键填入
|
|
|
|
|
|
Array.prototype.forEach.call(
|
|
|
|
|
|
document.querySelectorAll(".fill-link"), function (link) {
|
|
|
|
|
|
link.addEventListener("click", function () {
|
|
|
|
|
|
$("username").value = link.getAttribute("data-u");
|
|
|
|
|
|
$("password").value = link.getAttribute("data-p");
|
|
|
|
|
|
$("password").focus();
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// issue #152:OIDC 双轨——已配置 issuer/client_id 则显示 SSO 入口
|
|
|
|
|
|
var url = UserStore.oidcAuthorizeUrl("/auth/login.html");
|
|
|
|
|
|
if (url) {
|
|
|
|
|
|
var btn = $("ssoBtn");
|
|
|
|
|
|
btn.hidden = false;
|
|
|
|
|
|
btn.addEventListener("click", function () { location.href = url; });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
})();
|
2026-08-05 10:37:26 +08:00
|
|
|
|
</script>
|
2026-08-05 02:07:59 +00:00
|
|
|
|
</body>
|
|
|
|
|
|
</html>
|