本章概述

Bootstrap 提供了丰富的 JavaScript 插件,用于创建交互式的用户界面组件。这些插件基于现代 JavaScript 技术,提供了模态框、工具提示、弹出框、轮播图等功能。本章将深入学习如何使用和自定义这些插件。

学习目标

  • 掌握 Bootstrap JavaScript 插件的基本使用
  • 学会通过 JavaScript API 控制组件行为
  • 理解事件系统和回调函数
  • 掌握插件的配置和自定义
  • 学会创建自定义插件

JavaScript 插件基础

1. 插件引入和初始化

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Bootstrap JavaScript 插件</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <!-- 内容区域 -->
    <div class="container my-5">
        <h1>Bootstrap JavaScript 插件示例</h1>
        
        <!-- 模态框触发按钮 -->
        <button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#exampleModal">
            打开模态框
        </button>
        
        <!-- 工具提示按钮 -->
        <button type="button" class="btn btn-secondary" data-bs-toggle="tooltip" data-bs-placement="top" title="这是一个工具提示">
            悬停显示提示
        </button>
        
        <!-- 弹出框按钮 -->
        <button type="button" class="btn btn-info" data-bs-toggle="popover" data-bs-placement="right" data-bs-content="这是弹出框的内容" title="弹出框标题">
            点击显示弹出框
        </button>
    </div>
    
    <!-- 模态框 -->
    <div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title" id="exampleModalLabel">模态框标题</h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                </div>
                <div class="modal-body">
                    这是模态框的内容区域。
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
                    <button type="button" class="btn btn-primary">保存更改</button>
                </div>
            </div>
        </div>
    </div>

    <!-- Bootstrap JavaScript -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
    
    <script>
        // 初始化工具提示
        const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
        const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));
        
        // 初始化弹出框
        const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]');
        const popoverList = [...popoverTriggerList].map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl));
    </script>
</body>
</html>

2. 通过 JavaScript API 控制组件

// 模态框 API 示例
class ModalController {
    constructor() {
        this.modal = null;
        this.init();
    }
    
    init() {
        // 获取模态框元素
        const modalElement = document.getElementById('exampleModal');
        
        // 创建模态框实例
        this.modal = new bootstrap.Modal(modalElement, {
            backdrop: 'static', // 静态背景,点击背景不关闭
            keyboard: false     // 禁用 ESC 键关闭
        });
        
        // 绑定事件监听器
        this.bindEvents();
    }
    
    bindEvents() {
        // 监听模态框显示事件
        document.getElementById('exampleModal').addEventListener('show.bs.modal', (event) => {
            console.log('模态框即将显示');
        });
        
        // 监听模态框显示完成事件
        document.getElementById('exampleModal').addEventListener('shown.bs.modal', (event) => {
            console.log('模态框已显示');
            // 自动聚焦到第一个输入框
            const firstInput = event.target.querySelector('input');
            if (firstInput) {
                firstInput.focus();
            }
        });
        
        // 监听模态框隐藏事件
        document.getElementById('exampleModal').addEventListener('hide.bs.modal', (event) => {
            console.log('模态框即将隐藏');
        });
        
        // 监听模态框隐藏完成事件
        document.getElementById('exampleModal').addEventListener('hidden.bs.modal', (event) => {
            console.log('模态框已隐藏');
            // 清理表单数据
            const form = event.target.querySelector('form');
            if (form) {
                form.reset();
            }
        });
    }
    
    // 显示模态框
    show() {
        this.modal.show();
    }
    
    // 隐藏模态框
    hide() {
        this.modal.hide();
    }
    
    // 切换模态框显示状态
    toggle() {
        this.modal.toggle();
    }
    
    // 销毁模态框实例
    dispose() {
        this.modal.dispose();
    }
}

// 工具提示 API 示例
class TooltipController {
    constructor() {
        this.tooltips = new Map();
        this.init();
    }
    
    init() {
        // 初始化所有工具提示
        const tooltipElements = document.querySelectorAll('[data-bs-toggle="tooltip"]');
        
        tooltipElements.forEach(element => {
            const tooltip = new bootstrap.Tooltip(element, {
                delay: { show: 500, hide: 100 }, // 延迟显示和隐藏
                html: true,                       // 允许 HTML 内容
                placement: 'auto'                 // 自动定位
            });
            
            this.tooltips.set(element, tooltip);
        });
    }
    
    // 显示指定元素的工具提示
    show(element) {
        const tooltip = this.tooltips.get(element);
        if (tooltip) {
            tooltip.show();
        }
    }
    
    // 隐藏指定元素的工具提示
    hide(element) {
        const tooltip = this.tooltips.get(element);
        if (tooltip) {
            tooltip.hide();
        }
    }
    
    // 更新工具提示内容
    updateContent(element, newTitle) {
        const tooltip = this.tooltips.get(element);
        if (tooltip) {
            element.setAttribute('data-bs-original-title', newTitle);
            tooltip.setContent({ '.tooltip-inner': newTitle });
        }
    }
    
    // 销毁所有工具提示
    disposeAll() {
        this.tooltips.forEach(tooltip => {
            tooltip.dispose();
        });
        this.tooltips.clear();
    }
}

// 轮播图 API 示例
class CarouselController {
    constructor(carouselId) {
        this.carouselElement = document.getElementById(carouselId);
        this.carousel = new bootstrap.Carousel(this.carouselElement, {
            interval: 3000,  // 自动切换间隔
            wrap: true,      // 循环播放
            touch: true      // 触摸支持
        });
        
        this.bindEvents();
    }
    
    bindEvents() {
        // 监听轮播图滑动事件
        this.carouselElement.addEventListener('slide.bs.carousel', (event) => {
            console.log(`从第 ${event.from} 张切换到第 ${event.to} 张`);
        });
        
        // 监听轮播图滑动完成事件
        this.carouselElement.addEventListener('slid.bs.carousel', (event) => {
            console.log(`已切换到第 ${event.to} 张`);
            this.updateIndicators(event.to);
        });
    }
    
    // 切换到指定幻灯片
    to(index) {
        this.carousel.to(index);
    }
    
    // 切换到下一张
    next() {
        this.carousel.next();
    }
    
    // 切换到上一张
    prev() {
        this.carousel.prev();
    }
    
    // 暂停自动播放
    pause() {
        this.carousel.pause();
    }
    
    // 开始自动播放
    cycle() {
        this.carousel.cycle();
    }
    
    // 更新指示器
    updateIndicators(activeIndex) {
        const indicators = this.carouselElement.querySelectorAll('.carousel-indicators button');
        indicators.forEach((indicator, index) => {
            if (index === activeIndex) {
                indicator.classList.add('active');
                indicator.setAttribute('aria-current', 'true');
            } else {
                indicator.classList.remove('active');
                indicator.removeAttribute('aria-current');
            }
        });
    }
}

// 使用示例
document.addEventListener('DOMContentLoaded', function() {
    // 初始化控制器
    const modalController = new ModalController();
    const tooltipController = new TooltipController();
    
    // 绑定自定义按钮事件
    document.getElementById('showModalBtn')?.addEventListener('click', () => {
        modalController.show();
    });
    
    document.getElementById('hideModalBtn')?.addEventListener('click', () => {
        modalController.hide();
    });
});

高级插件应用

1. 动态内容模态框

<!-- 动态内容模态框 -->
<div class="container my-5">
    <h2>动态内容模态框</h2>
    
    <!-- 用户列表 -->
    <div class="row" id="userList">
        <div class="col-md-4 mb-3">
            <div class="card">
                <div class="card-body">
                    <h5 class="card-title">张三</h5>
                    <p class="card-text">前端开发工程师</p>
                    <button class="btn btn-primary" data-user-id="1" onclick="showUserDetails(this)">查看详情</button>
                </div>
            </div>
        </div>
        
        <div class="col-md-4 mb-3">
            <div class="card">
                <div class="card-body">
                    <h5 class="card-title">李四</h5>
                    <p class="card-text">后端开发工程师</p>
                    <button class="btn btn-primary" data-user-id="2" onclick="showUserDetails(this)">查看详情</button>
                </div>
            </div>
        </div>
        
        <div class="col-md-4 mb-3">
            <div class="card">
                <div class="card-body">
                    <h5 class="card-title">王五</h5>
                    <p class="card-text">UI/UX 设计师</p>
                    <button class="btn btn-primary" data-user-id="3" onclick="showUserDetails(this)">查看详情</button>
                </div>
            </div>
        </div>
    </div>
</div>

<!-- 动态模态框 -->
<div class="modal fade" id="userModal" tabindex="-1" aria-labelledby="userModalLabel" aria-hidden="true">
    <div class="modal-dialog modal-lg">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title" id="userModalLabel">用户详情</h5>
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
            </div>
            <div class="modal-body" id="userModalBody">
                <!-- 加载指示器 -->
                <div class="text-center" id="loadingIndicator">
                    <div class="spinner-border" role="status">
                        <span class="visually-hidden">加载中...</span>
                    </div>
                    <p class="mt-2">正在加载用户信息...</p>
                </div>
                
                <!-- 用户详情内容 -->
                <div id="userContent" style="display: none;">
                    <!-- 动态内容将在这里插入 -->
                </div>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
                <button type="button" class="btn btn-primary" id="editUserBtn">编辑用户</button>
            </div>
        </div>
    </div>
</div>

<script>
// 用户数据模拟
const userData = {
    1: {
        id: 1,
        name: '张三',
        position: '前端开发工程师',
        email: 'zhangsan@example.com',
        phone: '138-0000-0001',
        department: '技术部',
        joinDate: '2022-01-15',
        skills: ['JavaScript', 'Vue.js', 'React', 'CSS3', 'HTML5'],
        avatar: 'https://via.placeholder.com/150x150?text=张三'
    },
    2: {
        id: 2,
        name: '李四',
        position: '后端开发工程师',
        email: 'lisi@example.com',
        phone: '138-0000-0002',
        department: '技术部',
        joinDate: '2021-08-20',
        skills: ['Java', 'Spring Boot', 'MySQL', 'Redis', 'Docker'],
        avatar: 'https://via.placeholder.com/150x150?text=李四'
    },
    3: {
        id: 3,
        name: '王五',
        position: 'UI/UX 设计师',
        email: 'wangwu@example.com',
        phone: '138-0000-0003',
        department: '设计部',
        joinDate: '2023-03-10',
        skills: ['Figma', 'Sketch', 'Adobe XD', 'Photoshop', 'Illustrator'],
        avatar: 'https://via.placeholder.com/150x150?text=王五'
    }
};

// 动态模态框控制器
class DynamicModalController {
    constructor() {
        this.modal = new bootstrap.Modal(document.getElementById('userModal'));
        this.modalBody = document.getElementById('userModalBody');
        this.loadingIndicator = document.getElementById('loadingIndicator');
        this.userContent = document.getElementById('userContent');
        this.currentUserId = null;
        
        this.bindEvents();
    }
    
    bindEvents() {
        // 监听模态框隐藏事件,清理内容
        document.getElementById('userModal').addEventListener('hidden.bs.modal', () => {
            this.clearContent();
        });
        
        // 编辑用户按钮事件
        document.getElementById('editUserBtn').addEventListener('click', () => {
            this.editUser(this.currentUserId);
        });
    }
    
    // 显示用户详情
    async showUserDetails(userId) {
        this.currentUserId = userId;
        this.modal.show();
        
        // 显示加载指示器
        this.showLoading();
        
        try {
            // 模拟 API 调用延迟
            await this.delay(1000);
            
            // 获取用户数据
            const user = await this.fetchUserData(userId);
            
            // 渲染用户详情
            this.renderUserDetails(user);
            
            // 隐藏加载指示器,显示内容
            this.showContent();
            
        } catch (error) {
            console.error('加载用户数据失败:', error);
            this.showError('加载用户数据失败,请稍后重试。');
        }
    }
    
    // 模拟 API 调用
    async fetchUserData(userId) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                const user = userData[userId];
                if (user) {
                    resolve(user);
                } else {
                    reject(new Error('用户不存在'));
                }
            }, 500);
        });
    }
    
    // 渲染用户详情
    renderUserDetails(user) {
        const skillsHtml = user.skills.map(skill => 
            `<span class="badge bg-primary me-1">${skill}</span>`
        ).join('');
        
        this.userContent.innerHTML = `
            <div class="row">
                <div class="col-md-4 text-center">
                    <img src="${user.avatar}" alt="${user.name}" class="img-fluid rounded-circle mb-3" style="max-width: 150px;">
                    <h4>${user.name}</h4>
                    <p class="text-muted">${user.position}</p>
                </div>
                <div class="col-md-8">
                    <h5>基本信息</h5>
                    <table class="table table-borderless">
                        <tr>
                            <td><strong>邮箱:</strong></td>
                            <td>${user.email}</td>
                        </tr>
                        <tr>
                            <td><strong>电话:</strong></td>
                            <td>${user.phone}</td>
                        </tr>
                        <tr>
                            <td><strong>部门:</strong></td>
                            <td>${user.department}</td>
                        </tr>
                        <tr>
                            <td><strong>入职日期:</strong></td>
                            <td>${user.joinDate}</td>
                        </tr>
                    </table>
                    
                    <h5 class="mt-4">技能标签</h5>
                    <div class="skills-container">
                        ${skillsHtml}
                    </div>
                </div>
            </div>
        `;
    }
    
    // 显示加载状态
    showLoading() {
        this.loadingIndicator.style.display = 'block';
        this.userContent.style.display = 'none';
    }
    
    // 显示内容
    showContent() {
        this.loadingIndicator.style.display = 'none';
        this.userContent.style.display = 'block';
    }
    
    // 显示错误信息
    showError(message) {
        this.userContent.innerHTML = `
            <div class="alert alert-danger" role="alert">
                <i class="bi bi-exclamation-triangle"></i>
                ${message}
            </div>
        `;
        this.showContent();
    }
    
    // 清理内容
    clearContent() {
        this.userContent.innerHTML = '';
        this.currentUserId = null;
    }
    
    // 编辑用户
    editUser(userId) {
        // 这里可以打开编辑表单或跳转到编辑页面
        console.log(`编辑用户 ${userId}`);
        alert(`编辑用户 ${userId} 的功能将在这里实现`);
    }
    
    // 延迟函数
    delay(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
}

// 初始化动态模态框控制器
const dynamicModalController = new DynamicModalController();

// 全局函数,供 HTML 调用
function showUserDetails(button) {
    const userId = button.getAttribute('data-user-id');
    dynamicModalController.showUserDetails(userId);
}
</script>

事件系统与回调

1. Bootstrap 事件系统

// Bootstrap 事件管理器
class BootstrapEventManager {
    constructor() {
        this.eventListeners = new Map();
        this.init();
    }
    
    init() {
        this.setupGlobalEventListeners();
    }
    
    setupGlobalEventListeners() {
        // 模态框事件
        this.addModalEventListeners();
        
        // 轮播图事件
        this.addCarouselEventListeners();
        
        // 折叠面板事件
        this.addCollapseEventListeners();
        
        // 下拉菜单事件
        this.addDropdownEventListeners();
    }
    
    addModalEventListeners() {
        document.addEventListener('show.bs.modal', (event) => {
            console.log('模态框即将显示:', event.target.id);
            this.triggerCustomEvent('modal:beforeShow', {
                modal: event.target,
                relatedTarget: event.relatedTarget
            });
        });
        
        document.addEventListener('shown.bs.modal', (event) => {
            console.log('模态框已显示:', event.target.id);
            this.triggerCustomEvent('modal:afterShow', {
                modal: event.target
            });
            
            // 自动聚焦到第一个可聚焦元素
            this.focusFirstElement(event.target);
        });
        
        document.addEventListener('hide.bs.modal', (event) => {
            console.log('模态框即将隐藏:', event.target.id);
            this.triggerCustomEvent('modal:beforeHide', {
                modal: event.target
            });
        });
        
        document.addEventListener('hidden.bs.modal', (event) => {
            console.log('模态框已隐藏:', event.target.id);
            this.triggerCustomEvent('modal:afterHide', {
                modal: event.target
            });
            
            // 清理模态框数据
            this.cleanupModalData(event.target);
        });
    }
    
    addCarouselEventListeners() {
        document.addEventListener('slide.bs.carousel', (event) => {
            console.log(`轮播图滑动: 从第 ${event.from} 张到第 ${event.to} 张`);
            this.triggerCustomEvent('carousel:slide', {
                carousel: event.target,
                from: event.from,
                to: event.to,
                direction: event.direction
            });
        });
        
        document.addEventListener('slid.bs.carousel', (event) => {
            console.log(`轮播图滑动完成: 当前第 ${event.to} 张`);
            this.triggerCustomEvent('carousel:slid', {
                carousel: event.target,
                from: event.from,
                to: event.to
            });
            
            // 更新相关 UI
            this.updateCarouselUI(event.target, event.to);
        });
    }
    
    addCollapseEventListeners() {
        document.addEventListener('show.bs.collapse', (event) => {
            console.log('折叠面板即将展开:', event.target.id);
            this.triggerCustomEvent('collapse:beforeShow', {
                collapse: event.target
            });
        });
        
        document.addEventListener('shown.bs.collapse', (event) => {
            console.log('折叠面板已展开:', event.target.id);
            this.triggerCustomEvent('collapse:afterShow', {
                collapse: event.target
            });
        });
        
        document.addEventListener('hide.bs.collapse', (event) => {
            console.log('折叠面板即将收起:', event.target.id);
            this.triggerCustomEvent('collapse:beforeHide', {
                collapse: event.target
            });
        });
        
        document.addEventListener('hidden.bs.collapse', (event) => {
            console.log('折叠面板已收起:', event.target.id);
            this.triggerCustomEvent('collapse:afterHide', {
                collapse: event.target
            });
        });
    }
    
    addDropdownEventListeners() {
        document.addEventListener('show.bs.dropdown', (event) => {
            console.log('下拉菜单即将显示');
            this.triggerCustomEvent('dropdown:beforeShow', {
                dropdown: event.target
            });
        });
        
        document.addEventListener('shown.bs.dropdown', (event) => {
            console.log('下拉菜单已显示');
            this.triggerCustomEvent('dropdown:afterShow', {
                dropdown: event.target
            });
        });
        
        document.addEventListener('hide.bs.dropdown', (event) => {
            console.log('下拉菜单即将隐藏');
            this.triggerCustomEvent('dropdown:beforeHide', {
                dropdown: event.target
            });
        });
        
        document.addEventListener('hidden.bs.dropdown', (event) => {
            console.log('下拉菜单已隐藏');
            this.triggerCustomEvent('dropdown:afterHide', {
                dropdown: event.target
            });
        });
    }
    
    // 触发自定义事件
    triggerCustomEvent(eventName, data) {
        const customEvent = new CustomEvent(eventName, {
            detail: data,
            bubbles: true,
            cancelable: true
        });
        
        document.dispatchEvent(customEvent);
    }
    
    // 注册事件监听器
    on(eventName, callback) {
        if (!this.eventListeners.has(eventName)) {
            this.eventListeners.set(eventName, []);
        }
        
        this.eventListeners.get(eventName).push(callback);
        document.addEventListener(eventName, callback);
    }
    
    // 移除事件监听器
    off(eventName, callback) {
        if (this.eventListeners.has(eventName)) {
            const listeners = this.eventListeners.get(eventName);
            const index = listeners.indexOf(callback);
            
            if (index > -1) {
                listeners.splice(index, 1);
                document.removeEventListener(eventName, callback);
            }
        }
    }
    
    // 辅助方法
    focusFirstElement(modal) {
        const focusableElements = modal.querySelectorAll(
            'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
        );
        
        if (focusableElements.length > 0) {
            focusableElements[0].focus();
        }
    }
    
    cleanupModalData(modal) {
        // 清理表单数据
        const forms = modal.querySelectorAll('form');
        forms.forEach(form => {
            form.reset();
        });
        
        // 清理验证状态
        const validatedInputs = modal.querySelectorAll('.is-valid, .is-invalid');
        validatedInputs.forEach(input => {
            input.classList.remove('is-valid', 'is-invalid');
        });
    }
    
    updateCarouselUI(carousel, activeIndex) {
        // 更新自定义指示器
        const customIndicators = document.querySelectorAll(`[data-carousel-target="#${carousel.id}"] .custom-indicator`);
        customIndicators.forEach((indicator, index) => {
            if (index === activeIndex) {
                indicator.classList.add('active');
            } else {
                indicator.classList.remove('active');
            }
        });
    }
}

// 初始化事件管理器
const eventManager = new BootstrapEventManager();

// 使用示例:注册自定义事件监听器
eventManager.on('modal:afterShow', (event) => {
    const modal = event.detail.modal;
    console.log('自定义事件:模态框显示完成', modal.id);
    
    // 执行自定义逻辑
    if (modal.id === 'userModal') {
        // 加载用户数据
        console.log('开始加载用户数据...');
    }
});

eventManager.on('carousel:slid', (event) => {
    const { carousel, to } = event.detail;
    console.log('自定义事件:轮播图切换完成', carousel.id, to);
    
    // 执行自定义逻辑
    if (carousel.id === 'productCarousel') {
        // 更新产品信息
        console.log('更新产品信息...');
    }
});

2. 回调函数与 Promise 支持

// 带回调和 Promise 支持的插件包装器
class BootstrapPluginWrapper {
    constructor() {
        this.plugins = new Map();
    }
    
    // 模态框包装器
    modal(element, options = {}) {
        return new Promise((resolve, reject) => {
            try {
                const modal = new bootstrap.Modal(element, options);
                this.plugins.set(element, modal);
                
                // 监听显示完成事件
                element.addEventListener('shown.bs.modal', () => {
                    resolve(modal);
                }, { once: true });
                
                // 监听显示失败事件
                element.addEventListener('show.bs.modal', (event) => {
                    if (event.defaultPrevented) {
                        reject(new Error('模态框显示被阻止'));
                    }
                }, { once: true });
                
                modal.show();
            } catch (error) {
                reject(error);
            }
        });
    }
    
    // 工具提示包装器
    tooltip(element, options = {}) {
        return new Promise((resolve, reject) => {
            try {
                const tooltip = new bootstrap.Tooltip(element, options);
                this.plugins.set(element, tooltip);
                resolve(tooltip);
            } catch (error) {
                reject(error);
            }
        });
    }
    
    // 轮播图包装器
    carousel(element, options = {}) {
        return new Promise((resolve, reject) => {
            try {
                const carousel = new bootstrap.Carousel(element, options);
                this.plugins.set(element, carousel);
                resolve(carousel);
            } catch (error) {
                reject(error);
            }
        });
    }
    
    // 异步显示模态框
    async showModal(elementId, options = {}) {
        const element = document.getElementById(elementId);
        if (!element) {
            throw new Error(`找不到 ID 为 ${elementId} 的元素`);
        }
        
        try {
            const modal = await this.modal(element, options);
            console.log('模态框显示成功');
            return modal;
        } catch (error) {
            console.error('模态框显示失败:', error);
            throw error;
        }
    }
    
    // 异步隐藏模态框
    async hideModal(element) {
        return new Promise((resolve, reject) => {
            const modal = this.plugins.get(element);
            if (!modal) {
                reject(new Error('找不到模态框实例'));
                return;
            }
            
            // 监听隐藏完成事件
            element.addEventListener('hidden.bs.modal', () => {
                resolve();
            }, { once: true });
            
            modal.hide();
        });
    }
    
    // 批量初始化工具提示
    async initializeTooltips(selector = '[data-bs-toggle="tooltip"]') {
        const elements = document.querySelectorAll(selector);
        const promises = Array.from(elements).map(element => 
            this.tooltip(element)
        );
        
        try {
            const tooltips = await Promise.all(promises);
            console.log(`成功初始化 ${tooltips.length} 个工具提示`);
            return tooltips;
        } catch (error) {
            console.error('工具提示初始化失败:', error);
            throw error;
        }
    }
    
    // 链式操作支持
    chain(element) {
        return new PluginChain(element, this);
    }
    
    // 获取插件实例
    getInstance(element) {
        return this.plugins.get(element);
    }
    
    // 销毁插件实例
    dispose(element) {
        const plugin = this.plugins.get(element);
        if (plugin && typeof plugin.dispose === 'function') {
            plugin.dispose();
            this.plugins.delete(element);
        }
    }
    
    // 销毁所有插件实例
    disposeAll() {
        this.plugins.forEach((plugin, element) => {
            if (typeof plugin.dispose === 'function') {
                plugin.dispose();
            }
        });
        this.plugins.clear();
    }
}

// 链式操作类
class PluginChain {
    constructor(element, wrapper) {
        this.element = element;
        this.wrapper = wrapper;
        this.operations = [];
    }
    
    modal(options = {}) {
        this.operations.push(() => this.wrapper.modal(this.element, options));
        return this;
    }
    
    tooltip(options = {}) {
        this.operations.push(() => this.wrapper.tooltip(this.element, options));
        return this;
    }
    
    carousel(options = {}) {
        this.operations.push(() => this.wrapper.carousel(this.element, options));
        return this;
    }
    
    async execute() {
        const results = [];
        for (const operation of this.operations) {
            const result = await operation();
            results.push(result);
        }
        return results;
    }
}

// 使用示例
const pluginWrapper = new BootstrapPluginWrapper();

// 异步使用示例
async function demonstrateAsyncUsage() {
    try {
        // 显示模态框
        const modal = await pluginWrapper.showModal('exampleModal', {
            backdrop: 'static'
        });
        
        console.log('模态框已显示');
        
        // 等待 3 秒后隐藏
        setTimeout(async () => {
            await pluginWrapper.hideModal(document.getElementById('exampleModal'));
            console.log('模态框已隐藏');
        }, 3000);
        
        // 批量初始化工具提示
        const tooltips = await pluginWrapper.initializeTooltips();
        console.log('工具提示初始化完成');
        
    } catch (error) {
        console.error('操作失败:', error);
    }
}

// 链式操作示例
async function demonstrateChainUsage() {
    const element = document.getElementById('multiPluginElement');
    
    try {
        const results = await pluginWrapper
            .chain(element)
            .tooltip({ placement: 'top' })
            .execute();
            
        console.log('链式操作完成:', results);
    } catch (error) {
        console.error('链式操作失败:', error);
    }
}

性能优化

1. 插件懒加载

// 插件懒加载管理器
class PluginLazyLoader {
    constructor() {
        this.loadedPlugins = new Set();
        this.observers = new Map();
        this.init();
    }
    
    init() {
        this.setupIntersectionObserver();
        this.setupMutationObserver();
    }
    
    setupIntersectionObserver() {
        // 创建交叉观察器,用于检测元素进入视口
        this.intersectionObserver = new IntersectionObserver(
            (entries) => {
                entries.forEach(entry => {
                    if (entry.isIntersecting) {
                        this.loadPluginForElement(entry.target);
                        this.intersectionObserver.unobserve(entry.target);
                    }
                });
            },
            {
                rootMargin: '50px', // 提前 50px 开始加载
                threshold: 0.1
            }
        );
    }
    
    setupMutationObserver() {
        // 创建变化观察器,用于检测新添加的元素
        this.mutationObserver = new MutationObserver((mutations) => {
            mutations.forEach(mutation => {
                mutation.addedNodes.forEach(node => {
                    if (node.nodeType === Node.ELEMENT_NODE) {
                        this.scanForLazyPlugins(node);
                    }
                });
            });
        });
        
        this.mutationObserver.observe(document.body, {
            childList: true,
            subtree: true
        });
    }
    
    // 扫描需要懒加载的插件
    scanForLazyPlugins(container = document) {
        const lazyElements = container.querySelectorAll('[data-lazy-plugin]');
        
        lazyElements.forEach(element => {
            if (!this.loadedPlugins.has(element)) {
                this.intersectionObserver.observe(element);
            }
        });
    }
    
    // 为元素加载插件
    loadPluginForElement(element) {
        const pluginType = element.getAttribute('data-lazy-plugin');
        const pluginOptions = this.parseOptions(element.getAttribute('data-plugin-options'));
        
        switch (pluginType) {
            case 'tooltip':
                this.loadTooltip(element, pluginOptions);
                break;
            case 'popover':
                this.loadPopover(element, pluginOptions);
                break;
            case 'carousel':
                this.loadCarousel(element, pluginOptions);
                break;
            case 'modal':
                this.loadModal(element, pluginOptions);
                break;
            default:
                console.warn(`未知的插件类型: ${pluginType}`);
        }
        
        this.loadedPlugins.add(element);
    }
    
    loadTooltip(element, options = {}) {
        const tooltip = new bootstrap.Tooltip(element, options);
        console.log('懒加载工具提示:', element);
        return tooltip;
    }
    
    loadPopover(element, options = {}) {
        const popover = new bootstrap.Popover(element, options);
        console.log('懒加载弹出框:', element);
        return popover;
    }
    
    loadCarousel(element, options = {}) {
        const carousel = new bootstrap.Carousel(element, options);
        console.log('懒加载轮播图:', element);
        return carousel;
    }
    
    loadModal(element, options = {}) {
        const modal = new bootstrap.Modal(element, options);
        console.log('懒加载模态框:', element);
        return modal;
    }
    
    parseOptions(optionsString) {
        if (!optionsString) return {};
        
        try {
            return JSON.parse(optionsString);
        } catch (error) {
            console.warn('解析插件选项失败:', optionsString);
            return {};
        }
    }
    
    // 手动触发加载
    loadAll() {
        const lazyElements = document.querySelectorAll('[data-lazy-plugin]');
        lazyElements.forEach(element => {
            if (!this.loadedPlugins.has(element)) {
                this.loadPluginForElement(element);
            }
        });
    }
    
    // 销毁懒加载器
    destroy() {
        this.intersectionObserver.disconnect();
        this.mutationObserver.disconnect();
        this.loadedPlugins.clear();
    }
}

// 初始化懒加载器
const pluginLazyLoader = new PluginLazyLoader();

// 扫描现有元素
pluginLazyLoader.scanForLazyPlugins();

2. 内存管理和清理

// 插件内存管理器
class PluginMemoryManager {
    constructor() {
        this.pluginInstances = new WeakMap();
        this.eventListeners = new Map();
        this.timers = new Set();
        this.observers = new Set();
        
        this.setupCleanupListeners();
    }
    
    setupCleanupListeners() {
        // 页面卸载时清理
        window.addEventListener('beforeunload', () => {
            this.cleanup();
        });
        
        // 页面隐藏时清理不必要的资源
        document.addEventListener('visibilitychange', () => {
            if (document.hidden) {
                this.pauseNonEssentialOperations();
            } else {
                this.resumeOperations();
            }
        });
    }
    
    // 注册插件实例
    registerPlugin(element, plugin, type) {
        if (!this.pluginInstances.has(element)) {
            this.pluginInstances.set(element, new Map());
        }
        
        this.pluginInstances.get(element).set(type, plugin);
    }
    
    // 获取插件实例
    getPlugin(element, type) {
        const plugins = this.pluginInstances.get(element);
        return plugins ? plugins.get(type) : null;
    }
    
    // 注册事件监听器
    registerEventListener(element, event, handler) {
        const key = `${element.id || 'anonymous'}-${event}`;
        
        if (!this.eventListeners.has(key)) {
            this.eventListeners.set(key, []);
        }
        
        this.eventListeners.get(key).push({ element, event, handler });
        element.addEventListener(event, handler);
    }
    
    // 注册定时器
    registerTimer(timer) {
        this.timers.add(timer);
        return timer;
    }
    
    // 注册观察器
    registerObserver(observer) {
        this.observers.add(observer);
        return observer;
    }
    
    // 清理指定元素的资源
    cleanupElement(element) {
        // 清理插件实例
        const plugins = this.pluginInstances.get(element);
        if (plugins) {
            plugins.forEach(plugin => {
                if (typeof plugin.dispose === 'function') {
                    plugin.dispose();
                }
            });
            this.pluginInstances.delete(element);
        }
        
        // 清理事件监听器
        const elementId = element.id || 'anonymous';
        this.eventListeners.forEach((listeners, key) => {
            if (key.startsWith(elementId)) {
                listeners.forEach(({ element: el, event, handler }) => {
                    el.removeEventListener(event, handler);
                });
                this.eventListeners.delete(key);
            }
        });
    }
    
    // 暂停非必要操作
    pauseNonEssentialOperations() {
        // 暂停轮播图
        document.querySelectorAll('.carousel').forEach(carousel => {
            const carouselInstance = bootstrap.Carousel.getInstance(carousel);
            if (carouselInstance) {
                carouselInstance.pause();
            }
        });
        
        // 清理定时器
        this.timers.forEach(timer => {
            clearTimeout(timer);
            clearInterval(timer);
        });
        this.timers.clear();
        
        console.log('已暂停非必要操作');
    }
    
    // 恢复操作
    resumeOperations() {
        // 恢复轮播图
        document.querySelectorAll('.carousel[data-bs-ride="carousel"]').forEach(carousel => {
            const carouselInstance = bootstrap.Carousel.getInstance(carousel);
            if (carouselInstance) {
                carouselInstance.cycle();
            }
        });
        
        console.log('已恢复操作');
    }
    
    // 全面清理
    cleanup() {
        // 清理所有事件监听器
        this.eventListeners.forEach(listeners => {
            listeners.forEach(({ element, event, handler }) => {
                element.removeEventListener(event, handler);
            });
        });
        this.eventListeners.clear();
        
        // 清理定时器
        this.timers.forEach(timer => {
            clearTimeout(timer);
            clearInterval(timer);
        });
        this.timers.clear();
        
        // 清理观察器
        this.observers.forEach(observer => {
            if (typeof observer.disconnect === 'function') {
                observer.disconnect();
            }
        });
        this.observers.clear();
        
        console.log('内存清理完成');
    }
    
    // 获取内存使用情况
    getMemoryUsage() {
        return {
            pluginInstances: this.pluginInstances.size || 0,
            eventListeners: this.eventListeners.size,
            timers: this.timers.size,
            observers: this.observers.size
        };
    }
}

// 全局内存管理器
const memoryManager = new PluginMemoryManager();

// 使用示例
function createManagedTooltip(element, options = {}) {
    const tooltip = new bootstrap.Tooltip(element, options);
    memoryManager.registerPlugin(element, tooltip, 'tooltip');
    
    // 注册相关事件监听器
    const showHandler = () => console.log('工具提示显示');
    memoryManager.registerEventListener(element, 'shown.bs.tooltip', showHandler);
    
    return tooltip;
}

// 定期检查内存使用情况
const memoryCheckTimer = setInterval(() => {
    const usage = memoryManager.getMemoryUsage();
    console.log('内存使用情况:', usage);
}, 30000); // 每 30 秒检查一次

memoryManager.registerTimer(memoryCheckTimer);

可访问性支持

1. 键盘导航支持

// 键盘导航管理器
class KeyboardNavigationManager {
    constructor() {
        this.focusableElements = [
            'button',
            '[href]',
            'input',
            'select',
            'textarea',
            '[tabindex]:not([tabindex="-1"])'
        ].join(', ');
        
        this.init();
    }
    
    init() {
        this.setupGlobalKeyboardListeners();
        this.setupModalKeyboardNavigation();
        this.setupCarouselKeyboardNavigation();
        this.setupDropdownKeyboardNavigation();
    }
    
    setupGlobalKeyboardListeners() {
        document.addEventListener('keydown', (event) => {
            // ESC 键关闭模态框、下拉菜单等
            if (event.key === 'Escape') {
                this.handleEscapeKey(event);
            }
            
            // Tab 键焦点管理
            if (event.key === 'Tab') {
                this.handleTabKey(event);
            }
        });
    }
    
    setupModalKeyboardNavigation() {
        document.addEventListener('shown.bs.modal', (event) => {
            const modal = event.target;
            this.trapFocusInModal(modal);
        });
    }
    
    setupCarouselKeyboardNavigation() {
        document.querySelectorAll('.carousel').forEach(carousel => {
            carousel.addEventListener('keydown', (event) => {
                switch (event.key) {
                    case 'ArrowLeft':
                        event.preventDefault();
                        bootstrap.Carousel.getInstance(carousel)?.prev();
                        break;
                    case 'ArrowRight':
                        event.preventDefault();
                        bootstrap.Carousel.getInstance(carousel)?.next();
                        break;
                    case 'Home':
                        event.preventDefault();
                        bootstrap.Carousel.getInstance(carousel)?.to(0);
                        break;
                    case 'End':
                        event.preventDefault();
                        const items = carousel.querySelectorAll('.carousel-item');
                        bootstrap.Carousel.getInstance(carousel)?.to(items.length - 1);
                        break;
                }
            });
        });
    }
    
    setupDropdownKeyboardNavigation() {
        document.addEventListener('shown.bs.dropdown', (event) => {
            const dropdown = event.target;
            const menu = dropdown.querySelector('.dropdown-menu');
            
            if (menu) {
                this.setupDropdownMenuNavigation(menu);
            }
        });
    }
    
    handleEscapeKey(event) {
        // 关闭最顶层的模态框
        const openModals = document.querySelectorAll('.modal.show');
        if (openModals.length > 0) {
            const topModal = openModals[openModals.length - 1];
            const modalInstance = bootstrap.Modal.getInstance(topModal);
            if (modalInstance) {
                modalInstance.hide();
            }
            return;
        }
        
        // 关闭下拉菜单
        const openDropdowns = document.querySelectorAll('.dropdown-menu.show');
        if (openDropdowns.length > 0) {
            openDropdowns.forEach(dropdown => {
                const toggle = dropdown.previousElementSibling;
                if (toggle) {
                    bootstrap.Dropdown.getInstance(toggle)?.hide();
                }
            });
        }
    }
    
    handleTabKey(event) {
        // 检查是否在模态框内
        const activeModal = document.querySelector('.modal.show');
        if (activeModal) {
            this.handleModalTabNavigation(event, activeModal);
        }
    }
    
    trapFocusInModal(modal) {
        const focusableElements = modal.querySelectorAll(this.focusableElements);
        const firstElement = focusableElements[0];
        const lastElement = focusableElements[focusableElements.length - 1];
        
        // 聚焦到第一个元素
        if (firstElement) {
            firstElement.focus();
        }
        
        // 设置焦点陷阱
        modal.addEventListener('keydown', (event) => {
            if (event.key === 'Tab') {
                if (event.shiftKey) {
                    // Shift + Tab
                    if (document.activeElement === firstElement) {
                        event.preventDefault();
                        lastElement?.focus();
                    }
                } else {
                    // Tab
                    if (document.activeElement === lastElement) {
                        event.preventDefault();
                        firstElement?.focus();
                    }
                }
            }
        });
    }
    
    handleModalTabNavigation(event, modal) {
        const focusableElements = modal.querySelectorAll(this.focusableElements);
        const firstElement = focusableElements[0];
        const lastElement = focusableElements[focusableElements.length - 1];
        
        if (event.shiftKey) {
            // Shift + Tab
            if (document.activeElement === firstElement) {
                event.preventDefault();
                lastElement?.focus();
            }
        } else {
            // Tab
            if (document.activeElement === lastElement) {
                event.preventDefault();
                firstElement?.focus();
            }
        }
    }
    
    setupDropdownMenuNavigation(menu) {
        const menuItems = menu.querySelectorAll('.dropdown-item:not(.disabled)');
        let currentIndex = -1;
        
        menu.addEventListener('keydown', (event) => {
            switch (event.key) {
                case 'ArrowDown':
                    event.preventDefault();
                    currentIndex = (currentIndex + 1) % menuItems.length;
                    menuItems[currentIndex].focus();
                    break;
                case 'ArrowUp':
                    event.preventDefault();
                    currentIndex = currentIndex <= 0 ? menuItems.length - 1 : currentIndex - 1;
                    menuItems[currentIndex].focus();
                    break;
                case 'Home':
                    event.preventDefault();
                    currentIndex = 0;
                    menuItems[currentIndex].focus();
                    break;
                case 'End':
                    event.preventDefault();
                    currentIndex = menuItems.length - 1;
                    menuItems[currentIndex].focus();
                    break;
                case 'Enter':
                case ' ':
                    event.preventDefault();
                    if (currentIndex >= 0) {
                        menuItems[currentIndex].click();
                    }
                    break;
            }
        });
    }
    
    // 设置焦点指示器
    setupFocusIndicators() {
        const style = document.createElement('style');
        style.textContent = `
            .focus-visible {
                outline: 2px solid #007bff;
                outline-offset: 2px;
            }
            
            .modal .focus-visible {
                outline-color: #ffffff;
            }
            
            .btn:focus-visible {
                box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
            }
        `;
        document.head.appendChild(style);
    }
}

// 初始化键盘导航管理器
const keyboardNavManager = new KeyboardNavigationManager();
keyboardNavManager.setupFocusIndicators();

2. ARIA 标签优化

// ARIA 标签管理器
class AriaManager {
    constructor() {
        this.init();
    }
    
    init() {
        this.enhanceModals();
        this.enhanceCarousels();
        this.enhanceDropdowns();
        this.enhanceTooltips();
        this.enhanceCollapses();
    }
    
    enhanceModals() {
        document.querySelectorAll('.modal').forEach(modal => {
            // 确保模态框有正确的 ARIA 属性
            if (!modal.getAttribute('aria-labelledby') && !modal.getAttribute('aria-label')) {
                const title = modal.querySelector('.modal-title');
                if (title) {
                    const titleId = title.id || `modal-title-${Date.now()}`;
                    title.id = titleId;
                    modal.setAttribute('aria-labelledby', titleId);
                }
            }
            
            // 设置模态框描述
            const body = modal.querySelector('.modal-body');
            if (body && !modal.getAttribute('aria-describedby')) {
                const bodyId = body.id || `modal-body-${Date.now()}`;
                body.id = bodyId;
                modal.setAttribute('aria-describedby', bodyId);
            }
            
            // 监听模态框状态变化
            modal.addEventListener('shown.bs.modal', () => {
                modal.setAttribute('aria-hidden', 'false');
                this.announceToScreenReader('模态框已打开');
            });
            
            modal.addEventListener('hidden.bs.modal', () => {
                modal.setAttribute('aria-hidden', 'true');
                this.announceToScreenReader('模态框已关闭');
            });
        });
    }
    
    enhanceCarousels() {
        document.querySelectorAll('.carousel').forEach(carousel => {
            // 设置轮播图角色和标签
            carousel.setAttribute('role', 'region');
            carousel.setAttribute('aria-label', '图片轮播');
            
            // 增强指示器
            const indicators = carousel.querySelectorAll('.carousel-indicators button');
            indicators.forEach((indicator, index) => {
                indicator.setAttribute('aria-label', `转到第 ${index + 1} 张幻灯片`);
            });
            
            // 增强控制按钮
            const prevBtn = carousel.querySelector('.carousel-control-prev');
            const nextBtn = carousel.querySelector('.carousel-control-next');
            
            if (prevBtn) {
                prevBtn.setAttribute('aria-label', '上一张幻灯片');
            }
            
            if (nextBtn) {
                nextBtn.setAttribute('aria-label', '下一张幻灯片');
            }
            
            // 监听轮播图变化
            carousel.addEventListener('slid.bs.carousel', (event) => {
                const currentSlide = event.to + 1;
                const totalSlides = carousel.querySelectorAll('.carousel-item').length;
                this.announceToScreenReader(`当前第 ${currentSlide} 张,共 ${totalSlides} 张幻灯片`);
            });
        });
    }
    
    enhanceDropdowns() {
        document.querySelectorAll('[data-bs-toggle="dropdown"]').forEach(toggle => {
            const menu = document.querySelector(toggle.getAttribute('data-bs-target') || 
                                               toggle.getAttribute('href'));
            
            if (menu) {
                // 设置下拉菜单关系
                toggle.setAttribute('aria-haspopup', 'true');
                toggle.setAttribute('aria-expanded', 'false');
                
                const menuId = menu.id || `dropdown-menu-${Date.now()}`;
                menu.id = menuId;
                toggle.setAttribute('aria-controls', menuId);
                
                // 监听下拉菜单状态
                toggle.addEventListener('shown.bs.dropdown', () => {
                    toggle.setAttribute('aria-expanded', 'true');
                    this.announceToScreenReader('下拉菜单已展开');
                });
                
                toggle.addEventListener('hidden.bs.dropdown', () => {
                    toggle.setAttribute('aria-expanded', 'false');
                    this.announceToScreenReader('下拉菜单已收起');
                });
            }
        });
    }
    
    enhanceTooltips() {
        document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(element => {
            // 设置工具提示描述关系
            element.setAttribute('aria-describedby', element.getAttribute('data-bs-target') || 
                                                    `tooltip-${Date.now()}`);
        });
    }
    
    enhanceCollapses() {
        document.querySelectorAll('[data-bs-toggle="collapse"]').forEach(toggle => {
            const target = document.querySelector(toggle.getAttribute('data-bs-target') || 
                                                 toggle.getAttribute('href'));
            
            if (target) {
                // 设置折叠面板关系
                toggle.setAttribute('aria-expanded', target.classList.contains('show') ? 'true' : 'false');
                
                const targetId = target.id || `collapse-${Date.now()}`;
                target.id = targetId;
                toggle.setAttribute('aria-controls', targetId);
                
                // 监听折叠状态
                target.addEventListener('shown.bs.collapse', () => {
                    toggle.setAttribute('aria-expanded', 'true');
                    this.announceToScreenReader('面板已展开');
                });
                
                target.addEventListener('hidden.bs.collapse', () => {
                    toggle.setAttribute('aria-expanded', 'false');
                    this.announceToScreenReader('面板已收起');
                });
            }
        });
    }
    
    // 向屏幕阅读器宣布消息
    announceToScreenReader(message) {
        const announcement = document.createElement('div');
        announcement.setAttribute('aria-live', 'polite');
        announcement.setAttribute('aria-atomic', 'true');
        announcement.className = 'sr-only';
        announcement.textContent = message;
        
        document.body.appendChild(announcement);
        
        // 短暂延迟后移除
        setTimeout(() => {
            document.body.removeChild(announcement);
        }, 1000);
    }
    
    // 创建屏幕阅读器专用样式
    createScreenReaderStyles() {
        const style = document.createElement('style');
        style.textContent = `
            .sr-only {
                position: absolute !important;
                width: 1px !important;
                height: 1px !important;
                padding: 0 !important;
                margin: -1px !important;
                overflow: hidden !important;
                clip: rect(0, 0, 0, 0) !important;
                white-space: nowrap !important;
                border: 0 !important;
            }
            
            .sr-only-focusable:focus {
                position: static !important;
                width: auto !important;
                height: auto !important;
                padding: inherit !important;
                margin: inherit !important;
                overflow: visible !important;
                clip: auto !important;
                white-space: normal !important;
            }
        `;
        document.head.appendChild(style);
    }
}

// 初始化 ARIA 管理器
const ariaManager = new AriaManager();
ariaManager.createScreenReaderStyles();

本章总结

本章深入学习了 Bootstrap JavaScript 插件的使用和自定义开发:

主要内容

  1. 插件基础使用 - 掌握了模态框、工具提示、轮播图等核心插件的基本用法
  2. JavaScript API 控制 - 学会了通过编程方式控制插件行为和状态
  3. 高级插件应用 - 实现了动态内容模态框和智能工具提示系统
  4. 插件配置与自定义 - 了解了插件配置选项和自定义插件开发
  5. 事件系统 - 掌握了 Bootstrap 事件机制和回调函数使用
  6. 性能优化 - 学习了插件懒加载和内存管理技术
  7. 可访问性支持 - 实现了键盘导航和 ARIA 标签优化

核心技能

  • 熟练使用 Bootstrap JavaScript 插件
  • 能够通过 API 控制插件行为
  • 掌握事件监听和处理机制
  • 具备自定义插件开发能力
  • 了解性能优化和可访问性最佳实践

实际应用

  • 创建交互式用户界面
  • 开发动态内容展示系统
  • 实现无障碍访问支持
  • 优化页面性能和用户体验

练习题

基础练习

  1. 创建一个图片画廊,使用模态框显示大图,支持键盘导航
  2. 实现一个多步骤表单,使用模态框展示每个步骤
  3. 创建一个产品展示轮播图,包含缩略图导航和自动播放控制

进阶练习

  1. 开发一个消息通知系统,支持不同类型的通知和自动关闭
  2. 实现一个数据表格,使用工具提示显示详细信息
  3. 创建一个可拖拽的模态框,支持调整大小和位置

高级练习

  1. 开发一个完整的后台管理界面,集成所有 Bootstrap 插件
  2. 实现一个无障碍访问的在线购物车系统
  3. 创建一个响应式的数据可视化仪表板,支持实时更新
<!-- 高级轮播图 -->
<div class="container my-5">
    <h2>高级轮播图控制</h2>
    
    <div id="advancedCarousel" class="carousel slide" data-bs-ride="carousel">
        <!-- 指示器 -->
        <div class="carousel-indicators">
            <button type="button" data-bs-target="#advancedCarousel" data-bs-slide-to="0" class="active" aria-current="true" aria-label="Slide 1"></button>
            <button type="button" data-bs-target="#advancedCarousel" data-bs-slide-to="1" aria-label="Slide 2"></button>
            <button type="button" data-bs-target="#advancedCarousel" data-bs-slide-to="2" aria-label="Slide 3"></button>
            <button type="button" data-bs-target="#advancedCarousel" data-bs-slide-to="3" aria-label="Slide 4"></button>
        </div>
        
        <!-- 轮播内容 -->
        <div class="carousel-inner">
            <div class="carousel-item active">
                <img src="https://via.placeholder.com/800x400/007bff/ffffff?text=Slide+1" class="d-block w-100" alt="Slide 1">
                <div class="carousel-caption d-none d-md-block">
                    <h5>第一张幻灯片</h5>
                    <p>这是第一张幻灯片的描述内容。</p>
                </div>
            </div>
            <div class="carousel-item">
                <img src="https://via.placeholder.com/800x400/28a745/ffffff?text=Slide+2" class="d-block w-100" alt="Slide 2">
                <div class="carousel-caption d-none d-md-block">
                    <h5>第二张幻灯片</h5>
                    <p>这是第二张幻灯片的描述内容。</p>
                </div>
            </div>
            <div class="carousel-item">
                <img src="https://via.placeholder.com/800x400/ffc107/000000?text=Slide+3" class="d-block w-100" alt="Slide 3">
                <div class="carousel-caption d-none d-md-block">
                    <h5>第三张幻灯片</h5>
                    <p>这是第三张幻灯片的描述内容。</p>
                </div>
            </div>
            <div class="carousel-item">
                <img src="https://via.placeholder.com/800x400/dc3545/ffffff?text=Slide+4" class="d-block w-100" alt="Slide 4">
                <div class="carousel-caption d-none d-md-block">
                    <h5>第四张幻灯片</h5>
                    <p>这是第四张幻灯片的描述内容。</p>
                </div>
            </div>
        </div>
        
        <!-- 控制按钮 -->
        <button class="carousel-control-prev" type="button" data-bs-target="#advancedCarousel" data-bs-slide="prev">
            <span class="carousel-control-prev-icon" aria-hidden="true"></span>
            <span class="visually-hidden">Previous</span>
        </button>
        <button class="carousel-control-next" type="button" data-bs-target="#advancedCarousel" data-bs-slide="next">
            <span class="carousel-control-next-icon" aria-hidden="true"></span>
            <span class="visually-hidden">Next</span>
        </button>
    </div>
    
    <!-- 轮播控制面板 -->
    <div class="mt-4">
        <div class="row">
            <div class="col-md-6">
                <h5>播放控制</h5>
                <div class="btn-group" role="group">
                    <button type="button" class="btn btn-outline-primary" id="playBtn">播放</button>
                    <button type="button" class="btn btn-outline-secondary" id="pauseBtn">暂停</button>
                    <button type="button" class="btn btn-outline-info" id="prevBtn">上一张</button>
                    <button type="button" class="btn btn-outline-info" id="nextBtn">下一张</button>
                </div>
            </div>
            <div class="col-md-6">
                <h5>播放设置</h5>
                <div class="mb-2">
                    <label for="intervalRange" class="form-label">播放间隔: <span id="intervalValue">3000</span>ms</label>
                    <input type="range" class="form-range" id="intervalRange" min="1000" max="10000" step="500" value="3000">
                </div>
                <div class="form-check">
                    <input class="form-check-input" type="checkbox" id="autoplayCheck" checked>
                    <label class="form-check-label" for="autoplayCheck">自动播放</label>
                </div>
            </div>
        </div>
    </div>
    
    <!-- 缩略图导航 -->
    <div class="mt-4">
        <h5>缩略图导航</h5>
        <div class="row" id="thumbnailNav">
            <div class="col-3">
                <img src="https://via.placeholder.com/200x100/007bff/ffffff?text=Thumb+1" 
                     class="img-fluid thumbnail-img active" 
                     data-slide-to="0" 
                     alt="Thumbnail 1">
            </div>
            <div class="col-3">
                <img src="https://via.placeholder.com/200x100/28a745/ffffff?text=Thumb+2" 
                     class="img-fluid thumbnail-img" 
                     data-slide-to="1" 
                     alt="Thumbnail 2">
            </div>
            <div class="col-3">
                <img src="https://via.placeholder.com/200x100/ffc107/000000?text=Thumb+3" 
                     class="img-fluid thumbnail-img" 
                     data-slide-to="2" 
                     alt="Thumbnail 3">
            </div>
            <div class="col-3">
                <img src="https://via.placeholder.com/200x100/dc3545/ffffff?text=Thumb+4" 
                     class="img-fluid thumbnail-img" 
                     data-slide-to="3" 
                     alt="Thumbnail 4">
            </div>
        </div>
    </div>
</div>

<script>
// 高级轮播图控制器
class AdvancedCarouselController {
    constructor(carouselId) {
        this.carouselElement = document.getElementById(carouselId);
        this.carousel = new bootstrap.Carousel(this.carouselElement, {
            interval: 3000,
            wrap: true,
            touch: true,
            pause: 'hover'
        });
        
        this.currentSlide = 0;
        this.totalSlides = this.carouselElement.querySelectorAll('.carousel-item').length;
        this.isPlaying = true;
        
        this.bindEvents();
        this.initializeThumbnails();
        this.initializeControls();
    }
    
    bindEvents() {
        // 监听轮播图事件
        this.carouselElement.addEventListener('slide.bs.carousel', (event) => {
            this.currentSlide = event.to;
            this.updateThumbnails(event.to);
            this.updateProgress(event.to);
        });
        
        this.carouselElement.addEventListener('slid.bs.carousel', (event) => {
            console.log(`轮播图切换到第 ${event.to + 1} 张`);
        });
    }
    
    initializeThumbnails() {
        const thumbnails = document.querySelectorAll('.thumbnail-img');
        
        thumbnails.forEach((thumbnail, index) => {
            thumbnail.addEventListener('click', () => {
                this.carousel.to(index);
            });
            
            // 添加悬停效果
            thumbnail.addEventListener('mouseenter', () => {
                thumbnail.style.opacity = '0.8';
            });
            
            thumbnail.addEventListener('mouseleave', () => {
                thumbnail.style.opacity = '1';
            });
        });
    }
    
    initializeControls() {
        // 播放控制按钮
        document.getElementById('playBtn').addEventListener('click', () => {
            this.play();
        });
        
        document.getElementById('pauseBtn').addEventListener('click', () => {
            this.pause();
        });
        
        document.getElementById('prevBtn').addEventListener('click', () => {
            this.prev();
        });
        
        document.getElementById('nextBtn').addEventListener('click', () => {
            this.next();
        });
        
        // 播放间隔控制
        const intervalRange = document.getElementById('intervalRange');
        const intervalValue = document.getElementById('intervalValue');
        
        intervalRange.addEventListener('input', (e) => {
            const interval = parseInt(e.target.value);
            intervalValue.textContent = interval;
            this.setInterval(interval);
        });
        
        // 自动播放开关
        document.getElementById('autoplayCheck').addEventListener('change', (e) => {
            if (e.target.checked) {
                this.play();
            } else {
                this.pause();
            }
        });
    }
    
    updateThumbnails(activeIndex) {
        const thumbnails = document.querySelectorAll('.thumbnail-img');
        
        thumbnails.forEach((thumbnail, index) => {
            if (index === activeIndex) {
                thumbnail.classList.add('active');
            } else {
                thumbnail.classList.remove('active');
            }
        });
    }
    
    updateProgress(activeIndex) {
        const progress = ((activeIndex + 1) / this.totalSlides) * 100;
        console.log(`播放进度: ${progress.toFixed(1)}%`);
    }
    
    play() {
        this.carousel.cycle();
        this.isPlaying = true;
        
        // 更新按钮状态
        document.getElementById('playBtn').disabled = true;
        document.getElementById('pauseBtn').disabled = false;
    }
    
    pause() {
        this.carousel.pause();
        this.isPlaying = false;
        
        // 更新按钮状态
        document.getElementById('playBtn').disabled = false;
        document.getElementById('pauseBtn').disabled = true;
    }
    
    prev() {
        this.carousel.prev();
    }
    
    next() {
        this.carousel.next();
    }
    
    setInterval(interval) {
        // 重新创建轮播图实例以应用新的间隔
        this.carousel.dispose();
        this.carousel = new bootstrap.Carousel(this.carouselElement, {
            interval: interval,
            wrap: true,
            touch: true,
            pause: 'hover'
        });
        
        if (this.isPlaying) {
            this.carousel.cycle();
        }
    }
    
    // 跳转到指定幻灯片
    goToSlide(index) {
        if (index >= 0 && index < this.totalSlides) {
            this.carousel.to(index);
        }
    }
    
    // 获取当前幻灯片索引
    getCurrentSlide() {
        return this.currentSlide;
    }
    
    // 获取总幻灯片数量
    getTotalSlides() {
        return this.totalSlides;
    }
}

// 初始化高级轮播图控制器
const advancedCarouselController = new AdvancedCarouselController('advancedCarousel');

// 添加缩略图样式
const carouselStyle = document.createElement('style');
carouselStyle.textContent = `
    .thumbnail-img {
        cursor: pointer;
        border: 3px solid transparent;
        border-radius: 0.375rem;
        transition: all 0.3s ease;
    }
    
    .thumbnail-img:hover {
        border-color: #007bff;
        transform: scale(1.05);
    }
    
    .thumbnail-img.active {
        border-color: #007bff;
        box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
    }
    
    .carousel-control-prev,
    .carousel-control-next {
        width: 5%;
    }
    
    .carousel-indicators {
        margin-bottom: 1rem;
    }
`;
document.head.appendChild(carouselStyle);
</script>

插件配置与自定义

1. 插件配置选项

// Bootstrap 插件通用配置模式
class PluginConfigManager {
    constructor() {
        this.defaultConfigs = {
            modal: {
                backdrop: true,     // 背景遮罩
                keyboard: true,     // ESC 键关闭
                focus: true,        // 自动聚焦
                show: true          // 初始化时显示
            },
            tooltip: {
                animation: true,    // 动画效果
                delay: 0,          // 延迟时间
                html: false,       // HTML 内容
                placement: 'top',  // 位置
                selector: false,   // 选择器
                template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',
                title: '',         // 标题
                trigger: 'hover focus', // 触发方式
                offset: [0, 0],    // 偏移量
                container: false,  // 容器
                fallbackPlacements: ['top', 'right', 'bottom', 'left'],
                boundary: 'clippingParents',
                customClass: '',
                sanitize: true,
                allowList: {
                    // 允许的 HTML 标签和属性
                    'b': [],
                    'br': [],
                    'code': [],
                    'em': [],
                    'i': [],
                    'mark': [],
                    'small': [],
                    'span': [],
                    'strong': [],
                    'sub': [],
                    'sup': []
                },
                sanitizeFn: null,
                popperConfig: null
            },
            carousel: {
                interval: 5000,    // 自动切换间隔
                keyboard: true,    // 键盘控制
                pause: 'hover',    // 暂停条件
                ride: false,       // 自动开始
                wrap: true,        // 循环播放
                touch: true        // 触摸支持
            },
            collapse: {
                parent: null,      // 父容器
                toggle: true       // 切换状态
            },
            dropdown: {
                boundary: 'clippingParents',
                reference: 'toggle',
                display: 'dynamic',
                popperConfig: null,
                autoClose: true
            }
        };
    }
    
    // 获取默认配置
    getDefaultConfig(pluginName) {
        return { ...this.defaultConfigs[pluginName] };
    }
    
    // 合并配置
    mergeConfig(pluginName, userConfig) {
        const defaultConfig = this.getDefaultConfig(pluginName);
        return { ...defaultConfig, ...userConfig };
    }
    
    // 验证配置
    validateConfig(pluginName, config) {
        const validators = {
            modal: this.validateModalConfig,
            tooltip: this.validateTooltipConfig,
            carousel: this.validateCarouselConfig
        };
        
        const validator = validators[pluginName];
        if (validator) {
            return validator(config);
        }
        
        return { valid: true, errors: [] };
    }
    
    validateModalConfig(config) {
        const errors = [];
        
        if (typeof config.backdrop !== 'boolean' && config.backdrop !== 'static') {
            errors.push('backdrop 必须是 boolean 或 "static"');
        }
        
        if (typeof config.keyboard !== 'boolean') {
            errors.push('keyboard 必须是 boolean');
        }
        
        return {
            valid: errors.length === 0,
            errors
        };
    }
    
    validateTooltipConfig(config) {
        const errors = [];
        const validPlacements = ['auto', 'top', 'right', 'bottom', 'left'];
        
        if (!validPlacements.includes(config.placement)) {
            errors.push(`placement 必须是以下值之一: ${validPlacements.join(', ')}`);
        }
        
        if (typeof config.delay !== 'number' && typeof config.delay !== 'object') {
            errors.push('delay 必须是 number 或 object');
        }
        
        return {
            valid: errors.length === 0,
            errors
        };
    }
    
    validateCarouselConfig(config) {
        const errors = [];
        
        if (typeof config.interval !== 'number' && config.interval !== false) {
            errors.push('interval 必须是 number 或 false');
        }
        
        if (config.interval !== false && config.interval < 1000) {
            errors.push('interval 不能小于 1000ms');
        }
        
        return {
            valid: errors.length === 0,
            errors
        };
    }
}

// 使用配置管理器
const configManager = new PluginConfigManager();

// 示例:创建自定义配置的模态框
function createCustomModal(modalId, userConfig = {}) {
    const config = configManager.mergeConfig('modal', userConfig);
    const validation = configManager.validateConfig('modal', config);
    
    if (!validation.valid) {
        console.error('模态框配置错误:', validation.errors);
        return null;
    }
    
    const modalElement = document.getElementById(modalId);
    return new bootstrap.Modal(modalElement, config);
}

// 示例:创建自定义配置的工具提示
function createCustomTooltip(element, userConfig = {}) {
    const config = configManager.mergeConfig('tooltip', userConfig);
    const validation = configManager.validateConfig('tooltip', config);
    
    if (!validation.valid) {
        console.error('工具提示配置错误:', validation.errors);
        return null;
    }
    
    return new bootstrap.Tooltip(element, config);
}

// 使用示例
const customModal = createCustomModal('exampleModal', {
    backdrop: 'static',
    keyboard: false,
    focus: true
});

const customTooltip = createCustomTooltip(document.getElementById('customTooltipBtn'), {
    placement: 'bottom',
    delay: { show: 1000, hide: 500 },
    html: true,
    title: '<strong>自定义</strong> 工具提示内容'
});

2. 自定义插件开发

// 自定义插件:消息通知系统
class NotificationPlugin {
    constructor(options = {}) {
        this.options = {
            container: 'body',
            position: 'top-right',
            autoClose: true,
            closeDelay: 5000,
            maxNotifications: 5,
            animation: 'fade',
            ...options
        };
        
        this.notifications = [];
        this.container = null;
        
        this.init();
    }
    
    init() {
        this.createContainer();
        this.injectStyles();
    }
    
    createContainer() {
        this.container = document.createElement('div');
        this.container.className = `notification-container position-${this.options.position}`;
        
        const targetContainer = typeof this.options.container === 'string' 
            ? document.querySelector(this.options.container)
            : this.options.container;
            
        targetContainer.appendChild(this.container);
    }
    
    injectStyles() {
        if (document.getElementById('notification-styles')) return;
        
        const style = document.createElement('style');
        style.id = 'notification-styles';
        style.textContent = `
            .notification-container {
                position: fixed;
                z-index: 1060;
                pointer-events: none;
            }
            
            .notification-container.position-top-right {
                top: 1rem;
                right: 1rem;
            }
            
            .notification-container.position-top-left {
                top: 1rem;
                left: 1rem;
            }
            
            .notification-container.position-bottom-right {
                bottom: 1rem;
                right: 1rem;
            }
            
            .notification-container.position-bottom-left {
                bottom: 1rem;
                left: 1rem;
            }
            
            .notification-item {
                pointer-events: auto;
                margin-bottom: 0.5rem;
                min-width: 300px;
                max-width: 400px;
                opacity: 0;
                transform: translateX(100%);
                transition: all 0.3s ease;
            }
            
            .notification-item.show {
                opacity: 1;
                transform: translateX(0);
            }
            
            .notification-item.hide {
                opacity: 0;
                transform: translateX(100%);
            }
            
            .notification-progress {
                position: absolute;
                bottom: 0;
                left: 0;
                height: 3px;
                background-color: rgba(255, 255, 255, 0.3);
                transition: width linear;
            }
        `;
        
        document.head.appendChild(style);
    }
    
    show(message, type = 'info', options = {}) {
        const notification = this.createNotification(message, type, options);
        
        // 检查最大通知数量
        if (this.notifications.length >= this.options.maxNotifications) {
            this.remove(this.notifications[0]);
        }
        
        this.notifications.push(notification);
        this.container.appendChild(notification.element);
        
        // 触发显示动画
        setTimeout(() => {
            notification.element.classList.add('show');
        }, 10);
        
        // 自动关闭
        if (this.options.autoClose && notification.options.autoClose !== false) {
            this.startAutoClose(notification);
        }
        
        return notification;
    }
    
    createNotification(message, type, options) {
        const id = 'notification-' + Date.now() + Math.random().toString(36).substr(2, 9);
        const notificationOptions = {
            autoClose: this.options.autoClose,
            closeDelay: this.options.closeDelay,
            showProgress: true,
            ...options
        };
        
        const element = document.createElement('div');
        element.className = `notification-item alert alert-${type} alert-dismissible position-relative`;
        element.setAttribute('role', 'alert');
        element.innerHTML = `
            <div class="d-flex align-items-center">
                <div class="flex-grow-1">${message}</div>
                <button type="button" class="btn-close" aria-label="Close"></button>
            </div>
            ${notificationOptions.showProgress ? '<div class="notification-progress"></div>' : ''}
        `;
        
        const notification = {
            id,
            element,
            type,
            message,
            options: notificationOptions,
            timer: null,
            progressTimer: null
        };
        
        // 绑定关闭按钮事件
        const closeBtn = element.querySelector('.btn-close');
        closeBtn.addEventListener('click', () => {
            this.remove(notification);
        });
        
        // 鼠标悬停暂停自动关闭
        element.addEventListener('mouseenter', () => {
            this.pauseAutoClose(notification);
        });
        
        element.addEventListener('mouseleave', () => {
            this.resumeAutoClose(notification);
        });
        
        return notification;
    }
    
    startAutoClose(notification) {
        if (!notification.options.autoClose) return;
        
        const delay = notification.options.closeDelay;
        
        // 设置自动关闭定时器
        notification.timer = setTimeout(() => {
            this.remove(notification);
        }, delay);
        
        // 显示进度条
        if (notification.options.showProgress) {
            const progressBar = notification.element.querySelector('.notification-progress');
            if (progressBar) {
                progressBar.style.width = '100%';
                progressBar.style.transitionDuration = delay + 'ms';
                
                setTimeout(() => {
                    progressBar.style.width = '0%';
                }, 10);
            }
        }
    }
    
    pauseAutoClose(notification) {
        if (notification.timer) {
            clearTimeout(notification.timer);
            notification.timer = null;
        }
        
        const progressBar = notification.element.querySelector('.notification-progress');
        if (progressBar) {
            progressBar.style.animationPlayState = 'paused';
        }
    }
    
    resumeAutoClose(notification) {
        if (!notification.options.autoClose || notification.timer) return;
        
        // 重新开始自动关闭
        this.startAutoClose(notification);
        
        const progressBar = notification.element.querySelector('.notification-progress');
        if (progressBar) {
            progressBar.style.animationPlayState = 'running';
        }
    }
    
    remove(notification) {
        if (notification.timer) {
            clearTimeout(notification.timer);
        }
        
        notification.element.classList.remove('show');
        notification.element.classList.add('hide');
        
        setTimeout(() => {
            if (notification.element.parentNode) {
                notification.element.parentNode.removeChild(notification.element);
            }
            
            const index = this.notifications.indexOf(notification);
            if (index > -1) {
                this.notifications.splice(index, 1);
            }
        }, 300);
    }
    
    // 便捷方法
    success(message, options) {
        return this.show(message, 'success', options);
    }
    
    error(message, options) {
        return this.show(message, 'danger', options);
    }
    
    warning(message, options) {
        return this.show(message, 'warning', options);
    }
    
    info(message, options) {
        return this.show(message, 'info', options);
    }
    
    // 清除所有通知
    clear() {
        this.notifications.forEach(notification => {
            this.remove(notification);
        });
    }
    
    // 销毁插件
    destroy() {
        this.clear();
        if (this.container && this.container.parentNode) {
            this.container.parentNode.removeChild(this.container);
        }
    }
}

// 全局通知实例
const notification = new NotificationPlugin({
    position: 'top-right',
    maxNotifications: 3,
    closeDelay: 4000
});

// 使用示例
function showNotificationExamples() {
    notification.success('操作成功完成!');
    
    setTimeout(() => {
        notification.warning('这是一个警告消息');
    }, 1000);
    
    setTimeout(() => {
        notification.error('发生了一个错误');
    }, 2000);
    
    setTimeout(() => {
        notification.info('这是一条信息提示', {
            autoClose: false // 不自动关闭
        });
    }, 3000);
}

2. 智能工具提示系统

<!-- 智能工具提示系统 -->
<div class="container my-5">
    <h2>智能工具提示系统</h2>
    
    <div class="row">
        <div class="col-md-6">
            <h4>表单帮助提示</h4>
            <form id="smartForm">
                <div class="mb-3">
                    <label for="username" class="form-label">用户名</label>
                    <input type="text" 
                           class="form-control" 
                           id="username" 
                           data-smart-tooltip="true"
                           data-tooltip-type="validation"
                           data-validation-rules='["required", "minLength:3", "maxLength:20"]'
                           placeholder="请输入用户名">
                </div>
                
                <div class="mb-3">
                    <label for="email" class="form-label">邮箱地址</label>
                    <input type="email" 
                           class="form-control" 
                           id="email" 
                           data-smart-tooltip="true"
                           data-tooltip-type="validation"
                           data-validation-rules='["required", "email"]'
                           placeholder="请输入邮箱地址">
                </div>
                
                <div class="mb-3">
                    <label for="password" class="form-label">密码</label>
                    <input type="password" 
                           class="form-control" 
                           id="password" 
                           data-smart-tooltip="true"
                           data-tooltip-type="validation"
                           data-validation-rules='["required", "minLength:8", "hasNumber", "hasSpecialChar"]'
                           placeholder="请输入密码">
                </div>
                
                <button type="submit" class="btn btn-primary">提交</button>
            </form>
        </div>
        
        <div class="col-md-6">
            <h4>功能说明提示</h4>
            <div class="feature-demo">
                <button class="btn btn-outline-primary me-2" 
                        data-smart-tooltip="true"
                        data-tooltip-type="feature"
                        data-feature-title="保存功能"
                        data-feature-description="点击此按钮可以保存当前的工作进度。支持自动保存和手动保存两种模式。">
                    <i class="bi bi-save"></i> 保存
                </button>
                
                <button class="btn btn-outline-success me-2" 
                        data-smart-tooltip="true"
                        data-tooltip-type="feature"
                        data-feature-title="导出功能"
                        data-feature-description="将当前数据导出为 Excel、PDF 或 CSV 格式。支持自定义导出字段和格式。">
                    <i class="bi bi-download"></i> 导出
                </button>
                
                <button class="btn btn-outline-warning" 
                        data-smart-tooltip="true"
                        data-tooltip-type="feature"
                        data-feature-title="分享功能"
                        data-feature-description="生成分享链接,可以通过邮件、社交媒体或直接复制链接的方式分享给他人。">
                    <i class="bi bi-share"></i> 分享
                </button>
            </div>
        </div>
    </div>
</div>

<script>
// 智能工具提示系统
class SmartTooltipSystem {
    constructor() {
        this.tooltips = new Map();
        this.validationRules = {
            required: (value) => value.trim() !== '',
            email: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
            minLength: (value, length) => value.length >= parseInt(length),
            maxLength: (value, length) => value.length <= parseInt(length),
            hasNumber: (value) => /\d/.test(value),
            hasSpecialChar: (value) => /[!@#$%^&*(),.?":{}|<>]/.test(value)
        };
        
        this.init();
    }
    
    init() {
        this.initializeTooltips();
        this.bindEvents();
    }
    
    initializeTooltips() {
        const elements = document.querySelectorAll('[data-smart-tooltip="true"]');
        
        elements.forEach(element => {
            const tooltipType = element.getAttribute('data-tooltip-type');
            
            switch (tooltipType) {
                case 'validation':
                    this.createValidationTooltip(element);
                    break;
                case 'feature':
                    this.createFeatureTooltip(element);
                    break;
                default:
                    this.createBasicTooltip(element);
            }
        });
    }
    
    createValidationTooltip(element) {
        const tooltip = new bootstrap.Tooltip(element, {
            title: '请输入有效的值',
            placement: 'right',
            trigger: 'manual',
            html: true,
            customClass: 'validation-tooltip'
        });
        
        this.tooltips.set(element, {
            instance: tooltip,
            type: 'validation'
        });
    }
    
    createFeatureTooltip(element) {
        const title = element.getAttribute('data-feature-title');
        const description = element.getAttribute('data-feature-description');
        
        const content = `
            <div class="feature-tooltip">
                <h6 class="mb-2">${title}</h6>
                <p class="mb-0 small">${description}</p>
            </div>
        `;
        
        const tooltip = new bootstrap.Tooltip(element, {
            title: content,
            placement: 'top',
            html: true,
            delay: { show: 500, hide: 100 },
            customClass: 'feature-tooltip'
        });
        
        this.tooltips.set(element, {
            instance: tooltip,
            type: 'feature'
        });
    }
    
    createBasicTooltip(element) {
        const tooltip = new bootstrap.Tooltip(element);
        
        this.tooltips.set(element, {
            instance: tooltip,
            type: 'basic'
        });
    }
    
    bindEvents() {
        // 绑定表单验证事件
        document.querySelectorAll('[data-tooltip-type="validation"]').forEach(element => {
            element.addEventListener('input', (e) => {
                this.validateField(e.target);
            });
            
            element.addEventListener('blur', (e) => {
                this.validateField(e.target);
            });
            
            element.addEventListener('focus', (e) => {
                this.showValidationHint(e.target);
            });
        });
    }
    
    validateField(element) {
        const rules = JSON.parse(element.getAttribute('data-validation-rules') || '[]');
        const value = element.value;
        const errors = [];
        
        rules.forEach(rule => {
            if (rule.includes(':')) {
                const [ruleName, ruleValue] = rule.split(':');
                if (!this.validationRules[ruleName](value, ruleValue)) {
                    errors.push(this.getErrorMessage(ruleName, ruleValue));
                }
            } else {
                if (!this.validationRules[rule](value)) {
                    errors.push(this.getErrorMessage(rule));
                }
            }
        });
        
        this.updateValidationTooltip(element, errors);
    }
    
    showValidationHint(element) {
        const rules = JSON.parse(element.getAttribute('data-validation-rules') || '[]');
        const hints = rules.map(rule => {
            if (rule.includes(':')) {
                const [ruleName, ruleValue] = rule.split(':');
                return this.getHintMessage(ruleName, ruleValue);
            } else {
                return this.getHintMessage(rule);
            }
        });
        
        const tooltipData = this.tooltips.get(element);
        if (tooltipData && hints.length > 0) {
            const content = `
                <div class="validation-hint">
                    <h6 class="mb-2">输入要求:</h6>
                    <ul class="mb-0 small">
                        ${hints.map(hint => `<li>${hint}</li>`).join('')}
                    </ul>
                </div>
            `;
            
            tooltipData.instance.setContent({ '.tooltip-inner': content });
            tooltipData.instance.show();
        }
    }
    
    updateValidationTooltip(element, errors) {
        const tooltipData = this.tooltips.get(element);
        if (!tooltipData) return;
        
        if (errors.length > 0) {
            element.classList.add('is-invalid');
            element.classList.remove('is-valid');
            
            const content = `
                <div class="validation-error">
                    <h6 class="mb-2 text-danger">输入错误:</h6>
                    <ul class="mb-0 small">
                        ${errors.map(error => `<li class="text-danger">${error}</li>`).join('')}
                    </ul>
                </div>
            `;
            
            tooltipData.instance.setContent({ '.tooltip-inner': content });
            tooltipData.instance.show();
        } else {
            element.classList.remove('is-invalid');
            element.classList.add('is-valid');
            tooltipData.instance.hide();
        }
    }
    
    getErrorMessage(rule, value) {
        const messages = {
            required: '此字段为必填项',
            email: '请输入有效的邮箱地址',
            minLength: `最少需要 ${value} 个字符`,
            maxLength: `最多允许 ${value} 个字符`,
            hasNumber: '必须包含至少一个数字',
            hasSpecialChar: '必须包含至少一个特殊字符'
        };
        
        return messages[rule] || '输入格式不正确';
    }
    
    getHintMessage(rule, value) {
        const hints = {
            required: '此字段不能为空',
            email: '格式:example@domain.com',
            minLength: `至少 ${value} 个字符`,
            maxLength: `最多 ${value} 个字符`,
            hasNumber: '包含数字',
            hasSpecialChar: '包含特殊字符 (!@#$%^&* 等)'
        };
        
        return hints[rule] || '请按要求输入';
    }
    
    // 销毁所有工具提示
    dispose() {
        this.tooltips.forEach(tooltipData => {
            tooltipData.instance.dispose();
        });
        this.tooltips.clear();
    }
}

// 初始化智能工具提示系统
const smartTooltipSystem = new SmartTooltipSystem();

// 添加自定义样式
const style = document.createElement('style');
style.textContent = `
    .validation-tooltip .tooltip-inner {
        background-color: #dc3545;
        color: white;
        max-width: 300px;
    }
    
    .validation-tooltip .tooltip-arrow::before {
        border-right-color: #dc3545;
    }
    
    .feature-tooltip .tooltip-inner {
        background-color: #495057;
        color: white;
        max-width: 350px;
        text-align: left;
    }
    
    .validation-hint ul,
    .validation-error ul {
        padding-left: 1rem;
        margin-bottom: 0;
    }
    
    .validation-hint li,
    .validation-error li {
        margin-bottom: 0.25rem;
    }
`;
document.head.appendChild(style);
</script>