mistpe commited on
Commit
6aecb58
·
verified ·
1 Parent(s): 50fc7b7

Create static/index.html

Browse files
Files changed (1) hide show
  1. static/index.html +766 -0
static/index.html ADDED
@@ -0,0 +1,766 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>翻译助手</title>
7
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
8
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.1/axios.min.js"></script>
9
+ </head>
10
+ <body class="bg-gray-50">
11
+ <!-- 导航栏 -->
12
+ <nav class="bg-white shadow-md fixed w-full top-0 z-50">
13
+ <div class="max-w-7xl mx-auto px-4">
14
+ <div class="flex justify-between h-16">
15
+ <div class="flex items-center">
16
+ <span class="text-xl font-bold text-purple-700">翻译助手</span>
17
+ </div>
18
+ <div class="hidden md:flex items-center space-x-8">
19
+ <button class="nav-btn text-gray-700 hover:text-purple-700" data-target="text-trans">文本翻译</button>
20
+ <button class="nav-btn text-gray-700 hover:text-purple-700" data-target="file-trans">文件翻译</button>
21
+ </div>
22
+ <button id="mobile-menu-btn" class="md:hidden flex items-center">
23
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
24
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
25
+ </svg>
26
+ </button>
27
+ </div>
28
+ </div>
29
+ <!-- 移动端菜单 -->
30
+ <div id="mobile-menu" class="hidden md:hidden bg-white border-t">
31
+ <div class="px-2 pt-2 pb-3 space-y-1">
32
+ <button class="nav-btn block w-full text-left px-3 py-2 text-gray-700 hover:bg-purple-50" data-target="text-trans">文本翻译</button>
33
+ <button class="nav-btn block w-full text-left px-3 py-2 text-gray-700 hover:bg-purple-50" data-target="file-trans">文件翻译</button>
34
+ </div>
35
+ </div>
36
+ </nav>
37
+
38
+ <!-- 主要内容区域 -->
39
+ <main class="pt-20 px-4 max-w-7xl mx-auto">
40
+ <!-- 文本翻译页面 -->
41
+ <div id="text-trans" class="page">
42
+ <div class="flex flex-col lg:flex-row gap-6">
43
+ <!-- 左侧工具栏 -->
44
+ <div class="lg:w-64">
45
+ <div class="bg-white rounded-lg shadow p-4 space-y-4">
46
+ <div>
47
+ <label class="block text-sm font-medium text-gray-700 mb-1">源语言</label>
48
+ <select id="source-lang" class="w-full border-gray-300 rounded-md shadow-sm">
49
+ <option value="AUTO">自动检测</option>
50
+ <option value="ZH">中文</option>
51
+ <option value="EN">英语</option>
52
+ <option value="JA">日语</option>
53
+ <option value="KO">韩语</option>
54
+ <option value="FR">法语</option>
55
+ <option value="DE">德语</option>
56
+ <option value="ES">西班牙语</option>
57
+ <option value="RU">俄语</option>
58
+ <option value="IT">意大利语</option>
59
+ <option value="PT">葡萄牙语</option>
60
+ <option value="VI">越南语</option>
61
+ <option value="ID">印尼语</option>
62
+ <option value="TH">泰语</option>
63
+ <option value="MS">马来语</option>
64
+ <option value="AR">阿拉伯语</option>
65
+ <option value="HI">印地语</option>
66
+ </select>
67
+ </div>
68
+ <div>
69
+ <label class="block text-sm font-medium text-gray-700 mb-1">目标语言</label>
70
+ <select id="target-lang" class="w-full border-gray-300 rounded-md shadow-sm">
71
+ <option value="ZH">中文</option>
72
+ <option value="EN">英语</option>
73
+ <option value="JA">日语</option>
74
+ <option value="KO">韩语</option>
75
+ <option value="FR">法语</option>
76
+ <option value="DE">德语</option>
77
+ <option value="ES">西班牙语</option>
78
+ <option value="RU">俄语</option>
79
+ <option value="IT">意大利语</option>
80
+ <option value="PT">葡萄牙语</option>
81
+ <option value="VI">越南语</option>
82
+ <option value="ID">印尼语</option>
83
+ <option value="TH">泰语</option>
84
+ <option value="MS">马来语</option>
85
+ <option value="AR">阿拉伯语</option>
86
+ <option value="HI">印地语</option>
87
+ </select>
88
+ </div>
89
+ <button id="switch-lang" class="w-full py-2 px-4 border border-purple-700 text-purple-700 rounded-md hover:bg-purple-50">
90
+ 切换语言
91
+ </button>
92
+ </div>
93
+ </div>
94
+
95
+ <!-- 翻译区域 -->
96
+ <div class="flex-1">
97
+ <div class="grid md:grid-cols-2 gap-6">
98
+ <!-- 源文本 -->
99
+ <div class="bg-white rounded-lg shadow p-4">
100
+ <textarea
101
+ id="source-text"
102
+ class="w-full h-48 p-2 border-0 focus:ring-0 resize-none"
103
+ placeholder="请输入要翻译的文本"
104
+ ></textarea>
105
+ <div class="flex justify-between mt-2 text-sm text-gray-500">
106
+ <span id="char-count">0/5000</span>
107
+ <button id="clear-text" class="hover:text-gray-700">清空</button>
108
+ </div>
109
+ </div>
110
+
111
+ <!-- 译文 -->
112
+ <div class="bg-white rounded-lg shadow p-4">
113
+ <div id="target-text" class="h-48 p-2"></div>
114
+ <div class="flex justify-end mt-2">
115
+ <button id="copy-result" class="text-sm text-gray-500 hover:text-gray-700">复制结果</button>
116
+ </div>
117
+ </div>
118
+ </div>
119
+
120
+ <button id="translate-btn" class="mt-6 mx-auto block py-3 px-8 bg-purple-600 text-white rounded-lg hover:bg-purple-700 disabled:opacity-50">
121
+ 翻译
122
+ </button>
123
+ </div>
124
+ </div>
125
+ </div>
126
+
127
+ <!-- 文件翻译页面 -->
128
+ <div id="file-trans" class="page hidden">
129
+ <div class="flex flex-col lg:flex-row gap-6">
130
+ <!-- 左侧工具栏 -->
131
+ <div class="lg:w-64 space-y-6">
132
+ <div class="bg-white rounded-lg shadow p-4 space-y-4">
133
+ <div>
134
+ <label class="block text-sm font-medium text-gray-700 mb-1">源语言</label>
135
+ <select id="file-source-lang" class="w-full border-gray-300 rounded-md shadow-sm">
136
+ <option value="auto">自动检测</option>
137
+ <option value="en">英语</option>
138
+ <option value="zh">中文</option>
139
+ </select>
140
+ </div>
141
+ <div>
142
+ <label class="block text-sm font-medium text-gray-700 mb-1">目标语言</label>
143
+ <select id="file-target-lang" class="w-full border-gray-300 rounded-md shadow-sm">
144
+ <option value="zh">中文</option>
145
+ <option value="en">英语</option>
146
+ </select>
147
+ </div>
148
+ </div>
149
+
150
+ <div class="bg-white rounded-lg shadow p-4 space-y-4">
151
+ <button id="upload-btn" class="w-full py-2 px-4 bg-purple-600 text-white rounded-md hover:bg-purple-700">
152
+ 上传文件
153
+ </button>
154
+ <button id="file-translate-btn" class="w-full py-2 px-4 bg-purple-100 text-purple-700 rounded-md hover:bg-purple-200" disabled>
155
+ 开始翻译
156
+ </button>
157
+ <button id="export-btn" class="w-full py-2 px-4 border border-gray-300 text-gray-700 rounded-md hover:bg-gray-50" disabled>
158
+ 导出文档
159
+ </button>
160
+ </div>
161
+
162
+ <!-- 翻译进度 -->
163
+ <div id="progress-panel" class="bg-white rounded-lg shadow p-4 hidden">
164
+ <h3 class="text-sm font-medium text-gray-700 mb-4">翻译进度</h3>
165
+ <div class="space-y-2">
166
+ <div class="flex justify-between text-sm">
167
+ <span>总段落数</span>
168
+ <span id="total-segments">0</span>
169
+ </div>
170
+ <div class="flex justify-between text-sm">
171
+ <span>已翻译</span>
172
+ <span id="translated-segments">0</span>
173
+ </div>
174
+ <div class="h-2 bg-gray-200 rounded-full">
175
+ <div id="progress-bar" class="h-full bg-purple-600 rounded-full w-0 transition-all"></div>
176
+ </div>
177
+ </div>
178
+ </div>
179
+ </div>
180
+
181
+ <!-- 文件内容区域 -->
182
+ <div class="flex-1">
183
+ <!-- 上传区域 -->
184
+ <div id="upload-zone" class="h-64 border-2 border-dashed border-purple-400 rounded-lg flex flex-col items-center justify-center bg-white">
185
+ <svg class="w-12 h-12 text-purple-400 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
186
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"/>
187
+ </svg>
188
+ <p class="text-gray-600">拖放文件到这里或点击上传</p>
189
+ <p class="text-sm text-gray-500 mt-2">支持 TXT、DOCX、PDF、Markdown 格式</p>
190
+ </div>
191
+
192
+ <!-- 文档预览 -->
193
+ <div id="file-preview" class="hidden">
194
+ <div class="bg-white rounded-lg shadow">
195
+ <div class="border-b border-gray-200 p-4 flex justify-between items-center">
196
+ <span id="file-name" class="font-medium"></span>
197
+ <button id="close-preview" class="text-gray-500 hover:text-gray-700">
198
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
199
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
200
+ </svg>
201
+ </button>
202
+ </div>
203
+ <div class="p-6">
204
+ <div class="grid md:grid-cols-2 gap-6">
205
+ <div>
206
+ <h3 class="text-sm font-medium text-gray-700 mb-4">原文</h3>
207
+ <div id="file-content" class="space-y-4"></div>
208
+ </div>
209
+ <div>
210
+ <h3 class="text-sm font-medium text-gray-700 mb-4">译文</h3>
211
+ <div id="file-translation" class="space-y-4"></div>
212
+ </div>
213
+ </div>
214
+ </div>
215
+ </div>
216
+ </div>
217
+ </div>
218
+ </div>
219
+ </div>
220
+ </main>
221
+
222
+ <!-- 导出设置模态框 -->
223
+ <div id="export-modal" class="fixed inset-0 z-50 hidden">
224
+ <div class="absolute inset-0 bg-gray-500 bg-opacity-75"></div>
225
+ <div class="fixed inset-0 z-10 overflow-y-auto">
226
+ <div class="flex min-h-full items-end justify-center p-4 sm:items-center">
227
+ <div class="relative bg-white rounded-lg shadow-xl w-full max-w-md p-6">
228
+ <h3 class="text-lg font-medium text-gray-900 mb-4">导出设置(暂时不支持导出为PDF)</h3>
229
+ <div class="space-y-4">
230
+ <div>
231
+ <label class="block text-sm font-medium text-gray-700 mb-1">导出格式</label>
232
+ <select id="export-format" class="w-full rounded-md border-gray-300">
233
+ <option value="auto">自动 (与源文件相同)</option>
234
+ <option value="txt">纯文本 (TXT)</option>
235
+ <option value="docx">Word文档 (DOCX)</option>
236
+ <option value="md">Markdown (MD)</option>
237
+ <option value="html">网页 (HTML)</option>
238
+ </select>
239
+ </div>
240
+ <div>
241
+ <label class="block text-sm font-medium text-gray-700 mb-1">导出模式</label>
242
+ <div class="space-y-2">
243
+ <label class="inline-flex items-center">
244
+ <input type="radio" name="export-mode" value="translated" class="text-purple-600" checked>
245
+ <span class="ml-2">仅译文</span>
246
+ </label>
247
+ <label class="inline-flex items-center ml-4">
248
+ <input type="radio" name="export-mode" value="parallel" class="text-purple-600">
249
+ <span class="ml-2">对照模式</span>
250
+ </label>
251
+ </div>
252
+ </div>
253
+ </div>
254
+ <div class="mt-6 flex justify-end space-x-3">
255
+ <button id="cancel-export" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50">
256
+ 取消
257
+ </button>
258
+ <button id="confirm-export" class="px-4 py-2 bg-purple-600 text-white rounded-md hover:bg-purple-700">
259
+ 导出
260
+ </button>
261
+ </div>
262
+ </div>
263
+ </div>
264
+ </div>
265
+ </div>
266
+
267
+ <script>
268
+ // 常量定义
269
+ const API_ENDPOINTS = {
270
+ TRANSLATE_TEXT: '/translate_text',
271
+ UPLOAD_FILE: '/upload',
272
+ TRANSLATE_FILE: '/translate',
273
+ EXPORT: '/export'
274
+ };
275
+
276
+ // 工具函数
277
+ const utils = {
278
+ showError(message) {
279
+ // 简单的错误提示
280
+ alert(message);
281
+ },
282
+
283
+ async copyToClipboard(text) {
284
+ try {
285
+ await navigator.clipboard.writeText(text);
286
+ return true;
287
+ } catch (err) {
288
+ console.error('Copy failed:', err);
289
+ return false;
290
+ }
291
+ },
292
+
293
+ formatBytes(bytes) {
294
+ if (bytes === 0) return '0 B';
295
+ const k = 1024;
296
+ const sizes = ['B', 'KB', 'MB', 'GB'];
297
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
298
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
299
+ }
300
+ };
301
+
302
+ // API封装
303
+ const api = {
304
+ async translateText(text, sourceLang, targetLang) {
305
+ try {
306
+ const response = await fetch(API_ENDPOINTS.TRANSLATE_TEXT, {
307
+ method: 'POST',
308
+ headers: {
309
+ 'Content-Type': 'application/json',
310
+ },
311
+ body: JSON.stringify({
312
+ text,
313
+ source_lang: sourceLang,
314
+ target_lang: targetLang
315
+ })
316
+ });
317
+
318
+ if (!response.ok) {
319
+ throw new Error(`HTTP error! status: ${response.status}`);
320
+ }
321
+
322
+ const data = await response.json();
323
+ return data;
324
+ } catch (error) {
325
+ console.error('Translation failed:', error);
326
+ throw error;
327
+ }
328
+ },
329
+
330
+ async uploadFile(file, sourceLang, targetLang) {
331
+ const formData = new FormData();
332
+ formData.append('file', file);
333
+ formData.append('source_lang', sourceLang);
334
+ formData.append('target_lang', targetLang);
335
+
336
+ try {
337
+ const response = await fetch(API_ENDPOINTS.UPLOAD_FILE, {
338
+ method: 'POST',
339
+ body: formData
340
+ });
341
+
342
+ if (!response.ok) {
343
+ throw new Error(`HTTP error! status: ${response.status}`);
344
+ }
345
+
346
+ return await response.json();
347
+ } catch (error) {
348
+ console.error('File upload failed:', error);
349
+ throw error;
350
+ }
351
+ },
352
+
353
+ async translateFile(segments, sourceLang, targetLang) {
354
+ try {
355
+ const response = await fetch(API_ENDPOINTS.TRANSLATE_FILE, {
356
+ method: 'POST',
357
+ headers: {
358
+ 'Content-Type': 'application/json',
359
+ },
360
+ body: JSON.stringify({
361
+ segments,
362
+ source_lang: sourceLang,
363
+ target_lang: targetLang
364
+ })
365
+ });
366
+
367
+ if (!response.ok) {
368
+ throw new Error(`HTTP error! status: ${response.status}`);
369
+ }
370
+
371
+ return await response.json();
372
+ } catch (error) {
373
+ console.error('File translation failed:', error);
374
+ throw error;
375
+ }
376
+ },
377
+
378
+ async exportDocument(segments, format, mode, sourceFileType) {
379
+ try {
380
+ const response = await fetch(API_ENDPOINTS.EXPORT, {
381
+ method: 'POST',
382
+ headers: {
383
+ 'Content-Type': 'application/json',
384
+ },
385
+ body: JSON.stringify({
386
+ segments,
387
+ format,
388
+ mode,
389
+ source_file_type: sourceFileType
390
+ })
391
+ });
392
+
393
+ if (!response.ok) {
394
+ throw new Error(`HTTP error! status: ${response.status}`);
395
+ }
396
+
397
+ return await response.blob();
398
+ } catch (error) {
399
+ console.error('Export failed:', error);
400
+ throw error;
401
+ }
402
+ }
403
+ };
404
+
405
+ // 文本翻译页面类
406
+ class TextTranslation {
407
+ constructor() {
408
+ this.sourceText = document.getElementById('source-text');
409
+ this.targetText = document.getElementById('target-text');
410
+ this.translateBtn = document.getElementById('translate-btn');
411
+ this.sourceLang = document.getElementById('source-lang');
412
+ this.targetLang = document.getElementById('target-lang');
413
+ this.charCount = document.getElementById('char-count');
414
+ this.clearBtn = document.getElementById('clear-text');
415
+ this.copyBtn = document.getElementById('copy-result');
416
+ this.switchBtn = document.getElementById('switch-lang');
417
+
418
+ this.init();
419
+ }
420
+
421
+ init() {
422
+ this.bindEvents();
423
+ this.updateCharCount();
424
+ }
425
+
426
+ bindEvents() {
427
+ this.sourceText.addEventListener('input', () => this.updateCharCount());
428
+ this.translateBtn.addEventListener('click', () => this.translate());
429
+ this.clearBtn.addEventListener('click', () => this.clearText());
430
+ this.copyBtn.addEventListener('click', () => this.copyResult());
431
+ this.switchBtn.addEventListener('click', () => this.switchLanguages());
432
+ }
433
+
434
+ updateCharCount() {
435
+ const count = this.sourceText.value.length;
436
+ this.charCount.textContent = `${count}/5000`;
437
+ this.translateBtn.disabled = count === 0;
438
+ }
439
+
440
+ clearText() {
441
+ this.sourceText.value = '';
442
+ this.targetText.textContent = '';
443
+ this.updateCharCount();
444
+ }
445
+
446
+ async copyResult() {
447
+ const success = await utils.copyToClipboard(this.targetText.textContent);
448
+ if (success) {
449
+ this.copyBtn.textContent = '已复制';
450
+ setTimeout(() => {
451
+ this.copyBtn.textContent = '复制结果';
452
+ }, 2000);
453
+ }
454
+ }
455
+
456
+ switchLanguages() {
457
+ if (this.sourceLang.value === 'auto') return;
458
+ [this.sourceLang.value, this.targetLang.value] =
459
+ [this.targetLang.value, this.sourceLang.value];
460
+ }
461
+
462
+ async translate() {
463
+ if (!this.sourceText.value.trim()) return;
464
+
465
+ this.translateBtn.disabled = true;
466
+ try {
467
+ const result = await api.translateText(
468
+ this.sourceText.value,
469
+ this.sourceLang.value,
470
+ this.targetLang.value
471
+ );
472
+
473
+ this.targetText.textContent = result.translated;
474
+ } catch (error) {
475
+ utils.showError('翻译失败,请稍后重试');
476
+ } finally {
477
+ this.translateBtn.disabled = false;
478
+ }
479
+ }
480
+ }
481
+
482
+ // 文件翻译页面类
483
+ class FileTranslation {
484
+ constructor() {
485
+ this.uploadZone = document.getElementById('upload-zone');
486
+ this.filePreview = document.getElementById('file-preview');
487
+ this.uploadBtn = document.getElementById('upload-btn');
488
+ this.translateBtn = document.getElementById('file-translate-btn');
489
+ this.exportBtn = document.getElementById('export-btn');
490
+ this.progressPanel = document.getElementById('progress-panel');
491
+ this.closePreviewBtn = document.getElementById('close-preview');
492
+
493
+ this.sourceLang = document.getElementById('file-source-lang');
494
+ this.targetLang = document.getElementById('file-target-lang');
495
+
496
+ this.currentFile = null;
497
+ this.segments = [];
498
+
499
+ this.init();
500
+ }
501
+
502
+ init() {
503
+ this.bindEvents();
504
+ this.initDragDrop();
505
+ }
506
+
507
+ bindEvents() {
508
+ this.uploadBtn.addEventListener('click', () => this.triggerFileInput());
509
+ this.translateBtn.addEventListener('click', () => this.translateFile());
510
+ this.exportBtn.addEventListener('click', () => this.showExportModal());
511
+ this.closePreviewBtn.addEventListener('click', () => this.closePreview());
512
+
513
+ // 导出模态框事件
514
+ document.getElementById('confirm-export').addEventListener('click', () => this.exportFile());
515
+ document.getElementById('cancel-export').addEventListener('click', () => this.hideExportModal());
516
+ }
517
+
518
+ initDragDrop() {
519
+ this.uploadZone.addEventListener('dragover', (e) => {
520
+ e.preventDefault();
521
+ e.stopPropagation();
522
+ this.uploadZone.classList.add('border-purple-600');
523
+ });
524
+
525
+ this.uploadZone.addEventListener('dragleave', (e) => {
526
+ e.preventDefault();
527
+ e.stopPropagation();
528
+ this.uploadZone.classList.remove('border-purple-600');
529
+ });
530
+
531
+ this.uploadZone.addEventListener('drop', async (e) => {
532
+ e.preventDefault();
533
+ e.stopPropagation();
534
+ this.uploadZone.classList.remove('border-purple-600');
535
+
536
+ const file = e.dataTransfer.files[0];
537
+ await this.handleFile(file);
538
+ });
539
+ }
540
+
541
+ triggerFileInput() {
542
+ const input = document.createElement('input');
543
+ input.type = 'file';
544
+ input.accept = '.txt,.docx,.pdf,.md';
545
+ input.onchange = (e) => this.handleFile(e.target.files[0]);
546
+ input.click();
547
+ }
548
+
549
+ async handleFile(file) {
550
+ if (!file) return;
551
+
552
+ const validTypes = [
553
+ 'text/plain',
554
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
555
+ 'application/pdf',
556
+ 'text/markdown'
557
+ ];
558
+
559
+ if (!validTypes.includes(file.type)) {
560
+ utils.showError('���支持的文件格式');
561
+ return;
562
+ }
563
+
564
+ this.currentFile = file;
565
+ try {
566
+ const result = await api.uploadFile(
567
+ file,
568
+ this.sourceLang.value,
569
+ this.targetLang.value
570
+ );
571
+
572
+ this.segments = result.segments;
573
+ this.updatePreview();
574
+ this.translateBtn.disabled = false;
575
+ this.showProgressPanel();
576
+ } catch (error) {
577
+ utils.showError('文件上传失败,请重试');
578
+ }
579
+ }
580
+
581
+ updatePreview() {
582
+ this.uploadZone.classList.add('hidden');
583
+ this.filePreview.classList.remove('hidden');
584
+
585
+ document.getElementById('file-name').textContent = this.currentFile.name;
586
+
587
+ const contentDiv = document.getElementById('file-content');
588
+ contentDiv.innerHTML = this.segments.map((segment, index) => `
589
+ <div class="p-4 bg-gray-50 rounded">
590
+ <div class="text-gray-900">${segment.text}</div>
591
+ ${segment.type === 'heading' ? '<div class="text-xs text-gray-500 mt-1">标题</div>' : ''}
592
+ </div>
593
+ `).join('');
594
+ }
595
+
596
+ async translateFile() {
597
+ if (!this.segments.length) return;
598
+
599
+ this.translateBtn.disabled = true;
600
+ try {
601
+ const result = await api.translateFile(
602
+ this.segments,
603
+ this.sourceLang.value,
604
+ this.targetLang.value
605
+ );
606
+
607
+ this.segments = result.segments;
608
+ this.updateTranslations();
609
+ this.exportBtn.disabled = false;
610
+ this.updateProgress();
611
+ } catch (error) {
612
+ utils.showError('翻译失败,请重试');
613
+ } finally {
614
+ this.translateBtn.disabled = false;
615
+ }
616
+ }
617
+
618
+ updateTranslations() {
619
+ const translationDiv = document.getElementById('file-translation');
620
+ translationDiv.innerHTML = this.segments.map((segment, index) => `
621
+ <div class="p-4 bg-gray-50 rounded">
622
+ <div class="text-gray-900">${segment.translated || ''}</div>
623
+ ${segment.confidence ? `
624
+ <div class="mt-2 h-1 bg-gray-200 rounded-full overflow-hidden">
625
+ <div class="h-full bg-purple-600" style="width: ${segment.confidence * 100}%"></div>
626
+ </div>
627
+ ` : ''}
628
+ </div>
629
+ `).join('');
630
+ }
631
+
632
+ showProgressPanel() {
633
+ this.progressPanel.classList.remove('hidden');
634
+ this.updateProgress();
635
+ }
636
+
637
+ updateProgress() {
638
+ const total = this.segments.length;
639
+ const translated = this.segments.filter(s => s.translated).length;
640
+
641
+ document.getElementById('total-segments').textContent = total;
642
+ document.getElementById('translated-segments').textContent = translated;
643
+ document.getElementById('progress-bar').style.width = `${(translated / total * 100)}%`;
644
+ }
645
+
646
+ closePreview() {
647
+ this.uploadZone.classList.remove('hidden');
648
+ this.filePreview.classList.add('hidden');
649
+ this.progressPanel.classList.add('hidden');
650
+ this.translateBtn.disabled = true;
651
+ this.exportBtn.disabled = true;
652
+ this.currentFile = null;
653
+ this.segments = [];
654
+ }
655
+
656
+ showExportModal() {
657
+ document.getElementById('export-modal').classList.remove('hidden');
658
+ }
659
+
660
+ hideExportModal() {
661
+ document.getElementById('export-modal').classList.add('hidden');
662
+ }
663
+
664
+ async exportFile() {
665
+ const formatSelect = document.getElementById('export-format');
666
+ const selectedFormat = formatSelect.value;
667
+ const mode = document.querySelector('input[name="export-mode"]:checked').value;
668
+
669
+ // 确定最终使用的导出格式
670
+ let exportFormat;
671
+ if (selectedFormat === 'auto') {
672
+ // 如果选择"自动",则使用原文件格式
673
+ exportFormat = this.currentFile.name.split('.').pop();
674
+ } else {
675
+ // 否则使用用户选择的格式
676
+ exportFormat = selectedFormat;
677
+ }
678
+
679
+ try {
680
+ const blob = await api.exportDocument(
681
+ this.segments,
682
+ exportFormat,
683
+ mode,
684
+ this.currentFile.name.split('.').pop() // 原文件类型仍然需要传递给后端
685
+ );
686
+
687
+ // 根据选择的格式确定文件扩展名
688
+ let fileExtension = exportFormat;
689
+ if (exportFormat === 'auto') {
690
+ fileExtension = this.currentFile.name.split('.').pop();
691
+ }
692
+
693
+ // 创建新的文件名
694
+ const originalName = this.currentFile.name.split('.')[0];
695
+ const newFileName = `translated_${originalName}.${fileExtension}`;
696
+
697
+ const url = URL.createObjectURL(blob);
698
+ const a = document.createElement('a');
699
+ a.href = url;
700
+ a.download = newFileName;
701
+ document.body.appendChild(a);
702
+ a.click();
703
+ document.body.removeChild(a);
704
+ URL.revokeObjectURL(url);
705
+
706
+ this.hideExportModal();
707
+ } catch (error) {
708
+ utils.showError('导出失败,请重试');
709
+ }
710
+ }
711
+ }
712
+
713
+ // 页面导航控制类
714
+ class Navigation {
715
+ constructor() {
716
+ this.mobileMenuBtn = document.getElementById('mobile-menu-btn');
717
+ this.mobileMenu = document.getElementById('mobile-menu');
718
+ this.navButtons = document.querySelectorAll('.nav-btn');
719
+
720
+ this.init();
721
+ }
722
+
723
+ init() {
724
+ this.bindEvents();
725
+ }
726
+
727
+ bindEvents() {
728
+ this.mobileMenuBtn.addEventListener('click', () => this.toggleMobileMenu());
729
+
730
+ this.navButtons.forEach(btn => {
731
+ btn.addEventListener('click', () => {
732
+ this.switchPage(btn.dataset.target);
733
+ this.mobileMenu.classList.add('hidden');
734
+ });
735
+ });
736
+ }
737
+
738
+ toggleMobileMenu() {
739
+ this.mobileMenu.classList.toggle('hidden');
740
+ }
741
+
742
+ switchPage(pageId) {
743
+ document.querySelectorAll('.page').forEach(page => {
744
+ page.classList.add('hidden');
745
+ });
746
+ document.getElementById(pageId).classList.remove('hidden');
747
+
748
+ this.navButtons.forEach(btn => {
749
+ if (btn.dataset.target === pageId) {
750
+ btn.classList.add('text-purple-700');
751
+ } else {
752
+ btn.classList.remove('text-purple-700');
753
+ }
754
+ });
755
+ }
756
+ }
757
+
758
+ // 初始化应用
759
+ document.addEventListener('DOMContentLoaded', () => {
760
+ new Navigation();
761
+ new TextTranslation();
762
+ new FileTranslation();
763
+ });
764
+ </script>
765
+ </body>
766
+ </html>