Upload files to "/"

This commit is contained in:
mike 2026-07-24 16:15:03 +08:00
parent c1ef88567b
commit ce1b8430aa
2 changed files with 4019 additions and 0 deletions

3868
App.jsx Normal file

File diff suppressed because it is too large Load Diff

151
storage.js Normal file
View File

@ -0,0 +1,151 @@
// 本地部署版本的存储封装:通过 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;
}
export async function generateAiDraft(customerId, channel) {
const res = await fetch('/api/ai/draft', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ customerId, channel }),
});
const data = await res.json().catch(() => ({}));
if (!res.ok) throw new Error(data.error || 'generate failed');
return data;
}
// 批量上传PDF/图片形式的产品目录后端OCR识别+DeepSeek解析后返回结构化的产品数组
export async function parseCatalogFiles(files) {
const fd = new FormData();
Array.from(files).forEach((f) => fd.append('files', f));
const res = await fetch('/api/ai/parse-catalog', {
method: 'POST',
body: fd,
});
const data = await res.json().catch(() => ({}));
if (!res.ok) throw new Error(data.error || 'parse failed');
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;
}