Tools 是非官方社区Wiki。社区文档正在编写中,欢迎参与。 Wiki编辑答疑群:717421103
版本250923.2
全站通知:

全站公告

阅读

    

2025-10-23更新

    

最新编辑:

阅读:

  

更新日期:2025-10-23

  

最新编辑:

来自WIKI实验室WIKI_BWIKI_哔哩哔哩
跳到导航 跳到搜索
页面贡献者 :
sizau
空之云间

一个灵活、可配置、可持久化的站点公告系统脚本。

依赖

  • Bootstrap 3:提供基础样式(BWIKI 已默认加载)。
  • Font Awesome 6:用于显示标题前的图标。如果页面未引入,脚本会尝试从 `mirrors.sustech.edu.cn` CDN 自动加载。

使用

  1. 将下面的 JavaScript 代码添加到站点的全局脚本文件中(例如添加到 MediaWiki:Common.js)。
  2. 在代码底部的 `onDOMReady` 函数中,根据需求配置一个或多个公告对象。

一个最基本的使用示例如下:

onDOMReady(function () {
    var dbHelper = new IndexedDBHelper();

    // 配置一个简单的右下角浮窗公告
    var myNoticeConfig = {
        id: 'welcome_notice_2025',
        title: '欢迎访问本站',
        content: '这是一个简单的站点公告。',
        // 其他配置项可省略,将使用默认值
    };

    // 初始化公告
    new SiteAnnouncer(myNoticeConfig, dbHelper).init();
});

配置

所有配置都在一个 JavaScript 对象中定义。

公告配置对象属性
参数 类型 是否必需 默认值 说明
id String 公告的唯一标识符。用于在 IndexedDB 中存储关闭状态,每个公告必须不同
title String 公告的标题。
content String 公告的主体内容,支持 HTML 标签。
linkUrl String 在公告内容后附加的链接 URL。提供此项后会显示一个可点击链接。
linkText String '查看详情' `linkUrl` 对应链接的显示文本。
displayMode String 'float' 显示模式。'float' 为浮窗模式,'inline' 为内联模式。
floatPosition String 'bottom-right' 当 `displayMode` 为 'float' 时生效。可选值:'bottom-left', 'bottom-center', 'bottom-right'
containerSelector String 内联模式必需 当 `displayMode` 为 'inline' 时,指定公告要插入的目标元素的 CSS 选择器(如 '#siteNotice')。
insertionMethod String 'prepend' 当 `displayMode` 为 'inline' 时生效。决定插入方式:'prepend'(内部最前)、'append'(内部最后)、'before'(元素之前)、'after'(元素之后)。
startDate String 公告开始显示的日期(格式:YYYY-MM-DD)。如果省略,则立即生效。
endDate String 公告结束显示的日期(格式:YYYY-MM-DD)。如果省略,则永不过期。
type String 'info' 公告的 Bootstrap 样式类型。可选值:'info', 'success', 'warning', 'danger'
iconClass String 'fa-solid fa-bell' 标题前的 Font Awesome 图标 class。

代码

如果通过 Mediawiki:Common.js 进行加载和使用,必须使用 ES5 兼容的版本;可以通过 Widget 使用但是实践中不建议这样做。

一个比较常见的实践是通过 Mediawiki:Common.js 或 Widget + Mediawiki:Sitenotice 加载其他脚本文件,例如 MediaWiki:Site.js,然后将脚本放置到 MediaWiki:Site.js 里来规避 ES5 语法校验。

主功能 
window.safeOperation = window.safeOperation || function (fn, ...args) {
    try {
        return typeof fn === 'function' ? fn(...args) : null;
    } catch (e) {
        console.error(`${fn.name || 'Anonymous function'} 执行失败: ${e}`);
        return null;
    }
};

window.onDOMReady = window.onDOMReady || function (callback) {
    safeOperation(() => {
        const execute = () => safeOperation(callback);
        document.readyState === "loading"
            ? document.addEventListener("DOMContentLoaded", execute)
            : execute();
    });
};

class IndexedDBHelper {
    constructor(dbName = 'siteDataDB', storeName = 'userState') {
        this.dbName = dbName;
        this.storeName = storeName;
        this.db = null;
    }
    async _getDB() { if (this.db) return this.db; return new Promise((resolve, reject) => { const request = indexedDB.open(this.dbName, 1); request.onupgradeneeded = (event) => { const db = event.target.result; if (!db.objectStoreNames.contains(this.storeName)) { db.createObjectStore(this.storeName); } }; request.onsuccess = (event) => { this.db = event.target.result; resolve(this.db); }; request.onerror = (event) => { console.error('IndexedDB error:', event.target.errorCode); reject(event.target.errorCode); }; }); }
    async set(key, value) { const db = await this._getDB(); return new Promise((resolve, reject) => { const transaction = db.transaction([this.storeName], 'readwrite'); const store = transaction.objectStore(this.storeName); transaction.oncomplete = () => resolve(); transaction.onerror = (event) => reject(event.target.error); store.put(value, key); }); }
    async get(key) { const db = await this._getDB(); return new Promise((resolve, reject) => { const transaction = db.transaction([this.storeName], 'readonly'); const store = transaction.objectStore(this.storeName); const request = store.get(key); request.onsuccess = () => resolve(request.result); request.onerror = (event) => reject(event.target.error); }); }
}


class SiteAnnouncer {
    /**
     * @param {object} config 配置对象
     * @param {string} config.id 公告的唯一ID
     * @param {string} config.title 公告标题
     * @param {string} config.content 公告内容
     * @param {string} [config.linkUrl] 公告中可点击的链接
     * @param {string} [config.linkText='查看详情'] 链接的显示文字
     * @param {string} [config.displayMode='float'] 显示模式: 'float' 或 'inline'.
     * @param {string} [config.floatPosition='bottom-right'] 浮窗位置(displayMode为float时生效). 可选值: 'bottom-left', 'bottom-center', 'bottom-right'.
     * @param {string} [config.containerSelector] 当 displayMode 为 'inline' 时,必须提供此项作为目标元素.
     * @param {string} [config.insertionMethod='prepend'] 当 displayMode 为 'inline' 时生效。可选值: 'prepend', 'append', 'after', 'before'.
     * @param {string} [config.startDate] 起始日期 (YYYY-MM-DD)
     * @param {string} [config.endDate] 结束日期 (YYYY-MM-DD)
     * @param {string} [config.type='info'] Bootstrap 3 警告框类型 (info, success, warning, danger)
     * @param {string} [config.iconClass='fa-solid fa-bell'] 标题前的图标
     * @param {IndexedDBHelper} dbHelper IndexedDB帮助类实例
     */
    constructor(config, dbHelper) {
        this.config = {
            linkText: '查看详情',
            displayMode: 'float',
            floatPosition: 'bottom-right',
            insertionMethod: 'prepend',
            type: 'info',
            iconClass: 'fa-solid fa-bell',
            ...config
        };
        this.dbHelper = dbHelper;
        this.noticeElement = null;
        if (!this.config.id || !this.dbHelper) throw new Error('必须提供 id 和 dbHelper。');
        if (this.config.displayMode === 'inline' && !this.config.containerSelector) {
            throw new Error("当 displayMode 为 'inline' 时, 必须提供 containerSelector。");
        }
    }

    _isDateInRange() {
        const { startDate, endDate } = this.config;
        if (!startDate) return true;
        const today = new Date();
        today.setHours(0, 0, 0, 0);
        const start = new Date(startDate);
        start.setMinutes(start.getMinutes() + start.getTimezoneOffset());
        if (today < start) return false;
        if (endDate) {
            const end = new Date(endDate);
            end.setMinutes(end.getMinutes() + end.getTimezoneOffset());
            if (today > end) return false;
        }
        return true;
    }

    _injectFloatStyles() {
        if (document.getElementById('site-announcer-styles')) return;
        const style = document.createElement('style');
        style.id = 'site-announcer-styles';
        style.innerHTML = `
            .site-announcer-float {
                position: fixed; bottom: 20px; z-index: 1050; max-width: 400px;
                box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
            }
            .site-announcer-float--bottom-left { left: 20px; }
            .site-announcer-float--bottom-right { right: 20px; }
            .site-announcer-float--bottom-center { left: 50%; transform: translateX(-50%); }
            @media (max-width: 767px) {
                .site-announcer-float {
                    left: 15px !important; right: 15px !important; bottom: 15px;
                    max-width: none; transform: none !important;
                }
            }`;
        document.head.appendChild(style);
    }
    
    _createNoticeElement() {
        const { title, content, type, iconClass, displayMode, floatPosition, linkUrl, linkText } = this.config;
        const notice = document.createElement('div');
        let classNames = `alert alert-${type} alert-dismissible`;
        if (displayMode === 'float') {
            classNames += ` site-announcer-float site-announcer-float--${floatPosition}`;
            this._injectFloatStyles();
        }
        notice.className = classNames;
        notice.setAttribute('role', 'alert');
        let contentHTML = content;
        if (linkUrl) {
            const linkHTML = ` <a href="${linkUrl}" target="_blank" rel="noopener noreferrer" class="alert-link site-announcer-link">${linkText}</a>`;
            contentHTML += linkHTML;
        }
        notice.innerHTML = `
            <button type="button" class="close" data-dismiss="alert" aria-label="Close">
                <span aria-hidden="true">&times;</span>
            </button>
            <h4><i class="${iconClass}" style="margin-right: 9px;"></i>${title}</h4>
            <p>${contentHTML}</p>
        `;
        this.noticeElement = notice;
        this._attachEventListeners();
    }
    
    _attachEventListeners() {
        if (window.jQuery) {
            $(this.noticeElement).on('close.bs.alert', () => this._markAsDismissed());
        } else {
            const closeButton = this.noticeElement.querySelector('.close');
            if(closeButton) {
                closeButton.addEventListener('click', () => {
                    this._markAsDismissed();
                    this.noticeElement.style.display = 'none';
                });
            }
        }
        const linkElement = this.noticeElement.querySelector('.site-announcer-link');
        if (linkElement) {
            linkElement.addEventListener('click', () => {
                this._markAsDismissed();
                if (window.jQuery) {
                    $(this.noticeElement).alert('close');
                } else {
                    this.noticeElement.style.display = 'none';
                }
            });
        }
    }

    _markAsDismissed() {
        if (this.isDismissed) return;
        this.isDismissed = true;
        this.dbHelper.set(this.config.id, true).then(() => {
            console.log(`公告 "${this.config.id}" 已被关闭并记录。`);
        });
    }

    static _loadFontAwesomeIfNeeded() {
        if (SiteAnnouncer._faChecked) return;
        SiteAnnouncer._faChecked = true;
        const testIcon = document.createElement('i');
        testIcon.className = 'fa-solid';
        testIcon.style.cssText = 'position:absolute; top:-9999px; left:-9999px; opacity:0; pointer-events:none;';
        document.body.appendChild(testIcon);
        const fontFamily = window.getComputedStyle(testIcon, '::before').getPropertyValue('font-family');
        document.body.removeChild(testIcon);
        if (!fontFamily || !fontFamily.toLowerCase().includes('font awesome')) {
            console.log('Font Awesome not detected. Loading dynamically...');
            const link = document.createElement('link');
            link.rel = 'stylesheet';
            link.href = 'https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/font-awesome/6.0.0/css/all.min.css';
            document.head.appendChild(link);
        }
    }

    async init() {
        SiteAnnouncer._loadFontAwesomeIfNeeded();
        if (!this._isDateInRange()) return;
        const isDismissed = await this.dbHelper.get(this.config.id);
        if (isDismissed) return;
        this._createNoticeElement();
        if (this.config.displayMode === 'inline') {
            const container = document.querySelector(this.config.containerSelector);
            if (container) {
                switch (this.config.insertionMethod) {
                    case 'append': container.append(this.noticeElement); break;
                    case 'after': container.after(this.noticeElement); break;
                    case 'before': container.before(this.noticeElement); break;
                    default: container.prepend(this.noticeElement); break;
                }
            } else {
                console.error(`容器 "${this.config.containerSelector}" 未找到。`);
            }
        } else {
            document.body.appendChild(this.noticeElement);
        }
    }
}
SiteAnnouncer._faChecked = false;
主功能(ES5 兼容版本) 
window.safeOperation = window.safeOperation || function (fn) {
    try {
        if (typeof fn === 'function') {
            var args = Array.prototype.slice.call(arguments, 1);
            return fn.apply(null, args);
        }
        return null;
    } catch (e) {
        console.error((fn.name || 'Anonymous function') + ' 执行失败: ' + e);
        return null;
    }
};

window.onDOMReady = window.onDOMReady || function (callback) {
    safeOperation(function () {
        var execute = function () { safeOperation(callback); };
        if (document.readyState === "loading") {
            document.addEventListener("DOMContentLoaded", execute);
        } else {
            execute();
        }
    });
};

function IndexedDBHelper(dbName, storeName) {
    this.dbName = dbName === undefined ? 'siteDataDB' : dbName;
    this.storeName = storeName === undefined ? 'userState' : storeName;
    this.db = null;
}

IndexedDBHelper.prototype._getDB = function () {
    var self = this;
    if (self.db) {
        return Promise.resolve(self.db);
    }
    return new Promise(function (resolve, reject) {
        var request = indexedDB.open(self.dbName, 1);
        request.onupgradeneeded = function (event) {
            var db = event.target.result;
            if (!db.objectStoreNames.contains(self.storeName)) {
                db.createObjectStore(self.storeName);
            }
        };
        request.onsuccess = function (event) {
            self.db = event.target.result;
            resolve(self.db);
        };
        request.onerror = function (event) {
            console.error('IndexedDB error:', event.target.errorCode);
            reject(event.target.errorCode);
        };
    });
};

IndexedDBHelper.prototype.set = function (key, value) {
    var self = this;
    return self._getDB().then(function (db) {
        return new Promise(function (resolve, reject) {
            var transaction = db.transaction([self.storeName], 'readwrite');
            var store = transaction.objectStore(self.storeName);
            transaction.oncomplete = function () { resolve(); };
            transaction.onerror = function (event) { reject(event.target.error); };
            store.put(value, key);
        });
    });
};

IndexedDBHelper.prototype.get = function (key) {
    var self = this;
    return self._getDB().then(function (db) {
        return new Promise(function (resolve, reject) {
            var transaction = db.transaction([self.storeName], 'readonly');
            var store = transaction.objectStore(self.storeName);
            var request = store.get(key);
            request.onsuccess = function () { resolve(request.result); };
            request.onerror = function (event) { reject(event.target.error); };
        });
    });
};


function SiteAnnouncer(config, dbHelper) {
    /**
     * @param {object} config 配置对象
     * @param {string} config.id 公告的唯一ID
     * @param {string} config.title 公告标题
     * @param {string} config.content 公告内容
     * @param {string} [config.linkUrl] 公告中可点击的链接
     * @param {string} [config.linkText='查看详情'] 链接的显示文字
     * @param {string} [config.displayMode='float'] 显示模式: 'float' 或 'inline'.
     * @param {string} [config.floatPosition='bottom-right'] 浮窗位置(displayMode为float时生效). 可选值: 'bottom-left', 'bottom-center', 'bottom-right'.
     * @param {string} [config.containerSelector] 当 displayMode 为 'inline' 时,必须提供此项作为目标元素.
     * @param {string} [config.insertionMethod='prepend'] 当 displayMode 为 'inline' 时生效。可选值: 'prepend', 'append', 'after', 'before'.
     * @param {string} [config.startDate] 起始日期 (YYYY-MM-DD)
     * @param {string} [config.endDate] 结束日期 (YYYY-MM-DD)
     * @param {string} [config.type='info'] Bootstrap 3 警告框类型 (info, success, warning, danger)
     * @param {string} [config.iconClass='fa-solid fa-bell'] 标题前的图标
     * @param {IndexedDBHelper} dbHelper IndexedDB帮助类实例
     */
    var defaults = {
        linkText: '查看详情',
        displayMode: 'float',
        floatPosition: 'bottom-right',
        insertionMethod: 'prepend',
        type: 'info',
        iconClass: 'fa-solid fa-bell'
    };
    this.config = {};
    for (var key in defaults) {
        if (defaults.hasOwnProperty(key)) {
            this.config[key] = defaults[key];
        }
    }
    for (var key in config) {
        if (config.hasOwnProperty(key)) {
            this.config[key] = config[key];
        }
    }
    this.dbHelper = dbHelper;
    this.noticeElement = null;
    if (!this.config.id || !this.dbHelper) throw new Error('必须提供 id 和 dbHelper。');
    if (this.config.displayMode === 'inline' && !this.config.containerSelector) {
        throw new Error("当 displayMode 为 'inline' 时, 必须提供 containerSelector。");
    }
}

SiteAnnouncer.prototype._isDateInRange = function () {
    var config = this.config;
    if (!config.startDate) return true;
    var today = new Date();
    today.setHours(0, 0, 0, 0);
    var start = new Date(config.startDate);
    start.setMinutes(start.getMinutes() + start.getTimezoneOffset());
    if (today < start) return false;
    if (config.endDate) {
        var end = new Date(config.endDate);
        end.setMinutes(end.getMinutes() + end.getTimezoneOffset());
        if (today > end) return false;
    }
    return true;
};

SiteAnnouncer.prototype._injectFloatStyles = function () {
    if (document.getElementById('site-announcer-styles')) return;
    var style = document.createElement('style');
    style.id = 'site-announcer-styles';
    style.innerHTML = [
        '.site-announcer-float {',
        '    position: fixed; bottom: 20px; z-index: 1050; max-width: 400px;',
        '    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);',
        '}',
        '.site-announcer-float--bottom-left { left: 20px; }',
        '.site-announcer-float--bottom-right { right: 20px; }',
        '.site-announcer-float--bottom-center { left: 50%; transform: translateX(-50%); }',
        '@media (max-width: 767px) {',
        '    .site-announcer-float {',
        '        left: 15px !important; right: 15px !important; bottom: 15px;',
        '        max-width: none; transform: none !important;',
        '    }',
        '}'
    ].join('');
    document.head.appendChild(style);
};

SiteAnnouncer.prototype._createNoticeElement = function () {
    var config = this.config;
    var notice = document.createElement('div');
    var classNames = 'alert alert-' + config.type + ' alert-dismissible';
    if (config.displayMode === 'float') {
        classNames += ' site-announcer-float site-announcer-float--' + config.floatPosition;
        this._injectFloatStyles();
    }
    notice.className = classNames;
    notice.setAttribute('role', 'alert');
    var contentHTML = config.content;
    if (config.linkUrl) {
        var linkHTML = ' <a href="' + config.linkUrl + '" target="_blank" rel="noopener noreferrer" class="alert-link site-announcer-link">' + config.linkText + '</a>';
        contentHTML += linkHTML;
    }
    notice.innerHTML = [
        '<button type="button" class="close" data-dismiss="alert" aria-label="Close">',
        '    <span aria-hidden="true">&times;</span>',
        '</button>',
        '<h4><i class="' + config.iconClass + '" style="margin-right: 9px;"></i>' + config.title + '</h4>',
        '<p>' + contentHTML + '</p>'
    ].join('');
    this.noticeElement = notice;
    this._attachEventListeners();
};

SiteAnnouncer.prototype._attachEventListeners = function () {
    var self = this;
    if (window.jQuery) {
        $(this.noticeElement).on('close.bs.alert', function () { self._markAsDismissed(); });
    } else {
        var closeButton = this.noticeElement.querySelector('.close');
        if (closeButton) {
            closeButton.addEventListener('click', function () {
                self._markAsDismissed();
                self.noticeElement.style.display = 'none';
            });
        }
    }
    var linkElement = this.noticeElement.querySelector('.site-announcer-link');
    if (linkElement) {
        linkElement.addEventListener('click', function () {
            self._markAsDismissed();
            if (window.jQuery) {
                $(self.noticeElement).alert('close');
            } else {
                self.noticeElement.style.display = 'none';
            }
        });
    }
};

SiteAnnouncer.prototype._markAsDismissed = function () {
    if (this.isDismissed) return;
    this.isDismissed = true;
    this.dbHelper.set(this.config.id, true).then(function () {
        console.log('公告 "' + this.config.id + '" 已被关闭并记录。');
    }.bind(this));
};

SiteAnnouncer._loadFontAwesomeIfNeeded = function () {
    if (SiteAnnouncer._faChecked) return;
    SiteAnnouncer._faChecked = true;
    var testIcon = document.createElement('i');
    testIcon.className = 'fa-solid';
    testIcon.style.cssText = 'position:absolute; top:-9999px; left:-9999px; opacity:0; pointer-events:none;';
    document.body.appendChild(testIcon);
    var fontFamily = window.getComputedStyle(testIcon, '::before').getPropertyValue('font-family');
    document.body.removeChild(testIcon);
    if (!fontFamily || !fontFamily.toLowerCase().includes('font awesome')) {
        console.log('Font Awesome not detected. Loading dynamically...');
        var link = document.createElement('link');
        link.rel = 'stylesheet';
        link.href = 'https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/font-awesome/6.0.0/css/all.min.css';
        document.head.appendChild(link);
    }
};

SiteAnnouncer.prototype.init = function () {
    var self = this;
    SiteAnnouncer._loadFontAwesomeIfNeeded();
    if (!self._isDateInRange()) return;
    self.dbHelper.get(self.config.id).then(function (isDismissed) {
        if (isDismissed) return;
        self._createNoticeElement();
        if (self.config.displayMode === 'inline') {
            var container = document.querySelector(self.config.containerSelector);
            if (container) {
                switch (self.config.insertionMethod) {
                    case 'append': container.appendChild(self.noticeElement); break;
                    case 'after': container.parentNode.insertBefore(self.noticeElement, container.nextSibling); break;
                    case 'before': container.parentNode.insertBefore(self.noticeElement, container); break;
                    default: container.insertBefore(self.noticeElement, container.firstChild); break;
                }
            } else {
                console.error('容器 "' + self.config.containerSelector + '" 未找到。');
            }
        } else {
            document.body.appendChild(self.noticeElement);
        }
    });
};
SiteAnnouncer._faChecked = false;
用法示例 
onDOMReady(() => {

    const dbHelper = new IndexedDBHelper();

    // 示例 1: 在页面顶部 #siteNotice 后以内联形式显示的成功公告
    const topPageNotice = {
        id: 'update_notice_20251019',
        title: '服务升级完成',
        content: '网站核心服务已于今日凌晨完成升级,性能提升 20%。',
        displayMode: 'inline',
        containerSelector: '#siteNotice',
        insertionMethod: 'after',
        type: 'success',
        iconClass: 'fa-solid fa-circle-check'
    };

    // 示例 2: 左下角浮窗,带链接,指定起止日期
    const promoNotice = {
        id: 'promo_winter_2025',
        title: 'Steam 冬季促销',
        content: 'Steam 冬季促销正在进行中,游戏限时折扣!',
        linkUrl: '#',
        linkText: '前往商店',
        displayMode: 'float',
        floatPosition: 'bottom-left',
        startDate: '2025-10-01',
        endDate: '2028-10-31',
        type: 'info',
        iconClass: 'fa-solid fa-gift'
    };

    // 示例 3: 底部居中浮窗
    const privacyNotice = {
        id: 'privacy_policy_update_2025',
        title: '隐私政策更新提醒',
        content: '我们更新了隐私政策条款,以符合最新的数据保护法规。',
        displayMode: 'float',
        floatPosition: 'bottom-center',
        type: 'warning',
        iconClass: 'fa-solid fa-shield-halved'
    };

    // 示例 4: 一个已过期的公告,此公告将不会显示
    const expiredNotice = {
        id: 'expired_event_2024',
        title: '已结束的活动',
        content: '此活动已于去年结束。',
        startDate: '2024-01-01',
        endDate: '2024-01-31'
    };
    
    // 示例 5: 在主内容区末尾追加的危险警告
    const dangerNotice = {
        id: 'api_deprecation_warning',
        title: '重要:API 弃用警告',
        content: 'V1 版本的 API 将于下月起停止支持,请尽快迁移至 V2 版本。',
        displayMode: 'inline',
        containerSelector: '.game-bg.container',
        insertionMethod: 'append',
        type: 'danger',
        iconClass: 'fa-solid fa-triangle-exclamation'
    };


    // --- 初始化所有公告 ---
    new SiteAnnouncer(topPageNotice, dbHelper).init();
    new SiteAnnouncer(promoNotice, dbHelper).init();
    new SiteAnnouncer(privacyNotice, dbHelper).init();
    new SiteAnnouncer(expiredNotice, dbHelper).init();
    new SiteAnnouncer(dangerNotice, dbHelper).init();
});
用法示例(ES5 兼容版本) 
onDOMReady(function () {

    var dbHelper = new IndexedDBHelper();

    // 示例 1: 在页面顶部 #siteNotice 后以内联形式显示的成功公告
    var topPageNotice = {
        id: 'update_notice_20251019',
        title: '服务升级完成',
        content: '网站核心服务已于今日凌晨完成升级,性能提升 20%。',
        displayMode: 'inline',
        containerSelector: '#siteNotice',
        insertionMethod: 'after',
        type: 'success',
        iconClass: 'fa-solid fa-circle-check'
    };

    // 示例 2: 左下角浮窗,带链接,指定起止日期
    var promoNotice = {
        id: 'promo_winter_2025',
        title: 'Steam 冬季促销',
        content: 'Steam 冬季促销正在进行中,游戏限时折扣!',
        linkUrl: '#',
        linkText: '前往商店',
        displayMode: 'float',
        floatPosition: 'bottom-left',
        startDate: '2025-10-01',
        endDate: '2028-10-31',
        type: 'info',
        iconClass: 'fa-solid fa-gift'
    };

    // 示例 3: 底部居中浮窗
    var privacyNotice = {
        id: 'privacy_policy_update_2025',
        title: '隐私政策更新提醒',
        content: '我们更新了隐私政策条款,以符合最新的数据保护法规。',
        displayMode: 'float',
        floatPosition: 'bottom-center',
        type: 'warning',
        iconClass: 'fa-solid fa-shield-halved'
    };
    
    // 示例 4: 一个已过期的公告,此公告将不会显示
    var expiredNotice = {
        id: 'expired_event_2024',
        title: '已结束的活动',
        content: '此活动已于去年结束。',
        startDate: '2024-01-01',
        endDate: '2024-01-31'
    };
    
    // 示例 5: 在主内容区末尾追加的危险警告
    var dangerNotice = {
        id: 'api_deprecation_warning',
        title: '重要:API 弃用警告',
        content: 'V1 版本的 API 将于下月起停止支持,请尽快迁移至 V2 版本。',
        displayMode: 'inline',
        containerSelector: '.game-bg.container',
        insertionMethod: 'append',
        type: 'danger',
        iconClass: 'fa-solid fa-triangle-exclamation'
    };

    new SiteAnnouncer(topPageNotice, dbHelper).init();
    new SiteAnnouncer(promoNotice, dbHelper).init();
    new SiteAnnouncer(privacyNotice, dbHelper).init();
    new SiteAnnouncer(expiredNotice, dbHelper).init();
    new SiteAnnouncer(dangerNotice, dbHelper).init();
});