Files
aurora-admin/site/scenario/settings.html
T
aurora-admin f1fbfc2ddb
Regression / regression (push) Canceled after 0s
feat(品牌标识): 几何 K 图标(favicon/顶栏标记/theme-color) + 并行会话成果入库
## 品牌标识(本次会话)

起因:品牌此前没有任何图形标识 —— 唯一 favicon 是内联 data-URI 里的字母「A」,
那是 v2.0.0「Aurora Admin → Kole UI」改名漏掉的一处(PC 顶栏也是「A」,
移动端站已是「K」;移动端文档站则完全没有 favicon)。

- 几何:24 网格三个互不接触的笔画(竖 + 两斜),圆头描边;
  描边 2.25 → 16px 标签页尺寸下正好 1.5px = 规范原文「描边1.5px」
- 取色分两套(刻意):favicon 硬编码品牌蓝/白(渲染在浏览器标签栏,不继承 kole-dark);
  顶栏标记走 currentColor(实测暗色下自动转 rgb(20,22,28))
- 新增 theme-color 双条(light #FFFFFF / dark #1C1F26,取 --kole-color-card-bg)
- 修 site/app.js hero 标语 KOLE ADMIN → KOLE UI(改名变形残留)
- 移动端 7 个模板补 favicon(此前计数 0)

验收:门禁 9 条全 OK(site-routing/site-routes/mobile-docs/mobile-site/isolation/
theme/nav/i18n/icons);PC 回归 1464/1464 · 移动端 807/807,各连跑 8 次一致;
两端 favicon 405 字节逐字节一致;PC 站控制台错误 1→0。

## 并行会话成果(本次一并入库)

- 图标系统:2576 图标(TDesign/Element Plus,MIT)+ 11 端注入 + 5 个构建门禁工具
  + IconPreview 预览页 + ICON-SPEC.md 冻结规格
- 移动端平台:47 组件 × 6 端 + 文档站 53 页 + 隔离门禁
- PC 组件:103 个大后台组件 / 组件11 批次
- uni-app:PC 端试点 + 移动端端实现 + 真实编译验证

## 工程

- .gitignore 补 .scratch/ 与 .zcode-preexisting-*.txt(会话中间产物,实测 9.1MB,不入库)
- CHANGELOG 补品牌标识条目
- ROADMAP 登 S8-P4(品牌标识任务包 + og:image/apple-touch-icon 未做部分)
2026-09-21 10:05:48 +08:00

558 lines
29 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>模板 · 系统设置 - Kole UI</title>
<meta name="description" content="Kole UI 模板页库 · 系统设置:侧边分区 + 表单 + 开关组 + 单选组 + 保存反馈,全部视觉来自设计令牌与 components.css。">
<link rel="stylesheet" href="../../.design_library/kole-ui/colors_and_type.css">
<link rel="stylesheet" href="../../.design_library/kole-ui/components.css">
<!-- 扩展组件样式:components.css 只聚合 6 个核心组件;下列组件各自的样式表按需引入 -->
<link rel="stylesheet" href="../../frameworks/Breadcrumb.css">
<link rel="stylesheet" href="../../frameworks/Segmented.css">
<link rel="stylesheet" href="../../frameworks/Radio.css">
<link rel="stylesheet" href="../../frameworks/Checkbox.css">
<link rel="stylesheet" href="../../frameworks/Switch.css">
<link rel="stylesheet" href="../../frameworks/MessagePro.css">
<style>
/* 模板页布局:仅排版类样式,组件视觉全部来自 colors_and_type.css 令牌 + components.css */
* { box-sizing: border-box; }
body {
margin: 0; background: var(--kole-color-page-bg);
font-family: var(--kole-font-family); color: var(--kole-color-text-body);
font-size: var(--kole-type-body-size); line-height: var(--kole-type-body-height);
}
a { color: var(--kole-color-brand); text-decoration: none; }
a:hover { color: var(--kole-color-brand-hover); }
.st-topbar {
height: 56px; background: var(--kole-color-card-bg); border-bottom: 1px solid var(--kole-color-border);
display: flex; align-items: center; gap: var(--kole-space-16); padding: 0 var(--kole-space-24);
position: sticky; top: 0; z-index: 20;
}
.st-mark {
width: 28px; height: 28px; border-radius: var(--kole-radius-large);
background: var(--kole-color-brand); color: var(--kole-color-text-inverse);
display: flex; align-items: center; justify-content: center; font-weight: 600; flex-shrink: 0;
}
.st-brand { font-size: var(--kole-type-h2-size); font-weight: var(--kole-type-h2-weight); color: var(--kole-color-text-title); }
.st-topnav { margin-left: auto; display: flex; gap: var(--kole-space-20); font-size: var(--kole-type-caption-size); }
.st-topnav a { color: var(--kole-color-text-secondary); }
.st-topnav a[aria-current="page"] { color: var(--kole-color-brand); font-weight: 500; }
/* 面包屑 */
.st-crumb { max-width: 1200px; margin: 0 auto; padding: var(--kole-space-16) var(--kole-space-24) 0; }
.st-shell { max-width: 1200px; margin: 0 auto; padding: var(--kole-space-16) var(--kole-space-24) var(--kole-space-32);
display: grid; grid-template-columns: 200px minmax(0, 1fr); gap: var(--kole-space-24); align-items: start; }
/* 左侧分区导航 */
.st-sidenav { display: flex; flex-direction: column; gap: var(--kole-space-4); position: sticky; top: 72px; }
.st-sidenav button {
display: flex; align-items: center; height: var(--kole-control-height-default); padding: 0 var(--kole-space-12);
border: none; background: none; border-radius: var(--kole-radius-base); font-family: inherit;
font-size: var(--kole-type-body-size); color: var(--kole-color-text-body); cursor: pointer; text-align: left; width: 100%;
transition: background var(--kole-duration-fast) var(--kole-ease-standard);
}
.st-sidenav button:hover { background: var(--kole-color-brand-bg); }
.st-sidenav button[aria-selected="true"] { background: var(--kole-color-brand-bg); color: var(--kole-color-brand); font-weight: 500; }
/* 右侧内容 */
.st-panels { display: flex; flex-direction: column; gap: var(--kole-space-16); min-width: 0; }
.st-head { display: flex; align-items: flex-end; justify-content: space-between; gap: var(--kole-space-16); flex-wrap: wrap; }
.st-title { margin: 0; font-size: var(--kole-type-display-size); line-height: var(--kole-type-display-height); font-weight: var(--kole-type-display-weight); color: var(--kole-color-text-title); }
.st-sub { margin: var(--kole-space-4) 0 0; font-size: var(--kole-type-caption-size); color: var(--kole-color-text-secondary); }
.st-card { display: flex; flex-direction: column; gap: var(--kole-space-16); }
.st-card + .st-card { margin-top: var(--kole-space-16); }
.st-card-title { font-size: var(--kole-type-h2-size); font-weight: var(--kole-type-h2-weight); color: var(--kole-color-text-title); }
.st-card-desc { margin: var(--kole-space-4) 0 0; font-size: var(--kole-type-caption-size); color: var(--kole-color-text-secondary); }
/* 表单行:左标签 + 右控件 */
.st-form { display: flex; flex-direction: column; gap: var(--kole-space-16); }
.st-row { display: grid; grid-template-columns: 180px minmax(0, 1fr); gap: var(--kole-space-16); align-items: start; }
.st-row > label, .st-row > .st-label { font-size: var(--kole-type-body-size); color: var(--kole-color-text-body); padding-top: 5px; }
.st-row .hint { margin: var(--kole-space-4) 0 0; font-size: var(--kole-type-caption-size); color: var(--kole-color-text-secondary); }
.st-control { display: flex; flex-direction: column; gap: var(--kole-space-4); min-width: 0; }
.st-control .kole-input { width: 100%; max-width: 380px; }
.st-control .kole-input input, .st-control .kole-input select { flex: 1; min-width: 0; }
.st-control select.native {
height: var(--kole-control-height-default); max-width: 380px; width: 100%;
border: 1px solid var(--kole-color-border); border-radius: var(--kole-radius-base);
background: var(--kole-color-card-bg); color: var(--kole-color-text-body);
font-family: inherit; font-size: var(--kole-type-body-size); padding: 0 var(--kole-space-8); cursor: pointer;
}
.st-control select.native:focus-visible { outline: 2px solid var(--kole-color-focus-ring); outline-offset: 1px; border-color: var(--kole-color-brand); }
/* 开关行(复用 Switch 组件样式 .kole-switch-switch) */
.st-switch-row { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--kole-space-16); padding: var(--kole-space-12) 0; border-bottom: 1px solid var(--kole-color-border); }
.st-switch-row:last-child { border-bottom: none; padding-bottom: 0; }
.st-switch-text strong { display: block; font-size: var(--kole-type-body-size); font-weight: 400; color: var(--kole-color-text-body); }
.st-switch-text span { display: block; margin-top: var(--kole-space-4); font-size: var(--kole-type-caption-size); color: var(--kole-color-text-secondary); }
.st-switch-row .kole-switch-switch { cursor: pointer; flex-shrink: 0; }
.st-switch-row.is-locked .kole-switch-text { opacity: .6; }
/* 单选 / 复选组(复用 Radio / Checkbox 组件样式) */
.st-group { display: flex; flex-wrap: wrap; gap: var(--kole-space-16); }
.kole-radio-option, .kole-checkbox-option {
display: inline-flex; align-items: center; gap: var(--kole-space-8);
font-size: var(--kole-type-body-size); color: var(--kole-color-text-body); cursor: pointer;
}
.kole-radio-option input, .kole-checkbox-option input {
width: var(--kole-icon-size-16); height: var(--kole-icon-size-16); margin: 0;
accent-color: var(--kole-color-brand); cursor: pointer;
}
/* 底部保存栏 */
.st-footer {
position: sticky; bottom: 0; margin-top: var(--kole-space-16);
background: var(--kole-color-card-bg); border: 1px solid var(--kole-color-border);
border-radius: var(--kole-radius-large); box-shadow: var(--kole-shadow-low);
padding: var(--kole-space-12) var(--kole-space-16);
display: flex; align-items: center; justify-content: space-between; gap: var(--kole-space-12); flex-wrap: wrap;
}
.st-footer .dirty { font-size: var(--kole-type-caption-size); color: var(--kole-color-warning); }
.st-footer .saved { font-size: var(--kole-type-caption-size); color: var(--kole-color-text-secondary); }
.st-footer .btns { display: flex; gap: var(--kole-space-8); }
/* 危险区 */
.st-danger { border-color: var(--kole-color-error); }
.st-danger .st-card-title { color: var(--kole-color-error); }
[hidden] { display: none !important; }
@media (max-width: 900px) {
.st-shell { grid-template-columns: minmax(0, 1fr); }
.st-sidenav { position: static; flex-direction: row; flex-wrap: wrap; }
.st-sidenav button { width: auto; }
.st-row { grid-template-columns: minmax(0, 1fr); gap: var(--kole-space-4); }
.st-row > label, .st-row > .st-label { padding-top: 0; }
.st-topnav { display: none; }
}
</style>
</head>
<body>
<header class="st-topbar">
<span class="st-mark">K</span>
<span class="st-brand">Kole UI · 管理控制台</span>
<nav class="st-topnav">
<a href="dashboard.html">工作台</a>
<a href="order-list.html">订单列表</a>
<a href="user-management.html">用户管理</a>
<a href="settings.html" aria-current="page">系统设置</a>
</nav>
</header>
<nav class="st-crumb kole-breadcrumb" aria-label="面包屑">
<a class="kole-breadcrumb-item" href="dashboard.html">工作台</a>
<span class="kole-breadcrumb-sep">/</span>
<span class="kole-breadcrumb-item is-current" aria-current="page">系统设置</span>
</nav>
<div class="st-shell">
<nav class="st-sidenav" id="st-nav" role="tablist" aria-label="设置分区">
<button type="button" role="tab" aria-selected="true" data-panel="basic">基本信息</button>
<button type="button" role="tab" aria-selected="false" data-panel="security">安全策略</button>
<button type="button" role="tab" aria-selected="false" data-panel="notify">通知设置</button>
<button type="button" role="tab" aria-selected="false" data-panel="danger">危险操作</button>
</nav>
<main class="st-panels">
<div class="st-head">
<div>
<h1 class="st-title">系统设置</h1>
<p class="st-sub">模板页库示范:侧边分区 + 表单 + 开关组 + 单选组 + 保存反馈</p>
</div>
</div>
<!-- 基本信息 -->
<section class="st-panel" id="st-panel-basic" role="tabpanel" aria-label="基本信息">
<div class="card card-bordered card-p24">
<div class="st-card">
<div>
<h2 class="st-card-title">基本信息</h2>
<p class="st-card-desc">这些信息会出现在导出的报表页眉与通知邮件中。</p>
</div>
<div class="st-form">
<div class="st-row">
<label for="st-name">企业名称</label>
<div class="st-control">
<div class="kole-input"><input id="st-name" type="text" value="华信精密制造有限公司" placeholder="请输入企业全称"></div>
<p class="hint">将显示在报表页眉,建议填写工商登记全称</p>
</div>
</div>
<div class="st-row">
<label for="st-code">企业编码</label>
<div class="st-control">
<div class="kole-input"><input id="st-code" type="text" value="KOLE-8823" placeholder="请输入企业编码"></div>
<p class="hint">创建后不可修改,用于对接内部 ERP 系统</p>
</div>
</div>
<div class="st-row">
<span class="st-label" id="st-tz-label">时区</span>
<div class="st-control">
<select class="native" id="st-tz" aria-labelledby="st-tz-label">
<option value="Asia/Shanghai" selected>(UTC+08:00) 中国标准时间 · 北京</option>
<option value="Asia/Tokyo">(UTC+09:00) 日本标准时间 · 东京</option>
<option value="Europe/London">(UTC+00:00) 格林尼治标准时间 · 伦敦</option>
</select>
<p class="hint">影响报表统计口径与操作日志时间戳</p>
</div>
</div>
<div class="st-row">
<span class="st-label" id="st-lang-label">界面语言</span>
<div class="st-control">
<div class="st-group" role="radiogroup" aria-labelledby="st-lang-label">
<label class="kole-radio-option"><input type="radio" name="st-lang" value="zh" checked> 简体中文</label>
<label class="kole-radio-option"><input type="radio" name="st-lang" value="en"> English</label>
</div>
<p class="hint">新成员首次登录时的默认界面语言</p>
</div>
</div>
<div class="st-row">
<span class="st-label" id="st-density-label">信息密度</span>
<div class="st-control">
<div class="kole-segmented" role="group" aria-labelledby="st-density-label" id="st-density">
<button class="kole-segmented-item is-active" type="button" data-density="default">默认</button>
<button class="kole-segmented-item" type="button" data-density="compact">紧凑</button>
<button class="kole-segmented-item" type="button" data-density="loose">宽松</button>
</div>
<p class="hint">影响列表与表格的行高,不改动字号</p>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- 安全策略 -->
<section class="st-panel" id="st-panel-security" role="tabpanel" aria-label="安全策略" hidden>
<div class="card card-bordered card-p24">
<div class="st-card">
<div>
<h2 class="st-card-title">登录与安全</h2>
<p class="st-card-desc">面向全企业的登录策略,保存后对新登录生效。</p>
</div>
<div class="st-form">
<div class="st-row">
<span class="st-label" id="st-pwd-label">密码强度</span>
<div class="st-control">
<div class="st-group" role="radiogroup" aria-labelledby="st-pwd-label">
<label class="kole-radio-option"><input type="radio" name="st-pwd" value="normal" checked> 标准(≥8 位)</label>
<label class="kole-radio-option"><input type="radio" name="st-pwd" value="strong"> 强(≥12 位含大小写与符号)</label>
</div>
<p class="hint">影响成员修改密码时的校验规则</p>
</div>
</div>
<div class="st-row">
<label for="st-timeout">登录有效期</label>
<div class="st-control">
<select class="native" id="st-timeout">
<option value="30">30 分钟</option>
<option value="120" selected>2 小时</option>
<option value="480">8 小时</option>
</select>
<p class="hint">超过该时长无操作需重新登录</p>
</div>
</div>
<div class="st-row">
<span class="st-label" id="st-ip-label">IP 白名单</span>
<div class="st-control">
<label class="kole-checkbox-option" style="margin-bottom:8px"><input type="checkbox" id="st-ip-on"> 仅允许白名单 IP 登录</label>
<div class="kole-input" id="st-ip-box" style="max-width:380px; opacity:.55" aria-disabled="true">
<textarea id="st-ip" rows="2" disabled placeholder="每行一个 IP 或网段,例如 192.168.5.0/24"
style="border:none;outline:none;background:transparent;font:inherit;color:inherit;flex:1;min-width:0;resize:vertical;padding:6px 0"></textarea>
</div>
<p class="hint">启用前请确认当前 IP 在白名单内,否则会把自己锁在系统外</p>
</div>
</div>
</div>
</div>
</div>
<div class="card card-bordered card-p24">
<div class="st-card">
<div>
<h2 class="st-card-title">登录方式</h2>
<p class="st-card-desc">开关即时生效,不需要点击保存。</p>
</div>
<div class="st-switches" data-immediate="1">
<div class="st-switch-row">
<span class="st-switch-text"><strong>企业微信扫码登录</strong><span>成员可用企业微信扫码进入控制台</span></span>
<button class="kole-switch-switch is-on" type="button" role="switch" aria-checked="true" aria-label="企业微信扫码登录"></button>
</div>
<div class="st-switch-row">
<span class="st-switch-text"><strong>短信验证码登录</strong><span>接收短信验证码作为第二种登录方式</span></span>
<button class="kole-switch-switch" type="button" role="switch" aria-checked="false" aria-label="短信验证码登录"></button>
</div>
<div class="st-switch-row is-locked">
<span class="st-switch-text"><strong>单点登录(SSO)</strong><span>需接入方配置 IdP 元数据后启用,当前不可用</span></span>
<button class="kole-switch-switch" type="button" role="switch" aria-checked="false" aria-label="单点登录" disabled style="opacity:.55;cursor:not-allowed"></button>
</div>
</div>
</div>
</div>
</section>
<!-- 通知设置 -->
<section class="st-panel" id="st-panel-notify" role="tabpanel" aria-label="通知设置" hidden>
<div class="card card-bordered card-p24">
<div class="st-card">
<div>
<h2 class="st-card-title">通知渠道</h2>
<p class="st-card-desc">选择接收系统提醒的方式,开关即时生效。</p>
</div>
<div class="st-switches" data-immediate="1">
<div class="st-switch-row">
<span class="st-switch-text"><strong>站内通知</strong><span>控制台右上角的消息中心</span></span>
<button class="kole-switch-switch is-on" type="button" role="switch" aria-checked="true" aria-label="站内通知"></button>
</div>
<div class="st-switch-row">
<span class="st-switch-text"><strong>邮件通知</strong><span>发送至账号绑定邮箱</span></span>
<button class="kole-switch-switch is-on" type="button" role="switch" aria-checked="true" aria-label="邮件通知"></button>
</div>
<div class="st-switch-row">
<span class="st-switch-text"><strong>短信通知</strong><span>仅用于紧急告警,会产生短信费用</span></span>
<button class="kole-switch-switch" type="button" role="switch" aria-checked="false" aria-label="短信通知"></button>
</div>
</div>
</div>
</div>
<div class="card card-bordered card-p24">
<div class="st-card">
<div>
<h2 class="st-card-title">接收的事件</h2>
<p class="st-card-desc">选择哪些业务事件需要提醒。</p>
</div>
<div class="st-group" style="flex-direction:column; gap:10px">
<label class="kole-checkbox-option"><input type="checkbox" checked> 订单状态变更</label>
<label class="kole-checkbox-option"><input type="checkbox" checked> 待审核事项提醒</label>
<label class="kole-checkbox-option"><input type="checkbox"> 成员权限变更</label>
<label class="kole-checkbox-option"><input type="checkbox"> 系统维护公告</label>
</div>
</div>
</div>
</section>
<!-- 危险操作 -->
<section class="st-panel" id="st-panel-danger" role="tabpanel" aria-label="危险操作" hidden>
<div class="card card-bordered card-p24 st-danger">
<div class="st-card">
<div>
<h2 class="st-card-title">危险操作</h2>
<p class="st-card-desc">以下操作不可撤销,请谨慎执行。删除前建议先导出数据。</p>
</div>
<div class="st-switch-row">
<span class="st-switch-text"><strong>清空操作日志</strong><span>将删除全部历史操作记录,审计追溯能力将失效</span></span>
<button class="btn btn-default btn-md" type="button" id="st-clear-log">清空日志</button>
</div>
<div class="st-switch-row">
<span class="st-switch-text"><strong>停用当前企业</strong><span>停用后全部成员无法登录,数据保留 30 天后删除</span></span>
<button class="btn btn-danger btn-md" type="button" id="st-close-org">停用企业</button>
</div>
</div>
</div>
</section>
<!-- 保存栏 -->
<div class="st-footer">
<span class="dirty" id="st-dirty" hidden>● 有未保存的修改</span>
<span class="saved" id="st-saved">所有修改已保存 · 上次保存 09:30</span>
<div class="btns">
<button class="btn btn-default btn-md" type="button" id="st-reset" disabled>放弃修改</button>
<button class="btn btn-primary btn-md" type="button" id="st-save" disabled>保存</button>
</div>
</div>
<p style="margin:0; font-size:var(--kole-type-caption-size); color:var(--kole-color-text-secondary)">
模板页库 · 全部视觉来自 <a href="../../.design_library/kole-ui/colors_and_type.css">设计令牌</a> 与
<a href="../../.design_library/kole-ui/components.css">components.css</a>;
<a href="../index.html">← 返回组件文档</a>
</p>
</main>
</div>
<script>
(function () {
'use strict';
function esc(s) { return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;'); }
/* ── 分区切换 ─────────────────────────────────────────── */
var nav = document.getElementById('st-nav');
nav.addEventListener('click', function (e) {
var btn = e.target.closest('button[data-panel]');
if (!btn) return;
var key = btn.getAttribute('data-panel');
nav.querySelectorAll('button[data-panel]').forEach(function (b) { b.setAttribute('aria-selected', String(b === btn)); });
document.querySelectorAll('.st-panel').forEach(function (p) { p.hidden = p.id !== 'st-panel-' + key; });
var head = document.querySelector('.st-title');
head.textContent = btn.textContent.trim();
});
/* ── 保存栏:快照式脏检查 ───────────────────────────────
每个受控字段都登记进 collect()/apply(),脏 = 当前值与基线不等。
这样「改回原值」会自动回到干净态,新增字段也不必再补一遍还原代码。 */
var dirtyEl = document.getElementById('st-dirty');
var savedEl = document.getElementById('st-saved');
var saveBtn = document.getElementById('st-save');
var resetBtn = document.getElementById('st-reset');
var DENSITY_DEFAULT = 'default';
function activeDensity() {
var on = document.querySelector('#st-density .kole-segmented-item.is-active');
return on ? on.getAttribute('data-density') : DENSITY_DEFAULT;
}
function collect() {
var notify = [];
document.querySelectorAll('#st-panel-notify .kole-checkbox-option input').forEach(function (el, i) {
if (el.checked) notify.push(i);
});
return {
name: document.getElementById('st-name').value,
code: document.getElementById('st-code').value,
tz: document.getElementById('st-tz').value,
timeout: document.getElementById('st-timeout').value,
lang: (document.querySelector('input[name="st-lang"]:checked') || {}).value || '',
pwd: (document.querySelector('input[name="st-pwd"]:checked') || {}).value || '',
density: activeDensity(),
ipOn: document.getElementById('st-ip-on').checked,
ipList: document.getElementById('st-ip').value,
notify: notify.join(',')
};
}
function apply(s) {
/* 程序化赋值不会触发 input/change 事件,故不会反过来把自己标脏 */
document.getElementById('st-name').value = s.name;
document.getElementById('st-code').value = s.code;
document.getElementById('st-tz').value = s.tz;
document.getElementById('st-timeout').value = s.timeout;
var lang = document.querySelector('input[name="st-lang"][value="' + s.lang + '"]');
if (lang) lang.checked = true;
var pwd = document.querySelector('input[name="st-pwd"][value="' + s.pwd + '"]');
if (pwd) pwd.checked = true;
document.querySelectorAll('#st-density .kole-segmented-item').forEach(function (b) {
b.classList.toggle('is-active', b.getAttribute('data-density') === s.density);
});
var ipOn = document.getElementById('st-ip-on');
ipOn.checked = s.ipOn;
document.getElementById('st-ip').value = s.ipList;
syncIpBox();
var want = s.notify ? s.notify.split(',') : [];
document.querySelectorAll('#st-panel-notify .kole-checkbox-option input').forEach(function (el, i) {
el.checked = want.indexOf(String(i)) >= 0;
});
}
var baseline = collect();
function refreshDirty() {
var dirty = JSON.stringify(collect()) !== JSON.stringify(baseline);
dirtyEl.hidden = !dirty;
saveBtn.disabled = !dirty;
resetBtn.disabled = !dirty;
}
function markDirty() { refreshDirty(); }
function clearDirty() { refreshDirty(); }
['st-name', 'st-code', 'st-tz', 'st-timeout', 'st-ip'].forEach(function (id) {
var el = document.getElementById(id);
el.addEventListener('input', markDirty);
el.addEventListener('change', markDirty);
});
document.querySelectorAll('input[name="st-lang"], input[name="st-pwd"]').forEach(function (el) {
el.addEventListener('change', markDirty);
});
document.getElementById('st-density').addEventListener('click', function (e) {
var btn = e.target.closest('.kole-segmented-item');
if (!btn) return;
this.querySelectorAll('.kole-segmented-item').forEach(function (b) { b.classList.remove('is-active'); });
btn.classList.add('is-active');
markDirty();
});
document.querySelectorAll('#st-panel-notify .kole-checkbox-option input, #st-ip-on').forEach(function (el) {
el.addEventListener('change', markDirty);
});
saveBtn.addEventListener('click', function () {
/* 保存进行中:两个按钮都禁用 —— 否则用户会在「还没提交完」的窗口里点放弃修改,
拿到的是上一份基线,看起来像数据被还原成了旧值。 */
saveBtn.disabled = true;
resetBtn.disabled = true;
saveBtn.textContent = '保存中…';
setTimeout(function () {
saveBtn.textContent = '保存';
/* 保存成功:当前值成为新基线,脏态自然消失 */
baseline = collect();
clearDirty();
var t = new Date();
savedEl.textContent = '所有修改已保存 · 上次保存 ' + String(t.getHours()).padStart(2, '0') + ':' + String(t.getMinutes()).padStart(2, '0');
flash('设置已保存', 'success');
}, 700);
});
resetBtn.addEventListener('click', function () {
apply(baseline);
clearDirty();
flash('已放弃未保存的修改', 'info');
});
/* ── 开关:即时生效(不进入保存栏) ───────────────────── */
document.querySelectorAll('.st-switches .kole-switch-switch').forEach(function (sw) {
if (sw.disabled) return;
sw.addEventListener('click', function () {
var on = sw.getAttribute('aria-checked') !== 'true';
sw.setAttribute('aria-checked', String(on));
sw.classList.toggle('is-on', on);
var label = sw.getAttribute('aria-label');
flash(label + (on ? ' 已开启' : ' 已关闭'), on ? 'success' : 'info');
});
});
/* ── 安全策略:IP 白名单联动 ──────────────────────────── */
function syncIpBox() {
var box = document.getElementById('st-ip-box');
var area = document.getElementById('st-ip');
var on = document.getElementById('st-ip-on').checked;
area.disabled = !on;
box.style.opacity = on ? '1' : '.55';
box.setAttribute('aria-disabled', String(!on));
}
document.getElementById('st-ip-on').addEventListener('change', function () {
syncIpBox();
if (this.checked) document.getElementById('st-ip').focus();
});
/* ── 危险操作:二次确认 ───────────────────────────────── */
document.getElementById('st-clear-log').addEventListener('click', function () {
confirmDanger('清空操作日志', '将删除全部历史操作记录,审计追溯能力将失效。此操作不可撤销。');
});
document.getElementById('st-close-org').addEventListener('click', function () {
confirmDanger('停用当前企业', '停用后全部成员无法登录,数据保留 30 天后删除。此操作不可撤销。');
});
function confirmDanger(title, desc) {
if (window.confirm(title + '\n\n' + desc + '\n\n确认继续?')) {
flash('已确认「' + title + '」(示范交互,未真正执行)', 'warning');
}
}
/* ── 轻提示 ───────────────────────────────────────────── */
var msgWrap = null, msgTimer = null;
function flash(text, kind) {
if (!msgWrap) {
msgWrap = document.createElement('div');
msgWrap.className = 'kole-message-wrap';
msgWrap.setAttribute('role', 'status');
msgWrap.setAttribute('aria-live', 'polite');
document.body.appendChild(msgWrap);
}
var k = kind || 'success';
var icon = k === 'success' ? '✓' : k === 'info' ? 'i' : '!';
msgWrap.innerHTML = '<div class="kole-message is-' + k + '"><span class="kole-message-icon">' + icon + '</span>' +
'<span class="kole-message-content">' + esc(text) + '</span></div>';
clearTimeout(msgTimer);
msgTimer = setTimeout(function () { msgWrap.innerHTML = ''; }, 2000);
}
})();
</script>
</body>
</html>