window.__ModuleLoader__.load({ id: "dsh-plugin-douyin-account-detail", factory: (require) => { var module = { exports: {} }; var exports = module.exports; Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); let react = require("react"); let react_dom = require("react-dom"); //#region plugin css(暗色适配:嵌套变量 + 无浅色 fallback,见踩坑手册 3.4) const css = ".dvd_page{position:relative;background:var(--dsw-alias-bg-base);width:100%;min-width:0;height:100%;min-height:0;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family);flex:1;display:grid;grid-template-rows:minmax(0,1fr);overflow:hidden}.dvd_body{display:flex;flex-direction:column;height:100%;min-height:0;padding:16px;box-sizing:border-box;gap:12px}.dvd_toolbar{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.dvd_btn{border:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));border-radius:8px;padding:6px 14px;font:var(--dsw-font-s-14);cursor:pointer;background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated));color:var(--dsw-alias-label-primary);transition:background var(--ds-transition-duration-fast) var(--ds-ease-in-out)}.dvd_btn:hover{background:var(--dsw-alias-interactive-bg-hover)}.dvd_btn:disabled{opacity:.55;cursor:not-allowed}.dvd_btnSm{padding:3px 10px;font-size:12px;border-radius:6px;white-space:nowrap;min-width:70px}.dvd_btnPrimary{background:var(--dsw-alias-brand-primary,#4d6bfe);color:#fff;border-color:transparent}.dvd_btnPrimary:hover{background:var(--dsw-alias-brand-primary-hover,#3a55d6)}.dvd_mask{position:fixed;inset:0;background:rgba(0,0,0,.4);display:flex;align-items:flex-start;justify-content:center;padding-top:26vh;z-index:100}.dvd_modal{background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated));border-radius:12px;padding:20px;min-width:320px;max-width:480px;width:min(480px,92vw);box-shadow:0 8px 30px rgba(0,0,0,.2)}.dvd_modalTitle{font-size:16px;font-weight:600;margin-bottom:12px;color:var(--dsw-alias-label-primary)}.dvd_modalText{font:var(--dsw-font-s-14);color:var(--dsw-alias-label-secondary);margin-bottom:16px;line-height:1.7;word-break:break-all;display:flex;flex-direction:column;gap:6px}.dvd_modalBtns{display:flex;justify-content:flex-end;gap:8px}.dvd_title{font:var(--dsw-font-l-16);font-weight:600;color:var(--dsw-alias-label-primary)}.dvd_link{color:var(--dsw-alias-accent-fg);cursor:pointer;text-decoration:none;border-bottom:1px dashed var(--dsw-alias-accent-fg,transparent)}.dvd_link:hover{text-decoration:underline}.dvd_badge{display:inline-block;padding:2px 10px;border-radius:999px;font:var(--dsw-font-s-12);line-height:20px}.dvd_badgeSigned{background:var(--dsw-alias-accent-bg-subtle,var(--dsw-alias-accent-bg-soft));color:var(--dsw-alias-accent-fg)}.dvd_badgeExternal{background:var(--dsw-alias-ok-bg-subtle,var(--dsw-alias-ok-bg-soft));color:var(--dsw-alias-ok-fg)}.dvd_tabsCard{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;border:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));border-radius:8px;background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated));overflow:hidden}.dvd_infoGrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:10px 18px;flex:0 0 auto;margin-bottom:12px}.dvd_infoItem{display:flex;flex-direction:column;gap:4px;min-width:0}.dvd_infoItemWide{grid-column:1/-1}.dvd_infoLabel{font:var(--dsw-font-s-12);color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary))}.dvd_infoValue{font:var(--dsw-font-s-14);color:var(--dsw-alias-label-primary);word-break:break-word;white-space:pre-wrap;line-height:1.6;max-height:140px;overflow-y:auto}.dvd_tableWrap{flex:1;min-height:0;overflow:auto;border:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));border-radius:8px}.dvd_table{width:100%;border-collapse:collapse;font:var(--dsw-font-s-14);min-width:1180px}.dvd_table th{position:sticky;top:0;background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated));text-align:left;padding:8px 10px;border-bottom:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));font-weight:600;white-space:nowrap;user-select:none}.dvd_table td{padding:8px 10px;border-bottom:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));vertical-align:middle;white-space:nowrap;max-width:260px;overflow:hidden;text-overflow:ellipsis;line-height:1.5}.dvd_table td.dvd_cellTitle{max-width:300px}.dvd_pager{display:flex;align-items:center;gap:12px;flex:0 0 auto;margin-top:10px}.dvd_pagerInfo{font:var(--dsw-font-s-13);color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary))}.dvd_loading{display:flex;align-items:center;justify-content:center;height:100%;color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary))}.dvd_error{display:flex;align-items:center;justify-content:center;height:100%;color:var(--dsw-alias-danger-fg,var(--dsw-alias-error-fg))}.dvd_empty{display:flex;align-items:center;justify-content:center;height:100%;color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary))}.dvd_tabs{display:flex;align-items:stretch;border-bottom:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));padding:0 8px;gap:2px}.dvd_tab{background:none;border:none;padding:10px 16px;font:var(--dsw-font-s-14);color:var(--dsw-alias-label-secondary);cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1px;transition:color var(--ds-transition-duration-fast) var(--ds-ease-in-out)}.dvd_tab:hover{color:var(--dsw-alias-label-primary)}.dvd_tabActive{color:var(--dsw-alias-accent-fg);font-weight:600;border-bottom-color:var(--dsw-alias-accent-fg)}.dvd_tabPanel{flex:1;min-height:0;display:flex;flex-direction:column;padding:14px 16px;box-sizing:border-box;overflow:hidden}.dvd_personaPanel{overflow:auto;padding-right:6px}.dvd_tabState{padding:28px 16px;text-align:center;color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));font:var(--dsw-font-s-14)}.dvd_md{font:var(--dsw-font-s-14);line-height:1.75;color:var(--dsw-alias-label-primary);word-break:break-word}.dvd_md h1,.dvd_md h2,.dvd_md h3,.dvd_md h4{margin:18px 0 8px;font-weight:600;line-height:1.4}.dvd_md h1{font-size:20px;margin-top:26px}.dvd_md h2{font-size:17px;margin-top:24px}.dvd_md h3{font-size:15px;margin-top:20px}.dvd_md h4{font-size:14px;margin-top:18px}.dvd_md p{margin:8px 0}.dvd_md ul,.dvd_md ol{margin:8px 0;padding-left:22px}.dvd_md li{margin:2px 0}.dvd_md a{color:var(--dsw-alias-accent-fg);text-decoration:none;border-bottom:1px dashed var(--dsw-alias-accent-fg,transparent)}.dvd_md a:hover{text-decoration:underline}.dvd_md strong{font-weight:600}.dvd_md em{font-style:italic}.dvd_md code{font-family:var(--dsw-font-mono,monospace);font-size:.9em;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));border-radius:4px;padding:1px 5px}.dvd_md pre{background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));border-radius:8px;padding:12px;overflow:auto;margin:10px 0;line-height:1.55}.dvd_md pre code{background:none;border:none;padding:0;display:block;white-space:pre;font-size:13px}.dvd_md blockquote{margin:10px 0;padding:8px 14px;border-left:3px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-base);border-radius:0 8px 8px 0}.dvd_md blockquote p{margin:4px 0}.dvd_md table{border-collapse:collapse;margin:10px 0;width:100%;font:var(--dsw-font-s-13)}.dvd_md th,.dvd_md td{border:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));padding:7px 10px;text-align:left;vertical-align:top;line-height:1.6}.dvd_md th{background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated));font-weight:600;white-space:nowrap}.dvd_md th:first-child,.dvd_md td:first-child{width:18%;min-width:96px;max-width:220px;word-break:break-word}.dvd_md hr{border:none;border-top:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));margin:14px 0}"; const key = "dsh-plugin-douyin-account-detail/Detail.module.css"; if (!document.querySelector("style[data-plugin-css=" + JSON.stringify(key) + "]")) { const el = document.createElement("style"); el.dataset.plugin = "dsh-plugin-douyin-account-detail"; el.dataset.pluginCss = key; el.textContent = css; if (!document.querySelector('style[data-plugin-css="dvd-align"]')) { const el2 = document.createElement("style"); el2.dataset.pluginCss = "dvd-align"; el2.textContent = ".dvd_table thead th:last-child{text-align:center!important}.dvd_table tbody td:last-child{text-align:center!important}"; document.head.appendChild(el2); } document.head.appendChild(el); } const cssMap = { "page": "dvd_page", "body": "dvd_body", "toolbar": "dvd_toolbar", "btn": "dvd_btn", "btnSm": "dvd_btnSm", "btnPrimary": "dvd_btnPrimary", "mask": "dvd_mask", "modal": "dvd_modal", "modalTitle": "dvd_modalTitle", "modalText": "dvd_modalText", "modalBtns": "dvd_modalBtns", "title": "dvd_title", "link": "dvd_link", "badge": "dvd_badge", "badgeSigned": "dvd_badgeSigned", "badgeExternal": "dvd_badgeExternal", "infoCard": "dvd_tabsCard", "infoGrid": "dvd_infoGrid", "infoItem": "dvd_infoItem", "infoItemWide": "dvd_infoItemWide", "infoLabel": "dvd_infoLabel", "infoValue": "dvd_infoValue", "tabs": "dvd_tabs", "tab": "dvd_tab", "tabActive": "dvd_tabActive", "tabPanel": "dvd_tabPanel", "personaPanel": "dvd_personaPanel", "tabState": "dvd_tabState", "md": "dvd_md", "tableWrap": "dvd_tableWrap", "table": "dvd_table", "cellTitle": "dvd_cellTitle", "pager": "dvd_pager", "pagerInfo": "dvd_pagerInfo", "loading": "dvd_loading", "error": "dvd_error", "empty": "dvd_empty" }; //#endregion //#region 极简 markdown 渲染(账号设定 tab:无第三方依赖,渲染为 React 元素避免 XSS) // 行内语法:**粗体** / *斜体* / `行内代码` / [链接](url)(仅允许 http/https/mailto) const INLINE_RE = /(\*\*[^*]+\*\*|\*[^*]+\*|`[^`]+`|\[[^\]]+\]\([^)]+\))/g; // 文本段:含 \n 时拆为文本 +
(段落内换行在 HTML 中默认折叠为空格,需显式换行) function textWithBreaks(seg, keyBase) { if (!seg.includes("\n")) return [seg]; const parts = seg.split("\n"); const out = []; parts.forEach((p, i) => { if (p) out.push(p); if (i < parts.length - 1) out.push(react.createElement("br", { key: keyBase + "-br" + i })); }); return out; } function parseInline(text, keyBase) { if (!text) return [text]; const nodes = []; let last = 0, k = 0; for (const m of text.matchAll(INLINE_RE)) { if (m.index > last) nodes.push(...textWithBreaks(text.slice(last, m.index), keyBase + "-t" + k)); const tok = m[0]; if (tok.startsWith("**") && tok.endsWith("**") && tok.length > 4) { nodes.push(react.createElement("strong", { key: keyBase + "-b" + k }, tok.slice(2, -2))); } else if (tok.startsWith("*") && tok.endsWith("*") && tok.length > 2) { nodes.push(react.createElement("em", { key: keyBase + "-i" + k }, tok.slice(1, -1))); } else if (tok.startsWith("`") && tok.endsWith("`") && tok.length > 2) { nodes.push(react.createElement("code", { key: keyBase + "-c" + k }, tok.slice(1, -1))); } else if (tok.startsWith("[")) { const mm = tok.match(/^\[([^\]]+)\]\(([^)]+)\)$/); const href = mm && /^(https?:|mailto:)/i.test(mm[2]) ? mm[2] : null; nodes.push(href ? react.createElement("a", { key: keyBase + "-a" + k, href: href, target: "_blank", rel: "noopener noreferrer" }, mm[1]) : (mm ? mm[1] : tok)); } else { nodes.push(tok); } last = m.index + tok.length; k++; } if (last < text.length) nodes.push(...textWithBreaks(text.slice(last), keyBase + "-t" + k)); return nodes; } // 表格块:rows 为已去掉分隔行的字符串数组 function mdTable(rows, keyBase) { const split = (r) => r.replace(/^\|/, "").replace(/\|$/, "").split("|").map((c) => c.trim()); const head = split(rows[0]); const body = rows.slice(1).map(split); const firstColStyle = { width: "88px", maxWidth: "88px", minWidth: 0, wordBreak: "break-word" }; return react.createElement("table", { key: keyBase, style: { width: "100%" } }, react.createElement("thead", null, react.createElement("tr", null, head.map((h, i) => react.createElement("th", { key: "h" + i, style: i === 0 ? firstColStyle : undefined }, parseInline(h, keyBase + "-h" + i))))), react.createElement("tbody", null, body.map((row, ri) => react.createElement("tr", { key: "r" + ri }, row.map((c, ci) => react.createElement("td", { key: "c" + ci, style: ci === 0 ? firstColStyle : undefined }, parseInline(c, keyBase + "-c" + ri + "-" + ci))))))); } // 块级解析:标题/代码块/引用/表格/列表/分隔线/段落 function mdToReact(md) { if (typeof md !== "string" || !md.trim()) return null; const lines = md.replace(/\r\n/g, "\n").split("\n"); const out = []; let i = 0, k = 0; while (i < lines.length) { const t = lines[i].trim(); // 围栏代码块 const fence = t.match(/^```(\S*)\s*$/); if (fence) { const buf = []; i++; while (i < lines.length && !/^```\s*$/.test(lines[i].trim())) { buf.push(lines[i]); i++; } i++; out.push(react.createElement("pre", { key: "md" + k++ }, react.createElement("code", null, buf.join("\n")))); continue; } // 标题 const h = t.match(/^(#{1,4})\s+(.*)$/); if (h) { out.push(react.createElement("h" + h[1].length, { key: "md" + k++ }, parseInline(h[2], "h" + k))); i++; continue; } // 分隔线 if (/^(-{3,}|\*{3,}|_{3,})\s*$/.test(t)) { out.push(react.createElement("hr", { key: "md" + k++ })); i++; continue; } // 引用 if (t.startsWith(">")) { const buf = []; while (i < lines.length && lines[i].trim().startsWith(">")) { buf.push(lines[i].trim().replace(/^>\s?/, "")); i++; } out.push(react.createElement("blockquote", { key: "md" + k++ }, mdToReact(buf.join("\n")))); continue; } // 表格:当前行含 | 且下一行为 |---| 分隔行 if (t.includes("|") && i + 1 < lines.length && /^\s*\|?[\s:|-]+\|?\s*$/.test(lines[i + 1]) && lines[i + 1].includes("-")) { const sep = lines[i + 1].replace(/\s/g, ""); if (/^\|?(:?-+:?\|)+:?-+:?\|?$/.test(sep)) { const buf = [t]; i += 2; while (i < lines.length && lines[i].trim().includes("|")) { buf.push(lines[i].trim()); i++; } out.push(mdTable(buf, "md" + k++)); continue; } } // 无序列表 const ul = t.match(/^[-*+]\s+(.*)$/); if (ul) { const items = []; while (i < lines.length) { const mt = lines[i].trim().match(/^[-*+]\s+(.*)$/); if (mt) { items.push(mt[1]); i++; continue; } break; } out.push(react.createElement("ul", { key: "md" + k++ }, items.map((it, ii) => react.createElement("li", { key: ii }, parseInline(it, "li" + k + "-" + ii))))); continue; } // 有序列表 const ol = t.match(/^\d+[.)]\s+(.*)$/); if (ol) { const items = []; while (i < lines.length) { const mt = lines[i].trim().match(/^\d+[.)]\s+(.*)$/); if (mt) { items.push(mt[1]); i++; continue; } break; } out.push(react.createElement("ol", { key: "md" + k++ }, items.map((it, ii) => react.createElement("li", { key: ii }, parseInline(it, "li" + k + "-" + ii))))); continue; } // 空行跳过 if (t === "") { i++; continue; } // 段落:合并连续普通行(不跨空行) const para = [t]; i++; while (i < lines.length) { const nt = lines[i].trim(); if (nt === "" || /^(#{1,4}\s|```|>|[-*+]\s|\d+[.)]\s|.*\|)/.test(nt) || /^(-{3,}|\*{3,}|_{3,})\s*$/.test(nt)) break; para.push(nt); i++; } out.push(react.createElement("p", { key: "md" + k++ }, parseInline(para.join("\n"), "p" + k))); } return out; } //#endregion //#region 账号详情页 // 统一线性 SVG 图标(Lucide 风格:24 viewBox、stroke currentColor、圆角端点) const SvgBase = ({ d, size = 16 }) => react.createElement("svg", { viewBox: "0 0 24 24", width: size, height: size, fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", style: { verticalAlign: "-2px" }, }, d); const IconDownload = ({ size }) => react.createElement(SvgBase, { size, d: [react.createElement("path", { key: "p1", d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }), react.createElement("polyline", { key: "p2", points: "7 10 12 15 17 10" }), react.createElement("line", { key: "p3", x1: "12", y1: "15", x2: "12", y2: "3" })] }); const IconChart = ({ size }) => react.createElement(SvgBase, { size, d: [react.createElement("path", { key: "p1", d: "M3 3v18h18" }), react.createElement("path", { key: "p2", d: "M18 17V9" }), react.createElement("path", { key: "p3", d: "M13 17V5" }), react.createElement("path", { key: "p4", d: "M8 17v-3" })] }); const fmt = (v) => (v == null || v === "") ? "-" : String(v); // 时长格式化:秒 → 「X分Y秒」(不足 1 分钟显示「X秒」) const fmtDuration = (v) => { const s = Number(v); if (!Number.isFinite(s) || s <= 0) return "-"; const m = Math.floor(s / 60); const sec = Math.round(s % 60); return m > 0 ? `${m}分${sec}秒` : `${sec}秒`; }; // 视频表格列 const COLUMNS = [ ["序号", (r, i) => String(i)], ["视频ID", (r) => r.aweme_id], ["视频标题", (r) => r.video_title], ["点赞", (r) => r.like_display || fmt(r.like_count)], ["评论", (r) => fmt(r.comment_count)], ["分享", (r) => fmt(r.share_count)], ["收藏", (r) => fmt(r.collect_count)], ["播放", (r) => (r.play_count == null || Number(r.play_count) === 0) ? "-" : fmt(r.play_count)], ["时长", (r) => fmtDuration(r.duration)], ["发布时间", (r) => fmt(r.publish_time)], ["标签", (r) => fmt(r.tags)] ]; //#region V1.0 同步:视频互动趋势(lieflat-charts 风格内联 SVG 双序列折线;不引第三方图表库) // 数据 = 该账号全部视频(分页循环拉取,独立于列表分页);窗口 = 近半年(按发布时间) const TREND_DAYS = 180; const TREND_COLOR_LIKE = "#e5484d"; const TREND_COLOR_COMMENT = "#2f6fed"; // 数值混合解析:数字原样;"7.8万"/"500w"/"1.2亿" → 数值 function numOfMix(v) { const s = String(v == null ? "" : v).trim(); if (!s) return 0; const m = s.match(/^([\d.]+)\s*(亿|w|万)?$/); if (!m) return Number(s) || 0; let n = parseFloat(m[1]) || 0; if (m[2] === "亿") n *= 100000000; else if (m[2]) n *= 10000; return n; } function fmtWan(v) { const n = Number(v); if (!Number.isFinite(n) || n <= 0) return "0"; return n >= 10000 ? (n / 10000).toFixed(n >= 1000000 ? 0 : 1) + "万" : String(Math.round(n)); } // videos → 升序趋势点(近半年内有发布时间的视频);不足 2 点返回 [](前端显示空态提示) function buildTrendPoints(videos) { const cutoff = Date.now() - TREND_DAYS * 86400000; const pts = []; for (const v of videos || []) { const ds = String(v.publish_time || "").slice(0, 10); if (!/^\d{4}-\d{2}-\d{2}$/.test(ds)) continue; const t = new Date(ds + "T00:00:00"); if (Number.isNaN(t.getTime()) || t.getTime() < cutoff) continue; pts.push({ d: ds, like: numOfMix(v.like_display || v.like_count), comment: numOfMix(v.comment_count), title: String(v.video_title || "").slice(0, 40) || ds, }); } pts.sort((a, b) => (a.d < b.d ? -1 : a.d > b.d ? 1 : 0)); return pts; } // 内联 SVG 双序列折线(左轴=点赞 右轴=评论,各自归一化到自身量程) function trendSvg(pts) { const W = 640, H = 220, PL = 62, PR = 62, PT = 14, PB = 26; const n = pts.length; if (n < 2) return null; const xs = (i) => PL + (W - PL - PR) * i / (n - 1); const lmax = Math.max.apply(null, pts.map((p) => p.like)); const lmin = Math.min.apply(null, pts.map((p) => p.like)); const cmax = Math.max.apply(null, pts.map((p) => p.comment)); const cmin = Math.min.apply(null, pts.map((p) => p.comment)); const span = (lo, hi) => Math.max(hi - lo, 1); const yl = (v) => PT + (H - PT - PB) * (1 - (v - lmin) / span(lmin, Math.max(lmax, 1))); const yc = (v) => PT + (H - PT - PB) * (1 - (v - cmin) / span(cmin, Math.max(cmax, 1))); const grid = [0.25, 0.5, 0.75].map((g) => { const y = PT + (H - PT - PB) * g; return react.createElement("line", { key: "g" + g, x1: PL, x2: W - PR, y1: y, y2: y, style: { stroke: "var(--dsw-alias-border-l2,#e8e8ea)", strokeWidth: 1 } }); }); const likePts = pts.map((p, i) => `${xs(i).toFixed(1)},${yl(p.like).toFixed(1)}`).join(" "); const cmtPts = pts.map((p, i) => `${xs(i).toFixed(1)},${yc(p.comment).toFixed(1)}`).join(" "); const dots = n <= 40 ? pts.map((p, i) => [ react.createElement("circle", { key: "dl" + i, cx: xs(i), cy: yl(p.like), r: 2.2, fill: TREND_COLOR_LIKE }), react.createElement("circle", { key: "dc" + i, cx: xs(i), cy: yc(p.comment), r: 2.2, fill: TREND_COLOR_COMMENT }), ]) : []; const last = pts[n - 1]; const labelFill = { fill: "var(--dsw-alias-label-secondary,#8a8f98)", fontSize: 10 }; return react.createElement("svg", { viewBox: "0 0 " + W + " " + H, style: { width: "100%", height: "auto", display: "block" }, "aria-label": "互动趋势图" }, grid, react.createElement("polyline", { key: "pl", points: likePts, fill: "none", style: { stroke: TREND_COLOR_LIKE, strokeWidth: 1.8 } }), react.createElement("polyline", { key: "pc", points: cmtPts, fill: "none", style: { stroke: TREND_COLOR_COMMENT, strokeWidth: 1.8 } }), dots, // 左轴:点赞 量程(顶部=最大) react.createElement("text", { x: PL - 6, y: PT + 8, textAnchor: "end", style: Object.assign({ fill: TREND_COLOR_LIKE, fontSize: 10 }, {}) }, fmtWan(lmax)), react.createElement("text", { x: PL - 6, y: H - PB, textAnchor: "end", style: labelFill }, "0"), // 右轴:评论 量程(顶部=最大) react.createElement("text", { x: W - PR + 6, y: PT + 8, textAnchor: "start", style: { fill: TREND_COLOR_COMMENT, fontSize: 10 } }, fmtWan(cmax)), react.createElement("text", { x: W - PR + 6, y: H - PB, textAnchor: "start", style: labelFill }, "0"), // 日期刻度:首/中/尾 react.createElement("text", { x: PL, y: H - 8, textAnchor: "start", style: labelFill }, pts[0].d.slice(5)), react.createElement("text", { x: (PL + W - PR) / 2, y: H - 8, textAnchor: "middle", style: labelFill }, pts[Math.floor((n - 1) / 2)].d.slice(5)), react.createElement("text", { x: W - PR, y: H - 8, textAnchor: "end", style: labelFill }, last.d.slice(5))); } //#endregion function AccountDetailPage({ params }) { const accountId = params && params.accountId != null ? Number(params.accountId) : null; const [account, setAccount] = react.useState(null); const [items, setItems] = react.useState([]); const [total, setTotal] = react.useState(0); const [page, setPage] = react.useState(1); const [pageSize] = react.useState(20); const [loading, setLoading] = react.useState(accountId != null); const [error, setError] = react.useState(null); // 账号信息 / 账号设定 tab 状态(设定内容懒加载,仅首次切换时请求) const [activeTab, setActiveTab] = react.useState("info"); const [personaLoading, setPersonaLoading] = react.useState(false); const [personaError, setPersonaError] = react.useState(null); const [personaData, setPersonaData] = react.useState(null); const [personaLoaded, setPersonaLoaded] = react.useState(false); // V1.0 同步:账号数据分析 tab(读 account_analysis 最新 md;空态引导) const [analysisData, setAnalysisData] = react.useState(null); const [analysisLoading, setAnalysisLoading] = react.useState(false); const [analysisError, setAnalysisError] = react.useState(null); const [analysisLoaded, setAnalysisLoaded] = react.useState(false); const [analysisBusy, setAnalysisBusy] = react.useState(false); const loadAnalysisRef = react.useRef(null); // V1.0 同步:视频互动趋势(近半年点赞/评论折线,数据=该账号全部视频,与分页无关) const [trendPoints, setTrendPoints] = react.useState(null); // 对标创作状态(toast 提示 + 进行中的视频行 + 确认弹窗行) const [toast, setToast] = react.useState(null); const [creating, setCreating] = react.useState(null); const [benchmarkRow, setBenchmarkRow] = react.useState(null); // 视频解析确认弹窗:待解析视频行 const [analyzeRow, setAnalyzeRow] = react.useState(null); // 账号设定提炼:确认弹窗行 + 提炼中状态(每账号独立会话,多账号可并发) const [personaRow, setPersonaRow] = react.useState(null); const [personaBusy, setPersonaBusy] = react.useState(false); const accountIdRef = react.useRef(accountId); accountIdRef.current = accountId; // 当前账号刷新任务状态跟踪(imports 轮询用) const refreshPrevRef = react.useRef(false); // 视频解析任务状态(并发):videoId -> true 表示该视频解析中(每视频独立会话,互不阻塞) const [analyzing, setAnalyzing] = react.useState({}); const analyzingRef = react.useRef({}); analyzingRef.current = analyzing; const pageRef = react.useRef(1); pageRef.current = page; const loadRef = react.useRef(null); const load = (p) => { if (accountId == null) return; setLoading(true); fetch("/mcn/api/account/videos?accountId=" + accountId + "&page=" + p + "&pageSize=" + pageSize) .then((r) => r.json()) .then((d) => { if (!d.ok) throw new Error(d.error || "加载失败"); setAccount(d.account || null); setItems(d.items || []); setTotal(d.total || 0); setPage(d.page || 1); setError(null); }) .catch((e) => setError(e.message)) .finally(() => setLoading(false)); }; loadRef.current = load; // V1.0 同步:拉取该账号全量视频(分页 pageSize=100,上限 6 页)→ 计算趋势点(近半年,独立于列表分页) const loadTrend = () => { if (accountId == null) return; setTrendPoints(null); const acc = []; const fetchPage = (page) => fetch("/mcn/api/account/videos?accountId=" + accountId + "&page=" + page + "&pageSize=100") .then((r) => r.json()) .then((d) => { if (!d.ok) throw new Error(d.error || "加载失败"); acc.push.apply(acc, d.items || []); if (acc.length < (d.total || 0) && page < 6) return fetchPage(page + 1); setTrendPoints(buildTrendPoints(acc)); }) .catch(() => setTrendPoints([])); fetchPage(1); }; // 轮询视频解析任务(5s):某视频从 running → 非 running 即解析完成,自动刷新列表(并发多任务各自独立,完成后各自刷新) react.useEffect(() => { const timer = setInterval(() => { fetch("/mcn/api/task/status") .then((r) => r.json()) .then((d) => { if (!d.ok || !Array.isArray(d.analyze)) return; const running = {}; d.analyze.forEach((a) => { if (a.status === "running") running[a.videoId] = true; }); const prev = analyzingRef.current; const next = {}; let doneAny = false; Object.keys(prev).forEach((id) => { if (running[id]) next[id] = true; else doneAny = true; // 之前解析中 → 现在空闲:该视频解析完成 }); Object.keys(running).forEach((id) => { if (!prev[id]) next[id] = true; }); setAnalyzing(next); if (doneAny) { const p = pageRef.current; setTimeout(() => loadRef.current(p), 300); } // persona:当前账号提炼状态(running→idle 即完成,刷新账号设定数据) const plist = Array.isArray(d.persona) ? d.persona : []; const curId = accountIdRef.current; const pCur = plist.find((p) => p.accountId === curId); const pRunning = !!(pCur && pCur.status === "running"); setPersonaBusy((prev) => { if (pRunning) return true; if (prev && !pRunning) { setTimeout(() => { fetch("/mcn/api/account/persona?accountId=" + accountIdRef.current) .then((r) => r.json()) .then((dd) => { if (dd.ok) { setPersonaData({ content: dd.content, analysisTime: dd.analysisTime }); setPersonaLoaded(true); } }) .catch(() => {}); }, 300); } return false; }); // accountAnalysis:账号数据分析任务(running→idle 即完成,自动刷新数据分析内容) const alist2 = Array.isArray(d.accountAnalysis) ? d.accountAnalysis : []; const aCur = alist2.find((t) => t.accountId === accountIdRef.current); const aRunning = !!(aCur && aCur.status === "running"); setAnalysisBusy((prev) => { if (aRunning) return true; if (prev && !aRunning) { setTimeout(() => { setAnalysisLoaded(false); loadAnalysisRef.current(); }, 300); } return false; }); // imports:当前账号刷新任务(refresh:{accountId})running→idle 即完成,自动刷新账号信息与视频列表 const ilist = Array.isArray(d.imports) ? d.imports : []; const curId2 = accountIdRef.current; const refTask = ilist.find((t) => t.id === "refresh:" + curId2); const refRunning = !!(refTask && refTask.status === "running"); if (refreshPrevRef.current && !refRunning) { const p = pageRef.current; setTimeout(() => loadRef.current(p), 300); } refreshPrevRef.current = refRunning; }) .catch(() => {}); }, 5000); return () => clearInterval(timer); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); react.useEffect(() => { if (!toast) return; const timer = setTimeout(() => setToast(null), 4000); return () => clearTimeout(timer); }, [toast]); react.useEffect(() => { // 切换账号时重置 tab 状态,避免残留上一账号的内容 setActiveTab("info"); setPersonaLoaded(false); setPersonaData(null); setPersonaError(null); setToast(null); setCreating(null); setBenchmarkRow(null); setAnalyzeRow(null); setPersonaRow(null); setPersonaBusy(false); // V1.0 同步:数据分析 tab 状态随账号重置;趋势数据重新拉取 setAnalysisLoaded(false); setAnalysisData(null); setAnalysisError(null); setTrendPoints(null); loadTrend(); loadPersona(); load(1); // eslint-disable-next-line react-hooks/exhaustive-deps }, [accountId]); // 无参数(从工作台直接打开):提示从账号列表进入 if (accountId == null) { return react.createElement("main", { className: cssMap.page, "aria-label": "账号详情" }, react.createElement("div", { className: cssMap.body }, react.createElement("div", { className: cssMap.toolbar }, react.createElement("button", { type: "button", className: cssMap.btn, title: "返回账号列表", onClick: () => window.__mcnNav?.back() }, "←")), react.createElement("div", { className: cssMap.empty }, "请从「热门账号」列表点击账号的抖音号进入详情"))); } const totalPages = Math.max(1, Math.ceil(total / pageSize)); // 加载账号设定信息(读取 account_persona 最新一条 markdown 内容) const loadPersona = () => { setPersonaLoading(true); setPersonaError(null); fetch("/mcn/api/account/persona?accountId=" + accountId) .then((r) => r.json()) .then((d) => { if (!d.ok) throw new Error(d.error || "加载失败"); setPersonaData({ content: d.content, analysisTime: d.analysisTime }); setPersonaLoaded(true); }) .catch((e) => setPersonaError(e.message)) .finally(() => setPersonaLoading(false)); }; // 切换 tab;账号设定内容懒加载(仅首次);数据分析懒加载(仅首次) const switchTab = (tab) => { setActiveTab(tab); if (tab === "info" && !personaLoaded) loadPersona(); if (tab === "analysis" && !analysisLoaded) loadAnalysis(); }; // 加载账号数据分析(读取 account_analysis 最新一条 markdown 内容,V1.0 同步) const loadAnalysis = () => { setAnalysisLoading(true); setAnalysisError(null); fetch("/mcn/api/account/analysis?accountId=" + accountId) .then((r) => r.json()) .then((d) => { if (!d.ok) throw new Error(d.error || "加载失败"); setAnalysisData({ content: d.content, analysisTime: d.analysisTime }); setAnalysisLoaded(true); }) .catch((e) => setAnalysisError(e.message)) .finally(() => setAnalysisLoading(false)); }; loadAnalysisRef.current = loadAnalysis; const tabBtn = (label, value) => react.createElement("button", { type: "button", role: "tab", "aria-selected": activeTab === value, className: cssMap.tab + (activeTab === value ? " " + cssMap.tabActive : ""), onClick: () => switchTab(value), }, label); // 改写脚本:点击按钮 → 确认弹窗 → 账号设定 + 视频选题(只要选题)→ 创作全新故事脚本 // 复用固定会话 dsh_rewrite(无则新建),单任务串行:运行中重复点击会被宿主 409 拒绝 const REWRITE_SESSION = "dsh_rewrite"; const openBenchmark = (row) => setBenchmarkRow(row); const confirmBenchmark = () => { const row = benchmarkRow; if (!row) return; setBenchmarkRow(null); setCreating(row.id); // 账号设定:优先用已加载的缓存,否则按需拉取 const personaP = personaData && personaData.content ? Promise.resolve(personaData.content) : fetch("/mcn/api/account/persona?accountId=" + accountId) .then((r) => r.json()) .then((d) => (d.ok ? (d.content || "") : "")); personaP .then((persona) => { const lines = [ `请为达人「${account.account_name}」改写一条短视频故事脚本。`, ``, `【账号信息】`, `赛道:${account.track || "-"}`, `账号定位:${account.content || "-"}`, ``, `【账号设定】`, persona || "(该账号暂无账号设定文档)", ``, `【视频选题】`, row.topic ? row.topic : `标题:${row.video_title || "-"}\n视频ID:${row.aweme_id || "-"}`, ``, `请基于账号设定与上述视频选题,创作一个全新的故事脚本:`, `- 新故事、新情节,不要复刻原视频;`, `- 输出完整故事脚本(含标题、故事梗概、口播文案/台词);`, `- 不要输出分镜脚本,无需对标分析。`, ].join("\n"); return fetch("/mcn/api/creative", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ prompt: lines, sessionId: REWRITE_SESSION }), }).then((r) => r.json()); }) .then((d) => { if (!d.ok) throw new Error(d.error || "触发失败"); setToast(d.message || "已开始创作,请切换到对应会话查看"); }) .catch((e) => setToast("创作触发失败: " + e.message)) .finally(() => setCreating(null)); }; // 解析视频:确认弹窗 → 每视频独立 AI 会话(mcn-dou-analysis 功能五)MCP 解析并保存/写库;多视频可并发提交 const submitAnalyze = () => { const row = analyzeRow; if (!row) return; setAnalyzeRow(null); // 乐观标记:按钮立即变「解析中…」,轮询会从 task/status 同步真实状态 setAnalyzing((prev) => { const n = Object.assign({}, prev); n[row.id] = true; return n; }); fetch("/mcn/api/video/analyze", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ videoId: row.id }), }) .then((r) => r.json()) .then((d) => { if (!d.ok) { setAnalyzing((prev) => { const n = Object.assign({}, prev); delete n[row.id]; return n; }); throw new Error(d.error || "提交失败"); } setToast(d.message || "已提交解析,AI 正在解析(约 3-5 分钟)"); }) .catch((e) => setToast("解析提交失败: " + e.message)); }; // 提炼账号设定:确认弹窗 → 每账号独立 AI 会话(mcn-dou-analysis 功能三)筛选TOP4→MCP解析→生成人设卡并写库;多账号可并发 const submitPersonaExtract = () => { const row = personaRow; if (!row) return; setPersonaRow(null); setPersonaBusy(true); fetch("/mcn/api/account/persona/extract", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ accountId: row.id }), }) .then((r) => r.json()) .then((d) => { if (!d.ok) { setPersonaBusy(false); throw new Error(d.error || "提交失败"); } setToast(d.message || "已提交提炼账号设定,AI 正在执行(约 10-20 分钟)"); }) .catch((e) => setToast("提炼提交失败: " + e.message)); }; // 发起账号数据分析(V1.0 同步:mcn-dou-analysis 功能四;每账号独立会话,运行中禁重复发起) const submitAccountAnalysis = () => { if (accountId == null) return; if (!window.confirm("对该账号发起账号数据分析(功能四:近 4 个月最好/最差视频逐条拆解 → 8 章汇总报告,约 10-20 分钟)?")) return; setAnalysisBusy(true); fetch("/mcn/api/account/analysis/start", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ accountId: accountId }), }) .then((r) => r.json()) .then((d) => { if (!d.ok) { setAnalysisBusy(false); throw new Error(d.error || "提交失败"); } setToast(d.message || "已提交账号数据分析,AI 正在执行(约 10-20 分钟)"); }) .catch((e) => { setAnalysisBusy(false); setToast("数据分析提交失败: " + e.message); }); }; // 账号信息卡 const infoItem = (label, value, wide) => { const cls = cssMap.infoItem + (wide ? " " + cssMap.infoItemWide : ""); return react.createElement("div", { className: cls, key: label }, react.createElement("div", { className: cssMap.infoLabel }, label), react.createElement("div", { className: cssMap.infoValue }, value)); }; let infoCard = null; let tableArea = null; if (loading) { tableArea = react.createElement("div", { className: cssMap.loading }, "加载中…"); } else if (error) { tableArea = react.createElement("div", { className: cssMap.error }, error); } else { // 视频表格列(追加操作列:已解析(原始解析或拆解分析)→ 改写脚本;均无 → 下载图标占位;拆解详情经视频ID进入) const columns = COLUMNS.concat([[ "操作", (row) => (row.has_source || row.has_analysis) ? react.createElement("button", { type: "button", className: cssMap.btn + " " + cssMap.btnSm, disabled: creating === row.id, title: "基于账号设定与视频选题,改写一条全新的故事脚本", onClick: () => openBenchmark(row), }, creating === row.id ? "创作中…" : "改写脚本") : react.createElement("button", { type: "button", className: cssMap.btn + " " + cssMap.btnSm, disabled: !!analyzing[row.id], title: analyzing[row.id] ? "该视频正在解析中(约 3-5 分钟),可同时提交其他视频解析" : "解析本视频(MCP 解析,保存到本地并写入数据库)", onClick: () => setAnalyzeRow(row), }, analyzing[row.id] ? "解析中…" : react.createElement(IconDownload, {})), ]]); // 视频表格(账号信息 tab 面板内:表格区滚动,分页固定在面板底部) const videoTable = react.createElement("div", { className: cssMap.tableWrap }, items.length === 0 ? react.createElement("div", { className: cssMap.empty }, "暂无视频数据") : react.createElement("table", { className: cssMap.table }, react.createElement("thead", null, react.createElement("tr", null, columns.map((c) => react.createElement("th", { key: c[0] }, c[0])))), react.createElement("tbody", null, items.map((row, idx) => react.createElement("tr", { key: row.id }, columns.map((c) => { const val = c[1](row, (page - 1) * pageSize + idx + 1); const cellCls = c[0] === "视频标题" ? cssMap.cellTitle : ""; // 视频标题:有 video_url 时渲染为新页面链接 let content = val; let tip = c[0] === "视频标题" || c[0] === "标签" ? val : undefined; if (c[0] === "视频标题" && row.video_url) { content = react.createElement("a", { className: cssMap.link, href: row.video_url, target: "_blank", rel: "noopener noreferrer", title: "在新页面打开视频", }, val); tip = row.video_url; } // 视频ID:点击打开视频分析详情页 if (c[0] === "视频ID") { content = react.createElement("span", { className: cssMap.link, title: "查看视频分析详情", onClick: () => window.__mcnNav?.open("douyin-video-detail", { videoId: row.id, awemeId: row.aweme_id, accountId: accountId }), }, val); tip = "查看视频分析详情"; } return react.createElement("td", { key: c[0], className: cellCls, title: tip }, content); })))))); const pager = react.createElement("div", { className: cssMap.pager }, react.createElement("span", { className: cssMap.pagerInfo }, `共 ${total} 条视频`), react.createElement("span", { className: cssMap.pagerInfo }, `第 ${page} / ${totalPages} 页`), react.createElement("button", { type: "button", className: cssMap.btn, disabled: page <= 1 || loading, onClick: () => load(page - 1) }, "上一页"), react.createElement("button", { type: "button", className: cssMap.btn, disabled: page >= totalPages || loading, onClick: () => load(page + 1) }, "下一页")); // V1.0 同步:数据分析 tab 面板(account_analysis 最新 md;空态引导) const analysisPanel = react.createElement("div", { className: cssMap.tabPanel + " " + cssMap.personaPanel }, analysisLoading ? react.createElement("div", { className: cssMap.tabState }, "加载中…") : analysisError ? react.createElement("div", { className: cssMap.tabState }, "加载失败:" + analysisError) : analysisData && analysisData.content ? react.createElement("div", { className: cssMap.md }, mdToReact(analysisData.content)) : react.createElement("div", { className: cssMap.tabState }, react.createElement("div", null, "该账号暂无账号数据分析"), react.createElement("div", { style: { marginTop: 8, fontSize: 12, lineHeight: 1.6, opacity: 0.85 } }, "数据分析(账号内容结构拆解与定位诊断报告)生成后在此展示。可在对话中发起「分析这个账号的视频数据」,将按 mcn-dou-analysis 功能四执行并自动落库。")), react.createElement("button", { type: "button", className: cssMap.btn + " " + cssMap.btnPrimary, style: { marginTop: 12, alignSelf: "flex-start" }, disabled: analysisBusy || accountId == null, title: analysisBusy ? "该账号数据分析任务执行中(约 10-20 分钟),可先处理其他账号" : "调用抖音账号分析技能执行账号数据分析(功能四:样本拆解 → 8 章报告)", onClick: submitAccountAnalysis }, analysisBusy ? "分析中…" : "发起账号数据分析"), ); // V1.0 同步:视频互动趋势区块(近半年点赞/评论,lieflat 风格内联 SVG;不足 2 点或加载中不显示) const trendBlock = trendPoints == null ? null : trendPoints.length >= 2 ? react.createElement("div", { style: { border: "1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border))", borderRadius: 8, padding: "8px 12px 6px", marginBottom: 12, background: "var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated))" } }, react.createElement("div", { style: { display: "flex", alignItems: "center", gap: 14, marginBottom: 4, fontSize: 12 } }, react.createElement("span", { style: { color: "var(--dsw-alias-label-primary)" } }, "互动趋势"), react.createElement("span", { style: { display: "inline-flex", alignItems: "center", gap: 4, color: "#e5484d" } }, "● 点赞"), react.createElement("span", { style: { display: "inline-flex", alignItems: "center", gap: 4, color: "#2f6fed" } }, "● 评论"), react.createElement("span", { style: { marginLeft: "auto", color: "var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary))" } }, "近半年 · 按发布时间")), trendSvg(trendPoints)) : (total > 0 ? react.createElement("div", { style: { fontSize: 12, color: "var(--dsw-alias-label-secondary,#8a8f98)", marginBottom: 12 } }, "近半年互动数据不足 2 条,暂不展示趋势") : null); if (account) { infoCard = react.createElement("div", { className: cssMap.infoCard }, react.createElement("div", { className: cssMap.tabs, role: "tablist" }, tabBtn("账号信息", "info"), tabBtn("视频列表", "persona"), tabBtn("数据分析", "analysis")), activeTab === "info" ? react.createElement("div", { className: cssMap.tabPanel }, react.createElement("div", { className: cssMap.infoGrid }, infoItem("抖音号", fmt(account.douyin_id)), infoItem("赛道", fmt(account.track)), infoItem("AI制作", account.is_ai === "是" ? "是" : "否"), infoItem("粉丝", fmt(account.followers)), infoItem("获赞", fmt(account.total_likes)), infoItem("作品数", fmt(account.works_count)), infoItem("地区", fmt(account.location)), infoItem("年龄", fmt(account.age)), infoItem("获取视频", fmt(total)), infoItem("简介", fmt(account.bio), true), infoItem("定位/内容", fmt(account.content), true)), react.createElement("div", { style: { margin: "18px 0 10px", paddingTop: 12, borderTop: "1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border))", fontWeight: 600, fontSize: 15, color: "var(--dsw-alias-label-primary)" } }, "账号设定"), personaLoading ? react.createElement("div", { className: cssMap.tabState }, "加载中…") : personaError ? react.createElement("div", { className: cssMap.tabState }, "加载失败:" + personaError) : personaData && personaData.content ? react.createElement("div", { className: cssMap.md }, mdToReact(personaData.content)) : react.createElement("div", { className: cssMap.tabState }, react.createElement("div", null, "该账号暂无账号设定信息"), react.createElement("button", { type: "button", className: cssMap.btn + " " + cssMap.btnPrimary, style: { marginTop: 12 }, disabled: personaBusy, title: personaBusy ? "该账号正在提炼账号设定中(约 10-20 分钟),可先处理其他账号" : "调用抖音账号分析技能执行提炼账号设定(功能三:筛选TOP4→MCP解析→生成人设卡)", onClick: () => setPersonaRow(account), }, personaBusy ? "提炼中…" : "提炼账号设定"))) : activeTab === "analysis" ? analysisPanel : react.createElement("div", { className: cssMap.tabPanel }, trendBlock, videoTable, pager)); } } return react.createElement("main", { className: cssMap.page, "aria-label": "账号详情" }, react.createElement("div", { className: cssMap.body }, react.createElement("div", { className: cssMap.toolbar }, react.createElement("button", { type: "button", className: cssMap.btn, title: "返回账号列表", onClick: () => window.__mcnNav?.back() }, "←"), account ? react.createElement("span", { className: cssMap.title }, account.account_name || "账号详情") : react.createElement("span", { className: cssMap.title }, "账号详情"), toast ? react.createElement("span", { className: cssMap.pagerInfo }, toast) : null), infoCard || tableArea), benchmarkRow && react.createElement("div", { className: cssMap.mask, onClick: () => setBenchmarkRow(null) }, react.createElement("div", { className: cssMap.modal, onClick: (e) => e.stopPropagation() }, react.createElement("div", { className: cssMap.modalTitle }, "确认改写脚本"), react.createElement("div", { className: cssMap.modalText }, react.createElement("div", null, "视频:", benchmarkRow.video_title || "-"), react.createElement("div", null, "视频ID:", benchmarkRow.aweme_id || "-"), react.createElement("div", null, `将基于账号设定与该视频选题,在会话 dsh_rewrite 中创作一条全新的故事脚本(非分镜、不对标分析)。同一会话串行执行,运行中不可再次触发。`)), react.createElement("div", { className: cssMap.modalBtns }, react.createElement("button", { type: "button", className: cssMap.btn, onClick: () => setBenchmarkRow(null) }, "取消"), react.createElement("button", { type: "button", className: cssMap.btn + " " + cssMap.btnPrimary, onClick: confirmBenchmark }, "确认创作")))), analyzeRow && react.createElement("div", { className: cssMap.mask, onClick: () => setAnalyzeRow(null) }, react.createElement("div", { className: cssMap.modal, onClick: (e) => e.stopPropagation() }, react.createElement("div", { className: cssMap.modalTitle }, "确认解析视频"), react.createElement("div", { className: cssMap.modalText }, react.createElement("div", null, "视频:", analyzeRow.video_title || "-"), react.createElement("div", null, "视频ID:", analyzeRow.aweme_id || "-"), react.createElement("div", null, "将调用抖音账号分析技能解析本视频(MCP 解析,约 3-5 分钟),解析结果保存到本地并写入数据库。确认解析?")), react.createElement("div", { className: cssMap.modalBtns }, react.createElement("button", { type: "button", className: cssMap.btn, onClick: () => setAnalyzeRow(null) }, "取消"), react.createElement("button", { type: "button", className: cssMap.btn + " " + cssMap.btnPrimary, onClick: submitAnalyze }, "确认解析")))), personaRow && react.createElement("div", { className: cssMap.mask, onClick: () => setPersonaRow(null) }, react.createElement("div", { className: cssMap.modal, onClick: (e) => e.stopPropagation() }, react.createElement("div", { className: cssMap.modalTitle }, "确认提炼账号设定"), react.createElement("div", { className: cssMap.modalText }, react.createElement("div", null, "账号:", personaRow.account_name || "-"), react.createElement("div", null, "将调用抖音账号分析技能执行提炼账号设定(功能三:筛选 TOP4 视频 → MCP 解析 → 生成人设卡,约 10-20 分钟),结果保存到本地并写入数据库。确认执行?")), react.createElement("div", { className: cssMap.modalBtns }, react.createElement("button", { type: "button", className: cssMap.btn, onClick: () => setPersonaRow(null) }, "取消"), react.createElement("button", { type: "button", className: cssMap.btn + " " + cssMap.btnPrimary, onClick: submitPersonaExtract }, "确认提炼"))))); } //#endregion //#region plugin entry:注册到 MCN 工作台入口注册表(hidden:不显示在工作台网格,仅程序化导航打开) const inject = []; function apply() { window.__mcnEntries = window.__mcnEntries || []; // 幂等:避免插件热重载重复注册 if (!window.__mcnEntries.some((e) => e.id === "douyin-account-detail")) { window.__mcnEntries.push({ id: "douyin-account-detail", icon: IconChart, title: "账号详情", desc: "账号信息与视频列表(从账号列表进入)", hidden: true, component: AccountDetailPage, }); } } //#endregion exports.apply = apply; exports.inject = inject; return module.exports; } });