Files
matrix-screen-controller/核桃派软件源代码/app/static/index.html
T

944 lines
56 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, viewport-fit=cover">
<meta name="theme-color" content="#111318">
<title>奇妙小屏幕控制器</title>
<link rel="icon" href="data:,">
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<div class="app-shell">
<button id="drawer-toggle" class="drawer-toggle" type="button" aria-controls="workspace-drawer" aria-expanded="false" aria-label="打开功能菜单">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path class="drawer-toggle-menu-icon" d="M4 7h16M4 12h16M4 17h16"></path>
<path class="drawer-toggle-close-icon" d="M6 6l12 12M18 6L6 18"></path>
</svg>
</button>
<aside id="workspace-drawer" class="drawer" aria-label="功能选择" aria-hidden="true" inert>
<div class="drawer-header">
<div>
<p class="eyebrow" data-ui-copy-default-text="64 × 64 点阵屏"></p>
<p class="brand">奇妙小屏幕控制器</p>
</div>
</div>
<div class="workspace-order-editor">
<button id="workspace-order-toggle" class="workspace-order-toggle compact-button" type="button">自定义项目位置</button>
<p id="workspace-order-feedback" class="workspace-order-feedback" role="status" aria-live="polite" hidden></p>
</div>
<nav id="workspace-nav" class="workspace-nav" aria-label="工作区"></nav>
<p class="drawer-note" data-ui-copy-default-text="局域网设备控制台"></p>
</aside>
<button id="drawer-backdrop" class="drawer-backdrop" type="button" aria-label="关闭功能菜单" hidden></button>
<div class="app-main">
<header class="topbar">
<div class="topbar-copy">
<h1 id="current-workspace-title">设备状态和测试</h1>
<p id="last-message" class="last-message" aria-live="polite">控制台已就绪</p>
</div>
<div class="topbar-statuses" aria-label="设备实时状态">
<button id="topbar-current-content" class="current-content-indicator" type="button" aria-haspopup="dialog" aria-controls="current-display-dialog" aria-live="polite" aria-atomic="true">
当前画面:正在读取…
</button>
<button id="topbar-cpu" class="resource-indicator" data-ui-copy-value data-status="starting" type="button" aria-haspopup="dialog" aria-controls="resource-details-dialog" aria-label="查看 CPU 占用详情">
CPU -
</button>
<button id="topbar-memory" class="resource-indicator" data-ui-copy-value data-status="starting" type="button" aria-haspopup="dialog" aria-controls="resource-details-dialog" aria-label="查看内存占用详情">
RAM -
</button>
<div
id="topbar-low-voltage-warning"
class="low-voltage-warning"
data-mode="disabled"
role="status"
aria-live="polite"
aria-atomic="true"
hidden
></div>
<div id="topbar-voltage" class="voltage-indicator" data-ui-copy-value data-status="starting" role="status" aria-live="polite">
正在读取电压…
</div>
</div>
</header>
<section id="app-version-notice" class="sync-notice sync-notice-warning" role="alert" hidden>
<div>
<strong>控制程序已更新</strong>
<p>当前页面版本已过期。画板仍可编辑和导出,但刷新前不会再向设备或模板提交操作。</p>
</div>
<button id="reload-app" class="primary-button" type="button">刷新到新版本</button>
</section>
<section id="draft-conflict-notice" class="sync-notice sync-notice-warning" role="alert" hidden>
<div>
<strong>另一标签页修改了画板草稿</strong>
<p>请选择载入另一标签页的最新草稿,或明确保留当前标签页的草稿。</p>
</div>
<div class="sync-notice-actions">
<button id="load-external-draft" type="button">载入其他标签草稿</button>
<button id="keep-current-draft" class="primary-button" type="button">保留当前草稿</button>
</div>
</section>
<section id="board-shell" class="board-shell" aria-labelledby="board-title">
<div id="standard-board-shell">
<div class="board-heading">
<div>
<p id="board-eyebrow" class="eyebrow" data-ui-copy-value>64 × 64 统一画板</p>
<h2 id="board-title" data-ui-copy-value>合成预览</h2>
</div>
<div class="board-heading-context">
<span id="board-mode" class="orientation-label" data-ui-copy-value>正向 0° · 统一画板</span>
<section id="animation-edit-notice" class="animation-edit-notice" aria-live="polite" hidden>
<strong id="animation-edit-name">正在编辑:</strong>
<span id="animation-edit-status">已保存</span>
</section>
</div>
</div>
<div id="board-stage" class="board-stage">
<canvas id="scene-canvas" width="64" height="64" aria-label="统一画板的正向合成预览"></canvas>
<canvas id="scene-overlay" width="64" height="64" aria-label="统一画板交互层"></canvas>
<div id="pixel-grid" class="pixel-grid" aria-hidden="true" hidden></div>
</div>
<p id="board-status" class="board-status" aria-live="polite">正在恢复画板草稿…</p>
<div class="board-actions">
<button id="apply-composition" class="primary-button" type="button">应用统一画板</button>
<button id="exit-animation-edit" type="button" hidden>退出动图编辑</button>
<button id="export-composition" type="button">导出合成 PNG</button>
<button id="save-template" type="button" hidden>保存为模板</button>
</div>
<div id="canvas-edit-assistive" class="canvas-edit-assistive" hidden>
<button id="canvas-assistive-toggle" class="canvas-assistive-toggle" type="button"
aria-controls="canvas-assistive-menu" aria-expanded="false" aria-label="打开画布编辑菜单">
<span aria-hidden="true"></span>
</button>
<div id="canvas-assistive-menu" class="canvas-assistive-menu" role="menu" aria-label="画布编辑操作" hidden>
<p id="canvas-edit-mode-hint" role="status" aria-live="polite">绘画模式:直接在画布上绘制</p>
<button id="undo-canvas-edit" type="button" role="menuitem" aria-disabled="true"
data-unavailable-reason="还没有可以撤销的像素操作。">撤销</button>
<button id="exit-canvas-edit" type="button" role="menuitem">退出编辑</button>
<button id="move-canvas-view" type="button" role="menuitem">移动画布视线</button>
<button id="continue-canvas-edit" type="button" role="menuitem" aria-disabled="true"
data-unavailable-reason="当前已经是绘画模式。">继续编辑</button>
</div>
</div>
</div>
<section id="animation-preview-shell" class="animation-preview-shell" aria-labelledby="animation-preview-shell-title" hidden>
<h2 id="animation-preview-shell-title" class="sr-only">动图与当前画板预览</h2>
<article class="animation-preview-card">
<p class="eyebrow">已保存内容</p>
<h3>当前动图</h3>
<div class="animation-small-preview" aria-label="当前打开动图的循环预览">
<img id="animation-preview-image" alt="" width="64" height="64" hidden>
<span id="animation-preview-placeholder">请先打开一个动图</span>
</div>
<p id="animation-preview-status" class="animation-preview-status" aria-live="polite">请先打开一个动图</p>
</article>
<article class="animation-preview-card">
<p class="eyebrow">新建帧来源</p>
<h3>当前画板</h3>
<div class="animation-small-preview">
<canvas id="animation-board-preview" width="64" height="64" aria-label="当前统一画板预览"></canvas>
</div>
<p class="animation-preview-status">“从当前画板新建帧”将复制此画面</p>
</article>
</section>
</section>
<main id="workspace-root" class="workspace-root">
<section id="workspace-device" class="workspace" aria-label="设备状态和测试" hidden>
<div class="device-refresh-row">
<button id="refresh-status" class="secondary-button" type="button">刷新状态</button>
</div>
<article class="panel status-panel">
<div class="panel-heading">
<h3>屏幕状态</h3>
<span class="status-badge">仅为程序设定值,屏幕实际情况软件无法得知</span>
</div>
<dl class="status-grid status-grid-compact">
<div><dt>屏幕方向(设定值和现实角度无关,仅为软件内部旋转值)</dt><dd id="status-orientation" data-ui-copy-value>-</dd></div>
<div><dt>亮度(实际亮度会受到电压保护功能限制)</dt><dd id="status-brightness" data-ui-copy-value>-</dd></div>
</dl>
</article>
<article class="panel">
<h3>纯色测试</h3>
<div class="choice-grid color-choices" role="group" aria-label="纯色测试选择">
<button type="button" data-test-fill="#000000">全黑(不亮)</button>
<button type="button" data-test-fill="#FF0000">全红</button>
<button type="button" data-test-fill="#00FF00">全绿</button>
<button type="button" data-test-fill="#0000FF">全蓝</button>
<button type="button" data-test-fill="#FFFFFF">全白(高亮度会导致严重耗电)</button>
<button id="custom-test-fill" type="button">自定义纯色</button>
</div>
<div class="color-field"><span>颜色 RGB / HEX</span>
<button id="test-color" class="color-trigger" type="button" value="#40A0FF" data-color-picker data-color-readonly="true" aria-label="查看测试颜色">
<span class="color-trigger-swatch" aria-hidden="true"></span><span class="color-trigger-value">#40A0FF</span>
</button>
</div>
<div id="display-test-controls" class="display-test-controls" hidden>
<div class="panel-heading">
<label for="test-brightness">测试亮度(此亮度独立于屏幕亮度)</label>
<output id="test-brightness-value" data-ui-copy-value for="test-brightness" class="brightness-output">50%</output>
</div>
<input id="test-brightness" class="brightness-slider" type="range" min="1" max="100" value="50">
<p id="test-brightness-protection" class="brightness-protection-status" role="status" aria-live="polite" hidden></p>
<p id="display-test-feedback" class="settings-feedback" aria-live="polite" hidden></p>
<button id="exit-display-test" class="danger-button" type="button">退出测试</button>
</div>
</article>
<article class="panel device-storage-panel">
<div class="panel-heading"><h3>设备容量</h3></div>
<section class="template-storage device-storage-grid" aria-label="设备与软件空间">
<div><span>设备总空间</span><strong id="device-total-space" data-ui-copy-value>-</strong></div>
<div><span>设备可用空间</span><strong id="device-free-space" data-ui-copy-value>-</strong></div>
<div><span>当前软件总占用空间</span><strong id="device-software-size" data-ui-copy-value>-</strong></div>
<div><span>模板占用空间</span><strong id="device-template-size" data-ui-copy-value>-</strong></div>
</section>
<p id="device-storage-feedback" class="settings-feedback" aria-live="polite" hidden></p>
</article>
</section>
<section id="workspace-canvas" class="workspace" aria-labelledby="canvas-title" hidden>
<div class="workspace-heading">
<div>
<p class="eyebrow" data-ui-copy-default-text="内容"></p>
<h2 id="canvas-title" aria-label="像素画布" data-ui-copy-default-text="像素画布"></h2>
</div>
<span class="orientation-label" data-ui-copy-default-text="正向 0°"></span>
</div>
<article class="panel canvas-workbench">
<div class="canvas-toolbar">
<div class="color-field"><span>画笔颜色</span>
<button id="paint-color" class="color-trigger" type="button" value="#FFFFFF" data-color-picker aria-label="选择画笔颜色">
<span class="color-trigger-swatch" aria-hidden="true"></span><span class="color-trigger-value">#FFFFFF</span>
</button>
</div>
<div class="color-field"><span>背景颜色</span>
<button id="background-color" class="color-trigger" type="button" value="#000000" data-color-picker aria-label="选择画布背景颜色">
<span class="color-trigger-swatch" aria-hidden="true"></span><span class="color-trigger-value">#000000</span>
</button>
</div>
<div class="segmented canvas-tools" role="group" aria-label="绘制工具">
<button type="button" data-canvas-tool="brush">画笔</button>
<button type="button" data-canvas-tool="eraser">橡皮</button>
<button type="button" data-canvas-tool="eyedropper">吸管</button>
</div>
<div class="brush-size-control">
<div class="brush-size-heading"><span>笔尖粗细</span><output id="brush-size-value" data-ui-copy-value for="brush-size">1 px</output></div>
<div class="brush-size-row">
<span id="brush-size-preview" class="brush-size-preview" aria-hidden="true"></span>
<input id="brush-size" type="range" min="1" max="16" value="1" aria-label="画笔和橡皮粗细">
</div>
<div class="brush-size-presets" role="group" aria-label="笔尖粗细快捷选择">
<button type="button" data-brush-size="1">1</button><button type="button" data-brush-size="2">2</button>
<button type="button" data-brush-size="4">4</button><button type="button" data-brush-size="8">8</button>
</div>
<button id="pixel-edit-toggle" class="pixel-edit-toggle" type="button" aria-pressed="false" aria-describedby="pixel-edit-status">开启单像素编辑</button>
<p id="pixel-edit-status" class="pixel-edit-status" aria-live="polite" hidden></p>
</div>
</div>
<div class="choice-grid canvas-actions">
<button id="fill-canvas" type="button">填充背景</button>
<button id="clear-canvas" type="button">清空像素层</button>
</div>
<button id="start-canvas-edit" class="primary-button full-width" type="button">开始编辑</button>
<p class="hint">画布默认不会直接落笔。选好颜色和工具后,点击“开始编辑”进入全屏画布。</p>
</article>
</section>
<section id="workspace-text" class="workspace" aria-labelledby="text-title" hidden>
<div class="workspace-heading">
<div>
<p class="eyebrow" data-ui-copy-default-text="内容"></p>
<h2 id="text-title" aria-label="文字显示" data-ui-copy-default-text="文字显示"></h2>
</div>
<span class="orientation-label" data-ui-copy-default-text="正向 0°"></span>
</div>
<article class="panel text-elements-panel">
<div class="panel-heading">
<div>
<h3>文字元素</h3>
<p id="text-selection-status" class="subtle">新建文字或从列表选择一个元素</p>
</div>
<span class="status-badge" data-ui-copy-default-text="Pillow"></span>
</div>
<div class="text-element-actions">
<button id="create-text" class="primary-button" type="button">新建文字</button>
<button id="duplicate-text" type="button" aria-disabled="true" data-unavailable-reason="请先新建或选择一个文字元素。">复制选中</button>
<button id="delete-text" class="danger-button" type="button" aria-disabled="true" data-unavailable-reason="请先新建或选择一个文字元素。">删除选中</button>
</div>
<div id="text-element-list" class="text-element-list" role="list" aria-label="文字元素列表"></div>
</article>
<form id="text-form" class="panel text-form" autocomplete="off">
<fieldset id="text-content-fields" disabled>
<label class="wide-field">文字内容
<textarea id="text-value" rows="2" maxlength="512"></textarea>
</label>
</fieldset>
<div class="font-field wide-field">
<label id="text-font-label" for="text-font">字体</label>
<div class="font-control-row">
<div class="font-combobox-shell">
<input id="text-font" type="search" role="combobox" value="自动多语言字体 — 自动回退"
aria-labelledby="text-font-label" aria-autocomplete="list" aria-expanded="false"
aria-controls="text-font-options" autocapitalize="off" autocomplete="off" spellcheck="false">
<div id="text-font-options" class="font-options" role="listbox" aria-label="可用字体" hidden></div>
</div>
<button id="import-font" type="button">导入字体</button>
<input id="font-file" class="sr-only" type="file" accept=".ttf,.otf,.ttc,.otc">
</div>
<span class="hint">选择设备已有字体,或导入 TTF、OTF、TTC、OTC 文件;缺字时会自动回退。</span>
<p id="font-feedback" class="font-feedback" role="status" aria-live="polite" hidden></p>
</div>
<fieldset id="text-fields" disabled>
<div class="form-grid">
<label>字号
<input id="text-size" type="number" min="1" max="64" value="12" inputmode="numeric">
</label>
<label>X 坐标
<input id="text-x" type="number" min="-128" max="128" value="4" inputmode="numeric">
</label>
<label>Y 坐标
<input id="text-y" type="number" min="-128" max="128" value="24" inputmode="numeric">
</label>
<label>对齐
<select id="text-align">
<option value="left">左对齐</option>
<option value="center">居中</option>
<option value="right">右对齐</option>
</select>
</label>
<div class="color-field"><span>文字颜色</span>
<button id="text-color" class="color-trigger" type="button" value="#FFFFFF" data-color-picker aria-label="选择文字颜色">
<span class="color-trigger-swatch" aria-hidden="true"></span><span class="color-trigger-value">#FFFFFF</span>
</button>
</div>
</div>
</fieldset>
</form>
</section>
<section id="workspace-media-import" class="workspace" aria-labelledby="media-import-title" hidden>
<div class="workspace-heading">
<div>
<p class="eyebrow" data-ui-copy-default-text="内容"></p>
<h2 id="media-import-title" data-ui-copy-default-text="媒体内容转换"></h2>
</div>
<button id="refresh-media-imports" class="secondary-button" type="button" data-ui-copy-default-text="刷新任务"></button>
</div>
<form id="media-upload-form" class="panel media-upload-panel">
<div class="panel-heading">
<h3 data-ui-copy-default-text="导入图片、动图或视频"></h3>
</div>
<div class="form-grid">
<label class="media-source-field"><span data-ui-copy-default-text="源文件"></span>
<input id="media-source-file" type="file" required
accept="image/png,image/jpeg,image/gif,image/webp,image/avif,image/heic,image/heif,image/bmp,image/tiff,image/jp2,video/*,.mkv,.m4v,.mpeg,.mpg">
</label>
</div>
<button id="media-upload-button" class="primary-button" type="submit">上传文件</button>
<progress id="media-upload-progress" max="100" value="0" hidden></progress>
</form>
<p id="media-import-feedback" class="settings-feedback" aria-live="polite"
data-ui-copy-default-text="选择文件并填写保存名称。"></p>
<div id="media-import-jobs" class="media-job-list" aria-live="polite"></div>
</section>
<section id="workspace-animations" class="workspace" aria-labelledby="animations-title" hidden>
<div class="workspace-heading">
<div><p class="eyebrow" data-ui-copy-default-text="内容"></p><h2 id="animations-title">动图管理</h2></div>
<button id="refresh-animations" class="secondary-button" type="button">刷新</button>
</div>
<div class="animation-toolbar panel">
<button id="create-animation" class="primary-button" type="button">新建动图</button>
<button id="add-animation-frame" type="button" aria-disabled="true" data-unavailable-reason="请先新建或打开一个动图,再把当前画板添加为帧。">从当前画板新建帧</button>
</div>
<p id="animation-feedback" class="settings-feedback" aria-live="polite">请选择或新建动图。</p>
<section id="animation-frame-panel" class="panel" hidden>
<div class="panel-heading"><h3 id="animation-frame-title">当前动图</h3></div>
<div id="animation-batch-toolbar" class="animation-batch-toolbar" aria-label="动图帧批量操作">
<p id="animation-selection-status" aria-live="polite">未选择帧</p>
<div class="animation-batch-actions">
<button id="animation-batch-copy" type="button" aria-disabled="true" data-unavailable-reason="请先勾选至少一帧。">批量复制</button>
<button id="animation-batch-move" type="button" aria-disabled="true" data-unavailable-reason="请先勾选至少一帧。">批量移动</button>
<button id="animation-batch-delete" class="danger-button" type="button" aria-disabled="true" data-unavailable-reason="请先勾选至少一帧。">批量删除</button>
</div>
</div>
<div id="animation-frames" class="template-grid" aria-label="动图帧"></div>
<nav id="animation-frame-pagination" class="animation-frame-pagination" aria-label="动图帧分页" hidden>
<button id="animation-page-prev" type="button">上一页</button>
<p id="animation-page-status" aria-live="polite">第 1 / 1 页 · 共 0 帧</p>
<button id="animation-page-next" type="button">下一页</button>
</nav>
</section>
<section class="animation-existing" aria-labelledby="animation-existing-title">
<div class="panel-heading"><h3 id="animation-existing-title">保存的动图</h3></div>
<div id="animation-folders" class="animation-folder-list" aria-label="已有动图"></div>
</section>
</section>
<section id="workspace-templates" class="workspace" aria-labelledby="templates-title" hidden>
<div class="workspace-heading">
<div>
<p class="eyebrow" data-ui-copy-default-text="内容"></p>
<h2 id="templates-title">模板管理</h2>
</div>
<div class="workspace-heading-actions">
<button id="select-default-content" class="secondary-button" type="button" aria-pressed="false">设置默认显示内容</button>
<button id="refresh-templates" class="secondary-button" type="button">刷新</button>
</div>
</div>
<p id="template-feedback" class="settings-feedback" aria-live="polite">正在读取模板…</p>
<div id="template-grid" class="template-grid" aria-label="已保存模板"></div>
</section>
<section id="workspace-settings" class="workspace" aria-labelledby="settings-title" hidden>
<div class="workspace-heading">
<div>
<p class="eyebrow" data-ui-copy-default-text="设备"></p>
<h2 id="settings-title">系统设置</h2>
</div>
</div>
<article class="panel setting-card">
<div>
<div class="panel-heading">
<div>
<h3>屏幕亮度</h3>
<p class="hint">拖动时自动应用,松手后立即保存。(使用电池时,不建议亮度长期高于70)</p>
</div>
<output id="brightness-value" data-ui-copy-value for="config-brightness" class="brightness-output">40%</output>
</div>
<input id="config-brightness" class="brightness-slider" type="range" min="1" max="100" value="40">
<p
id="brightness-protection-status"
class="brightness-protection-status"
data-mode="inactive"
role="status"
aria-live="polite"
aria-atomic="true"
hidden
></p>
</div>
</article>
<article class="panel setting-card">
<div>
<h3>屏幕方向</h3>
<p class="hint">角度仅作为4个方向的标识符,和现实方向无关,设置到实际方向正确之后自动储存在系统内。</p>
</div>
<div class="orientation-grid" role="group" aria-label="屏幕方向(设定值和现实角度无关,仅为软件内部旋转值)">
<button type="button" data-orientation="0">0°</button>
<button type="button" data-orientation="90">90°</button>
<button type="button" data-orientation="180">180°</button>
<button type="button" data-orientation="270">270°</button>
</div>
</article>
<article class="panel setting-card">
<div>
<h3>屏幕扫描刷新率</h3>
<p class="hint">设置屏幕的最高扫描频率,不建议低于80Hz。更低的频率可以大大降低功耗,但会导致屏幕闪烁。</p>
</div>
<div id="matrix-refresh-rate-grid" class="refresh-rate-grid" role="group" aria-label="屏幕扫描刷新率上限">
<button type="button" data-matrix-refresh-rate="15">15 Hz</button>
<button type="button" data-matrix-refresh-rate="20">20 Hz</button>
<button type="button" data-matrix-refresh-rate="30">30 Hz</button>
<button type="button" data-matrix-refresh-rate="45">45 Hz</button>
<button type="button" data-matrix-refresh-rate="60">60 Hz</button>
<button type="button" data-matrix-refresh-rate="80">80 Hz</button>
<button type="button" data-matrix-refresh-rate="100">100 Hz</button>
</div>
<p class="hint" data-ui-copy-default-text="这里设置的是扫描上限,不是保证精确达到的实时测量值,也不同于下方网页预览刷新间隔。"></p>
</article>
<article class="panel setting-card voltage-setting-card">
<div>
<h3>供电电压</h3>
<p class="hint">设备的输入电压应该为5V,稍微低一点是正常的。但如果严重偏低请充电。</p>
</div>
<dl class="voltage-details">
<div><dt>当前电压</dt><dd id="voltage-current" data-ui-copy-value>-</dd></div>
<div><dt>传感器状态</dt><dd id="voltage-sensor-status" data-ui-copy-value>正在读取</dd></div>
<div><dt>校准状态</dt><dd id="voltage-calibration-status" data-ui-copy-value>未校准</dd></div>
<div><dt>校准时间</dt><dd id="voltage-calibrated-at" data-ui-copy-value>-</dd></div>
</dl>
<div class="voltage-actions">
<button id="start-voltage-calibration" class="primary-button" type="button" aria-disabled="true" data-unavailable-reason="正在读取电压传感器状态,请稍候。">校准电压传感器</button>
<button id="reset-voltage-calibration" type="button">恢复标称值</button>
</div>
<div class="low-voltage-protection-setting">
<div class="low-voltage-protection-copy">
<h3>低电压亮度保护</h3>
<p id="low-voltage-protection-description" class="hint">开启后,电压降低时会限制实际亮度;严重欠压时会临时屏蔽用户画面。限制亮度解除之后恢复到用户设置的亮度</p>
</div>
<label class="switch-control" for="low-voltage-protection-enabled">
<input
id="low-voltage-protection-enabled"
type="checkbox"
role="switch"
aria-label="低电压亮度保护"
aria-describedby="low-voltage-protection-description low-voltage-protection-feedback"
aria-disabled="true"
data-unavailable-reason="正在读取保护开关状态,请稍候。"
>
<span class="switch-track" aria-hidden="true"></span>
<span id="low-voltage-protection-switch-state" class="switch-state">关闭</span>
</label>
</div>
<p
id="low-voltage-protection-feedback"
class="low-voltage-protection-feedback"
role="status"
aria-live="polite"
aria-atomic="true"
hidden
></p>
</article>
<article class="panel setting-card">
<div>
<h3>预览刷新间隔</h3>
<p class="hint">设置设备状态和系统设置中当前屏幕画面的刷新速度。</p>
</div>
<label class="number-unit-field" for="preview-refresh-interval">
<input
id="preview-refresh-interval"
type="number"
min="1"
max="60000"
step="1"
inputmode="numeric"
value="1000"
>
<span>毫秒</span>
</label>
<p id="preview-refresh-warning" class="preview-refresh-warning" role="status" hidden>
刷新间隔过短,会增加核桃派负载,可能影响系统运行。
</p>
<p id="preview-refresh-error" class="error-text" role="alert" hidden></p>
</article>
<article class="panel setting-card wifi-setting-card">
<div>
<h3>WiFi设置</h3>
<p class="hint">设备会自动连接保存的WiFi。如果一段时间没有连接,会显示设备保存的WiFi信息,请开启对应WiFi用于连接设备。设备连接WiFi后,如果没有进入网页端就会显示对应ip</p>
</div>
<div class="wifi-fields">
<label>WiFi 名称(SSID)
<input id="wifi-ssid" data-ui-copy-ignore type="text" autocomplete="off" aria-describedby="wifi-ssid-modified" required>
<span id="wifi-ssid-modified" class="wifi-modified-note" role="status" hidden>对应内容已经修改</span>
</label>
<label>WiFi 密码
<input id="wifi-password" data-ui-copy-ignore type="text" autocomplete="off" aria-describedby="wifi-password-modified" placeholder="读取中…">
<span id="wifi-password-modified" class="wifi-modified-note" role="status" hidden>对应内容已经修改</span>
</label>
<div class="wifi-dhcp-row">
<div>
<strong>自动获取 IP(DHCP)</strong>
<p id="wifi-dhcp-description" class="hint">关闭后可指定固定 IPv4 地址。</p>
</div>
<label class="switch-control" for="wifi-dhcp-enabled">
<input id="wifi-dhcp-enabled" type="checkbox" role="switch" aria-describedby="wifi-dhcp-description wifi-dhcp-modified" checked>
<span class="switch-track" aria-hidden="true"></span>
<span id="wifi-dhcp-state" class="switch-state">开启</span>
</label>
<p id="wifi-dhcp-modified" class="wifi-modified-note" role="status" hidden>对应内容已经修改</p>
</div>
<div id="wifi-manual-fields" class="wifi-manual-fields" hidden>
<label>静态 IPv4 地址
<input id="wifi-address" data-ui-copy-ignore type="text" inputmode="decimal" aria-describedby="wifi-address-modified" placeholder="192.168.1.50">
<span id="wifi-address-modified" class="wifi-modified-note" role="status" hidden>对应内容已经修改</span>
</label>
<label>网关
<input id="wifi-gateway" data-ui-copy-ignore type="text" inputmode="decimal" aria-describedby="wifi-gateway-modified" placeholder="192.168.1.1">
<span id="wifi-gateway-modified" class="wifi-modified-note" role="status" hidden>对应内容已经修改</span>
</label>
<label>子网前缀(选填)
<input id="wifi-prefix" data-ui-copy-ignore type="number" min="1" max="32" step="1" inputmode="numeric" aria-describedby="wifi-prefix-modified" placeholder="24">
<span id="wifi-prefix-modified" class="wifi-modified-note" role="status" hidden>对应内容已经修改</span>
</label>
<label>DNS(选填)
<input id="wifi-dns" data-ui-copy-ignore type="text" inputmode="decimal" aria-describedby="wifi-dns-modified" placeholder="留空使用网关,多个用逗号分隔">
<span id="wifi-dns-modified" class="wifi-modified-note" role="status" hidden>对应内容已经修改</span>
</label>
</div>
<p id="wifi-active-status" class="hint" role="status">正在读取当前网络…</p>
</div>
<div class="wifi-actions">
<button id="wifi-apply-immediate" type="button">立即生效(会导致网页断开)</button>
<button id="wifi-apply-next-boot" type="button">下次开机生效</button>
</div>
<p id="wifi-feedback" class="settings-feedback" aria-live="polite" hidden></p>
</article>
<article class="panel setting-card wifi-prompt-setting-card">
<div>
<h3>网络提示显示</h3>
<p class="hint">设置设备开机后等待多少秒再显示网络连接提示。</p>
</div>
<div class="wifi-prompt-delay-row">
<label class="number-unit-field" for="wifi-prompt-delay">
<input id="wifi-prompt-delay" type="number" min="1" max="3600" step="1" inputmode="numeric" value="30">
<span>秒后显示网络提示</span>
</label>
<button id="wifi-prompt-delay-save" type="button">保存</button>
</div>
<p id="wifi-prompt-feedback" class="settings-feedback" aria-live="polite" hidden></p>
</article>
<article class="panel setting-card ota-setting-card">
<div>
<h3>软件版本</h3>
</div>
<dl class="ota-version-details">
<div><dt>当前软件版本</dt><dd id="ota-current-version" data-ui-copy-value>读取中…</dd></div>
<div><dt>最新功能更新时间</dt><dd id="ota-feature-updated-at" data-ui-copy-value>读取中…</dd></div>
</dl>
<input id="ota-file-input" class="sr-only" type="file" accept=".ota,application/octet-stream">
<div class="ota-actions">
<button id="ota-select-file" class="primary-button" type="button">导入 OTA 更新</button>
<span class="ota-power-warning">不要在更新期间断电。</span>
</div>
<div id="ota-progress-group" class="ota-progress-group" hidden>
<div class="ota-progress-heading">
<strong id="ota-progress-stage" data-ui-copy-value>准备更新</strong>
<span id="ota-progress-value" data-ui-copy-value>0%</span>
</div>
<progress id="ota-progress" max="100" value="0">0%</progress>
</div>
<p id="ota-feedback" class="settings-feedback" role="status" aria-live="polite" aria-atomic="true" hidden></p>
<div id="ota-failure-log-actions" class="ota-failure-log-actions" hidden>
<button id="ota-view-failure-log" type="button">查看失败日志</button>
<button id="ota-copy-failure-log" type="button">复制日志</button>
</div>
</article>
<article class="panel setting-card">
<div class="low-voltage-protection-setting">
<div class="low-voltage-protection-copy">
<h3>性能模式</h3>
<p id="performance-mode-description" class="hint">如果使用出现卡顿,可以打开此选项,但会降低续航。</p>
</div>
<label class="switch-control" for="performance-mode-enabled">
<input
id="performance-mode-enabled"
type="checkbox"
role="switch"
aria-label="性能模式"
aria-describedby="performance-mode-description performance-mode-feedback"
aria-disabled="true"
data-unavailable-reason="正在读取 CPU 调频状态,请稍候。"
>
<span class="switch-track" aria-hidden="true"></span>
<span id="performance-mode-switch-state" class="switch-state">关闭</span>
</label>
</div>
<p
id="performance-mode-feedback"
class="low-voltage-protection-feedback"
role="status"
aria-live="polite"
aria-atomic="true"
>正在读取 CPU 调频状态…</p>
</article>
<article class="panel setting-card">
<div>
<h3>模板界面同时播放的动图数量</h3>
<p id="animation-preview-concurrency-warning" class="hint">不建议开启太多,否则会出现严重的性能问题。</p>
</div>
<label class="number-unit-field" for="animation-preview-max-concurrent">
<input
id="animation-preview-max-concurrent"
type="number"
min="1"
max="50"
step="1"
inputmode="numeric"
value="2"
aria-describedby="animation-preview-concurrency-warning animation-preview-concurrency-feedback"
>
<span>个</span>
</label>
<p
id="animation-preview-concurrency-feedback"
class="settings-feedback"
role="status"
aria-live="polite"
aria-atomic="true"
hidden
></p>
</article>
<article class="panel setting-card frp-setting-card">
<div>
<h3>frp配置</h3>
<p class="hint">此功能通常是远程排查使用,请保证你知道你在做什么。</p>
</div>
<dl class="frp-status-details">
<div><dt>当前配置</dt><dd id="frp-selected-name">无</dd></div>
<div><dt>运行状态</dt><dd id="frp-service-state">关闭</dd></div>
</dl>
<p id="frp-feedback" class="settings-feedback" role="status" aria-live="polite"></p>
<div class="frp-actions">
<button id="frp-service-toggle" class="primary-button" type="button" aria-disabled="true" data-unavailable-reason="请先导入或新建 FRP 配置。">启动frp</button>
<button id="frp-edit-config" type="button">编辑配置文件</button>
<button id="network-diagnostics-run" type="button">网络检测</button>
</div>
</article>
<p id="settings-feedback" class="settings-feedback" aria-live="polite" hidden></p>
</section>
</main>
</div>
</div>
<dialog id="template-name-dialog" class="template-dialog">
<form method="dialog">
<div class="template-dialog-heading">
<h2 id="template-dialog-title">保存为模板</h2>
<p id="template-dialog-note" hidden></p>
</div>
<label>模板名称
<input id="template-name-input" type="text" maxlength="80" autocomplete="off" required>
</label>
<div class="template-dialog-actions">
<button type="submit" value="cancel" formnovalidate>取消</button>
<button class="primary-button" type="submit" value="save">确认</button>
</div>
</form>
</dialog>
<dialog id="template-confirm-dialog" class="template-dialog">
<form method="dialog">
<h2>确认模板操作</h2>
<p id="template-confirm-message"></p>
<div class="template-dialog-actions">
<button type="submit" value="cancel">取消</button>
<button class="primary-button" type="submit" value="confirm">确认</button>
</div>
</form>
</dialog>
<dialog id="template-save-choice-dialog" class="template-dialog">
<form method="dialog">
<h2>保存模板</h2>
<p id="template-save-choice-message">请选择保存方式。</p>
<div class="template-dialog-actions template-save-choice-actions">
<button type="submit" value="cancel">取消</button>
<button id="template-save-new" type="submit" value="new">另存为新模板</button>
<button id="template-save-current" class="primary-button" type="submit" value="current">保存到当前模板</button>
</div>
</form>
</dialog>
<dialog id="library-copy-dialog" class="template-dialog">
<form method="dialog">
<h2>选择复制目标</h2>
<label>复制到
<select id="library-copy-target"><option value="static">静态模板</option></select>
</label>
<div class="template-dialog-actions"><button value="cancel">取消</button><button class="primary-button" value="copy">复制</button></div>
</form>
</dialog>
<dialog id="animation-dirty-dialog" class="template-dialog">
<form method="dialog">
<h2>当前动图帧尚未保存</h2>
<p id="animation-dirty-message">是否应用当前修改到动图?</p>
<div class="template-dialog-actions"><button value="cancel">取消</button><button value="discard">否,放弃修改</button><button class="primary-button" value="save">应用修改</button></div>
</form>
</dialog>
<dialog id="animation-operation-dialog" class="template-dialog">
<form method="dialog">
<h2 id="animation-operation-title">确认动图操作</h2>
<p id="animation-operation-message"></p>
<div class="template-dialog-actions">
<button type="submit" value="cancel">取消</button>
<button id="animation-operation-confirm" class="primary-button" type="submit" value="confirm">确认</button>
</div>
</form>
</dialog>
<dialog id="animation-copy-destination-dialog" class="template-dialog">
<form method="dialog">
<h2>选择批量复制目标</h2>
<label>复制到动图
<select id="animation-copy-destination"></select>
</label>
<div class="template-dialog-actions">
<button type="submit" value="cancel">取消</button>
<button class="primary-button" type="submit" value="next">下一步</button>
</div>
</form>
</dialog>
<dialog id="animation-position-dialog" class="template-dialog animation-position-dialog">
<form method="dialog">
<h2 id="animation-position-title">选择插入位置</h2>
<p id="animation-position-hint">选择目标帧后,所选帧将插入到该帧之后;选择“插入到最前”则置于第一帧前。</p>
<label>插入位置
<select id="animation-position-target"></select>
</label>
<div class="template-dialog-actions">
<button type="submit" value="cancel">取消</button>
<button class="primary-button" type="submit" value="confirm">确认位置</button>
</div>
</form>
</dialog>
<dialog id="voltage-calibration-dialog" class="template-dialog voltage-calibration-dialog">
<form method="dialog">
<h2>校准电压传感器</h2>
<p>保持整机正常约 5V。把可靠万用表并联到 ADC 相同的屏幕输入 5V/GND 测量点,再输入万用表读数。</p>
<label>万用表参考电压(V)
<input id="voltage-reference-input" type="number" min="4.500" max="5.500" step="0.001" inputmode="decimal" value="5.000" required>
</label>
<p id="voltage-calibration-dialog-feedback" class="hint" aria-live="polite">点击“采样并预览”,约需 1 秒。</p>
<dl id="voltage-calibration-preview" class="voltage-details" hidden>
<div><dt>参考电压</dt><dd id="calibration-reference-result">-</dd></div>
<div><dt>未校准读数</dt><dd id="calibration-raw-result">-</dd></div>
<div><dt>当前系数</dt><dd id="calibration-current-factor">-</dd></div>
<div><dt>建议系数</dt><dd id="calibration-proposed-factor">-</dd></div>
<div><dt>预计结果</dt><dd id="calibration-projected-result">-</dd></div>
</dl>
<div class="voltage-calibration-actions">
<button type="submit" value="cancel">取消</button>
<button id="preview-voltage-calibration" type="button">采样并预览</button>
<button id="confirm-voltage-calibration" class="primary-button" type="button" aria-disabled="true" data-unavailable-reason="请先完成“采样并预览”,再保存校准。">保存校准</button>
</div>
</form>
</dialog>
<dialog id="current-display-dialog" class="template-dialog current-display-dialog" aria-labelledby="current-display-dialog-title">
<div class="current-display-dialog-body">
<div class="current-display-dialog-heading">
<div>
<h2 id="current-display-dialog-title">当前画面预览</h2>
<p id="current-display-dialog-name" class="subtle" data-ui-copy-value>正在读取…</p>
</div>
<button id="current-display-dialog-close" type="button" aria-label="关闭当前画面预览">关闭</button>
</div>
<button id="current-display-preview-surface" class="current-display-preview-surface" type="button" aria-label="当前实屏逻辑帧预览">
<img id="current-display-preview-image" alt="当前实屏逻辑帧" hidden>
<span id="current-display-preview-placeholder">正在读取当前画面…</span>
</button>
<section id="current-display-playback-controls" class="current-display-playback-controls" aria-label="动图播放控制" hidden>
<span id="current-display-seek-unavailable-reason" class="sr-only">请先完成当前进度跳转。</span>
<div class="current-display-progress-heading">
<label for="current-display-progress">播放位置</label>
<output id="current-display-progress-time" for="current-display-progress" data-ui-copy-value>0:00.00 / 0:00.00</output>
</div>
<input id="current-display-progress" type="range" min="0" max="0" step="1" value="0" aria-label="动图播放位置">
<div class="current-display-playback-actions">
<button id="current-display-play-pause" class="primary-button" type="button">暂停</button>
<div id="current-display-speed-controls" class="current-display-speed-controls" role="group" aria-label="动图播放倍速">
<button type="button" data-playback-speed="0.5">0.5×</button>
<button type="button" data-playback-speed="1">1×</button>
<button type="button" data-playback-speed="1.5">1.5×</button>
<button type="button" data-playback-speed="2">2×</button>
</div>
</div>
<p id="current-display-playback-feedback" class="hint" aria-live="polite" hidden></p>
</section>
</div>
</dialog>
<dialog id="resource-details-dialog" class="template-dialog resource-details-dialog" aria-labelledby="resource-details-title">
<form method="dialog">
<h2 id="resource-details-title">资源占用详情</h2>
<p id="resource-details-message" class="resource-details-message" role="status"></p>
<section class="resource-details-section" aria-labelledby="resource-cpu-title">
<h3 id="resource-cpu-title">CPU</h3>
<dl class="resource-details-grid">
<div><dt>应用占用核心</dt><dd id="resource-cpu-app-cores">-</dd></div>
<div><dt>折合单核占用</dt><dd id="resource-cpu-single-percent">-</dd></div>
<div><dt>占整机 CPU</dt><dd id="resource-cpu-app-percent">-</dd></div>
<div><dt>整机 CPU 总占用</dt><dd id="resource-cpu-total-percent">-</dd></div>
</dl>
<div id="resource-core-list" class="resource-core-list" aria-label="每核心 CPU 占用"></div>
</section>
<section class="resource-details-section" aria-labelledby="resource-memory-title">
<h3 id="resource-memory-title">内存</h3>
<dl class="resource-details-grid">
<div><dt>应用常驻内存</dt><dd id="resource-memory-app">-</dd></div>
<div><dt>应用占整机内存</dt><dd id="resource-memory-app-percent">-</dd></div>
<div><dt>整机内存总占用</dt><dd id="resource-memory-total-percent">-</dd></div>
</dl>
</section>
<div class="template-dialog-actions resource-dialog-actions">
<button id="resource-details-close" class="primary-button" type="submit" value="close">关闭</button>
</div>
</form>
</dialog>
<dialog id="ota-failure-log-dialog" class="template-dialog ota-failure-log-dialog" aria-labelledby="ota-failure-log-title">
<div class="ota-failure-log-dialog-body">
<div class="template-dialog-heading">
<div>
<h2 id="ota-failure-log-title">OTA 更新失败日志</h2>
<p>可将完整内容复制后提供给维护人员排查。</p>
</div>
</div>
<pre id="ota-failure-log-content" tabindex="0" aria-label="OTA 更新失败诊断日志"></pre>
<p id="ota-failure-log-feedback" class="settings-feedback" role="status" aria-live="polite" hidden></p>
<div class="template-dialog-actions">
<button id="ota-failure-log-close" type="button">关闭</button>
<button id="ota-failure-log-copy" class="primary-button" type="button">复制全部日志</button>
</div>
</div>
</dialog>
<dialog id="frp-config-dialog" class="template-dialog frp-config-dialog" aria-labelledby="frp-config-dialog-title">
<div class="frp-config-dialog-body">
<div class="template-dialog-heading">
<div>
<h2 id="frp-config-dialog-title">编辑 FRP 配置文件</h2>
<p>配置可能包含服务器地址和令牌,请只在可信网络中操作。</p>
</div>
</div>
<label>已保存配置
<select id="frp-config-select"><option value="">暂无配置</option></select>
</label>
<label>配置名称
<input id="frp-config-name" type="text" maxlength="80" autocomplete="off" placeholder="远程维护">
</label>
<label>文件格式
<select id="frp-config-format">
<option value=".toml">TOML</option>
<option value=".yaml">YAML</option>
<option value=".json">JSON</option>
<option value=".ini">INI(兼容格式)</option>
</select>
</label>
<label>配置原文
<textarea id="frp-config-content" data-ui-copy-ignore spellcheck="false" aria-describedby="frp-config-feedback"></textarea>
</label>
<input id="frp-config-file" class="sr-only" type="file" accept=".toml,.yaml,.yml,.json,.ini,text/plain,application/json">
<p id="frp-config-feedback" class="settings-feedback" role="status" aria-live="polite"></p>
<div class="frp-config-secondary-actions">
<button id="frp-config-new" type="button">新建空白配置</button>
<button id="frp-config-upload" type="button">上传配置文件</button>
<button id="frp-config-delete" class="danger-button" type="button">删除</button>
</div>
<div class="template-dialog-actions">
<button id="frp-config-close" type="button">关闭</button>
<button id="frp-config-save" class="primary-button" type="button">保存配置</button>
</div>
</div>
</dialog>
<dialog id="network-diagnostics-dialog" class="template-dialog network-diagnostics-dialog" aria-labelledby="network-diagnostics-title">
<div class="network-diagnostics-dialog-body">
<h2 id="network-diagnostics-title">网络检测结果</h2>
<p id="network-diagnostics-summary" role="status">正在检测…</p>
<ul id="network-diagnostics-checks"></ul>
<div class="template-dialog-actions">
<button id="network-diagnostics-close" class="primary-button" type="button">关闭</button>
</div>
</div>
</dialog>
<div id="control-unavailable-tooltip" class="control-unavailable-tooltip" role="tooltip" hidden></div>
<div id="control-feedback-toast" class="control-feedback-toast" role="status" aria-live="polite" aria-atomic="true" hidden></div>
<noscript>此控制台需要启用 JavaScript。</noscript>
<script type="module" src="/static/app.js"></script>
</body>
</html>