UNPKG

yyf_aka-web-components

Version:

A collection of modern Web Components including counter, data table and custom button components

402 lines (355 loc) 12 kB
/** * 计数器组件 - Web Component 示例 * * 功能特性: * - 自定义元素:使用 <counter-component> 标签 * - Shadow DOM:封装样式和结构 * - 属性绑定:支持初始值、步长、最小值、最大值 * - 事件系统:触发自定义事件 * - 公共 API:提供多种操作方法 * * 使用示例: * <counter-component initial="5" step="2" min="0" max="20"></counter-component> * * 属性说明: * - initial: 初始值(默认:0) * - step: 步长(默认:1) * - min: 最小值(默认:无限制) * - max: 最大值(默认:无限制) * * 事件说明: * - counter-change: 计数器值变化时触发 * detail: { action: string, value: number, oldValue: number } * * 公共方法: * - getValue(): 获取当前值 * - setValue(value): 设置新值 * - reset(): 重置为初始值 * - increment(): 增加一个步长 * - decrement(): 减少一个步长 */ class CounterComponent extends HTMLElement { /** * 构造函数 * 初始化组件的基本属性和状态 */ constructor() { super(); // 创建 Shadow DOM,mode: 'open' 允许外部访问 this.attachShadow({ mode: 'open' }); // 计数器状态 this._value = 0; // 当前值 this._initialValue = 0; // 初始值 this._step = 1; // 步长 this._min = null; // 最小值 this._max = null; // 最大值 } /** * 定义需要观察的属性 * 当这些属性发生变化时,会触发 attributeChangedCallback * @returns {string[]} 需要观察的属性名数组 */ static get observedAttributes() { return ['initial', 'step', 'min', 'max']; } /** * 组件连接到 DOM 时调用 * 初始化组件状态、渲染界面、绑定事件 */ connectedCallback() { // 从属性中读取配置参数 this._initialValue = parseInt(this.getAttribute('initial')) || 0; this._step = parseInt(this.getAttribute('step')) || 1; this._min = this.hasAttribute('min') ? parseInt(this.getAttribute('min')) : null; this._max = this.hasAttribute('max') ? parseInt(this.getAttribute('max')) : null; // 设置初始值 this._value = this._initialValue; // 渲染组件界面 this.render(); // 绑定事件监听器 this.addEventListeners(); } /** * 观察的属性发生变化时调用 * @param {string} name - 发生变化的属性名 * @param {string} oldValue - 变化前的值 * @param {string} newValue - 变化后的值 */ attributeChangedCallback(name, oldValue, newValue) { // 只有当值真正发生变化时才处理 if (oldValue !== newValue) { switch (name) { case 'initial': this._initialValue = parseInt(newValue) || 0; break; case 'step': this._step = parseInt(newValue) || 1; break; case 'min': this._min = this.hasAttribute('min') ? parseInt(newValue) : null; break; case 'max': this._max = this.hasAttribute('max') ? parseInt(newValue) : null; break; } // 重新渲染界面 this.render(); } } /** * 绑定事件监听器 * 为按钮添加点击事件处理 */ addEventListeners() { const shadow = this.shadowRoot; // 增加按钮事件监听 const incrementBtn = shadow.querySelector('.increment-btn'); if (incrementBtn) { incrementBtn.addEventListener('click', () => { this.increment(); }); } // 减少按钮事件监听 const decrementBtn = shadow.querySelector('.decrement-btn'); if (decrementBtn) { decrementBtn.addEventListener('click', () => { this.decrement(); }); } // 重置按钮事件监听 const resetBtn = shadow.querySelector('.reset-btn'); if (resetBtn) { resetBtn.addEventListener('click', () => { this.reset(); }); } } /** * 渲染组件界面 * 根据当前状态渲染计数器的显示内容 */ render() { this.shadowRoot.innerHTML = ` <style> :host { display: inline-block; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } .counter-container { display: flex; flex-direction: column; align-items: center; gap: 15px; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 16px; color: white; box-shadow: 0 8px 32px rgba(0,0,0,0.1); min-width: 200px; } .counter-display { font-size: 3rem; font-weight: 300; text-align: center; margin: 0; text-shadow: 0 2px 4px rgba(0,0,0,0.2); } .counter-info { font-size: 0.9rem; opacity: 0.8; text-align: center; margin: 0; } .counter-controls { display: flex; gap: 10px; align-items: center; } .counter-btn { background: rgba(255,255,255,0.2); border: 2px solid rgba(255,255,255,0.3); color: white; padding: 8px 16px; border-radius: 8px; cursor: pointer; font-size: 1.2rem; font-weight: 600; transition: all 0.3s ease; min-width: 50px; } .counter-btn:hover { background: rgba(255,255,255,0.3); border-color: rgba(255,255,255,0.5); transform: translateY(-2px); } .counter-btn:active { transform: translateY(0); } .counter-btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; } .increment-btn { background: rgba(76, 175, 80, 0.8); border-color: rgba(76, 175, 80, 0.9); } .decrement-btn { background: rgba(244, 67, 54, 0.8); border-color: rgba(244, 67, 54, 0.9); } .reset-btn { background: rgba(255, 193, 7, 0.8); border-color: rgba(255, 193, 7, 0.9); font-size: 1rem; } @media (max-width: 480px) { .counter-container { padding: 15px; min-width: 180px; } .counter-display { font-size: 2.5rem; } .counter-controls { flex-direction: column; gap: 8px; } .counter-btn { width: 100%; min-width: auto; } } </style> <div class="counter-container"> <h2 class="counter-display">${this._value}</h2> <p class="counter-info"> 步长: ${this._step} ${this._min !== null ? `<br>最小值: ${this._min}` : ''} ${this._max !== null ? `<br>最大值: ${this._max}` : ''} </p> <div class="counter-controls"> <button class="counter-btn decrement-btn" ${this._min !== null && this._value <= this._min ? 'disabled' : ''}> - </button> <button class="counter-btn reset-btn">重置</button> <button class="counter-btn increment-btn" ${this._max !== null && this._value >= this._max ? 'disabled' : ''}> + </button> </div> </div> <slot name="footer"></slot> `; // 重新绑定事件监听器 this.addEventListeners(); } // 公共方法 /** * 获取当前计数器值 * @returns {number} 当前值 */ getValue() { return this._value; } /** * 设置计数器值 * @param {number} value - 要设置的新值 */ setValue(value) { const oldValue = this._value; const newValue = parseInt(value); // 检查边界条件 if (this._min !== null && newValue < this._min) { this._value = this._min; } else if (this._max !== null && newValue > this._max) { this._value = this._max; } else { this._value = newValue; } // 触发值变化事件 this.dispatchEvent(new CustomEvent('counter-change', { detail: { action: 'set', value: this._value, oldValue: oldValue }, bubbles: true, composed: true })); // 重新渲染界面 this.render(); } /** * 重置计数器为初始值 */ reset() { const oldValue = this._value; this._value = this._initialValue; // 触发值变化事件 this.dispatchEvent(new CustomEvent('counter-change', { detail: { action: 'reset', value: this._value, oldValue: oldValue }, bubbles: true, composed: true })); // 重新渲染界面 this.render(); } /** * 增加计数器值 */ increment() { const oldValue = this._value; const newValue = this._value + this._step; // 检查最大值限制 if (this._max !== null && newValue > this._max) { this._value = this._max; } else { this._value = newValue; } // 触发值变化事件 this.dispatchEvent(new CustomEvent('counter-change', { detail: { action: 'increment', value: this._value, oldValue: oldValue }, bubbles: true, composed: true })); // 重新渲染界面 this.render(); } /** * 减少计数器值 */ decrement() { const oldValue = this._value; const newValue = this._value - this._step; // 检查最小值限制 if (this._min !== null && newValue < this._min) { this._value = this._min; } else { this._value = newValue; } // 触发值变化事件 this.dispatchEvent(new CustomEvent('counter-change', { detail: { action: 'decrement', value: this._value, oldValue: oldValue }, bubbles: true, composed: true })); // 重新渲染界面 this.render(); } } // 导出组件类 export default CounterComponent;