272 lines
10 KiB
JavaScript
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;
|