PharC commited on
Commit
2d75d78
·
verified ·
1 Parent(s): 47d60cb

Create static/app.js

Browse files
Files changed (1) hide show
  1. static/app.js +347 -0
static/app.js ADDED
@@ -0,0 +1,347 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ let currentResults = null;
2
+ let currentBatchResults = null;
3
+
4
+ // 标签页切换
5
+ function switchTab(tabName) {
6
+ // 隐藏所有标签内容
7
+ document.querySelectorAll('.tab-content').forEach(tab => {
8
+ tab.classList.remove('active');
9
+ });
10
+ document.querySelectorAll('.tab-button').forEach(btn => {
11
+ btn.classList.remove('active');
12
+ });
13
+
14
+ // 显示选中的标签
15
+ document.getElementById(tabName + '-tab').classList.add('active');
16
+ event.target.classList.add('active');
17
+
18
+ // 隐藏结果区域
19
+ document.getElementById('results').style.display = 'none';
20
+ document.getElementById('batchResults').style.display = 'none';
21
+ }
22
+
23
+ // 单个基因设计
24
+ document.getElementById('primerForm').addEventListener('submit', async function(e) {
25
+ e.preventDefault();
26
+
27
+ const geneSymbol = document.getElementById('geneSymbol').value.trim();
28
+ const species = document.getElementById('species').value;
29
+
30
+ if (!geneSymbol) {
31
+ alert('请输入基因名称');
32
+ return;
33
+ }
34
+
35
+ // 显示加载状态
36
+ document.getElementById('loading').style.display = 'block';
37
+ document.getElementById('results').style.display = 'none';
38
+ document.getElementById('batchResults').style.display = 'none';
39
+ document.getElementById('submitBtn').disabled = true;
40
+
41
+ try {
42
+ const response = await fetch('/design_primers', {
43
+ method: 'POST',
44
+ headers: {
45
+ 'Content-Type': 'application/json',
46
+ },
47
+ body: JSON.stringify({
48
+ gene_symbol: geneSymbol,
49
+ species: species
50
+ })
51
+ });
52
+
53
+ const data = await response.json();
54
+
55
+ if (data.error) {
56
+ showError(data.error);
57
+ } else {
58
+ currentResults = [{ gene: geneSymbol, status: 'success', data: data }];
59
+ showResults(data);
60
+ }
61
+ } catch (error) {
62
+ showError('网络错误,请检查连接后重试');
63
+ } finally {
64
+ document.getElementById('loading').style.display = 'none';
65
+ document.getElementById('submitBtn').disabled = false;
66
+ }
67
+ });
68
+
69
+ // 批量基因设计
70
+ document.getElementById('batchForm').addEventListener('submit', async function(e) {
71
+ e.preventDefault();
72
+
73
+ const batchGenesText = document.getElementById('batchGenes').value.trim();
74
+ const species = document.getElementById('batchSpecies').value;
75
+
76
+ if (!batchGenesText) {
77
+ alert('请输入基因列表');
78
+ return;
79
+ }
80
+
81
+ // 解析基因列表
82
+ const geneList = parseGeneList(batchGenesText);
83
+
84
+ if (geneList.length === 0) {
85
+ alert('未找到有效的基因名称');
86
+ return;
87
+ }
88
+
89
+ // 显示进度条
90
+ document.getElementById('batchProgress').style.display = 'block';
91
+ document.getElementById('results').style.display = 'none';
92
+ document.getElementById('batchResults').style.display = 'none';
93
+ document.getElementById('batchSubmitBtn').disabled = true;
94
+
95
+ try {
96
+ const response = await fetch('/batch_design_primers', {
97
+ method: 'POST',
98
+ headers: {
99
+ 'Content-Type': 'application/json',
100
+ },
101
+ body: JSON.stringify({
102
+ gene_list: geneList,
103
+ species: species
104
+ })
105
+ });
106
+
107
+ const data = await response.json();
108
+ currentBatchResults = data.results;
109
+ showBatchResults(data.results);
110
+
111
+ } catch (error) {
112
+ showError('网络错误,请检查连接后重试');
113
+ } finally {
114
+ document.getElementById('batchProgress').style.display = 'none';
115
+ document.getElementById('batchSubmitBtn').disabled = false;
116
+ }
117
+ });
118
+
119
+ function parseGeneList(text) {
120
+ // 支持多种分隔符:换行、逗号、空格
121
+ return text
122
+ .split(/[\n,\s]+/)
123
+ .map(gene => gene.trim())
124
+ .filter(gene => gene.length > 0);
125
+ }
126
+
127
+ function showError(message) {
128
+ document.getElementById('results').innerHTML = `
129
+ <div class="error">
130
+ ❌ ${message}
131
+ </div>
132
+ `;
133
+ document.getElementById('results').style.display = 'block';
134
+ }
135
+
136
+ function showResults(data) {
137
+ const geneInfo = data.gene_info;
138
+ const primers = data.primers;
139
+
140
+ // 显示基因信息
141
+ document.getElementById('geneInfo').innerHTML = `
142
+ <h3>📋 基因信息</h3>
143
+ <p><strong>基因名称:</strong> ${geneInfo.symbol}</p>
144
+ <p><strong>RefSeq ID:</strong> ${geneInfo.nm_id}</p>
145
+ <p><strong>外显子交界点:</strong> ${geneInfo.junctions.join(', ')}</p>
146
+ `;
147
+
148
+ // 显示引物结果
149
+ let primerHtml = '<h3>🎯 推荐引物序列</h3>';
150
+
151
+ primers.forEach(primer => {
152
+ primerHtml += `
153
+ <div class="primer-card">
154
+ <div class="primer-header">
155
+ <span class="primer-id">引物对 ${primer.id}</span>
156
+ <span class="product-size">${primer.product_size} bp</span>
157
+ </div>
158
+
159
+ <div class="primer-sequences">
160
+ <div class="sequence-box">
161
+ <div class="sequence-label">正向引物 (Forward)</div>
162
+ <div class="sequence">${primer.forward}</div>
163
+ </div>
164
+ <div class="sequence-box">
165
+ <div class="sequence-label">反向引物 (Reverse)</div>
166
+ <div class="sequence">${primer.reverse}</div>
167
+ </div>
168
+ </div>
169
+
170
+ <div class="tm-info">
171
+ <span>正向Tm: ${primer.f_tm}°C</span>
172
+ <span>反向Tm: ${primer.r_tm}°C</span>
173
+ <span>${primer.junction_info}</span>
174
+ </div>
175
+ </div>
176
+ `;
177
+ });
178
+
179
+ document.getElementById('primerResults').innerHTML = primerHtml;
180
+ document.getElementById('exportSection').style.display = 'block';
181
+ document.getElementById('results').style.display = 'block';
182
+ }
183
+
184
+ function showBatchResults(results) {
185
+ const successCount = results.filter(r => r.status === 'success').length;
186
+ const failedCount = results.filter(r => r.status === 'failed').length;
187
+ const totalPrimers = results
188
+ .filter(r => r.status === 'success')
189
+ .reduce((sum, r) => sum + r.data.primers.length, 0);
190
+
191
+ // 显示统计信息
192
+ document.getElementById('resultSummary').innerHTML = `
193
+ <div class="summary-stats">
194
+ <div class="stat-item">
195
+ <div class="stat-number">${results.length}</div>
196
+ <div class="stat-label">总基因数</div>
197
+ </div>
198
+ <div class="stat-item">
199
+ <div class="stat-number">${successCount}</div>
200
+ <div class="stat-label">成功设计</div>
201
+ </div>
202
+ <div class="stat-item">
203
+ <div class="stat-number">${failedCount}</div>
204
+ <div class="stat-label">设计失败</div>
205
+ </div>
206
+ <div class="stat-item">
207
+ <div class="stat-number">${totalPrimers}</div>
208
+ <div class="stat-label">引物对总数</div>
209
+ </div>
210
+ </div>
211
+ `;
212
+
213
+ // 显示失败的基因
214
+ const failedGenes = results.filter(r => r.status === 'failed');
215
+ if (failedGenes.length > 0) {
216
+ let failedHtml = '<div class="failed-genes"><h4>⚠️ 设计失败的基因</h4>';
217
+ failedGenes.forEach(gene => {
218
+ failedHtml += `
219
+ <div class="failed-gene">
220
+ <strong>${gene.gene}:</strong> ${gene.error}
221
+ </div>
222
+ `;
223
+ });
224
+ failedHtml += '</div>';
225
+ document.getElementById('failedGenes').innerHTML = failedHtml;
226
+ } else {
227
+ document.getElementById('failedGenes').innerHTML = '';
228
+ }
229
+
230
+ // 显示成功的引物结果
231
+ let primerHtml = '<h3>🎯 批量引物设计结果</h3>';
232
+
233
+ results.filter(r => r.status === 'success').forEach(result => {
234
+ const geneInfo = result.data.gene_info;
235
+ const primers = result.data.primers;
236
+
237
+ primerHtml += `<div style="margin-bottom: 40px; border: 2px solid #e1e5e9; border-radius: 8px; padding: 20px;">`;
238
+ primerHtml += `<h4 style="color: #4facfe; margin-bottom: 15px;">📋 ${geneInfo.symbol} (${geneInfo.nm_id})</h4>`;
239
+
240
+ primers.forEach(primer => {
241
+ primerHtml += `
242
+ <div class="primer-card">
243
+ <div class="primer-header">
244
+ <span class="primer-id">引物对 ${primer.id}</span>
245
+ <span class="product-size">${primer.product_size} bp</span>
246
+ </div>
247
+
248
+ <div class="primer-sequences">
249
+ <div class="sequence-box">
250
+ <div class="sequence-label">正向引物 (Forward)</div>
251
+ <div class="sequence">${primer.forward}</div>
252
+ </div>
253
+ <div class="sequence-box">
254
+ <div class="sequence-label">反向引物 (Reverse)</div>
255
+ <div class="sequence">${primer.reverse}</div>
256
+ </div>
257
+ </div>
258
+
259
+ <div class="tm-info">
260
+ <span>正向Tm: ${primer.f_tm}°C</span>
261
+ <span>反向Tm: ${primer.r_tm}°C</span>
262
+ <span>${primer.junction_info}</span>
263
+ </div>
264
+ </div>
265
+ `;
266
+ });
267
+
268
+ primerHtml += '</div>';
269
+ });
270
+
271
+ document.getElementById('batchPrimerResults').innerHTML = primerHtml;
272
+ document.getElementById('batchResults').style.display = 'block';
273
+ }
274
+
275
+ // 导出单个结果
276
+ async function exportResults(format) {
277
+ if (!currentResults) {
278
+ alert('没有可导出的结果');
279
+ return;
280
+ }
281
+
282
+ try {
283
+ const response = await fetch('/export_primers', {
284
+ method: 'POST',
285
+ headers: {
286
+ 'Content-Type': 'application/json',
287
+ },
288
+ body: JSON.stringify({
289
+ format: format,
290
+ data: currentResults
291
+ })
292
+ });
293
+
294
+ if (response.ok) {
295
+ const blob = await response.blob();
296
+ const url = window.URL.createObjectURL(blob);
297
+ const a = document.createElement('a');
298
+ a.href = url;
299
+ a.download = response.headers.get('Content-Disposition')?.split('filename=')[1] || `primers.${format}`;
300
+ document.body.appendChild(a);
301
+ a.click();
302
+ window.URL.revokeObjectURL(url);
303
+ document.body.removeChild(a);
304
+ } else {
305
+ alert('导出失败,请重试');
306
+ }
307
+ } catch (error) {
308
+ alert('导出失败:' + error.message);
309
+ }
310
+ }
311
+
312
+ // 导出批量结果
313
+ async function exportBatchResults(format) {
314
+ if (!currentBatchResults) {
315
+ alert('没有可导出的结果');
316
+ return;
317
+ }
318
+
319
+ try {
320
+ const response = await fetch('/export_primers', {
321
+ method: 'POST',
322
+ headers: {
323
+ 'Content-Type': 'application/json',
324
+ },
325
+ body: JSON.stringify({
326
+ format: format,
327
+ data: currentBatchResults
328
+ })
329
+ });
330
+
331
+ if (response.ok) {
332
+ const blob = await response.blob();
333
+ const url = window.URL.createObjectURL(blob);
334
+ const a = document.createElement('a');
335
+ a.href = url;
336
+ a.download = response.headers.get('Content-Disposition')?.split('filename=')[1] || `batch_primers.${format}`;
337
+ document.body.appendChild(a);
338
+ a.click();
339
+ window.URL.revokeObjectURL(url);
340
+ document.body.removeChild(a);
341
+ } else {
342
+ alert('导出失败,请重试');
343
+ }
344
+ } catch (error) {
345
+ alert('导出失败:' + error.message);
346
+ }
347
+ }