Files
OMS/app/wap/view/neworder/custom-select.js
2026-01-04 19:08:31 +08:00

307 lines
8.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Copyright 2012-2026 ShopeX (https://www.shopex.cn)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* 自定义 Select 组件
*/
class CustomSelect {
// 静态属性:存储所有实例
static instances = [];
static currentOpenInstance = null;
constructor(element, options = {}) {
this.element = element;
this.options = options;
this.isOpen = false;
this.selectedValue = '';
this.selectedText = '';
// 将实例添加到全局管理中
CustomSelect.instances.push(this);
this.init();
}
init() {
this.createCustomSelect();
this.bindEvents();
this.setInitialValue();
}
createCustomSelect() {
// 隐藏原始select
this.element.style.display = 'none';
// 创建自定义select容器
this.customSelect = document.createElement('div');
this.customSelect.className = 'custom-select';
// 创建触发器
this.trigger = document.createElement('div');
this.trigger.className = 'custom-select-trigger';
this.valueElement = document.createElement('span');
this.valueElement.className = 'custom-select-value';
this.arrow = document.createElement('img');
this.arrow.className = 'custom-select-arrow';
this.arrow.src = './icon-arrow-down.png';
this.trigger.appendChild(this.valueElement);
this.trigger.appendChild(this.arrow);
// 创建下拉列表
this.dropdown = document.createElement('div');
this.dropdown.className = 'custom-select-dropdown';
this.dropdown.style.display = 'none';
this.customSelect.appendChild(this.trigger);
this.customSelect.appendChild(this.dropdown);
// 插入到原始select后面
this.element.parentNode.insertBefore(this.customSelect, this.element.nextSibling);
this.updateOptions();
}
updateOptions() {
this.dropdown.innerHTML = '';
Array.from(this.element.options).forEach((option, index) => {
const optionElement = document.createElement('div');
optionElement.className = 'custom-select-option';
optionElement.textContent = option.text;
optionElement.dataset.value = option.value;
optionElement.dataset.index = index;
if (option.disabled) {
optionElement.classList.add('disabled');
}
if (option.selected) {
optionElement.classList.add('selected');
this.selectedValue = option.value;
this.selectedText = option.text;
}
this.dropdown.appendChild(optionElement);
});
}
bindEvents() {
// 点击触发器
this.trigger.addEventListener('click', (e) => {
e.stopPropagation();
this.toggle();
});
// 点击选项
this.dropdown.addEventListener('click', (e) => {
if (e.target.classList.contains('custom-select-option') && !e.target.classList.contains('disabled')) {
this.selectOption(e.target);
}
});
// 键盘事件
this.customSelect.addEventListener('keydown', (e) => {
this.handleKeydown(e);
});
}
selectOption(optionElement) {
const value = optionElement.dataset.value;
const text = optionElement.textContent;
const index = parseInt(optionElement.dataset.index);
// 更新原始select
this.element.selectedIndex = index;
// 更新显示
this.selectedValue = value;
this.selectedText = text;
this.valueElement.textContent = text;
this.valueElement.classList.remove('placeholder');
// 更新选中状态
this.dropdown.querySelectorAll('.custom-select-option').forEach(opt => {
opt.classList.remove('selected');
});
optionElement.classList.add('selected');
// 触发change事件
const event = new Event('change', { bubbles: true });
this.element.dispatchEvent(event);
this.close();
}
setInitialValue() {
const selectedOption = this.element.options[this.element.selectedIndex];
if (selectedOption) {
this.selectedValue = selectedOption.value;
this.selectedText = selectedOption.text;
this.valueElement.textContent = selectedOption.text;
} else {
this.valueElement.textContent = this.options.placeholder || '请选择';
this.valueElement.classList.add('placeholder');
}
}
toggle() {
if (this.isOpen) {
this.close();
} else {
this.open();
}
}
open() {
// 关闭其他所有打开的select
CustomSelect.closeAll();
this.isOpen = true;
CustomSelect.currentOpenInstance = this;
this.dropdown.style.display = 'block';
this.trigger.classList.add('active');
this.arrow.classList.add('open');
// 滚动到选中项
const selectedOption = this.dropdown.querySelector('.custom-select-option.selected');
if (selectedOption) {
selectedOption.scrollIntoView({ block: 'nearest' });
}
}
close() {
this.isOpen = false;
if (CustomSelect.currentOpenInstance === this) {
CustomSelect.currentOpenInstance = null;
}
this.dropdown.style.display = 'none';
this.trigger.classList.remove('active');
this.arrow.classList.remove('open');
}
// 静态方法关闭所有打开的select
static closeAll() {
CustomSelect.instances.forEach(instance => {
if (instance.isOpen) {
instance.close();
}
});
}
handleKeydown(e) {
if (!this.isOpen) {
if (e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown') {
e.preventDefault();
this.open();
}
return;
}
const options = Array.from(this.dropdown.querySelectorAll('.custom-select-option:not(.disabled)'));
const currentIndex = options.findIndex(opt => opt.classList.contains('selected'));
switch (e.key) {
case 'ArrowDown':
e.preventDefault();
const nextIndex = currentIndex < options.length - 1 ? currentIndex + 1 : 0;
this.selectOption(options[nextIndex]);
break;
case 'ArrowUp':
e.preventDefault();
const prevIndex = currentIndex > 0 ? currentIndex - 1 : options.length - 1;
this.selectOption(options[prevIndex]);
break;
case 'Enter':
e.preventDefault();
if (currentIndex >= 0) {
this.selectOption(options[currentIndex]);
}
break;
case 'Escape':
e.preventDefault();
this.close();
break;
}
}
// 销毁组件
destroy() {
// 从全局实例列表中移除
const index = CustomSelect.instances.indexOf(this);
if (index > -1) {
CustomSelect.instances.splice(index, 1);
}
if (CustomSelect.currentOpenInstance === this) {
CustomSelect.currentOpenInstance = null;
}
if (this.customSelect && this.customSelect.parentNode) {
this.customSelect.parentNode.removeChild(this.customSelect);
}
this.element.style.display = '';
}
// 设置值
setValue(value) {
const option = Array.from(this.element.options).find(opt => opt.value === value);
if (option) {
this.element.selectedIndex = option.index;
this.selectedValue = option.value;
this.selectedText = option.text;
this.valueElement.textContent = option.text;
this.valueElement.classList.remove('placeholder');
// 更新选中状态
this.dropdown.querySelectorAll('.custom-select-option').forEach(opt => {
opt.classList.remove('selected');
});
this.dropdown.querySelector(`[data-value="${value}"]`).classList.add('selected');
}
}
// 获取值
getValue() {
return this.selectedValue;
}
}
// 初始化所有自定义select
function initCustomSelects() {
const selects = document.querySelectorAll('select');
selects.forEach(select => {
if (!select.dataset.customized) {
new CustomSelect(select);
select.dataset.customized = 'true';
}
});
}
// 全局点击事件关闭所有打开的select
document.addEventListener('click', (e) => {
// 检查点击是否在任何custom-select内部
const isInsideCustomSelect = e.target.closest('.custom-select');
if (!isInsideCustomSelect) {
CustomSelect.closeAll();
}
});
// 导出供全局使用
window.CustomSelect = CustomSelect;
window.initCustomSelects = initCustomSelects;