| <!DOCTYPE html> |
| <html lang="en-US"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>QA 标注系统 - 管理后台</title> |
| <link rel="stylesheet" href="/static/css/manager.css"> |
| </head> |
| <body> |
| <div class="app-container"> |
| |
| <aside class="sidebar"> |
| <div class="sidebar-header"> |
| <h1 data-i18n="app.manager">管理后台</h1> |
| </div> |
| <nav class="sidebar-nav"> |
| |
| <div class="nav-group"> |
| <button class="nav-item active" data-section="users"> |
| <span class="nav-icon">👥</span> |
| <span class="nav-text" data-i18n="nav.userManagement">用户管理</span> |
| </button> |
| <button class="nav-item" data-section="datasets"> |
| <span class="nav-icon">📊</span> |
| <span class="nav-text" data-i18n="nav.datasetManagement">数据库管理</span> |
| </button> |
| <button class="nav-item" data-section="projects"> |
| <span class="nav-icon">📁</span> |
| <span class="nav-text" data-i18n="nav.projectManagement">项目管理</span> |
| </button> |
| <button class="nav-item" data-section="annotation-configs"> |
| <span class="nav-icon">⚙️</span> |
| <span class="nav-text" data-i18n="nav.annotationConfig">标注配置</span> |
| </button> |
| <button class="nav-item" data-section="seed-questions"> |
| <span class="nav-icon">🌱</span> |
| <span class="nav-text" data-i18n="nav.seedQuestionManagement">种子问题管理</span> |
| </button> |
| <button class="nav-item" data-section="system-config"> |
| <span class="nav-icon">⚙️</span> |
| <span class="nav-text" data-i18n="nav.systemConfig">系统配置</span> |
| </button> |
| </div> |
| |
| <div class="nav-divider"></div> |
| |
| <div class="nav-group"> |
| <button class="nav-item nav-item-link" id="goToUserBtn"> |
| <span class="nav-icon">👤</span> |
| <span class="nav-text" data-i18n="app.userCenter">用户中心</span> |
| </button> |
| </div> |
| </nav> |
| <div class="sidebar-footer"> |
| <button class="btn-language-switch" id="languageSwitchBtn" title="切换语言 / Switch Language"> |
| <span id="currentLanguage">中文</span> |
| </button> |
| <button class="btn-logout" id="logoutBtn" data-i18n="actions.logout">退出登录</button> |
| </div> |
| </aside> |
|
|
| |
| <main class="main-content"> |
| |
| <section class="content-section active" id="users-section"> |
| <div class="section-content-wrapper"> |
| |
| <div id="user-management-container"></div> |
| </div> |
| </section> |
|
|
| |
| <section class="content-section" id="datasets-section"> |
| <div class="section-content-wrapper"> |
| |
| <div id="dataset-management-container"></div> |
| </div> |
| </section> |
|
|
| |
| <section class="content-section" id="projects-section"> |
| <div class="section-header"> |
| <h2 data-i18n="nav.projectManagement">项目管理</h2> |
| <div class="header-actions"> |
| <button class="btn btn-secondary" id="showProjectUsageBtn">📖 <span data-i18n="project.projectUsage">项目功能说明</span></button> |
| <button class="btn btn-primary" id="addProjectBtn"><span data-i18n="project.addProject">添加项目</span></button> |
| <button class="btn btn-success" id="importProjectBtn"><span data-i18n="project.importProject">导入项目</span></button> |
| </div> |
| </div> |
| <div class="section-content-wrapper"> |
| <div class="section-content"> |
| |
| <div id="projects-list"> |
| <div id="projectsCardContainer" class="projects-card-container"> |
| <div class="loading" style="text-align: center; padding: 40px; color: #999;" data-i18n="common.loading">加载中...</div> |
| </div> |
| </div> |
|
|
| |
| <div id="project-detail-container" style="display: none;"></div> |
| </div> |
| </div> |
| </section> |
|
|
| |
| <section class="content-section" id="annotation-configs-section"> |
| <div class="section-content-wrapper"> |
| |
| <div id="annotation-config-management-container"></div> |
| </div> |
| </section> |
|
|
| |
| <section class="content-section" id="seed-questions-section"> |
| <div class="section-content-wrapper"> |
| |
| <div id="seed-question-management-container"></div> |
| </div> |
| </section> |
|
|
| |
| <section class="content-section" id="system-config-section"> |
| <div class="section-content-wrapper"> |
| |
| <div id="system-config-management-container"></div> |
| </div> |
| </section> |
| </main> |
| </div> |
|
|
| |
| <div class="modal" id="modal"> |
| <div class="modal-content"> |
| <div class="modal-header"> |
| <h3 id="modalTitle">标题</h3> |
| <button class="modal-close" id="modalClose">×</button> |
| </div> |
| <div class="modal-body" id="modalBody"> |
| |
| </div> |
| <div class="modal-footer"> |
| <button class="btn btn-secondary" id="modalCancel" data-i18n="actions.cancel">取消</button> |
| <button class="btn btn-primary" id="modalSubmit" data-i18n="actions.confirm">确定</button> |
| </div> |
| </div> |
| </div> |
|
|
| <script src="https://cdn.jsdelivr.net/npm/crypto-js@4.2.0/crypto-js.js"></script> |
| <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script> |
| <script src="https://unpkg.com/i18next@23.7.11/dist/umd/i18next.min.js"></script> |
| <script src="/static/js/api.js"></script> |
| <script src="/static/js/project-api.js"></script> |
| <script src="/static/js/pagination.js"></script> |
| <script src="/static/js/i18n-helper.js"></script> |
| <script src="/static/js/manager.js"></script> |
|
|
| |
| <script> |
| |
| window.addEventListener('load', async function() { |
| |
| if (typeof i18next === 'undefined') { |
| console.error('i18next 库未加载'); |
| return; |
| } |
| |
| |
| const savedLanguage = localStorage.getItem('appLanguage') || 'en-US'; |
| |
| try { |
| |
| const random = Math.floor(Math.random() * 1000000); |
| console.log('Loading language files with random:', random); |
| const [zhCN, enUS] = await Promise.all([ |
| fetch(`/static/locales/zh-CN.json?v=${random}`).then(r => { |
| console.log('zh-CN status:', r.status); |
| return r.json(); |
| }), |
| fetch(`/static/locales/en-US.json?v=${random}`).then(r => { |
| console.log('en-US status:', r.status); |
| return r.json(); |
| }) |
| ]); |
| |
| |
| console.log('=== Language file debug ==='); |
| console.log('zhCN has dataset:', !!zhCN.translation.dataset); |
| console.log('zhCN.dataset.manageConfigs:', zhCN.translation.dataset?.manageConfigs); |
| console.log('zhCN.dataset all keys:', Object.keys(zhCN.translation.dataset || {})); |
| console.log('Total dataset keys count:', Object.keys(zhCN.translation.dataset || {}).length); |
| console.log('========================'); |
| |
| await i18next.init({ |
| lng: savedLanguage, |
| fallbackLng: 'en-US', |
| debug: false, |
| resources: { |
| 'zh-CN': { |
| translation: zhCN.translation |
| }, |
| 'en-US': { |
| translation: enUS.translation |
| } |
| } |
| }); |
| |
| |
| window.i18next = i18next; |
| window.t = function(key, options) { |
| return i18next.t(key, options); |
| }; |
| |
| |
| const i18nReadyEvent = new CustomEvent('i18next-ready', { |
| detail: { language: savedLanguage } |
| }); |
| window.dispatchEvent(i18nReadyEvent); |
| |
| |
| updatePageLanguage(); |
| |
| |
| const langBtn = document.getElementById('languageSwitchBtn'); |
| if (langBtn) { |
| langBtn.addEventListener('click', toggleLanguage); |
| } |
| } catch (error) { |
| console.error('i18next 初始化失败:', error); |
| } |
| }); |
| |
| |
| function toggleLanguage() { |
| if (!window.i18next) return; |
| |
| const currentLang = window.i18next.language; |
| const newLang = currentLang === 'zh-CN' ? 'en-US' : 'zh-CN'; |
| |
| window.i18next.changeLanguage(newLang, () => { |
| localStorage.setItem('appLanguage', newLang); |
| updatePageLanguage(); |
| }); |
| } |
| |
| |
| function updatePageLanguage() { |
| if (!window.i18next) return; |
| |
| const lang = window.i18next.language; |
| |
| |
| document.documentElement.lang = lang; |
| |
| |
| const langBtn = document.getElementById('currentLanguage'); |
| if (langBtn) { |
| langBtn.textContent = lang === 'zh-CN' ? '中文' : 'English'; |
| } |
| |
| |
| document.querySelectorAll('[data-i18n]').forEach(element => { |
| const key = element.getAttribute('data-i18n'); |
| const translation = window.i18next.t(key); |
| |
| |
| if (element.children.length === 1 && element.children[0].tagName === 'SPAN') { |
| |
| const span = element.children[0]; |
| if (span.hasAttribute('data-i18n')) { |
| span.textContent = window.i18next.t(span.getAttribute('data-i18n')); |
| } else { |
| element.textContent = translation; |
| } |
| } else { |
| element.textContent = translation; |
| } |
| }); |
| |
| |
| document.querySelectorAll('[data-i18n-title]').forEach(element => { |
| const key = element.getAttribute('data-i18n-title'); |
| element.title = window.i18next.t(key); |
| }); |
| |
| |
| document.querySelectorAll('[data-i18n-placeholder]').forEach(element => { |
| const key = element.getAttribute('data-i18n-placeholder'); |
| element.placeholder = window.i18next.t(key); |
| }); |
| |
| |
| document.querySelectorAll('select').forEach(select => { |
| select.querySelectorAll('option').forEach(option => { |
| const i18nKey = option.getAttribute('data-i18n'); |
| if (i18nKey) { |
| |
| if (option.firstChild) { |
| option.firstChild.textContent = window.i18next.t(i18nKey); |
| } else { |
| option.textContent = window.i18next.t(i18nKey); |
| } |
| } |
| }); |
| }); |
| |
| |
| document.title = window.i18next.t('app.title') + ' - ' + window.i18next.t('app.manager'); |
| |
| |
| const activeSection = document.querySelector('.content-section.active'); |
| const activeSectionId = activeSection ? activeSection.id : ''; |
| |
| |
| const sectionContainers = { |
| 'users-section': 'user-management-container', |
| 'datasets-section': 'dataset-management-container', |
| 'annotation-configs-section': 'annotation-config-management-container', |
| 'seed-questions-section': 'seed-question-management-container', |
| 'system-config-section': 'system-config-management-container' |
| }; |
| |
| |
| Object.keys(sectionContainers).forEach(sectionId => { |
| if (sectionId !== activeSectionId) { |
| const containerId = sectionContainers[sectionId]; |
| const container = document.getElementById(containerId); |
| if (container) { |
| container.innerHTML = ''; |
| } |
| } |
| }); |
| |
| |
| if (activeSection) { |
| const sectionId = activeSection.id; |
| |
| |
| if (sectionId === 'users-section' && window.UserManagement) { |
| window.UserManagement.loadUsers(); |
| } else if (sectionId === 'datasets-section' && window.DatasetManagement) { |
| window.DatasetManagement.loadDatasets(); |
| } else if (sectionId === 'projects-section') { |
| |
| const projectList = document.getElementById('projects-list'); |
| const projectDetail = document.getElementById('project-detail-container'); |
| if (projectDetail && projectDetail.style.display !== 'none' && window.currentProjectId) { |
| |
| if (window.viewProjectDetail) { |
| window.viewProjectDetail(window.currentProjectId); |
| } |
| } else { |
| |
| loadProjects(); |
| } |
| } else if (sectionId === 'annotation-configs-section' && window.AnnotationConfigManagement) { |
| window.AnnotationConfigManagement.loadAnnotationConfigs(); |
| } else if (sectionId === 'seed-questions-section' && window.SeedQuestionManagement) { |
| window.SeedQuestionManagement.loadSeedQuestions(); |
| } |
| } |
| } |
| </script> |
| </body> |
| </html> |
|
|