Files
aurora-admin/site/scenario/settings.html
T
aurora-admin b59247231a
Regression / regression (push) Canceled after 0s
feat(安全+品牌): 对外产物发布脱敏(3注入点/2465文件0泄露) + Kole Cup 定稿
安全:CHANGELOG.md 是仓库内部变更流水(含服务器目录、镜像回滚标签、内网网段、
部署时序、AI 工作流用语),此前被 build-site.ps1 / precompute.mjs / build-mobile.mjs
原样注入站点,而站点是公网可下载的静态文件 —— 抓一次 /site/m/changelog.html
即可拿到内网地址段与服务器目录布局。

- 新增 tools/lib/redact-publish.mjs(零依赖,构建期过滤,不改 CHANGELOG.md,
  内部可追溯性完整保留)
- 接入 precompute.mjs(PC data.json/changelog.json)与 build-mobile.mjs
  (移动端更新日志页);build-site.ps1 不碰(ASCII-only 铁律)
- 只处理 changelog 字段:components[].sources 是规范实现源码,逐字保真
  (详情页代码区主动高亮注释,剥注释会破坏该功能)
- 实测消除:/opt/aurora-admin.prev-*、kole-ui-showcase:pre-*、docker compose、
  192.168.5.7、16 位产物指纹、1531/1531、并发会话/本会话/派子 agent
- settings.html 演示占位 IP 192.168.5.0/24(= 真实网段)改为 RFC 5737 的 192.0.2.0/24

品牌:Kole Cup 饮料杯标记定稿(几何 K → 圆角杯盖 + 杯身负空间 K,无吸管),
brand-mark.json 升 schemaVersion 3(paths 支持 { d, evenodd }),
verify:brand 增至 25 条(新增 B9b:负空间必须带 fill-rule)。

验证:发布集 2465 文件全量扫描 0 泄露;PC 回归 100%(1464/1464) ·
移动端 100%(807/807);门禁品牌 25 / 隔离 31 / 移动文档 12 / 示例 9 / 版本 40 / i18n 17 全绿;
已按 AGENTS §九 发布公网,2461/2461 逐字节一致,五项验收全过。

已知未处理(既有缺口,ROADMAP S7-P28 已记录):data.mobile.json 的
meta.generated 为墙上时钟,会让 CI 的「生成物可复现」断言在重跑构建后永远非空;
该 CI 流水线本身亦从未通过(无 runner)。
2026-09-23 07:31:06 +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.0.2.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>