/** * 标注配置管理模块 JavaScript * 提供标注配置的查看、创建、编辑、删除功能 */ (function() { 'use strict'; // 引用全局的翻译函数 // 翻译函数由 i18n-helper.js 提供 // 模块私有状态 let configsPaginator = null; let editingId = null; let container = null; // 标注配置管理模块 const AnnotationConfigManagement = { init: function(containerElement) { container = containerElement; this.initPaginators(); this.initEventListeners(); // 等待 i18next 初始化完成后再加载数据 const loadData = () => { this.loadAnnotationConfigs(); }; // 检查 i18next 是否已可用(通过测试一个翻译键) const checkI18nReady = () => { if (window.i18next && window.i18next.t) { try { const test = window.i18next.t('common.loading'); // 如果翻译键正常工作(返回值不是键名本身),开始加载数据 if (test && test !== 'common.loading') { loadData(); return true; } } catch (e) { // i18next 还没准备好 } } return false; }; // 立即检查一次 if (checkI18nReady()) { return; } // 等待 i18next 初始化 const checkI18n = setInterval(() => { if (checkI18nReady()) { clearInterval(checkI18n); } }, 100); // 超时保护(10秒后强制加载) setTimeout(() => { clearInterval(checkI18n); console.warn('i18next initialization timeout or check failed, loading data anyway'); loadData(); }, 10000); }, initPaginators: function() { // 初始化标注配置管理分页器 configsPaginator = createPaginator('configs', { initialPage: 1, initialPageSize: 20, onPageChange: (page, pageSize) => { this.loadAnnotationConfigs(); }, onPageSizeChange: (pageSize) => { this.loadAnnotationConfigs(); } }); }, initEventListeners: function() { // 添加配置按钮 const addConfigBtn = container.querySelector('#addConfigBtn'); if (addConfigBtn) { addConfigBtn.addEventListener('click', () => { this.showAnnotationConfigForm(); }); } // 使用说明按钮 const showConfigUsageBtn = container.querySelector('#showConfigUsageBtn'); if (showConfigUsageBtn) { showConfigUsageBtn.addEventListener('click', () => { this.showConfigUsage(); }); } }, loadAnnotationConfigs: async function() { if (!configsPaginator) return; try { const skip = configsPaginator.getSkip(); const limit = configsPaginator.getLimit(); const configs = await apiGet(`/annotation-configs/?skip=${skip}&limit=${limit}`); // 获取总数:如果返回的数据量小于limit,说明这是最后一页 // 如果等于limit,可能还有更多数据,尝试获取下一页来判断 let totalCount; if (configs.length < limit) { totalCount = skip + configs.length; } else { // 尝试获取下一页来判断是否还有更多数据 try { const nextPage = await apiGet(`/annotation-configs/?skip=${skip + limit}&limit=1`); if (nextPage.length > 0) { // 还有更多数据,但不知道总数,使用估算值(当前页数 * 每页数量 + 1) totalCount = (skip + limit) + 1; } else { // 没有更多数据了 totalCount = skip + limit; } } catch (error) { // 如果获取失败,假设还有更多数据 totalCount = (skip + limit) + 1; } } configsPaginator.setTotalCount(totalCount); this.renderAnnotationConfigsTable(configs); } catch (error) { console.error('加载标注配置失败:', error); showError(t('config.loadFailed') + ': ' + (error.message || t('common.unknownError'))); } }, renderAnnotationConfigsTable: async function(configs) { const tbody = container.querySelector('#configsTableBody'); const paginationContainer = container.querySelector('#configsPaginationContainer'); if (configs.length === 0) { tbody.innerHTML = `
${t('config.selectTypeFirst')}
${t('config.unknownType')}
${t('config.noOptionsHint')}
`}${t('config.noOptionsHint')}
`; } }, saveAnnotationConfig: async function() { const name = document.getElementById('name').value; const description = document.getElementById('description').value; const annotation_type = document.getElementById('annotation_type').value; const required = document.getElementById('required').checked; const show_reason = document.getElementById('show_reason').checked; const show_confidence = document.getElementById('show_confidence').checked; if (!annotation_type) { showError(t('config.selectTypeFirst')); return; } // 根据标注类型收集配置数据 let config_obj; try { config_obj = this.collectConfigData(annotation_type); } catch (error) { showError(t('config.configDataError') + ': ' + error.message); return; } const data = { name, description: description || null, annotation_type, required, show_reason, show_confidence, config: config_obj }; try { if (editingId) { data.id = editingId; await apiPut(`/annotation-configs/${editingId}`, data); showSuccess(t('config.updateSuccess')); } else { await apiPost('/annotation-configs/', data); showSuccess(t('config.createSuccess')); } closeModal(); // 如果是新建配置,重置到第一页 if (!editingId && configsPaginator) { configsPaginator.reset(); } this.loadAnnotationConfigs(); } catch (error) { showError(t('config.saveFailed') + ': ' + (error.message || t('common.unknownError'))); } }, collectConfigData: function(annotationType) { switch(annotationType) { case 'score': const minScore = parseInt(document.getElementById('min_score').value); const maxScore = parseInt(document.getElementById('max_score').value); const scoreStep = parseFloat(document.getElementById('score_step').value) || 1.0; if (isNaN(minScore) || isNaN(maxScore)) { throw new Error(t('config.scoreMustBeNumber')); } if (minScore >= maxScore) { throw new Error(t('config.maxScoreMustBeGreaterThanMin')); } return { min_score: minScore, max_score: maxScore, score_step: scoreStep }; case 'category': const categoriesText = document.getElementById('categories').value; const categories = categoriesText .split('\n') .map(c => c.trim()) .filter(c => c.length > 0); return { categories: categories.length > 0 ? categories : null }; case 'text': const maxLength = document.getElementById('max_length').value; return { max_length: maxLength ? parseInt(maxLength) : null }; case 'single_choice': case 'multi_choice': const optionItems = document.querySelectorAll('.option-item'); if (optionItems.length === 0) { throw new Error(t('config.atLeastOneOption')); } const options = Array.from(optionItems).map((item, index) => { const optionId = item.querySelector('.option-id').value.trim(); const label = item.querySelector('.option-label').value.trim(); const description = item.querySelector('.option-description').value.trim(); const value = item.querySelector('.option-value').value.trim(); const order = parseInt(item.querySelector('.option-order').value) || index; const enabled = item.querySelector('.option-enabled').checked; if (!optionId || !label || !value) { throw new Error(`${t('config.option')} ${index + 1} ${t('config.optionFieldsRequired')}`); } // 尝试将值转换为合适的类型 let parsedValue = value; if (!isNaN(value) && value.trim() !== '') { if (value.includes('.')) { parsedValue = parseFloat(value); } else { parsedValue = parseInt(value); } } else if (value.toLowerCase() === 'true') { parsedValue = true; } else if (value.toLowerCase() === 'false') { parsedValue = false; } return { option_id: optionId, label: label, description: description || null, value: parsedValue, order: order, enabled: enabled }; }); return { options }; case 'binary': const trueLabel = document.getElementById('true_label').value.trim() || t('common.yes'); const falseLabel = document.getElementById('false_label').value.trim() || t('common.no'); return { true_label: trueLabel, false_label: falseLabel }; default: throw new Error(t('config.unknownType')); } }, editAnnotationConfig: async function(configId) { try { // 先检查是否有标注结果 const stats = await apiGet(`/annotation-configs/${configId}/results-count`); if (stats.count > 0) { const datasetsInfo = stats.datasets.length > 0 ? `${t('config.relatedDatasets')}${stats.datasets.map(d => d.name).join('、')}` : ''; const message = `${t('config.editBlocked.results', {count: stats.count, datasetCount: stats.dataset_count})}\n${datasetsInfo}\n\n${t('config.editBlocked.instruction')}`; showError(message); return; } // 如果没有标注结果,继续加载配置并显示编辑表单 const config = await apiGet(`/annotation-configs/${configId}`); this.showAnnotationConfigForm(config); } catch (error) { // 如果统计接口返回404,说明配置不存在 if (error.status === 404) { showError(t('config.configNotFound') + ': ' + (error.message || t('common.unknownError'))); } else if (error.status === 400) { // 如果统计接口返回400错误,说明有标注结果,错误信息已经包含详细信息 showError(error.message || t('config.editBlocked.defaultMessage')); } else { showError(t('config.loadFailed') + ': ' + (error.message || t('common.unknownError'))); } } }, clearConfigResults: async function(configId) { try { // 先检查是否有标注结果 const stats = await apiGet(`/annotation-configs/${configId}/results-count`); if (stats.count === 0) { showError(t('config.noResultsToClear')); return; } // 显示确认对话框,包含详细信息 const datasetsInfo = stats.datasets.length > 0 ? `${t('config.relatedDatasets')}${stats.datasets.map(d => d.name).join('、')}\n` : ''; const message = `${t('config.clearResultsConfirm')}\n\n${t('config.resultsCount')}${stats.count} ${t('config.items')}\n${t('config.datasetCount')}${stats.dataset_count} ${t('config.units')}\n${datasetsInfo}\n${t('config.irreversibleOperation')}`; if (!confirm(message)) { return; } // 调用清除接口 const result = await apiDelete(`/annotation-configs/${configId}/results`); showSuccess(result.message || `${t('config.clearedResults')} ${result.deleted_count || 0} ${t('config.items')}`); // 刷新配置列表 this.loadAnnotationConfigs(); } catch (error) { // 如果统计接口返回404,说明配置不存在 if (error.status === 404) { showError(t('config.configNotFound') + ': ' + (error.message || t('common.unknownError'))); } else { showError(t('config.clearResultsFailed') + ': ' + (error.message || t('common.unknownError'))); } } }, deleteAnnotationConfig: async function(configId) { try { // 先检查是否有标注结果 const stats = await apiGet(`/annotation-configs/${configId}/results-count`); if (stats.count > 0) { const datasetsInfo = stats.datasets.length > 0 ? `${t('config.relatedDatasets')}${stats.datasets.map(d => d.name).join('、')}` : ''; const message = `${t('config.deleteBlocked.results', {count: stats.count, datasetCount: stats.dataset_count})}\n${datasetsInfo}\n\n${t('config.deleteBlocked.instruction')}`; showError(message); return; } // 如果没有标注结果,显示确认对话框 if (!confirm(t('config.deleteConfirm'))) { return; } await apiDelete(`/annotation-configs/${configId}`); showSuccess(t('config.deleteSuccess')); // 如果当前页没有数据了,回到上一页 if (configsPaginator) { configsPaginator.adjustPageAfterDelete(); } this.loadAnnotationConfigs(); } catch (error) { // 如果统计接口返回404,说明配置不存在 if (error.status === 404) { showError(t('config.configNotFound') + ': ' + (error.message || t('common.unknownError'))); } else if (error.status === 400) { // 如果统计接口或删除接口返回400错误,说明有标注结果,错误信息已经包含详细信息 showError(error.message || t('config.deleteBlocked.defaultMessage')); } else { showError(t('config.deleteFailed') + ': ' + (error.message || t('common.unknownError'))); } } }, showConfigUsage: function() { // 构建标注配置功能说明内容 const usageContent = `