feat: add sortable market metric columns
This commit is contained in:
@@ -93,4 +93,32 @@ describe("filter-sort-controller", () => {
|
||||
|
||||
expect(result.slice(-2).map((record) => record.authorId)).toEqual(["c", "d"]);
|
||||
});
|
||||
|
||||
test("sorts by backend metric descending and keeps empty values at the end", () => {
|
||||
const result = applyFilterAndSort(
|
||||
[
|
||||
{
|
||||
...baseRecords[0],
|
||||
backendMetrics: {
|
||||
afterViewSearchRate: "0.36%"
|
||||
}
|
||||
},
|
||||
{
|
||||
...baseRecords[1],
|
||||
backendMetrics: {
|
||||
afterViewSearchRate: "1.4%"
|
||||
}
|
||||
},
|
||||
baseRecords[2]
|
||||
],
|
||||
{
|
||||
sort: {
|
||||
direction: "desc",
|
||||
field: "afterViewSearchRate"
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
expect(result.map((record) => record.authorId)).toEqual(["b", "a", "c"]);
|
||||
});
|
||||
});
|
||||
|
||||
+1039
-25
File diff suppressed because it is too large
Load Diff
+517
-20
@@ -48,9 +48,22 @@ describe("market-dom-sync", () => {
|
||||
)
|
||||
).not.toBeNull();
|
||||
expect(
|
||||
document.querySelector('[data-market-header-cell="backendMetrics"]')
|
||||
document.querySelector('[data-market-header-cell="afterViewSearchRate"]')
|
||||
).not.toBeNull();
|
||||
expect(document.querySelectorAll("[data-market-row-cell]").length).toBe(6);
|
||||
expect(
|
||||
document.querySelector('[data-market-header-cell="afterViewSearchCount"]')
|
||||
).not.toBeNull();
|
||||
expect(
|
||||
document.querySelector('[data-market-header-cell="a3IncreaseCount"]')
|
||||
).not.toBeNull();
|
||||
expect(
|
||||
document.querySelector('[data-market-header-cell="newA3Rate"]')
|
||||
).not.toBeNull();
|
||||
expect(document.querySelector('[data-market-header-cell="cpa3"]')).not.toBeNull();
|
||||
expect(
|
||||
document.querySelector('[data-market-header-cell="cpSearch"]')
|
||||
).not.toBeNull();
|
||||
expect(document.querySelectorAll("[data-market-row-cell]").length).toBe(16);
|
||||
});
|
||||
|
||||
test("renders loading, success, missing, and failed states", () => {
|
||||
@@ -87,12 +100,15 @@ describe("market-dom-sync", () => {
|
||||
});
|
||||
|
||||
expect(alphaRow.singleCell.textContent).toBe("加载中...");
|
||||
expect(alphaRow.backendMetricsCell.textContent).toBe("加载中...");
|
||||
expect(alphaRow.backendMetricsCells.afterViewSearchRate.textContent).toBe("加载中...");
|
||||
expect(betaRow.singleCell.textContent).toBe("0.5% - 1%");
|
||||
expect(betaRow.personalCell.textContent).toBe("0.02% - 0.1%");
|
||||
expect(betaRow.backendMetricsCell.textContent).toContain("看后搜率");
|
||||
expect(betaRow.backendMetricsCell.textContent).toContain("0.36%");
|
||||
expect(betaRow.backendMetricsCell.textContent).toContain("CPA3");
|
||||
expect(betaRow.backendMetricsCells.afterViewSearchRate.textContent).toBe("0.36%");
|
||||
expect(betaRow.backendMetricsCells.afterViewSearchCount.textContent).toBe("9,689.96");
|
||||
expect(betaRow.backendMetricsCells.a3IncreaseCount.textContent).toBe("78,366.22");
|
||||
expect(betaRow.backendMetricsCells.newA3Rate.textContent).toBe("3.44%");
|
||||
expect(betaRow.backendMetricsCells.cpa3.textContent).toBe("1.79");
|
||||
expect(betaRow.backendMetricsCells.cpSearch.textContent).toBe("14.46");
|
||||
|
||||
renderMarketRowState(betaRow, {
|
||||
authorId: "b",
|
||||
@@ -104,7 +120,8 @@ describe("market-dom-sync", () => {
|
||||
personalVideoAfterSearchRate: "0.02 - 0.1%"
|
||||
}
|
||||
});
|
||||
expect(betaRow.backendMetricsCell.textContent).toBe("暂无数据");
|
||||
expect(betaRow.backendMetricsCells.afterViewSearchRate.textContent).toBe("暂无数据");
|
||||
expect(betaRow.backendMetricsCells.cpSearch.textContent).toBe("暂无数据");
|
||||
|
||||
renderMarketRowState(betaRow, {
|
||||
authorId: "b",
|
||||
@@ -114,7 +131,8 @@ describe("market-dom-sync", () => {
|
||||
});
|
||||
expect(betaRow.singleCell.textContent).toBe("加载失败");
|
||||
expect(betaRow.personalCell.textContent).toBe("加载失败");
|
||||
expect(betaRow.backendMetricsCell.textContent).toBe("加载失败");
|
||||
expect(betaRow.backendMetricsCells.afterViewSearchRate.textContent).toBe("加载失败");
|
||||
expect(betaRow.backendMetricsCells.cpSearch.textContent).toBe("加载失败");
|
||||
});
|
||||
|
||||
test("hides rows outside the visible author ids", () => {
|
||||
@@ -152,27 +170,55 @@ describe("market-dom-sync", () => {
|
||||
throw new Error("Expected market table");
|
||||
}
|
||||
|
||||
expect(readRightHeaderTexts()).toEqual([
|
||||
"21-60s报价",
|
||||
expect(readRightHeaderTexts()).toEqual(["21-60s报价", "操作"]);
|
||||
expect(readPluginHeaderTexts()).toEqual([
|
||||
"单视频看后搜率",
|
||||
"个人视频看后搜率",
|
||||
"秒探指标",
|
||||
"操作"
|
||||
"看后搜率",
|
||||
"看后搜数",
|
||||
"新增A3数",
|
||||
"新增A3率",
|
||||
"CPA3",
|
||||
"cp_search"
|
||||
]);
|
||||
expect(
|
||||
document
|
||||
.querySelector(".section-wrapper.sticky-header")
|
||||
?.classList.contains("hide-scrollbar")
|
||||
).toBe(false);
|
||||
expect(
|
||||
document.querySelector(".section-wrapper:not(.sticky-header)")?.classList.contains(
|
||||
"hide-scrollbar"
|
||||
)
|
||||
).toBe(false);
|
||||
expect(readScrollHintText()).toBe("横向滚动可查看看后搜率、秒探指标");
|
||||
expect(document.querySelectorAll('[data-testid="market-scroll-hint"]')).toHaveLength(1);
|
||||
expect(
|
||||
(
|
||||
document.querySelector('[data-testid="plugin-header"]') as HTMLElement
|
||||
).style.position
|
||||
).not.toBe("sticky");
|
||||
const pluginHeaderCells = Array.from(
|
||||
document.querySelectorAll('[data-testid="plugin-header"] > .header-cell')
|
||||
) as HTMLElement[];
|
||||
expect(pluginHeaderCells[0]?.style.width).toBe("160px");
|
||||
expect(pluginHeaderCells[1]?.style.width).toBe("160px");
|
||||
expect(pluginHeaderCells[0]?.style.whiteSpace).toBe("nowrap");
|
||||
expect(pluginHeaderCells[1]?.style.whiteSpace).toBe("nowrap");
|
||||
expect(
|
||||
Number.parseFloat(
|
||||
(
|
||||
document.querySelector('[data-testid="right-header"]') as HTMLElement
|
||||
).style.width
|
||||
)
|
||||
).toBeGreaterThan(350);
|
||||
).toBe(350);
|
||||
expect(
|
||||
Number.parseFloat(
|
||||
(
|
||||
document.querySelector('[data-testid="right-section"]') as HTMLElement
|
||||
).style.width
|
||||
)
|
||||
).toBeGreaterThan(350);
|
||||
).toBe(350);
|
||||
expect(table.rows.map((row) => row.authorId)).toEqual(["111", "222"]);
|
||||
|
||||
renderMarketRowState(table.rows[0], {
|
||||
@@ -194,13 +240,21 @@ describe("market-dom-sync", () => {
|
||||
}
|
||||
});
|
||||
|
||||
expect(readRightRowTexts(0)).toEqual([
|
||||
"¥450,000",
|
||||
expect(readRightRowTexts(0)).toEqual(["¥450,000", "下单"]);
|
||||
expect(readPluginRowTexts(0)).toEqual([
|
||||
"0.5% - 1%",
|
||||
"0.02% - 0.1%",
|
||||
"看后搜率0.36%看后搜数9,689.96新增A3数78,366.22新增A3率3.44%CPA31.79cp_search14.46",
|
||||
"下单"
|
||||
"0.36%",
|
||||
"9,689.96",
|
||||
"78,366.22",
|
||||
"3.44%",
|
||||
"1.79",
|
||||
"14.46"
|
||||
]);
|
||||
expect(table.rows[0].singleCell.style.width).toBe("160px");
|
||||
expect(table.rows[0].personalCell.style.width).toBe("160px");
|
||||
expect(table.rows[0].singleCell.style.whiteSpace).toBe("nowrap");
|
||||
expect(table.rows[0].personalCell.style.whiteSpace).toBe("nowrap");
|
||||
|
||||
applyRowVisibility(table, new Set(["222"]));
|
||||
|
||||
@@ -211,7 +265,8 @@ describe("market-dom-sync", () => {
|
||||
applyRowOrder(table, ["222", "111"]);
|
||||
|
||||
expect(readAuthorNames()).toEqual(["达人 B", "达人 A"]);
|
||||
expect(readRightRowTexts(0)).toEqual(["¥20,000", "", "", "", "下单"]);
|
||||
expect(readRightRowTexts(0)).toEqual(["¥20,000", "下单"]);
|
||||
expect(readPluginRowTexts(0)).toEqual(["", "", "", "", "", "", "", ""]);
|
||||
expect(table.rows[0].exportFields).toMatchObject({
|
||||
"21-60s报价": "¥450,000",
|
||||
"代表视频": "代表视频A",
|
||||
@@ -219,6 +274,94 @@ describe("market-dom-sync", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("keeps a single scroll hint across repeated syncs", () => {
|
||||
document.body.innerHTML = buildRealMarketGridFixture();
|
||||
|
||||
expect(syncMarketTable(document)).not.toBeNull();
|
||||
expect(syncMarketTable(document)).not.toBeNull();
|
||||
|
||||
expect(document.querySelectorAll('[data-testid="market-scroll-hint"]')).toHaveLength(1);
|
||||
expect(readScrollHintText()).toBe("横向滚动可查看看后搜率、秒探指标");
|
||||
});
|
||||
|
||||
test("uses native-like alignment styles for plugin cells", () => {
|
||||
document.body.innerHTML = buildRealMarketGridFixtureWithScopedAttributes();
|
||||
|
||||
const table = syncMarketTable(document);
|
||||
if (!table) {
|
||||
throw new Error("Expected market table");
|
||||
}
|
||||
|
||||
const pluginHeaderCell = document.querySelector(
|
||||
'[data-testid="plugin-header"] [data-market-header-cell="singleVideoAfterSearchRate"]'
|
||||
) as HTMLElement | null;
|
||||
const pluginBodyCell = table.rows[0].singleCell;
|
||||
|
||||
expect(pluginHeaderCell?.style.display).toBe("flex");
|
||||
expect(pluginHeaderCell?.style.alignItems).toBe("center");
|
||||
expect(pluginBodyCell.style.display).toBe("flex");
|
||||
expect(pluginBodyCell.style.alignItems).toBe("center");
|
||||
expect(pluginBodyCell.style.paddingTop).toBe("12px");
|
||||
expect(pluginBodyCell.style.paddingBottom).toBe("12px");
|
||||
});
|
||||
|
||||
test("keeps native-like alignment styles after repeated syncs", () => {
|
||||
document.body.innerHTML = buildRealMarketGridFixtureWithScopedAttributes();
|
||||
|
||||
expect(syncMarketTable(document)).not.toBeNull();
|
||||
const secondTable = syncMarketTable(document);
|
||||
if (!secondTable) {
|
||||
throw new Error("Expected market table");
|
||||
}
|
||||
|
||||
const pluginBodyCell = secondTable.rows[0].singleCell;
|
||||
expect(pluginBodyCell.style.display).toBe("flex");
|
||||
expect(pluginBodyCell.style.alignItems).toBe("center");
|
||||
expect(pluginBodyCell.style.paddingTop).toBe("12px");
|
||||
expect(pluginBodyCell.style.paddingBottom).toBe("12px");
|
||||
expect(pluginBodyCell.hasAttribute("data-v-cell-scope")).toBe(true);
|
||||
});
|
||||
|
||||
test("keeps export field alignment when a row is missing the price cell", () => {
|
||||
document.body.innerHTML = buildRealMarketGridFixtureWithMissingPriceCell();
|
||||
|
||||
const initialTable = syncMarketTable(document);
|
||||
if (!initialTable) {
|
||||
throw new Error("Expected market table");
|
||||
}
|
||||
|
||||
renderMarketRowState(initialTable.rows[1], {
|
||||
authorId: "222",
|
||||
authorName: "达人 B",
|
||||
backendMetrics: {
|
||||
a3IncreaseCount: "78,366.22",
|
||||
afterViewSearchCount: "9,689.96",
|
||||
afterViewSearchRate: "0.36%",
|
||||
cpSearch: "14.46",
|
||||
cpa3: "1.79",
|
||||
newA3Rate: "3.44%"
|
||||
},
|
||||
backendMetricsStatus: "success",
|
||||
status: "success",
|
||||
rates: {
|
||||
singleVideoAfterSearchRate: "0.5%-1%",
|
||||
personalVideoAfterSearchRate: "0.02 - 0.1%"
|
||||
}
|
||||
});
|
||||
|
||||
const table = syncMarketTable(document);
|
||||
if (!table) {
|
||||
throw new Error("Expected market table after rerender");
|
||||
}
|
||||
|
||||
expect(table.rows[1].exportFields).toMatchObject({
|
||||
"21-60s报价": "",
|
||||
"代表视频": "代表视频B",
|
||||
"达人信息": "达人 B"
|
||||
});
|
||||
expect(table.rows[1].exportFields?.["21-60s报价"]).not.toContain("看后搜率");
|
||||
});
|
||||
|
||||
test("falls back to the market vue state when the DOM has no author id", () => {
|
||||
document.body.innerHTML = buildRealMarketGridFixtureWithoutAuthorIds();
|
||||
attachMarketVueState([
|
||||
@@ -244,6 +387,146 @@ describe("market-dom-sync", () => {
|
||||
expect(table.rows.map((row) => row.authorId)).toEqual(["111", "222"]);
|
||||
});
|
||||
|
||||
test("fills blank export cells from the market vue state", () => {
|
||||
document.body.innerHTML = buildRichMarketGridFixtureWithBlankSecondRow();
|
||||
attachMarketVueState([
|
||||
{
|
||||
attribute_datas: {
|
||||
avg_search_after_view_rate_30d: "0.003",
|
||||
burst_text_rate: "1",
|
||||
city: "温州",
|
||||
content_theme_labels_180d: ["有趣剧情创作", "亲情剧集", "情感短剧"],
|
||||
follower: "4550556",
|
||||
gender: "2",
|
||||
interact_rate_within_30d: "0.0572",
|
||||
link_link_cnt_by_industry: "27029613",
|
||||
nickname: "达人 A",
|
||||
play_over_rate_within_30d: "0.263",
|
||||
price_20_60: "155000",
|
||||
prospective_20_60_cpm: "21.2362",
|
||||
tags_relation: {
|
||||
剧情搞笑: ["剧情"]
|
||||
}
|
||||
},
|
||||
expected_play_num: "7298854",
|
||||
star_id: "111"
|
||||
},
|
||||
{
|
||||
attribute_datas: {
|
||||
avg_search_after_view_rate_30d: "0.003",
|
||||
burst_text_rate: "0",
|
||||
city: "杭州",
|
||||
content_theme_labels_180d: ["搞笑剧情", "大学宿舍趣事", "校园生活"],
|
||||
follower: "901234",
|
||||
gender: "2",
|
||||
interact_rate_within_30d: "0.072",
|
||||
link_link_cnt_by_industry: "20773000",
|
||||
nickname: "达人 B",
|
||||
play_over_rate_within_30d: "0.35",
|
||||
price_20_60: "38000",
|
||||
prospective_20_60_cpm: "182.5",
|
||||
tags_relation: {
|
||||
剧情搞笑: ["剧情"]
|
||||
}
|
||||
},
|
||||
expected_play_num: "208000",
|
||||
star_id: "222"
|
||||
}
|
||||
]);
|
||||
|
||||
const table = syncMarketTable(document);
|
||||
if (!table) {
|
||||
throw new Error("Expected market table");
|
||||
}
|
||||
|
||||
expect(table.rows[1].authorId).toBe("222");
|
||||
expect(table.rows[1].price21To60s).toBe("¥38,000");
|
||||
expect(table.rows[1].exportFields).toMatchObject({
|
||||
"21-60s报价": "¥38,000",
|
||||
互动率: "7.2%",
|
||||
内容主题: "搞笑剧情 大学宿舍趣事 1+",
|
||||
完播率: "35%",
|
||||
爆文率: "-",
|
||||
粉丝数: "90.1w",
|
||||
达人信息: "达人 B 女 杭州",
|
||||
达人类型: "剧情搞笑",
|
||||
连接用户数: "2,077.3w",
|
||||
预期CPM: "182.5",
|
||||
预期播放量: "20.8w"
|
||||
});
|
||||
expect(table.rows[1].rates).toEqual({
|
||||
singleVideoAfterSearchRate: "0.3%"
|
||||
});
|
||||
});
|
||||
|
||||
test("finds market rows in nested vue children", () => {
|
||||
document.body.innerHTML = buildRichMarketGridFixtureWithBlankSecondRow();
|
||||
attachNestedMarketVueState([
|
||||
{
|
||||
attribute_datas: {
|
||||
city: "杭州",
|
||||
follower: "901234",
|
||||
gender: "2",
|
||||
interact_rate_within_30d: "0.072",
|
||||
link_link_cnt_by_industry: "20773000",
|
||||
nickname: "达人 B",
|
||||
play_over_rate_within_30d: "0.35",
|
||||
price_20_60: "38000",
|
||||
prospective_20_60_cpm: "182.5",
|
||||
tags_relation: {
|
||||
剧情搞笑: ["剧情"]
|
||||
}
|
||||
},
|
||||
expected_play_num: "208000",
|
||||
star_id: "222"
|
||||
}
|
||||
]);
|
||||
|
||||
const table = syncMarketTable(document);
|
||||
if (!table) {
|
||||
throw new Error("Expected market table");
|
||||
}
|
||||
|
||||
expect(table.rows[1].price21To60s).toBe("¥38,000");
|
||||
expect(table.rows[1].exportFields).toMatchObject({
|
||||
粉丝数: "90.1w",
|
||||
预期播放量: "20.8w",
|
||||
互动率: "7.2%"
|
||||
});
|
||||
});
|
||||
|
||||
test("prefers vue fallback when the price cell is polluted", () => {
|
||||
document.body.innerHTML = buildRichMarketGridFixtureWithPollutedSecondPrice();
|
||||
attachMarketVueState([
|
||||
{
|
||||
attribute_datas: {
|
||||
city: "杭州",
|
||||
follower: "901234",
|
||||
gender: "2",
|
||||
interact_rate_within_30d: "0.072",
|
||||
link_link_cnt_by_industry: "20773000",
|
||||
nickname: "达人 B",
|
||||
play_over_rate_within_30d: "0.35",
|
||||
price_20_60: "38000",
|
||||
prospective_20_60_cpm: "182.5",
|
||||
tags_relation: {
|
||||
剧情搞笑: ["剧情"]
|
||||
}
|
||||
},
|
||||
expected_play_num: "208000",
|
||||
star_id: "222"
|
||||
}
|
||||
]);
|
||||
|
||||
const table = syncMarketTable(document);
|
||||
if (!table) {
|
||||
throw new Error("Expected market table");
|
||||
}
|
||||
|
||||
expect(table.rows[1].price21To60s).toBe("¥38,000");
|
||||
expect(table.rows[1].exportFields?.["21-60s报价"]).toBe("¥38,000");
|
||||
});
|
||||
|
||||
test("falls back to serialized market rows when vue state is unavailable", () => {
|
||||
document.body.innerHTML = buildRealMarketGridFixtureWithoutAuthorIds();
|
||||
document.documentElement.setAttribute(
|
||||
@@ -385,8 +668,167 @@ function buildRealMarketGridFixtureWithoutAuthorIds() {
|
||||
`;
|
||||
}
|
||||
|
||||
function buildRealMarketGridFixtureWithMissingPriceCell() {
|
||||
return `
|
||||
<div class="base-author-list">
|
||||
<div class="section-wrapper sticky-header hide-scrollbar">
|
||||
<div style="width: 310px; display: flex; position: sticky; left: 0; z-index: 11;">
|
||||
<div class="header-cell" style="min-width: 310px;">达人信息</div>
|
||||
</div>
|
||||
<div class="middle-columns" style="width: 190px; display: flex;">
|
||||
<div class="header-cell" style="min-width: 190px;">代表视频</div>
|
||||
</div>
|
||||
<div data-testid="right-header" style="width: 350px; display: flex; position: sticky; right: 0; z-index: 11;">
|
||||
<div class="header-cell" style="min-width: 150px;">21-60s报价</div>
|
||||
<div class="header-cell" style="min-width: 200px;">操作</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section-wrapper hide-scrollbar">
|
||||
<div class="content-section" data-testid="author-section" style="width: 310px; display: flex; position: sticky; left: 0; z-index: 11;">
|
||||
<div class="content-column" style="min-width: 310px;">
|
||||
<div class="content-cell" data-testid="author-cell-111" style="height: 120px;">
|
||||
<a href="https://xingtu.cn/ad/creator/author-homepage/douyin-video/111">达人 A</a>
|
||||
</div>
|
||||
<div class="content-cell" data-testid="author-cell-222" style="height: 120px;">
|
||||
<a href="https://xingtu.cn/ad/creator/author-homepage/douyin-video/222">达人 B</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="middle-columns" style="width: 190px; display: flex;">
|
||||
<div class="content-column" style="min-width: 190px;">
|
||||
<div class="content-cell" style="height: 120px;">代表视频A</div>
|
||||
<div class="content-cell" style="height: 120px;">代表视频B</div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-testid="right-section" class="content-section" style="width: 350px; display: flex; position: sticky; right: 0; z-index: 11;">
|
||||
<div class="content-column" style="min-width: 150px;">
|
||||
<div class="content-cell" style="height: 120px;">¥450,000</div>
|
||||
</div>
|
||||
<div class="content-column" style="min-width: 200px;">
|
||||
<div class="content-cell" data-testid="action-cell-111" style="height: 120px;">下单</div>
|
||||
<div class="content-cell" data-testid="action-cell-222" style="height: 120px;">下单</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function buildRealMarketGridFixtureWithScopedAttributes() {
|
||||
return buildRealMarketGridFixture()
|
||||
.replace(
|
||||
'<div class="header-cell" style="min-width: 190px;">代表视频</div>',
|
||||
'<div data-v-header-scope class="header-cell" style="min-width: 190px;">代表视频</div>'
|
||||
)
|
||||
.replace(
|
||||
'<div class="content-cell" style="height: 120px;">代表视频A</div>',
|
||||
'<div data-v-cell-scope class="content-cell" style="height: 120px;">代表视频A</div>'
|
||||
)
|
||||
.replace(
|
||||
'<div class="content-cell" style="height: 120px;">代表视频B</div>',
|
||||
'<div data-v-cell-scope class="content-cell" style="height: 120px;">代表视频B</div>'
|
||||
);
|
||||
}
|
||||
|
||||
function buildRichMarketGridFixtureWithBlankSecondRow() {
|
||||
return `
|
||||
<div class="base-author-list">
|
||||
<div class="section-wrapper sticky-header hide-scrollbar">
|
||||
<div style="width: 310px; display: flex; position: sticky; left: 0; z-index: 11;">
|
||||
<div class="header-cell" style="min-width: 310px;">达人信息</div>
|
||||
</div>
|
||||
<div class="middle-columns" style="width: 1210px; display: flex;">
|
||||
<div class="header-cell" style="min-width: 190px;">代表视频</div>
|
||||
<div class="header-cell" style="min-width: 120px;">达人类型</div>
|
||||
<div class="header-cell" style="min-width: 180px;">内容主题</div>
|
||||
<div class="header-cell" style="min-width: 120px;">连接用户数</div>
|
||||
<div class="header-cell" style="min-width: 120px;">粉丝数</div>
|
||||
<div class="header-cell" style="min-width: 120px;">预期CPM</div>
|
||||
<div class="header-cell" style="min-width: 120px;">预期播放量</div>
|
||||
<div class="header-cell" style="min-width: 120px;">互动率</div>
|
||||
<div class="header-cell" style="min-width: 120px;">完播率</div>
|
||||
<div class="header-cell" style="min-width: 120px;">爆文率</div>
|
||||
</div>
|
||||
<div data-testid="right-header" style="width: 350px; display: flex; position: sticky; right: 0; z-index: 11;">
|
||||
<div class="header-cell" style="min-width: 150px;">21-60s报价</div>
|
||||
<div class="header-cell" style="min-width: 200px;">操作</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section-wrapper hide-scrollbar">
|
||||
<div class="content-section" style="width: 310px; display: flex; position: sticky; left: 0; z-index: 11;">
|
||||
<div class="content-column" style="min-width: 310px;">
|
||||
<div class="content-cell" style="height: 120px;">
|
||||
<a href="https://xingtu.cn/ad/creator/author-homepage/douyin-video/111">达人 A</a>
|
||||
</div>
|
||||
<div class="content-cell" style="height: 120px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="middle-columns" style="width: 1210px; display: flex;">
|
||||
<div class="content-column" style="min-width: 190px;">
|
||||
<div class="content-cell" style="height: 120px;">代表视频A</div>
|
||||
<div class="content-cell" style="height: 120px;"></div>
|
||||
</div>
|
||||
<div class="content-column" style="min-width: 120px;">
|
||||
<div class="content-cell" style="height: 120px;">剧情搞笑</div>
|
||||
<div class="content-cell" style="height: 120px;"></div>
|
||||
</div>
|
||||
<div class="content-column" style="min-width: 180px;">
|
||||
<div class="content-cell" style="height: 120px;">有趣剧情创作 亲情剧集 1+</div>
|
||||
<div class="content-cell" style="height: 120px;"></div>
|
||||
</div>
|
||||
<div class="content-column" style="min-width: 120px;">
|
||||
<div class="content-cell" style="height: 120px;">2,703w</div>
|
||||
<div class="content-cell" style="height: 120px;"></div>
|
||||
</div>
|
||||
<div class="content-column" style="min-width: 120px;">
|
||||
<div class="content-cell" style="height: 120px;">455.1w</div>
|
||||
<div class="content-cell" style="height: 120px;"></div>
|
||||
</div>
|
||||
<div class="content-column" style="min-width: 120px;">
|
||||
<div class="content-cell" style="height: 120px;">21.2</div>
|
||||
<div class="content-cell" style="height: 120px;"></div>
|
||||
</div>
|
||||
<div class="content-column" style="min-width: 120px;">
|
||||
<div class="content-cell" style="height: 120px;">729.9w</div>
|
||||
<div class="content-cell" style="height: 120px;"></div>
|
||||
</div>
|
||||
<div class="content-column" style="min-width: 120px;">
|
||||
<div class="content-cell" style="height: 120px;">5.7%</div>
|
||||
<div class="content-cell" style="height: 120px;"></div>
|
||||
</div>
|
||||
<div class="content-column" style="min-width: 120px;">
|
||||
<div class="content-cell" style="height: 120px;">26.3%</div>
|
||||
<div class="content-cell" style="height: 120px;"></div>
|
||||
</div>
|
||||
<div class="content-column" style="min-width: 120px;">
|
||||
<div class="content-cell" style="height: 120px;">100%</div>
|
||||
<div class="content-cell" style="height: 120px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-section" style="width: 350px; display: flex; position: sticky; right: 0; z-index: 11;">
|
||||
<div class="content-column" style="min-width: 150px;">
|
||||
<div class="content-cell" style="height: 120px;">¥155,000</div>
|
||||
<div class="content-cell" style="height: 120px;"></div>
|
||||
</div>
|
||||
<div class="content-column" style="min-width: 200px;">
|
||||
<div class="content-cell" style="height: 120px;">下单</div>
|
||||
<div class="content-cell" style="height: 120px;">下单</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function buildRichMarketGridFixtureWithPollutedSecondPrice() {
|
||||
return buildRichMarketGridFixtureWithBlankSecondRow().replace(
|
||||
'<div class="content-cell" style="height: 120px;"></div>\n </div>\n <div class="content-column" style="min-width: 200px;">',
|
||||
'<div class="content-cell" style="height: 120px;">看后搜率0.39%看后搜数2,248.33新增A3数0.00新增A3率0%CPA30.00cp_search20.01</div>\n </div>\n <div class="content-column" style="min-width: 200px;">'
|
||||
);
|
||||
}
|
||||
|
||||
function attachMarketVueState(
|
||||
marketList: Array<{ attribute_datas?: { nickname?: string }; star_id?: string }>
|
||||
marketList: Array<Record<string, unknown>>
|
||||
) {
|
||||
const marketRoot = document.querySelector(".base-author-list");
|
||||
if (!(marketRoot instanceof HTMLElement)) {
|
||||
@@ -405,6 +847,40 @@ function attachMarketVueState(
|
||||
});
|
||||
}
|
||||
|
||||
function attachNestedMarketVueState(marketList: Array<Record<string, unknown>>) {
|
||||
const marketRoot = document.querySelector(".base-author-list");
|
||||
if (!(marketRoot instanceof HTMLElement)) {
|
||||
throw new Error("Expected market root");
|
||||
}
|
||||
|
||||
Object.defineProperty(marketRoot, "__vue__", {
|
||||
configurable: true,
|
||||
value: {
|
||||
$children: [
|
||||
{
|
||||
$children: [
|
||||
{
|
||||
_setupState: {},
|
||||
$children: [
|
||||
{
|
||||
_setupState: {
|
||||
__$temp_1: {
|
||||
marketList
|
||||
}
|
||||
},
|
||||
$children: []
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
_setupState: {}
|
||||
}
|
||||
],
|
||||
_setupState: {}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function readRightHeaderTexts() {
|
||||
return Array.from(
|
||||
document.querySelectorAll('[data-testid="right-header"] > *'),
|
||||
@@ -412,6 +888,13 @@ function readRightHeaderTexts() {
|
||||
);
|
||||
}
|
||||
|
||||
function readPluginHeaderTexts() {
|
||||
return Array.from(
|
||||
document.querySelectorAll('[data-testid="plugin-header"] > *'),
|
||||
(cell) => cell.textContent?.trim() ?? ""
|
||||
);
|
||||
}
|
||||
|
||||
function readRightRowTexts(rowIndex: number) {
|
||||
return Array.from(
|
||||
document.querySelectorAll('[data-testid="right-section"] > .content-column'),
|
||||
@@ -420,6 +903,20 @@ function readRightRowTexts(rowIndex: number) {
|
||||
);
|
||||
}
|
||||
|
||||
function readPluginRowTexts(rowIndex: number) {
|
||||
return Array.from(
|
||||
document.querySelectorAll('[data-testid="plugin-section"] > .content-column'),
|
||||
(column) =>
|
||||
column.querySelectorAll(".content-cell")[rowIndex]?.textContent?.trim() ?? ""
|
||||
);
|
||||
}
|
||||
|
||||
function readScrollHintText() {
|
||||
return (
|
||||
document.querySelector('[data-testid="market-scroll-hint"]')?.textContent?.trim() ?? ""
|
||||
);
|
||||
}
|
||||
|
||||
function readAuthorNames() {
|
||||
return Array.from(
|
||||
document.querySelectorAll('[data-testid="author-section"] .content-cell a'),
|
||||
|
||||
Reference in New Issue
Block a user