interface NotificationOptions {
    title?: string;
    content?: string;
    bars?: number[];
    autoClose?: number;
    type?: ToastType;
}

interface DialogCreateOptions {
    showHeader?: boolean;
    showFooter?: boolean;
}

class UiFeedbackService {
    private readonly _notificationInstances: Set<HTMLElement> = new Set();
    private _tooltipFrameId: number | null = null;
    private _tooltipTarget: HTMLElement | null = null;
    private _tooltipElement: HTMLElement | null = null;
    private _tooltipLastTop: number | null = null;
    private _tooltipLastLeft: number | null = null;
    private _tooltipLastBottomState: boolean | null = null;
    private readonly _tooltipRepositionHandler = (): void => {
        this.scheduleTooltipPositionUpdate();
    };

    private static readonly DEFAULT_NOTIFICATION_GAP = 15;
    /**
     * 创建通用的遮罩层和对话框结构
     * @param {string} title - 标题
     * @param {string} content - 内容
     * @param {DialogType} type - 类型:DialogType.Alert | DialogType.Confirm | DialogType.Prompt
     * @returns {{ overlay: HTMLElement; dialog: HTMLElement }} 包含 overlay 和 dialog 的对象
     */
    public createDialog(
        title: string,
        content: string | HTMLElement,
        type: DialogType = DialogType.Alert,
        options: DialogCreateOptions = {}
    ): { overlay: HTMLElement; dialog: HTMLElement } {
        const overlay = document.createElement('div');
        overlay.className = 'dialog-overlay';

        const dialog = document.createElement('div');
        dialog.className = 'dialog';

        const showHeader = options.showHeader !== false;
        const defaultShowFooter =
            type === DialogType.Alert || type === DialogType.Confirm || type === DialogType.Prompt;
        const showFooter = options.showFooter ?? defaultShowFooter;

        if (showHeader) {
            const header = document.createElement('div');
            header.className = 'dialog__header';
            header.innerHTML = `
                <span>${title || '提示'}</span>
                <button type="button" class="dialog__close" aria-label="关闭">
                    <i class="fa fa-times"></i>
                </button>
            `;
            dialog.appendChild(header);
        }

        const body = document.createElement('div');
        body.className = 'dialog__body';
        if (typeof content === 'string') {
            body.innerHTML = `<div class="dialog__message">${content}</div>`;
        } else {
            body.appendChild(content);
        }

        if (type === DialogType.Prompt) {
            const inputWrapper = document.createElement('div');
            inputWrapper.className = 'dialog__input-wrapper';
            inputWrapper.innerHTML = `<input type="text" class="dialog__input" autocomplete="off">`;
            body.appendChild(inputWrapper);
        }

        dialog.appendChild(body);

        if (showFooter) {
            const footer = document.createElement('div');
            footer.className = 'dialog__footer';

            if (type === DialogType.Alert) {
                footer.innerHTML = `
                    <button
                        type="button"
                        class="dialog__btn dialog__btn--primary"
                        data-action="confirm"
                    >确定</button>
                `;
            } else if (type === DialogType.Confirm || type === DialogType.Prompt) {
                footer.innerHTML = `
                    <button
                        type="button"
                        class="dialog__btn dialog__btn--default"
                        data-action="cancel"
                    >取消</button>
                    <button
                        type="button"
                        class="dialog__btn dialog__btn--primary"
                        data-action="confirm"
                    >确定</button>
                `;
            }

            dialog.appendChild(footer);
        }

        overlay.appendChild(dialog);
        document.body.appendChild(overlay);

        // 强制重排以触发动画
        overlay.offsetHeight;
        overlay.classList.add('dialog-overlay--visible');

        return { overlay, dialog };
    }

    /**
     * 关闭对话框并移除 DOM 元素
     * @param {HTMLElement} overlay - 遮罩层元素
     */
    public closeDialog(overlay: HTMLElement): void {
        overlay.classList.remove('dialog-overlay--visible');
        overlay.addEventListener(
            'transitionend',
            () => {
                if (overlay.parentNode) {
                    overlay.parentNode.removeChild(overlay);
                }
            },
            { once: true }
        );
    }

    /**
     * 显示模态提示框
     * @param {string} message - 消息内容
     * @param {string} title - 对话框标题,默认为 '提示'
     * @returns {Promise<void>} 当用户点击确定或关闭对话框时,Promise 解析
     */
    public alert(message: string, title: string = '提示'): Promise<void> {
        return new Promise((resolve) => {
            const { overlay, dialog } = this.createDialog(title, message, DialogType.Alert);

            const handleClose = () => {
                this.closeDialog(overlay);
                resolve();
            };

            dialog.querySelector('[data-action="confirm"]').addEventListener('click', handleClose);
            dialog.querySelector('.dialog__close').addEventListener('click', handleClose);

            // 聚焦主要按钮
            (dialog.querySelector('.dialog__btn--primary') as HTMLElement)?.focus();
        });
    }

    /**
     * 显示模态确认框
     * @param {string} message - 消息内容
     * @param {string} title - 对话框标题,默认为 '确认'
     * @returns {Promise<boolean>} 用户点击确定返回 true,取消返回 false
     */
    public confirm(message: string, title: string = '确认'): Promise<boolean> {
        return new Promise((resolve) => {
            const { overlay, dialog } = this.createDialog(title, message, DialogType.Confirm);

            const handleConfirm = () => {
                this.closeDialog(overlay);
                resolve(true);
            };

            const handleCancel = () => {
                this.closeDialog(overlay);
                resolve(false);
            };

            dialog
                .querySelector('[data-action="confirm"]')
                .addEventListener('click', handleConfirm);
            dialog.querySelector('[data-action="cancel"]').addEventListener('click', handleCancel);
            dialog.querySelector('.dialog__close').addEventListener('click', handleCancel);

            (dialog.querySelector('.dialog__btn--primary') as HTMLElement)?.focus();
        });
    }

    /**
     * 显示模态输入框
     * @param {string} message - 提示消息
     * @param {string} title - 对话框标题,默认为 '输入'
     * @param {string} defaultValue - 输入框默认值
     * @returns {Promise<string | null>} 用户点击确定返回输入值,取消返回 null
     */
    public prompt(
        message: string,
        title: string = '输入',
        defaultValue: string = ''
    ): Promise<string | null> {
        return new Promise((resolve) => {
            const { overlay, dialog } = this.createDialog(title, message, DialogType.Prompt);
            const input = dialog.querySelector('.dialog__input') as HTMLInputElement;
            if (!input) {
                console.error('Prompt dialog input element not found');
                resolve(null);
                return;
            }
            input.value = defaultValue;

            const handleConfirm = () => {
                const val = input.value;
                this.closeDialog(overlay);
                resolve(val);
            };

            const handleCancel = () => {
                this.closeDialog(overlay);
                resolve(null);
            };

            dialog
                .querySelector('[data-action="confirm"]')
                .addEventListener('click', handleConfirm);
            dialog.querySelector('[data-action="cancel"]').addEventListener('click', handleCancel);
            dialog.querySelector('.dialog__close').addEventListener('click', handleCancel);

            // 处理输入框的 Enter 和 Escape 键
            input.addEventListener('keydown', (e) => {
                if (e.key === 'Enter') {
                    handleConfirm();
                }
                if (e.key === 'Escape') {
                    handleCancel();
                }
            });

            setTimeout(() => input.focus(), 50);
        });
    }

    /**
     * 显示 Toast 消息
     * @param {string} message - 消息内容
     * @param {ToastType} type - 消息类型,默认为 Info
     * @param {number} duration - 显示持续时间,单位毫秒,默认为 3000ms
     * @param {ToastPosition} position - 显示位置,默认为 TopCenter
     */
    public toast(
        message: string,
        type: ToastType = ToastType.Info,
        duration: number = 3000,
        position: ToastPosition = ToastPosition.TopCenter
    ): void {
        const container = this.getToastContainer(position);

        const toast = document.createElement('div');
        toast.className = `toast toast--${type}`;

        let iconClass = 'fa-info-circle';
        if (type === ToastType.Success) {
            iconClass = 'fa-check-circle';
        }
        if (type === ToastType.Warning) {
            iconClass = 'fa-exclamation-triangle';
        }
        if (type === ToastType.Error) {
            iconClass = 'fa-times-circle';
        }

        toast.innerHTML = `
                <i class="toast__icon fa ${iconClass}"></i>
                <span class="toast__content">${message}</span>
            `;

        container.appendChild(toast);

        // 计时器管理
        let timeoutId: number | null = null;
        let remainingTime: number = duration;
        let startTime: number = Date.now();

        const closeToast = () => {
            toast.classList.add('is-leaving');
            toast.addEventListener(
                'animationend',
                () => {
                    if (toast.parentNode) {
                        toast.parentNode.removeChild(toast);
                    }
                },
                { once: true }
            );
        };

        const startTimer = () => {
            startTime = Date.now();
            timeoutId = setTimeout(closeToast, remainingTime);
        };

        const pauseTimer = () => {
            if (timeoutId !== null) {
                clearTimeout(timeoutId);
                timeoutId = null;
                remainingTime -= Date.now() - startTime;
                // 确保剩余时间不为负数
                if (remainingTime < 0) {
                    remainingTime = 0;
                }
            }
        };

        const resumeTimer = () => {
            if (remainingTime > 0) {
                startTimer();
            } else {
                closeToast();
            }
        };

        // 鼠标悬停时暂停,移开时继续
        toast.addEventListener('mouseenter', pauseTimer);
        toast.addEventListener('mouseleave', resumeTimer);

        // 移动端触摸支持
        toast.addEventListener('touchstart', pauseTimer, { passive: true });
        toast.addEventListener('touchend', resumeTimer, { passive: true });

        // 启动初始计时器
        startTimer();
    }

    /**
     * 获取或创建指定位置的 Toast 容器
     * @param {ToastPosition} position - 位置枚举值
     * @returns {HTMLElement} Toast 容器元素
     */
    private getToastContainer(position: ToastPosition): HTMLElement {
        // 使用 data-position 属性查找容器
        let container = document.querySelector(
            `.toast-container[data-position="${position}"]`
        ) as HTMLElement | null;

        if (!container) {
            container = document.createElement('div');
            container.className = `toast-container toast-container--${position}`;
            container.dataset.position = position;
            document.body.appendChild(container);
        }

        return container;
    }

    /**
     * 显示工具提示。
     * @param {HTMLElement} element 目标元素。
     * @param {string} text 提示内容。
     */
    public showTooltip(element: HTMLElement, text: string): void {
        if (!text) {
            return;
        }

        let tooltip = document.getElementById('dialog-tooltip') as HTMLElement | null;
        if (!tooltip) {
            tooltip = document.createElement('div');
            tooltip.id = 'dialog-tooltip';
            tooltip.className = 'dialog-tooltip';
            document.body.appendChild(tooltip);
        }

        this._tooltipTarget = element;
        this._tooltipElement = tooltip;
        this._tooltipLastTop = null;
        this._tooltipLastLeft = null;
        this._tooltipLastBottomState = null;
        tooltip.textContent = text;
        tooltip.classList.remove('is-bottom');
        tooltip.classList.add('is-visible');
        this.bindTooltipPositionEvents();
        this.scheduleTooltipPositionUpdate();
    }

    /**
     * 隐藏工具提示。
     */
    public hideTooltip(): void {
        const tooltip = this._tooltipElement ?? document.getElementById('dialog-tooltip');
        if (tooltip) {
            tooltip.classList.remove('is-visible');
        }

        this.unbindTooltipPositionEvents();
        this.cancelTooltipPositionUpdate();
        this._tooltipTarget = null;
        this._tooltipElement = null;
        this._tooltipLastTop = null;
        this._tooltipLastLeft = null;
        this._tooltipLastBottomState = null;
    }

    private bindTooltipPositionEvents(): void {
        window.addEventListener('scroll', this._tooltipRepositionHandler, {
            passive: true,
            capture: true,
        });
        window.addEventListener('resize', this._tooltipRepositionHandler, { passive: true });
    }

    private unbindTooltipPositionEvents(): void {
        window.removeEventListener('scroll', this._tooltipRepositionHandler, true);
        window.removeEventListener('resize', this._tooltipRepositionHandler);
    }

    private scheduleTooltipPositionUpdate(): void {
        if (this._tooltipFrameId !== null) {
            return;
        }

        this._tooltipFrameId = window.requestAnimationFrame(() => {
            this._tooltipFrameId = null;
            this.updateTooltipPosition();
        });
    }

    private cancelTooltipPositionUpdate(): void {
        if (this._tooltipFrameId === null) {
            return;
        }

        window.cancelAnimationFrame(this._tooltipFrameId);
        this._tooltipFrameId = null;
    }

    private updateTooltipPosition(): void {
        const element = this._tooltipTarget;
        const tooltip = this._tooltipElement;

        if (element === null || tooltip === null) {
            return;
        }

        if (!document.body.contains(element)) {
            this.hideTooltip();
            return;
        }

        const rect = element.getBoundingClientRect();
        if (rect.width === 0 && rect.height === 0) {
            this.hideTooltip();
            return;
        }

        const tooltipRect = tooltip.getBoundingClientRect();

        let top = rect.top - tooltipRect.height - 8;
        let left = rect.left + (rect.width - tooltipRect.width) / 2;
        const isBottom = top < 0;

        if (isBottom) {
            top = rect.bottom + 8;
        }

        if (left < 0) {
            left = 4;
        } else if (left + tooltipRect.width > window.innerWidth) {
            left = window.innerWidth - tooltipRect.width - 4;
        }

        const roundedTop = Math.round(top);
        const roundedLeft = Math.round(left);

        if (this._tooltipLastBottomState !== isBottom) {
            tooltip.classList.toggle('is-bottom', isBottom);
            this._tooltipLastBottomState = isBottom;
        }

        if (this._tooltipLastTop !== roundedTop) {
            tooltip.style.top = `${roundedTop}px`;
            this._tooltipLastTop = roundedTop;
        }

        if (this._tooltipLastLeft !== roundedLeft) {
            tooltip.style.left = `${roundedLeft}px`;
            this._tooltipLastLeft = roundedLeft;
        }
    }

    /**
     * 显示通知框。
     * @param {NotificationOptions} option 通知配置项。
     * @returns {HTMLElement} 通知框元素。
     */
    public showNotification(option: NotificationOptions = {}): HTMLElement {
        const setting: Required<NotificationOptions> = {
            title: '',
            content: '',
            bars: [],
            autoClose: 0,
            type: ToastType.Info,
            ...option,
        };

        const top = this.calculateNotificationTopPosition();
        const box = this.createNotificationBox(setting);
        box.style.top = `${top}px`;

        document.body.appendChild(box);
        this._notificationInstances.add(box);

        if (setting.autoClose > 0) {
            window.setTimeout(() => {
                this.closeNotification(box);
            }, setting.autoClose);
        }

        requestAnimationFrame(() => {
            box.classList.add('notification-box--enter');
        });

        return box;
    }

    /**
     * 设置通知框内容。
     * @param {HTMLElement} box 通知框元素。
     * @param {string} content 新内容。
     */
    public setNotificationContent(box: HTMLElement, content: string): void {
        if (!this.validateNotificationBox(box)) {
            return;
        }

        const contentEl = box.querySelector('.notification-box__content') as HTMLElement | null;
        if (contentEl) {
            contentEl.textContent = String(content || '');
        }
    }

    /**
     * 设置通知框标题。
     * @param {HTMLElement} box 通知框元素。
     * @param {string} title 新标题。
     */
    public setNotificationTitle(box: HTMLElement, title: string): void {
        if (!this.validateNotificationBox(box)) {
            return;
        }

        const titleEl = box.querySelector('.notification-box__title') as HTMLElement | null;
        if (titleEl) {
            titleEl.textContent = String(title || '');
        }
    }

    /**
     * 设置通知框进度。
     * @param {HTMLElement} box 通知框元素。
     * @param {number[]} values 进度值数组。
     */
    public setNotificationProgress(box: HTMLElement, values: number[]): void {
        if (!this.validateNotificationBox(box) || !Array.isArray(values)) {
            return;
        }

        const progressContainers = box.querySelectorAll('.notification-box__progress');

        values.forEach((value, index) => {
            if (typeof value === 'number' && index < progressContainers.length) {
                const clampedValue = Math.max(0, Math.min(100, value));
                const progress = progressContainers[index] as HTMLElement;
                const track = progress.querySelector(
                    '.notification-box__progress-track'
                ) as HTMLElement | null;
                const bar = progress.querySelector(
                    '.notification-box__progress-bar'
                ) as HTMLElement | null;
                const textValue = `${clampedValue.toFixed(1)}%`;

                if (bar) {
                    if (clampedValue > 0) {
                        bar.style.width = `${clampedValue}%`;
                        bar.style.display = '';
                    } else {
                        bar.style.width = '0%';
                        bar.style.display = 'none';
                    }
                }

                const textInternal = track
                    ? (track.querySelector(
                          '.notification-box__progress-text'
                      ) as HTMLElement | null)
                    : null;
                const textExternal = progress.querySelector(
                    '.notification-box__progress-text--external'
                ) as HTMLElement | null;

                if (textInternal) {
                    textInternal.textContent = textValue;
                } else if (textExternal) {
                    textExternal.remove();
                    const newText = document.createElement('span');
                    newText.className = 'notification-box__progress-text';
                    newText.textContent = textValue;
                    if (track) {
                        track.appendChild(newText);
                    }
                } else if (track) {
                    const newText = document.createElement('span');
                    newText.className = 'notification-box__progress-text';
                    newText.textContent = textValue;
                    track.appendChild(newText);
                }
            }
        });
    }

    /**
     * 关闭通知框。
     * @param {HTMLElement} box 要关闭的通知框。
     */
    public closeNotification(box: HTMLElement): void {
        if (!this.validateNotificationBox(box)) {
            return;
        }

        box.classList.add('notification-box--exit');

        window.setTimeout(() => {
            if (box.parentNode) {
                this._notificationInstances.delete(box);
                box.parentNode.removeChild(box);
                this.recalculateNotificationPositions();
            }
        }, 300);
    }

    /**
     * 关闭所有通知框。
     */
    public closeAllNotifications(): void {
        const boxes = Array.from(this._notificationInstances);
        boxes.forEach((box) => {
            this.closeNotification(box);
        });
    }

    /**
     * 获取活跃通知数量。
     * @returns {number} 当前通知数量。
     */
    public getNotificationCount(): number {
        return this._notificationInstances.size;
    }

    private validateNotificationBox(box: HTMLElement): boolean {
        return Boolean(box && box.parentNode && this._notificationInstances.has(box));
    }

    private calculateNotificationTopPosition(): number {
        let top = UiFeedbackService.DEFAULT_NOTIFICATION_GAP;
        this._notificationInstances.forEach((box) => {
            if (box.offsetParent !== null) {
                top += box.offsetHeight + UiFeedbackService.DEFAULT_NOTIFICATION_GAP;
            }
        });
        return top;
    }

    private recalculateNotificationPositions(): void {
        let currentTop = UiFeedbackService.DEFAULT_NOTIFICATION_GAP;
        this._notificationInstances.forEach((box) => {
            if (box.offsetParent !== null) {
                box.style.top = `${currentTop}px`;
                currentTop += box.offsetHeight + UiFeedbackService.DEFAULT_NOTIFICATION_GAP;
            }
        });
    }

    private createNotificationBox(setting: Required<NotificationOptions>): HTMLElement {
        const box = document.createElement('div');
        box.className = `notification-box notification-box--${setting.type}`;

        if (setting.title) {
            const title = document.createElement('div');
            title.className = 'notification-box__title';
            title.textContent = setting.title;
            box.appendChild(title);
        }

        const container = document.createElement('div');
        container.className = 'notification-box__content-container';

        const content = document.createElement('div');
        content.className = 'notification-box__content';
        content.textContent = setting.content;
        container.appendChild(content);

        this.createNotificationProgressBars(container, setting.bars);

        box.appendChild(container);
        this.addNotificationCloseButton(box);

        return box;
    }

    private createNotificationProgressBars(container: HTMLElement, bars: number[]): void {
        if (!Array.isArray(bars) || bars.length === 0) {
            return;
        }

        bars.forEach((value) => {
            if (typeof value === 'number') {
                const clampedValue = Math.max(0, Math.min(100, value));
                const progress = document.createElement('div');
                progress.className = 'notification-box__progress';

                const track = document.createElement('div');
                track.className = 'notification-box__progress-track';

                const bar = document.createElement('div');
                bar.className = 'notification-box__progress-bar';
                if (clampedValue > 0) {
                    bar.style.width = `${clampedValue}%`;
                    bar.style.display = '';
                } else {
                    bar.style.width = '0%';
                    bar.style.display = 'none';
                }

                const textValue = `${clampedValue.toFixed(1)}%`;
                const text = document.createElement('span');
                text.className = 'notification-box__progress-text';
                text.textContent = textValue;

                track.appendChild(bar);
                track.appendChild(text);
                progress.appendChild(track);
                container.appendChild(progress);
            }
        });
    }

    private addNotificationCloseButton(box: HTMLElement): void {
        const closeBtn = document.createElement('button');
        closeBtn.className = 'notification-box__close';
        closeBtn.innerHTML = '&times;';
        closeBtn.setAttribute('aria-label', '关闭通知');
        closeBtn.addEventListener('click', (e: MouseEvent) => {
            e.preventDefault();
            e.stopPropagation();
            this.closeNotification(box);
        });

        box.appendChild(closeBtn);
    }
}

export enum DialogType {
    Alert = 'alert',
    Prompt = 'prompt',
    Confirm = 'confirm',
    Custom = 'custom',
}

export enum ToastType {
    Success = 'success',
    Error = 'error',
    Info = 'info',
    Warning = 'warning',
}

export enum ToastPosition {
    TopLeft = 'top-left',
    TopRight = 'top-right',
    TopCenter = 'top-center',
    BottomLeft = 'bottom-left',
    BottomRight = 'bottom-right',
    BottomCenter = 'bottom-center',
}

export const uiFeedbackService = new UiFeedbackService();
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

这是一个完整的 UI 反馈服务类,提供了现代 Web 应用中常见的用户交互反馈功能。让我详细解释一下各个部分:

核心功能概览

这个 UiFeedbackService 类实现了以下 UI 反馈组件:

  • 对话框 (Dialog): Alert、Confirm、Prompt
  • Toast 通知 (Toast): 轻量级消息提示
  • 工具提示 (Tooltip): 鼠标悬停提示
  • 通知框 (Notification): 持久化通知消息

接口定义

interface NotificationOptions {
    title?: string;           // 通知标题
    content?: string;         // 通知内容
    bars?: number[];         // 进度条数组
    autoClose?: number;      // 自动关闭时间(ms)
    type?: ToastType;        // 通知类型
}

interface DialogCreateOptions {
    showHeader?: boolean;    // 是否显示头部
    showFooter?: boolean;    // 是否显示底部
}

主要功能模块

1. 对话框系统 (Dialog)

核心方法 createDialog()

  • 创建模态对话框的 DOM 结构
  • 支持自定义标题、内容和类型
  • 自动处理显示/隐藏动画

三种对话框类型

// 提示框 - 只有确认按钮
alert(message: string, title: string = '提示'): Promise<void>

// 确认框 - 确认/取消按钮
confirm(message: string, title: string = '确认'): Promise<boolean>

// 输入框 - 带输入框的确认框
prompt(message: string, title: string = '输入', defaultValue: string = ''): Promise<string | null>

2. Toast 通知系统

特点

  • 支持 4 种类型:Success、Error、Info、Warning
  • 6 种位置:上中下 × 左中右
  • 鼠标悬停暂停自动关闭
  • 移动端触摸支持
toast(
    message: string,
    type: ToastType = ToastType.Info,
    duration: number = 3000,
    position: ToastPosition = ToastPosition.TopCenter
): void

3. 工具提示系统 (Tooltip)

智能定位

  • 自动检测屏幕边界,避免溢出
  • 响应滚动和窗口大小变化
  • 使用 requestAnimationFrame 优化性能
  • 自动处理目标元素移除情况

关键特性

showTooltip(element: HTMLElement, text: string): void
hideTooltip(): void

// 私有方法处理定位逻辑
private updateTooltipPosition(): void  // 核心定位算法
private scheduleTooltipPositionUpdate(): void  // 性能优化

4. 通知框系统 (Notification)

功能丰富

  • 支持标题、内容、多进度条
  • 自动堆叠排列,动态重新计算位置
  • 可手动或自动关闭
  • 实时更新内容和进度

主要 API

showNotification(option: NotificationOptions): HTMLElement
setNotificationContent(box: HTMLElement, content: string): void
setNotificationProgress(box: HTMLElement, values: number[]): void
closeNotification(box: HTMLElement): void

设计亮点

1. 性能优化

  • 使用 requestAnimationFrame 优化 tooltip 重定位
  • 事件委托和及时清理事件监听器
  • DOM 操作批量处理

2. 用户体验

  • 平滑的 CSS 动画过渡
  • 键盘支持 (Enter/Escape)
  • 移动端触摸支持
  • 无障碍访问 (aria-label)

3. 架构设计

  • 单例模式 (export const uiFeedbackService)
  • Promise 基础的异步 API
  • 类型安全的 TypeScript 实现
  • 模块化的方法组织

4. 容错处理

  • 输入验证和边界检查
  • DOM 元素存在性验证
  • 优雅的错误恢复

使用示例

// 对话框
await uiFeedbackService.alert('操作成功!');
const result = await uiFeedbackService.confirm('确认删除吗?');

// Toast
uiFeedbackService.toast('保存成功', ToastType.Success);

// 通知框
const notification = uiFeedbackService.showNotification({
    title: '文件上传',
    content: '正在上传...',
    bars: [0],
    type: ToastType.Info
});

// 更新进度
uiFeedbackService.setNotificationProgress(notification, [50]);

这个服务类提供了完整的用户界面反馈解决方案,代码质量高,功能完善,可以直接用于生产环境。

评论加载中...