feat: 允许没有背景#636
Conversation
Summary by CodeRabbit发布说明
概览此PR为日志界面添加隐藏的开发者触发机制,允许用户通过7次快速点击在3秒内切换开发模式。该模式通过localStorage持久化,并通过CustomEvent通知背景管理模块绕过背景图像应用逻辑。 变更开发者模式触发和背景绕过
预估代码审查工作量🎯 3 (中等) | ⏱️ ~20 分钟 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (2)
src_assets/common/assets/web/components/LogsSection.vuesrc_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.vuesrc_assets/common/assets/web/composables/useBackground.js
🔇 Additional comments (1)
src_assets/common/assets/web/components/LogsSection.vue (1)
80-83: 阈值、超时和存储键提为常量是个不错的改动这段改动提升了可读性和后续调整成本。
| 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 |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "LogsSection.vue" -type f 2>/dev/null | head -20Repository: AlkaidLab/foundation-sunshine
Length of output: 131
🏁 Script executed:
git ls-files | grep -i logssectionRepository: AlkaidLab/foundation-sunshine
Length of output: 129
🏁 Script executed:
wc -l src_assets/common/assets/web/components/LogsSection.vueRepository: 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 -nRepository: 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.
| 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.
| // 监听背景旁路切换 | ||
| window.addEventListener('sunshine-background-bypass', (e) => { | ||
| if (e.detail?.enabled) { | ||
| document.body.style.background = '' | ||
| } else { | ||
| loadBackground() | ||
| } | ||
| }) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# 查找并列出该文件
find . -type f -name "useBackground.js" | head -20Repository: 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/nullRepository: AlkaidLab/foundation-sunshine
Length of output: 1353
🏁 Script executed:
# 读取文件的最后部分,确认导出方式
tail -30 src_assets/common/assets/web/composables/useBackground.jsRepository: AlkaidLab/foundation-sunshine
Length of output: 887
🏁 Script executed:
# 查看文件的导入部分(行 1-30)
head -100 src_assets/common/assets/web/composables/useBackground.js | head -30Repository: AlkaidLab/foundation-sunshine
Length of output: 874
🏁 Script executed:
# 搜索这个 composable 在项目中的使用场景
rg "useBackground" --type js --type vue -B 2 -A 2 | head -100Repository: AlkaidLab/foundation-sunshine
Length of output: 101
🏁 Script executed:
# 查看是否有 onMounted/onUnmounted 等生命周期钩子
rg "onMounted|onUnmounted|onScopeDispose" src_assets/common/assets/web/composables/useBackground.jsRepository: AlkaidLab/foundation-sunshine
Length of output: 55
🏁 Script executed:
# 读取文件的中间部分来找到函数声明
sed -n '100,150p' src_assets/common/assets/web/composables/useBackground.jsRepository: AlkaidLab/foundation-sunshine
Length of output: 1357
🏁 Script executed:
# 查看函数声明
rg "^export|^function|^const.*=" src_assets/common/assets/web/composables/useBackground.js | head -20Repository: AlkaidLab/foundation-sunshine
Length of output: 758
🏁 Script executed:
# 搜索 useBackground 的使用(更宽泛的搜索)
find . -type f \( -name "*.js" -o -name "*.vue" \) -exec grep -l "useBackground" {} \; | head -10Repository: AlkaidLab/foundation-sunshine
Length of output: 193
🏁 Script executed:
# 查看 Vue 相关导入
grep -n "^import.*from.*vue" src_assets/common/assets/web/composables/useBackground.jsRepository: 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 10Repository: AlkaidLab/foundation-sunshine
Length of output: 1120
🏁 Script executed:
# 找到函数声明行并读取之后的内容,确认生命周期钩子
sed -n '150,250p' src_assets/common/assets/web/composables/useBackground.jsRepository: AlkaidLab/foundation-sunshine
Length of output: 3088
🏁 Script executed:
# 查看 Navbar.vue 的完整导入和函数调用
head -50 ./src_assets/common/assets/web/components/layout/Navbar.vueRepository: AlkaidLab/foundation-sunshine
Length of output: 1992
🏁 Script executed:
# 查看 Navbar.vue 的完整脚本部分(找生命周期钩子)
sed -n '34,150p' ./src_assets/common/assets/web/components/layout/Navbar.vueRepository: AlkaidLab/foundation-sunshine
Length of output: 2982
🏁 Script executed:
# 再次搜索是否有其他地方调用 useBackground
rg "useBackground" --type js -A 3 -B 3Repository: AlkaidLab/foundation-sunshine
Length of output: 1827
🏁 Script executed:
# 查看 useBackground 函数是否有其他的事件监听器需要清理
sed -n '250,362p' src_assets/common/assets/web/composables/useBackground.jsRepository: 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 2Repository: AlkaidLab/foundation-sunshine
Length of output: 101
🏁 Script executed:
# 再次确认只有 Navbar.vue 使用 useBackground
rg "useBackground" --type js -lRepository: 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.jsRepository: AlkaidLab/foundation-sunshine
Length of output: 340
🏁 Script executed:
# 查看 MutationObserver 的完整情况
sed -n '328,350p' src_assets/common/assets/web/composables/useBackground.jsRepository: 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.
No description provided.