// ========================================== // 本地导出实现方案 (基于1688插件方式) // ========================================== /** * 导出配置 */ interface ExportConfig { downloadType: '1' | '2'; // 1=平铺, 2=分组到子文件夹 includeJson: boolean; // 是否导出product.json } /** * 导出素材到本地 * 在 background.ts 中实现 */ async function exportToLocal( folderName: string, materials: { texts: TextMaterial[]; images: ImageMaterial[]; }, config: ExportConfig ) { const downloadTasks: Promise[] = []; // 1. 导出图片 for (const img of materials.images) { const groupFolder = config.downloadType === '2' ? img.groupName : ''; // 文件名: 分组key-索引-规格名(可选).扩展名 const ext = img.url.split('.').pop()?.split('?')[0] || 'jpg'; let filename = `${img.groupKey}-${String(img.index).padStart(3, '0')}`; if (img.variantName) { filename += `-${img.variantName}`; } filename += `.${ext}`; // 构建完整路径: 商品名/分组/文件名 const path = [folderName, groupFolder, filename] .filter(Boolean) .join('/'); downloadTasks.push( chrome.downloads.download({ url: img.url, filename: path, conflictAction: 'uniquify', saveAs: false }).then(() => { console.log(`Downloaded: ${path}`); }) ); } // 2. 导出product.json (Ozon API格式) if (config.includeJson) { const productData = buildOzonProductJson(materials); const jsonBlob = new Blob( [JSON.stringify(productData, null, 2)], { type: 'application/json' } ); const jsonUrl = URL.createObjectURL(jsonBlob); downloadTasks.push( chrome.downloads.download({ url: jsonUrl, filename: `${folderName}/product.json`, conflictAction: 'overwrite', saveAs: false }).then(() => { URL.revokeObjectURL(jsonUrl); }) ); } // 等待所有下载完成 await Promise.allSettled(downloadTasks); return { total: downloadTasks.length, folder: folderName }; } /** * 构建Ozon API格式的JSON * 参考: https://docs.ozon.ru/api/seller/zh/#operation/ProductAPI_ImportProductsV3 */ function buildOzonProductJson(materials: { texts: TextMaterial[]; images: ImageMaterial[]; }): OzonProductImport { const title = materials.texts.find(t => t.kind === 'title')?.content || ''; const desc = materials.texts.find(t => t.kind === 'desc')?.content || ''; const params = materials.texts.find(t => t.kind === 'params'); // 图片URL按分组整理 const mainImages = materials.images .filter(img => img.groupKey === 'main') .map(img => img.url); const skuImages = materials.images .filter(img => img.groupKey === 'sku') .reduce((acc, img) => { if (img.variantName) { acc[img.variantName] = img.url; } return acc; }, {} as Record); return { items: [{ // 基础信息 name: title, description: desc, offer_id: '', // 需要用户填写 // 图片 images: mainImages, color_image: skuImages[Object.keys(skuImages)[0]] || '', // 参数 (简化版,实际需要映射到Ozon类目属性) attributes: params?.pairs?.map(p => ({ complex_id: 0, id: 0, // 需要查询Ozon类目属性字典 values: [{ value: p.value }] })) || [], // 尺寸重量 (需要从参数中提取或用户填写) height: 0, width: 0, depth: 0, dimension_unit: 'cm', weight: 0, weight_unit: 'g' }] }; } /** * 消息处理: 导出命令 */ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { if (msg.name === 'export-to-local') { exportToLocal( msg.payload.folderName, msg.payload.materials, msg.payload.config ) .then(result => sendResponse({ ok: true, data: result })) .catch(error => sendResponse({ ok: false, error: error.message })); return true; // 保持异步通道 } }); // ========================================== // Manifest配置 // ========================================== /* { "optional_permissions": [ "downloads" // 放在optional中,首次导出时才申请 ], "host_permissions": [ "https://www.ozon.ru/*", "https://cdn*.ozon.ru/*" // 图片CDN ] } */ // ========================================== // Side Panel UI - 导出操作 // ========================================== /*

导出选项

文件将保存到: ~/Downloads/[商品名]/

*/ async function handleExport() { // 1. 首次使用时请求downloads权限 const hasPermission = await chrome.permissions.contains({ permissions: ['downloads'] }); if (!hasPermission) { const granted = await chrome.permissions.request({ permissions: ['downloads'] }); if (!granted) { alert('需要下载权限才能导出文件'); return; } } // 2. 获取当前文件夹数据 const materials = await getCurrentFolderMaterials(); const folderName = cleanFilename(materials.title || '未命名商品'); // 3. 发送导出消息到background const result = await chrome.runtime.sendMessage({ name: 'export-to-local', payload: { folderName, materials, config: { downloadType: document.querySelector('input[name="exportType"]:checked').value, includeJson: document.querySelector('input[name="includeJson"]').checked } } }); if (result.ok) { alert(`成功导出 ${result.data.total} 个文件到:\n~/Downloads/${result.data.folder}/`); } } /** * 清理文件名中的非法字符 */ function cleanFilename(name: string): string { return name .replace(/[<>:"/\\|?*]/g, '_') // Windows非法字符 .replace(/\s+/g, '_') // 空格替换为下划线 .substring(0, 100); // 限制长度 } // ========================================== // 类型定义 // ========================================== interface TextMaterial { kind: 'title' | 'params' | 'desc' | 'price'; content: string; pairs?: Array<{ key: string; value: string }>; } interface ImageMaterial { groupKey: 'main' | 'sku' | 'detail' | 'video'; groupName: string; variantName?: string; // SKU规格名 url: string; index: number; } interface OzonProductImport { items: Array<{ name: string; description: string; offer_id: string; images: string[]; color_image: string; attributes: Array<{ complex_id: number; id: number; values: Array<{ dictionary_value_id?: number; value?: string; }>; }>; height: number; width: number; depth: number; dimension_unit: string; weight: number; weight_unit: string; }>; }