tt-help-cli-ycl 1.4.144 → 1.4.145
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/scripts/test-daemon-failover.mjs +61 -4
- package/src/clash/checker.js +41 -0
- package/src/clash/daemon.js +26 -0
- package/src/cli/tag.js +104 -329
- package/src/lib/constants.js +6 -5
- package/src/watch/data-store.js +88 -0
- package/src/watch/public/app.js +232 -0
- package/src/watch/public/index.html +53 -0
- package/src/watch/public/style.css +202 -0
- package/src/watch/server.js +60 -0
- package/src/watchdog/process-manager.js +11 -6
package/src/watch/data-store.js
CHANGED
|
@@ -608,6 +608,92 @@ function getTargetUsersByCountryFromDb(targetLocations = [], options = {}) {
|
|
|
608
608
|
};
|
|
609
609
|
}
|
|
610
610
|
|
|
611
|
+
/**
|
|
612
|
+
* 精确商家:目标商家(tt_seller=1 且 verified=0 且 目标国家)中,
|
|
613
|
+
* 最近 7 天有 isECVideo 播放 > 1000 的视频,且有商品、商品评论数=0、销量 > 10。
|
|
614
|
+
* 支持分页与搜索(unique_id/nickname),返回 total + users。
|
|
615
|
+
*/
|
|
616
|
+
function getPreciseMerchantsFromDb(targetLocations = [], options = {}) {
|
|
617
|
+
if (!getDb()) return null;
|
|
618
|
+
if (!targetLocations.length) return { total: 0, users: [] };
|
|
619
|
+
|
|
620
|
+
const { limit, offset, search } = options;
|
|
621
|
+
const placeholders = targetLocations.map(() => "?").join(", ");
|
|
622
|
+
|
|
623
|
+
const where = `
|
|
624
|
+
tt_seller = 1
|
|
625
|
+
AND verified = 0
|
|
626
|
+
AND location_created IN (${placeholders})
|
|
627
|
+
AND top_video_seo_url IS NOT NULL AND top_video_seo_url != ''
|
|
628
|
+
AND product_review_count = 0
|
|
629
|
+
AND product_sold_count > 10
|
|
630
|
+
AND recent_ec_videos IS NOT NULL AND recent_ec_videos != ''
|
|
631
|
+
AND EXISTS (
|
|
632
|
+
SELECT 1 FROM json_each(recent_ec_videos)
|
|
633
|
+
WHERE CAST(json_extract(value, '$.playCount') AS INTEGER) > 1000
|
|
634
|
+
)
|
|
635
|
+
`;
|
|
636
|
+
|
|
637
|
+
let searchSql = "";
|
|
638
|
+
const searchParams = [];
|
|
639
|
+
if (search) {
|
|
640
|
+
searchSql = ` AND (unique_id LIKE ? OR nickname LIKE ?)`;
|
|
641
|
+
searchParams.push(`%${search}%`, `%${search}%`);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
const totalRow = getDb()
|
|
645
|
+
.prepare(`SELECT COUNT(*) AS n FROM jobs WHERE ${where}${searchSql}`)
|
|
646
|
+
.get(...targetLocations, ...searchParams);
|
|
647
|
+
const total = totalRow?.n || 0;
|
|
648
|
+
|
|
649
|
+
const selectSql = `
|
|
650
|
+
SELECT
|
|
651
|
+
unique_id,
|
|
652
|
+
nickname,
|
|
653
|
+
sec_uid,
|
|
654
|
+
status,
|
|
655
|
+
follower_count,
|
|
656
|
+
video_count,
|
|
657
|
+
location_created,
|
|
658
|
+
latest_video_time,
|
|
659
|
+
refresh_time,
|
|
660
|
+
top_video_play_count,
|
|
661
|
+
top_video_href,
|
|
662
|
+
top_video_is_ad,
|
|
663
|
+
top_video_product_id,
|
|
664
|
+
top_video_product_title,
|
|
665
|
+
top_video_seo_url,
|
|
666
|
+
product_rating,
|
|
667
|
+
product_review_count,
|
|
668
|
+
product_sold_count,
|
|
669
|
+
product_name,
|
|
670
|
+
recent_ec_videos,
|
|
671
|
+
(SELECT COUNT(*) FROM json_each(recent_ec_videos)
|
|
672
|
+
WHERE CAST(json_extract(value, '$.playCount') AS INTEGER) > 1000) AS ecOver1k,
|
|
673
|
+
(SELECT MAX(CAST(json_extract(value, '$.playCount') AS INTEGER))
|
|
674
|
+
FROM json_each(recent_ec_videos)) AS maxEcPlay
|
|
675
|
+
FROM jobs
|
|
676
|
+
WHERE ${where}${searchSql}
|
|
677
|
+
ORDER BY product_sold_count DESC, unique_id ASC
|
|
678
|
+
`;
|
|
679
|
+
|
|
680
|
+
if (limit !== undefined) {
|
|
681
|
+
const safeLimit = Math.max(Math.min(Math.floor(limit), 1000), 1);
|
|
682
|
+
const safeOffset = Math.max(Math.floor(offset || 0), 0);
|
|
683
|
+
const rows = getDb()
|
|
684
|
+
.prepare(`${selectSql} LIMIT ? OFFSET ?`)
|
|
685
|
+
.all(...targetLocations, ...searchParams, safeLimit, safeOffset)
|
|
686
|
+
.map(mapJobRow);
|
|
687
|
+
return { total, limit: safeLimit, offset: safeOffset, users: rows };
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
const rows = getDb()
|
|
691
|
+
.prepare(selectSql)
|
|
692
|
+
.all(...targetLocations, ...searchParams)
|
|
693
|
+
.map(mapJobRow);
|
|
694
|
+
return { total, users: rows };
|
|
695
|
+
}
|
|
696
|
+
|
|
611
697
|
/**
|
|
612
698
|
* 查询已修改(modified_at IS NOT NULL)且 location_created 不在 targetLocations 中的目标商家,
|
|
613
699
|
* 按国家分组返回。
|
|
@@ -3549,6 +3635,8 @@ export function createStore(filePath, options = {}) {
|
|
|
3549
3635
|
getRawJobsPage: getRawJobsPageFromDb,
|
|
3550
3636
|
getTargetUsers: getTargetUsersFromDb,
|
|
3551
3637
|
getTargetUsersByCountry: getTargetUsersByCountryFromDb,
|
|
3638
|
+
getPreciseMerchants: (targetLocations, options) =>
|
|
3639
|
+
getPreciseMerchantsFromDb(targetLocations, options),
|
|
3552
3640
|
getModifiedTargetUsersOutsideLocations: (loc) =>
|
|
3553
3641
|
getModifiedTargetUsersOutsideLocations(loc),
|
|
3554
3642
|
getStats,
|
package/src/watch/public/app.js
CHANGED
|
@@ -185,6 +185,7 @@ function renderStats() {
|
|
|
185
185
|
flashEl("statPending", d.pendingUsers);
|
|
186
186
|
flashEl("statError", d.errorUsers);
|
|
187
187
|
flashEl("statTarget", d.targetUsers, { full: true });
|
|
188
|
+
flashEl("statPrecise", d.preciseMerchants, { full: true });
|
|
188
189
|
// 检测目标商家新增,放烟花(仅首页,且开关开启时)
|
|
189
190
|
const hash = window.location.hash.replace(/^#/, "");
|
|
190
191
|
if (
|
|
@@ -1137,6 +1138,10 @@ document.getElementById("statTargetCard").addEventListener("click", () => {
|
|
|
1137
1138
|
navigateToTarget();
|
|
1138
1139
|
});
|
|
1139
1140
|
|
|
1141
|
+
document.getElementById("statPreciseCard").addEventListener("click", () => {
|
|
1142
|
+
navigateToPrecise();
|
|
1143
|
+
});
|
|
1144
|
+
|
|
1140
1145
|
// Hash 路由
|
|
1141
1146
|
window.addEventListener("hashchange", handleRoute);
|
|
1142
1147
|
window.addEventListener("DOMContentLoaded", handleRoute);
|
|
@@ -1290,6 +1295,8 @@ function handleRoute() {
|
|
|
1290
1295
|
showRawPage();
|
|
1291
1296
|
} else if (hash === "#target") {
|
|
1292
1297
|
showTargetPage();
|
|
1298
|
+
} else if (hash === "#precise") {
|
|
1299
|
+
showPrecisePage();
|
|
1293
1300
|
} else if (hash === "#tags") {
|
|
1294
1301
|
showTagsPage();
|
|
1295
1302
|
} else if (hash === "#settings") {
|
|
@@ -1323,6 +1330,7 @@ function showPendingPage() {
|
|
|
1323
1330
|
document.getElementById("userUpdatePage").classList.remove("active");
|
|
1324
1331
|
document.getElementById("rawPage").classList.remove("active");
|
|
1325
1332
|
document.getElementById("targetPage").classList.remove("active");
|
|
1333
|
+
document.getElementById("precisePage").classList.remove("active");
|
|
1326
1334
|
document.getElementById("tagsPage").classList.remove("active");
|
|
1327
1335
|
document.getElementById("settingsPage").classList.remove("active");
|
|
1328
1336
|
fetchPendingByCountry();
|
|
@@ -1335,6 +1343,7 @@ function showUserUpdatePage() {
|
|
|
1335
1343
|
document.getElementById("userUpdatePage").classList.add("active");
|
|
1336
1344
|
document.getElementById("rawPage").classList.remove("active");
|
|
1337
1345
|
document.getElementById("targetPage").classList.remove("active");
|
|
1346
|
+
document.getElementById("precisePage").classList.remove("active");
|
|
1338
1347
|
document.getElementById("tagsPage").classList.remove("active");
|
|
1339
1348
|
document.getElementById("settingsPage").classList.remove("active");
|
|
1340
1349
|
fetchUserUpdateByCountry();
|
|
@@ -1354,6 +1363,7 @@ function showRawPage() {
|
|
|
1354
1363
|
document.getElementById("userUpdatePage").classList.remove("active");
|
|
1355
1364
|
document.getElementById("rawPage").classList.add("active");
|
|
1356
1365
|
document.getElementById("targetPage").classList.remove("active");
|
|
1366
|
+
document.getElementById("precisePage").classList.remove("active");
|
|
1357
1367
|
document.getElementById("tagsPage").classList.remove("active");
|
|
1358
1368
|
document.getElementById("settingsPage").classList.remove("active");
|
|
1359
1369
|
fetchRawByCountry();
|
|
@@ -1366,6 +1376,7 @@ function showMainPage() {
|
|
|
1366
1376
|
document.getElementById("userUpdatePage").classList.remove("active");
|
|
1367
1377
|
document.getElementById("rawPage").classList.remove("active");
|
|
1368
1378
|
document.getElementById("targetPage").classList.remove("active");
|
|
1379
|
+
document.getElementById("precisePage").classList.remove("active");
|
|
1369
1380
|
document.getElementById("tagsPage").classList.remove("active");
|
|
1370
1381
|
document.getElementById("settingsPage").classList.remove("active");
|
|
1371
1382
|
document.getElementById("watchdogPage").classList.remove("active");
|
|
@@ -1379,6 +1390,10 @@ function navigateToTarget() {
|
|
|
1379
1390
|
window.location.hash = "#target";
|
|
1380
1391
|
}
|
|
1381
1392
|
|
|
1393
|
+
function navigateToPrecise() {
|
|
1394
|
+
window.location.hash = "#precise";
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1382
1397
|
function navigateToTags() {
|
|
1383
1398
|
window.location.hash = "#tags";
|
|
1384
1399
|
}
|
|
@@ -1399,6 +1414,7 @@ function hideAllPages() {
|
|
|
1399
1414
|
document.getElementById("userUpdatePage").classList.remove("active");
|
|
1400
1415
|
document.getElementById("rawPage").classList.remove("active");
|
|
1401
1416
|
document.getElementById("targetPage").classList.remove("active");
|
|
1417
|
+
document.getElementById("precisePage").classList.remove("active");
|
|
1402
1418
|
document.getElementById("tagsPage").classList.remove("active");
|
|
1403
1419
|
document.getElementById("settingsPage").classList.remove("active");
|
|
1404
1420
|
document.getElementById("watchdogPage").classList.remove("active");
|
|
@@ -1858,6 +1874,220 @@ async function submitAddTags() {
|
|
|
1858
1874
|
}
|
|
1859
1875
|
}
|
|
1860
1876
|
|
|
1877
|
+
// ========== 精准商家页面 ==========
|
|
1878
|
+
|
|
1879
|
+
async function showPrecisePage() {
|
|
1880
|
+
hideAllPages();
|
|
1881
|
+
document.getElementById("precisePage").classList.add("active");
|
|
1882
|
+
// 每次进入都默认视频模式
|
|
1883
|
+
setPreciseView("video");
|
|
1884
|
+
showLoading("正在加载精准商家数据...");
|
|
1885
|
+
await loadPreciseUsersPage(false, true);
|
|
1886
|
+
hideLoading();
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
let currentPreciseUsers = [];
|
|
1890
|
+
let currentPreciseTotal = 0;
|
|
1891
|
+
let currentPreciseLoading = false;
|
|
1892
|
+
let currentPreciseAllLoaded = false;
|
|
1893
|
+
let currentPreciseSeq = 0;
|
|
1894
|
+
let currentPreciseView = "video";
|
|
1895
|
+
const PRECISE_PAGE_SIZE = 200;
|
|
1896
|
+
|
|
1897
|
+
async function loadPreciseUsersPage(append = false, skipLoading = false) {
|
|
1898
|
+
if (currentPreciseLoading) return;
|
|
1899
|
+
currentPreciseLoading = true;
|
|
1900
|
+
const seq = ++currentPreciseSeq;
|
|
1901
|
+
const search = document.getElementById("preciseSearchInput")
|
|
1902
|
+
? document.getElementById("preciseSearchInput").value.trim()
|
|
1903
|
+
: "";
|
|
1904
|
+
const offset = append ? currentPreciseUsers.length : 0;
|
|
1905
|
+
if (!append && !skipLoading) showLoading("正在加载数据...");
|
|
1906
|
+
|
|
1907
|
+
let data = null;
|
|
1908
|
+
let error = null;
|
|
1909
|
+
try {
|
|
1910
|
+
let url = `/api/precise-merchants?limit=${PRECISE_PAGE_SIZE}&offset=${offset}`;
|
|
1911
|
+
if (search) url += `&search=${encodeURIComponent(search)}`;
|
|
1912
|
+
const res = await fetch(url);
|
|
1913
|
+
data = await res.json();
|
|
1914
|
+
} catch (e) {
|
|
1915
|
+
error = e;
|
|
1916
|
+
console.error("加载精准商家失败:", e);
|
|
1917
|
+
} finally {
|
|
1918
|
+
if (!append && !skipLoading) hideLoading();
|
|
1919
|
+
currentPreciseLoading = false;
|
|
1920
|
+
if (error || !data) return;
|
|
1921
|
+
if (seq !== currentPreciseSeq && !append) return;
|
|
1922
|
+
if (!append) {
|
|
1923
|
+
currentPreciseUsers = data.users || [];
|
|
1924
|
+
currentPreciseTotal = data.total || 0;
|
|
1925
|
+
} else {
|
|
1926
|
+
currentPreciseUsers = currentPreciseUsers.concat(data.users || []);
|
|
1927
|
+
currentPreciseTotal = data.total || 0;
|
|
1928
|
+
}
|
|
1929
|
+
currentPreciseAllLoaded = currentPreciseUsers.length >= currentPreciseTotal;
|
|
1930
|
+
renderPreciseView();
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
function setPreciseView(view) {
|
|
1935
|
+
currentPreciseView = view === "table" ? "table" : "video";
|
|
1936
|
+
const tableBtn = document.getElementById("preciseViewTableBtn");
|
|
1937
|
+
const videoBtn = document.getElementById("preciseViewVideoBtn");
|
|
1938
|
+
if (tableBtn)
|
|
1939
|
+
tableBtn.classList.toggle("active", currentPreciseView === "table");
|
|
1940
|
+
if (videoBtn)
|
|
1941
|
+
videoBtn.classList.toggle("active", currentPreciseView === "video");
|
|
1942
|
+
const videoWrap = document.getElementById("preciseVideoWrap");
|
|
1943
|
+
const tableWrap = document.getElementById("preciseTableWrap");
|
|
1944
|
+
if (videoWrap)
|
|
1945
|
+
videoWrap.style.display = currentPreciseView === "video" ? "" : "none";
|
|
1946
|
+
if (tableWrap)
|
|
1947
|
+
tableWrap.style.display = currentPreciseView === "table" ? "" : "none";
|
|
1948
|
+
renderPreciseView();
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
function renderPreciseView() {
|
|
1952
|
+
if (currentPreciseView === "table") {
|
|
1953
|
+
renderPreciseTable();
|
|
1954
|
+
} else {
|
|
1955
|
+
renderPreciseVideos();
|
|
1956
|
+
}
|
|
1957
|
+
updatePreciseMoreHint();
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
function updatePreciseMoreHint() {
|
|
1961
|
+
const moreHint = document.getElementById("preciseMoreHint");
|
|
1962
|
+
const totalEl = document.getElementById("precisePageStatTotal");
|
|
1963
|
+
if (totalEl)
|
|
1964
|
+
totalEl.textContent = formatStatNum(currentPreciseTotal, { full: true });
|
|
1965
|
+
if (!moreHint) return;
|
|
1966
|
+
if (currentPreciseTotal === 0) {
|
|
1967
|
+
moreHint.textContent = "";
|
|
1968
|
+
moreHint.style.display = "none";
|
|
1969
|
+
} else if (currentPreciseAllLoaded) {
|
|
1970
|
+
moreHint.textContent = `共 ${currentPreciseTotal} 个,已全部加载`;
|
|
1971
|
+
moreHint.onclick = null;
|
|
1972
|
+
moreHint.style.cursor = "default";
|
|
1973
|
+
moreHint.style.color = "#6b7280";
|
|
1974
|
+
} else {
|
|
1975
|
+
moreHint.textContent = "点击加载更多";
|
|
1976
|
+
moreHint.onclick = () => loadPreciseUsersPage(true);
|
|
1977
|
+
moreHint.style.cursor = "pointer";
|
|
1978
|
+
moreHint.style.color = "#60a5fa";
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
function renderPreciseTable() {
|
|
1983
|
+
const el = document.getElementById("preciseTable");
|
|
1984
|
+
if (!el) return;
|
|
1985
|
+
if (!currentPreciseUsers.length) {
|
|
1986
|
+
el.innerHTML =
|
|
1987
|
+
'<tr><td colspan="12" style="text-align:center;color:#666;padding:20px">暂无精准商家</td></tr>';
|
|
1988
|
+
return;
|
|
1989
|
+
}
|
|
1990
|
+
el.innerHTML = currentPreciseUsers
|
|
1991
|
+
.map((u, i) => {
|
|
1992
|
+
const sold = u.productSoldCount ?? 0;
|
|
1993
|
+
const ec = u.ecOver1k ?? 0;
|
|
1994
|
+
const maxEc = u.maxEcPlay ?? 0;
|
|
1995
|
+
const title = (u.productName || "").replace(/</g, "<");
|
|
1996
|
+
return `<tr>
|
|
1997
|
+
<td style="text-align:right;color:#666">${i + 1}</td>
|
|
1998
|
+
<td><a href="https://www.tiktok.com/@${encodeURIComponent(u.uniqueId)}" target="_blank">@${escapeHtml(u.uniqueId)}</a></td>
|
|
1999
|
+
<td>${escapeHtml(u.nickname || "-")}</td>
|
|
2000
|
+
<td>${formatNum(u.followerCount || 0)}</td>
|
|
2001
|
+
<td>${u.videoCount || 0}</td>
|
|
2002
|
+
<td>${u.locationCreated || "-"}</td>
|
|
2003
|
+
<td style="color:#22c55e">${formatNum(sold)}</td>
|
|
2004
|
+
<td style="color:#f87171">${u.productReviewCount ?? 0}</td>
|
|
2005
|
+
<td>${u.productRating != null ? u.productRating : "-"}</td>
|
|
2006
|
+
<td>${ec}</td>
|
|
2007
|
+
<td>${formatNum(maxEc)}</td>
|
|
2008
|
+
<td style="max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${title}">${title || "-"}</td>
|
|
2009
|
+
</tr>`;
|
|
2010
|
+
})
|
|
2011
|
+
.join("");
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
function renderPreciseVideos() {
|
|
2015
|
+
const wrap = document.getElementById("preciseVideoWrap");
|
|
2016
|
+
if (!wrap) return;
|
|
2017
|
+
if (!currentPreciseUsers.length) {
|
|
2018
|
+
wrap.innerHTML =
|
|
2019
|
+
'<div style="color:#666;text-align:center;padding:24px">暂无精准商家</div>';
|
|
2020
|
+
return;
|
|
2021
|
+
}
|
|
2022
|
+
wrap.innerHTML = currentPreciseUsers
|
|
2023
|
+
.map((u) => {
|
|
2024
|
+
const vids = Array.isArray(u.recentEcVideos) ? u.recentEcVideos : [];
|
|
2025
|
+
const maxPlay = vids.reduce((m, v) => Math.max(m, v.playCount || 0), 0);
|
|
2026
|
+
const sold = u.productSoldCount ?? 0;
|
|
2027
|
+
const review = u.productReviewCount ?? 0;
|
|
2028
|
+
const rating = u.productRating != null ? u.productRating : "-";
|
|
2029
|
+
const userLink = `https://www.tiktok.com/@${encodeURIComponent(u.uniqueId)}`;
|
|
2030
|
+
const tiles = vids
|
|
2031
|
+
.reduce((acc, v) => {
|
|
2032
|
+
const play = v.playCount || 0;
|
|
2033
|
+
const time = v.createTime ? formatShortDate(v.createTime) : "";
|
|
2034
|
+
const isAd = v.isAd ? '<span class="pv-ad">广告</span>' : "";
|
|
2035
|
+
const prod = v.seoUrl
|
|
2036
|
+
? `<a class="pv-product" href="${escapeAttr(v.seoUrl)}" target="_blank" title="${escapeHtml(v.productTitle || "查看商品")}"><span class="pv-product-icon">🛒</span>${escapeHtml((v.productTitle || "查看商品").slice(0, 80))}</a>`
|
|
2037
|
+
: "";
|
|
2038
|
+
acc.push(`<div class="precise-video-item">
|
|
2039
|
+
<a class="precise-video-tile" href="${escapeAttr(v.href || "#")}" target="_blank" title="播放视频">
|
|
2040
|
+
<span class="pv-play">▶</span>
|
|
2041
|
+
<span class="pv-count${maxPlay > 0 && play === maxPlay ? " pv-count-max" : ""}">${formatNum(play)}</span>
|
|
2042
|
+
<span class="pv-time">${time}</span>
|
|
2043
|
+
${isAd}
|
|
2044
|
+
</a>
|
|
2045
|
+
${prod}
|
|
2046
|
+
</div>`);
|
|
2047
|
+
return acc;
|
|
2048
|
+
}, [])
|
|
2049
|
+
.join("");
|
|
2050
|
+
return `<div class="precise-merchant-card">
|
|
2051
|
+
<div class="precise-merchant-head">
|
|
2052
|
+
<a class="pm-user" href="${userLink}" target="_blank">@${escapeHtml(u.uniqueId)}</a>
|
|
2053
|
+
<span class="pm-nick">${escapeHtml(u.nickname || "-")}</span>
|
|
2054
|
+
<span class="pm-country">${u.locationCreated || "-"}</span>
|
|
2055
|
+
<span class="pm-stats">粉丝 ${formatNum(u.followerCount || 0)} · 视频 ${u.videoCount || 0} · 销量 ${formatNum(sold)} · 评论 ${review} · 评分 ${rating} · EC视频 ${vids.length}</span>
|
|
2056
|
+
</div>
|
|
2057
|
+
${tiles ? `<div class="precise-video-grid">${tiles}</div>` : '<div style="color:#666;font-size:12px;padding:6px 0">该商家暂无 EC 视频数据</div>'}
|
|
2058
|
+
</div>`;
|
|
2059
|
+
})
|
|
2060
|
+
.join("");
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
function formatShortDate(sec) {
|
|
2064
|
+
if (!sec) return "";
|
|
2065
|
+
const d = new Date(sec * 1000);
|
|
2066
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
2067
|
+
return `${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
function escapeAttr(str) {
|
|
2071
|
+
return String(str)
|
|
2072
|
+
.replace(/&/g, "&")
|
|
2073
|
+
.replace(/"/g, """)
|
|
2074
|
+
.replace(/</g, "<");
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
function clearPreciseFilters() {
|
|
2078
|
+
const input = document.getElementById("preciseSearchInput");
|
|
2079
|
+
if (input) input.value = "";
|
|
2080
|
+
loadPreciseUsersPage();
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
let preciseSearchTimer = null;
|
|
2084
|
+
document.getElementById("preciseSearchInput").addEventListener("input", () => {
|
|
2085
|
+
if (preciseSearchTimer) clearTimeout(preciseSearchTimer);
|
|
2086
|
+
preciseSearchTimer = setTimeout(() => {
|
|
2087
|
+
loadPreciseUsersPage();
|
|
2088
|
+
}, 300);
|
|
2089
|
+
});
|
|
2090
|
+
|
|
1861
2091
|
// ========== 关键词页面 ==========
|
|
1862
2092
|
|
|
1863
2093
|
let currentTags = [];
|
|
@@ -1879,6 +2109,7 @@ function showTagsPage() {
|
|
|
1879
2109
|
document.getElementById("userUpdatePage").classList.remove("active");
|
|
1880
2110
|
document.getElementById("rawPage").classList.remove("active");
|
|
1881
2111
|
document.getElementById("targetPage").classList.remove("active");
|
|
2112
|
+
document.getElementById("precisePage").classList.remove("active");
|
|
1882
2113
|
document.getElementById("tagsPage").classList.add("active");
|
|
1883
2114
|
document.getElementById("settingsPage").classList.remove("active");
|
|
1884
2115
|
showLoading("正在加载关键词...");
|
|
@@ -2353,6 +2584,7 @@ function showTargetPage() {
|
|
|
2353
2584
|
document.getElementById("userUpdatePage").classList.remove("active");
|
|
2354
2585
|
document.getElementById("rawPage").classList.remove("active");
|
|
2355
2586
|
document.getElementById("targetPage").classList.add("active");
|
|
2587
|
+
document.getElementById("precisePage").classList.remove("active");
|
|
2356
2588
|
document.getElementById("tagsPage").classList.remove("active");
|
|
2357
2589
|
document.getElementById("settingsPage").classList.remove("active");
|
|
2358
2590
|
showLoading("正在加载目标商家数据...");
|
|
@@ -59,6 +59,10 @@
|
|
|
59
59
|
<div class="label">目标商家</div>
|
|
60
60
|
<div class="value target" id="statTarget">0</div>
|
|
61
61
|
</div>
|
|
62
|
+
<div class="stat-card clickable" id="statPreciseCard" onclick="navigateToPrecise()">
|
|
63
|
+
<div class="label">精准商家</div>
|
|
64
|
+
<div class="value target" id="statPrecise">0</div>
|
|
65
|
+
</div>
|
|
62
66
|
<div class="stat-card clickable pending-card" id="statCommentTaskCard">
|
|
63
67
|
<div class="label">待评论任务</div>
|
|
64
68
|
<div class="value rose" id="statCommentTask">0</div>
|
|
@@ -379,6 +383,55 @@
|
|
|
379
383
|
</div>
|
|
380
384
|
<div id="backToTop" title="回到顶部(可拖动)">↑</div>
|
|
381
385
|
</div>
|
|
386
|
+
<div id="precisePage">
|
|
387
|
+
<div class="stats" style="margin-bottom:16px">
|
|
388
|
+
<div class="stat-card">
|
|
389
|
+
<div class="label">精准商家</div>
|
|
390
|
+
<div class="value target" id="precisePageStatTotal">0</div>
|
|
391
|
+
</div>
|
|
392
|
+
<div class="stat-card clickable pending-card" onclick="navigateToMain()" style="background:rgba(167,139,250,0.1)">
|
|
393
|
+
<div class="label">← 返回主页面</div>
|
|
394
|
+
</div>
|
|
395
|
+
</div>
|
|
396
|
+
<div class="table-wrap">
|
|
397
|
+
<div class="precise-list-header">
|
|
398
|
+
<h3>精准商家列表</h3>
|
|
399
|
+
<div class="view-toggle">
|
|
400
|
+
<button id="preciseViewTableBtn" onclick="setPreciseView('table')">表格</button>
|
|
401
|
+
<button id="preciseViewVideoBtn" onclick="setPreciseView('video')" class="active">视频</button>
|
|
402
|
+
</div>
|
|
403
|
+
</div>
|
|
404
|
+
<div class="controls">
|
|
405
|
+
<input type="text" id="preciseSearchInput" placeholder="搜索精准商家用户名 / 昵称...">
|
|
406
|
+
<button onclick="clearPreciseFilters()">清空筛选</button>
|
|
407
|
+
</div>
|
|
408
|
+
<div id="preciseVideoWrap"></div>
|
|
409
|
+
<div class="table-scroll" id="preciseTableWrap" style="display:none">
|
|
410
|
+
<table>
|
|
411
|
+
<thead>
|
|
412
|
+
<tr>
|
|
413
|
+
<th style="width:48px">#</th>
|
|
414
|
+
<th>用户名</th>
|
|
415
|
+
<th>昵称</th>
|
|
416
|
+
<th>粉丝</th>
|
|
417
|
+
<th>视频</th>
|
|
418
|
+
<th>国家</th>
|
|
419
|
+
<th>销量</th>
|
|
420
|
+
<th>评论</th>
|
|
421
|
+
<th>评分</th>
|
|
422
|
+
<th>EC超1k视频数</th>
|
|
423
|
+
<th>最高EC播放</th>
|
|
424
|
+
<th>商品</th>
|
|
425
|
+
</tr>
|
|
426
|
+
</thead>
|
|
427
|
+
<tbody id="preciseTable"></tbody>
|
|
428
|
+
</table>
|
|
429
|
+
</div>
|
|
430
|
+
<div id="preciseMoreHint" onclick="loadPreciseUsersPage(true)"
|
|
431
|
+
style="text-align:center;padding:10px 8px;font-size:13px;color:#6b7280;cursor:default;user-select:none;transition:color 0.2s">
|
|
432
|
+
</div>
|
|
433
|
+
</div>
|
|
434
|
+
</div>
|
|
382
435
|
<div id="tagsPage">
|
|
383
436
|
<div class="stats" style="margin-bottom:16px">
|
|
384
437
|
<div class="stat-card">
|
|
@@ -112,6 +112,12 @@ body {
|
|
|
112
112
|
color: #f472b6;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
/* 精准商家卡片文字改为红色 */
|
|
116
|
+
#statPreciseCard .label,
|
|
117
|
+
#statPreciseCard .value {
|
|
118
|
+
color: #ef4444;
|
|
119
|
+
}
|
|
120
|
+
|
|
115
121
|
.stat-card .value-sub {
|
|
116
122
|
font-size: 12px;
|
|
117
123
|
font-weight: 400;
|
|
@@ -878,6 +884,14 @@ td.user-id:hover {
|
|
|
878
884
|
display: block;
|
|
879
885
|
}
|
|
880
886
|
|
|
887
|
+
#precisePage {
|
|
888
|
+
display: none;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
#precisePage.active {
|
|
892
|
+
display: block;
|
|
893
|
+
}
|
|
894
|
+
|
|
881
895
|
#backToTop {
|
|
882
896
|
position: fixed;
|
|
883
897
|
bottom: 32px;
|
|
@@ -1892,3 +1906,191 @@ td.user-id:hover {
|
|
|
1892
1906
|
color: #888;
|
|
1893
1907
|
font-weight: 600;
|
|
1894
1908
|
}
|
|
1909
|
+
|
|
1910
|
+
/* ===== 精准商家:视频卡片视图 ===== */
|
|
1911
|
+
.precise-list-header {
|
|
1912
|
+
display: flex;
|
|
1913
|
+
align-items: center;
|
|
1914
|
+
justify-content: space-between;
|
|
1915
|
+
gap: 10px;
|
|
1916
|
+
margin-bottom: 8px;
|
|
1917
|
+
}
|
|
1918
|
+
.precise-list-header h3 {
|
|
1919
|
+
margin: 0;
|
|
1920
|
+
}
|
|
1921
|
+
.view-toggle {
|
|
1922
|
+
display: inline-flex;
|
|
1923
|
+
align-items: center;
|
|
1924
|
+
gap: 2px;
|
|
1925
|
+
border-bottom: 2px solid #2f2f3f;
|
|
1926
|
+
}
|
|
1927
|
+
.view-toggle button {
|
|
1928
|
+
padding: 7px 18px;
|
|
1929
|
+
border: none;
|
|
1930
|
+
border-bottom: 2px solid transparent;
|
|
1931
|
+
border-radius: 6px 6px 0 0;
|
|
1932
|
+
background: transparent;
|
|
1933
|
+
color: #9aa;
|
|
1934
|
+
font-size: 13px;
|
|
1935
|
+
cursor: pointer;
|
|
1936
|
+
transition: all 0.2s;
|
|
1937
|
+
}
|
|
1938
|
+
.view-toggle button:hover {
|
|
1939
|
+
color: #ccc;
|
|
1940
|
+
}
|
|
1941
|
+
.view-toggle button.active {
|
|
1942
|
+
color: #fff;
|
|
1943
|
+
border-bottom-color: #7c3aed;
|
|
1944
|
+
background: rgba(124, 58, 237, 0.14);
|
|
1945
|
+
font-weight: 600;
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
.precise-video-wrap {
|
|
1949
|
+
display: flex;
|
|
1950
|
+
flex-direction: column;
|
|
1951
|
+
gap: 14px;
|
|
1952
|
+
}
|
|
1953
|
+
.precise-merchant-card {
|
|
1954
|
+
background: #1a1a24;
|
|
1955
|
+
border: 1px solid #2a2a3a;
|
|
1956
|
+
border-radius: 10px;
|
|
1957
|
+
padding: 12px 14px;
|
|
1958
|
+
}
|
|
1959
|
+
.precise-merchant-head {
|
|
1960
|
+
display: flex;
|
|
1961
|
+
flex-wrap: wrap;
|
|
1962
|
+
align-items: baseline;
|
|
1963
|
+
gap: 10px;
|
|
1964
|
+
margin-bottom: 10px;
|
|
1965
|
+
}
|
|
1966
|
+
.precise-merchant-head .pm-user {
|
|
1967
|
+
font-weight: 700;
|
|
1968
|
+
color: #60a5fa;
|
|
1969
|
+
text-decoration: none;
|
|
1970
|
+
font-size: 15px;
|
|
1971
|
+
}
|
|
1972
|
+
.precise-merchant-head .pm-user:hover {
|
|
1973
|
+
text-decoration: underline;
|
|
1974
|
+
}
|
|
1975
|
+
.precise-merchant-head .pm-nick {
|
|
1976
|
+
color: #ccc;
|
|
1977
|
+
font-size: 13px;
|
|
1978
|
+
}
|
|
1979
|
+
.precise-merchant-head .pm-country {
|
|
1980
|
+
font-size: 11px;
|
|
1981
|
+
background: #2a2a3a;
|
|
1982
|
+
color: #a78bfa;
|
|
1983
|
+
padding: 1px 7px;
|
|
1984
|
+
border-radius: 10px;
|
|
1985
|
+
}
|
|
1986
|
+
.precise-merchant-head .pm-stats {
|
|
1987
|
+
color: #888;
|
|
1988
|
+
font-size: 12px;
|
|
1989
|
+
margin-left: auto;
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1992
|
+
.precise-video-grid {
|
|
1993
|
+
display: grid;
|
|
1994
|
+
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
|
1995
|
+
gap: 10px;
|
|
1996
|
+
}
|
|
1997
|
+
.precise-video-item {
|
|
1998
|
+
background: #20202e;
|
|
1999
|
+
border: 1px solid #2c2c3c;
|
|
2000
|
+
border-radius: 8px;
|
|
2001
|
+
overflow: hidden;
|
|
2002
|
+
position: relative;
|
|
2003
|
+
display: flex;
|
|
2004
|
+
flex-direction: column;
|
|
2005
|
+
}
|
|
2006
|
+
.precise-video-tile {
|
|
2007
|
+
display: block;
|
|
2008
|
+
position: relative;
|
|
2009
|
+
aspect-ratio: 9 / 14;
|
|
2010
|
+
background: linear-gradient(160deg, #2a2a3a 0%, #1c1c28 100%);
|
|
2011
|
+
color: #e0e0e0;
|
|
2012
|
+
text-decoration: none;
|
|
2013
|
+
overflow: hidden;
|
|
2014
|
+
}
|
|
2015
|
+
.precise-video-tile:hover {
|
|
2016
|
+
border-color: #7c3aed;
|
|
2017
|
+
}
|
|
2018
|
+
.precise-video-tile .pv-play {
|
|
2019
|
+
position: absolute;
|
|
2020
|
+
left: 50%;
|
|
2021
|
+
top: 50%;
|
|
2022
|
+
transform: translate(-50%, -50%);
|
|
2023
|
+
width: 42px;
|
|
2024
|
+
height: 42px;
|
|
2025
|
+
border-radius: 50%;
|
|
2026
|
+
background: rgba(0, 0, 0, 0.55);
|
|
2027
|
+
display: flex;
|
|
2028
|
+
align-items: center;
|
|
2029
|
+
justify-content: center;
|
|
2030
|
+
font-size: 20px;
|
|
2031
|
+
color: #fff;
|
|
2032
|
+
}
|
|
2033
|
+
.precise-video-tile .pv-count {
|
|
2034
|
+
position: absolute;
|
|
2035
|
+
left: 6px;
|
|
2036
|
+
top: 6px;
|
|
2037
|
+
font-size: 12px;
|
|
2038
|
+
color: #fff;
|
|
2039
|
+
text-shadow: 0 1px 2px #000;
|
|
2040
|
+
}
|
|
2041
|
+
/* 播放量最大的视频:播放量文字特殊高亮 */
|
|
2042
|
+
.precise-video-tile .pv-count-max {
|
|
2043
|
+
color: #1a1a24;
|
|
2044
|
+
font-weight: 800;
|
|
2045
|
+
background: #fbbf24;
|
|
2046
|
+
padding: 1px 5px;
|
|
2047
|
+
border-radius: 4px;
|
|
2048
|
+
text-shadow: none;
|
|
2049
|
+
box-shadow: 0 0 6px rgba(251, 191, 36, 0.8);
|
|
2050
|
+
}
|
|
2051
|
+
.precise-video-tile .pv-time {
|
|
2052
|
+
position: absolute;
|
|
2053
|
+
left: 6px;
|
|
2054
|
+
top: 24px;
|
|
2055
|
+
font-size: 11px;
|
|
2056
|
+
color: #cfcfcf;
|
|
2057
|
+
text-shadow: 0 1px 2px #000;
|
|
2058
|
+
}
|
|
2059
|
+
.precise-video-tile .pv-ad {
|
|
2060
|
+
position: absolute;
|
|
2061
|
+
right: 6px;
|
|
2062
|
+
top: 6px;
|
|
2063
|
+
font-size: 10px;
|
|
2064
|
+
background: rgba(254, 44, 85, 0.9);
|
|
2065
|
+
color: #fff;
|
|
2066
|
+
padding: 1px 5px;
|
|
2067
|
+
border-radius: 4px;
|
|
2068
|
+
}
|
|
2069
|
+
/* 商品名悬浮在图片上,最多 2 行、固定高度以容纳 2 行文字 */
|
|
2070
|
+
.precise-video-item .pv-product {
|
|
2071
|
+
position: absolute;
|
|
2072
|
+
left: 0;
|
|
2073
|
+
right: 0;
|
|
2074
|
+
bottom: 0;
|
|
2075
|
+
padding: 30px 8px 8px;
|
|
2076
|
+
background: linear-gradient(to top, rgba(0, 0, 0, 0.92), rgba(0, 0, 0, 0));
|
|
2077
|
+
color: #fbbf24;
|
|
2078
|
+
font-size: 12px;
|
|
2079
|
+
line-height: 1.35;
|
|
2080
|
+
text-decoration: none;
|
|
2081
|
+
text-align: left;
|
|
2082
|
+
display: -webkit-box;
|
|
2083
|
+
-webkit-box-orient: vertical;
|
|
2084
|
+
-webkit-line-clamp: 2;
|
|
2085
|
+
line-clamp: 2;
|
|
2086
|
+
overflow: hidden;
|
|
2087
|
+
box-sizing: border-box;
|
|
2088
|
+
height: 71px;
|
|
2089
|
+
}
|
|
2090
|
+
.precise-video-item .pv-product:hover {
|
|
2091
|
+
color: #fff;
|
|
2092
|
+
text-decoration: underline;
|
|
2093
|
+
}
|
|
2094
|
+
.precise-video-item .pv-product .pv-product-icon {
|
|
2095
|
+
margin-right: 4px;
|
|
2096
|
+
}
|