scriptCat/chanmama/productHistoryKOCHelper.user.js
intelligrow 5c45d3523e fix: 移除调试日志以清理代码
删除了解析完成时的调试日志,旨在简化代码并提高可读性。此更改不影响功能,保持代码整洁。
2025-06-20 22:40:05 +08:00

1072 lines
37 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ==UserScript==
// @name 蝉妈妈产品历史KOC助手
// @namespace https://github.com/scriptCat/
// @version 1.0.0
// @description 蝉妈妈平台产品历史KOC数据分析和导出助手
// @author wangxi
// @match https://www.chanmama.com/promotionRank/*
// @grant GM_xmlhttpRequest
// @grant GM_notification
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_log
// @grant GM_addStyle
// @grant GM_setClipboard
// @connect chanmama.com
// @connect api-service.chanmama.com
// @require https://cdn.jsdelivr.net/npm/sweetalert2@11
// @license MIT
// ==/UserScript==
/* ==UserConfig==
settings:
enabled:
title: 启用功能
description: 是否启用蝉妈妈产品历史KOC助手功能
type: checkbox
default: true
timeout:
title: 超时时间
description: 网络请求超时时间
type: number
default: 30
min: 10
max: 120
unit: 秒
debug:
title: 调试模式
description: 启用详细日志输出
type: checkbox
default: false
==/UserConfig== */
(function() {
'use strict';
// 配置常量
const CONFIG = {
debug: true, // 强制开启调试模式
enabled: GM_getValue('settings.enabled', true),
timeout: GM_getValue('settings.timeout', 30) * 1000,
api: {
baseUrl: 'https://api-service.chanmama.com',
endpoint: '/v1/product/author/analysis'
},
pagination: {
pageSize: 30,
amountThreshold: 10000 // 金额门槛,低于此值停止翻页
}
};
// 工具函数
function log(message, level = 'info') {
const logMessage = `[${GM_info.script.name}] ${message}`;
// 强制输出所有日志到GM日志和控制台便于调试
GM_log(logMessage, level);
console.log(logMessage);
}
function showNotification(title, text, type = 'info') {
GM_notification({
title: title,
text: text,
timeout: 5000,
onclick: () => log('通知被点击')
});
}
function makeRequest(url, options = {}) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: options.method || 'GET',
url: url,
headers: {
'Content-Type': 'application/json',
...options.headers
},
data: options.data,
timeout: CONFIG.timeout,
anonymous: false,
onload: (response) => {
if (response.status >= 200 && response.status < 300) {
try {
const data = JSON.parse(response.responseText);
resolve(data);
} catch (error) {
resolve(response.responseText);
}
} else {
reject(new Error(`HTTP ${response.status}: ${response.statusText}`));
}
},
onerror: () => reject(new Error('网络请求失败')),
ontimeout: () => reject(new Error('请求超时'))
});
});
}
// SweetAlert2 弹窗函数
function showInfo(title, text) {
Swal.fire({
title: title,
text: text,
icon: 'info',
confirmButtonText: '确定'
});
}
function showSuccess(title, text) {
Swal.fire({
title: title,
text: text,
icon: 'success',
timer: 3000,
showConfirmButton: false
});
}
function showError(title, text) {
Swal.fire({
title: title,
text: text,
icon: 'error',
confirmButtonText: '确定'
});
}
async function showConfirm(title, text) {
const result = await Swal.fire({
title: title,
text: text,
icon: 'question',
showCancelButton: true,
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;
} catch (error) {
log(`获取KOC历史数据失败: ${error.message}`, 'error');
throw error;
}
}
/**
* 解析区间文本为平均值
* 例如: "750~1000" -> 875, "1w~2.5w" -> 17500
*/
function parseRangeText(rangeText) {
if (!rangeText || rangeText === '0' || rangeText === '') return 0;
// 正确处理万单位:先解析每个数值,再转换单位
const split = rangeText.split('~');
const values = split.map(s => {
const trimmed = s.trim();
if (trimmed.includes('w')) {
// 提取数字部分乘以10000
const numPart = parseFloat(trimmed.replace('w', '')) || 0;
return numPart * 10000;
} else {
return parseFloat(trimmed) || 0;
}
});
const result = values.reduce((sum, val) => sum + val, 0) / values.length;
return result;
}
/**
* 解析KOC数据
*/
function parseKOCData(apiResponse) {
if (!apiResponse || !apiResponse.data || !apiResponse.data.list) {
log('数据格式错误', 'error');
return [];
}
const results = [];
const kocList = apiResponse.data.list;
kocList.forEach((koc, index) => {
// 基础信息
const baseInfo = {
昵称: koc.nickname || '',
达人UID: koc.unique_id || koc.short_id || '',
粉丝数: koc.follower_count || 0,
类型: koc.label || '',
口碑分: (koc.reputation && koc.reputation.score) || 0,
关联商品销售量区间: koc.volume_text || '',
关联商品销售金额区间: koc.amount_text || '',
预估关联商品销售量: parseRangeText(koc.volume_text),
预估关联商品销售金额: parseRangeText(koc.amount_text)
};
// 计算关联商品视频的总销售量和金额
let totalVideoVolume = 0;
let totalVideoAmount = 0;
const videoProductIds = new Set();
if (koc.a && Array.isArray(koc.a)) {
koc.a.forEach((video, videoIndex) => {
if (video.product_id) {
videoProductIds.add(video.product_id);
}
const volumeValue = parseRangeText(video.volume_text);
const amountValue = parseRangeText(video.amount_text);
totalVideoVolume += volumeValue;
totalVideoAmount += amountValue;
});
}
baseInfo.预估关联商品视频销售量 = totalVideoVolume;
baseInfo.预估关联商品视频销售金额 = totalVideoAmount;
baseInfo.关联商品蝉妈妈ID = Array.from(videoProductIds).join(',');
results.push(baseInfo);
});
return results;
}
// UI相关函数 - 创建简洁美观的悬浮窗
function createFloatingWindow() {
// 检查悬浮窗是否已存在
if (document.querySelector('.koc-floating-window')) {
log('悬浮窗已存在');
return true;
}
// 创建简洁的悬浮窗
const floatingWindow = document.createElement('div');
floatingWindow.className = 'koc-floating-window';
floatingWindow.innerHTML = `
<div class="koc-window-header">
<span class="koc-window-title">KOC数据采集</span>
<button class="koc-close-btn" title="关闭">×</button>
</div>
<div class="koc-window-content">
<div class="koc-api-status">
<div class="koc-status-indicator"></div>
<span class="koc-status-text">准备采集...</span>
</div>
<div class="koc-actions">
<button class="koc-download-btn" type="button">
<svg class="koc-btn-icon" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/>
</svg>
<span class="koc-btn-text">开始采集</span>
</button>
</div>
</div>
`;
// 设置悬浮窗样式
floatingWindow.style.cssText = `
position: fixed;
bottom: 30px;
right: 30px;
width: 180px;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
z-index: 9999;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
user-select: none;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
opacity: 0;
transform: translateY(20px) scale(0.95);
`;
// 添加美观的样式
GM_addStyle(`
.koc-floating-window {
animation: kocSlideIn 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards;
}
@keyframes kocSlideIn {
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.koc-window-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px 8px;
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
cursor: move;
user-select: none;
}
.koc-window-title {
font-size: 13px;
font-weight: 600;
color: #374151;
letter-spacing: 0.025em;
}
.koc-close-btn {
width: 20px;
height: 20px;
border: none;
background: transparent;
border-radius: 6px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
color: #6b7280;
transition: all 0.2s ease;
font-weight: 300;
}
.koc-close-btn:hover {
background: rgba(239, 68, 68, 0.1);
color: #ef4444;
transform: scale(1.1);
}
.koc-window-content {
padding: 16px;
}
.koc-api-status {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: rgba(16, 185, 129, 0.05);
border-radius: 6px;
border: 1px solid rgba(16, 185, 129, 0.1);
margin-bottom: 12px;
}
.koc-actions {
display: flex;
flex-direction: column;
gap: 8px;
}
.koc-download-btn {
width: 100%;
padding: 12px 16px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 13px;
font-weight: 500;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
position: relative;
overflow: hidden;
}
.koc-download-btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s;
}
.koc-download-btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
}
.koc-download-btn:hover::before {
left: 100%;
}
.koc-download-btn:active {
transform: translateY(0);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}
.koc-download-btn:disabled {
background: linear-gradient(135deg, #d1d5db 0%, #9ca3af 100%);
cursor: not-allowed;
transform: none;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.koc-btn-icon {
width: 16px;
height: 16px;
flex-shrink: 0;
}
.koc-status-indicator {
width: 8px;
height: 8px;
border-radius: 50%;
background: #10b981;
animation: kocPulse 2s infinite;
}
@keyframes kocPulse {
0%, 100% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.5;
transform: scale(1.2);
}
}
.koc-status-text {
font-size: 11px;
color: #6b7280;
font-weight: 500;
}
.koc-floating-window.dragging {
transition: none !important;
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
transform: scale(1.02);
}
/* 状态颜色 */
.koc-status.waiting .koc-status-indicator {
background: #10b981;
}
.koc-status.processing .koc-status-indicator {
background: #f59e0b;
animation: kocSpin 1s linear infinite;
}
.koc-status.success .koc-status-indicator {
background: #10b981;
}
.koc-status.error .koc-status-indicator {
background: #ef4444;
}
@keyframes kocSpin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
/* 响应式设计 */
@media (max-width: 768px) {
.koc-floating-window {
bottom: 20px;
right: 20px;
width: 160px;
}
}
`);
// 添加到页面
document.body.appendChild(floatingWindow);
// 设置拖拽功能
setupSimpleDrag(floatingWindow);
// 设置事件监听器
setupFloatingWindowEvents(floatingWindow);
// 显示动画
setTimeout(() => {
floatingWindow.style.opacity = '1';
floatingWindow.style.transform = 'translateY(0) scale(1)';
}, 100);
return true;
}
// 更新悬浮窗状态
function updateFloatingWindowStatus(message, type = 'info') {
const floatingWindow = document.querySelector('.koc-floating-window');
if (!floatingWindow) return;
const statusText = floatingWindow.querySelector('.koc-status-text');
const statusIndicator = floatingWindow.querySelector('.koc-status-indicator');
if (statusText) {
statusText.textContent = message;
}
if (statusIndicator) {
statusIndicator.className = 'koc-status-indicator';
statusIndicator.classList.add(`koc-status-${type}`);
}
}
// 处理下载按钮点击
async function handleDownloadClick() {
const floatingWindow = document.querySelector('.koc-floating-window');
if (!floatingWindow) {
log('悬浮窗不存在', 'error');
return;
}
const downloadBtn = floatingWindow.querySelector('.koc-download-btn');
if (!downloadBtn) {
log('下载按钮不存在', 'error');
return;
}
if (downloadBtn.disabled) {
log('按钮已禁用,请等待当前操作完成', 'warn');
return;
}
try {
log('=== 开始KOC数据采集 ===', 'info');
// 设置按钮状态为处理中
setStatus(floatingWindow, 'processing', '正在采集数据...');
downloadBtn.disabled = true;
downloadBtn.innerHTML = `
<svg class="koc-btn-icon" viewBox="0 0 24 24" fill="currentColor" style="animation: spin 1s linear infinite;">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
</svg>
<span class="koc-btn-text">采集中...</span>
`;
// 从当前页面URL提取product_id
function extractProductIdFromUrl() {
const url = window.location.href;
const match = url.match(/promotionRank\/([^\.]+)\.html/);
return match ? match[1] : "MUJ0VtooAuIRKA33vMWQN2QUo4A016WL";
}
// 计算时间范围近365天T-1结束
function getDateRange() {
const endDate = new Date();
endDate.setDate(endDate.getDate() - 1); // T-1
const startDate = new Date();
startDate.setDate(startDate.getDate() - 365); // 近365天
return {
start_date: startDate.toISOString().split('T')[0],
end_date: endDate.toISOString().split('T')[0]
};
}
const dateRange = getDateRange();
const productId = extractProductIdFromUrl();
const apiUrl = `${CONFIG.api.baseUrl}${CONFIG.api.endpoint}`;
log(`产品ID: ${productId}`, 'debug');
log(`时间范围: ${dateRange.start_date}${dateRange.end_date}`, 'debug');
let allData = [];
let currentPage = 1;
let shouldContinue = true;
while (shouldContinue) {
log(`请求第 ${currentPage} 页数据`, 'info');
// 构造请求参数
const requestParams = new URLSearchParams({
product_id: productId,
sort: 'amount',
order_by: 'desc',
has_contact: '0',
is_new_corp: '0',
self_play: '0',
shop_play: '0',
author_play: '1',
live_room_status: '0',
category: '',
reputation_level: '-1',
follower_count: '',
keyword: '',
page: currentPage.toString(),
size: CONFIG.pagination.pageSize.toString(),
take_product_method: '1',
start_date: dateRange.start_date,
end_date: dateRange.end_date
}).toString();
log(`请求参数: ${requestParams}`, 'debug');
// 发起API请求
const response = await makeRequest(`${apiUrl}?${requestParams}`, {
method: 'GET'
});
// 解析数据
const parsedData = parseKOCData(response);
if (parsedData.length === 0) {
log(`${currentPage} 页无数据,停止采集`, 'info');
break;
}
// 检查金额门槛 - 使用预估关联商品视频销售金额的最小值
const amountValues = parsedData.map(author => author.预估关联商品视频销售金额 || 0);
const minAmount = Math.min(...amountValues);
log(`${currentPage} 页最小金额: ${minAmount}`, 'debug');
if (minAmount < CONFIG.pagination.amountThreshold) {
log(`达到金额门槛 ${CONFIG.pagination.amountThreshold},停止采集`, 'info');
// 过滤掉低于门槛的数据
const filteredData = parsedData.filter(author => author.预估关联商品视频销售金额 >= CONFIG.pagination.amountThreshold);
allData = allData.concat(filteredData);
shouldContinue = false;
} else {
allData = allData.concat(parsedData);
currentPage++;
// 防止无限循环最多采集100页
if (currentPage > 100) {
log('达到最大页数限制,停止采集', 'warn');
shouldContinue = false;
}
}
// 更新状态
setStatus(floatingWindow, 'processing', `已采集 ${allData.length} 条数据`);
// 延迟1秒避免请求过于频繁
await new Promise(resolve => setTimeout(resolve, 1000));
}
log(`=== 数据采集完成 ===`, 'info');
log(`总共采集 ${allData.length} 条符合条件的达人数据`, 'info');
// 打印完整数据到日志
log('📊 KOC数据采集结果:', 'info');
log(`📈 共采集 ${allData.length} 条达人数据`, 'info');
log('📋 数据详情:', 'info');
log(JSON.stringify(allData, null, 2), 'info');
// 显示成功提示
setStatus(floatingWindow, 'success', `已采集 ${allData.length} 条数据`);
showSuccess('采集成功', `已采集 ${allData.length} 条符合条件的达人数据,请查看控制台`);
// 5秒后恢复按钮状态
setTimeout(() => {
resetButton(floatingWindow);
}, 5000);
} catch (error) {
log(`=== 数据采集失败 ===`, 'error');
log(`错误详情: ${error.message}`, 'error');
log(`错误堆栈: ${error.stack}`, 'error');
setStatus(floatingWindow, 'error', '采集失败');
showError('采集失败', error.message);
// 5秒后恢复按钮状态
setTimeout(() => {
resetButton(floatingWindow);
}, 5000);
}
}
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() {
if (!CONFIG.enabled) {
log('脚本已禁用');
return;
}
try {
// 输出启动信息
log('🎯 KOC数据采集工具启动', 'info');
// 等待页面加载完成
await waitForPageLoad();
// 检查是否在正确的页面
if (!isValidPage()) {
log('当前页面不支持此功能');
return;
}
// 创建悬浮窗
let windowCreated = createFloatingWindow();
if (!windowCreated) {
log('首次创建悬浮窗失败,延迟重试...', 'warn');
setTimeout(() => {
windowCreated = createFloatingWindow();
if (!windowCreated) {
log('延迟重试失败设置DOM观察器', 'warn');
setupWindowObserver();
}
}, 2000);
}
showToast('KOC数据采集工具已就绪', 'success');
} catch (error) {
log(`初始化失败: ${error.message}`, 'error');
showToast('初始化失败', 'error');
}
}
function setupWindowObserver() {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
// 检查是否已经有悬浮窗
if (document.querySelector('.koc-floating-window')) {
observer.disconnect();
return;
}
// 简单创建悬浮窗
createFloatingWindow();
observer.disconnect();
}
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
log('DOM观察器已设置');
}
function waitForPageLoad() {
return new Promise((resolve) => {
if (document.readyState === 'complete') {
resolve();
} else {
window.addEventListener('load', resolve);
}
});
}
function isValidPage() {
// 检查是否在蝉妈妈的推广排名页面
return window.location.hostname.includes('chanmama.com') &&
window.location.pathname.includes('/promotionRank');
}
// 备用拖拽方案
function setupSimpleDrag(floatingWindow) {
const header = floatingWindow.querySelector('.koc-window-header');
let isDragging = false;
let startX, startY, startLeft, startTop;
header.addEventListener('mousedown', (e) => {
if (e.target.closest('.koc-close-btn')) return;
isDragging = true;
startX = e.clientX;
startY = e.clientY;
// 获取当前位置正确处理bottom/right定位
const rect = floatingWindow.getBoundingClientRect();
startLeft = rect.left;
startTop = rect.top;
floatingWindow.classList.add('dragging');
e.preventDefault();
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const deltaX = e.clientX - startX;
const deltaY = e.clientY - startY;
let newLeft = startLeft + deltaX;
let newTop = startTop + deltaY;
// 限制在视窗内
const maxX = window.innerWidth - floatingWindow.offsetWidth;
const maxY = window.innerHeight - floatingWindow.offsetHeight;
newLeft = Math.max(0, Math.min(newLeft, maxX));
newTop = Math.max(0, Math.min(newTop, maxY));
// 设置新位置清除bottom/right定位
floatingWindow.style.left = newLeft + 'px';
floatingWindow.style.top = newTop + 'px';
floatingWindow.style.right = 'auto';
floatingWindow.style.bottom = 'auto';
});
document.addEventListener('mouseup', () => {
if (isDragging) {
isDragging = false;
floatingWindow.classList.remove('dragging');
}
});
}
// 设置悬浮窗事件
function setupFloatingWindowEvents(floatingWindow) {
const downloadBtn = floatingWindow.querySelector('.koc-download-btn');
const closeBtn = floatingWindow.querySelector('.koc-close-btn');
// 关闭功能
closeBtn.addEventListener('click', () => {
floatingWindow.style.opacity = '0';
floatingWindow.style.transform = 'translateY(20px) scale(0.95)';
setTimeout(() => {
floatingWindow.remove();
}, 300);
});
// 下载按钮点击 - 直接调用handleDownloadClick
downloadBtn.addEventListener('click', handleDownloadClick);
}
// 设置状态
function setStatus(floatingWindow, status, text) {
const apiStatusContainer = floatingWindow.querySelector('.koc-api-status');
const apiStatusText = floatingWindow.querySelector('.koc-api-status .koc-status-text');
if (apiStatusContainer && apiStatusText) {
// 移除所有状态类
apiStatusContainer.classList.remove('waiting', 'processing', 'success', 'error');
// 添加新状态类
apiStatusContainer.classList.add(status);
apiStatusText.textContent = text;
// 根据状态更新样式
switch (status) {
case 'processing':
apiStatusContainer.style.background = 'rgba(245, 158, 11, 0.1)';
apiStatusContainer.style.borderColor = 'rgba(245, 158, 11, 0.2)';
break;
case 'success':
apiStatusContainer.style.background = 'rgba(16, 185, 129, 0.1)';
apiStatusContainer.style.borderColor = 'rgba(16, 185, 129, 0.2)';
break;
case 'error':
apiStatusContainer.style.background = 'rgba(239, 68, 68, 0.1)';
apiStatusContainer.style.borderColor = 'rgba(239, 68, 68, 0.2)';
break;
default:
apiStatusContainer.style.background = 'rgba(16, 185, 129, 0.05)';
apiStatusContainer.style.borderColor = 'rgba(16, 185, 129, 0.1)';
}
}
}
// 重置按钮
function resetButton(floatingWindow) {
const downloadBtn = floatingWindow.querySelector('.koc-download-btn');
if (downloadBtn) {
downloadBtn.disabled = false;
downloadBtn.innerHTML = `
<svg class="koc-btn-icon" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/>
</svg>
<span class="koc-btn-text">开始采集</span>
`;
}
// 恢复状态显示
const apiStatusText = floatingWindow.querySelector('.koc-api-status .koc-status-text');
if (apiStatusText) {
apiStatusText.textContent = '准备采集...';
}
setStatus(floatingWindow, 'waiting', '准备采集...');
}
// 创建简洁的toast提示
function showToast(message, type = 'info') {
// 移除已存在的toast
const existingToast = document.querySelector('.koc-toast');
if (existingToast) {
existingToast.remove();
}
const toast = document.createElement('div');
toast.className = `koc-toast koc-toast-${type}`;
toast.innerHTML = `
<div class="koc-toast-icon">
${getToastIcon(type)}
</div>
<div class="koc-toast-message">${message}</div>
`;
// 添加toast样式
GM_addStyle(`
.koc-toast {
position: fixed;
top: 20px;
right: 20px;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 12px;
padding: 16px 20px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
z-index: 10000;
display: flex;
align-items: center;
gap: 12px;
font-size: 15px;
font-weight: 600;
max-width: 350px;
min-width: 280px;
animation: kocToastSlideIn 0.3s ease;
transition: all 0.3s ease;
}
@keyframes kocToastSlideIn {
from {
opacity: 0;
transform: translateX(100%);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.koc-toast-icon {
width: 20px;
height: 20px;
flex-shrink: 0;
}
.koc-toast-message {
color: #374151;
line-height: 1.4;
}
.koc-toast-success {
border-left: 5px solid #10b981;
}
.koc-toast-error {
border-left: 5px solid #ef4444;
}
.koc-toast-info {
border-left: 5px solid #3b82f6;
}
.koc-toast-warning {
border-left: 5px solid #f59e0b;
}
.koc-toast.fade-out {
opacity: 0;
transform: translateX(100%);
}
`);
document.body.appendChild(toast);
// 3秒后自动消失
setTimeout(() => {
toast.classList.add('fade-out');
setTimeout(() => {
if (toast.parentNode) {
toast.remove();
}
}, 300);
}, 3000);
// 点击关闭
toast.addEventListener('click', () => {
toast.classList.add('fade-out');
setTimeout(() => {
if (toast.parentNode) {
toast.remove();
}
}, 300);
});
}
// 获取toast图标
function getToastIcon(type) {
switch (type) {
case 'success':
return `<svg viewBox="0 0 24 24" fill="currentColor" style="color: #10b981;">
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>`;
case 'error':
return `<svg viewBox="0 0 24 24" fill="currentColor" style="color: #ef4444;">
<path d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>`;
case 'warning':
return `<svg viewBox="0 0 24 24" fill="currentColor" style="color: #f59e0b;">
<path d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"/>
</svg>`;
default:
return `<svg viewBox="0 0 24 24" fill="currentColor" style="color: #3b82f6;">
<path d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>`;
}
}
// 页面加载完成后运行
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();