V2.0.0: 新增用户权限动态配置、会员套餐配置、数据包购买功能
新功能: - 用户权限动态配置(翻译次数、页数限制) - 会员套餐动态配置(名称、价格、周期) - 数据包购买套餐管理 - 收入统计功能 - 数据包销售排行 技术更新: - 新增 DynamicConfig 模型支持动态配置 - 新增 DataPackage 和 UserPackage 模型 - 后台管理增加数据包管理模块
This commit is contained in:
286
static/js/main.js
Normal file
286
static/js/main.js
Normal file
@@ -0,0 +1,286 @@
|
||||
/**
|
||||
* PDF翻译助手前端脚本
|
||||
*/
|
||||
|
||||
// 当前翻译ID
|
||||
let currentTranslationId = null;
|
||||
let currentTaskId = null;
|
||||
|
||||
// 上传表单处理
|
||||
document.getElementById('uploadForm').addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const fileInput = document.getElementById('pdfFile');
|
||||
const file = fileInput.files[0];
|
||||
|
||||
if (!file) {
|
||||
alert('请选择PDF文件');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!file.name.toLowerCase().endsWith('.pdf')) {
|
||||
alert('只支持PDF文件');
|
||||
return;
|
||||
}
|
||||
|
||||
// 显示进度区域
|
||||
document.getElementById('progressSection').style.display = 'block';
|
||||
document.getElementById('resultSection').style.display = 'none';
|
||||
document.getElementById('cacheNotice').style.display = 'none';
|
||||
|
||||
// 显示加载状态
|
||||
const submitBtn = document.getElementById('submitBtn');
|
||||
const btnText = document.getElementById('btnText');
|
||||
const btnSpinner = document.getElementById('btnSpinner');
|
||||
|
||||
submitBtn.disabled = true;
|
||||
btnText.textContent = '上传中...';
|
||||
btnSpinner.style.display = 'inline-block';
|
||||
|
||||
// 构建表单数据
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
const instruction = document.getElementById('instruction')?.value;
|
||||
if (instruction) {
|
||||
formData.append('instruction', instruction);
|
||||
}
|
||||
|
||||
try {
|
||||
// 上传文件
|
||||
const response = await fetch('/api/upload', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(result.error || '上传失败');
|
||||
}
|
||||
|
||||
currentTranslationId = result.translation_id;
|
||||
currentTaskId = result.task_id;
|
||||
|
||||
// 如果使用缓存,直接显示结果
|
||||
if (result.from_cache) {
|
||||
document.getElementById('cacheNotice').style.display = 'block';
|
||||
showResult(currentTranslationId);
|
||||
} else {
|
||||
// 开始轮询进度
|
||||
pollProgress(currentTaskId, currentTranslationId);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
alert('上传失败: ' + error.message);
|
||||
resetUploadButton();
|
||||
}
|
||||
});
|
||||
|
||||
// 轮询翻译进度
|
||||
async function pollProgress(taskId, translationId) {
|
||||
const progressBar = document.getElementById('progressBar');
|
||||
const progressMessage = document.getElementById('progressMessage');
|
||||
|
||||
const poll = async () => {
|
||||
try {
|
||||
// 同时检查任务状态和翻译状态
|
||||
const taskResponse = await fetch(`/api/task/${taskId}`);
|
||||
const taskResult = await taskResponse.json();
|
||||
|
||||
const transResponse = await fetch(`/api/status/${translationId}`);
|
||||
const transResult = await transResponse.json();
|
||||
|
||||
// 更新进度
|
||||
if (taskResult.progress) {
|
||||
progressBar.style.width = taskResult.progress + '%';
|
||||
progressBar.textContent = taskResult.progress + '%';
|
||||
}
|
||||
|
||||
if (taskResult.message) {
|
||||
progressMessage.textContent = taskResult.message;
|
||||
}
|
||||
|
||||
// 检查是否完成
|
||||
if (taskResult.status === 'completed' || transResult.status === 'completed') {
|
||||
progressBar.style.width = '100%';
|
||||
progressBar.textContent = '100%';
|
||||
progressMessage.textContent = '翻译完成!';
|
||||
|
||||
// 显示结果
|
||||
setTimeout(() => showResult(translationId), 500);
|
||||
return;
|
||||
}
|
||||
|
||||
if (taskResult.status === 'failed') {
|
||||
progressMessage.textContent = '翻译失败: ' + (taskResult.error || '未知错误');
|
||||
resetUploadButton();
|
||||
return;
|
||||
}
|
||||
|
||||
// 继续轮询
|
||||
setTimeout(poll, 2000);
|
||||
|
||||
} catch (error) {
|
||||
console.error('轮询失败:', error);
|
||||
setTimeout(poll, 3000);
|
||||
}
|
||||
};
|
||||
|
||||
poll();
|
||||
}
|
||||
|
||||
// 显示翻译结果
|
||||
async function showResult(translationId) {
|
||||
const resultSection = document.getElementById('resultSection');
|
||||
const resultContent = document.getElementById('resultContent');
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/result/${translationId}`);
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(result.error || '获取结果失败');
|
||||
}
|
||||
|
||||
// 渲染Markdown内容
|
||||
resultContent.innerHTML = renderMarkdown(result.content);
|
||||
resultSection.style.display = 'block';
|
||||
|
||||
resetUploadButton();
|
||||
|
||||
} catch (error) {
|
||||
alert('获取结果失败: ' + error.message);
|
||||
resetUploadButton();
|
||||
}
|
||||
}
|
||||
|
||||
// 重置上传按钮
|
||||
function resetUploadButton() {
|
||||
const submitBtn = document.getElementById('submitBtn');
|
||||
const btnText = document.getElementById('btnText');
|
||||
const btnSpinner = document.getElementById('btnSpinner');
|
||||
|
||||
submitBtn.disabled = false;
|
||||
btnText.textContent = '开始翻译';
|
||||
btnSpinner.style.display = 'none';
|
||||
}
|
||||
|
||||
// 下载结果
|
||||
document.getElementById('downloadBtn')?.addEventListener('click', function() {
|
||||
if (currentTranslationId) {
|
||||
window.location.href = `/api/download/${currentTranslationId}`;
|
||||
}
|
||||
});
|
||||
|
||||
// 对比查看
|
||||
document.getElementById('viewCompare')?.addEventListener('click', async function() {
|
||||
if (!currentTranslationId) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/compare/${currentTranslationId}`);
|
||||
const result = await response.json();
|
||||
|
||||
// 显示对比视图
|
||||
showCompareView(result);
|
||||
|
||||
} catch (error) {
|
||||
alert('获取对比失败: ' + error.message);
|
||||
}
|
||||
});
|
||||
|
||||
// 显示对比视图
|
||||
function showCompareView(data) {
|
||||
const resultContent = document.getElementById('resultContent');
|
||||
|
||||
resultContent.innerHTML = `
|
||||
<div class="compare-container">
|
||||
<div class="compare-panel original">
|
||||
<h5>原文</h5>
|
||||
<div class="content">${escapeHtml(data.original)}</div>
|
||||
</div>
|
||||
<div class="compare-panel translated">
|
||||
<h5>译文</h5>
|
||||
<div class="content">${renderMarkdown(data.translated)}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// 重新翻译
|
||||
document.getElementById('retranslateBtn')?.addEventListener('click', async function() {
|
||||
if (!currentTranslationId) return;
|
||||
|
||||
const instruction = document.getElementById('retranslateInstruction').value;
|
||||
if (!instruction.trim()) {
|
||||
alert('请输入翻译要求');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/retranslate/${currentTranslationId}`, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({instruction: instruction})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(result.error || '重译请求失败');
|
||||
}
|
||||
|
||||
// 开始新的翻译任务
|
||||
currentTranslationId = result.translation_id;
|
||||
document.getElementById('progressSection').style.display = 'block';
|
||||
document.getElementById('resultSection').style.display = 'none';
|
||||
pollProgress(null, currentTranslationId);
|
||||
|
||||
} catch (error) {
|
||||
alert('重译失败: ' + error.message);
|
||||
}
|
||||
});
|
||||
|
||||
// 简单Markdown渲染
|
||||
function renderMarkdown(text) {
|
||||
// 标题
|
||||
text = text.replace(/^## (.*)$/gm, '<h2>$1</h2>');
|
||||
text = text.replace(/^# (.*)$/gm, '<h1>$1</h1>');
|
||||
|
||||
// 分隔线
|
||||
text = text.replace(/^---$/gm, '<hr>');
|
||||
|
||||
// 引用
|
||||
text = text.replace(/^> (.*)$/gm, '<blockquote>$1</blockquote>');
|
||||
|
||||
// 段落
|
||||
text = text.replace(/\n\n/g, '</p><p>');
|
||||
text = '<p>' + text + '</p>';
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
// HTML转义
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// 检查用户登录状态
|
||||
async function checkUserStatus() {
|
||||
try {
|
||||
const response = await fetch('/api/user/info');
|
||||
const result = await response.json();
|
||||
|
||||
if (result.user) {
|
||||
console.log('用户已登录:', result.user.username);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('检查用户状态失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载时检查状态
|
||||
document.addEventListener('DOMContentLoaded', checkUserStatus);
|
||||
Reference in New Issue
Block a user