280 lines
8.5 KiB
JavaScript
280 lines
8.5 KiB
JavaScript
const BackgroundManager = (function () {
|
|
const CACHE_KEY = 'background_cache';
|
|
const LAST_UPDATE_KEY = 'background_last_update';
|
|
const CACHE_EXPIRE_DAYS = 30;
|
|
const CONFIG_ENDPOINT = '/api/calendar/sys-config';
|
|
let configCache = null;
|
|
|
|
const UNSPLASH_ACCESS_KEY = 'osWW4Fx0IbpfJs2LbCl_Jpv5ej-6ZaphyIX9sgdPdxI';
|
|
const PICSUM_BASE_URL = 'https://picsum.photos';
|
|
const CHINESE_THEME_KEYWORDS = [
|
|
'chinese landscape', 'chinese painting', 'bamboo', 'lotus', 'plum blossom',
|
|
'orchid', 'chrysanthemum', 'mountain water', 'ink wash', 'traditional chinese',
|
|
'landscape painting', 'watercolor', 'minimalist', 'serene', 'nature'
|
|
];
|
|
|
|
async function loadConfig() {
|
|
if (configCache) return configCache;
|
|
try {
|
|
const res = await fetch(CONFIG_ENDPOINT, { cache: 'no-store' });
|
|
if (!res.ok) {
|
|
configCache = {};
|
|
return configCache;
|
|
}
|
|
const data = await res.json().catch(() => ({}));
|
|
const cfg = data && data.config && typeof data.config === 'object' ? data.config : {};
|
|
configCache = cfg;
|
|
return cfg;
|
|
} catch (_) {
|
|
configCache = {};
|
|
return configCache;
|
|
}
|
|
}
|
|
|
|
async function getLocalPoolBackground(cfg) {
|
|
try {
|
|
const theme = String((cfg && (cfg.background_theme || cfg.backgroundTheme)) || '默认背景');
|
|
const params = new URLSearchParams();
|
|
params.set('theme', theme);
|
|
params.set('prefer_local', '1');
|
|
const res = await fetch('/api/home/bg?' + params.toString(), { cache: 'no-store' });
|
|
if (!res.ok) return '';
|
|
const data = await res.json().catch(() => ({}));
|
|
const url = data && typeof data.url === 'string' ? data.url : '';
|
|
return url || '';
|
|
} catch (_) {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
async function getAllPoolBackground() {
|
|
const themes = [
|
|
'夜空星轨',
|
|
'蓝天白云',
|
|
'治愈森林',
|
|
'运动风',
|
|
'科技霓虹',
|
|
'商务简约',
|
|
'暖色日落',
|
|
'冷色极夜'
|
|
];
|
|
const theme = themes[Math.floor(Math.random() * themes.length)];
|
|
try {
|
|
const params = new URLSearchParams();
|
|
params.set('theme', theme);
|
|
params.set('prefer_local', '1');
|
|
const res = await fetch('/api/home/bg?' + params.toString(), { cache: 'no-store' });
|
|
if (!res.ok) return '';
|
|
const data = await res.json().catch(() => ({}));
|
|
const url = data && typeof data.url === 'string' ? data.url : '';
|
|
return url || '';
|
|
} catch (_) {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
async function getDailyBackground() {
|
|
const cfg = await loadConfig();
|
|
const today = new Date().toDateString();
|
|
const cached = getCachedBackground();
|
|
|
|
if (cached && cached.date === today && cached.url) {
|
|
return cached.url;
|
|
}
|
|
|
|
const enableDynamic = cfg && Object.prototype.hasOwnProperty.call(cfg, 'ENABLE_DYNAMIC_BACKGROUND')
|
|
? cfg.ENABLE_DYNAMIC_BACKGROUND !== false
|
|
: true;
|
|
const modeRaw = cfg && cfg.background_mode ? String(cfg.background_mode) : '';
|
|
const mode = modeRaw.toUpperCase();
|
|
const useAllPool = mode === 'ALL_POOL';
|
|
|
|
if (!enableDynamic) {
|
|
const url = getDefaultBackground();
|
|
cacheBackground(url, today);
|
|
return url;
|
|
}
|
|
|
|
if (useAllPool) {
|
|
const allUrl = await getAllPoolBackground();
|
|
if (allUrl) {
|
|
cacheBackground(allUrl, today);
|
|
return allUrl;
|
|
}
|
|
const fallback = getDefaultBackground();
|
|
cacheBackground(fallback, today);
|
|
return fallback;
|
|
}
|
|
|
|
if (cfg && (cfg.background_theme || cfg.backgroundTheme)) {
|
|
const localUrl = await getLocalPoolBackground(cfg);
|
|
if (localUrl) {
|
|
cacheBackground(localUrl, today);
|
|
return localUrl;
|
|
}
|
|
const fallback = getDefaultBackground();
|
|
cacheBackground(fallback, today);
|
|
return fallback;
|
|
}
|
|
|
|
try {
|
|
const unsplashUrl = await tryUnsplash();
|
|
if (unsplashUrl) {
|
|
cacheBackground(unsplashUrl, today);
|
|
return unsplashUrl;
|
|
}
|
|
} catch (error) {
|
|
console.warn('Unsplash API failed:', error);
|
|
}
|
|
|
|
if (cached && cached.url) return cached.url;
|
|
try {
|
|
const picsumUrl = await tryPicsum();
|
|
if (picsumUrl) {
|
|
cacheBackground(picsumUrl, today);
|
|
return picsumUrl;
|
|
}
|
|
} catch (error) {
|
|
console.warn('Picsum API failed:', error);
|
|
}
|
|
const fallback = getDefaultBackground();
|
|
cacheBackground(fallback, today);
|
|
return fallback;
|
|
}
|
|
|
|
async function tryUnsplash() {
|
|
if (!UNSPLASH_ACCESS_KEY || UNSPLASH_ACCESS_KEY === 'your_unsplash_access_key_here') {
|
|
return null;
|
|
}
|
|
|
|
const randomKeyword = CHINESE_THEME_KEYWORDS[Math.floor(Math.random() * CHINESE_THEME_KEYWORDS.length)];
|
|
const url = `https://api.unsplash.com/photos/random?query=${encodeURIComponent(randomKeyword)}&orientation=landscape&client_id=${UNSPLASH_ACCESS_KEY}`;
|
|
|
|
try {
|
|
const response = await fetch(url, {
|
|
method: 'GET',
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
'Accept-Version': 'v1'
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
console.warn('Unsplash API error:', response.status, response.statusText);
|
|
return null;
|
|
}
|
|
|
|
const data = await response.json();
|
|
return data.urls.regular;
|
|
} catch (error) {
|
|
console.warn('Unsplash fetch failed:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function tryPicsum() {
|
|
const width = window.innerWidth;
|
|
const height = window.innerHeight;
|
|
const imageId = Math.floor(Math.random() * 1000);
|
|
|
|
return `${PICSUM_BASE_URL}/${width}/${height}?blur=2&image=${imageId}`;
|
|
}
|
|
|
|
function getDefaultBackground() {
|
|
return 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
|
|
}
|
|
|
|
function getCachedBackground() {
|
|
try {
|
|
const cached = localStorage.getItem(CACHE_KEY);
|
|
const lastUpdate = localStorage.getItem(LAST_UPDATE_KEY);
|
|
|
|
if (!cached || !lastUpdate) return null;
|
|
|
|
// 检查缓存是否过期(30天)
|
|
const updateDate = new Date(lastUpdate);
|
|
const expireDate = new Date(updateDate);
|
|
expireDate.setDate(expireDate.getDate() + CACHE_EXPIRE_DAYS);
|
|
|
|
if (new Date() > expireDate) {
|
|
localStorage.removeItem(CACHE_KEY);
|
|
localStorage.removeItem(LAST_UPDATE_KEY);
|
|
return null;
|
|
}
|
|
|
|
return JSON.parse(cached);
|
|
} catch (error) {
|
|
console.warn('Failed to read cache:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function cacheBackground(url, date) {
|
|
try {
|
|
const cacheData = { url, date };
|
|
localStorage.setItem(CACHE_KEY, JSON.stringify(cacheData));
|
|
localStorage.setItem(LAST_UPDATE_KEY, new Date().toISOString());
|
|
} catch (error) {
|
|
console.warn('Failed to cache background:', error);
|
|
}
|
|
}
|
|
|
|
function applyBackground(url) {
|
|
const body = document.body;
|
|
|
|
let bgContainer = document.getElementById('background-container');
|
|
if (!bgContainer) {
|
|
bgContainer = document.createElement('div');
|
|
bgContainer.id = 'background-container';
|
|
const isImage = typeof url === 'string' && (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('/'));
|
|
bgContainer.style.cssText = `
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
z-index: -1;
|
|
background: ${isImage ? `url(${url}) center/cover no-repeat` : url};
|
|
opacity: 0.15;
|
|
filter: blur(8px);
|
|
`;
|
|
document.body.appendChild(bgContainer);
|
|
} else {
|
|
const isImage = typeof url === 'string' && (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('/'));
|
|
bgContainer.style.background = isImage ? `url(${url}) center/cover no-repeat` : url;
|
|
}
|
|
|
|
body.style.background = 'transparent';
|
|
const importantElements = document.querySelectorAll('.card, .calendar-grid, .navbar');
|
|
importantElements.forEach(el => {
|
|
el.style.backgroundColor = 'rgba(255, 255, 255, 0.95)';
|
|
el.style.backdropFilter = 'blur(10px)';
|
|
});
|
|
}
|
|
|
|
async function init() {
|
|
try {
|
|
const backgroundUrl = await getDailyBackground();
|
|
applyBackground(backgroundUrl);
|
|
} catch (error) {
|
|
console.error('Failed to set background:', error);
|
|
applyBackground(getDefaultBackground());
|
|
}
|
|
}
|
|
|
|
return {
|
|
init,
|
|
refresh: init
|
|
};
|
|
})();
|
|
|
|
// 页面加载时初始化
|
|
window.addEventListener('load', () => {
|
|
BackgroundManager.init();
|
|
});
|
|
|
|
// 导出供其他模块使用
|
|
if (typeof module !== 'undefined' && module.exports) {
|
|
module.exports = BackgroundManager;
|
|
}
|