From 106bbcaa567a0c1fa9fd58169d939ef56fba76de Mon Sep 17 00:00:00 2001 From: HIsheR Date: Mon, 26 Oct 2026 18:47:04 +0800 Subject: [PATCH] =?UTF-8?q?fix(ui):=20=E6=94=B6=E6=95=9B=E6=BF=80=E6=B4=BB?= =?UTF-8?q?=E6=80=81=E6=8F=8F=E8=BE=B9=E3=80=81=E4=BF=AE=E6=AD=A3=E5=86=85?= =?UTF-8?q?=E5=B1=82=E5=9C=86=E8=A7=92=E6=BA=A2=E5=87=BA=E5=B9=B6=E6=9B=BF?= =?UTF-8?q?=E6=8D=A2=20emoji=20=E5=9B=BE=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/demo/main.ts | 26 ++++++++++++-- llms-full.txt | 6 ++-- packages/design/tokens/tokens.css | 7 ++-- packages/design/tokens/tokens.json | 45 +++++++++++++++++++++++-- packages/skin/ui/src/dataTable.ts | 4 +-- packages/skin/ui/src/icons.ts | 30 +++++++++++++++++ packages/skin/ui/ui.css | 54 +++++++++++++++++++++--------- packages/tools/cli/recipes.md | 2 +- 8 files changed, 147 insertions(+), 27 deletions(-) create mode 100644 packages/skin/ui/src/icons.ts diff --git a/apps/demo/main.ts b/apps/demo/main.ts index a81c567..89f2a4a 100644 --- a/apps/demo/main.ts +++ b/apps/demo/main.ts @@ -316,7 +316,7 @@ const content = h( '双向绑定', 'textField 与信号双向同步:输入即回显;prefix/suffix 渲染为输入框内侧附加块;clearable 开启后有值即显一键清空。', textField({ placeholder: '输入名字…', value: name }), - textField({ placeholder: '搜索…', value: name, prefix: '🔍', clearable: true }), + textField({ placeholder: '搜索…', value: name, prefix: searchIcon(), clearable: true }), textField({ value: price, suffix: '元', clearable: true }), h('span', { class: 'out' }, () => (name() ? `你好,${name()}!` : '等待输入…')), ), @@ -663,7 +663,7 @@ const content = h( button({ label: '切换数据态', onClick: () => noResult(!noResult()) }), ), () => (noResult() - ? empty({ title: '暂无匹配结果', description: '试试放宽筛选条件,或清空关键词重新搜索。', icon: '🔍', action: button({ label: '重置筛选', variant: 'ghost', onClick: () => noResult(false) }) }) + ? empty({ title: '暂无匹配结果', description: '试试放宽筛选条件,或清空关键词重新搜索。', icon: searchIcon(), action: button({ label: '重置筛选', variant: 'ghost', onClick: () => noResult(false) }) }) : card({ title: '搜索结果' }, h('p', null, '这里展示命中的数据列表。'))), ), section( @@ -1585,3 +1585,25 @@ const app = h('div', { class: 'shell' }, toc, content); mount('#app', () => app); buildToc(toc, content); + +/*** 放大镜图标:demo 内联 SVG,避免 emoji 在不同系统渲染成彩色位图 ***/ +function searchIcon(): HTMLElement { + const ns = 'http://www.w3.org/2000/svg'; + const holder = document.createElement('span'); + holder.style.display = 'inline-flex'; + const svg = document.createElementNS(ns, 'svg'); + svg.setAttribute('viewBox', '0 0 16 16'); + svg.setAttribute('width', '14'); + svg.setAttribute('height', '14'); + svg.setAttribute('fill', 'none'); + svg.setAttribute('stroke', 'currentColor'); + svg.setAttribute('stroke-width', '1.5'); + svg.setAttribute('stroke-linecap', 'round'); + const lens = document.createElementNS(ns, 'circle'); + lens.setAttribute('cx', '7'); lens.setAttribute('cy', '7'); lens.setAttribute('r', '4.5'); + const handle = document.createElementNS(ns, 'line'); + handle.setAttribute('x1', '10.5'); handle.setAttribute('y1', '10.5'); handle.setAttribute('x2', '14'); handle.setAttribute('y2', '14'); + svg.append(lens, handle); + holder.append(svg); + return holder; +} diff --git a/llms-full.txt b/llms-full.txt index e955a27..f71525a 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -2185,6 +2185,7 @@ node packages/tools/cli/bin/ua.js |docs|tokens - --ua-color-highlight: rgba(255, 255, 255, 0.5)(暗色 rgba(255, 255, 255, 0.08)) - --ua-color-accent: #185fa5(暗色 #378add) - --ua-color-accent-contrast: #ffffff(暗色 #042c53) +- --ua-color-accent-soft: color-mix(in srgb, var(--ua-color-accent) 72%, var(--ua-color-bg))(暗色 color-mix(in srgb, var(--ua-color-accent) 82%, var(--ua-color-bg))) - --ua-color-tint-accent: color-mix(in srgb, var(--ua-color-accent) 10%, transparent)(暗色 color-mix(in srgb, var(--ua-color-accent) 14%, transparent)) - --ua-color-tint-accent-weak: color-mix(in srgb, var(--ua-color-accent) 5%, transparent)(暗色 color-mix(in srgb, var(--ua-color-accent) 8%, transparent)) - --ua-color-danger: #a32d2d(暗色 #d96a6a) @@ -2193,6 +2194,7 @@ node packages/tools/cli/bin/ua.js |docs|tokens - --ua-radius-sm: 4px - --ua-radius-md: 8px - --ua-radius-lg: 12px +- --ua-radius-md-inset: calc(var(--ua-radius-md) - 1px) - --ua-space-1: 4px - --ua-space-2: 8px - --ua-space-3: 12px @@ -2253,7 +2255,7 @@ node packages/tools/cli/bin/ua.js |docs|tokens - --ua-focus-ring-warning: 0 0 0 3px color-mix(in srgb, var(--ua-color-warning) 25%, transparent)(暗色 0 0 0 3px color-mix(in srgb, var(--ua-color-warning) 34%, transparent)) - --ua-focus-ring-danger: 0 0 0 3px color-mix(in srgb, var(--ua-color-danger) 18%, transparent)(暗色 0 0 0 3px color-mix(in srgb, var(--ua-color-danger) 30%, transparent)) - --ua-focus-ring-pulse: 0 0 0 5px color-mix(in srgb, var(--ua-color-accent) 4%, transparent)(暗色 0 0 0 5px color-mix(in srgb, var(--ua-color-accent) 8%, transparent)) -- --ua-focus-ring-selected: 0 0 0 2px var(--ua-color-accent) +- --ua-focus-ring-selected: 0 0 0 1.5px var(--ua-color-accent-soft) - --ua-control-h-sm: 28px - --ua-control-h-md: 34px - --ua-control-h-lg: 40px @@ -2462,7 +2464,7 @@ effect(() => { document.documentElement.style.colorScheme = dark() ? 'dark' : 'light'; // 原生控件(date/滚动条)跟着变 localStorage.setItem('ua-theme', dark() ? 'dark' : 'light'); }); -floatButton({ icon: '🌗', label: '切换主题', onClick: () => dark(!dark()) }); +floatButton({ label: '切换主题', onClick: () => dark(!dark()) }); ``` 要点: diff --git a/packages/design/tokens/tokens.css b/packages/design/tokens/tokens.css index eb8c883..8730228 100644 --- a/packages/design/tokens/tokens.css +++ b/packages/design/tokens/tokens.css @@ -19,6 +19,7 @@ --ua-color-highlight: rgba(255, 255, 255, 0.5); /* 内高光白:主按钮等实色控件顶缘的 1px 高光(暗色必须降档,否则刺眼) */ --ua-color-accent: #185fa5; /* 强调色:主操作、选中态、焦点环底色 */ --ua-color-accent-contrast: #ffffff; /* 强调色上的文字色:保证主按钮对比度 */ + --ua-color-accent-soft: color-mix(in srgb, var(--ua-color-accent) 72%, var(--ua-color-bg)); /* 柔和强调色:细线 / 指示条 / 内嵌焦点环用,纯 accent 做 2px 实线会发刺 */ --ua-color-tint-accent: color-mix(in srgb, var(--ua-color-accent) 10%, transparent); /* 语义淡底标准档:选中底、激活项底 */ --ua-color-tint-accent-weak: color-mix(in srgb, var(--ua-color-accent) 5%, transparent); /* 语义淡底弱档:悬停底、极弱强调底 */ --ua-color-danger: #a32d2d; /* 危险色:错误、删除、校验失败 */ @@ -29,6 +30,7 @@ --ua-radius-sm: 4px; /* 小圆角:按钮 / 输入框 / 标签等控件 */ --ua-radius-md: 8px; /* 中圆角:卡片 / 面板 / 分组容器 */ --ua-radius-lg: 12px; /* 大圆角:弹窗 / 抽屉等浮层外壳 */ + --ua-radius-md-inset: calc(var(--ua-radius-md) - 1px); /* 内衬圆角:贴边的内层块(输入附加块等)与外层弧线对齐,减去 1px 边框 */ /* ---- 间距:4px 基准四档 + 表格单元格复合值 ---- */ --ua-space-1: 4px; /* 间距 1 档:图标与文字之间、紧邻元素 */ @@ -105,7 +107,7 @@ --ua-focus-ring-warning: 0 0 0 3px color-mix(in srgb, var(--ua-color-warning) 25%, transparent); /* 语义警告焦点环:评分等以警告色为主色的控件 */ --ua-focus-ring-danger: 0 0 0 3px color-mix(in srgb, var(--ua-color-danger) 18%, transparent); /* 错误强调环:校验失败时叠在被标红控件外侧,比 danger 描边更外扩一圈 */ --ua-focus-ring-pulse: 0 0 0 5px color-mix(in srgb, var(--ua-color-accent) 4%, transparent); /* 呼吸光晕档:与焦点环交替出现做脉冲动画(时间线条目) */ - --ua-focus-ring-selected: 0 0 0 2px var(--ua-color-accent); /* 选中环(非焦点):色板格子等「当前选中」标记,实色不透明 */ + --ua-focus-ring-selected: 0 0 0 1.5px var(--ua-color-accent-soft); /* 选中环(非焦点):细一档 + 柔和色,2px 实色在色板/网格里像加粗边框 */ /* ---- 控件高度:三档,略放大以提升可点面积 ---- */ --ua-control-h-sm: 28px; /* 小控件高度:紧凑工具栏、标签内嵌按钮 */ @@ -136,6 +138,7 @@ --ua-color-highlight: rgba(255, 255, 255, 0.08); /* 暗色内高光:暗底上白高光刺眼,降到 8% 只留一道细光 */ --ua-color-accent: #378add; /* 暗色强调色:提亮以保证暗底可读 */ --ua-color-accent-contrast: #042c53; /* 暗色强调色上的文字色:深色反白,维持对比 */ + --ua-color-accent-soft: color-mix(in srgb, var(--ua-color-accent) 82%, var(--ua-color-bg)); /* 暗色柔和强调色:与亮色同结构,混入暗底即自然降饱和 */ --ua-color-tint-accent: color-mix(in srgb, var(--ua-color-accent) 14%, transparent); /* 暗色淡底标准档:暗底上淡底不可见,透明度升一档 */ --ua-color-tint-accent-weak: color-mix(in srgb, var(--ua-color-accent) 8%, transparent); /* 暗色淡底弱档:透明度升一档 */ --ua-color-danger: #d96a6a; /* 暗色危险色 */ @@ -159,5 +162,5 @@ --ua-focus-ring-warning: 0 0 0 3px color-mix(in srgb, var(--ua-color-warning) 34%, transparent); /* 暗色警告焦点环 */ --ua-focus-ring-danger: 0 0 0 3px color-mix(in srgb, var(--ua-color-danger) 30%, transparent); /* 暗色错误强调环 */ --ua-focus-ring-pulse: 0 0 0 5px color-mix(in srgb, var(--ua-color-accent) 8%, transparent); /* 暗色呼吸光晕:暗底上低透明度看不见,加倍 */ - --ua-focus-ring-selected: 0 0 0 2px var(--ua-color-accent); /* 暗色选中环:随 accent 翻转,与亮色同结构 */ + --ua-focus-ring-selected: 0 0 0 1.5px var(--ua-color-accent-soft); /* 暗色选中环:随 accent 翻转并与亮色同口径 */ } diff --git a/packages/design/tokens/tokens.json b/packages/design/tokens/tokens.json index b1d6f9c..fff560a 100644 --- a/packages/design/tokens/tokens.json +++ b/packages/design/tokens/tokens.json @@ -44,12 +44,53 @@ "warning": { "light": "#9a6b1f", "dark": "#d9a94f" + }, + "surface2": { + "light": "color-mix(in srgb, var(--ua-color-text) 4%, var(--ua-color-bg))", + "dark": "color-mix(in srgb, var(--ua-color-text) 5%, var(--ua-color-bg))" + }, + "surface3": { + "light": "color-mix(in srgb, var(--ua-color-text) 8%, var(--ua-color-bg))", + "dark": "color-mix(in srgb, var(--ua-color-text) 10%, var(--ua-color-bg))" + }, + "surfaceInset": { + "light": "color-mix(in srgb, var(--ua-color-text) 5%, var(--ua-color-bg))", + "dark": "color-mix(in srgb, var(--ua-color-text) 6%, var(--ua-color-bg))" + }, + "textSecondary": { + "light": "#4b5158", + "dark": "#b4bac2" + }, + "textFaint": { + "light": "#9aa0a8", + "dark": "#62676f" + }, + "overlay": { + "light": "rgba(0, 0, 0, 0.45)", + "dark": "rgba(0, 0, 0, 0.6)" + }, + "overlayStrong": { + "light": "rgba(0, 0, 0, 0.6)", + "dark": "rgba(0, 0, 0, 0.7)" + }, + "tintAccent": { + "light": "color-mix(in srgb, var(--ua-color-accent) 10%, transparent)", + "dark": "color-mix(in srgb, var(--ua-color-accent) 14%, transparent)" + }, + "tintAccentWeak": { + "light": "color-mix(in srgb, var(--ua-color-accent) 5%, transparent)", + "dark": "color-mix(in srgb, var(--ua-color-accent) 8%, transparent)" + }, + "accentSoft": { + "light": "color-mix(in srgb, var(--ua-color-accent) 72%, var(--ua-color-bg))", + "dark": "color-mix(in srgb, var(--ua-color-accent) 82%, var(--ua-color-bg))" } }, "radius": { "sm": "4px", "md": "8px", - "lg": "12px" + "lg": "12px", + "mdInset": "calc(var(--ua-radius-md) - 1px)" }, "space": { "1": "4px", @@ -196,4 +237,4 @@ "modal": 400, "toast": 500 } -} +} \ No newline at end of file diff --git a/packages/skin/ui/src/dataTable.ts b/packages/skin/ui/src/dataTable.ts index ffb748c..d1793b4 100644 --- a/packages/skin/ui/src/dataTable.ts +++ b/packages/skin/ui/src/dataTable.ts @@ -6,7 +6,7 @@ import { sortPlugin, } from '@ui-arch/table-core'; import type { RowKey } from '@ui-arch/table-core'; -import { computed, effect, h, renderList, signal } from '@ui-arch/frame'; +import { iconSearch } from './icons.js'; import { computed, effect, h, renderList, signal } from '@ui-arch/frame'; import { attachResize } from './columnResize.js'; import { exportButton } from './csv.js'; import { button } from './button.js'; @@ -168,7 +168,7 @@ export function dataTable(props: DataTableProps): H const filterInput = textField({ placeholder: `筛选 ${filterKey}…`, - prefix: '🔍', + prefix: iconSearch(), value: keyword, // 受控信号:空状态里的「清除筛选」能同时清掉输入框内容 disabled: props.loading, // 加载中禁止筛选,避免与在途结果竞争 onInput: (value) => { diff --git a/packages/skin/ui/src/icons.ts b/packages/skin/ui/src/icons.ts new file mode 100644 index 0000000..d75e6de --- /dev/null +++ b/packages/skin/ui/src/icons.ts @@ -0,0 +1,30 @@ +/*** 内联图标工厂:组件内部用小图标一律走 SVG,不用 emoji —— emoji 在 Windows / macOS 会渲染成彩色位图,尺寸、颜色、基线都不可控 ***/ + +const NS = 'http://www.w3.org/2000/svg'; // SVG 命名空间常量,createElementNS 必填 + +/*** 放大镜图标:搜索框的默认前缀。外层 span 承载布局,返回 HTMLElement 以兼容 prefix 入参类型 ***/ +export function iconSearch(): HTMLElement { + const holder = document.createElement('span'); + holder.style.display = 'inline-flex'; // 让图标与输入文本在同一基线居中 + holder.setAttribute('aria-hidden', 'true'); // 纯装饰:语义由输入框 placeholder / label 承担 + const svg = document.createElementNS(NS, 'svg'); + svg.setAttribute('viewBox', '0 0 16 16'); + svg.setAttribute('width', '14'); + svg.setAttribute('height', '14'); + svg.setAttribute('fill', 'none'); + svg.setAttribute('stroke', 'currentColor'); // 跟随主题文字色,暗色自动翻转 + svg.setAttribute('stroke-width', '1.5'); + svg.setAttribute('stroke-linecap', 'round'); + const lens = document.createElementNS(NS, 'circle'); + lens.setAttribute('cx', '7'); + lens.setAttribute('cy', '7'); + lens.setAttribute('r', '4.5'); + const handle = document.createElementNS(NS, 'line'); + handle.setAttribute('x1', '10.5'); + handle.setAttribute('y1', '10.5'); + handle.setAttribute('x2', '14'); + handle.setAttribute('y2', '14'); + svg.append(lens, handle); + holder.append(svg); + return holder; +} diff --git a/packages/skin/ui/ui.css b/packages/skin/ui/ui.css index 3a3a2ca..63229c3 100644 --- a/packages/skin/ui/ui.css +++ b/packages/skin/ui/ui.css @@ -485,8 +485,8 @@ body.ua-col-resizing { 但两者的密集列表换成内嵌描边后更整齐;与 cascader 项保持同一口径 */ .ua-option:focus-visible, .ua-menu-item:focus-visible { - outline: 2px solid var(--ua-color-accent); - outline-offset: -2px; + outline: 1.5px solid var(--ua-color-accent-soft); /* 内嵌焦点环:细一档 + 柔和色,2px 实色在密集列表里像粗边框 */ + outline-offset: -1.5px; } /* 选中态:低视觉比重——淡色底 + 弱化文字,不抢焦点 */ .ua-option-selected { @@ -537,6 +537,16 @@ body.ua-col-resizing { left: auto; right: 0; } +/* 首末项补圆角:面板内距 4px 小于 8px 圆角,悬停底色块的直角会压出面板弧线 */ +.ua-menu-pop > .ua-menu-item:first-child { + border-radius: var(--ua-radius-sm) var(--ua-radius-sm) 0 0; +} +.ua-menu-pop > .ua-menu-item:last-child { + border-radius: 0 0 var(--ua-radius-sm) var(--ua-radius-sm); +} +.ua-menu-pop > .ua-menu-item:only-child { + border-radius: var(--ua-radius-sm); +} .ua-menu-pop.is-flip { top: auto; /* 空间不足翻到触发器上方 */ bottom: calc(100% + 4px); @@ -1666,8 +1676,8 @@ button.ua-step-dot:hover { .ua-tab:focus-visible { /* 页签条是 overflow-x:auto 的滚动容器,外偏移焦点环会被裁掉, 这里改用与 seg-btn 一致的内嵌描边,键盘用户才真看得见焦点 */ - outline: 2px solid var(--ua-color-accent); - outline-offset: -2px; + outline: 1.5px solid var(--ua-color-accent-soft); /* 内嵌焦点环:细一档 + 柔和色,2px 实色在密集列表里像粗边框 */ + outline-offset: -1.5px; color: var(--ua-color-accent); } .ua-tab:disabled { @@ -1724,9 +1734,9 @@ button.ua-step-dot:hover { bottom: -1px; left: 0; width: 0; - height: 2px; + height: 1.5px; /* 指示条不做 2px 实线:细一档已足够标识,粗条在浅底上过于抢眼 */ border-radius: 1px; - background: var(--ua-color-accent); + background: var(--ua-color-accent-soft); /* 柔和强调色:纯 accent 满饱和做长条会发刺 */ transition: left var(--ua-duration-slow) var(--ua-ease-out), width var(--ua-duration-slow) var(--ua-ease-out); } @@ -2206,6 +2216,16 @@ button.ua-step-dot:hover { font-size: var(--ua-font-size-md); user-select: none; } +/* 附加块自带底色,首末两端必须跟组圆角对齐(减去 1px 边框),否则直角会压出外层圆角 */ +.ua-input-group > .ua-input-addon:first-child { + border-radius: var(--ua-radius-md-inset) 0 0 var(--ua-radius-md-inset); +} +.ua-input-group > .ua-input-addon:last-child { + border-radius: 0 var(--ua-radius-md-inset) var(--ua-radius-md-inset) 0; +} +.ua-input-group > .ua-input-addon:only-child { + border-radius: var(--ua-radius-md-inset); +} .ua-input-clear-group { display: inline-flex; /* 清空按钮 / 计数 + 原有 suffix 并排 */ align-items: center; @@ -2411,8 +2431,8 @@ button.ua-step-dot:hover { color: var(--ua-color-text); } .ua-seg-btn:focus-visible { - outline: 2px solid var(--ua-color-accent); - outline-offset: -2px; + outline: 1.5px solid var(--ua-color-accent-soft); /* 内嵌焦点环:细一档 + 柔和色,2px 实色在密集列表里像粗边框 */ + outline-offset: -1.5px; } .ua-seg-btn.is-active { color: var(--ua-color-accent); @@ -3220,8 +3240,8 @@ button.ua-step-dot:hover { cursor: not-allowed; } .ua-cascader-opt:focus-visible { - outline: 2px solid var(--ua-color-accent); - outline-offset: -2px; + outline: 1.5px solid var(--ua-color-accent-soft); /* 内嵌焦点环:细一档 + 柔和色,2px 实色在密集列表里像粗边框 */ + outline-offset: -1.5px; } .ua-cascader-arrow { color: var(--ua-color-text-muted); @@ -3424,6 +3444,7 @@ button.ua-step-dot:hover { border-radius: var(--ua-radius-md); background: var(--ua-color-surface); box-shadow: var(--ua-shadow-4); + overflow: clip; /* 裁掉内层格子的直角底:不建滚动容器,日历/色板本身不需要滚动 */ } .ua-datepicker-pop.ua-open { display: block; @@ -3503,8 +3524,8 @@ button.ua-step-dot:hover { cursor: not-allowed; } .ua-datepicker-day:focus-visible { - outline: 2px solid var(--ua-color-accent); - outline-offset: -2px; + outline: 1.5px solid var(--ua-color-accent-soft); /* 内嵌焦点环:细一档 + 柔和色,2px 实色在密集列表里像粗边框 */ + outline-offset: -1.5px; } .ua-datepicker-foot { display: flex; @@ -3667,6 +3688,7 @@ button.ua-step-dot:hover { border-radius: var(--ua-radius-md); background: var(--ua-color-surface); box-shadow: var(--ua-shadow-4); + overflow: clip; /* 裁掉内层格子的直角底:不建滚动容器,日历/色板本身不需要滚动 */ } .ua-colorpicker-pop.ua-open { display: block; @@ -4064,10 +4086,10 @@ button.ua-step-dot:hover { position: absolute; left: calc(-1 * var(--ua-space-3)); top: 50%; - width: 2px; + width: 1.5px; height: 70%; transform: translateY(-50%); - background: var(--ua-color-accent); + background: var(--ua-color-accent-soft); border-radius: 1px; } .ua-anchor-link.is-disabled { @@ -4722,8 +4744,8 @@ h5.ua-type-title { font-size: var(--ua-font-size-md); } background: var(--ua-color-tint-accent-weak); } .ua-row-clickable:focus-visible { - outline: 2px solid var(--ua-color-accent); - outline-offset: -2px; /* 环画在行内,避免被表格滚动容器裁掉 */ + outline: 1.5px solid var(--ua-color-accent-soft); /* 内嵌焦点环:细一档 + 柔和色,2px 实色在密集列表里像粗边框 */ + outline-offset: -1.5px; /* 环画在行内,避免被表格滚动容器裁掉 */ } /* —— 通知角落:右上与 toast 同侧,左列放次要信息 —— */ diff --git a/packages/tools/cli/recipes.md b/packages/tools/cli/recipes.md index b77c3ea..dbbc9ae 100644 --- a/packages/tools/cli/recipes.md +++ b/packages/tools/cli/recipes.md @@ -194,7 +194,7 @@ effect(() => { document.documentElement.style.colorScheme = dark() ? 'dark' : 'light'; // 原生控件(date/滚动条)跟着变 localStorage.setItem('ua-theme', dark() ? 'dark' : 'light'); }); -floatButton({ icon: '🌗', label: '切换主题', onClick: () => dark(!dark()) }); +floatButton({ label: '切换主题', onClick: () => dark(!dark()) }); ``` 要点: