guanghulab/modules/m-channel/channel-enhancements.js
Guanghu Domestic Migration d1e47f4565
Some checks are pending
自动更新代码和重启 / update-and-restart (push) Waiting to run
CI检查 + 自动部署 / check (push) Waiting to run
CI检查 + 自动部署 / deploy (push) Blocked by required conditions
重启聊天服务 / restart (push) Waiting to run
chore: import sanitized domestic snapshot for REPO-002
Source snapshot: ca48d3ddf926d79aa138306164169baf764bb829
2026-07-17 15:54:41 +08:00

339 lines
12 KiB
JavaScript
Raw Permalink 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.

// channel-enhancements.js - 频道搜索、面包屑、快捷键增强功能(终极修复版)
(function() {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
function init() {
addStyles();
createSearchBox();
createBreadcrumb();
createShortcutHint();
initShortcuts();
bindCardClicks(); // ★ 新增:为卡片绑定点击切换模块
window.addEventListener('hashchange', updateBreadcrumb);
updateBreadcrumb();
}
function addStyles() {
const style = document.createElement('style');
style.textContent = `
.channel-search-container {
padding: 16px 20px;
background: #f8f9fa;
border-bottom: 1px solid #e9ecef;
display: flex;
align-items: center;
gap: 10px;
}
.channel-search-input {
flex: 1;
padding: 10px 16px;
border: 1px solid #ced4da;
border-radius: 24px;
font-size: 14px;
outline: none;
transition: border-color 0.2s;
}
.channel-search-input:focus {
border-color: #4d6bfe;
box-shadow: 0 0 0 3px rgba(77,107,254,0.1);
}
.channel-search-clear {
background: none;
border: none;
font-size: 18px;
cursor: pointer;
color: #6c757d;
padding: 0 8px;
display: none;
}
.channel-search-clear:hover {
color: #212529;
}
.channel-search-input:not(:placeholder-shown) + .channel-search-clear {
display: inline-block;
}
.highlight {
background-color: #ffeb3b;
padding: 2px 0;
border-radius: 2px;
}
.no-results {
text-align: center;
padding: 40px;
color: #6c757d;
font-style: italic;
}
.channel-breadcrumb {
padding: 12px 20px;
background: white;
border-bottom: 1px solid #e9ecef;
font-size: 14px;
}
.channel-breadcrumb a {
color: #4d6bfe;
text-decoration: none;
}
.channel-breadcrumb a:hover {
text-decoration: underline;
}
.channel-breadcrumb span {
color: #6c757d;
}
.channel-breadcrumb .current {
color: #212529;
font-weight: 500;
}
.shortcut-hint {
position: fixed;
bottom: 20px;
right: 20px;
background: rgba(0,0,0,0.7);
color: white;
padding: 8px 16px;
border-radius: 30px;
font-size: 12px;
backdrop-filter: blur(4px);
z-index: 1000;
}
.shortcut-hint kbd {
background: rgba(255,255,255,0.2);
padding: 2px 6px;
border-radius: 4px;
margin: 0 2px;
}
.module-card.selected {
outline: 2px solid #4d6bfe;
outline-offset: 2px;
transform: scale(1.02);
transition: all 0.2s;
}
`;
document.head.appendChild(style);
}
function createSearchBox() {
const container = document.querySelector('.module-grid') || document.querySelector('#module-list') || document.querySelector('.channel-content');
if (!container) return;
const searchContainer = document.createElement('div');
searchContainer.className = 'channel-search-container';
searchContainer.innerHTML = `
<input type="text" class="channel-search-input" placeholder="搜索模块...">
<button class="channel-search-clear">&times;</button>
`;
container.parentNode.insertBefore(searchContainer, container);
const searchInput = searchContainer.querySelector('.channel-search-input');
const clearBtn = searchContainer.querySelector('.channel-search-clear');
searchInput.addEventListener('input', function() {
filterModules(this.value);
});
clearBtn.addEventListener('click', function() {
searchInput.value = '';
filterModules('');
searchInput.focus();
});
window.__channelSearchInput = searchInput;
}
function filterModules(keyword) {
const cards = document.querySelectorAll('.module-card');
const container = document.querySelector('.module-grid') || document.querySelector('#module-list');
let hasResults = false;
// 移除所有高亮
removeAllHighlights();
cards.forEach(card => {
const text = card.innerText || card.textContent;
if (keyword === '') {
card.style.display = '';
hasResults = true;
} else {
const lowerText = text.toLowerCase();
const lowerKeyword = keyword.toLowerCase();
if (lowerText.includes(lowerKeyword)) {
card.style.display = '';
highlightText(card, keyword);
hasResults = true;
} else {
card.style.display = 'none';
}
}
});
let noResultsEl = document.querySelector('.no-results');
if (!hasResults && keyword !== '') {
if (!noResultsEl) {
noResultsEl = document.createElement('div');
noResultsEl.className = 'no-results';
noResultsEl.textContent = '没有找到匹配的模块';
container.parentNode.insertBefore(noResultsEl, container.nextSibling);
}
} else {
if (noResultsEl) noResultsEl.remove();
}
}
function removeAllHighlights() {
document.querySelectorAll('.highlight').forEach(span => {
const parent = span.parentNode;
parent.replaceChild(document.createTextNode(span.textContent), span);
parent.normalize();
});
}
function highlightText(card, keyword) {
const regex = new RegExp(`(${keyword})`, 'gi');
const walk = document.createTreeWalker(card, NodeFilter.SHOW_TEXT, {
acceptNode: function(node) {
if (node.parentNode.classList && node.parentNode.classList.contains('highlight')) {
return NodeFilter.FILTER_REJECT;
}
return NodeFilter.FILTER_ACCEPT;
}
}, false);
const textNodes = [];
while (walk.nextNode()) textNodes.push(walk.currentNode);
textNodes.forEach(node => {
const text = node.nodeValue;
if (regex.test(text)) {
const span = document.createElement('span');
span.className = 'highlight';
span.innerHTML = text.replace(regex, '<span class="highlight">$1</span>');
node.parentNode.replaceChild(span, node);
}
});
}
function createBreadcrumb() {
const searchContainer = document.querySelector('.channel-search-container');
const breadcrumb = document.createElement('div');
breadcrumb.className = 'channel-breadcrumb';
breadcrumb.id = 'channelBreadcrumb';
if (searchContainer) {
searchContainer.parentNode.insertBefore(breadcrumb, searchContainer.nextSibling);
} else {
const container = document.querySelector('.module-grid') || document.querySelector('#module-list');
if (container) {
container.parentNode.insertBefore(breadcrumb, container);
}
}
}
function updateBreadcrumb() {
const breadcrumb = document.getElementById('channelBreadcrumb');
if (!breadcrumb) return;
const hash = window.location.hash.slice(1) || '';
let moduleName = '频道';
if (hash.startsWith('module-')) {
const moduleId = hash.replace('module-', '');
const moduleNames = {
'M06': '工单管理',
'M08': '数据看板',
'M11': '组件库',
'debug': '调试面板'
};
moduleName = moduleNames[moduleId] || moduleId;
}
breadcrumb.innerHTML = `
<a href="#/">首页</a> &gt;
<a href="#/channel">频道</a> &gt;
<span class="current">${moduleName}</span>
`;
}
function createShortcutHint() {
const hint = document.createElement('div');
hint.className = 'shortcut-hint';
hint.innerHTML = `
<kbd>⌘K</kbd> 搜索 ·
<kbd>↑</kbd><kbd>↓</kbd> 选择 ·
<kbd>Enter</kbd> 打开 ·
<kbd>Esc</kbd> 关闭
`;
document.body.appendChild(hint);
}
function initShortcuts() {
document.addEventListener('keydown', function(e) {
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
const searchInput = window.__channelSearchInput;
if (searchInput) searchInput.focus();
}
if (e.key === 'Escape') {
const searchInput = window.__channelSearchInput;
if (searchInput && document.activeElement === searchInput) {
searchInput.value = '';
filterModules('');
searchInput.blur();
} else if (searchInput && searchInput.value) {
searchInput.value = '';
filterModules('');
}
}
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
const cards = Array.from(document.querySelectorAll('.module-card:not([style*="display: none"])'));
if (cards.length === 0) return;
e.preventDefault();
let selectedIndex = cards.findIndex(card => card.classList.contains('selected'));
if (selectedIndex === -1) {
selectedIndex = e.key === 'ArrowDown' ? 0 : cards.length - 1;
} else {
cards[selectedIndex].classList.remove('selected');
if (e.key === 'ArrowDown') {
selectedIndex = (selectedIndex + 1) % cards.length;
} else {
selectedIndex = (selectedIndex - 1 + cards.length) % cards.length;
}
}
cards[selectedIndex].classList.add('selected');
cards[selectedIndex].scrollIntoView({ block: 'nearest' });
}
if (e.key === 'Enter') {
const selected = document.querySelector('.module-card.selected');
if (selected) {
selected.click(); // 触发我们绑定的点击事件
}
}
});
}
// ★★★ 核心修复:为所有卡片绑定点击切换模块 ★★★
function bindCardClicks() {
const grid = document.querySelector('.module-grid') || document.querySelector('#module-list') || document.body;
grid.addEventListener('click', function(e) {
const card = e.target.closest('.module-card');
if (!card) return;
// 阻止可能冲突的其他事件(可选)
e.preventDefault();
// 根据卡片文字判断是哪个模块
const text = card.innerText || card.textContent;
let moduleId = null;
if (text.includes('工单管理')) moduleId = 'M06';
else if (text.includes('数据统计')) moduleId = 'M08';
else if (text.includes('组件库')) moduleId = 'M11';
else if (text.includes('调试面板')) moduleId = 'debug';
// 如果识别不到,尝试从其他属性获取(比如 data-module-id
else if (card.dataset.moduleId) moduleId = card.dataset.moduleId;
if (moduleId) {
// 改变 hash触发路由更新
window.location.hash = `module-${moduleId}`;
}
});
}
})();