⚠️
${tUI('加载失败')}
${message}
${tUI('你仍然可以在搜索框中手动输入模型名称')}
`;
// 不禁用搜索框,允许用户手动输入模型名称
};
// 更新选中计数
const updateCount = () => {
countLabel.textContent = `${tUI('已选')} ${selectedItems.size} ${tUI('项')}`;
// 如果有选中项或搜索框有内容,则启用确认按钮
const hasInput = searchInput.value.trim().length > 0;
confirmBtn.disabled = selectedItems.size === 0 && !hasInput;
};
// 渲染列表
const renderList = (searchTerm = '') => {
listContainer.innerHTML = '';
const filteredItems = searchTerm
? allItems.filter(item => item.toLowerCase().includes(searchTerm.toLowerCase()))
: allItems;
if (filteredItems.length === 0) {
const emptyHint = document.createElement('div');
emptyHint.className = 'pa-multi-listbox-empty';
emptyHint.textContent = searchTerm ? tUI('未找到匹配项') : tUI('暂无可用项');
listContainer.appendChild(emptyHint);
return;
}
filteredItems.forEach(itemName => {
const item = document.createElement('div');
item.className = 'pa-multi-listbox-item';
if (selectedItems.has(itemName)) {
item.classList.add('selected');
}
const checkbox = document.createElement('div');
checkbox.className = 'pa-multi-listbox-checkbox';
const label = document.createElement('span');
label.textContent = itemName;
item.appendChild(checkbox);
item.appendChild(label);
// 点击切换选中状态
item.addEventListener('click', () => {
if (selectedItems.has(itemName)) {
selectedItems.delete(itemName);
item.classList.remove('selected');
} else {
selectedItems.add(itemName);
item.classList.add('selected');
}
updateCount();
});
listContainer.appendChild(item);
});
};
// 初始化数据
const initialize = async () => {
showLoading();
try {
const items = await fetchItems();
if (!items || !Array.isArray(items)) {
throw new Error(tUI('数据格式错误'));
}
allItems = items;
renderList();
} catch (error) {
showError(error.message || tUI('未知错误'));
}
};
// 搜索框事件
searchInput.addEventListener('input', (e) => {
renderList(e.target.value);
updateCount(); // 输入时更新按钮状态
});
// 取消按钮
cancelBtn.addEventListener('click', () => {
if (onCancel) {
onCancel();
}
close();
});
// 确定按钮
confirmBtn.addEventListener('click', () => {
const selected = Array.from(selectedItems);
const searchValue = searchInput.value; // 获取搜索框的值
onConfirm(selected, searchValue); // 将搜索框的值作为第二个参数传递
close();
});
// 创建透明背景层
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: var(--settings-multi-listbox-z-index);
`;
overlay.appendChild(listbox);
document.body.appendChild(overlay);
localizeElement(listbox);
const stopListboxLocalization = observeLocalizedMutations(listbox);
// 计算并设置位置(右对齐按钮,自适应上下)
const viewportHeight = window.innerHeight;
const viewportWidth = window.innerWidth;
const listboxHeight = 400; // max-height
const spaceBelow = viewportHeight - btnRect.bottom;
const spaceAbove = btnRect.top;
if (spaceBelow >= listboxHeight || spaceBelow > spaceAbove) {
// 显示在按钮下方
listbox.style.top = `${btnRect.bottom + 4}px`;
} else {
// 显示在按钮上方
listbox.style.bottom = `${viewportHeight - btnRect.top + 4}px`;
}
// 右对齐按钮
listbox.style.right = `${viewportWidth - btnRect.right}px`;
// 关闭函数
const close = () => {
stopListboxLocalization();
if (overlay.parentNode) {
document.body.removeChild(overlay);
}
};
// 点击背景关闭
overlay.addEventListener('click', (e) => {
if (e.target === overlay) {
if (onCancel) {
onCancel();
}
close();
}
});
// 初始化数据
initialize();
// 聚焦搜索框
setTimeout(() => searchInput.focus(), 100);
}
/**
* 创建 SplitButton 组件 (参考 PrimeVue SplitButton)
* @param {Object} options 配置选项
* @param {string} options.label 按钮文本
* @param {string} options.icon 按钮图标
* @param {string} options.className 自定义类名
* @param {Function} options.onClick 主按钮点击回调
* @param {Array} options.items 下拉菜单项 [{label, icon, command, separator, checked, disabled}]
* @returns {Object} { container, updateLabel, updateIcon, updateMenu, setMainButtonDisabled, setMenuButtonDisabled }
*/
export function createSplitButton(options) {
const { label = '', icon = '', className = '', onClick = () => { }, items = [], align = 'left' } = options;
const container = document.createElement('div');
container.className = `p-splitbutton p-component${className ? ' ' + className : ''}`;
container.style.display = 'inline-flex';
container.style.position = 'relative';
// 1. 主按钮
const mainKey = document.createElement('button');
mainKey.className = 'p-button p-component p-button-text p-button-sm';
mainKey.type = 'button';
mainKey.style.borderTopRightRadius = '0';
mainKey.style.borderBottomRightRadius = '0';
mainKey.style.borderRight = '0 none';
// 内容容器
const mainContent = document.createElement('span');
mainContent.className = 'p-button-label p-c';
mainContent.style.display = 'flex';
mainContent.style.alignItems = 'center';
mainContent.style.gap = '0.5rem';
const renderMainButton = (text, iconClass, spinning = false) => {
mainContent.innerHTML = '';
if (iconClass) {
const i = document.createElement('span');
i.className = `p-button-icon p-button-icon-left ${iconClass} ${spinning ? 'spinning' : ''}`;
mainContent.appendChild(i);
}
const span = document.createElement('span');
span.className = 'p-button-label';
span.textContent = tUI(text, text);
mainContent.appendChild(span);
// 更新 stored data for subsequent updates via updateIcon
mainKey.dataset.label = text;
mainKey.dataset.icon = iconClass || '';
};
renderMainButton(label, icon);
mainKey.appendChild(mainContent);
mainKey.addEventListener('click', (e) => {
e.stopPropagation();
onClick(e);
});
// 2. 下拉触发按钮
const menuBtn = document.createElement('button');
menuBtn.className = 'p-button p-component p-button-icon-only p-button-text p-button-sm p-splitbutton-menubutton';
menuBtn.type = 'button';
menuBtn.style.borderTopLeftRadius = '0';
menuBtn.style.borderBottomLeftRadius = '0';
menuBtn.innerHTML = '