Skip to content

feat: 允许没有背景#636

Open
ShadowLemoon wants to merge 1 commit into
AlkaidLab:masterfrom
ShadowLemoon:feat/no-background
Open

feat: 允许没有背景#636
ShadowLemoon wants to merge 1 commit into
AlkaidLab:masterfrom
ShadowLemoon:feat/no-background

Conversation

@ShadowLemoon
Copy link
Copy Markdown
Collaborator

No description provided.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 9, 2026

Review Change Stack

Summary by CodeRabbit

发布说明

  • 新功能
    • 添加了隐藏的开发者模式功能,通过特定的用户交互可激活/禁用
    • 开发者模式启用时,支持绕过背景图像应用,并恢复默认背景显示

概览

此PR为日志界面添加隐藏的开发者触发机制,允许用户通过7次快速点击在3秒内切换开发模式。该模式通过localStorage持久化,并通过CustomEvent通知背景管理模块绕过背景图像应用逻辑。

变更

开发者模式触发和背景绕过

层级 / 文件 摘要
UI触发器定义与状态管理
src_assets/common/assets/web/components/LogsSection.vue
添加ref导入和开发模式常量(7次点击阈值、3秒超时、localStorage键)。在模板中引入隐藏的.dev-trigger元素;在script中声明devModedevTapCount状态,实现handleDevTap处理器;当达到点击阈值时切换开发模式状态并派发sunshine-background-bypass CustomEvent。添加.dev-trigger CSS以保持不可见状态。
背景绕过逻辑实现
src_assets/common/assets/web/composables/useBackground.js
添加isDevModeEnabledclearDevModBypass辅助函数。修改setBackground在开发模式启用时短路执行(清除内联背景,跳过颜色分析)。在saveBackgroundclearBackground中调用清除逻辑以重置开发模式旗标。添加window事件监听器响应sunshine-background-bypass事件,启用时清除背景样式,禁用时重新加载保存的背景。

预估代码审查工作量

🎯 3 (中等) | ⏱️ ~20 分钟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive 拉取请求没有提供描述文本,无法评估其与变更集的相关性。 建议添加拉取请求描述,说明此功能的目的、使用方法以及与现有功能的交互。
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 拉取请求的标题清晰地总结了主要变更:添加无背景功能的开发者模式切换机制。
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
src_assets/common/assets/web/composables/useBackground.js (1)

198-205: ⚡ Quick win

建议把 dev key 和事件名抽成共享常量,避免跨文件漂移

这里与 LogsSection.vue 使用同一组字符串字面量;建议集中定义后统一引用,减少未来改名或拼写不一致导致的隐性故障。

建议修复
+// src_assets/common/assets/web/constants/devMode.js
+export const DEV_MODE_STORAGE_KEY = 'sunshine_dev_mode'
+export const BACKGROUND_BYPASS_EVENT = 'sunshine-background-bypass'
-const isDevMode = () => localStorage.getItem('sunshine_dev_mode') === '1'
+import { DEV_MODE_STORAGE_KEY, BACKGROUND_BYPASS_EVENT } from '../constants/devMode'
+const isDevMode = () => localStorage.getItem(DEV_MODE_STORAGE_KEY) === '1'
@@
-    if (localStorage.getItem('sunshine_dev_mode') === '1') {
-      localStorage.setItem('sunshine_dev_mode', '0')
+    if (localStorage.getItem(DEV_MODE_STORAGE_KEY) === '1') {
+      localStorage.setItem(DEV_MODE_STORAGE_KEY, '0')
@@
-    window.addEventListener('sunshine-background-bypass', onBackgroundBypass)
+    window.addEventListener(BACKGROUND_BYPASS_EVENT, onBackgroundBypass)

As per coding guidelines src_assets/**/*.{vue,js,html}: 基于 Vue.js 的 Web 配置面板。审查 XSS/CSRF 安全性、 组件设计、状态管理和可访问性。

Also applies to: 340-346

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src_assets/common/assets/web/composables/useBackground.js` around lines 198 -
205, Extract the literal key and event name used for dev bypass into shared
constants and update usages: create a small constants export (e.g., DEV_MODE_KEY
and DEV_MODE_EVENT) and replace the string literals in useBackground.js
(functions isDevMode and clearDevBypass) and in LogsSection.vue so both files
import and reference the same symbols; ensure the constants file is co-located
with other web composables and exported for reuse to avoid future drift and
typos.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src_assets/common/assets/web/components/LogsSection.vue`:
- Around line 174-187: The code directly accesses localStorage in the devMode
initialization and inside handleDevTap (symbols: DEV_STORAGE_KEY, devMode,
handleDevTap, devTapTimer, DEV_TAP_TIMEOUT, DEV_TAP_THRESHOLD); wrap both the
read and write operations in try-catch blocks (or guard with a safe localStorage
helper) so SecurityError in privacy/locked environments won't throw—on read
return a safe default (devMode false) when access fails and on write silently
ignore failures (or update an in-memory fallback), keep the rest of handleDevTap
logic intact and still dispatch the toggle event after updating the safe state.

In `@src_assets/common/assets/web/composables/useBackground.js`:
- Around line 339-346: The global event listener ('sunshine-background-bypass')
and the MutationObserver created inside useBackground are not cleaned up,
causing leaks; update useBackground to store references to the
window.addEventListener handler and the MutationObserver instance and register a
cleanup using Vue's onScopeDispose: remove the window event listener via
window.removeEventListener('sunshine-background-bypass', handler) and call
observer.disconnect() inside the onScopeDispose callback so both the listener
and observer are properly released when the component using useBackground is
unmounted.

---

Nitpick comments:
In `@src_assets/common/assets/web/composables/useBackground.js`:
- Around line 198-205: Extract the literal key and event name used for dev
bypass into shared constants and update usages: create a small constants export
(e.g., DEV_MODE_KEY and DEV_MODE_EVENT) and replace the string literals in
useBackground.js (functions isDevMode and clearDevBypass) and in LogsSection.vue
so both files import and reference the same symbols; ensure the constants file
is co-located with other web composables and exported for reuse to avoid future
drift and typos.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bd047fed-7b9f-49e8-9967-c160e0fa32e2

📥 Commits

Reviewing files that changed from the base of the PR and between 1543450 and 44b710b.

📒 Files selected for processing (2)
  • src_assets/common/assets/web/components/LogsSection.vue
  • src_assets/common/assets/web/composables/useBackground.js
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Windows
🧰 Additional context used
📓 Path-based instructions (1)
src_assets/**/*.{vue,js,html}

⚙️ CodeRabbit configuration file

src_assets/**/*.{vue,js,html}: 基于 Vue.js 的 Web 配置面板。审查 XSS/CSRF 安全性、 组件设计、状态管理和可访问性。

Files:

  • src_assets/common/assets/web/components/LogsSection.vue
  • src_assets/common/assets/web/composables/useBackground.js
🔇 Additional comments (1)
src_assets/common/assets/web/components/LogsSection.vue (1)

80-83: 阈值、超时和存储键提为常量是个不错的改动

这段改动提升了可读性和后续调整成本。

Comment on lines +174 to +187
const devMode = ref(localStorage.getItem(DEV_STORAGE_KEY) === '1')
const devTapCount = ref(0)
let devTapTimer = null

const handleDevTap = () => {
devTapCount.value++
clearTimeout(devTapTimer)
devTapTimer = setTimeout(() => { devTapCount.value = 0 }, DEV_TAP_TIMEOUT)

if (devTapCount.value >= DEV_TAP_THRESHOLD) {
devTapCount.value = 0
devMode.value = !devMode.value
localStorage.setItem(DEV_STORAGE_KEY, devMode.value ? '1' : '0')
// Dispatch event so other components can react
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

find . -name "LogsSection.vue" -type f 2>/dev/null | head -20

Repository: AlkaidLab/foundation-sunshine

Length of output: 131


🏁 Script executed:

git ls-files | grep -i logssection

Repository: AlkaidLab/foundation-sunshine

Length of output: 129


🏁 Script executed:

wc -l src_assets/common/assets/web/components/LogsSection.vue

Repository: AlkaidLab/foundation-sunshine

Length of output: 133


🏁 Script executed:

cat -n src_assets/common/assets/web/components/LogsSection.vue | sed -n '160,200p'

Repository: AlkaidLab/foundation-sunshine

Length of output: 1462


🏁 Script executed:

cat -n src_assets/common/assets/web/components/LogsSection.vue | sed -n '1,60p'

Repository: AlkaidLab/foundation-sunshine

Length of output: 2906


🏁 Script executed:

rg "localStorage" src_assets/common/assets/web/components/LogsSection.vue -n

Repository: AlkaidLab/foundation-sunshine

Length of output: 217


🏁 Script executed:

cat -n src_assets/common/assets/web/components/LogsSection.vue | sed -n '120,180p'

Repository: AlkaidLab/foundation-sunshine

Length of output: 2381


🏁 Script executed:

cat -n src_assets/common/assets/web/components/LogsSection.vue | sed -n '80,135p'

Repository: AlkaidLab/foundation-sunshine

Length of output: 1838


为 dev mode 的本地存储读写添加异常处理,防止受限环境下页面崩溃

Line 174 和 Line 186 直接访问 localStorage,在隐私模式、禁用存储或企业政策限制的环境中会抛 SecurityError,导致日志页面初始化或功能失效。需要用 try-catch 封装安全读写并在失败时降级。

建议修复
 const DEV_TAP_THRESHOLD = 7
 const DEV_TAP_TIMEOUT = 3000
 const DEV_STORAGE_KEY = 'sunshine_dev_mode'
+
+const readDevMode = () => {
+  try {
+    return localStorage.getItem(DEV_STORAGE_KEY) === '1'
+  } catch {
+    return false
+  }
+}
+
+const writeDevMode = (enabled) => {
+  try {
+    localStorage.setItem(DEV_STORAGE_KEY, enabled ? '1' : '0')
+  } catch (e) {
+    console.warn('Persist dev mode failed:', e)
+  }
+}
 
 // Dev mode: 7 taps within 3 seconds
-const devMode = ref(localStorage.getItem(DEV_STORAGE_KEY) === '1')
+const devMode = ref(readDevMode())
 const devTapCount = ref(0)
 let devTapTimer = null
@@
   if (devTapCount.value >= DEV_TAP_THRESHOLD) {
     devTapCount.value = 0
     devMode.value = !devMode.value
-    localStorage.setItem(DEV_STORAGE_KEY, devMode.value ? '1' : '0')
+    writeDevMode(devMode.value)
     // Dispatch event so other components can react
     window.dispatchEvent(new CustomEvent('sunshine-background-bypass', { detail: { enabled: devMode.value } }))
   }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const devMode = ref(localStorage.getItem(DEV_STORAGE_KEY) === '1')
const devTapCount = ref(0)
let devTapTimer = null
const handleDevTap = () => {
devTapCount.value++
clearTimeout(devTapTimer)
devTapTimer = setTimeout(() => { devTapCount.value = 0 }, DEV_TAP_TIMEOUT)
if (devTapCount.value >= DEV_TAP_THRESHOLD) {
devTapCount.value = 0
devMode.value = !devMode.value
localStorage.setItem(DEV_STORAGE_KEY, devMode.value ? '1' : '0')
// Dispatch event so other components can react
const DEV_TAP_THRESHOLD = 7
const DEV_TAP_TIMEOUT = 3000
const DEV_STORAGE_KEY = 'sunshine_dev_mode'
const readDevMode = () => {
try {
return localStorage.getItem(DEV_STORAGE_KEY) === '1'
} catch {
return false
}
}
const writeDevMode = (enabled) => {
try {
localStorage.setItem(DEV_STORAGE_KEY, enabled ? '1' : '0')
} catch (e) {
console.warn('Persist dev mode failed:', e)
}
}
// Dev mode: 7 taps within 3 seconds
const devMode = ref(readDevMode())
const devTapCount = ref(0)
let devTapTimer = null
const handleDevTap = () => {
devTapCount.value++
clearTimeout(devTapTimer)
devTapTimer = setTimeout(() => { devTapCount.value = 0 }, DEV_TAP_TIMEOUT)
if (devTapCount.value >= DEV_TAP_THRESHOLD) {
devTapCount.value = 0
devMode.value = !devMode.value
writeDevMode(devMode.value)
// Dispatch event so other components can react
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src_assets/common/assets/web/components/LogsSection.vue` around lines 174 -
187, The code directly accesses localStorage in the devMode initialization and
inside handleDevTap (symbols: DEV_STORAGE_KEY, devMode, handleDevTap,
devTapTimer, DEV_TAP_TIMEOUT, DEV_TAP_THRESHOLD); wrap both the read and write
operations in try-catch blocks (or guard with a safe localStorage helper) so
SecurityError in privacy/locked environments won't throw—on read return a safe
default (devMode false) when access fails and on write silently ignore failures
(or update an in-memory fallback), keep the rest of handleDevTap logic intact
and still dispatch the toggle event after updating the safe state.

Comment on lines +339 to +346
// 监听背景旁路切换
window.addEventListener('sunshine-background-bypass', (e) => {
if (e.detail?.enabled) {
document.body.style.background = ''
} else {
loadBackground()
}
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

# 查找并列出该文件
find . -type f -name "useBackground.js" | head -20

Repository: AlkaidLab/foundation-sunshine

Length of output: 133


🏁 Script executed:

# 获取文件大小和行数
wc -l src_assets/common/assets/web/composables/useBackground.js 2>/dev/null || echo "文件未找到"

Repository: AlkaidLab/foundation-sunshine

Length of output: 135


🏁 Script executed:

# 读取第 339-346 行的代码
sed -n '330,360p' src_assets/common/assets/web/composables/useBackground.js 2>/dev/null || echo "无法读取文件"

Repository: AlkaidLab/foundation-sunshine

Length of output: 935


🏁 Script executed:

# 读取文件的前 50 行来理解其结构
head -50 src_assets/common/assets/web/composables/useBackground.js 2>/dev/null

Repository: AlkaidLab/foundation-sunshine

Length of output: 1353


🏁 Script executed:

# 读取文件的最后部分,确认导出方式
tail -30 src_assets/common/assets/web/composables/useBackground.js

Repository: AlkaidLab/foundation-sunshine

Length of output: 887


🏁 Script executed:

# 查看文件的导入部分(行 1-30)
head -100 src_assets/common/assets/web/composables/useBackground.js | head -30

Repository: AlkaidLab/foundation-sunshine

Length of output: 874


🏁 Script executed:

# 搜索这个 composable 在项目中的使用场景
rg "useBackground" --type js --type vue -B 2 -A 2 | head -100

Repository: AlkaidLab/foundation-sunshine

Length of output: 101


🏁 Script executed:

# 查看是否有 onMounted/onUnmounted 等生命周期钩子
rg "onMounted|onUnmounted|onScopeDispose" src_assets/common/assets/web/composables/useBackground.js

Repository: AlkaidLab/foundation-sunshine

Length of output: 55


🏁 Script executed:

# 读取文件的中间部分来找到函数声明
sed -n '100,150p' src_assets/common/assets/web/composables/useBackground.js

Repository: AlkaidLab/foundation-sunshine

Length of output: 1357


🏁 Script executed:

# 查看函数声明
rg "^export|^function|^const.*=" src_assets/common/assets/web/composables/useBackground.js | head -20

Repository: AlkaidLab/foundation-sunshine

Length of output: 758


🏁 Script executed:

# 搜索 useBackground 的使用(更宽泛的搜索)
find . -type f \( -name "*.js" -o -name "*.vue" \) -exec grep -l "useBackground" {} \; | head -10

Repository: AlkaidLab/foundation-sunshine

Length of output: 193


🏁 Script executed:

# 查看 Vue 相关导入
grep -n "^import.*from.*vue" src_assets/common/assets/web/composables/useBackground.js

Repository: AlkaidLab/foundation-sunshine

Length of output: 55


🏁 Script executed:

# 查看 Navbar.vue 中如何使用 useBackground
grep -n "useBackground" ./src_assets/common/assets/web/components/layout/Navbar.vue -B 5 -A 10

Repository: AlkaidLab/foundation-sunshine

Length of output: 1120


🏁 Script executed:

# 找到函数声明行并读取之后的内容,确认生命周期钩子
sed -n '150,250p' src_assets/common/assets/web/composables/useBackground.js

Repository: AlkaidLab/foundation-sunshine

Length of output: 3088


🏁 Script executed:

# 查看 Navbar.vue 的完整导入和函数调用
head -50 ./src_assets/common/assets/web/components/layout/Navbar.vue

Repository: AlkaidLab/foundation-sunshine

Length of output: 1992


🏁 Script executed:

# 查看 Navbar.vue 的完整脚本部分(找生命周期钩子)
sed -n '34,150p' ./src_assets/common/assets/web/components/layout/Navbar.vue

Repository: AlkaidLab/foundation-sunshine

Length of output: 2982


🏁 Script executed:

# 再次搜索是否有其他地方调用 useBackground
rg "useBackground" --type js -A 3 -B 3

Repository: AlkaidLab/foundation-sunshine

Length of output: 1827


🏁 Script executed:

# 查看 useBackground 函数是否有其他的事件监听器需要清理
sed -n '250,362p' src_assets/common/assets/web/composables/useBackground.js

Repository: AlkaidLab/foundation-sunshine

Length of output: 3601


🏁 Script executed:

# 检查是否有其他组件调用 useBackground
find . -type f \( -name "*.vue" -o -name "*.js" \) -exec grep -l "useBackground" {} \;

Repository: AlkaidLab/foundation-sunshine

Length of output: 193


🏁 Script executed:

# 查看是否有多个页面/组件使用 useBackground
rg "from.*useBackground|import.*useBackground" --type js --type vue -B 2

Repository: AlkaidLab/foundation-sunshine

Length of output: 101


🏁 Script executed:

# 再次确认只有 Navbar.vue 使用 useBackground
rg "useBackground" --type js -l

Repository: AlkaidLab/foundation-sunshine

Length of output: 189


🏁 Script executed:

# 检查 useBackground.js 是否已经导入了 Vue 的 API
head -20 src_assets/common/assets/web/composables/useBackground.js | grep "from.*vue"

Repository: AlkaidLab/foundation-sunshine

Length of output: 55


🏁 Script executed:

# 完整查看 useBackground.js 的第 1-10 行,确认导入情况
head -10 src_assets/common/assets/web/composables/useBackground.js

Repository: AlkaidLab/foundation-sunshine

Length of output: 340


🏁 Script executed:

# 查看 MutationObserver 的完整情况
sed -n '328,350p' src_assets/common/assets/web/composables/useBackground.js

Repository: AlkaidLab/foundation-sunshine

Length of output: 765


全局事件监听器和 MutationObserver 缺少清理,会导致内存泄漏

在 useBackground() 中注册的事件监听器和 MutationObserver 没有对应的清理机制。由于该 composable 在 Vue 组件的 script setup 中被调用,应在组件卸载时清理这些资源。建议使用 Vue 的 onScopeDispose 生命周期 API 清理。

建议修复
+import { getCurrentScope, onScopeDispose } from 'vue'
 import ColorThief from 'colorthief'
@@
   // 监听主题切换
   if (typeof document !== 'undefined') {
     const handleThemeChange = () => setTimeout(recheckBackgroundBrightness, 100)
     const observerConfig = { attributes: true, attributeFilter: ['data-bs-theme'] }
     const observer = new MutationObserver(handleThemeChange)
     observer.observe(document.documentElement, observerConfig)
     observer.observe(document.body, observerConfig)

+    if (getCurrentScope()) {
+      onScopeDispose(() => observer.disconnect())
+    }

     // 监听背景旁路切换
-    window.addEventListener('sunshine-background-bypass', (e) => {
+    const onBackgroundBypass = (e) => {
       if (e.detail?.enabled) {
         document.body.style.background = ''
       } else {
         loadBackground()
       }
-    })
+    }
+    window.addEventListener('sunshine-background-bypass', onBackgroundBypass)
+    if (getCurrentScope()) {
+      onScopeDispose(() => window.removeEventListener('sunshine-background-bypass', onBackgroundBypass))
+    }
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src_assets/common/assets/web/composables/useBackground.js` around lines 339 -
346, The global event listener ('sunshine-background-bypass') and the
MutationObserver created inside useBackground are not cleaned up, causing leaks;
update useBackground to store references to the window.addEventListener handler
and the MutationObserver instance and register a cleanup using Vue's
onScopeDispose: remove the window event listener via
window.removeEventListener('sunshine-background-bypass', handler) and call
observer.disconnect() inside the onScopeDispose callback so both the listener
and observer are properly released when the component using useBackground is
unmounted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant