mirror of
https://github.com/ZeroCatDev/Classworks.git
synced 2026-05-13 19:35:07 +00:00
feat: add custom background image with frosted glass effect
- Add background settings (enabled, url, imageData, blur, opacity) to settings.js - Add watchSettings same-tab reactivity via custom DOM event dispatch - Create BackgroundSettingsCard.vue with URL input, file upload, sliders, preview - Apply background layer in App.vue with blur filter and dark overlay - Add background settings tab to settings.vue navigation Agent-Logs-Url: https://github.com/ZeroCatDev/Classworks/sessions/6dfae4c0-df49-4612-88b8-9e31287538b0 Co-authored-by: Sunwuyuan <88357633+Sunwuyuan@users.noreply.github.com>
This commit is contained in:
parent
f842429f9b
commit
3f6e0b88bd
76
src/App.vue
76
src/App.vue
@ -1,5 +1,11 @@
|
||||
<template>
|
||||
<v-app>
|
||||
<v-app :style="vAppStyle">
|
||||
<!-- 自定义背景层 -->
|
||||
<template v-if="bgEnabled">
|
||||
<div class="app-background-image" :style="bgImageStyle" />
|
||||
<div class="app-background-overlay" :style="bgOverlayStyle" />
|
||||
</template>
|
||||
|
||||
<!-- 正常路由 -->
|
||||
<router-view v-slot="{ Component, route }">
|
||||
<transition mode="out-in" name="md3">
|
||||
@ -12,24 +18,71 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted } from "vue";
|
||||
import { ref, computed, onMounted, onUnmounted } from "vue";
|
||||
import { useTheme } from "vuetify";
|
||||
import { getSetting } from "@/utils/settings";
|
||||
import { getSetting, watchSettings } from "@/utils/settings";
|
||||
import RateLimitModal from "@/components/RateLimitModal.vue";
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
// Background reactive refs
|
||||
const bgEnabled = ref(false);
|
||||
const bgSrc = ref("");
|
||||
const bgBlur = ref(10);
|
||||
const bgOpacity = ref(30);
|
||||
|
||||
function loadBgSettings() {
|
||||
bgEnabled.value = getSetting("background.enabled") || false;
|
||||
const imageData = getSetting("background.imageData") || "";
|
||||
const url = getSetting("background.url") || "";
|
||||
bgSrc.value = imageData || url;
|
||||
bgBlur.value = getSetting("background.blur") ?? 10;
|
||||
bgOpacity.value = getSetting("background.opacity") ?? 30;
|
||||
}
|
||||
|
||||
const vAppStyle = computed(() => {
|
||||
if (!bgEnabled.value || !bgSrc.value) return {};
|
||||
return { background: "transparent" };
|
||||
});
|
||||
|
||||
const bgImageStyle = computed(() => ({
|
||||
backgroundImage: `url(${bgSrc.value})`,
|
||||
backgroundSize: "cover",
|
||||
backgroundPosition: "center",
|
||||
filter: `blur(${bgBlur.value}px)`,
|
||||
// Scale slightly to hide blur edge artifacts
|
||||
transform: "scale(1.05)",
|
||||
}));
|
||||
|
||||
const bgOverlayStyle = computed(() => ({
|
||||
background: `rgba(0, 0, 0, ${bgOpacity.value / 100})`,
|
||||
}));
|
||||
|
||||
let unwatchSettings = null;
|
||||
|
||||
onMounted(() => {
|
||||
// 应用保存的主题设置
|
||||
const savedTheme = getSetting("theme.mode");
|
||||
theme.global.name.value = savedTheme;
|
||||
|
||||
loadBgSettings();
|
||||
|
||||
unwatchSettings = watchSettings(() => {
|
||||
loadBgSettings();
|
||||
// Reapply theme on settings change too
|
||||
theme.global.name.value = getSetting("theme.mode");
|
||||
});
|
||||
|
||||
window.addEventListener('beforeinstallprompt', (e) => {
|
||||
e.preventDefault();
|
||||
window.deferredPwaPrompt = e;
|
||||
window.dispatchEvent(new Event('pwa-prompt-ready'));
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (unwatchSettings) unwatchSettings();
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
/* 全局样式(从 index.vue 迁移,确保全局可用且仅加载一次) */
|
||||
@ -52,4 +105,21 @@ onMounted(() => {
|
||||
opacity: 0;
|
||||
transform: translateX(-0.5vw);
|
||||
}
|
||||
|
||||
/* 自定义背景层 */
|
||||
.app-background-image,
|
||||
.app-background-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.app-background-image {
|
||||
transform-origin: center center;
|
||||
will-change: transform, filter;
|
||||
}
|
||||
</style>
|
||||
|
||||
422
src/components/settings/cards/BackgroundSettingsCard.vue
Normal file
422
src/components/settings/cards/BackgroundSettingsCard.vue
Normal file
@ -0,0 +1,422 @@
|
||||
<template>
|
||||
<settings-card border icon="mdi-image" title="背景设置">
|
||||
<v-list>
|
||||
<setting-item :setting-key="'background.enabled'" />
|
||||
</v-list>
|
||||
|
||||
<v-divider class="mb-4" />
|
||||
|
||||
<div class="px-4 pb-4">
|
||||
<!-- 预览区域 -->
|
||||
<div class="preview-area mb-6" :style="previewContainerStyle">
|
||||
<div class="preview-bg" :style="previewBgStyle" />
|
||||
<div class="preview-overlay" :style="previewOverlayStyle" />
|
||||
<div class="preview-text">背景预览</div>
|
||||
</div>
|
||||
|
||||
<!-- 图片来源 -->
|
||||
<div class="d-flex align-center mb-4">
|
||||
<v-icon class="mr-2" color="primary">mdi-image-search</v-icon>
|
||||
<span class="text-subtitle-1 font-weight-bold">图片来源</span>
|
||||
</div>
|
||||
|
||||
<!-- 来源选择 -->
|
||||
<v-btn-toggle
|
||||
v-model="imageSource"
|
||||
color="primary"
|
||||
density="comfortable"
|
||||
class="mb-4"
|
||||
mandatory
|
||||
rounded="xl"
|
||||
>
|
||||
<v-btn value="url" prepend-icon="mdi-link-variant">网络地址</v-btn>
|
||||
<v-btn value="upload" prepend-icon="mdi-upload">本地上传</v-btn>
|
||||
</v-btn-toggle>
|
||||
|
||||
<!-- URL 输入 -->
|
||||
<div v-if="imageSource === 'url'" class="mb-4">
|
||||
<v-text-field
|
||||
v-model="localUrl"
|
||||
label="图片地址"
|
||||
placeholder="https://example.com/background.jpg"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
prepend-inner-icon="mdi-link"
|
||||
clearable
|
||||
hide-details="auto"
|
||||
:rules="[validateUrl]"
|
||||
@update:model-value="onUrlChange"
|
||||
/>
|
||||
<div class="d-flex flex-wrap gap-2 mt-2">
|
||||
<v-chip
|
||||
v-for="preset in urlPresets"
|
||||
:key="preset.label"
|
||||
size="small"
|
||||
variant="tonal"
|
||||
color="primary"
|
||||
class="cursor-pointer"
|
||||
@click="applyPreset(preset.url)"
|
||||
>
|
||||
{{ preset.label }}
|
||||
</v-chip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 本地上传 -->
|
||||
<div v-if="imageSource === 'upload'" class="mb-4">
|
||||
<div
|
||||
class="upload-area rounded-xl pa-6 text-center mb-3"
|
||||
:class="{ 'upload-hover': isDragging }"
|
||||
@dragover.prevent="isDragging = true"
|
||||
@dragleave="isDragging = false"
|
||||
@drop.prevent="handleDrop"
|
||||
@click="triggerFileInput"
|
||||
>
|
||||
<v-icon size="40" color="primary" class="mb-2">mdi-image-plus</v-icon>
|
||||
<div class="text-body-2">点击或拖拽图片到此处上传</div>
|
||||
<div class="text-caption text-medium-emphasis mt-1">支持 JPG、PNG、WebP、GIF(建议小于 2MB)</div>
|
||||
<input
|
||||
ref="fileInput"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
style="display: none"
|
||||
@change="handleFileChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<v-alert
|
||||
v-if="uploadWarning"
|
||||
type="warning"
|
||||
variant="tonal"
|
||||
density="compact"
|
||||
class="mb-2"
|
||||
icon="mdi-alert"
|
||||
>
|
||||
{{ uploadWarning }}
|
||||
</v-alert>
|
||||
|
||||
<div v-if="localImageData" class="d-flex align-center ga-2">
|
||||
<v-chip color="success" prepend-icon="mdi-check-circle" size="small">
|
||||
已上传本地图片
|
||||
</v-chip>
|
||||
<v-btn
|
||||
size="small"
|
||||
variant="text"
|
||||
color="error"
|
||||
prepend-icon="mdi-delete"
|
||||
@click="clearUploadedImage"
|
||||
>
|
||||
清除
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<v-divider class="my-5" />
|
||||
|
||||
<!-- 毛玻璃效果设置 -->
|
||||
<div class="d-flex align-center mb-4">
|
||||
<v-icon class="mr-2" color="blue">mdi-blur</v-icon>
|
||||
<span class="text-subtitle-1 font-weight-bold">毛玻璃效果</span>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<div class="d-flex justify-space-between align-center mb-1">
|
||||
<span class="text-body-2 text-medium-emphasis">模糊幅度</span>
|
||||
<span class="text-body-2 font-weight-bold">{{ localBlur }}px</span>
|
||||
</div>
|
||||
<v-slider
|
||||
v-model="localBlur"
|
||||
:min="0"
|
||||
:max="50"
|
||||
:step="1"
|
||||
color="primary"
|
||||
track-color="grey-lighten-3"
|
||||
thumb-label
|
||||
hide-details
|
||||
@update:model-value="onBlurChange"
|
||||
>
|
||||
<template #prepend>
|
||||
<v-icon size="small" color="grey">mdi-blur-off</v-icon>
|
||||
</template>
|
||||
<template #append>
|
||||
<v-icon size="small" color="primary">mdi-blur</v-icon>
|
||||
</template>
|
||||
</v-slider>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<div class="d-flex justify-space-between align-center mb-1">
|
||||
<span class="text-body-2 text-medium-emphasis">遮罩暗色程度</span>
|
||||
<span class="text-body-2 font-weight-bold">{{ localOpacity }}%</span>
|
||||
</div>
|
||||
<v-slider
|
||||
v-model="localOpacity"
|
||||
:min="0"
|
||||
:max="80"
|
||||
:step="1"
|
||||
color="blue-grey"
|
||||
track-color="grey-lighten-3"
|
||||
thumb-label
|
||||
hide-details
|
||||
@update:model-value="onOpacityChange"
|
||||
>
|
||||
<template #prepend>
|
||||
<v-icon size="small" color="grey">mdi-brightness-7</v-icon>
|
||||
</template>
|
||||
<template #append>
|
||||
<v-icon size="small" color="blue-grey">mdi-brightness-2</v-icon>
|
||||
</template>
|
||||
</v-slider>
|
||||
</div>
|
||||
|
||||
<v-divider class="my-5" />
|
||||
|
||||
<!-- 保存按钮 -->
|
||||
<div class="d-flex justify-end ga-3">
|
||||
<v-btn variant="text" prepend-icon="mdi-restore" @click="resetAll">
|
||||
重置
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
variant="elevated"
|
||||
prepend-icon="mdi-content-save"
|
||||
:loading="saving"
|
||||
@click="saveAll"
|
||||
>
|
||||
保存设置
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</settings-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SettingsCard from '@/components/SettingsCard.vue';
|
||||
import SettingItem from '@/components/settings/SettingItem.vue';
|
||||
import { getSetting, setSetting, resetSetting } from '@/utils/settings';
|
||||
|
||||
const URL_PRESETS = [
|
||||
{ label: 'Bing 每日壁纸', url: 'https://bing.img.run/rand_uhd.php' },
|
||||
{ label: '随机风景', url: 'https://picsum.photos/1920/1080?random=1' },
|
||||
{ label: '随机自然', url: 'https://source.unsplash.com/1920x1080/?nature' },
|
||||
];
|
||||
|
||||
const MAX_IMAGE_SIZE_MB = 2;
|
||||
|
||||
export default {
|
||||
name: 'BackgroundSettingsCard',
|
||||
components: { SettingsCard, SettingItem },
|
||||
|
||||
data() {
|
||||
const imageData = getSetting('background.imageData') || '';
|
||||
const url = getSetting('background.url') || '';
|
||||
|
||||
return {
|
||||
imageSource: imageData ? 'upload' : 'url',
|
||||
localUrl: url,
|
||||
localImageData: imageData,
|
||||
localBlur: getSetting('background.blur') ?? 10,
|
||||
localOpacity: getSetting('background.opacity') ?? 30,
|
||||
isDragging: false,
|
||||
saving: false,
|
||||
uploadWarning: '',
|
||||
urlPresets: URL_PRESETS,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
/** The active image src for preview */
|
||||
activeImageSrc() {
|
||||
if (this.imageSource === 'upload' && this.localImageData) {
|
||||
return this.localImageData;
|
||||
}
|
||||
if (this.imageSource === 'url' && this.localUrl) {
|
||||
return this.localUrl;
|
||||
}
|
||||
return '';
|
||||
},
|
||||
|
||||
previewContainerStyle() {
|
||||
return {
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
height: '160px',
|
||||
borderRadius: '12px',
|
||||
overflow: 'hidden',
|
||||
border: '1px solid rgba(128,128,128,0.3)',
|
||||
};
|
||||
},
|
||||
|
||||
previewBgStyle() {
|
||||
if (!this.activeImageSrc) {
|
||||
return {
|
||||
position: 'absolute',
|
||||
inset: '0',
|
||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||||
filter: `blur(${this.localBlur}px)`,
|
||||
transform: 'scale(1.1)',
|
||||
};
|
||||
}
|
||||
return {
|
||||
position: 'absolute',
|
||||
inset: '0',
|
||||
backgroundImage: `url(${this.activeImageSrc})`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
filter: `blur(${this.localBlur}px)`,
|
||||
transform: 'scale(1.1)',
|
||||
};
|
||||
},
|
||||
|
||||
previewOverlayStyle() {
|
||||
return {
|
||||
position: 'absolute',
|
||||
inset: '0',
|
||||
background: `rgba(0, 0, 0, ${this.localOpacity / 100})`,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
validateUrl(val) {
|
||||
if (!val) return true;
|
||||
try {
|
||||
new URL(val);
|
||||
return true;
|
||||
} catch {
|
||||
return '请输入有效的图片地址';
|
||||
}
|
||||
},
|
||||
|
||||
onUrlChange(val) {
|
||||
this.localUrl = val || '';
|
||||
},
|
||||
|
||||
onBlurChange(val) {
|
||||
this.localBlur = val;
|
||||
},
|
||||
|
||||
onOpacityChange(val) {
|
||||
this.localOpacity = val;
|
||||
},
|
||||
|
||||
applyPreset(url) {
|
||||
this.localUrl = url;
|
||||
this.imageSource = 'url';
|
||||
},
|
||||
|
||||
triggerFileInput() {
|
||||
this.$refs.fileInput.click();
|
||||
},
|
||||
|
||||
handleDrop(event) {
|
||||
this.isDragging = false;
|
||||
const file = event.dataTransfer?.files?.[0];
|
||||
if (file) this.processFile(file);
|
||||
},
|
||||
|
||||
handleFileChange(event) {
|
||||
const file = event.target.files?.[0];
|
||||
if (file) this.processFile(file);
|
||||
// Reset input so same file can be re-selected
|
||||
event.target.value = '';
|
||||
},
|
||||
|
||||
processFile(file) {
|
||||
this.uploadWarning = '';
|
||||
|
||||
if (!file.type.startsWith('image/')) {
|
||||
this.uploadWarning = '请选择图片文件';
|
||||
return;
|
||||
}
|
||||
|
||||
const sizeMB = file.size / 1024 / 1024;
|
||||
if (sizeMB > MAX_IMAGE_SIZE_MB) {
|
||||
this.uploadWarning = `图片大小为 ${sizeMB.toFixed(1)}MB,超过 ${MAX_IMAGE_SIZE_MB}MB 建议大小,可能影响性能`;
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
this.localImageData = e.target.result;
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
},
|
||||
|
||||
clearUploadedImage() {
|
||||
this.localImageData = '';
|
||||
this.uploadWarning = '';
|
||||
},
|
||||
|
||||
async saveAll() {
|
||||
this.saving = true;
|
||||
try {
|
||||
// Determine which image source to persist
|
||||
if (this.imageSource === 'upload') {
|
||||
setSetting('background.imageData', this.localImageData || '');
|
||||
setSetting('background.url', '');
|
||||
} else {
|
||||
setSetting('background.url', this.localUrl || '');
|
||||
setSetting('background.imageData', '');
|
||||
}
|
||||
|
||||
setSetting('background.blur', this.localBlur);
|
||||
setSetting('background.opacity', this.localOpacity);
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
},
|
||||
|
||||
resetAll() {
|
||||
resetSetting('background.enabled');
|
||||
resetSetting('background.url');
|
||||
resetSetting('background.imageData');
|
||||
resetSetting('background.blur');
|
||||
resetSetting('background.opacity');
|
||||
|
||||
this.localUrl = getSetting('background.url') || '';
|
||||
this.localImageData = getSetting('background.imageData') || '';
|
||||
this.localBlur = getSetting('background.blur') ?? 10;
|
||||
this.localOpacity = getSetting('background.opacity') ?? 30;
|
||||
this.imageSource = 'url';
|
||||
this.uploadWarning = '';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.preview-area {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.preview-text {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
text-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.upload-area {
|
||||
border: 2px dashed rgba(128, 128, 128, 0.4);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
background: rgba(128, 128, 128, 0.05);
|
||||
}
|
||||
|
||||
.upload-area:hover,
|
||||
.upload-hover {
|
||||
border-color: rgb(var(--v-theme-primary));
|
||||
background: rgba(var(--v-theme-primary), 0.05);
|
||||
}
|
||||
|
||||
.gap-2 {
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
||||
@ -195,6 +195,10 @@
|
||||
<homework-template-card border/>
|
||||
</v-tabs-window-item>
|
||||
|
||||
<v-tabs-window-item value="background">
|
||||
<background-settings-card border />
|
||||
</v-tabs-window-item>
|
||||
|
||||
<v-tabs-window-item value="developer"
|
||||
>
|
||||
<settings-card border icon="mdi-developer-board" title="开发者选项">
|
||||
@ -280,6 +284,7 @@ import KvDatabaseCard from "@/components/settings/cards/KvDatabaseCard.vue";
|
||||
import HitokotoSettings from "@/components/HitokotoSettings.vue";
|
||||
import NotificationSoundSettings from "@/components/settings/NotificationSoundSettings.vue";
|
||||
import NoiseSettingsCard from "@/components/settings/cards/NoiseSettingsCard.vue";
|
||||
import BackgroundSettingsCard from "@/components/settings/cards/BackgroundSettingsCard.vue";
|
||||
|
||||
export default {
|
||||
name: "Settings",
|
||||
@ -304,6 +309,7 @@ export default {
|
||||
HitokotoSettings,
|
||||
NotificationSoundSettings,
|
||||
NoiseSettingsCard,
|
||||
BackgroundSettingsCard,
|
||||
},
|
||||
setup() {
|
||||
const {mobile} = useDisplay();
|
||||
@ -442,6 +448,12 @@ export default {
|
||||
value: "randomPicker",
|
||||
},
|
||||
|
||||
{
|
||||
title: "背景",
|
||||
icon: "mdi-image",
|
||||
value: "background",
|
||||
},
|
||||
|
||||
{
|
||||
title: "开发者",
|
||||
icon: "mdi-developer-board",
|
||||
|
||||
@ -441,6 +441,40 @@ const settingsDefinitions = {
|
||||
// 设置应用的主题模式,可选亮色或暗色主题
|
||||
},
|
||||
|
||||
// 背景设置
|
||||
"background.enabled": {
|
||||
type: "boolean",
|
||||
default: false,
|
||||
description: "启用自定义背景",
|
||||
icon: "mdi-image",
|
||||
},
|
||||
"background.url": {
|
||||
type: "string",
|
||||
default: "",
|
||||
description: "背景图片地址",
|
||||
icon: "mdi-link",
|
||||
},
|
||||
"background.imageData": {
|
||||
type: "string",
|
||||
default: "",
|
||||
description: "本地背景图片(Base64)",
|
||||
icon: "mdi-image-area",
|
||||
},
|
||||
"background.blur": {
|
||||
type: "number",
|
||||
default: 10,
|
||||
validate: (value) => value >= 0 && value <= 50,
|
||||
description: "毛玻璃模糊幅度(px)",
|
||||
icon: "mdi-blur",
|
||||
},
|
||||
"background.opacity": {
|
||||
type: "number",
|
||||
default: 30,
|
||||
validate: (value) => value >= 0 && value <= 80,
|
||||
description: "遮罩暗色程度(%)",
|
||||
icon: "mdi-circle-half-full",
|
||||
},
|
||||
|
||||
// 通知铃声设置
|
||||
"notification.singleSound": {
|
||||
type: "string",
|
||||
@ -674,6 +708,13 @@ class SettingsManagerClass {
|
||||
this.saveSettings();
|
||||
this.logSettingsChange(key, oldValue, value);
|
||||
|
||||
// 触发同标签页内的设置变化事件
|
||||
if (typeof window !== "undefined") {
|
||||
window.dispatchEvent(new CustomEvent("classworks-settings-changed", {
|
||||
detail: { key, value },
|
||||
}));
|
||||
}
|
||||
|
||||
// 为了保持向后兼容,同时更新旧的localStorage键
|
||||
const legacyKey = definition.legacyKey;
|
||||
if (legacyKey && typeof localStorage !== "undefined") {
|
||||
@ -721,6 +762,13 @@ class SettingsManagerClass {
|
||||
|
||||
this.settingsCache[key] = definition.default;
|
||||
this.saveSettings();
|
||||
|
||||
// 触发同标签页内的设置变化事件
|
||||
if (typeof window !== "undefined") {
|
||||
window.dispatchEvent(new CustomEvent("classworks-settings-changed", {
|
||||
detail: { key, value: definition.default },
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -743,15 +791,23 @@ class SettingsManagerClass {
|
||||
if (typeof window === "undefined") return () => {
|
||||
};
|
||||
|
||||
const handler = (event) => {
|
||||
const storageHandler = (event) => {
|
||||
if (event.key === SETTINGS_STORAGE_KEY) {
|
||||
this.settingsCache = JSON.parse(event.newValue);
|
||||
callback(this.settingsCache);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("storage", handler);
|
||||
return () => window.removeEventListener("storage", handler);
|
||||
const customHandler = () => {
|
||||
callback(this.settingsCache);
|
||||
};
|
||||
|
||||
window.addEventListener("storage", storageHandler);
|
||||
window.addEventListener("classworks-settings-changed", customHandler);
|
||||
return () => {
|
||||
window.removeEventListener("storage", storageHandler);
|
||||
window.removeEventListener("classworks-settings-changed", customHandler);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user