// ==UserScript== // @name 究极美化必应搜索页面(毛玻璃)(原生JS重构版) // @namespace http://tampermonkey.net/ // @version 3.0.3 // @description 美化必应搜索页面 (重构版 - 使用localStorage) // @author Onion (重构优化版) // @match *://*.cn.bing.com/* // @match *://*.bing.com/* // @match *://*.baidu.com/* // @icon https://gitee.com/onion-big/gitstore/raw/main/js/jpg/beautify.png // @license MPL2.0 // @grant GM_notification // @run-at document-start // ==/UserScript== (function() { 'use strict'; // ==================== 配置管理模块 ==================== class ConfigManager { constructor() { this.storageKey = 'bing_beautify_config'; this.cacheKey = 'bing_beautify_cache'; this.defaultConfig = { blurLevel: 13, opacity1: 83, // 渐变色透明度 (0-100) opacity2: 75, // 图片透明度 (0-100) backgroundType: 'gradient', // 'gradient' | 'image' | 'custom' backgroundImage: this.getDefaultImages()[0], customImageBase64: '', searchboxStyle: 'transparent' // 'transparent' | 'colorful' }; this.initConfig(); } getDefaultImages() { return [ "https://bing.biturl.top/?resolution=1920&format=image&index=0&mkt=zh-CN", "https://bing.com/th?id=OHR.NationalDay2022_ZH-CN3861603311_1920x1080.jpg", "https://bing.com/th?id=OHR.BridgeofSighs_ZH-CN5414607871_1920x1080.jpg", "https://images4.alphacoders.com/171/171916.jpg", "https://images5.alphacoders.com/613/613927.jpg", "https://images2.alphacoders.com/606/606275.jpg", "https://images2.alphacoders.com/742/742320.png", "https://dogefs.s3.ladydaily.com/~/source/wallhaven/full/8o/wallhaven-8o2dpj.png?w=2560&fmt=webp" ]; } initConfig() { try { const stored = localStorage.getItem(this.storageKey); this.config = stored ? { ...this.defaultConfig, ...JSON.parse(stored) } : { ...this.defaultConfig }; this.saveConfig(); } catch (error) { console.error('配置初始化失败:', error); this.config = { ...this.defaultConfig }; this.saveConfig(); } } get(key) { return this.config[key]; } set(key, value) { this.config[key] = value; this.saveConfig(); } update(updates) { Object.assign(this.config, updates); this.saveConfig(); } saveConfig() { try { localStorage.setItem(this.storageKey, JSON.stringify(this.config)); } catch (error) { console.error('配置保存失败:', error); } } reset() { this.config = { ...this.defaultConfig }; this.saveConfig(); } // 透明度转换:0-100 转为 0-255 的十六进制 opacityToHex(opacity) { const value = Math.round((opacity / 100) * 255); return value.toString(16).padStart(2, '0'); } // 十六进制转透明度:0-255 转为 0-100 hexToOpacity(hex) { if (!hex) return 80; const value = parseInt(hex, 16); return Math.round((value / 255) * 100); } // 图片缓存管理 getCachedImage(url) { try { const cache = JSON.parse(localStorage.getItem(this.cacheKey) || '{}'); const cached = cache[url]; // 必应每日壁纸需要每日更新 if (url.includes('bing.biturl.top') && cached) { const today = new Date().toDateString(); if (cached.date !== today) { delete cache[url]; localStorage.setItem(this.cacheKey, JSON.stringify(cache)); return null; } } return cached?.data || null; } catch (error) { console.error('获取缓存图片失败:', error); return null; } } setCachedImage(url, base64Data) { try { const cache = JSON.parse(localStorage.getItem(this.cacheKey) || '{}'); cache[url] = { data: base64Data, date: new Date().toDateString(), timestamp: Date.now() }; // 清理过期缓存(30天前) const expireTime = Date.now() - (30 * 24 * 60 * 60 * 1000); Object.keys(cache).forEach(key => { if (cache[key].timestamp < expireTime) { delete cache[key]; } }); localStorage.setItem(this.cacheKey, JSON.stringify(cache)); } catch (error) { console.error('缓存图片失败:', error); } } // 将图片转换为base64存储 async convertImageToBase64(imageUrl) { try { // 先检查缓存 const cached = this.getCachedImage(imageUrl); if (cached) { console.log('使用缓存图片:', imageUrl); return cached; } console.log('下载并缓存图片:', imageUrl); const response = await fetch(imageUrl); const blob = await response.blob(); return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onloadend = () => { const result = reader.result; // 缓存图片 this.setCachedImage(imageUrl, result); resolve(result); }; reader.onerror = reject; reader.readAsDataURL(blob); }); } catch (error) { console.error('图片转换base64失败:', error); return null; } } } // ==================== 样式管理模块 ==================== class StyleManager { constructor(config) { this.config = config; this.injectedStyles = new Set(); } injectGlobalStyles() { const css = ` @keyframes fadeIn { 0% { opacity: 0; transform: translateY(-10px); } 100% { opacity: 1; transform: translateY(0); } } /* 设置框样式 - 强制最高优先级 */ .beautify-settings { position: fixed !important; top: 50% !important; left: 50% !important; transform: translate(-50%, -50%) !important; width: min(520px, 95vw) !important; max-height: 85vh !important; background: rgba(255, 255, 255, 0.98) !important; backdrop-filter: blur(20px) !important; border-radius: 15px !important; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3) !important; z-index: 999999999 !important; padding: 0 !important; margin: 0 !important; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important; display: none !important; animation: fadeIn 0.3s ease-out !important; border: 2px solid rgba(76, 175, 80, 0.3) !important; overflow: hidden !important; pointer-events: auto !important; } .beautify-settings.show { display: block !important; visibility: visible !important; opacity: 1 !important; } .beautify-settings-header { display: flex !important; justify-content: space-between !important; align-items: center !important; padding: 15px 20px !important; border-bottom: 1px solid rgba(0, 0, 0, 0.1) !important; cursor: move !important; background: rgba(76, 175, 80, 0.1) !important; border-radius: 15px 15px 0 0 !important; flex-shrink: 0 !important; } .beautify-settings-title { font-size: 16px !important; font-weight: 600 !important; color: #333 !important; margin: 0 !important; white-space: nowrap !important; } .beautify-close-btn { background: rgba(255, 0, 0, 0.1) !important; border: none !important; font-size: 20px !important; cursor: pointer !important; color: #666 !important; padding: 5px !important; width: 30px !important; height: 30px !important; display: flex !important; align-items: center !important; justify-content: center !important; border-radius: 50% !important; transition: all 0.2s ease !important; flex-shrink: 0 !important; } .beautify-close-btn:hover { background: rgba(255, 0, 0, 0.2) !important; color: #ff0000 !important; transform: scale(1.1) !important; } .beautify-settings-content { padding: 20px !important; max-height: calc(85vh - 120px) !important; overflow-y: auto !important; background: rgba(255, 255, 255, 0.95) !important; border-radius: 0 0 15px 15px !important; } .beautify-form-group { margin-bottom: 18px !important; } .beautify-form-group:last-child { margin-bottom: 0 !important; } .beautify-label { display: block !important; margin-bottom: 8px !important; font-size: 14px !important; font-weight: 500 !important; color: #555 !important; white-space: nowrap !important; overflow: hidden !important; text-overflow: ellipsis !important; } .beautify-input, .beautify-select { width: 100% !important; padding: 10px 12px !important; border: 2px solid rgba(0, 0, 0, 0.1) !important; border-radius: 8px !important; font-size: 14px !important; background: white !important; transition: border-color 0.2s ease !important; box-sizing: border-box !important; } .beautify-input:focus, .beautify-select:focus { outline: none !important; border-color: #4CAF50 !important; } /* 滑块样式 */ .beautify-slider-container { display: flex !important; align-items: center !important; gap: 10px !important; } .beautify-slider { flex: 1 !important; height: 6px !important; background: #ddd !important; border-radius: 3px !important; outline: none !important; border: none !important; } .beautify-slider::-webkit-slider-thumb { appearance: none !important; width: 18px !important; height: 18px !important; background: #4CAF50 !important; border-radius: 50% !important; cursor: pointer !important; } .beautify-slider::-moz-range-thumb { width: 18px !important; height: 18px !important; background: #4CAF50 !important; border-radius: 50% !important; cursor: pointer !important; border: none !important; } .beautify-value-display { min-width: 45px !important; text-align: right !important; font-size: 12px !important; color: #666 !important; background: rgba(0, 0, 0, 0.05) !important; padding: 4px 8px !important; border-radius: 4px !important; } .beautify-button-group { display: flex !important; gap: 10px !important; margin-top: 20px !important; padding-top: 15px !important; border-top: 1px solid rgba(0, 0, 0, 0.1) !important; } .beautify-btn { flex: 1 !important; padding: 10px 16px !important; border: none !important; border-radius: 8px !important; font-size: 13px !important; font-weight: 500 !important; cursor: pointer !important; transition: all 0.2s ease !important; } .beautify-btn-primary { background: #4CAF50 !important; color: white !important; } .beautify-btn-primary:hover { background: #45a049 !important; transform: translateY(-1px) !important; } .beautify-btn-secondary { background: #f5f5f5 !important; color: #666 !important; } .beautify-btn-secondary:hover { background: #e5e5e5 !important; transform: translateY(-1px) !important; } /* 搜索框控制按钮 */ .beautify-control-buttons { display: flex !important; gap: 5px !important; } .beautify-control-btn { padding: 8px 12px !important; background: rgba(255, 255, 255, 0.9) !important; border: 1px solid rgba(0, 0, 0, 0.1) !important; border-radius: 6px !important; font-size: 12px !important; cursor: pointer !important; transition: all 0.2s ease !important; color: #444 !important; backdrop-filter: blur(10px) !important; } .beautify-control-btn:hover { background: rgba(255, 255, 255, 1) !important; transform: translateY(-1px) !important; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1) !important; } .beautify-control-btn.active { background: #4CAF50 !important; color: white !important; } /* 透明化处理 */ .pagereco_CB, .pagereco_CBImageCard, .pagereco_CBTextCard, #tabcontrol_8_156412_navr, #lMapContainer, .b_algoSlug .algoSlug_icon, #b_PagAboveFooter, .tta_incell, .tta_outcell, #tta_output_ta { background: transparent !important; border: none !important; } /* Header 统一背景 */ #b_header { border-bottom: none !important; background: transparent !important; } .b_searchboxForm { background: rgba(255, 255, 255, 0.8) !important; backdrop-filter: blur(10px) !important; border-radius: 25px !important; } /* 结果框样式 */ .b_algo, .b_ans { border-radius: 10px !important; backdrop-filter: blur(${this.config.get('blurLevel')}px) !important; margin-bottom: 15px !important; transition: all 0.3s ease !important; } .b_algo:hover, .b_ans:hover { transform: translateY(-2px) !important; box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1) !important; } /* 文件上传样式 */ .beautify-file-upload { position: relative !important; display: inline-block !important; width: 100% !important; } .beautify-file-input { position: absolute !important; opacity: 0 !important; width: 100% !important; height: 100% !important; cursor: pointer !important; } .beautify-file-label { display: block !important; padding: 12px 15px !important; border: 2px dashed rgba(0, 0, 0, 0.2) !important; border-radius: 8px !important; text-align: center !important; cursor: pointer !important; transition: all 0.2s ease !important; background: rgba(0, 0, 0, 0.02) !important; font-size: 13px !important; } .beautify-file-label:hover { border-color: #4CAF50 !important; background: rgba(76, 175, 80, 0.05) !important; } /* 遮罩层 */ .beautify-overlay { position: fixed !important; top: 0 !important; left: 0 !important; width: 100vw !important; height: 100vh !important; background: rgba(0, 0, 0, 0.3) !important; z-index: 999999998 !important; display: none !important; } .beautify-overlay.show { display: block !important; } `; this.injectCSS('global-styles', css); } injectCSS(id, css) { if (this.injectedStyles.has(id)) return; const style = document.createElement('style'); style.id = id; style.textContent = css; (document.head || document.documentElement).appendChild(style); this.injectedStyles.add(id); } updateResultBoxStyles() { const opacity1 = this.config.get('opacity1'); const opacity2 = this.config.get('opacity2'); const opacity = this.config.get('backgroundType') === 'image' ? opacity2 : opacity1; const alpha = opacity / 100; const elements = document.querySelectorAll('.b_algo, .b_ans'); elements.forEach(el => { el.style.backgroundColor = `rgba(255, 255, 255, ${alpha})`; el.style.backdropFilter = `blur(${this.config.get('blurLevel')}px)`; }); } updateSearchboxStyle() { const searchboxForm = document.querySelector('.b_searchboxForm'); if (!searchboxForm) return; const style = this.config.get('searchboxStyle'); const opacity2 = this.config.get('opacity2') / 100; if (style === 'transparent') { searchboxForm.style.background = `rgba(255, 255, 255, ${opacity2})`; searchboxForm.style.backgroundImage = 'none'; } else if (style === 'colorful') { searchboxForm.style.backgroundImage = 'linear-gradient(to right, rgb(255, 221, 238), skyblue)'; searchboxForm.style.background = 'none'; } } async updateHeaderBackground() { // 统一header背景 const header = document.getElementById('b_header'); if (!header) return; const backgroundType = this.config.get('backgroundType'); let backgroundImage = ''; switch (backgroundType) { case 'gradient': backgroundImage = 'linear-gradient(to right, #FFDDEE, skyblue)'; break; case 'image': const imageUrl = this.config.get('backgroundImage'); const cachedImage = await this.config.convertImageToBase64(imageUrl); backgroundImage = `url(${cachedImage || imageUrl})`; break; case 'custom': const base64Image = this.config.get('customImageBase64'); if (base64Image) { backgroundImage = `url(${base64Image})`; } else { backgroundImage = 'linear-gradient(to right, #FFDDEE, skyblue)'; } break; } header.style.background = backgroundImage; header.style.backgroundSize = 'cover'; header.style.backgroundPosition = 'center'; header.style.backgroundAttachment = 'fixed'; } } // ==================== 背景管理模块 ==================== class BackgroundManager { constructor(config) { this.config = config; } async applyBackground() { const container = document.getElementById('b_content') || document.documentElement; const backgroundType = this.config.get('backgroundType'); // 清除之前的背景设置 container.style.backgroundImage = ''; container.style.backgroundColor = ''; document.documentElement.style.background = ''; switch (backgroundType) { case 'gradient': this.applyGradientBackground(container); break; case 'image': await this.applyImageBackground(container); break; case 'custom': this.applyCustomBackground(container); break; } } applyGradientBackground(container) { container.style.backgroundImage = 'linear-gradient(to right, #FFDDEE, skyblue)'; container.style.backgroundAttachment = 'fixed'; } async applyImageBackground(container) { const imageUrl = this.config.get('backgroundImage'); // 尝试使用缓存的base64图片 const cachedImage = await this.config.convertImageToBase64(imageUrl); if (cachedImage) { container.style.background = `url(${cachedImage}) center/cover fixed`; } else { // 回退到直接URL container.style.background = `url(${imageUrl}) center/cover fixed`; } } applyCustomBackground(container) { const base64Image = this.config.get('customImageBase64'); if (base64Image) { container.style.background = `url(${base64Image}) center/cover fixed`; } else { this.applyGradientBackground(container); // 回退到渐变色 } } // 为百度设置特殊背景 applyBaiduBackground() { const wrapper = document.getElementById('wrapper_wrapper'); if (wrapper) { const imageUrl = this.config.get('backgroundImage'); wrapper.style.background = `url(${imageUrl}) center/cover fixed`; } } } // ==================== UI管理模块 ==================== class UIManager { constructor(config, styleManager, backgroundManager) { this.config = config; this.styleManager = styleManager; this.backgroundManager = backgroundManager; this.settingsPanel = null; this.overlay = null; this.dragData = { isDragging: false, offset: { x: 0, y: 0 } }; } createControlButtons() { const scopebar = document.querySelector('.b_scopebar'); if (!scopebar || !scopebar.children[0]) return; const controlContainer = document.createElement('div'); controlContainer.className = 'beautify-control-buttons'; const buttons = [ { text: '透明', action: () => this.setSearchboxStyle('transparent') }, { text: '炫彩', action: () => this.setSearchboxStyle('colorful') }, { text: '渐变', action: () => this.setBackgroundType('gradient') }, { text: '图片', action: () => this.setBackgroundType('image') } ]; buttons.forEach(btn => { const button = document.createElement('button'); button.className = 'beautify-control-btn'; button.textContent = btn.text; button.addEventListener('click', btn.action); controlContainer.appendChild(button); }); // 设置按钮 const settingsBtn = document.createElement('button'); settingsBtn.className = 'beautify-control-btn'; settingsBtn.textContent = '⚙️'; settingsBtn.style.fontSize = '16px'; settingsBtn.addEventListener('click', () => { console.log('设置按钮被点击了'); this.showSettingsPanel(); }); const headerNav = document.getElementById('id_h'); if (headerNav) { headerNav.appendChild(settingsBtn); } else { // 如果找不到id_h,就添加到控制按钮组里 controlContainer.appendChild(settingsBtn); } scopebar.children[0].appendChild(controlContainer); } async setSearchboxStyle(style) { this.config.set('searchboxStyle', style); this.styleManager.updateSearchboxStyle(); this.updateActiveButtons(); } async setBackgroundType(type) { this.config.set('backgroundType', type); await this.backgroundManager.applyBackground(); await this.styleManager.updateHeaderBackground(); this.styleManager.updateResultBoxStyles(); this.updateActiveButtons(); } updateActiveButtons() { // 更新按钮激活状态 document.querySelectorAll('.beautify-control-btn').forEach(btn => { btn.classList.remove('active'); }); const searchboxStyle = this.config.get('searchboxStyle'); const backgroundType = this.config.get('backgroundType'); document.querySelectorAll('.beautify-control-btn').forEach(btn => { if ((btn.textContent === '透明' && searchboxStyle === 'transparent') || (btn.textContent === '炫彩' && searchboxStyle === 'colorful') || (btn.textContent === '渐变' && backgroundType === 'gradient') || (btn.textContent === '图片' && backgroundType === 'image')) { btn.classList.add('active'); } }); } createOverlay() { if (this.overlay) return; this.overlay = document.createElement('div'); this.overlay.className = 'beautify-overlay'; this.overlay.addEventListener('click', () => this.hideSettingsPanel()); document.body.appendChild(this.overlay); } createSettingsPanel() { console.log('创建设置面板'); if (this.settingsPanel) { console.log('设置面板已存在'); return; } // 创建遮罩层 this.createOverlay(); const panel = document.createElement('div'); panel.className = 'beautify-settings'; panel.innerHTML = `