欢迎加入开源鸿蒙PC社区:https://harmonypc.csdn.net/

atomgit仓库地址:https://atomgit.com/feng8403000/cms


在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

一、引言

1.1 UI组件的重要性

在桌面应用开发中,UI组件是用户与应用交互的核心。一个优秀的组件系统应该具备:

  • 可复用性:组件可以在应用的不同位置重复使用
  • 可维护性:组件结构清晰,易于维护和扩展
  • 一致性:视觉风格和交互行为保持一致
  • 可访问性:支持键盘导航和屏幕阅读器

1.2 Electron中的UI开发

在Electron应用中开发UI组件需要考虑:

  • 跨平台兼容性:在不同操作系统上保持一致的体验
  • 性能优化:避免渲染性能问题
  • 原生集成:与操作系统原生UI风格保持协调

1.3 本章概述

本章将详细介绍如何在鸿蒙PC Electron应用中开发UI组件,包括:

  • 组件架构设计
  • 核心组件实现
  • 交互模式设计
  • 响应式布局
  • 动画效果实现

二、组件架构设计

2.1 组件分类

根据功能和复杂度,UI组件可以分为以下几类:

分类 描述 示例
基础组件 最基本的UI元素 Button, Input, Label
容器组件 用于组织和布局其他组件 Card, Panel, Layout
数据组件 用于展示和操作数据 List, Table, Tree
表单组件 用于用户输入和表单验证 Form, Select, Checkbox
反馈组件 用于向用户提供反馈 Dialog, Toast, Progress
导航组件 用于应用内导航 Menu, Tab, Breadcrumb

2.2 组件设计原则

// 组件基类
class UIComponent {
    constructor(options = {}) {
        this.element = null;
        this.options = {
            className: '',
            id: '',
            attributes: {},
            ...options
        };
        this.listeners = [];
    }

    render() {
        // 渲染组件
        throw new Error('render() must be implemented');
    }

    mount(parent) {
        // 挂载到DOM
        if (this.element && parent) {
            parent.appendChild(this.element);
        }
    }

    unmount() {
        // 从DOM移除
        if (this.element && this.element.parentNode) {
            this.element.parentNode.removeChild(this.element);
        }
        this.removeAllListeners();
    }

    on(event, callback) {
        // 添加事件监听
        if (this.element) {
            this.element.addEventListener(event, callback);
            this.listeners.push({ event, callback });
        }
    }

    off(event, callback) {
        // 移除事件监听
        if (this.element) {
            this.element.removeEventListener(event, callback);
            this.listeners = this.listeners.filter(
                l => !(l.event === event && l.callback === callback)
            );
        }
    }

    removeAllListeners() {
        // 移除所有事件监听
        this.listeners.forEach(({ event, callback }) => {
            if (this.element) {
                this.element.removeEventListener(event, callback);
            }
        });
        this.listeners = [];
    }

    setAttribute(name, value) {
        // 设置属性
        if (this.element) {
            this.element.setAttribute(name, value);
        }
    }

    getAttribute(name) {
        // 获取属性
        return this.element ? this.element.getAttribute(name) : null;
    }

    addClass(className) {
        // 添加CSS类
        if (this.element) {
            this.element.classList.add(className);
        }
    }

    removeClass(className) {
        // 移除CSS类
        if (this.element) {
            this.element.classList.remove(className);
        }
    }

    toggleClass(className, force) {
        // 切换CSS类
        if (this.element) {
            this.element.classList.toggle(className, force);
        }
    }
}

三、核心组件实现

3.1 Button组件

class Button extends UIComponent {
    constructor(options = {}) {
        super(options);
        this.type = options.type || 'default';
        this.size = options.size || 'normal';
        this.disabled = options.disabled || false;
        this.loading = options.loading || false;
        this.icon = options.icon || null;
        this.text = options.text || '';
        this.onClick = options.onClick || null;
    }

    render() {
        const button = document.createElement('button');
        
        // 设置基本属性
        button.type = 'button';
        button.className = this.getClassName();
        
        if (this.options.id) {
            button.id = this.options.id;
        }
        
        if (this.disabled) {
            button.disabled = true;
        }
        
        // 设置内部HTML
        button.innerHTML = this.getInnerHTML();
        
        // 添加点击事件
        if (this.onClick && !this.disabled) {
            button.addEventListener('click', this.onClick);
            this.listeners.push({ event: 'click', callback: this.onClick });
        }
        
        this.element = button;
        return this.element;
    }

    getClassName() {
        const classes = ['btn'];
        
        // 类型类
        classes.push(`btn-${this.type}`);
        
        // 大小类
        classes.push(`btn-${this.size}`);
        
        // 状态类
        if (this.disabled) {
            classes.push('btn-disabled');
        }
        
        if (this.loading) {
            classes.push('btn-loading');
        }
        
        // 自定义类
        if (this.options.className) {
            classes.push(this.options.className);
        }
        
        return classes.join(' ');
    }

    getInnerHTML() {
        let html = '';
        
        // 图标
        if (this.icon) {
            html += `<span class="btn-icon material-icons">${this.icon}</span>`;
        }
        
        // 文本
        if (this.text) {
            html += `<span class="btn-text">${this.text}</span>`;
        }
        
        // 加载状态
        if (this.loading) {
            html = '<span class="btn-spinner"></span>';
        }
        
        return html;
    }

    setText(text) {
        this.text = text;
        if (this.element) {
            const textElement = this.element.querySelector('.btn-text');
            if (textElement) {
                textElement.textContent = text;
            }
        }
    }

    setLoading(loading) {
        this.loading = loading;
        if (this.element) {
            if (loading) {
                this.element.classList.add('btn-loading');
                this.element.innerHTML = '<span class="btn-spinner"></span>';
            } else {
                this.element.classList.remove('btn-loading');
                this.element.innerHTML = this.getInnerHTML();
            }
        }
    }

    setDisabled(disabled) {
        this.disabled = disabled;
        if (this.element) {
            this.element.disabled = disabled;
            if (disabled) {
                this.element.classList.add('btn-disabled');
            } else {
                this.element.classList.remove('btn-disabled');
            }
        }
    }
}
/* Button组件样式 */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 8px 16px;
    font-family: var(--font-family);
    font-size: 14px;
    font-weight: 500;
    border: none;
    border-radius: 8px;
    cursor: pointer;
    transition: all 0.25s ease;
    outline: none;
}

/* 默认按钮 */
.btn-default {
    background-color: var(--bg-card);
    color: var(--text-primary);
    border: 1px solid var(--border);
}

.btn-default:hover:not(:disabled) {
    background-color: var(--bg-hover);
    border-color: var(--border-light);
}

/* 主要按钮 */
.btn-primary {
    background-color: var(--primary);
    color: var(--bg-paper);
}

.btn-primary:hover:not(:disabled) {
    background-color: var(--primary-light);
}

/* 成功按钮 */
.btn-success {
    background-color: var(--success);
    color: white;
}

.btn-success:hover:not(:disabled) {
    background-color: #43a047;
}

/* 警告按钮 */
.btn-warning {
    background-color: var(--warning);
    color: white;
}

.btn-warning:hover:not(:disabled) {
    background-color: #fb8c00;
}

/* 危险按钮 */
.btn-danger {
    background-color: var(--error);
    color: white;
}

.btn-danger:hover:not(:disabled) {
    background-color: #e53935;
}

/* 按钮大小 */
.btn-sm {
    padding: 4px 12px;
    font-size: 12px;
}

.btn-normal {
    padding: 8px 16px;
    font-size: 14px;
}

.btn-lg {
    padding: 12px 24px;
    font-size: 16px;
}

/* 禁用状态 */
.btn-disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* 加载状态 */
.btn-loading {
    pointer-events: none;
}

.btn-spinner {
    width: 16px;
    height: 16px;
    border: 2px solid rgba(255, 255, 255, 0.3);
    border-top-color: white;
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
}

@keyframes spin {
    to { transform: rotate(360deg); }
}

3.2 Card组件

class Card extends UIComponent {
    constructor(options = {}) {
        super(options);
        this.header = options.header || null;
        this.footer = options.footer || null;
        this.content = options.content || null;
        this.hoverable = options.hoverable || false;
        this.clickable = options.clickable || false;
        this.onClick = options.onClick || null;
    }

    render() {
        const card = document.createElement('div');
        card.className = this.getClassName();
        
        if (this.options.id) {
            card.id = this.options.id;
        }
        
        // 头部
        if (this.header) {
            const header = document.createElement('div');
            header.className = 'card-header';
            header.innerHTML = this.header;
            card.appendChild(header);
        }
        
        // 内容区
        const content = document.createElement('div');
        content.className = 'card-content';
        
        if (this.content) {
            if (typeof this.content === 'string') {
                content.innerHTML = this.content;
            } else {
                content.appendChild(this.content);
            }
        }
        
        card.appendChild(content);
        
        // 底部
        if (this.footer) {
            const footer = document.createElement('div');
            footer.className = 'card-footer';
            footer.innerHTML = this.footer;
            card.appendChild(footer);
        }
        
        // 点击事件
        if (this.clickable && this.onClick) {
            card.style.cursor = 'pointer';
            card.addEventListener('click', this.onClick);
            this.listeners.push({ event: 'click', callback: this.onClick });
        }
        
        this.element = card;
        return this.element;
    }

    getClassName() {
        const classes = ['card'];
        
        if (this.hoverable) {
            classes.push('card-hoverable');
        }
        
        if (this.clickable) {
            classes.push('card-clickable');
        }
        
        if (this.options.className) {
            classes.push(this.options.className);
        }
        
        return classes.join(' ');
    }

    setContent(content) {
        this.content = content;
        if (this.element) {
            const contentElement = this.element.querySelector('.card-content');
            if (contentElement) {
                if (typeof content === 'string') {
                    contentElement.innerHTML = content;
                } else {
                    contentElement.innerHTML = '';
                    contentElement.appendChild(content);
                }
            }
        }
    }

    setHeader(header) {
        this.header = header;
        if (this.element) {
            const headerElement = this.element.querySelector('.card-header');
            if (headerElement) {
                headerElement.innerHTML = header;
            }
        }
    }

    setFooter(footer) {
        this.footer = footer;
        if (this.element) {
            const footerElement = this.element.querySelector('.card-footer');
            if (footerElement) {
                footerElement.innerHTML = footer;
            }
        }
    }
}
/* Card组件样式 */
.card {
    background-color: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: 12px;
    overflow: hidden;
    transition: all 0.25s ease;
}

.card-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 16px;
    border-bottom: 1px solid var(--border);
    background-color: var(--bg-panel);
}

.card-content {
    padding: 16px;
}

.card-footer {
    padding: 12px 16px;
    border-top: 1px solid var(--border);
    background-color: var(--bg-panel);
}

.card-hoverable:hover {
    box-shadow: var(--shadow-lg);
    transform: translateY(-2px);
}

.card-clickable {
    cursor: pointer;
}

.card-clickable:hover {
    border-color: var(--primary);
}

3.3 Input组件

class Input extends UIComponent {
    constructor(options = {}) {
        super(options);
        this.type = options.type || 'text';
        this.placeholder = options.placeholder || '';
        this.value = options.value || '';
        this.label = options.label || null;
        this.required = options.required || false;
        this.disabled = options.disabled || false;
        this.error = options.error || null;
        this.icon = options.icon || null;
        this.onChange = options.onChange || null;
        this.onFocus = options.onFocus || null;
        this.onBlur = options.onBlur || null;
    }

    render() {
        const container = document.createElement('div');
        container.className = 'input-wrapper';
        
        if (this.options.id) {
            container.id = this.options.id;
        }
        
        // 标签
        if (this.label) {
            const label = document.createElement('label');
            label.className = 'input-label';
            label.textContent = this.label;
            
            if (this.required) {
                const requiredMark = document.createElement('span');
                requiredMark.className = 'required-mark';
                requiredMark.textContent = '*';
                label.appendChild(requiredMark);
            }
            
            container.appendChild(label);
        }
        
        // 输入框容器
        const inputContainer = document.createElement('div');
        inputContainer.className = 'input-container';
        
        // 前置图标
        if (this.icon) {
            const icon = document.createElement('span');
            icon.className = 'input-icon material-icons';
            icon.textContent = this.icon;
            inputContainer.appendChild(icon);
        }
        
        // 输入框
        const input = document.createElement('input');
        input.type = this.type;
        input.className = 'input-field';
        input.placeholder = this.placeholder;
        input.value = this.value;
        
        if (this.required) {
            input.required = true;
        }
        
        if (this.disabled) {
            input.disabled = true;
        }
        
        inputContainer.appendChild(input);
        
        container.appendChild(inputContainer);
        
        // 错误提示
        if (this.error) {
            const errorElement = document.createElement('span');
            errorElement.className = 'input-error';
            errorElement.textContent = this.error;
            container.appendChild(errorElement);
        }
        
        // 添加事件监听
        if (this.onChange) {
            input.addEventListener('input', this.onChange);
            this.listeners.push({ event: 'input', callback: this.onChange });
        }
        
        if (this.onFocus) {
            input.addEventListener('focus', this.onFocus);
            this.listeners.push({ event: 'focus', callback: this.onFocus });
        }
        
        if (this.onBlur) {
            input.addEventListener('blur', this.onBlur);
            this.listeners.push({ event: 'blur', callback: this.onBlur });
        }
        
        this.element = container;
        this.inputElement = input;
        return this.element;
    }

    getValue() {
        return this.inputElement ? this.inputElement.value : '';
    }

    setValue(value) {
        this.value = value;
        if (this.inputElement) {
            this.inputElement.value = value;
        }
    }

    setError(error) {
        this.error = error;
        if (this.element) {
            let errorElement = this.element.querySelector('.input-error');
            
            if (error) {
                if (!errorElement) {
                    errorElement = document.createElement('span');
                    errorElement.className = 'input-error';
                    this.element.appendChild(errorElement);
                }
                errorElement.textContent = error;
                this.element.classList.add('has-error');
            } else {
                if (errorElement) {
                    errorElement.remove();
                }
                this.element.classList.remove('has-error');
            }
        }
    }

    focus() {
        if (this.inputElement) {
            this.inputElement.focus();
        }
    }

    blur() {
        if (this.inputElement) {
            this.inputElement.blur();
        }
    }
}
/* Input组件样式 */
.input-wrapper {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.input-label {
    font-size: 13px;
    font-weight: 500;
    color: var(--text-primary);
    display: flex;
    align-items: center;
    gap: 4px;
}

.required-mark {
    color: var(--error);
}

.input-container {
    position: relative;
    display: flex;
    align-items: center;
}

.input-icon {
    position: absolute;
    left: 12px;
    color: var(--text-muted);
    font-size: 18px;
}

.input-field {
    width: 100%;
    padding: 10px 12px;
    padding-left: 40px;
    font-family: var(--font-family);
    font-size: 14px;
    color: var(--text-primary);
    background-color: var(--bg-panel);
    border: 1px solid var(--border);
    border-radius: 8px;
    outline: none;
    transition: all 0.2s ease;
}

.input-field:focus {
    border-color: var(--primary);
    box-shadow: 0 0 0 3px rgba(0, 217, 255, 0.1);
}

.input-field::placeholder {
    color: var(--text-muted);
}

.input-field:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

.input-error {
    font-size: 12px;
    color: var(--error);
}

.has-error .input-field {
    border-color: var(--error);
}

四、交互模式设计

4.1 对话框组件

class Dialog extends UIComponent {
    constructor(options = {}) {
        super(options);
        this.title = options.title || '';
        this.content = options.content || '';
        this.buttons = options.buttons || [];
        this.modal = options.modal || true;
        this.draggable = options.draggable || true;
        this.closable = options.closable || true;
        this.width = options.width || 400;
        this.onClose = options.onClose || null;
        this.onButtonClick = options.onButtonClick || null;
    }

    render() {
        // 创建遮罩层
        const overlay = document.createElement('div');
        overlay.className = 'modal-overlay';
        overlay.style.display = 'none';
        
        // 创建对话框
        const dialog = document.createElement('div');
        dialog.className = 'modal-dialog';
        dialog.style.width = `${this.width}px`;
        
        if (this.options.id) {
            dialog.id = this.options.id;
        }
        
        // 头部
        const header = document.createElement('div');
        header.className = 'modal-header';
        
        if (this.title) {
            const title = document.createElement('h3');
            title.className = 'modal-title';
            title.textContent = this.title;
            header.appendChild(title);
        }
        
        // 关闭按钮
        if (this.closable) {
            const closeBtn = document.createElement('button');
            closeBtn.className = 'modal-close-btn';
            closeBtn.innerHTML = '<span class="material-icons">close</span>';
            closeBtn.addEventListener('click', () => this.close());
            header.appendChild(closeBtn);
        }
        
        dialog.appendChild(header);
        
        // 内容区
        const content = document.createElement('div');
        content.className = 'modal-content';
        
        if (typeof this.content === 'string') {
            content.innerHTML = this.content;
        } else {
            content.appendChild(this.content);
        }
        
        dialog.appendChild(content);
        
        // 按钮区
        if (this.buttons.length > 0) {
            const footer = document.createElement('div');
            footer.className = 'modal-footer';
            
            this.buttons.forEach((btn, index) => {
                const button = document.createElement('button');
                button.className = `btn btn-${btn.type || 'default'}`;
                button.textContent = btn.text;
                
                if (btn.key) {
                    button.dataset.key = btn.key;
                }
                
                button.addEventListener('click', () => {
                    if (this.onButtonClick) {
                        this.onButtonClick(btn, index);
                    }
                    
                    if (btn.close !== false) {
                        this.close();
                    }
                });
                
                footer.appendChild(button);
            });
            
            dialog.appendChild(footer);
        }
        
        overlay.appendChild(dialog);
        
        // 点击遮罩关闭
        if (this.modal) {
            overlay.addEventListener('click', (e) => {
                if (e.target === overlay) {
                    this.close();
                }
            });
        }
        
        this.element = overlay;
        this.dialog = dialog;
        
        return this.element;
    }

    open() {
        if (this.element) {
            this.element.style.display = 'flex';
            document.body.style.overflow = 'hidden';
        }
    }

    close() {
        if (this.element) {
            this.element.style.display = 'none';
            document.body.style.overflow = '';
            
            if (this.onClose) {
                this.onClose();
            }
        }
    }

    setContent(content) {
        this.content = content;
        if (this.dialog) {
            const contentElement = this.dialog.querySelector('.modal-content');
            if (contentElement) {
                if (typeof content === 'string') {
                    contentElement.innerHTML = content;
                } else {
                    contentElement.innerHTML = '';
                    contentElement.appendChild(content);
                }
            }
        }
    }
}
/* Dialog组件样式 */
.modal-overlay {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: rgba(0, 0, 0, 0.5);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1000;
    backdrop-filter: blur(4px);
}

.modal-dialog {
    background-color: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: var(--shadow-lg);
    overflow: hidden;
    animation: modalIn 0.25s ease;
}

@keyframes modalIn {
    from {
        opacity: 0;
        transform: scale(0.95) translateY(-20px);
    }
    to {
        opacity: 1;
        transform: scale(1) translateY(0);
    }
}

.modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 16px;
    border-bottom: 1px solid var(--border);
    background-color: var(--bg-panel);
}

.modal-title {
    font-size: 16px;
    font-weight: 600;
    color: var(--text-primary);
    margin: 0;
}

.modal-close-btn {
    background: none;
    border: none;
    color: var(--text-secondary);
    cursor: pointer;
    padding: 4px;
    border-radius: 4px;
    transition: background-color 0.2s ease;
}

.modal-close-btn:hover {
    background-color: var(--bg-hover);
}

.modal-content {
    padding: 20px;
    max-height: 400px;
    overflow-y: auto;
}

.modal-footer {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    padding: 16px;
    border-top: 1px solid var(--border);
    background-color: var(--bg-panel);
}

4.2 Toast组件

class Toast extends UIComponent {
    constructor(options = {}) {
        super(options);
        this.type = options.type || 'info';
        this.message = options.message || '';
        this.duration = options.duration || 3000;
        this.position = options.position || 'bottom-right';
        this.onClose = options.onClose || null;
    }

    render() {
        const toast = document.createElement('div');
        toast.className = this.getClassName();
        
        if (this.options.id) {
            toast.id = this.options.id;
        }
        
        // 图标
        const icon = document.createElement('span');
        icon.className = `toast-icon material-icons ${this.type}`;
        icon.textContent = this.getIcon();
        toast.appendChild(icon);
        
        // 消息内容
        const message = document.createElement('span');
        message.className = 'toast-message';
        message.textContent = this.message;
        toast.appendChild(message);
        
        // 关闭按钮
        const closeBtn = document.createElement('button');
        closeBtn.className = 'toast-close-btn';
        closeBtn.innerHTML = '<span class="material-icons">close</span>';
        closeBtn.addEventListener('click', () => this.hide());
        toast.appendChild(closeBtn);
        
        this.element = toast;
        return this.element;
    }

    getClassName() {
        const classes = ['toast', `toast-${this.type}`, `toast-${this.position}`];
        
        if (this.options.className) {
            classes.push(this.options.className);
        }
        
        return classes.join(' ');
    }

    getIcon() {
        switch (this.type) {
            case 'success':
                return 'check_circle';
            case 'error':
                return 'error';
            case 'warning':
                return 'warning';
            case 'info':
            default:
                return 'info';
        }
    }

    show() {
        if (this.element) {
            document.body.appendChild(this.element);
            this.element.classList.add('toast-show');
            
            if (this.duration > 0) {
                setTimeout(() => {
                    this.hide();
                }, this.duration);
            }
        }
    }

    hide() {
        if (this.element) {
            this.element.classList.remove('toast-show');
            
            setTimeout(() => {
                if (this.element && this.element.parentNode) {
                    this.element.parentNode.removeChild(this.element);
                }
                
                if (this.onClose) {
                    this.onClose();
                }
            }, 300);
        }
    }

    static show(options) {
        const toast = new Toast(options);
        toast.render();
        toast.show();
        return toast;
    }
}
/* Toast组件样式 */
.toast {
    position: fixed;
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 16px;
    background-color: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: 8px;
    box-shadow: var(--shadow);
    z-index: 1001;
    opacity: 0;
    transform: translateX(100%);
    transition: all 0.3s ease;
}

.toast-show {
    opacity: 1;
    transform: translateX(0);
}

/* 位置 */
.toast-bottom-right {
    right: 20px;
    bottom: 20px;
}

.toast-bottom-left {
    left: 20px;
    bottom: 20px;
}

.toast-top-right {
    right: 20px;
    top: 20px;
}

.toast-top-left {
    left: 20px;
    top: 20px;
}

.toast-top-center {
    top: 20px;
    left: 50%;
    transform: translateX(-50%);
}

.toast-bottom-center {
    bottom: 20px;
    left: 50%;
    transform: translateX(-50%);
}

/* 类型 */
.toast-info {
    border-left: 4px solid var(--info);
}

.toast-success {
    border-left: 4px solid var(--success);
}

.toast-warning {
    border-left: 4px solid var(--warning);
}

.toast-error {
    border-left: 4px solid var(--error);
}

.toast-icon {
    font-size: 20px;
}

.toast-icon.info {
    color: var(--info);
}

.toast-icon.success {
    color: var(--success);
}

.toast-icon.warning {
    color: var(--warning);
}

.toast-icon.error {
    color: var(--error);
}

.toast-message {
    font-size: 14px;
    color: var(--text-primary);
    flex: 1;
}

.toast-close-btn {
    background: none;
    border: none;
    color: var(--text-secondary);
    cursor: pointer;
    padding: 2px;
    border-radius: 4px;
    transition: background-color 0.2s ease;
}

.toast-close-btn:hover {
    background-color: var(--bg-hover);
}

五、响应式布局

5.1 栅格系统

/* 响应式栅格系统 */
.grid-container {
    display: grid;
    gap: 16px;
}

/* 默认布局 - 桌面端 */
.grid-1 { grid-template-columns: repeat(1, 1fr); }
.grid-2 { grid-template-columns: repeat(2, 1fr); }
.grid-3 { grid-template-columns: repeat(3, 1fr); }
.grid-4 { grid-template-columns: repeat(4, 1fr); }
.grid-5 { grid-template-columns: repeat(5, 1fr); }
.grid-6 { grid-template-columns: repeat(6, 1fr); }

/* 平板端 */
@media (max-width: 1024px) {
    .grid-3 { grid-template-columns: repeat(2, 1fr); }
    .grid-4 { grid-template-columns: repeat(2, 1fr); }
    .grid-5 { grid-template-columns: repeat(3, 1fr); }
    .grid-6 { grid-template-columns: repeat(3, 1fr); }
}

/* 移动端 */
@media (max-width: 768px) {
    .grid-2 { grid-template-columns: repeat(1, 1fr); }
    .grid-3 { grid-template-columns: repeat(1, 1fr); }
    .grid-4 { grid-template-columns: repeat(1, 1fr); }
    .grid-5 { grid-template-columns: repeat(1, 1fr); }
    .grid-6 { grid-template-columns: repeat(1, 1fr); }
}

/* 响应式列偏移 */
.grid-offset-1 { margin-left: calc(100% / 12); }
.grid-offset-2 { margin-left: calc(200% / 12); }
.grid-offset-3 { margin-left: calc(300% / 12); }

5.2 弹性布局组件

class FlexLayout extends UIComponent {
    constructor(options = {}) {
        super(options);
        this.direction = options.direction || 'row';
        this.justifyContent = options.justifyContent || 'flex-start';
        this.alignItems = options.alignItems || 'stretch';
        this.wrap = options.wrap || false;
        this.gap = options.gap || 0;
        this.children = options.children || [];
    }

    render() {
        const container = document.createElement('div');
        container.className = this.getClassName();
        
        if (this.options.id) {
            container.id = this.options.id;
        }
        
        // 设置样式
        container.style.flexDirection = this.direction;
        container.style.justifyContent = this.justifyContent;
        container.style.alignItems = this.alignItems;
        container.style.flexWrap = this.wrap ? 'wrap' : 'nowrap';
        container.style.gap = `${this.gap}px`;
        
        // 添加子元素
        this.children.forEach(child => {
            if (child instanceof UIComponent) {
                child.render();
                container.appendChild(child.element);
            } else if (typeof child === 'string') {
                const textNode = document.createTextNode(child);
                container.appendChild(textNode);
            } else if (child instanceof HTMLElement) {
                container.appendChild(child);
            }
        });
        
        this.element = container;
        return this.element;
    }

    getClassName() {
        const classes = ['flex-layout'];
        
        if (this.options.className) {
            classes.push(this.options.className);
        }
        
        return classes.join(' ');
    }

    addChild(child) {
        this.children.push(child);
        
        if (this.element) {
            if (child instanceof UIComponent) {
                child.render();
                this.element.appendChild(child.element);
            } else if (typeof child === 'string') {
                const textNode = document.createTextNode(child);
                this.element.appendChild(textNode);
            } else if (child instanceof HTMLElement) {
                this.element.appendChild(child);
            }
        }
    }

    removeChild(child) {
        const index = this.children.indexOf(child);
        
        if (index !== -1) {
            this.children.splice(index, 1);
            
            if (this.element && child instanceof UIComponent && child.element) {
                this.element.removeChild(child.element);
            }
        }
    }
}

六、动画效果实现

6.1 CSS动画库

/* 动画类 */
.animate-fade-in {
    animation: fadeIn 0.3s ease forwards;
}

@keyframes fadeIn {
    from { opacity: 0; }
    to { opacity: 1; }
}

.animate-slide-in-right {
    animation: slideInRight 0.3s ease forwards;
}

@keyframes slideInRight {
    from {
        opacity: 0;
        transform: translateX(20px);
    }
    to {
        opacity: 1;
        transform: translateX(0);
    }
}

.animate-slide-in-left {
    animation: slideInLeft 0.3s ease forwards;
}

@keyframes slideInLeft {
    from {
        opacity: 0;
        transform: translateX(-20px);
    }
    to {
        opacity: 1;
        transform: translateX(0);
    }
}

.animate-slide-in-up {
    animation: slideInUp 0.3s ease forwards;
}

@keyframes slideInUp {
    from {
        opacity: 0;
        transform: translateY(20px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

.animate-scale-in {
    animation: scaleIn 0.25s ease forwards;
}

@keyframes scaleIn {
    from {
        opacity: 0;
        transform: scale(0.95);
    }
    to {
        opacity: 1;
        transform: scale(1);
    }
}

.animate-bounce {
    animation: bounce 0.5s ease;
}

@keyframes bounce {
    0%, 100% {
        transform: translateY(0);
    }
    50% {
        transform: translateY(-10px);
    }
}

.animate-pulse {
    animation: pulse 2s ease-in-out infinite;
}

@keyframes pulse {
    0%, 100% {
        opacity: 1;
    }
    50% {
        opacity: 0.7;
    }
}

/* 过渡动画 */
.transition-fast {
    transition: all 0.15s ease;
}

.transition-normal {
    transition: all 0.25s ease;
}

.transition-slow {
    transition: all 0.35s ease;
}

6.2 交互动画

class AnimationManager {
    constructor() {
        this.animations = [];
    }

    animate(element, animationClass, options = {}) {
        const { 
            delay = 0, 
            duration = 300, 
            onComplete = null 
        } = options;
        
        return new Promise((resolve) => {
            setTimeout(() => {
                element.classList.add(animationClass);
                
                setTimeout(() => {
                    element.classList.remove(animationClass);
                    
                    if (onComplete) {
                        onComplete();
                    }
                    
                    resolve();
                }, duration);
            }, delay);
        });
    }

    fadeIn(element, options = {}) {
        return this.animate(element, 'animate-fade-in', options);
    }

    slideInRight(element, options = {}) {
        return this.animate(element, 'animate-slide-in-right', options);
    }

    slideInLeft(element, options = {}) {
        return this.animate(element, 'animate-slide-in-left', options);
    }

    slideInUp(element, options = {}) {
        return this.animate(element, 'animate-slide-in-up', options);
    }

    scaleIn(element, options = {}) {
        return this.animate(element, 'animate-scale-in', options);
    }

    bounce(element, options = {}) {
        return this.animate(element, 'animate-bounce', { ...options, duration: 500 });
    }

    pulse(element, options = {}) {
        const { duration = 2000, iterations = Infinity } = options;
        
        element.classList.add('animate-pulse');
        
        if (iterations !== Infinity) {
            setTimeout(() => {
                element.classList.remove('animate-pulse');
            }, duration * iterations);
        }
        
        return {
            stop: () => {
                element.classList.remove('animate-pulse');
            }
        };
    }

    parallax(element, options = {}) {
        const { speed = 0.5 } = options;
        
        const handleScroll = () => {
            const scrollTop = window.scrollY;
            element.style.transform = `translateY(${scrollTop * speed}px)`;
        };
        
        window.addEventListener('scroll', handleScroll);
        
        return {
            stop: () => {
                window.removeEventListener('scroll', handleScroll);
            }
        };
    }

    stagger(elements, animationClass, options = {}) {
        const { delay = 50 } = options;
        
        return Promise.all(
            elements.map((element, index) => 
                this.animate(element, animationClass, { delay: index * delay })
            )
        );
    }
}

七、总结与展望

7.1 功能回顾

本章详细介绍了UI组件开发与交互设计的实现,包括:

  1. 组件架构:组件基类设计和组件分类
  2. 核心组件:Button、Card、Input、Dialog、Toast等组件
  3. 交互模式:对话框、提示框等交互组件
  4. 响应式布局:栅格系统和弹性布局
  5. 动画效果:CSS动画和JavaScript动画管理

7.2 技术亮点

  • 组件化设计:模块化、可复用的组件系统
  • 统一风格:使用CSS变量实现主题一致性
  • 交互反馈:完善的用户交互反馈机制
  • 响应式设计:适配不同屏幕尺寸
  • 动画效果:丰富的动画提升用户体验

7.3 未来扩展

未来可以考虑添加以下功能:

  • 组件库:更丰富的组件集合
  • 拖拽功能:支持组件拖拽排序
  • 虚拟滚动:优化大数据列表渲染
  • 无障碍支持:增强可访问性
  • 国际化支持:多语言组件

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐