// 本地部署版本的存储封装:通过 fetch 调用本机 Express 服务的 API, // 数据最终落盘到 server/data.json。接口形态与 Claude artifacts 的 // window.storage 保持一致(get 返回 {key, value},不存在时 reject), // 这样上层业务代码(App.jsx)几乎不需要改动。 const API_BASE = '/api/storage'; export async function storageGet(key) { const res = await fetch(`${API_BASE}/${encodeURIComponent(key)}`); if (res.status === 404) { throw new Error(`key not found: ${key}`); } if (!res.ok) { throw new Error(`storage get failed: ${res.status}`); } const data = await res.json(); return { key, value: data.value }; } export async function storageSet(key, value) { const res = await fetch(`${API_BASE}/${encodeURIComponent(key)}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ value }), }); if (!res.ok) { throw new Error(`storage set failed: ${res.status}`); } return res.json(); } // 上传一个与客户相关的沟通附件(文件本身存到磁盘,元数据由调用方存进客户记录) export async function uploadFile(customerId, file) { const fd = new FormData(); fd.append('file', file); const res = await fetch(`/api/uploads/${encodeURIComponent(customerId)}`, { method: 'POST', body: fd, }); if (!res.ok) { throw new Error(`upload failed: ${res.status}`); } return res.json(); } export async function deleteFile(customerId, fileName) { const res = await fetch(`/api/uploads/${encodeURIComponent(customerId)}/${encodeURIComponent(fileName)}`, { method: 'DELETE', }); if (!res.ok) { throw new Error(`delete failed: ${res.status}`); } return res.json(); } export async function authCheck() { const res = await fetch('/api/auth/check'); return res.json(); } export async function authLogin(username, password) { const res = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }), }); const data = await res.json().catch(() => ({})); if (!res.ok) throw new Error(data.error || 'login failed'); return data; } export async function authLogout() { const res = await fetch('/api/auth/logout', { method: 'POST' }); return res.json().catch(() => ({})); } export async function authChangePassword(currentPassword, newPassword) { const res = await fetch('/api/auth/change-password', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ currentPassword, newPassword }), }); const data = await res.json().catch(() => ({})); if (!res.ok) throw new Error(data.error || 'change password failed'); return data; } export async function getAiConfig() { const res = await fetch('/api/ai-config'); return res.json().catch(() => ({ hasKey: false })); } export async function saveAiConfig(apiKey) { const res = await fetch('/api/ai-config', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ apiKey }), }); const data = await res.json().catch(() => ({})); if (!res.ok) throw new Error(data.error || 'save failed'); return data; } export async function deleteAiConfig() { const res = await fetch('/api/ai-config', { method: 'DELETE' }); return res.json().catch(() => ({})); } export async function testAiConfig() { const res = await fetch('/api/ai-config/test', { method: 'POST' }); const data = await res.json().catch(() => ({})); if (!res.ok) throw new Error(data.error || 'test failed'); return data; } // 批量上传PDF/图片形式的产品目录,后端OCR识别+DeepSeek解析后返回结构化的产品数组。 // hints可选:{ category, brand, type },提前告诉后端这批产品大概是什么类目/品牌/品类, // 减少AI瞎猜的情况,也能省一点点解析时间。 export async function parseCatalogFiles(files, hints) { const fd = new FormData(); Array.from(files).forEach((f) => fd.append('files', f)); if (hints) { if (hints.category) fd.append('hintCategory', hints.category); if (hints.brand) fd.append('hintBrand', hints.brand); if (hints.type) fd.append('hintType', hints.type); } const res = await fetch('/api/ai/parse-catalog', { method: 'POST', body: fd, }); let data; try { data = await res.json(); } catch (e) { // 响应体不是合法JSON——常见原因:Nginx因为文件太大/请求超时直接拦截返回了HTML错误页, // 请求根本没到达Node服务。把HTTP状态码带出来,方便定位是不是这个原因。 throw new Error(`parse failed (HTTP ${res.status},响应内容不是JSON,可能是Nginx上传大小/超时限制拦截了请求)`); } if (!res.ok) throw new Error(data.error || `parse failed (HTTP ${res.status})`); return data; // { products, ocrTextLength } } // 把一批中文词汇翻译成目标语言(en/fr/ru/es/ar),返回 {原词: 译文} 的映射。 // 词条缓存由调用方(App.jsx)自己管理,这里只负责翻译本身。 export async function translateTerms(terms, targetLang) { const res = await fetch('/api/ai/translate-terms', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ terms, targetLang }), }); const data = await res.json().catch(() => ({})); if (!res.ok) throw new Error(data.error || 'translate failed'); return data.translations; }