mds多语言,前端

This commit is contained in:
2026-05-24 16:51:16 +08:00
parent 028e7cbc63
commit ab2641876d
9 changed files with 905 additions and 122 deletions
+166 -1
View File
@@ -494,5 +494,170 @@ window.__i18n_de_DE__ = {
'alert.binlog_listener_stopped': 'Binlog-Listener gestoppt',
'alert.high_error_rate': 'Hohe Fehlerrate',
'alert.memory_warning': 'Speichernutzung-Warnung',
'alert.cpu_warning': 'CPU-Nutzung-Warnung'
'alert.cpu_warning': 'CPU-Nutzung-Warnung',
// ================================================================================
// MDS-Modul (Daten-Bereitstellungssystem)
// ================================================================================
// ============ App-Titel ============
'mds.app.title': 'Daten-Bereitstellungssystem',
'mds.app.guide': 'Benutzerhandbuch',
'mds.app.apiDoc': 'API-Doku',
// ============ Navigation ============
'mds.nav.material': 'Material',
'mds.nav.matVer': 'Mat-Version',
'mds.nav.workcenter': 'Arbeitsplatz',
'mds.nav.matWc': 'Arbeitsplan',
'mds.nav.bom': 'Stückliste',
'mds.nav.mold': 'Werkzeug',
'mds.nav.matWcMold': 'Mat-Wc-Werkzeug',
'mds.nav.all': 'Alle',
// ============ Status-Labels (P0) ============
'mds.status.pending': 'Ausstehend',
'mds.status.compliancePass': 'Compliance bestanden',
'mds.status.complianceError': 'Compliance fehlerhaft',
'mds.status.relationPass': 'Relation bestanden',
'mds.status.relationError': 'Relation fehlerhaft',
'mds.status.syncError': 'Sync fehlgeschlagen',
'mds.status.synced': 'Synchronisiert',
'mds.status.all': 'Alle',
// ============ Aktion-Buttons (P1) ============
'mds.action.import': 'Importieren',
'mds.action.validate': 'Validieren',
'mds.action.sync': 'Synchronisieren',
'mds.action.query': 'Abfrage',
'mds.action.reset': 'Zurücksetzen',
'mds.action.save': 'Speichern',
'mds.action.delete': 'Löschen',
'mds.action.cancel': 'Abbrechen',
'mds.action.confirm': 'Bestätigen',
'mds.action.close': 'Schließen',
'mds.action.clear': 'Löschen',
'mds.action.export': 'Exportieren',
'mds.action.exportTemplate': 'Vorlage exportieren',
'mds.action.refresh': 'Aktualisieren',
'mds.action.filter': 'Filtern',
'mds.action.edit': 'Bearbeiten',
'mds.action.view': 'Anzeigen',
'mds.action.selectAll': 'Alle auswählen',
'mds.action.deselectAll': 'Auswahl aufheben',
'mds.action.batchDelete': 'Massenlöschung',
'mds.action.batchSync': 'Massensync',
'mds.action.showRules': 'Regeln anzeigen',
'mds.action.preciseFilter': 'Präziser Filter',
'mds.action.clearFilter': 'Filter löschen',
// ============ Validierung ============
'mds.validation.title': 'Validierung',
'mds.validation.complete': 'Validierung abgeschlossen: {pass} bestanden, {fail} fehlgeschlagen',
'mds.validation.noPending': 'Keine ausstehenden Datensätze',
'mds.validation.confirmStart': 'Fehlende Felder werden mit Standardwerten gefüllt. Fortfahren?',
'mds.validation.processing': 'Validierung läuft',
'mds.validation.failed': 'Validierung fehlgeschlagen',
'mds.validation.progress': 'Verarbeitet {current}/{total}',
'mds.validation.rulesTitle': 'Validierungsregeln',
'mds.validation.noRules': 'Keine Validierungsregeln',
// ============ Sync ============
'mds.sync.title': 'Synchronisation',
'mds.sync.complete': 'Sync abgeschlossen: {accounts} Konten, {synced} synchronisiert, {dedup} Dedup fehlgeschlagen, {failed} sonstige fehlgeschlagen',
'mds.sync.noData': 'Keine [Relation bestanden] oder [Sync fehlgeschlagen] Datensätze zum Synchronisieren',
'mds.sync.selectTarget': 'Bitte mindestens ein Zielkonto auswählen',
'mds.sync.selectMode': 'Sync-Modus auswählen',
'mds.sync.incremental': 'Inkrementelle Sync',
'mds.sync.refresh': 'Aktualisierungs-Sync',
'mds.sync.confirmRefresh': 'Aktualisierungs-Sync löscht alle Daten in der Zieltabelle. Vorsicht!',
'mds.sync.processing': 'Synchronisierung läuft',
'mds.sync.progress': 'Sync-Fortschritt',
// ============ Upload ============
'mds.upload.title': 'Excel-Daten importieren',
'mds.upload.success': 'Import abgeschlossen: {inserted} eingefügt, {skipped} übersprungen',
'mds.upload.invalidType': 'Bitte Excel- oder CSV-Datei hochladen',
'mds.upload.noFile': 'Bitte zuerst eine Datei auswählen',
'mds.upload.dragDrop': 'Klicken oder ziehen zum Hochladen (.xlsx, .xls, .csv)',
'mds.upload.processing': 'Import läuft',
'mds.upload.dedupStrategy': 'Dedup-Strategie',
'mds.upload.overwrite': 'Überschreiben',
'mds.upload.skip': 'Überspringen',
// ============ Tabelle ============
'mds.table.noData': 'Keine Daten',
'mds.table.loading': 'Laden...',
'mds.table.selectAll': 'Alle auswählen',
'mds.table.perPage': '/ Seite',
'mds.table.total': 'Gesamt {count}',
'mds.table.edit': 'Bearbeiten',
'mds.table.delete': 'Löschen',
'mds.table.export': 'Vorlage exportieren',
'mds.table.selected': '{count} ausgewählt',
'mds.table.actions': 'Aktionen',
'mds.table.status': 'Status',
'mds.table.columns': 'Spalten',
// ============ Modal ============
'mds.modal.confirm': 'Bestätigen',
'mds.modal.cancel': 'Abbrechen',
'mds.modal.close': 'Schließen',
'mds.modal.importTitle': 'Excel-Daten importieren',
'mds.modal.filterTitle': 'Präziser Filter',
'mds.modal.editTitle': 'Datensatz bearbeiten',
'mds.modal.validationRules': 'Validierungsregeln',
'mds.modal.syncTitle': 'Daten synchronisieren',
'mds.modal.deleteConfirm': 'Diesen Datensatz löschen?',
'mds.modal.batchDeleteConfirm': '{count} ausgewählte Datensätze löschen?',
// ============ Fehler ============
'mds.error.queryFailed': 'Abfrage fehlgeschlagen',
'mds.error.uploadFailed': 'Upload fehlgeschlagen',
'mds.error.timeout': 'Anfrage-Timeout',
'mds.error.loadFailed': 'Laden fehlgeschlagen, bitte erneut versuchen',
'mds.error.validateFailed': 'Validierung fehlgeschlagen',
'mds.error.syncFailed': 'Sync fehlgeschlagen',
'mds.error.noPermission': 'Keine Berechtigung',
'mds.error.invalidData': 'Ungültiges Datenformat',
'mds.error.duplicateKey': 'Duplikat: {field}={value} bereits vorhanden',
'mds.error.foreignKeyViolation': 'Fremdschlüsselverletzung: {field} Referenz nicht gefunden',
'mds.error.validationFailed': 'Validierung fehlgeschlagen: {reason}',
// ============ Erfolg ============
'mds.success.queryComplete': 'Abfrage abgeschlossen',
'mds.success.uploadComplete': 'Import abgeschlossen',
'mds.success.validateComplete': 'Validierung abgeschlossen',
'mds.success.syncComplete': 'Sync abgeschlossen',
'mds.success.deleteComplete': 'Löschen abgeschlossen',
'mds.success.saveComplete': 'Speichern abgeschlossen',
'mds.success.exportComplete': 'Export abgeschlossen',
// ============ Statistiken ============
'mds.stats.total': 'Gesamt',
'mds.stats.pending': 'Ausstehend',
'mds.stats.passed': 'Bestanden',
'mds.stats.failed': 'Fehlgeschlagen',
'mds.stats.synced': 'Synchronisiert',
'mds.stats.today': 'Heute',
'mds.stats.week': 'Diese Woche',
'mds.stats.month': 'Dieser Monat',
// ============ Formular ============
'mds.form.required': 'Pflichtfeld',
'mds.form.optional': 'Optional',
'mds.form.defaultValue': 'Standardwert',
'mds.form.placeholder': 'Wert eingeben',
'mds.form.selectPlaceholder': 'Auswählen',
// ============ Sonstiges ============
'mds.other.loading': 'Laden...',
'mds.other.processing': 'Verarbeitung...',
'mds.other.noData': 'Keine Daten',
'mds.other.confirm': 'Bestätigen',
'mds.other.cancel': 'Abbrechen',
'mds.other.tip': 'Hinweis',
'mds.other.warning': 'Warnung',
'mds.other.error': 'Fehler',
'mds.other.success': 'Erfolg'
};
+166 -1
View File
@@ -493,5 +493,170 @@ window.__i18n_en_US__ = {
'alert.binlog_listener_stopped': 'Binlog listener stopped',
'alert.high_error_rate': 'High error rate',
'alert.memory_warning': 'Memory usage warning',
'alert.cpu_warning': 'CPU usage warning'
'alert.cpu_warning': 'CPU usage warning',
// ================================================================================
// MDS Module (Data Staging System)
// ================================================================================
// ============ App Title ============
'mds.app.title': 'Data Staging System',
'mds.app.guide': 'User Guide',
'mds.app.apiDoc': 'API Docs',
// ============ Navigation ============
'mds.nav.material': 'Material',
'mds.nav.matVer': 'Mat Version',
'mds.nav.workcenter': 'Work Center',
'mds.nav.matWc': 'Routing',
'mds.nav.bom': 'BOM',
'mds.nav.mold': 'Mold',
'mds.nav.matWcMold': 'Mat-Wc-Mold',
'mds.nav.all': 'All',
// ============ Status Labels (P0) ============
'mds.status.pending': 'Pending',
'mds.status.compliancePass': 'Compliance Pass',
'mds.status.complianceError': 'Compliance Error',
'mds.status.relationPass': 'Relation Pass',
'mds.status.relationError': 'Relation Error',
'mds.status.syncError': 'Sync Failed',
'mds.status.synced': 'Synced',
'mds.status.all': 'All',
// ============ Action Buttons (P1) ============
'mds.action.import': 'Import',
'mds.action.validate': 'Validate',
'mds.action.sync': 'Sync',
'mds.action.query': 'Query',
'mds.action.reset': 'Reset',
'mds.action.save': 'Save',
'mds.action.delete': 'Delete',
'mds.action.cancel': 'Cancel',
'mds.action.confirm': 'Confirm',
'mds.action.close': 'Close',
'mds.action.clear': 'Clear',
'mds.action.export': 'Export',
'mds.action.exportTemplate': 'Export Template',
'mds.action.refresh': 'Refresh',
'mds.action.filter': 'Filter',
'mds.action.edit': 'Edit',
'mds.action.view': 'View',
'mds.action.selectAll': 'Select All',
'mds.action.deselectAll': 'Deselect All',
'mds.action.batchDelete': 'Batch Delete',
'mds.action.batchSync': 'Batch Sync',
'mds.action.showRules': 'View Rules',
'mds.action.preciseFilter': 'Precise Filter',
'mds.action.clearFilter': 'Clear Filter',
// ============ Validation ============
'mds.validation.title': 'Validation',
'mds.validation.complete': 'Validation complete: {pass} passed, {fail} failed',
'mds.validation.noPending': 'No pending records',
'mds.validation.confirmStart': 'Missing fields will be filled with default values. Proceed?',
'mds.validation.processing': 'Validating',
'mds.validation.failed': 'Validation failed',
'mds.validation.progress': 'Processed {current}/{total}',
'mds.validation.rulesTitle': 'Validation Rules',
'mds.validation.noRules': 'No validation rules',
// ============ Sync ============
'mds.sync.title': 'Sync',
'mds.sync.complete': 'Sync complete: {accounts} accounts, {synced} synced, {dedup} dedup failed, {failed} other failed',
'mds.sync.noData': 'No [Relation Pass] or [Sync Failed] records to sync',
'mds.sync.selectTarget': 'Please select at least one target account',
'mds.sync.selectMode': 'Select sync mode',
'mds.sync.incremental': 'Incremental Sync',
'mds.sync.refresh': 'Refresh Sync',
'mds.sync.confirmRefresh': 'Refresh sync will delete all data in target table. Proceed with caution!',
'mds.sync.processing': 'Syncing',
'mds.sync.progress': 'Sync Progress',
// ============ Upload ============
'mds.upload.title': 'Import Excel Data',
'mds.upload.success': 'Import complete: {inserted} inserted, {skipped} skipped',
'mds.upload.invalidType': 'Please upload Excel or CSV file',
'mds.upload.noFile': 'Please select a file first',
'mds.upload.dragDrop': 'Click or drag to upload (.xlsx, .xls, .csv)',
'mds.upload.processing': 'Importing',
'mds.upload.dedupStrategy': 'Dedup Strategy',
'mds.upload.overwrite': 'Overwrite',
'mds.upload.skip': 'Skip',
// ============ Table ============
'mds.table.noData': 'No data',
'mds.table.loading': 'Loading...',
'mds.table.selectAll': 'Select All',
'mds.table.perPage': '/ page',
'mds.table.total': 'Total {count}',
'mds.table.edit': 'Edit',
'mds.table.delete': 'Delete',
'mds.table.export': 'Export Template',
'mds.table.selected': '{count} selected',
'mds.table.actions': 'Actions',
'mds.table.status': 'Status',
'mds.table.columns': 'Columns',
// ============ Modal ============
'mds.modal.confirm': 'Confirm',
'mds.modal.cancel': 'Cancel',
'mds.modal.close': 'Close',
'mds.modal.importTitle': 'Import Excel Data',
'mds.modal.filterTitle': 'Precise Filter',
'mds.modal.editTitle': 'Edit Record',
'mds.modal.validationRules': 'Validation Rules',
'mds.modal.syncTitle': 'Sync Data',
'mds.modal.deleteConfirm': 'Delete this record?',
'mds.modal.batchDeleteConfirm': 'Delete {count} selected records?',
// ============ Errors ============
'mds.error.queryFailed': 'Query failed',
'mds.error.uploadFailed': 'Upload failed',
'mds.error.timeout': 'Request timeout',
'mds.error.loadFailed': 'Load failed, please retry',
'mds.error.validateFailed': 'Validation failed',
'mds.error.syncFailed': 'Sync failed',
'mds.error.noPermission': 'No permission',
'mds.error.invalidData': 'Invalid data format',
'mds.error.duplicateKey': 'Duplicate data: {field}={value} already exists',
'mds.error.foreignKeyViolation': 'Foreign key violation: {field} reference not found',
'mds.error.validationFailed': 'Validation failed: {reason}',
// ============ Success ============
'mds.success.queryComplete': 'Query complete',
'mds.success.uploadComplete': 'Import complete',
'mds.success.validateComplete': 'Validation complete',
'mds.success.syncComplete': 'Sync complete',
'mds.success.deleteComplete': 'Delete complete',
'mds.success.saveComplete': 'Save complete',
'mds.success.exportComplete': 'Export complete',
// ============ Stats ============
'mds.stats.total': 'Total',
'mds.stats.pending': 'Pending',
'mds.stats.passed': 'Passed',
'mds.stats.failed': 'Failed',
'mds.stats.synced': 'Synced',
'mds.stats.today': 'Today',
'mds.stats.week': 'This Week',
'mds.stats.month': 'This Month',
// ============ Form ============
'mds.form.required': 'Required',
'mds.form.optional': 'Optional',
'mds.form.defaultValue': 'Default',
'mds.form.placeholder': 'Enter value',
'mds.form.selectPlaceholder': 'Select',
// ============ Other ============
'mds.other.loading': 'Loading...',
'mds.other.processing': 'Processing...',
'mds.other.noData': 'No data',
'mds.other.confirm': 'Confirm',
'mds.other.cancel': 'Cancel',
'mds.other.tip': 'Tip',
'mds.other.warning': 'Warning',
'mds.other.error': 'Error',
'mds.other.success': 'Success'
};
+166 -1
View File
@@ -494,5 +494,170 @@ window.__i18n_zh_CN__ = {
'alert.binlog_listener_stopped': 'Binlog监听器已停止',
'alert.high_error_rate': '错误率过高',
'alert.memory_warning': '内存使用警告',
'alert.cpu_warning': 'CPU使用警告'
'alert.cpu_warning': 'CPU使用警告',
// ================================================================================
// MDS模块 (数据清洗管理系统)
// ================================================================================
// ============ 应用标题 ============
'mds.app.title': '数据清洗管理系统',
'mds.app.guide': '操作指引',
'mds.app.apiDoc': 'API文档',
// ============ 导航菜单 ============
'mds.nav.material': '物料',
'mds.nav.matVer': '产线版本',
'mds.nav.workcenter': '工作中心',
'mds.nav.matWc': '工艺路线',
'mds.nav.bom': 'BOM',
'mds.nav.mold': '模具',
'mds.nav.matWcMold': '机台模具',
'mds.nav.all': '全部',
// ============ 状态标签 (P0级) ============
'mds.status.pending': '待处理',
'mds.status.compliancePass': '初检通过',
'mds.status.complianceError': '初检错误',
'mds.status.relationPass': '联检通过',
'mds.status.relationError': '联检错误',
'mds.status.syncError': '推送失败',
'mds.status.synced': '已推送',
'mds.status.all': '全部',
// ============ 操作按钮 (P1级) ============
'mds.action.import': '导入',
'mds.action.validate': '校验',
'mds.action.sync': '推送',
'mds.action.query': '查询',
'mds.action.reset': '重置',
'mds.action.save': '保存',
'mds.action.delete': '删除',
'mds.action.cancel': '取消',
'mds.action.confirm': '确定',
'mds.action.close': '关闭',
'mds.action.clear': '清空',
'mds.action.export': '导出',
'mds.action.exportTemplate': '导出模板',
'mds.action.refresh': '刷新',
'mds.action.filter': '筛选',
'mds.action.edit': '编辑',
'mds.action.view': '查看',
'mds.action.selectAll': '全选',
'mds.action.deselectAll': '取消全选',
'mds.action.batchDelete': '批量删除',
'mds.action.batchSync': '批量推送',
'mds.action.showRules': '查看规则',
'mds.action.preciseFilter': '精准筛选',
'mds.action.clearFilter': '清空筛选',
// ============ 校验相关 ============
'mds.validation.title': '校验',
'mds.validation.complete': '校验完成: 通过{pass}条,失败{fail}条',
'mds.validation.noPending': '没有待处理的记录',
'mds.validation.confirmStart': '缺失的字段值将自动填充为默认值,确定开始校验吗?',
'mds.validation.processing': '校验中',
'mds.validation.failed': '校验失败',
'mds.validation.progress': '已处理 {current}/{total}',
'mds.validation.rulesTitle': '校验规则',
'mds.validation.noRules': '暂无校验规则',
// ============ 推送相关 ============
'mds.sync.title': '推送',
'mds.sync.complete': '推送完成: {accounts}个账套, 成功{synced}条, 去重失败{dedup}条, 其他失败{failed}条',
'mds.sync.noData': '没有【联合校验通过】或【同步失败】的记录可推送',
'mds.sync.selectTarget': '请至少选择一个目标账套',
'mds.sync.selectMode': '选择推送模式',
'mds.sync.incremental': '增量推送',
'mds.sync.refresh': '刷新推送',
'mds.sync.confirmRefresh': '刷新推送将删除正式表所有数据,请谨慎操作!',
'mds.sync.processing': '推送中',
'mds.sync.progress': '推送进度',
// ============ 上传相关 ============
'mds.upload.title': '导入Excel数据',
'mds.upload.success': '导入完成: 成功{inserted}条, 跳过{skipped}条',
'mds.upload.invalidType': '请上传Excel或CSV文件',
'mds.upload.noFile': '请先选择文件',
'mds.upload.dragDrop': '点击或拖拽文件上传(支持 .xlsx, .xls, .csv',
'mds.upload.processing': '导入中',
'mds.upload.dedupStrategy': '去重策略',
'mds.upload.overwrite': '覆盖',
'mds.upload.skip': '跳过',
// ============ 表格相关 ============
'mds.table.noData': '暂无数据',
'mds.table.loading': '加载中...',
'mds.table.selectAll': '全选',
'mds.table.perPage': '条/页',
'mds.table.total': '共 {count} 条',
'mds.table.edit': '编辑',
'mds.table.delete': '删除',
'mds.table.export': '导出模板',
'mds.table.selected': '已选择 {count} 条',
'mds.table.actions': '操作',
'mds.table.status': '状态',
'mds.table.columns': '列',
// ============ 弹窗相关 ============
'mds.modal.confirm': '确认',
'mds.modal.cancel': '取消',
'mds.modal.close': '关闭',
'mds.modal.importTitle': '导入Excel数据',
'mds.modal.filterTitle': '精准筛选',
'mds.modal.editTitle': '编辑记录',
'mds.modal.validationRules': '校验规则',
'mds.modal.syncTitle': '推送数据',
'mds.modal.deleteConfirm': '确定要删除这条记录吗?',
'mds.modal.batchDeleteConfirm': '确定要删除选中的 {count} 条记录吗?',
// ============ 错误消息 ============
'mds.error.queryFailed': '查询失败',
'mds.error.uploadFailed': '上传失败',
'mds.error.timeout': '请求超时',
'mds.error.loadFailed': '加载失败,请重试',
'mds.error.validateFailed': '校验失败',
'mds.error.syncFailed': '推送失败',
'mds.error.noPermission': '没有权限',
'mds.error.invalidData': '数据格式错误',
'mds.error.duplicateKey': '数据重复: {field}={value} 已存在',
'mds.error.foreignKeyViolation': '外键约束违反: {field} 引用不存在',
'mds.error.validationFailed': '校验失败: {reason}',
// ============ 成功消息 ============
'mds.success.queryComplete': '查询完成',
'mds.success.uploadComplete': '导入完成',
'mds.success.validateComplete': '校验完成',
'mds.success.syncComplete': '推送完成',
'mds.success.deleteComplete': '删除完成',
'mds.success.saveComplete': '保存完成',
'mds.success.exportComplete': '导出完成',
// ============ 统计信息 ============
'mds.stats.total': '总计',
'mds.stats.pending': '待处理',
'mds.stats.passed': '已通过',
'mds.stats.failed': '失败',
'mds.stats.synced': '已推送',
'mds.stats.today': '今日',
'mds.stats.week': '本周',
'mds.stats.month': '本月',
// ============ 表单标签 ============
'mds.form.required': '必填',
'mds.form.optional': '可选',
'mds.form.defaultValue': '默认值',
'mds.form.placeholder': '请输入',
'mds.form.selectPlaceholder': '请选择',
// ============ 其他提示 ============
'mds.other.loading': '加载中...',
'mds.other.processing': '处理中...',
'mds.other.noData': '暂无数据',
'mds.other.confirm': '确定',
'mds.other.cancel': '取消',
'mds.other.tip': '提示',
'mds.other.warning': '警告',
'mds.other.error': '错误',
'mds.other.success': '成功'
};
+60 -21
View File
@@ -115,24 +115,29 @@
<body>
<div class="container py-5">
<div class="text-center text-white mb-5">
<h1 class="mb-2">数据清洗管理系统</h1>
<h1 class="mb-2" data-i18n="mds.app.title">数据清洗管理系统</h1>
<!-- <p class="mb-2 opacity-75">主数据管理与数据质量控制平台</p> -->
<div class="mt-2">
<div class="mt-2 d-flex justify-content-center align-items-center">
<a href="/mds/user-guide" class="header-link me-3" target="_blank">
<i class="bi bi-book"></i> 操作指引
<i class="bi bi-book"></i> <span data-i18n="mds.app.guide">操作指引</span>
</a>
<a href="/docs" class="header-link">
<i class="bi bi-code-slash"></i> API文档
<a href="/docs" class="header-link me-3">
<i class="bi bi-code-slash"></i> <span data-i18n="mds.app.apiDoc">API文档</span>
</a>
<select id="lang-selector" class="form-select form-select-sm" style="width: auto; background-color: rgba(255,255,255,0.9); border: none;">
<option value="zh-CN">🇨🇳 中文</option>
<option value="en-US">🇺🇸 English</option>
<option value="de-DE">🇩🇪 Deutsch</option>
</select>
</div>
</div>
<div class="text-center mb-4">
<button class="btn btn-light btn-lg shadow-sm me-3" id="globalValidateBtn">
<i class="bi bi-shield-check"></i> 校验全部
<i class="bi bi-shield-check"></i> <span data-i18n="mds.action.validate">校验</span>
</button>
<button class="btn btn-light btn-lg shadow-sm" id="globalSyncBtn">
<i class="bi bi-cloud-upload"></i> 推送全部
<i class="bi bi-cloud-upload"></i> <span data-i18n="mds.action.sync">推送</span>
</button>
</div>
@@ -234,7 +239,7 @@
<div class="modal-dialog modal-lg modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="globalOpsDialogTitle">操作确认</h5>
<h5 class="modal-title" id="globalOpsDialogTitle" data-i18n="mds.modal.confirm">操作确认</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
@@ -246,19 +251,19 @@
<thead>
<tr>
<th style="width:40px">#</th>
<th>模块</th>
<th style="width:100px">待处理</th>
<th data-i18n="mds.nav.material">模块</th>
<th style="width:100px" data-i18n="mds.stats.pending">待处理</th>
</tr>
</thead>
<tbody id="moduleListBody"></tbody>
</table>
<div class="alert alert-warning mb-0">
<i class="bi bi-exclamation-triangle"></i> 预估处理: <strong id="totalPending">0</strong> 条数据
<i class="bi bi-exclamation-triangle"></i> <span data-i18n="mds.stats.total">预估处理</span>: <strong id="totalPending">0</strong> <span data-i18n="mds.table.total">条数据</span>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" id="confirmGlobalOps">确认执行</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" data-i18n="mds.modal.cancel">取消</button>
<button type="button" class="btn btn-primary" id="confirmGlobalOps" data-i18n="mds.modal.confirm">确认执行</button>
</div>
</div>
</div>
@@ -270,7 +275,7 @@
<div class="modal-header">
<h5 class="modal-title">
<i class="bi bi-hourglass-split" id="progressIcon"></i>
<span id="progressTitle">执行中...</span>
<span id="progressTitle" data-i18n="mds.other.processing">执行中...</span>
</h5>
</div>
<div class="modal-body">
@@ -283,16 +288,16 @@
</div>
<div id="summaryReport" style="display: none;">
<div class="alert alert-success mb-3">
<i class="bi bi-check-circle"></i> 执行完成
<i class="bi bi-check-circle"></i> <span data-i18n="mds.success.syncComplete">执行完成</span>
</div>
<div class="row text-center mb-3">
<div class="col">
<h4 class="text-success" id="totalSuccess">0</h4>
<small class="text-muted">成功</small>
<small class="text-muted" data-i18n="mds.stats.passed">成功</small>
</div>
<div class="col">
<h4 class="text-danger" id="totalFailed">0</h4>
<small class="text-muted">失败</small>
<small class="text-muted" data-i18n="mds.stats.failed">失败</small>
</div>
<div class="col">
<h4 class="text-primary" id="totalTime">0s</h4>
@@ -300,20 +305,54 @@
</div>
</div>
<table class="table table-sm">
<thead><tr><th>模块</th><th>成功</th><th>失败</th><th>状态</th></tr></thead>
<thead><tr><th data-i18n="mds.nav.material">模块</th><th data-i18n="mds.stats.passed">成功</th><th data-i18n="mds.stats.failed">失败</th><th data-i18n="mds.table.status">状态</th></tr></thead>
<tbody id="detailResultBody"></tbody>
</table>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" id="cancelExecutionBtn">取消执行</button>
<button type="button" class="btn btn-primary d-none" id="viewDetailBtn">查看详情</button>
<button type="button" class="btn btn-secondary d-none" id="closeProgressBtn" data-bs-dismiss="modal">关闭</button>
<button type="button" class="btn btn-danger" id="cancelExecutionBtn" data-i18n="mds.action.cancel">取消执行</button>
<button type="button" class="btn btn-primary d-none" id="viewDetailBtn" data-i18n="mds.action.view">查看详情</button>
<button type="button" class="btn btn-secondary d-none" id="closeProgressBtn" data-bs-dismiss="modal" data-i18n="mds.modal.close">关闭</button>
</div>
</div>
</div>
</div>
<!-- i18n国际化支持 -->
<script src="/static/lib/i18n/i18n.js"></script>
<script src="/static/lib/i18n/zh-CN.js"></script>
<script src="/static/lib/i18n/en-US.js"></script>
<script src="/static/lib/i18n/de-DE.js"></script>
<script src="/static/mds/js/global-ops-controller.js"></script>
<script>
// 初始化i18n并设置语言
document.addEventListener('DOMContentLoaded', function() {
// 初始化i18n实例
if (typeof i18n !== 'undefined') {
const savedLang = localStorage.getItem('i18n_lang') || 'zh-CN';
i18n.init(savedLang);
}
// 语言切换处理
const langSelector = document.getElementById('lang-selector');
if (langSelector) {
const savedLang = localStorage.getItem('i18n_lang') || 'zh-CN';
langSelector.value = savedLang;
langSelector.addEventListener('change', function() {
const lang = this.value;
if (typeof switchLanguage === 'function') {
switchLanguage(lang);
} else if (typeof i18n !== 'undefined') {
// 降级处理:如果switchLanguage未定义,直接调用i18n
i18n.switchLanguage(lang);
localStorage.setItem('i18n_lang', lang);
location.reload();
}
});
}
});
</script>
</body>
</html>
+108 -19
View File
@@ -11,63 +11,93 @@ const API_BASE = '/api/mds';
/**
* 缓冲表状态枚举定义
* @enum {Object}
*
* 支持国际化label属性改为getter动态翻译
*/
const STAGING_STATUS = {
PENDING: {
value: 'pending',
label: '待处理',
labelKey: 'mds.status.pending',
colorClass: 'text-warning',
bgClass: 'bg-warning',
badgeClass: 'status-badge status-badge-pending',
icon: 'clock'
icon: 'clock',
get label() {
return typeof i18n !== 'undefined' ?
i18n.t(this.labelKey) : '待处理';
}
},
COMPLIANCE_PASS: {
value: 'compliance_pass',
label: '初检通过',
labelKey: 'mds.status.compliancePass',
colorClass: 'text-info',
bgClass: 'bg-info',
badgeClass: 'status-badge status-badge-compliance_pass',
icon: 'check-circle'
icon: 'check-circle',
get label() {
return typeof i18n !== 'undefined' ?
i18n.t(this.labelKey) : '初检通过';
}
},
COMPLIANCE_ERROR: {
value: 'compliance_error',
label: '初检错误',
labelKey: 'mds.status.complianceError',
colorClass: 'text-danger',
bgClass: 'bg-danger',
badgeClass: 'status-badge status-badge-compliance_error',
icon: 'x-circle'
icon: 'x-circle',
get label() {
return typeof i18n !== 'undefined' ?
i18n.t(this.labelKey) : '初检错误';
}
},
RELATION_PASS: {
value: 'relation_pass',
label: '联检通过',
labelKey: 'mds.status.relationPass',
colorClass: 'text-success',
bgClass: 'bg-success',
badgeClass: 'status-badge status-badge-relation_pass',
icon: 'link'
icon: 'link',
get label() {
return typeof i18n !== 'undefined' ?
i18n.t(this.labelKey) : '联检通过';
}
},
RELATION_ERROR: {
value: 'relation_error',
label: '联检错误',
labelKey: 'mds.status.relationError',
colorClass: 'text-warning',
bgClass: 'bg-warning',
badgeClass: 'status-badge status-badge-relation_error',
icon: 'alert-circle'
icon: 'alert-circle',
get label() {
return typeof i18n !== 'undefined' ?
i18n.t(this.labelKey) : '联检错误';
}
},
SYNC_ERROR: {
value: 'sync_error',
label: '推送失败',
labelKey: 'mds.status.syncError',
colorClass: 'text-warning',
bgClass: 'bg-warning',
badgeClass: 'status-badge status-badge-sync_error',
icon: 'exclamation-triangle'
icon: 'exclamation-triangle',
get label() {
return typeof i18n !== 'undefined' ?
i18n.t(this.labelKey) : '推送失败';
}
},
SYNCED: {
value: 'synced',
label: '已推送',
labelKey: 'mds.status.synced',
colorClass: 'text-info',
bgClass: 'bg-info',
badgeClass: 'status-badge status-badge-synced',
icon: 'send'
icon: 'send',
get label() {
return typeof i18n !== 'undefined' ?
i18n.t(this.labelKey) : '已推送';
}
}
};
@@ -501,23 +531,28 @@ async function showValidationRulesModal(tableKey, tableName) {
try {
const rules = await getValidationRules(tableKey);
const modalTitle = typeof i18n !== 'undefined' ?
`${tableName || tableKey} ${i18n.t('mds.validation.rulesTitle')}` :
`${tableName || tableKey} 校验规则`;
const loadingText = typeof i18n !== 'undefined' ? i18n.t('mds.other.loading') : '加载中...';
const closeText = typeof i18n !== 'undefined' ? i18n.t('mds.modal.close') : '关闭';
modalManager.register('validationRules', {
id: 'validationRulesModal',
title: `${tableName || tableKey} 校验规则`,
title: modalTitle,
size: 'xl',
body: (data) => {
if (!data.rules) {
return '<div class="text-center py-4"><div class="spinner-border text-primary"></div><p class="mt-2 text-muted">加载中...</p></div>';
return `<div class="text-center py-4"><div class="spinner-border text-primary"></div><p class="mt-2 text-muted">${loadingText}</p></div>`;
}
return renderValidationRulesHtml(data.rules);
},
footer: '<button type="button" class="btn btn-primary" data-bs-dismiss="modal">关闭</button>',
footer: `<button type="button" class="btn btn-primary" data-bs-dismiss="modal">${closeText}</button>`,
onOpen: (data, modalElement) => {
if (data.rules) {
const bodyEl = modalElement.querySelector('#validationRulesModal_body');
bodyEl.innerHTML = renderValidationRulesHtml(data.rules);
// 调整弹窗尺寸,充分利用屏幕空间
const dialog = modalElement.querySelector('.modal-dialog');
if (dialog) {
dialog.style.maxWidth = '95%';
@@ -535,6 +570,60 @@ async function showValidationRulesModal(tableKey, tableName) {
modalManager.open('validationRules', { rules });
} catch (error) {
console.error('显示校验规则失败:', error);
showMessage('加载校验规则失败', 'danger');
const errorMsg = typeof i18n !== 'undefined' ? i18n.t('mds.error.loadFailed') : '加载校验规则失败';
showMessage(errorMsg, 'danger');
}
}
/**
* 错误码常量定义
*/
const ERROR_CODES = {
DUPLICATE_KEY: 'DUPLICATE_KEY',
INVALID_FORMAT: 'INVALID_FORMAT',
FOREIGN_KEY_VIOLATION: 'FOREIGN_KEY_VIOLATION',
VALIDATION_FAILED: 'VALIDATION_FAILED',
QUERY_FAILED: 'QUERY_FAILED',
UPLOAD_FAILED: 'UPLOAD_FAILED',
TIMEOUT: 'TIMEOUT',
NO_PERMISSION: 'NO_PERMISSION',
INVALID_DATA: 'INVALID_DATA'
};
/**
* 错误码到翻译键的映射表
*/
const ERROR_CODE_MAP = {
DUPLICATE_KEY: 'mds.error.duplicateKey',
INVALID_FORMAT: 'mds.error.invalidData',
FOREIGN_KEY_VIOLATION: 'mds.error.foreignKeyViolation',
VALIDATION_FAILED: 'mds.error.validationFailed',
QUERY_FAILED: 'mds.error.queryFailed',
UPLOAD_FAILED: 'mds.error.uploadFailed',
TIMEOUT: 'mds.error.timeout',
NO_PERMISSION: 'mds.error.noPermission',
INVALID_DATA: 'mds.error.invalidData'
};
/**
* 翻译后端错误消息
* @param {Object} response - API响应对象
* @returns {string} 翻译后的错误消息
*/
function translateError(response) {
if (!response || !response.error) {
return response?.message || 'Unknown error';
}
const { code, params = {} } = response.error;
const errorKey = ERROR_CODE_MAP[code] || `mds.error.${code}`;
if (typeof i18n !== 'undefined') {
const translated = i18n.t(errorKey, params);
if (translated !== errorKey) {
return translated;
}
}
return `Error: ${code}`;
}
+30 -13
View File
@@ -69,6 +69,17 @@ class DataTable {
init() {
this.render();
this.bindEvents();
this.bindLanguageChangeListener();
}
/**
* 监听语言切换事件
*/
bindLanguageChangeListener() {
window.addEventListener('languageChanged', () => {
this.render();
this.loadData();
});
}
/**
@@ -91,7 +102,7 @@ class DataTable {
<tr>
<td colspan="${this.columns.length + 1}" class="text-center text-muted py-4">
<div class="spinner-border text-primary" role="status" style="width: 2rem; height: 2rem;">
<span class="visually-hidden">加载中...</span>
<span class="visually-hidden">${typeof i18n !== 'undefined' ? i18n.t('mds.table.loading') : '加载中...'}</span>
</div>
</td>
</tr>
@@ -102,16 +113,16 @@ class DataTable {
<div class="table-footer-fixed d-flex justify-content-between align-items-center px-2 py-2 bg-white border-top" style="font-size:0.75rem">
<div class="d-flex align-items-center gap-2">
<button class="btn btn-sm btn-outline-success font-monospace" id="selectAllPagesBtn" style="width: 150px;">
<i class="bi bi-check-all"></i> (<span id="totalCount">0</span>)
<i class="bi bi-check-all"></i> <span id="selectAllBtnText">${typeof i18n !== 'undefined' ? i18n.t('mds.action.selectAll') : ''}(<span id="totalCount">0</span>)</span>
</button>
<button class="btn btn-sm btn-outline-primary font-monospace" id="batchEditBtn" disabled style="width: 150px;">
<i class="bi bi-pencil"></i> (<span id="selectedCount">0</span>)
<i class="bi bi-pencil"></i> <span id="editBtnText">${typeof i18n !== 'undefined' ? i18n.t('mds.action.edit') : ''}(<span id="selectedCount">0</span>)</span>
</button>
<button class="btn btn-sm btn-outline-danger font-monospace" id="batchDeleteBtn" disabled style="width: 150px;">
<i class="bi bi-trash"></i> (<span id="selectedCountDup">0</span>)
<i class="bi bi-trash"></i> <span id="deleteBtnText">${typeof i18n !== 'undefined' ? i18n.t('mds.action.delete') : ''}(<span id="selectedCountDup">0</span>)</span>
</button>
<button class="btn btn-sm btn-outline-info font-monospace" id="templateBtn" style="width: 150px;">
<i class="bi bi-download"></i> <span id="exportBtnText"></span>
<i class="bi bi-download"></i> <span id="exportBtnText">${typeof i18n !== 'undefined' ? i18n.t('mds.action.exportTemplate') : ''}</span>
</button>
</div>
<div class="d-flex align-items-center gap-2">
@@ -122,8 +133,8 @@ class DataTable {
<option value="500" ${this.pageSize === 500 ? 'selected' : ''}>500</option>
<option value="1000" ${this.pageSize === 1000 ? 'selected' : ''}>1000</option>
</select>
<span class="font-monospace">/</span>
<span id="totalInfo" class="font-monospace"> 0 </span>
<span class="font-monospace">${typeof i18n !== 'undefined' ? i18n.t('mds.table.perPage') : '条/页'}</span>
<span id="totalInfo" class="font-monospace">${typeof i18n !== 'undefined' ? i18n.t('mds.table.total', {count: 0}) : '共 0 条'}</span>
<nav>
<ul class="pagination pagination-sm mb-0" id="pagination"></ul>
</nav>
@@ -358,7 +369,9 @@ class DataTable {
const totalInfo = document.getElementById('totalInfo');
if (totalInfo) {
totalInfo.textContent = `${this.total}`;
totalInfo.textContent = typeof i18n !== 'undefined' ?
i18n.t('mds.table.total', {count: this.total}) :
`${this.total}`;
}
this.updateTotalCount();
@@ -367,7 +380,7 @@ class DataTable {
tbody.innerHTML = `
<tr>
<td colspan="${this.columns.length + 1}" class="text-center text-muted py-4">
暂无数据
${typeof i18n !== 'undefined' ? i18n.t('mds.table.noData') : '暂无数据'}
</td>
</tr>
`;
@@ -1084,13 +1097,15 @@ class DataTable {
if (!btn) return;
const isAllSelected = this.selectedAllPages || this.selectedIds.size === this.total;
const selectAllText = typeof i18n !== 'undefined' ? i18n.t('mds.action.selectAll') : '全选';
const selectedText = typeof i18n !== 'undefined' ? i18n.t('mds.table.selected', {count: this.selectedIds.size}) : `已全选${this.selectedIds.size}`;
if (isAllSelected && this.selectedIds.size > 0) {
btn.className = 'btn btn-sm btn-success font-monospace';
btn.innerHTML = `<i class="bi bi-check-all"></i> 已全选${this.selectedIds.size}`;
btn.innerHTML = `<i class="bi bi-check-all"></i> ${selectedText}`;
} else {
btn.className = 'btn btn-sm btn-outline-success font-monospace';
btn.innerHTML = `<i class="bi bi-check-all"></i> 全选`;
btn.innerHTML = `<i class="bi bi-check-all"></i> ${selectAllText}`;
}
}
@@ -1113,7 +1128,8 @@ class DataTable {
// 如果已全选,则取消全选
if (this.selectedAllPages || this.selectedIds.size === this.total) {
this.clearSelection();
showMessage('已取消全选', 'info');
const cancelMsg = typeof i18n !== 'undefined' ? i18n.t('mds.other.confirm') : '已取消全选';
showMessage(cancelMsg, 'info');
return;
}
@@ -1182,7 +1198,8 @@ class DataTable {
});
} catch (error) {
console.error('全选失败:', error);
showMessage('全选失败', 'danger');
const errorMsg = typeof i18n !== 'undefined' ? i18n.t('mds.error.queryFailed') : '全选失败';
showMessage(errorMsg, 'danger');
} finally {
hideLoading();
}
+104 -18
View File
@@ -472,7 +472,10 @@ class MDSPageController {
handleResponse(response, (data) => {
const result = data.data;
showMessage(`导入完成: 成功${result.inserted}条, 跳过${result.skipped}`, 'success');
const msg = typeof i18n !== 'undefined' ?
i18n.t('mds.upload.success', {inserted: result.inserted, skipped: result.skipped}) :
`导入完成: 成功${result.inserted}条, 跳过${result.skipped}`;
showMessage(msg, 'success');
this.resetUploadArea();
this.pendingFile = null;
@@ -485,7 +488,8 @@ class MDSPageController {
});
} catch (error) {
hideLoading();
showMessage('上传失败', 'danger');
const errorMsg = typeof i18n !== 'undefined' ? i18n.t('mds.error.uploadFailed') : '上传失败';
showMessage(errorMsg, 'danger');
} finally {
// 恢复上传按钮
if (uploadBtn) {
@@ -525,11 +529,16 @@ class MDSPageController {
return;
}
if (!confirm('缺失的字段值将自动填充为默认值,确定开始校验吗?')) {
const confirmMsg = typeof i18n !== 'undefined' ?
i18n.t('mds.validation.confirmStart') :
'缺失的字段值将自动填充为默认值,确定开始校验吗?';
if (!confirm(confirmMsg)) {
return;
}
showProgress('校验中', pendingCount);
const progressTitle = typeof i18n !== 'undefined' ? i18n.t('mds.validation.processing') : '校验中';
showProgress(progressTitle, pendingCount);
let totalValidated = 0;
let totalRejected = 0;
@@ -574,7 +583,10 @@ class MDSPageController {
hideProgress();
const filledMsg = totalFilled ? `,填充默认值${totalFilled}` : '';
showMessage(`校验完成: 通过${totalValidated}条,失败${totalRejected}${filledMsg}`, 'success');
const msg = typeof i18n !== 'undefined' ?
i18n.t('mds.validation.complete', {pass: totalValidated, fail: totalRejected}) + filledMsg :
`校验完成: 通过${totalValidated}条,失败${totalRejected}${filledMsg}`;
showMessage(msg, 'success');
this.dataTable.refresh();
this.statusCard.refresh();
}
@@ -603,16 +615,21 @@ class MDSPageController {
}
async validateAllData() {
if (!confirm('缺失的字段值将自动填充为默认值,确定校验所有待处理数据吗?')) return;
const confirmMsg = typeof i18n !== 'undefined' ?
i18n.t('mds.validation.confirmStart') :
'缺失的字段值将自动填充为默认值,确定校验所有待处理数据吗?';
if (!confirm(confirmMsg)) return;
showProgress('校验所有表', 100);
const progressTitle = typeof i18n !== 'undefined' ? i18n.t('mds.validation.processing') : '校验所有表';
showProgress(progressTitle, 100);
const response = await callApi('/validate_all', 'POST');
hideProgress();
handleResponse(response, (data) => {
showMessage('所有表校验完成', 'success');
const msg = typeof i18n !== 'undefined' ? i18n.t('mds.success.validateComplete') : '所有表校验完成';
showMessage(msg, 'success');
this.dataTable.refresh();
this.statusCard.refresh();
});
@@ -621,7 +638,8 @@ class MDSPageController {
async syncData() {
const response = await callApi(`/status/${this.tableKey}`);
if (response.success !== 1) {
showMessage('获取状态失败', 'danger');
const errorMsg = typeof i18n !== 'undefined' ? i18n.t('mds.error.queryFailed') : '获取状态失败';
showMessage(errorMsg, 'danger');
return;
}
@@ -631,7 +649,8 @@ class MDSPageController {
const retryExceeded = stats.retry_exceeded || 0;
if (relationPassCount === 0 && syncErrorCount === 0) {
showMessage('没有【联合校验通过】或【同步失败】的记录可推送', 'warning');
const msg = typeof i18n !== 'undefined' ? i18n.t('mds.sync.noData') : '没有【联合校验通过】或【同步失败】的记录可推送';
showMessage(msg, 'warning');
return;
}
@@ -646,7 +665,10 @@ class MDSPageController {
const targetDbParam = targetDbs.join(',');
const totalCount = (relationPassCount + syncErrorCount) * targetDbs.length;
showProgress(mode === 'incremental' ? '增量推送中' : '刷新推送中', totalCount);
const syncModeText = mode === 'incremental' ?
(typeof i18n !== 'undefined' ? i18n.t('mds.sync.incremental') : '增量推送中') :
(typeof i18n !== 'undefined' ? i18n.t('mds.sync.refresh') : '刷新推送中');
showProgress(syncModeText, totalCount);
let totalSynced = 0;
let totalFailed = 0;
@@ -686,7 +708,10 @@ class MDSPageController {
processed += batchSynced + batchFailed + batchDedup;
if (processed > 0) {
updateProgress(processed, totalCount, `已处理 ${processed}/${totalCount}`);
const progressText = typeof i18n !== 'undefined' ?
i18n.t('mds.validation.progress', {current: processed, total: totalCount}) :
`已处理 ${processed}/${totalCount}`;
updateProgress(processed, totalCount, progressText);
}
// 如果本批次没有处理任何记录,说明已完成
@@ -700,9 +725,15 @@ class MDSPageController {
hideProgress();
if (totalFailed > 0 || totalDedup > 0) {
showMessage(`推送完成: ${targetDbs.length}个账套, 成功${totalSynced}条, 去重失败${totalDedup}条, 其他失败${totalFailed}`, 'warning');
const msg = typeof i18n !== 'undefined' ?
i18n.t('mds.sync.complete', {accounts: targetDbs.length, synced: totalSynced, dedup: totalDedup, failed: totalFailed}) :
`推送完成: ${targetDbs.length}个账套, 成功${totalSynced}条, 去重失败${totalDedup}条, 其他失败${totalFailed}`;
showMessage(msg, 'warning');
} else {
showMessage(`推送完成: ${targetDbs.length}个账套, 成功${totalSynced}`, 'success');
const msg = typeof i18n !== 'undefined' ?
i18n.t('mds.sync.complete', {accounts: targetDbs.length, synced: totalSynced, dedup: 0, failed: 0}) :
`推送完成: ${targetDbs.length}个账套, 成功${totalSynced}`;
showMessage(msg, 'success');
}
this.dataTable.refresh();
@@ -813,7 +844,8 @@ class MDSPageController {
hideLoading();
handleResponse(response, (data) => {
showMessage('所有表推送完成', 'success');
const msg = typeof i18n !== 'undefined' ? i18n.t('mds.success.syncComplete') : '所有表推送完成';
showMessage(msg, 'success');
this.dataTable.refresh();
this.statusCard.refresh();
});
@@ -1403,14 +1435,16 @@ class MDSPageController {
hideLoading();
handleResponse(response, () => {
showMessage('保存成功', 'success');
const msg = typeof i18n !== 'undefined' ? i18n.t('mds.success.saveComplete') : '保存成功';
showMessage(msg, 'success');
bootstrap.Modal.getInstance(document.getElementById('editModal')).hide();
this.dataTable.refresh();
});
}
async deleteRecord(stagingId) {
if (!confirm('确定删除此记录吗?')) return;
const confirmMsg = typeof i18n !== 'undefined' ? i18n.t('mds.modal.deleteConfirm') : '确定删除此记录吗?';
if (!confirm(confirmMsg)) return;
showLoading();
@@ -1419,7 +1453,8 @@ class MDSPageController {
hideLoading();
handleResponse(response, () => {
showMessage('删除成功', 'success');
const msg = typeof i18n !== 'undefined' ? i18n.t('mds.success.deleteComplete') : '删除成功';
showMessage(msg, 'success');
const detailModal = document.getElementById('detailModal');
if (detailModal && bootstrap.Modal.getInstance(detailModal)) {
@@ -1890,3 +1925,54 @@ class MDSPageController {
}
}
}
/**
* 无刷新语言切换功能
*/
async function switchLanguage(lang) {
if (typeof i18n === 'undefined') {
console.warn('i18n not initialized');
return;
}
try {
await i18n.switchLanguage(lang);
localStorage.setItem('i18n_lang', lang);
updateDynamicElements();
window.dispatchEvent(new CustomEvent('languageChanged', {
detail: { lang: lang }
}));
} catch (error) {
console.error('Language switch failed:', error);
}
}
/**
* 更新动态元素翻译
*/
function updateDynamicElements() {
if (typeof i18n === 'undefined') return;
document.querySelectorAll('[data-i18n]').forEach(el => {
const key = el.getAttribute('data-i18n');
el.textContent = i18n.t(key);
});
if (window.statusCardInstance) {
window.statusCardInstance.refresh();
}
if (window.dataTableInstance) {
window.dataTableInstance.refresh();
}
const modals = document.querySelectorAll('.modal.show');
modals.forEach(modal => {
modal.querySelectorAll('[data-i18n]').forEach(el => {
const key = el.getAttribute('data-i18n');
el.textContent = i18n.t(key);
});
});
}
+31 -9
View File
@@ -23,19 +23,40 @@ class StatusCard {
this.render();
this.loadStats();
this.startAutoRefresh();
this.bindLanguageChangeListener();
}
/**
* 监听语言切换事件
*/
bindLanguageChangeListener() {
window.addEventListener('languageChanged', () => {
this.render();
});
}
/**
* 渲染状态卡片
*/
render() {
const labels = {
all: typeof i18n !== 'undefined' ? i18n.t('mds.status.all') : '全部',
pending: STAGING_STATUS.PENDING.label,
compliancePass: STAGING_STATUS.COMPLIANCE_PASS.label,
complianceError: STAGING_STATUS.COMPLIANCE_ERROR.label,
relationPass: STAGING_STATUS.RELATION_PASS.label,
relationError: STAGING_STATUS.RELATION_ERROR.label,
syncError: STAGING_STATUS.SYNC_ERROR.label,
synced: STAGING_STATUS.SYNCED.label
};
this.container.innerHTML = `
<div class="row g-2">
<div class="col">
<div class="card status-card active" data-status="">
<div class="card-body text-center">
<div class="status-number" style="color: #191919;" id="statusTotalCount">-</div>
<div class="status-label">全部</div>
<div class="status-label">${labels.all}</div>
</div>
</div>
</div>
@@ -43,7 +64,7 @@ class StatusCard {
<div class="card status-card" data-status="pending">
<div class="card-body text-center">
<div class="status-number" style="color: #9e9e9e;" id="pendingCount">-</div>
<div class="status-label">待处理</div>
<div class="status-label">${labels.pending}</div>
</div>
</div>
</div>
@@ -51,7 +72,7 @@ class StatusCard {
<div class="card status-card" data-status="compliance_pass">
<div class="card-body text-center">
<div class="status-number" style="color: #00c345;" id="compliancePassCount">-</div>
<div class="status-label">初检通过</div>
<div class="status-label">${labels.compliancePass}</div>
</div>
</div>
</div>
@@ -59,7 +80,7 @@ class StatusCard {
<div class="card status-card" data-status="compliance_error">
<div class="card-body text-center">
<div class="status-number" style="color: #ff9300;" id="complianceErrorCount">-</div>
<div class="status-label">初检错误</div>
<div class="status-label">${labels.complianceError}</div>
</div>
</div>
</div>
@@ -67,7 +88,7 @@ class StatusCard {
<div class="card status-card" data-status="relation_pass">
<div class="card-body text-center">
<div class="status-number" style="color: #1677ff;" id="relationPassCount">-</div>
<div class="status-label">联检通过</div>
<div class="status-label">${labels.relationPass}</div>
</div>
</div>
</div>
@@ -75,7 +96,7 @@ class StatusCard {
<div class="card status-card" data-status="relation_error">
<div class="card-body text-center">
<div class="status-number" style="color: #f52222;" id="relationErrorCount">-</div>
<div class="status-label">联检错误</div>
<div class="status-label">${labels.relationError}</div>
</div>
</div>
</div>
@@ -83,7 +104,7 @@ class StatusCard {
<div class="card status-card" data-status="sync_error">
<div class="card-body text-center">
<div class="status-number" style="color: #eb2f96;" id="syncErrorCount">-</div>
<div class="status-label">推送失败</div>
<div class="status-label">${labels.syncError}</div>
</div>
</div>
</div>
@@ -91,7 +112,7 @@ class StatusCard {
<div class="card status-card" data-status="synced">
<div class="card-body text-center">
<div class="status-number" style="color: #7500ea;" id="syncedCount">-</div>
<div class="status-label">已推送</div>
<div class="status-label">${labels.synced}</div>
</div>
</div>
</div>
@@ -245,9 +266,10 @@ class StatusCard {
}
/**
* 手动刷新
* 手动刷新重新渲染和加载数据
*/
refresh() {
this.render();
this.loadStats();
}
+74 -39
View File
@@ -13,32 +13,41 @@
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary mb-2">
<div class="container-fluid">
<a class="navbar-brand" href="/mds"><i class="bi bi-clipboard-data"></i> 数据清洗管理系统</a>
<a class="navbar-brand" href="/mds"><i class="bi bi-clipboard-data"></i> <span data-i18n="mds.app.title">数据清洗管理系统</span></a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link {material_active}" href="/mds/material"><i class="bi {material_icon}"></i> 物料</a>
<a class="nav-link {material_active}" href="/mds/material"><i class="bi {material_icon}"></i> <span data-i18n="mds.nav.material">物料</span></a>
</li>
<li class="nav-item">
<a class="nav-link {mat_ver_active}" href="/mds/mat-ver"><i class="bi {mat_ver_icon}"></i> 产线版本</a>
<a class="nav-link {mat_ver_active}" href="/mds/mat-ver"><i class="bi {mat_ver_icon}"></i> <span data-i18n="mds.nav.matVer">产线版本</span></a>
</li>
<li class="nav-item">
<a class="nav-link {workcenter_active}" href="/mds/workcenter"><i class="bi {workcenter_icon}"></i> 工作中心</a>
<a class="nav-link {workcenter_active}" href="/mds/workcenter"><i class="bi {workcenter_icon}"></i> <span data-i18n="mds.nav.workcenter">工作中心</span></a>
</li>
<li class="nav-item">
<a class="nav-link {mat_wc_active}" href="/mds/mat-wc"><i class="bi {mat_wc_icon}"></i> 工艺路线</a>
<a class="nav-link {mat_wc_active}" href="/mds/mat-wc"><i class="bi {mat_wc_icon}"></i> <span data-i18n="mds.nav.matWc">工艺路线</span></a>
</li>
<li class="nav-item">
<a class="nav-link {mat_wc_bom_active}" href="/mds/mat-wc-bom"><i class="bi {mat_wc_bom_icon}"></i> BOM</a>
<a class="nav-link {mat_wc_bom_active}" href="/mds/mat-wc-bom"><i class="bi {mat_wc_bom_icon}"></i> <span data-i18n="mds.nav.bom">BOM</span></a>
</li>
<li class="nav-item">
<a class="nav-link {mold_active}" href="/mds/mold"><i class="bi {mold_icon}"></i> 模具</a>
<a class="nav-link {mold_active}" href="/mds/mold"><i class="bi {mold_icon}"></i> <span data-i18n="mds.nav.mold">模具</span></a>
</li>
<li class="nav-item">
<a class="nav-link {mat_wc_mold_active}" href="/mds/mat-wc-mold"><i class="bi {mat_wc_mold_icon}"></i> 机台模具</a>
<a class="nav-link {mat_wc_mold_active}" href="/mds/mat-wc-mold"><i class="bi {mat_wc_mold_icon}"></i> <span data-i18n="mds.nav.matWcMold">机台模具</span></a>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item">
<select id="lang-selector" class="form-select form-select-sm" style="width: auto; background-color: rgba(255,255,255,0.9); border: none;">
<option value="zh-CN">🇨🇳 中文</option>
<option value="en-US">🇺🇸 English</option>
<option value="de-DE">🇩🇪 Deutsch</option>
</select>
</li>
</ul>
</div>
@@ -54,26 +63,26 @@
<div class="row g-2 align-items-center">
<div class="col-auto d-flex gap-2">
<button class="btn btn-primary btn-sm font-monospace" style="width:150px" data-bs-toggle="modal" data-bs-target="#uploadModal">
<i class="bi bi-upload"></i> 导入
<i class="bi bi-upload"></i> <span data-i18n="mds.action.import">导入</span>
</button>
<button class="btn btn-success btn-sm font-monospace" style="width:150px" id="validateBtn">
<i class="bi bi-shield-check"></i> 校验
<i class="bi bi-shield-check"></i> <span data-i18n="mds.action.validate">校验</span>
</button>
<button class="btn btn-info btn-sm font-monospace" style="width:150px" id="syncBtn">
<i class="bi bi-cloud-upload"></i> 推送
<i class="bi bi-cloud-upload"></i> <span data-i18n="mds.action.sync">推送</span>
</button>
<button class="btn btn-outline-warning btn-sm font-monospace" style="width:150px" id="rulesBtn">
<i class="bi bi-sliders"></i> 校验规则
<i class="bi bi-sliders"></i> <span data-i18n="mds.validation.rulesTitle">校验规则</span>
</button>
</div>
<div class="col-auto">
<select class="form-select form-select-sm" id="statusFilter" style="width:100px">
<option value="">全部状态</option>
<option value="pending">待处理</option>
<option value="relation_pass">校验通过</option>
<option value="compliance_error">校验失败</option>
<option value="sync_error">推送失败</option>
<option value="synced">已推送</option>
<option value="" data-i18n="mds.status.all">全部状态</option>
<option value="pending" data-i18n="mds.status.pending">待处理</option>
<option value="relation_pass" data-i18n="mds.status.relationPass">校验通过</option>
<option value="compliance_error" data-i18n="mds.status.complianceError">校验失败</option>
<option value="sync_error" data-i18n="mds.status.syncError">推送失败</option>
<option value="synced" data-i18n="mds.status.synced">已推送</option>
</select>
</div>
<div class="col-auto">
@@ -90,13 +99,13 @@
</div>
<div class="col-auto d-flex gap-2">
<button class="btn btn-primary btn-sm font-monospace" style="width:150px" id="searchBtn">
<i class="bi bi-search"></i> 查询
<i class="bi bi-search"></i> <span data-i18n="mds.action.query">查询</span>
</button>
<button class="btn btn-outline-info btn-sm font-monospace" style="width:150px" data-bs-toggle="modal" data-bs-target="#advancedFilterModal">
<i class="bi bi-funnel"></i> 精准筛选
<i class="bi bi-funnel"></i> <span data-i18n="mds.action.preciseFilter">精准筛选</span>
</button>
<button class="btn btn-outline-secondary btn-sm font-monospace" style="width:150px" id="resetBtn">
<i class="bi bi-arrow-counterclockwise"></i> 重置
<i class="bi bi-arrow-counterclockwise"></i> <span data-i18n="mds.action.reset">重置</span>
</button>
</div>
</div>
@@ -109,20 +118,20 @@
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">导入Excel数据</h5>
<h5 class="modal-title" data-i18n="mds.modal.importTitle">导入Excel数据</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">去重策略</label>
<label class="form-label" data-i18n="mds.upload.dedupStrategy">去重策略</label>
<div class="d-flex gap-3">
<div class="form-check">
<input class="form-check-input" type="radio" name="dedupStrategy" id="dedupOverwrite" value="overwrite" checked>
<label class="form-check-label" for="dedupOverwrite">覆盖重复</label>
<label class="form-check-label" for="dedupOverwrite" data-i18n="mds.upload.overwrite">覆盖重复</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="dedupStrategy" id="dedupSkip" value="skip">
<label class="form-check-label" for="dedupSkip">跳过重复</label>
<label class="form-check-label" for="dedupSkip" data-i18n="mds.upload.skip">跳过重复</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="dedupStrategy" id="dedupReject" value="reject">
@@ -132,13 +141,13 @@
<small class="text-muted">覆盖重复:更新已有记录;跳过重复:保留原有记录;拒绝重复:有重复时报错</small>
</div>
<div class="file-upload-area" id="uploadArea">
<p class="mb-0">点击或拖拽文件上传(支持 .xlsx, .xls, .csv</p>
<p class="mb-0" data-i18n="mds.upload.dragDrop">点击或拖拽文件上传(支持 .xlsx, .xls, .csv</p>
<input type="file" id="fileInput" accept=".xlsx,.xls,.csv" style="display: none;">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" id="uploadBtn">上传</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" data-i18n="mds.modal.cancel">取消</button>
<button type="button" class="btn btn-primary" id="uploadBtn" data-i18n="mds.action.import">上传</button>
</div>
</div>
</div>
@@ -148,7 +157,7 @@
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">精准筛选</h5>
<h5 class="modal-title" data-i18n="mds.modal.filterTitle">精准筛选</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
@@ -187,9 +196,9 @@
</button>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary btn-sm" id="clearAllFilters">清空</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary btn-sm" id="applyAdvancedFilter">应用筛选</button>
<button type="button" class="btn btn-outline-secondary btn-sm" id="clearAllFilters" data-i18n="mds.action.clear">清空</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" data-i18n="mds.modal.cancel">取消</button>
<button type="button" class="btn btn-primary btn-sm" id="applyAdvancedFilter" data-i18n="mds.action.filter">应用筛选</button>
</div>
</div>
</div>
@@ -199,7 +208,7 @@
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">批量编辑记录</h5>
<h5 class="modal-title" data-i18n="mds.modal.editTitle">批量编辑记录</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
@@ -213,8 +222,8 @@
<div id="batchEditFields"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" id="applyBatchEdit">确定</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" data-i18n="mds.modal.cancel">取消</button>
<button type="button" class="btn btn-primary" id="applyBatchEdit" data-i18n="mds.modal.confirm">确定</button>
</div>
</div>
</div>
@@ -224,22 +233,27 @@
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">编辑记录</h5>
<h5 class="modal-title" data-i18n="mds.modal.editTitle">编辑记录</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="editForm"></form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" id="deleteBtn">删除</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" id="saveBtn">保存</button>
<button type="button" class="btn btn-danger" id="deleteBtn" data-i18n="mds.action.delete">删除</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" data-i18n="mds.modal.cancel">取消</button>
<button type="button" class="btn btn-primary" id="saveBtn" data-i18n="mds.action.save">保存</button>
</div>
</div>
</div>
</div>
<script src="/static/lib/bootstrap/bootstrap.bundle.min.js"></script>
<!-- i18n国际化支持 -->
<script src="/static/lib/i18n/i18n.js"></script>
<script src="/static/lib/i18n/zh-CN.js"></script>
<script src="/static/lib/i18n/en-US.js"></script>
<script src="/static/lib/i18n/de-DE.js"></script>
<script src="/static/mds/js/common.js"></script>
<script src="/static/mds/js/modal-manager.js"></script>
<script src="/static/mds/js/data-table.js"></script>
@@ -251,6 +265,27 @@
<script src="/static/mds/js/mds-page-controller.js"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
// 初始化i18n
if (typeof i18n !== 'undefined') {
const savedLang = localStorage.getItem('i18n_lang') || 'zh-CN';
i18n.init(savedLang);
}
// 语言切换处理
const langSelector = document.getElementById('lang-selector');
if (langSelector) {
const savedLang = localStorage.getItem('i18n_lang') || 'zh-CN';
langSelector.value = savedLang;
langSelector.addEventListener('change', function() {
const lang = this.value;
if (typeof switchLanguage === 'function') {
switchLanguage(lang);
}
});
}
// 初始化页面控制器
new MDSPageController(MDS_PAGE_CONFIG);
});
</script>