Files
TalkingQ_Dashboard/scripts/app.js
“yuanmengy” 69785c214f first commit
2026-06-10 17:31:01 +08:00

272 lines
10 KiB
JavaScript

/**
* TalkingQ Platform - App Core
* Router, sidebar, topbar clock, toast notifications
*/
// ─── Toast ──────────────────────────────────────────────────────────────────
const Toast = (() => {
let container = null;
function getContainer() {
if (!container) {
container = document.createElement('div');
container.className = 'toast-container';
document.body.appendChild(container);
}
return container;
}
function show(message, type = 'success', duration = 3000) {
const icons = { success: 'check-circle', error: 'x-circle', warning: 'alert-triangle', info: 'info' };
const toast = document.createElement('div');
toast.className = `toast ${type}`;
toast.innerHTML = `
<i data-lucide="${icons[type] || 'info'}" style="width:16px;height:16px;flex-shrink:0"></i>
<span style="font-size:13.5px">${message}</span>
`;
getContainer().appendChild(toast);
lucide.createIcons({ elements: [toast] });
setTimeout(() => {
toast.style.opacity = '0';
toast.style.transform = 'translateX(100%)';
toast.style.transition = 'all 0.3s';
setTimeout(() => toast.remove(), 300);
}, duration);
}
return { show,
success: (m, duration) => show(m, 'success', duration),
error: (m, duration) => show(m, 'error', duration),
warning: (m, duration) => show(m, 'warning', duration),
info: (m, duration) => show(m, 'info', duration),
};
})();
// ─── Router ──────────────────────────────────────────────────────────────────
const pageNames = {
'login': '登录',
'dashboard': '数据仪表盘',
'agent-dev': '对话应用配置',
'agent-manage': '应用管理',
'device': '设备管理',
'ota': 'OTA 升级',
'session': '会话详情',
'badcase': 'Bad Case 管理',
'rbac': '权限管理 (RBAC)',
'privacy': '隐私保护 (PII)',
'settings': '系统设置',
'knowledge-base': '知识库管理',
'kb-create': '创建知识库',
'kb-detail': '知识库详情',
};
const pageRenderers = {};
function registerPage(id, renderer) {
pageRenderers[id] = renderer;
}
function navigateTo(pageId) {
// Auth check - login page doesn't need auth
if (pageId !== 'login' && !checkAuth()) {
return;
}
const sidebar = document.getElementById('sidebar');
const topbar = document.querySelector('.topbar');
const mainContent = document.getElementById('mainContent');
const container = document.getElementById('pageContainer');
// Handle login page - full screen, hide sidebar and topbar
if (pageId === 'login') {
document.body.classList.add('login-mode');
if (pageRenderers[pageId]) {
container.innerHTML = `<div class="page active" id="page-${pageId}"></div>`;
pageRenderers[pageId](document.getElementById(`page-${pageId}`));
}
return;
}
// Restore normal layout for other pages
document.body.classList.remove('login-mode');
// Update nav
document.querySelectorAll('.nav-item').forEach(el => {
el.classList.toggle('active', el.dataset.page === pageId);
});
// Update breadcrumb
const breadcrumb = document.getElementById('breadcrumb');
if (breadcrumb) breadcrumb.textContent = pageNames[pageId] || pageId;
// Render page
if (!container) return;
if (pageRenderers[pageId]) {
container.innerHTML = `<div class="page active" id="page-${pageId}"></div>`;
pageRenderers[pageId](document.getElementById(`page-${pageId}`));
} else {
container.innerHTML = `
<div class="page active">
<div class="empty-state">
<i data-lucide="construction" style="width:48px;height:48px;color:#d9d9d9"></i>
<p style="font-size:16px;font-weight:600;margin-bottom:8px">功能开发中</p>
<p>该页面正在建设中,敬请期待</p>
</div>
</div>`;
lucide.createIcons();
}
}
// ─── Topbar Clock ────────────────────────────────────────────────────────────
function updateClock() {
const el = document.getElementById('topbarTime');
if (!el) return;
const now = new Date();
el.textContent = now.toLocaleString('zh-CN', {
month: '2-digit', day: '2-digit',
hour: '2-digit', minute: '2-digit', second: '2-digit',
hour12: false
});
}
// ─── Sidebar Toggle ──────────────────────────────────────────────────────────
function initSidebar() {
const sidebar = document.getElementById('sidebar');
const toggleBtn = document.getElementById('sidebarToggle');
const menuBtn = document.getElementById('menuBtn');
toggleBtn?.addEventListener('click', () => {
sidebar.classList.toggle('collapsed');
});
menuBtn?.addEventListener('click', () => {
if (window.innerWidth <= 768) {
sidebar.classList.toggle('mobile-open');
} else {
sidebar.classList.toggle('collapsed');
}
});
}
// ─── Nav Click ───────────────────────────────────────────────────────────────
function initNav() {
document.querySelectorAll('.nav-item[data-page]').forEach(el => {
el.addEventListener('click', e => {
e.preventDefault();
navigateTo(el.dataset.page);
if (window.innerWidth <= 768) {
document.getElementById('sidebar')?.classList.remove('mobile-open');
}
});
});
}
// ─── Modal Helper ────────────────────────────────────────────────────────────
function createModal({ title, content, onConfirm, confirmText = '确认', cancelText = '取消', size = '', showCancel = true }) {
const overlay = document.createElement('div');
overlay.className = 'modal-overlay';
const sizeClass = size ? ` ${String(size).startsWith('modal-') ? size : `modal-${size}`}` : '';
overlay.innerHTML = `
<div class="modal${sizeClass}">
<div class="modal-header">
<span class="modal-title">${title}</span>
<button class="modal-close"><i data-lucide="x"></i></button>
</div>
<div class="modal-body">${content}</div>
<div class="modal-footer">
${showCancel ? `<button class="btn btn-outline cancel-btn">${cancelText}</button>` : ''}
${onConfirm ? `<button class="btn btn-primary confirm-btn">${confirmText}</button>` : ''}
</div>
</div>`;
document.body.appendChild(overlay);
document.body.classList.add('modal-open');
lucide.createIcons({ elements: [overlay] });
const close = () => {
overlay.classList.remove('open');
setTimeout(() => {
overlay.remove();
if (!document.querySelector('.modal-overlay')) {
document.body.classList.remove('modal-open');
}
}, 200);
};
overlay.querySelector('.modal-close').addEventListener('click', close);
overlay.querySelector('.cancel-btn')?.addEventListener('click', close);
overlay.addEventListener('click', e => { if (e.target === overlay) close(); });
if (onConfirm) {
overlay.querySelector('.confirm-btn').addEventListener('click', async () => {
const confirmBtn = overlay.querySelector('.confirm-btn');
confirmBtn.disabled = true;
try {
const result = await onConfirm(overlay.querySelector('.modal-body'));
if (result === false) return;
close();
} finally {
if (document.body.contains(overlay)) confirmBtn.disabled = false;
}
});
}
requestAnimationFrame(() => overlay.classList.add('open'));
return { overlay, close };
}
// ─── Pagination Helper ───────────────────────────────────────────────────────
function renderPagination(container, { total, page, pageSize, onChange }) {
const totalPages = Math.ceil(total / pageSize);
container.innerHTML = `
<div style="display:flex;align-items:center;gap:8px;justify-content:flex-end;margin-top:16px">
<span style="font-size:13px;color:var(--text-muted)">共 ${total} 条</span>
<button class="btn btn-outline btn-sm" ${page <= 1 ? 'disabled' : ''} data-page="${page-1}">上一页</button>
<span style="font-size:13px;padding:0 4px">${page} / ${totalPages}</span>
<button class="btn btn-outline btn-sm" ${page >= totalPages ? 'disabled' : ''} data-page="${page+1}">下一页</button>
</div>`;
container.querySelectorAll('[data-page]').forEach(btn => {
btn.addEventListener('click', () => !btn.disabled && onChange(parseInt(btn.dataset.page)));
});
}
// ─── Init ────────────────────────────────────────────────────────────────────
document.addEventListener('DOMContentLoaded', () => {
initSidebar();
initNav();
updateClock();
setInterval(updateClock, 1000);
lucide.createIcons();
// Check auth and navigate
const loggedIn = sessionStorage.getItem('talkingq_logged_in');
const targetPage = loggedIn === 'true' ? 'dashboard' : 'login';
// Use a small delay to ensure DOM is fully ready
setTimeout(() => {
navigateTo(targetPage);
}, 50);
});
// Auth check helper
function checkAuth() {
return sessionStorage.getItem('talkingq_logged_in') === 'true';
}
// Logout helper
function doLogout() {
sessionStorage.removeItem('talkingq_user');
sessionStorage.removeItem('talkingq_logged_in');
Toast.info('已退出登录');
setTimeout(() => {
navigateTo('login');
}, 300);
}
window.Toast = Toast;
window.navigateTo = navigateTo;
window.registerPage = registerPage;
window.createModal = createModal;
window.renderPagination = renderPagination;
window.checkAuth = checkAuth;