chore: 版本升级到0.4.0并添加右键颜色设置菜单

- 新增右键快捷菜单,支持调整字体和背景颜色
- 移除页面点击翻页默认事件,新增自定义右键菜单逻辑
- 新增颜色选择器相关样式与工具函数
- 优化弹窗关闭逻辑,支持ESC键隐藏菜单
This commit is contained in:
sparksfly 2026-09-01 15:47:45 +08:00
parent ee4d7df7d0
commit 1458397398
2 changed files with 169 additions and 9 deletions

View File

@ -1,7 +1,7 @@
{
"name": "stealthreader",
"private": true,
"version": "0.3.2",
"version": "0.4.0",
"type": "module",
"scripts": {
"dev": "vite",

View File

@ -1,11 +1,17 @@
<template>
<div class="glass-panel" :style="readerStyle" data-tauri-drag-region @mousedown="onWindowMouseDown">
<div
class="glass-panel"
:style="readerStyle"
data-tauri-drag-region
@mousedown="onWindowMouseDown"
@contextmenu.prevent="onContextMenu"
>
<div class="reading-wrap">
<div class="chapter-name" :class="{ 'chapter-name--hidden': currentPage !== 0 }">
{{ chapter?.title || "加载中..." }}
</div>
<div ref="pageEl" class="page-body" @click="onPageClick">
<div ref="pageEl" class="page-body">
<div class="page-text">{{ currentText }}</div>
</div>
@ -26,6 +32,67 @@
</div>
</div>
</div>
<n-popover
v-model:show="menuShow"
trigger="manual"
:x="menuX"
:y="menuY"
placement="bottom-start"
:show-arrow="false"
@clickoutside="menuShow = false"
>
<div class="ctx-menu" @contextmenu.prevent>
<div class="ctx-item">
<span class="ctx-label">字体颜色</span>
<div class="color-picker-control">
<n-color-picker v-model:value="menuFontColor" show-alpha :modes="['hex']" placement="bottom-end">
<template #trigger="{ value, onClick, ref: triggerRef }">
<div
:ref="triggerRef"
class="color-swatch"
:style="{ backgroundColor: value || '#000000' }"
role="button"
tabindex="0"
aria-label="选择字体颜色"
@click="onClick"
@keydown.enter="onClick"
@keydown.space.prevent="onClick"
/>
</template>
</n-color-picker>
<div class="color-values">
<span>{{ getColorDetails(menuFontColor).color }}</span>
<small>透明度 {{ getColorDetails(menuFontColor).opacity }}%</small>
</div>
</div>
</div>
<div class="ctx-item">
<span class="ctx-label">背景颜色</span>
<div class="color-picker-control">
<n-color-picker v-model:value="menuBackgroundColor" show-alpha :modes="['hex']" placement="bottom-end">
<template #trigger="{ value, onClick, ref: triggerRef }">
<div
:ref="triggerRef"
class="color-swatch"
:style="{ backgroundColor: value || '#ffffff' }"
role="button"
tabindex="0"
aria-label="选择背景颜色"
@click="onClick"
@keydown.enter="onClick"
@keydown.space.prevent="onClick"
/>
</template>
</n-color-picker>
<div class="color-values">
<span>{{ getColorDetails(menuBackgroundColor).color }}</span>
<small>透明度 {{ getColorDetails(menuBackgroundColor).opacity }}%</small>
</div>
</div>
</div>
</div>
</n-popover>
</div>
</template>
@ -123,20 +190,56 @@ const nextPage = () => {
if (currentPage.value < pages.value.length - 1) currentPage.value++
}
const onPageClick = (e: MouseEvent) => {
const el = e.currentTarget as HTMLElement
const rect = el.getBoundingClientRect()
if (e.clientX - rect.left < rect.width / 2) prevPage()
else nextPage()
// /
const menuShow = ref(false)
const menuX = ref(0)
const menuY = ref(0)
const menuFontColor = ref(settingStore.reader.fontColor)
const menuBackgroundColor = ref(settingStore.reader.backgroundColor)
const getColorDetails = (value: string | null) => {
const color = value || "#000000"
const match =
color.match(/^#([\da-f]{6})([\da-f]{2})$/i) ||
color.match(/^#([\da-f]{3})([\da-f])$/i)
const alphaMatch = color.match(/^rgba\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*([\d.]+)\s*\)$/i)
const alpha = match
? Math.round((parseInt(match[2].length === 1 ? match[2] + match[2] : match[2], 16) / 255) * 100)
: alphaMatch
? Math.round(Number(alphaMatch[1]) * 100)
: 100
return {
color: match ? `#${match[1].toUpperCase()}` : color,
opacity: Math.min(100, Math.max(0, alpha)),
}
}
const onContextMenu = (e: MouseEvent) => {
menuX.value = e.clientX
menuY.value = e.clientY
menuShow.value = true
}
watch(menuFontColor, (v) => {
if (v) settingStore.reader.fontColor = v
})
watch(menuBackgroundColor, (v) => {
if (v) settingStore.reader.backgroundColor = v
})
const onWindowMouseDown = (e: MouseEvent) => {
if (e.button !== 0) return
const target = e.target as HTMLElement | null
if (target?.closest("button, a, input, textarea, select, [role='button']")) return
void appWindow.startDragging()
}
const onKeydown = (e: KeyboardEvent) => {
if (e.key === "Escape") {
menuShow.value = false
return
}
if (e.key === "ArrowLeft") {
e.preventDefault()
switchChapter(-1)
@ -266,7 +369,6 @@ onUnmounted(() => {
.page-body {
flex: 1;
min-height: 0;
cursor: pointer;
}
.page-text {
@ -327,4 +429,62 @@ onUnmounted(() => {
.nav-back {
padding: 0 4px;
}
.ctx-menu {
display: flex;
flex-direction: column;
gap: 12px;
min-width: 176px;
}
.ctx-item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.ctx-label {
font-size: 13px;
color: var(--color-text-primary);
white-space: nowrap;
}
.color-picker-control {
display: inline-flex;
flex-direction: column;
align-items: flex-start;
gap: 6px;
}
.color-swatch {
width: 22px;
height: 22px;
border: 2px solid #ffffff;
border-radius: 50%;
cursor: pointer;
background-position: 0 0, 0 4px, 4px -4px, -4px 0;
background-size: 8px 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.color-swatch:focus-visible {
outline: 2px solid var(--color-accent);
outline-offset: 2px;
}
.color-values {
display: flex;
flex-direction: column;
gap: 1px;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
font-size: 12px;
line-height: 16px;
}
.color-values small {
color: var(--color-text-secondary);
font-family: inherit;
font-size: 11px;
}
</style>