// ocr.js lucide.createIcons(); async function checkAuth() { const r = await fetch('/api/auth/me'); const d = await r.json(); const profile = document.getElementById('userProfile'); const entry = document.getElementById('loginEntryBtn'); const submitBtn = document.getElementById('submitBtn'); if(d.logged_in) { if(profile) profile.classList.remove('hidden'); if(entry) entry.classList.add('hidden'); submitBtn.disabled = false; submitBtn.classList.remove('opacity-50', 'cursor-not-allowed'); const phoneDisp = document.getElementById('userPhoneDisplay'); if(phoneDisp) phoneDisp.innerText = d.phone; // 处理积分显示 const pointsBadge = document.getElementById('pointsBadge'); const pointsDisplay = document.getElementById('pointsDisplay'); if(pointsBadge && pointsDisplay) { pointsBadge.classList.remove('hidden'); pointsDisplay.innerText = d.points; } const headerPoints = document.getElementById('headerPoints'); if(headerPoints) headerPoints.innerText = d.points; } else { if(profile) profile.classList.add('hidden'); if(entry) entry.classList.remove('hidden'); submitBtn.disabled = true; submitBtn.classList.add('opacity-50', 'cursor-not-allowed'); } lucide.createIcons(); } let uploadedFiles = []; const FIXED_MODEL = 'gemini-3-flash-preview'; function updateCostPreview() { const costPreview = document.getElementById('costPreview'); const isPremium = document.getElementById('isPremium')?.checked || false; // 默认消耗 1 积分,优质模式 X2 let cost = 1; if(isPremium) cost *= 2; costPreview.innerText = `本次解析将消耗 ${cost} 积分`; costPreview.classList.remove('hidden'); } function renderImagePreviews() { const prev = document.getElementById('imagePreview'); if(!prev) return; prev.innerHTML = ''; uploadedFiles.forEach((file, index) => { const d = document.createElement('div'); d.className = 'relative w-24 h-24 rounded-2xl overflow-hidden flex-shrink-0 border-2 border-white shadow-md group'; d.innerHTML = `
图${index + 1}
`; prev.appendChild(d); const reader = new FileReader(); reader.onload = (ev) => { d.innerHTML = `
图${index + 1}
`; lucide.createIcons(); }; reader.readAsDataURL(file); }); } function removeUploadedFile(index) { uploadedFiles.splice(index, 1); renderImagePreviews(); } function handleNewFiles(files) { const newFiles = Array.from(files).filter(f => f.type.startsWith('image/')); if (newFiles.length === 0) return; if (uploadedFiles.length + newFiles.length > 3) { showToast('最多只能上传 3 张照片', 'warning'); const remaining = 3 - uploadedFiles.length; if (remaining > 0) { uploadedFiles = uploadedFiles.concat(newFiles.slice(0, remaining)); } } else { uploadedFiles = uploadedFiles.concat(newFiles); } renderImagePreviews(); } document.getElementById('fileInput').onchange = (e) => { handleNewFiles(e.target.files); e.target.value = ''; }; // 粘贴上传逻辑 document.addEventListener('paste', (e) => { const items = (e.clipboardData || e.originalEvent.clipboardData).items; const files = []; for (let item of items) { if (item.type.indexOf('image') !== -1) { files.push(item.getAsFile()); } } if (files.length > 0) { handleNewFiles(files); } }); // === 高度定制的复制逻辑 === function generateCopyString(data) { const r = data.right; const l = data.left; const tPd = data.total_pd; const blocks = []; const fmtDeg = (val) => { if (!val || val === "未标明" || val.trim() === "") return null; const num = parseFloat(val); if (isNaN(num)) return null; const deg = Math.round(num * 100); if (deg === 0) return null; if (deg < 0) return Math.abs(deg).toString(); if (deg > 0) return "+" + deg.toString(); return null; }; const fmtCyl = (val) => fmtDeg(val); const fmtRaw = (val) => (!val || val === "未标明" || val.trim() === "") ? null : val.trim(); const fmtPD = (val) => { if (!val || val === "未标明" || val.trim() === "") return null; const num = parseFloat(val); if (isNaN(num)) return val.trim(); return num.toString(); }; const rS = fmtDeg(r.s), lS = fmtDeg(l.s); const rC = fmtCyl(r.c), lC = fmtCyl(l.c); const rA = rC ? fmtRaw(r.a) : null, lA = lC ? fmtRaw(l.a) : null; const rAdd = fmtDeg(r.add), lAdd = fmtDeg(l.add); const rPH = fmtRaw(r.ph), lPH = fmtRaw(l.ph); const rPd = fmtPD(r.pd), lPd = fmtPD(l.pd), totalPd = fmtPD(tPd); if (!rS && !rC && !lS && !lC) return "平光"; const isDoubleSame = (rS === lS) && (rC === lC) && (rA === lA); if (isDoubleSame) { let block = `双眼${rS || ""}`; if (rC) block += `散光${rC}`; if (rA) block += `轴位${rA}`; blocks.push(block); } else { if (rS || rC) { let block = `右眼${rS || ""}`; if (rC) block += `散光${rC}`; if (rA) block += `轴位${rA}`; blocks.push(block); } if (lS || lC) { let block = `左眼${lS || ""}`; if (lC) block += `散光${lC}`; if (lA) block += `轴位${lA}`; blocks.push(block); } } if (rAdd && lAdd && rAdd === lAdd) blocks.push(`ADD${rAdd}`); else { if (rAdd) blocks.push(`ADD${rAdd}`); if (lAdd) blocks.push(`ADD${lAdd}`); } if (rPH) blocks.push(`瞳高右眼${rPH}`); if (lPH) blocks.push(`瞳高左眼${lPH}`); if (totalPd) blocks.push(`瞳距${totalPd}`); else { if (rPd) blocks.push(`右眼瞳距${rPd}`); if (lPd) blocks.push(`左眼瞳距${lPd}`); } return blocks.join(' '); } async function copyToClipboard(text) { try { await navigator.clipboard.writeText(text); return true; } catch (err) { console.warn("现代复制API失败,尝试传统方法:", err); try { const textarea = document.createElement('textarea'); textarea.value = text; textarea.style.position = 'fixed'; textarea.style.left = '-9999px'; textarea.style.top = '0'; textarea.setAttribute('readonly', ''); document.body.appendChild(textarea); textarea.focus(); textarea.select(); const successful = document.execCommand('copy'); document.body.removeChild(textarea); return successful; } catch (e) { return false; } } } const dropZone = document.getElementById('dropZone'); if (dropZone) { ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(name => { dropZone.addEventListener(name, (e) => { e.preventDefault(); e.stopPropagation(); }, false); }); dropZone.addEventListener('drop', (e) => { handleNewFiles(e.dataTransfer.files); }, false); } document.getElementById('isPremium').onchange = () => updateCostPreview(); document.getElementById('submitBtn').onclick = async () => { const btn = document.getElementById('submitBtn'); const authCheck = await fetch('/api/auth/me'); const authData = await authCheck.json(); if(!authData.logged_in) return showToast('请先登录', 'warning'); if(uploadedFiles.length === 0) return showToast('请上传验光单照片', 'warning'); const isPremium = document.getElementById('isPremium')?.checked || false; const cost = isPremium ? 2 : 1; if(authData.points < cost) return showToast('积分不足', 'warning'); btn.disabled = true; const btnText = btn.querySelector('span'); btnText.innerText = "AI 正在深度解读中..."; document.getElementById('statusInfo').classList.remove('hidden'); document.getElementById('placeholder').classList.add('hidden'); document.getElementById('finalWrapper').classList.remove('hidden'); const textResult = document.getElementById('textResult'); textResult.innerHTML = '
正在提取光学数据...
'; lucide.createIcons(); try { let image_urls = []; const uploadData = new FormData(); for(let f of uploadedFiles) uploadData.append('images', f); const upR = await fetch('/api/upload', { method: 'POST', body: uploadData }); const upRes = await upR.json(); if(upRes.error) throw new Error(upRes.error); image_urls = upRes.urls; const messages = [ { role: "system", content: AppPrompts.systemMessage }, { role: "user", content: [ { type: "text", text: "请解读这张验光单。" } ] } ]; image_urls.forEach(url => { messages[1].content.push({ type: "image_url", image_url: { url: url } }); }); const r = await fetch('/api/generate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mode: 'trial', is_premium: isPremium, prompt: "解读验光单", model: FIXED_MODEL, messages: messages, image_urls: image_urls }) }); const res = await r.json(); if(res.error) throw new Error(res.error); const content = res.data[0].content; // 分离正文和 JSON 数据 let displayMarkdown = content; let jsonData = null; const jsonMatch = content.match(/```json\n?([\s\S]*?)\n?```/); if (jsonMatch && jsonMatch[1]) { try { jsonData = JSON.parse(jsonMatch[1].trim()); // 实时解析 Markdown (过滤 JSON 块) displayMarkdown = content.replace(/```json[\s\S]*?```/g, '').trim(); } catch (e) { console.error("JSON 解析失败", e); } } // 渲染 Markdown marked.setOptions({ gfm: true, breaks: true, tables: true, headerIds: true, mangle: false }); textResult.innerHTML = marked.parse(displayMarkdown); // 处理复制按钮逻辑 const actionBtn = document.getElementById('resultActions'); const copyBtn = document.getElementById('copyJsonBtn'); if (jsonData && actionBtn && copyBtn) { actionBtn.classList.remove('hidden'); copyBtn.onclick = async () => { const copyString = generateCopyString(jsonData); const success = await copyToClipboard(copyString); if (success) { showToast('解析数据已成功复制', 'success'); } else { showToast('复制失败,请手动选择', 'error'); } }; } checkAuth(); } catch (e) { showToast('解读失败: ' + e.message, 'error'); textResult.innerHTML = `
解读失败: ${e.message}
`; } finally { btn.disabled = false; btnText.innerText = "开始解读验光单"; document.getElementById('statusInfo').classList.add('hidden'); } }; async function init() { checkAuth(); updateCostPreview(); } function showToast(message, type = 'info') { const container = document.getElementById('toastContainer'); if(!container) { // 如果在 iframe 中,尝试调用父页面的 toast if(window.parent && window.parent.showToast) { window.parent.showToast(message, type); return; } alert(message); return; } const toast = document.createElement('div'); toast.className = `toast ${type}`; const colors = { success: 'text-emerald-500', error: 'text-rose-500', warning: 'text-amber-500', info: 'text-indigo-500' }; const icons = { success: 'check-circle', error: 'x-circle', warning: 'alert-triangle', info: 'info' }; toast.innerHTML = `${message}`; container.appendChild(toast); lucide.createIcons(); setTimeout(() => { toast.style.animation = 'slideOutRight 0.3s ease-in'; setTimeout(() => toast.remove(), 300); }, 3000); } init();