298 lines
11 KiB
TypeScript
298 lines
11 KiB
TypeScript
import type {
|
|
KeywordAnalysisRow,
|
|
ProductPlanningCompetitorAnalysisRecord,
|
|
ProductPlanningKeywordInsightGroup,
|
|
ProductPlanningKeywordInsightSuggestion,
|
|
ProductPlanningTaobaoItem,
|
|
} from "./types";
|
|
|
|
function formatPlatform(value: ProductPlanningTaobaoItem["platform"], isTmall: boolean) {
|
|
if (value === "jd") return "京东";
|
|
return isTmall ? "天猫" : "淘宝";
|
|
}
|
|
|
|
function formatDataSource(value: ProductPlanningTaobaoItem["dataSource"]) {
|
|
if (value === "detail-plus") return "详情增强";
|
|
if (value === "legacy") return "旧接口兜底";
|
|
if (value === "jd") return "京东接口";
|
|
return "未标记";
|
|
}
|
|
|
|
function formatPropertySummary(row: ProductPlanningTaobaoItem, limit = 10) {
|
|
return row.properties
|
|
.slice(0, limit)
|
|
.map((entry) => `${entry.name}:${entry.value}`)
|
|
.join(" | ");
|
|
}
|
|
|
|
function formatConversionRate(value?: number) {
|
|
if (typeof value !== "number") return "";
|
|
return `${(value * 100).toFixed(2)}%`;
|
|
}
|
|
|
|
function triggerBlobDownload(blob: Blob, fileName: string) {
|
|
const url = URL.createObjectURL(blob);
|
|
const anchor = document.createElement("a");
|
|
anchor.href = url;
|
|
anchor.download = fileName;
|
|
document.body.appendChild(anchor);
|
|
anchor.click();
|
|
document.body.removeChild(anchor);
|
|
URL.revokeObjectURL(url);
|
|
}
|
|
|
|
export async function downloadKeywordAnalysisWorkbook(
|
|
rows: KeywordAnalysisRow[],
|
|
baseFileName = "商品策划工具_关键词分析"
|
|
) {
|
|
const { Workbook } = await import("exceljs");
|
|
|
|
const workbook = new Workbook();
|
|
const sheet = workbook.addWorksheet("关键词拆词结果");
|
|
sheet.columns = [
|
|
{ header: "词语", key: "keyword", width: 24 },
|
|
{ header: "拆分片段", key: "segments", width: 28 },
|
|
{ header: "标签分类", key: "category", width: 16 },
|
|
{ header: "出现次数", key: "occurrenceCount", width: 12 },
|
|
{ header: "搜索人气", key: "searchPopularity", width: 14 },
|
|
{ header: "支付转化率", key: "conversionRate", width: 14 },
|
|
];
|
|
|
|
sheet.getRow(1).font = { bold: true };
|
|
sheet.views = [{ state: "frozen", ySplit: 1 }];
|
|
|
|
for (const row of rows) {
|
|
sheet.addRow({
|
|
keyword: row.keyword,
|
|
segments: row.segments.join(" / "),
|
|
category: row.category,
|
|
occurrenceCount: row.occurrenceCount,
|
|
searchPopularity: row.searchPopularity ?? "",
|
|
conversionRate: formatConversionRate(row.conversionRate),
|
|
});
|
|
}
|
|
|
|
const buffer = await workbook.xlsx.writeBuffer();
|
|
const blob = new Blob([buffer], {
|
|
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
});
|
|
triggerBlobDownload(blob, `${baseFileName}_${new Date().toISOString().slice(0, 10)}.xlsx`);
|
|
}
|
|
|
|
async function downloadWorkbook(params: {
|
|
worksheetName: string;
|
|
columns: Array<{ header: string; key: string; width: number }>;
|
|
rows: Array<Record<string, unknown>>;
|
|
fileName: string;
|
|
}) {
|
|
const { Workbook } = await import("exceljs");
|
|
|
|
const workbook = new Workbook();
|
|
const sheet = workbook.addWorksheet(params.worksheetName);
|
|
sheet.columns = params.columns;
|
|
sheet.getRow(1).font = { bold: true };
|
|
sheet.views = [{ state: "frozen", ySplit: 1 }];
|
|
|
|
for (const row of params.rows) {
|
|
sheet.addRow(row);
|
|
}
|
|
|
|
const buffer = await workbook.xlsx.writeBuffer();
|
|
const blob = new Blob([buffer], {
|
|
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
});
|
|
triggerBlobDownload(blob, `${params.fileName}_${new Date().toISOString().slice(0, 10)}.xlsx`);
|
|
}
|
|
|
|
export async function downloadCompetitorWorkbook(
|
|
rows: ProductPlanningTaobaoItem[],
|
|
baseFileName = "商品策划工具_竞品分析"
|
|
) {
|
|
await downloadWorkbook({
|
|
worksheetName: "竞品分析",
|
|
fileName: baseFileName,
|
|
columns: [
|
|
{ header: "数据来源", key: "dataSource", width: 14 },
|
|
{ header: "平台", key: "platform", width: 12 },
|
|
{ header: "商品ID", key: "itemId", width: 18 },
|
|
{ header: "请求ID", key: "requestedItemId", width: 18 },
|
|
{ header: "商品标题", key: "title", width: 40 },
|
|
{ header: "品牌", key: "brand", width: 18 },
|
|
{ header: "店铺", key: "shopName", width: 26 },
|
|
{ header: "店铺ID", key: "shopId", width: 18 },
|
|
{ header: "发货地", key: "location", width: 18 },
|
|
{ header: "类目ID", key: "categoryId", width: 18 },
|
|
{ header: "当前价格", key: "price", width: 14 },
|
|
{ header: "划线价格", key: "originalPrice", width: 14 },
|
|
{ header: "总销量", key: "totalSales", width: 14 },
|
|
{ header: "近期销量", key: "recentSales", width: 14 },
|
|
{ header: "材质", key: "material", width: 24 },
|
|
{ header: "详细材质", key: "detailedMaterial", width: 36 },
|
|
{ header: "标准品类", key: "standardProductType", width: 22 },
|
|
{ header: "市场分类", key: "marketCategoryName", width: 22 },
|
|
{ header: "价格带", key: "priceBand", width: 16 },
|
|
{ header: "SKU数量", key: "skuCount", width: 12 },
|
|
{ header: "主图数", key: "itemImageCount", width: 12 },
|
|
{ header: "详情图数", key: "detailImageCount", width: 12 },
|
|
{ header: "主图视频", key: "videoUrl", width: 36 },
|
|
{ header: "企划详情ID", key: "detailStorageRecordId", width: 14 },
|
|
{ header: "产品记录ID", key: "productRecordId", width: 14 },
|
|
{ header: "参数预览", key: "propertySummary", width: 80 },
|
|
{ header: "商品链接", key: "detailUrl", width: 48 },
|
|
{ header: "来源链接", key: "sourceUrl", width: 48 },
|
|
],
|
|
rows: rows.map((row) => ({
|
|
dataSource: formatDataSource(row.dataSource),
|
|
platform: formatPlatform(row.platform, row.isTmall),
|
|
itemId: row.itemId,
|
|
requestedItemId: row.requestedItemId || row.itemId,
|
|
title: row.title,
|
|
brand: row.brand || "",
|
|
shopName: row.shopName || "",
|
|
shopId: row.shopId || "",
|
|
location: row.location || "",
|
|
categoryId: row.categoryId || "",
|
|
price: row.price ?? "",
|
|
originalPrice: row.originalPrice ?? "",
|
|
totalSales: row.totalSales ?? "",
|
|
recentSales: row.recentSales ?? "",
|
|
material: row.material || "",
|
|
detailedMaterial: row.detailedMaterial || "",
|
|
standardProductType: row.standardProductType || "",
|
|
marketCategoryName: row.marketCategoryName || "",
|
|
priceBand: row.priceBand || "",
|
|
skuCount: row.skuCount ?? row.skus.length,
|
|
itemImageCount: row.itemImages.length,
|
|
detailImageCount: row.detailImages.length,
|
|
videoUrl: row.videoUrl || "",
|
|
detailStorageRecordId: row.detailStorageRecordId || "",
|
|
productRecordId: row.productRecordId || "",
|
|
propertySummary: formatPropertySummary(row),
|
|
detailUrl: row.detailUrl || "",
|
|
sourceUrl: row.sourceUrl || "",
|
|
})),
|
|
});
|
|
}
|
|
|
|
export async function downloadSalesWorkbook(
|
|
rows: ProductPlanningTaobaoItem[],
|
|
baseFileName = "商品策划工具_ID销量查询"
|
|
) {
|
|
await downloadWorkbook({
|
|
worksheetName: "ID销量查询",
|
|
fileName: baseFileName,
|
|
columns: [
|
|
{ header: "平台", key: "platform", width: 12 },
|
|
{ header: "商品ID", key: "itemId", width: 18 },
|
|
{ header: "商品标题", key: "title", width: 40 },
|
|
{ header: "近期销量", key: "recentSales", width: 14 },
|
|
{ header: "当前价格", key: "price", width: 14 },
|
|
{ header: "划线价格", key: "originalPrice", width: 14 },
|
|
{ header: "品牌", key: "brand", width: 18 },
|
|
{ header: "店铺", key: "shopName", width: 26 },
|
|
{ header: "发货地", key: "location", width: 18 },
|
|
{ header: "材质", key: "material", width: 22 },
|
|
{ header: "详情图数", key: "detailImageCount", width: 12 },
|
|
{ header: "参数预览", key: "propertySummary", width: 80 },
|
|
{ header: "商品链接", key: "detailUrl", width: 48 },
|
|
],
|
|
rows: rows.map((row) => ({
|
|
platform: formatPlatform(row.platform, row.isTmall),
|
|
itemId: row.itemId,
|
|
title: row.title,
|
|
recentSales: row.recentSales ?? "",
|
|
price: row.price ?? "",
|
|
originalPrice: row.originalPrice ?? "",
|
|
brand: row.brand || "",
|
|
shopName: row.shopName || "",
|
|
location: row.location || "",
|
|
material: row.material || "",
|
|
detailImageCount: row.detailImages.length,
|
|
propertySummary: formatPropertySummary(row),
|
|
detailUrl: row.detailUrl || "",
|
|
})),
|
|
});
|
|
}
|
|
|
|
export async function downloadKeywordInsightSuggestionsWorkbook(
|
|
rows: ProductPlanningKeywordInsightSuggestion[],
|
|
baseFileName = "商品策划工具_关键词洞察_搜索建议"
|
|
) {
|
|
await downloadWorkbook({
|
|
worksheetName: "搜索建议",
|
|
fileName: baseFileName,
|
|
columns: [
|
|
{ header: "排名", key: "rank", width: 10 },
|
|
{ header: "关键词", key: "keyword", width: 28 },
|
|
{ header: "权重", key: "weight", width: 16 },
|
|
],
|
|
rows: rows.map((row) => ({
|
|
rank: row.rank,
|
|
keyword: row.keyword,
|
|
weight: row.weight,
|
|
})),
|
|
});
|
|
}
|
|
|
|
export async function downloadKeywordInsightGroupsWorkbook(
|
|
rows: ProductPlanningKeywordInsightGroup[],
|
|
baseFileName = "商品策划工具_关键词洞察_标签分组"
|
|
) {
|
|
await downloadWorkbook({
|
|
worksheetName: "标签分组",
|
|
fileName: baseFileName,
|
|
columns: [
|
|
{ header: "组序号", key: "index", width: 10 },
|
|
{ header: "分组类型", key: "type", width: 20 },
|
|
{ header: "标签名称", key: "title", width: 28 },
|
|
{ header: "标签热度", key: "tagType", width: 12 },
|
|
],
|
|
rows: rows.flatMap((group) =>
|
|
group.tags.map((tag) => ({
|
|
index: group.index,
|
|
type: group.type,
|
|
title: tag.title,
|
|
tagType: tag.type === "hot" ? "热门" : "普通",
|
|
}))
|
|
),
|
|
});
|
|
}
|
|
|
|
export function downloadMarkdownFile(
|
|
markdown: string,
|
|
baseFileName = "商品策划工具_分析结果"
|
|
) {
|
|
const blob = new Blob([markdown], {
|
|
type: "text/markdown;charset=utf-8",
|
|
});
|
|
triggerBlobDownload(blob, `${baseFileName}_${new Date().toISOString().slice(0, 10)}.md`);
|
|
}
|
|
|
|
export async function downloadCompetitorAnalysisWorkbook(
|
|
records: Record<string, ProductPlanningCompetitorAnalysisRecord>,
|
|
baseFileName = "商品策划工具_竞品分析结果"
|
|
) {
|
|
const rows = Object.entries(records)
|
|
.map(([key, value]) => ({
|
|
key,
|
|
kind: value.kind === "sku" ? "SKU卖点分析" : "图片分析",
|
|
title: value.title,
|
|
updatedAt: value.updatedAt,
|
|
markdown: value.markdown,
|
|
}))
|
|
.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
|
|
|
|
await downloadWorkbook({
|
|
worksheetName: "AI分析结果",
|
|
fileName: baseFileName,
|
|
columns: [
|
|
{ header: "结果键", key: "key", width: 26 },
|
|
{ header: "分析类型", key: "kind", width: 16 },
|
|
{ header: "标题", key: "title", width: 42 },
|
|
{ header: "更新时间", key: "updatedAt", width: 24 },
|
|
{ header: "Markdown结果", key: "markdown", width: 120 },
|
|
],
|
|
rows,
|
|
});
|
|
}
|