(format)
| 1710 | |
| 1711 | // 导出账号 |
| 1712 | async function exportAccounts(format) { |
| 1713 | const count = getEffectiveCount(); |
| 1714 | if (count === 0) { |
| 1715 | toast.warning('请先选择要导出的账号'); |
| 1716 | return; |
| 1717 | } |
| 1718 | |
| 1719 | toast.info(`正在导出 ${count} 个账号...`); |
| 1720 | |
| 1721 | try { |
| 1722 | const response = await fetch('/api/accounts/export/' + format, { |
| 1723 | method: 'POST', |
| 1724 | headers: { |
| 1725 | 'Content-Type': 'application/json', |
| 1726 | }, |
| 1727 | body: JSON.stringify(buildBatchPayload()) |
| 1728 | }); |
| 1729 | |
| 1730 | if (!response.ok) { |
| 1731 | throw new Error(`导出失败: HTTP ${response.status}`); |
| 1732 | } |
| 1733 | |
| 1734 | // 获取文件内容 |
| 1735 | const blob = await response.blob(); |
| 1736 | |
| 1737 | // 从 Content-Disposition 获取文件名 |
| 1738 | const disposition = response.headers.get('Content-Disposition'); |
| 1739 | let filename = `accounts_${Date.now()}.${(format === 'cpa' || format === 'sub2api') ? 'json' : (format === 'codex' ? 'jsonl' : format)}`; |
| 1740 | if (disposition) { |
| 1741 | const match = disposition.match(/filename=(.+)/); |
| 1742 | if (match) { |
| 1743 | filename = match[1]; |
| 1744 | } |
| 1745 | } |
| 1746 | |
| 1747 | // 创建下载链接 |
| 1748 | const url = window.URL.createObjectURL(blob); |
| 1749 | const a = document.createElement('a'); |
| 1750 | a.href = url; |
| 1751 | a.download = filename; |
| 1752 | document.body.appendChild(a); |
| 1753 | a.click(); |
| 1754 | window.URL.revokeObjectURL(url); |
| 1755 | a.remove(); |
| 1756 | |
| 1757 | toast.success('导出成功'); |
| 1758 | } catch (error) { |
| 1759 | console.error('导出失败:', error); |
| 1760 | toast.error('导出失败: ' + error.message); |
| 1761 | } |
| 1762 | } |
| 1763 | |
| 1764 | // HTML 转义 |
| 1765 | function escapeHtml(text) { |
no test coverage detected