feat: 添加飞书同步功能,增强数据采集工具

在产品历史KOC助手中新增飞书同步功能,允许用户将采集到的数据同步到飞书多维表格。此更新包括飞书应用ID、密钥及表格ID的配置选项,支持对已存在记录的检查与批量上传,提升了数据管理的灵活性和效率。同时,优化了数据筛选条件,确保采集的数据符合用户需求。
This commit is contained in:
intelligrow 2025-06-24 17:22:30 +08:00
parent 805d3037e6
commit ce078e9da6

View File

@ -14,12 +14,13 @@
// @grant GM_setClipboard // @grant GM_setClipboard
// @connect chanmama.com // @connect chanmama.com
// @connect api-service.chanmama.com // @connect api-service.chanmama.com
// @require https://cdn.jsdelivr.net/npm/sweetalert2@11 // @connect open.feishu.cn
// @require https://unpkg.com/sweetalert2@11
// @license MIT // @license MIT
// ==/UserScript== // ==/UserScript==
/* ==UserConfig== /* ==UserConfig==
settings: basic:
enabled: enabled:
title: 启用功能 title: 启用功能
description: 是否启用蝉妈妈产品历史KOC助手功能 description: 是否启用蝉妈妈产品历史KOC助手功能
@ -38,23 +39,94 @@ settings:
description: 启用详细日志输出 description: 启用详细日志输出
type: checkbox type: checkbox
default: false default: false
---
feishu:
enableSync:
title: 启用飞书同步
description: 是否将采集到的数据同步到飞书多维表格
type: checkbox
default: false
appId:
title: 飞书应用ID
description: 飞书开放平台应用的App ID
type: text
default: "cli_a73b734d0564d01c"
appSecret:
title: 飞书应用密钥
description: 飞书开放平台应用的App Secret
type: text
default: "xeKSIOpQiVW6oHeJPH4EvlZMZ1QmnCxx"
password: true
bitableAppId:
title: 多维表格App ID
description: 飞书多维表格的App ID
type: text
default: "ILIYb7MUCatbfNsdUMccHsgjnLf"
tableId:
title: 数据表ID
description: 存储KOC数据的表格ID
type: text
default: "tblK71li9kOfOc7C"
filters:
amountThreshold:
title: 金额门槛
description: 预估关联商品视频销售金额的最低门槛低于此值停止采集
type: number
default: 10000
videoRatioThreshold:
title: 视频带货占比门槛
description: 视频销售金额占总销售金额的比例门槛如0.5表示视频销售需占总销售的50%以上
type: number
default: 0.5
maxFollowers:
title: 粉丝数上限
description: 达人粉丝数上限只采集粉丝数少于此数值的达人
type: number
default: 500000
==/UserConfig== */ ==/UserConfig== */
(function() { (function() {
'use strict'; 'use strict';
// 检测依赖库加载状态
function checkLibraryStatus() {
if (typeof Swal !== 'undefined') {
log('SweetAlert2 库加载成功');
} else {
log('SweetAlert2 库未加载,将使用备用方案', 'warn');
}
}
// 配置常量 // 配置常量
const CONFIG = { const CONFIG = {
debug: true, // 强制开启调试模式 debug: GM_getValue('basic.debug', false),
enabled: GM_getValue('settings.enabled', true), enabled: GM_getValue('basic.enabled', true),
timeout: GM_getValue('settings.timeout', 30) * 1000, timeout: GM_getValue('basic.timeout', 30) * 1000,
api: { api: {
baseUrl: 'https://api-service.chanmama.com', baseUrl: 'https://api-service.chanmama.com',
endpoint: '/v1/product/author/analysis' endpoint: '/v1/product/author/analysis'
}, },
pagination: { pagination: {
pageSize: 30, pageSize: 30,
amountThreshold: 10000 // 金额门槛,低于此值停止翻页 amountThreshold: GM_getValue('filters.amountThreshold', 10000)
},
filters: {
videoRatioThreshold: GM_getValue('filters.videoRatioThreshold', 0.5),
maxFollowers: GM_getValue('filters.maxFollowers', 500000)
},
feishu: {
enabled: GM_getValue('feishu.enableSync', false),
appId: GM_getValue('feishu.appId', ''),
appSecret: GM_getValue('feishu.appSecret', ''),
bitableAppId: GM_getValue('feishu.bitableAppId', ''),
tableId: GM_getValue('feishu.tableId', ''),
urls: {
tenantAccessToken: "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",
bitableBatchCreate: (appId, tableId) =>
`https://open.feishu.cn/open-apis/bitable/v1/apps/${appId}/tables/${tableId}/records/batch_create`,
bitableSearchRecords: (appId, tableId) =>
`https://open.feishu.cn/open-apis/bitable/v1/apps/${appId}/tables/${tableId}/records/search`
}
} }
}; };
@ -88,6 +160,13 @@ settings:
timeout: CONFIG.timeout, timeout: CONFIG.timeout,
anonymous: false, anonymous: false,
onload: (response) => { onload: (response) => {
// 调试信息:记录详细的请求和响应
if (CONFIG.debug) {
log(`请求URL: ${url}`);
log(`响应状态: ${response.status} ${response.statusText}`);
log(`响应内容: ${response.responseText.substring(0, 500)}...`);
}
if (response.status >= 200 && response.status < 300) { if (response.status >= 200 && response.status < 300) {
try { try {
const data = JSON.parse(response.responseText); const data = JSON.parse(response.responseText);
@ -96,7 +175,16 @@ settings:
resolve(response.responseText); resolve(response.responseText);
} }
} else { } else {
reject(new Error(`HTTP ${response.status}: ${response.statusText}`)); // 解析错误响应
let errorDetail = response.statusText;
try {
const errorData = JSON.parse(response.responseText);
errorDetail = errorData.msg || errorData.message || errorData.error || response.statusText;
} catch (e) {
// 如果无法解析JSON使用原始响应
errorDetail = response.responseText || response.statusText;
}
reject(new Error(`HTTP ${response.status}: ${errorDetail}`));
} }
}, },
onerror: () => reject(new Error('网络请求失败')), onerror: () => reject(new Error('网络请求失败')),
@ -104,69 +192,308 @@ settings:
}); });
}); });
} }
// SweetAlert2 弹窗函数 // 重试函数
const retry = async (fn, retries = 3, delay = 1000) => {
for (let i = 0; i < retries; i++) {
try {
return await fn();
} catch (error) {
if (i < retries - 1) {
log(`重试中... (${i + 1}/${retries})`, 'warn');
await new Promise((resolve) => setTimeout(resolve, delay));
} else {
throw error;
}
}
}
};
// 飞书API相关函数
async function fetchFeishuTenantAccessToken() {
if (!CONFIG.feishu.appId || !CONFIG.feishu.appSecret) {
throw new Error('飞书应用ID或密钥未配置');
}
log(`正在获取飞书访问令牌App ID: ${CONFIG.feishu.appId.substring(0, 8)}...`);
try {
const response = await retry(() => makeRequest(
CONFIG.feishu.urls.tenantAccessToken,
{
method: 'POST',
data: JSON.stringify({
app_id: CONFIG.feishu.appId,
app_secret: CONFIG.feishu.appSecret,
})
}
));
log(`飞书API响应: code=${response.code}, msg=${response.msg || '无'}`);
if (response.code !== 0) {
throw new Error(`获取飞书访问令牌失败: ${response.msg || response.message || '未知错误'}`);
}
if (!response.tenant_access_token) {
throw new Error('飞书返回的访问令牌为空');
}
log('飞书访问令牌获取成功');
return response.tenant_access_token;
} catch (error) {
log(`获取飞书访问令牌失败: ${error.message}`, 'error');
log(`请检查1.应用ID和密钥是否正确 2.应用是否有多维表格权限 3.网络连接是否正常`, 'error');
throw error;
}
}
/**
* 获取飞书表格中所有已存在的达人UID支持自动翻页
*/
async function getExistingKOCUIDs(accessToken) {
try {
const url = CONFIG.feishu.urls.bitableSearchRecords(CONFIG.feishu.bitableAppId, CONFIG.feishu.tableId);
const headers = {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
};
const existingUIDs = new Set();
let hasMore = true;
let pageToken = null;
let pageCount = 0;
log('开始获取飞书表格中已存在的达人UID...');
while (hasMore) {
pageCount++;
log(`获取第 ${pageCount} 页已存在的UID...`);
// 构造请求体
const requestBody = {
field_names: ['达人UID'], // 只获取达人UID字段
automatic_fields: false
};
// 构造URL参数
let requestUrl = url + '?page_size=500&user_id_type=open_id';
if (pageToken) {
requestUrl += `&page_token=${encodeURIComponent(pageToken)}`;
}
const response = await retry(() => makeRequest(requestUrl, {
method: 'POST',
data: JSON.stringify(requestBody),
headers: headers
}));
if (response.code !== 0) {
throw new Error(`获取已存在UID失败: ${response.msg || response.message || '未知错误'}`);
}
const data = response.data || {};
const items = data.items || [];
// 提取UID - 处理富文本格式
items.forEach(item => {
const fields = item.fields || {};
const uidField = fields['达人UID'];
let uid = '';
if (uidField) {
if (Array.isArray(uidField) && uidField.length > 0) {
// 处理富文本格式 [{"text": "值", "type": "text"}]
for (const item of uidField) {
if (item.type === 'text' && item.text) {
uid += item.text;
}
}
} else if (typeof uidField === 'string') {
// 处理普通文本格式
uid = uidField;
}
if (uid) {
existingUIDs.add(uid);
}
}
});
log(`${pageCount} 页获取到 ${items.length} 条记录`);
// 检查是否还有更多数据
hasMore = data.has_more || false;
pageToken = data.page_token || null;
// 防止无限循环
if (pageCount > 1000) {
log('达到最大翻页限制,停止获取', 'warn');
break;
}
// 批次间延迟,避免频率限制
if (hasMore) {
await new Promise(resolve => setTimeout(resolve, 200));
}
}
log(`总共获取 ${pageCount} 页,找到 ${existingUIDs.size} 个已存在的达人UID`);
return existingUIDs;
} catch (error) {
log(`获取已存在UID失败: ${error.message}`, 'error');
// 如果获取失败,返回空集合,继续处理
return new Set();
}
}
async function batchUploadKOCRecordsToFeishu(accessToken, kocDataList) {
try {
const url = CONFIG.feishu.urls.bitableBatchCreate(CONFIG.feishu.bitableAppId, CONFIG.feishu.tableId);
const headers = {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
};
log(`批量上传URL: ${url}`);
log(`上传记录数: ${kocDataList.length}`);
// 构造批量记录数据
const records = kocDataList.map(kocData => ({
fields: {
'昵称': kocData.昵称,
'达人UID': kocData.达人UID,
'粉丝数': kocData.粉丝数,
'类型': kocData.类型,
'口碑分': kocData.口碑分,
'关联商品销售量区间': kocData.关联商品销售量区间,
'关联商品销售金额区间': kocData.关联商品销售金额区间,
'预估关联商品销售量': kocData.预估关联商品销售量,
'预估关联商品销售金额': kocData.预估关联商品销售金额,
'预估关联商品视频销售量': kocData.预估关联商品视频销售量,
'预估关联商品视频销售金额': kocData.预估关联商品视频销售金额,
'视频带货占比': kocData.视频带货占比,
'关联商品蝉妈妈ID': kocData.关联商品蝉妈妈ID,
'采集时间': new Date().getTime(), // 毫秒级时间戳
}
}));
// 分批上传每批最多100条记录
const batchSize = 100;
const batches = [];
for (let i = 0; i < records.length; i += batchSize) {
batches.push(records.slice(i, i + batchSize));
}
log(`分为 ${batches.length} 批上传,每批最多 ${batchSize} 条记录`);
let totalUploaded = 0;
for (let i = 0; i < batches.length; i++) {
const batch = batches[i];
const body = JSON.stringify({ records: batch });
log(`上传第 ${i + 1}/${batches.length} 批,包含 ${batch.length} 条记录`);
const response = await retry(() => makeRequest(url, {
method: 'POST',
data: body,
headers: headers
}));
if (response.code !== 0) {
throw new Error(`批量上传到飞书失败: ${response.msg || response.message || '未知错误'}`);
}
totalUploaded += batch.length;
log(`${i + 1} 批上传成功: ${batch.length} 条记录`);
// 批次间延迟500ms避免频率限制
if (i < batches.length - 1) {
await new Promise(resolve => setTimeout(resolve, 500));
}
}
log(`批量上传完成: 总共 ${totalUploaded} 条记录`);
return { code: 0, uploaded: totalUploaded };
} catch (error) {
// 特殊处理403权限错误
if (error.message.includes('HTTP 403')) {
log('HTTP 403权限错误诊断:', 'error');
log(`- App ID: ${CONFIG.feishu.appId}`, 'error');
log(`- Bitable App ID: ${CONFIG.feishu.bitableAppId}`, 'error');
log(`- Table ID: ${CONFIG.feishu.tableId}`, 'error');
log('请检查以下几点:', 'error');
log('1. 飞书应用是否有多维表格权限', 'error');
log('2. App ID和App Secret是否正确', 'error');
log('3. 多维表格App ID和表格ID是否正确', 'error');
log('4. 应用是否已发布并获得权限', 'error');
throw new Error(`飞书权限错误 (403): 请检查应用权限配置`);
}
log(`批量上传到飞书失败: ${error.message}`, 'error');
throw error;
}
}
// 弹窗函数 - 带 SweetAlert2 备用方案
function showInfo(title, text) { function showInfo(title, text) {
Swal.fire({ if (typeof Swal !== 'undefined') {
title: title, Swal.fire({
text: text, title: title,
icon: 'info', text: text,
confirmButtonText: '确定' icon: 'info',
}); confirmButtonText: '确定'
});
} else {
alert(`${title}\n${text}`);
log(`Info: ${title} - ${text}`);
}
} }
function showSuccess(title, text) { function showSuccess(title, text) {
Swal.fire({ if (typeof Swal !== 'undefined') {
title: title, Swal.fire({
text: text, title: title,
icon: 'success', text: text,
timer: 3000, icon: 'success',
showConfirmButton: false timer: 3000,
}); showConfirmButton: false
});
} else {
showToast(`${title}: ${text}`, 'success');
log(`Success: ${title} - ${text}`);
}
} }
function showError(title, text) { function showError(title, text) {
Swal.fire({ if (typeof Swal !== 'undefined') {
title: title, Swal.fire({
text: text, title: title,
icon: 'error', text: text,
confirmButtonText: '确定' icon: 'error',
}); confirmButtonText: '确定'
});
} else {
alert(`错误: ${title}\n${text}`);
log(`Error: ${title} - ${text}`, 'error');
}
} }
async function showConfirm(title, text) { async function showConfirm(title, text) {
const result = await Swal.fire({ if (typeof Swal !== 'undefined') {
title: title, const result = await Swal.fire({
text: text, title: title,
icon: 'question', text: text,
showCancelButton: true, icon: 'question',
confirmButtonText: '确定', showCancelButton: true,
cancelButtonText: '取消' confirmButtonText: '确定',
}); cancelButtonText: '取消'
return result.isConfirmed;
}
// 数据导出函数
function exportToJSON(data, filename = 'koc_history') {
// JSON导出逻辑
log('导出JSON格式数据');
const jsonStr = JSON.stringify(data, null, 2);
GM_setClipboard(jsonStr);
showSuccess('导出成功', 'JSON数据已复制到剪贴板');
}
// KOC数据处理函数
async function fetchKOCHistory(productId) {
try {
log(`获取产品 ${productId} 的KOC历史数据`);
const response = await makeRequest(`${CONFIG.api.baseUrl}${CONFIG.api.endpoint}`, {
method: 'POST',
data: JSON.stringify({ productId })
}); });
return response; return result.isConfirmed;
} catch (error) { } else {
log(`获取KOC历史数据失败: ${error.message}`, 'error'); return confirm(`${title}\n${text}`);
throw error;
} }
} }
@ -240,6 +567,12 @@ settings:
baseInfo.预估关联商品视频销售量 = totalVideoVolume; baseInfo.预估关联商品视频销售量 = totalVideoVolume;
baseInfo.预估关联商品视频销售金额 = totalVideoAmount; baseInfo.预估关联商品视频销售金额 = totalVideoAmount;
baseInfo.关联商品蝉妈妈ID = Array.from(videoProductIds).join(','); baseInfo.关联商品蝉妈妈ID = Array.from(videoProductIds).join(',');
// 计算视频带货占比限制最大值为1
const totalAmount = baseInfo.预估关联商品销售金额 || 0;
let videoRatio = totalAmount > 0 ? totalVideoAmount / totalAmount : 0;
videoRatio = Math.min(videoRatio, 1); // 限制最大值为1
baseInfo.视频带货占比 = videoRatio;
results.push(baseInfo); results.push(baseInfo);
}); });
@ -270,6 +603,14 @@ settings:
<div class="koc-status-indicator"></div> <div class="koc-status-indicator"></div>
<span class="koc-status-text">准备采集...</span> <span class="koc-status-text">准备采集...</span>
</div> </div>
${CONFIG.feishu.enabled ? `
<div class="koc-feishu-status">
<svg class="koc-feishu-icon" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.94-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/>
</svg>
<span class="koc-feishu-text">飞书同步已启用</span>
</div>
` : ''}
<div class="koc-actions"> <div class="koc-actions">
<button class="koc-download-btn" type="button"> <button class="koc-download-btn" type="button">
<svg class="koc-btn-icon" viewBox="0 0 24 24" fill="currentColor"> <svg class="koc-btn-icon" viewBox="0 0 24 24" fill="currentColor">
@ -456,6 +797,30 @@ settings:
font-weight: 500; font-weight: 500;
} }
.koc-feishu-status {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 12px;
background: rgba(59, 130, 246, 0.05);
border-radius: 6px;
border: 1px solid rgba(59, 130, 246, 0.1);
margin-bottom: 12px;
}
.koc-feishu-icon {
width: 16px;
height: 16px;
color: #3b82f6;
flex-shrink: 0;
}
.koc-feishu-text {
font-size: 11px;
color: #3b82f6;
font-weight: 500;
}
.koc-floating-window.dragging { .koc-floating-window.dragging {
transition: none !important; transition: none !important;
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15); box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
@ -577,7 +942,7 @@ settings:
function extractProductIdFromUrl() { function extractProductIdFromUrl() {
const url = window.location.href; const url = window.location.href;
const match = url.match(/promotionRank\/([^\.]+)\.html/); const match = url.match(/promotionRank\/([^\.]+)\.html/);
return match ? match[1] : "MUJ0VtooAuIRKA33vMWQN2QUo4A016WL"; return match ? match[1] : "";
} }
// 计算时间范围近365天T-1结束 // 计算时间范围近365天T-1结束
@ -644,19 +1009,45 @@ settings:
break; break;
} }
// 检查金额门槛 - 使用预估关联商品视频销售金额的最小值 // 应用筛选条件
const amountValues = parsedData.map(author => author.预估关联商品视频销售金额 || 0); const filteredData = parsedData.filter(author => {
const minAmount = Math.min(...amountValues); // 检查金额门槛
log(`${currentPage} 页最小金额: ${minAmount}`, 'debug'); if (author.预估关联商品视频销售金额 < CONFIG.pagination.amountThreshold) {
log(`筛选掉达人 ${author.昵称}: 视频销售金额 ${author.预估关联商品视频销售金额} < ${CONFIG.pagination.amountThreshold}`, 'debug');
return false;
}
// 检查粉丝数上限
const followers = author.粉丝数 || 0;
if (followers >= CONFIG.filters.maxFollowers) {
log(`筛选掉达人 ${author.昵称}: 粉丝数 ${followers} >= ${CONFIG.filters.maxFollowers}`, 'debug');
return false;
}
// 检查视频带货占比(使用已计算的值)
const videoRatio = author.视频带货占比 || 0;
if (videoRatio < CONFIG.filters.videoRatioThreshold) {
log(`筛选掉达人 ${author.昵称}: 视频带货占比 ${videoRatio.toFixed(3)} < ${CONFIG.filters.videoRatioThreshold}`, 'debug');
return false;
}
return true;
});
if (minAmount < CONFIG.pagination.amountThreshold) { log(`${currentPage} 页原始数据 ${parsedData.length} 条,筛选后 ${filteredData.length}`, 'info');
// 检查金额门槛 - 使用预估关联商品销售金额的最大值来决定是否停止采集
const amountValues = parsedData.map(author => author.预估关联商品销售金额 || 0);
const maxAmount = Math.max(...amountValues);
log(`${currentPage} 页最大金额: ${maxAmount}`, 'debug');
if (maxAmount < CONFIG.pagination.amountThreshold) {
log(`达到金额门槛 ${CONFIG.pagination.amountThreshold},停止采集`, 'info'); log(`达到金额门槛 ${CONFIG.pagination.amountThreshold},停止采集`, 'info');
// 过滤掉低于门槛的数据
const filteredData = parsedData.filter(author => author.预估关联商品视频销售金额 >= CONFIG.pagination.amountThreshold);
allData = allData.concat(filteredData); allData = allData.concat(filteredData);
shouldContinue = false; shouldContinue = false;
} else { } else {
allData = allData.concat(parsedData); allData = allData.concat(filteredData);
currentPage++; currentPage++;
// 防止无限循环最多采集100页 // 防止无限循环最多采集100页
@ -676,15 +1067,98 @@ settings:
log(`=== 数据采集完成 ===`, 'info'); log(`=== 数据采集完成 ===`, 'info');
log(`总共采集 ${allData.length} 条符合条件的达人数据`, 'info'); log(`总共采集 ${allData.length} 条符合条件的达人数据`, 'info');
// 显示采集结果的toast提示
showToast(`📊 采集完成:找到 ${allData.length} 个满足条件的达人`, 'success');
// 打印完整数据到日志 // 打印完整数据到日志
log('📊 KOC数据采集结果:', 'info'); log('📊 KOC数据采集结果:', 'info');
log(`📈 共采集 ${allData.length} 条达人数据`, 'info'); log(`📈 共采集 ${allData.length} 条达人数据`, 'info');
log('📋 数据详情:', 'info'); log('📋 数据详情:', 'info');
log(JSON.stringify(allData, null, 2), 'info'); log(JSON.stringify(allData, null, 2), 'info');
// 显示成功提示 // 飞书同步处理
setStatus(floatingWindow, 'success', `已采集 ${allData.length} 条数据`); let feishuResult = 'Success';
showSuccess('采集成功', `已采集 ${allData.length} 条符合条件的达人数据,请查看控制台`); let syncLogs = [];
let syncedCount = 0;
if (CONFIG.feishu.enabled && allData.length > 0) {
try {
log(`=== 开始同步到飞书多维表格 ===`, 'info');
setStatus(floatingWindow, 'processing', '同步到飞书中...');
// 检查飞书配置
if (!CONFIG.feishu.bitableAppId || !CONFIG.feishu.tableId) {
throw new Error('飞书多维表格配置不完整,请检查配置');
}
const accessToken = await fetchFeishuTenantAccessToken();
syncLogs.push('飞书访问令牌获取成功');
setStatus(floatingWindow, 'processing', '检查已存在记录...');
// 获取已存在的达人UID
const existingUIDs = await getExistingKOCUIDs(accessToken);
syncLogs.push(`查询到 ${existingUIDs.size} 个已存在的达人UID`);
// 过滤掉已存在的记录
const newRecords = allData.filter(kocData => !existingUIDs.has(kocData.达人UID));
const skippedCount = allData.length - newRecords.length;
log(`原始数据 ${allData.length} 条,已存在 ${skippedCount} 条,需要上传 ${newRecords.length} 条新记录`);
syncLogs.push(`总计 ${allData.length} 条记录,跳过 ${skippedCount} 条已存在,上传 ${newRecords.length} 条新记录`);
if (newRecords.length > 0) {
setStatus(floatingWindow, 'processing', `批量上传 ${newRecords.length} 条新记录...`);
const result = await batchUploadKOCRecordsToFeishu(accessToken, newRecords);
syncedCount = result.uploaded || newRecords.length;
syncLogs.push(`✅ 批量上传完成: ${syncedCount} 条记录`);
setStatus(floatingWindow, 'success', `上传完成: ${syncedCount} 条新记录`);
// 显示上传结果的toast提示
showToast(`📤 飞书同步:${skippedCount} 个重复跳过,${syncedCount} 个新增写入`, 'info');
} else {
syncLogs.push('✅ 没有需要上传的新记录');
setStatus(floatingWindow, 'success', '所有记录均已存在,无需上传');
// 显示全部重复的toast提示
showToast(`📋 飞书同步:${allData.length} 个达人均已存在,无新增记录`, 'warning');
}
log(`=== 飞书同步完成 ===`, 'info');
log(`成功同步 ${syncedCount} 条新数据到飞书多维表格`, 'info');
} catch (error) {
feishuResult = 'Failed';
syncLogs.push(`❌ 飞书同步失败: ${error.message}`);
log(`飞书同步失败: ${error.message}`, 'error');
setStatus(floatingWindow, 'error', '飞书同步失败');
showError('飞书同步失败', error.message);
// 显示同步失败的toast提示
showToast(`❌ 飞书同步失败:${error.message}`, 'error');
}
}
// 显示最终结果
const successMessage = CONFIG.feishu.enabled
? `已采集 ${allData.length} 条数据${syncedCount > 0 ? `,同步 ${syncedCount} 条新数据到飞书` : ',所有数据均已存在'}`
: `已采集 ${allData.length} 条符合条件的达人数据,请查看控制台`;
setStatus(floatingWindow, 'success', `采集完成 (${allData.length}条)`);
showSuccess('采集成功', successMessage);
// 显示最终结果的详细toast
if (CONFIG.feishu.enabled) {
if (syncedCount > 0) {
showToast(`✅ 任务完成:采集 ${allData.length} 个达人,新增 ${syncedCount} 个到飞书`, 'success');
} else {
showToast(`✅ 任务完成:采集 ${allData.length} 个达人,全部已存在`, 'info');
}
} else {
showToast(`✅ 任务完成:采集 ${allData.length} 个符合条件的达人`, 'success');
}
// 5秒后恢复按钮状态 // 5秒后恢复按钮状态
setTimeout(() => { setTimeout(() => {
@ -705,22 +1179,6 @@ settings:
} }
} }
function getCurrentProductId() {
// 从页面URL或DOM元素中获取产品ID
const urlMatch = window.location.pathname.match(/\/product\/(\d+)/);
if (urlMatch) {
return urlMatch[1];
}
// 尝试从页面元素获取
const productElement = document.querySelector('[data-product-id]');
if (productElement) {
return productElement.getAttribute('data-product-id');
}
return null;
}
// 主逻辑 // 主逻辑
async function init() { async function init() {
if (!CONFIG.enabled) { if (!CONFIG.enabled) {
@ -729,10 +1187,30 @@ settings:
} }
try { try {
// 检测依赖库状态
checkLibraryStatus();
// 检查飞书配置
if (CONFIG.feishu.enabled) {
if (!CONFIG.feishu.appId || !CONFIG.feishu.appSecret) {
log('⚠️ 飞书同步已启用但应用ID或密钥未配置', 'warn');
showToast('飞书配置不完整请检查应用ID和密钥', 'warning');
} else if (!CONFIG.feishu.bitableAppId || !CONFIG.feishu.tableId) {
log('⚠️ 飞书多维表格配置不完整', 'warn');
showToast('飞书多维表格配置不完整请检查多维表格App ID和数据表ID', 'warning');
} else {
log('✅ 飞书同步配置完整', 'info');
}
} else {
log(' 飞书同步未启用', 'info');
}
// 输出启动信息 // 输出启动信息
log('🎯 KOC数据采集工具启动', 'info'); log('🎯 KOC数据采集工具启动', 'info');
log(`📋 筛选条件:`, 'info');
log(` - 金额门槛: ${CONFIG.pagination.amountThreshold}`, 'info');
log(` - 粉丝数上限: <${CONFIG.filters.maxFollowers.toLocaleString()}`, 'info');
log(` - 视频带货占比: ≥${(CONFIG.filters.videoRatioThreshold * 100).toFixed(1)}%`, 'info');
// 等待页面加载完成 // 等待页面加载完成
await waitForPageLoad(); await waitForPageLoad();