refactor: 移除设置页键盘操作支持并升级 1.0.7

- SettingsView 删除方向键切换、roving tabindex、ARIA 单选组语义与焦点环,仅保留原生按钮点击切换
- README 清理过时描述并补充 v1.0.7 更新日志
- 版本升级 1.0.7
This commit is contained in:
2026-08-08 20:11:28 +08:00
parent df9cd52de6
commit 96dd1ef4d3
5 changed files with 80 additions and 60 deletions
+5 -1
View File
@@ -128,6 +128,11 @@ pnpm build # 生产构建
## 更新日志 ## 更新日志
### v1.0.7
- 移除设置页方向键切换、roving tabindex 与 ARIA 单选组语义,保留原生按钮点击切换
- 同步清理 README 中已过时的「设置选项支持键盘操作」描述
### v1.0.6 ### v1.0.6
- 深色模式设置入口移至 Halo 官方「外观」分组 - 深色模式设置入口移至 Halo 官方「外观」分组
@@ -138,7 +143,6 @@ pnpm build # 生产构建
- 同步设置 `color-scheme`,缓解深色模式刷新闪烁 - 同步设置 `color-scheme`,缓解深色模式刷新闪烁
- 增加多标签页主题同步与 `useDarkMode` 单元测试 - 增加多标签页主题同步与 `useDarkMode` 单元测试
- 侧边栏切换按钮与设置选项支持键盘操作
- 后端日志改用 Lombok `@Slf4j` - 后端日志改用 Lombok `@Slf4j`
- 裁剪 vendored `package.json`,新增 Dark Reader `SHA256SUMS` 完整性校验 - 裁剪 vendored `package.json`,新增 Dark Reader `SHA256SUMS` 完整性校验
- `plugin.yaml` 改为安装后由用户手动启用 - `plugin.yaml` 改为安装后由用户手动启用
@@ -0,0 +1,70 @@
# 修改交接单:移除设置页键盘操作支持(2026-08-08)
> 审查窗口产出,**交付开发窗口执行**;审查窗口不修改代码。
> 本文档为**唯一交接文件**(已合并 `recheck-entry-2026-08-08.md` 的内容)。
> 用户决定:设置页的键盘操作支持"没必要",要求移除。
## 零、背景:1.0.6 已落实项(复查结论,无需重复处理)
提交 `394613f fix: 深色模式入口移至外观分组并移除侧边栏按钮(1.0.6)` 已复查通过:
- 菜单移入「外观」分组(`ui/src/index.ts` `group: 'interface'`),线上已生效
- 侧边栏注入按钮已移除(`injector.ts``ThemeToggle.vue` 已删除),线上 `.theme-toggle` 数量 = 0
- `verify-toggle.py` 已改为 localStorage + `StorageEvent` 驱动 + color-scheme 断言
- 文档已同步、版本号 1.0.6
- 构建验证:vitest 8/8 · vue-tsc · prettier · vite build · `gradlew build` 全绿
唯一遗留的 R-1(README 过时描述)由本文档第四节第 4 点一并处理。
## 一、需求描述
移除 `SettingsView.vue`(深色模式设置页)中为"键盘操作"特意实现的逻辑:
方向键切换选项、roving tabindex、ARIA 单选组语义、焦点环等。保留最简交互:点击三个选项按钮切换主题模式。
## 二、决策依据(用户补充)
- Halo 后台的官方页面与第三方插件页面均**没有方向键特殊交互的惯例**:按方向键上下键只用于页面滚动/翻页,没有其他特殊功能。
- 设置页单独实现"方向键切换主题选项"radiogroup 键盘导航)与平台整体交互习惯不一致,用户群体也不会预期方向键会改变主题选择,因此判定该键盘操作支持**不必要**,予以移除。
- 保留原生 `<button>` 即可:点击可用,浏览器内置的 Tab 聚焦 + Enter/空格 激活属基础可用性,非刻意实现,无需额外维护。
## 三、现状代码体现(`ui/src/views/SettingsView.vue`
| 行号 | 内容 | 作用 |
| --- | --- | --- |
| L10 | `import { computed, nextTick, ref } from 'vue'` | `nextTick`/`ref` 仅键盘导航使用 |
| L27 | `const optionEls = ref<HTMLButtonElement[]>([])` | 选项按钮 DOM 引用数组 |
| L29-L35 | `activeIndex` computed | 当前选中索引 |
| L37-L39 | `setOptionRef(el, index)` | 收集按钮 ref |
| L41-L45 | `focusOption(index)` | 方向键切换选中并聚焦 |
| L47-L61 | `onKeydown(event)` | ArrowUp/Down/Left/Right/Home/End 处理 |
| L81-L84 | `role="radiogroup"` `aria-label` `@keydown="onKeydown"` | 单选组容器语义 |
| L88-L104 | button 上的 `role="radio"` / `:aria-checked` / `:tabindex`roving/ `:ref` / `@click` | 单选按钮语义与焦点管理 |
| L162-L165 | `.dark-mode-settings__option:focus-visible` | 键盘焦点环(可选移除) |
连带:`README.md` L141 `- 侧边栏切换按钮与设置选项支持键盘操作`(R-1,侧边栏按钮已删,"键盘操作"整体废弃)。
## 四、修改建议
### 方案 A(推荐):移除键盘导航实现,保留简单按钮
`ui/src/views/SettingsView.vue`
1. **script**
- 删除 L27 `optionEls`、L29-L35 `activeIndex`、L37-L39 `setOptionRef`、L41-L45 `focusOption`、L47-L61 `onKeydown`
- L10 import 精简为 `import { computed } from 'vue'``nextTick``ref` 不再使用)
2. **template**
- L81-L84 删除 `role="radiogroup"``aria-label="主题模式"``@keydown="onKeydown"`
- L88-L104 按钮上删除 `role="radio"``:aria-checked``:tabindex``:ref`,仅保留 `type="button"``:class="{ 'is-active': ... }"``@click="setTheme(option.value)"``v-for``index` 不再需要,可去掉)
3. **style**(可选):删除 L162-L165 `:focus-visible` 规则;也可保留(无害,仅鼠标点击时不会触发)
4. **README.md**:删除 L141 整行(R-1 一并处理)
5. 构建回归:`cd ui && pnpm build``pnpm type-check``pnpm prettier``pnpm lint``pnpm test:unit`;根目录 `gradlew build`
### 方案 B(备选,不推荐):彻底无键盘语义
- 三个选项改回 `<div>` + `@click`(删除 button 全部键盘/语义属性)
- 需在样式补 `cursor: pointer`;可访问性退化,仅当明确要求时使用
## 五、注意
- 删除逻辑不涉及 `useDarkMode` / `darkreader-engine`,状态管理与主题切换不受影响。
- vitest 仅覆盖 `useDarkMode`,不受本次改动影响。
- 本次为前端 UI 变更,需重新构建部署后验证。
+1 -1
View File
@@ -1,3 +1,3 @@
version=1.0.6 version=1.0.7
org.gradle.jvmargs=-Xmx2g -Dfile.encoding=UTF-8 org.gradle.jvmargs=-Xmx2g -Dfile.encoding=UTF-8
org.gradle.java.home=D:/settings/Language/Java/jdk-25.0.1 org.gradle.java.home=D:/settings/Language/Java/jdk-25.0.1
@@ -11,7 +11,7 @@ import run.halo.app.plugin.PluginContext;
* <p>Only one main class extending {@link BasePlugin} is allowed per plugin.</p> * <p>Only one main class extending {@link BasePlugin} is allowed per plugin.</p>
* *
* @author LHY * @author LHY
* @since 1.0.6 * @since 1.0.7
*/ */
@Slf4j @Slf4j
@Component @Component
+3 -57
View File
@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ThemeMode } from '../composables/useDarkMode' import type { ThemeMode } from '../composables/useDarkMode'
import { useDarkMode } from '../composables/useDarkMode' import { useDarkMode } from '../composables/useDarkMode'
import { computed, nextTick, ref } from 'vue' import { computed } from 'vue'
const { theme, isDark, setTheme } = useDarkMode() const { theme, isDark, setTheme } = useDarkMode()
@@ -17,42 +17,6 @@ const modeOptions: { value: ThemeMode; label: string; description: string }[] =
{ value: 'dark', label: '深色', description: '始终使用深色模式' }, { value: 'dark', label: '深色', description: '始终使用深色模式' },
{ value: 'auto', label: '跟随系统', description: '根据系统设置自动切换' }, { value: 'auto', label: '跟随系统', description: '根据系统设置自动切换' },
] ]
const optionEls = ref<HTMLButtonElement[]>([])
const activeIndex = computed(() =>
Math.max(
0,
modeOptions.findIndex((option) => option.value === theme.value),
),
)
function setOptionRef(el: unknown, index: number): void {
if (el) {
optionEls.value[index] = el as HTMLButtonElement
}
}
function focusOption(index: number): void {
const next = (index + modeOptions.length) % modeOptions.length
setTheme(modeOptions[next].value)
void nextTick(() => optionEls.value[next]?.focus())
}
function onKeydown(event: KeyboardEvent): void {
if (event.key === 'ArrowDown' || event.key === 'ArrowRight') {
event.preventDefault()
focusOption(activeIndex.value + 1)
} else if (event.key === 'ArrowUp' || event.key === 'ArrowLeft') {
event.preventDefault()
focusOption(activeIndex.value - 1)
} else if (event.key === 'Home') {
event.preventDefault()
focusOption(0)
} else if (event.key === 'End') {
event.preventDefault()
focusOption(modeOptions.length - 1)
}
}
</script> </script>
<template> <template>
@@ -67,26 +31,13 @@ function onKeydown(event: KeyboardEvent): void {
当前生效<strong>{{ currentEffectiveMode }}</strong> 当前生效<strong>{{ currentEffectiveMode }}</strong>
</div> </div>
<div <div class="dark-mode-settings__options">
class="dark-mode-settings__options"
role="radiogroup"
aria-label="主题模式"
@keydown="onKeydown"
>
<button <button
v-for="(option, index) in modeOptions" v-for="option in modeOptions"
:key="option.value" :key="option.value"
type="button" type="button"
role="radio"
class="dark-mode-settings__option" class="dark-mode-settings__option"
:class="{ 'is-active': theme === option.value }" :class="{ 'is-active': theme === option.value }"
:aria-checked="theme === option.value"
:tabindex="theme === option.value ? 0 : -1"
:ref="
(el: unknown) => {
setOptionRef(el, index)
}
"
@click="setTheme(option.value)" @click="setTheme(option.value)"
> >
<div class="dark-mode-settings__option-label">{{ option.label }}</div> <div class="dark-mode-settings__option-label">{{ option.label }}</div>
@@ -163,11 +114,6 @@ function onKeydown(event: KeyboardEvent): void {
background-color: var(--halo-bg-hover); background-color: var(--halo-bg-hover);
} }
.dark-mode-settings__option:focus-visible {
outline: 2px solid var(--halo-accent-primary);
outline-offset: 2px;
}
.dark-mode-settings__option.is-active { .dark-mode-settings__option.is-active {
background-color: var(--halo-menu-item-active); background-color: var(--halo-menu-item-active);
border-color: var(--halo-accent-primary); border-color: var(--halo-accent-primary);