fix: refine market toolbar filters

This commit is contained in:
wxs
2026-07-09 21:33:58 +08:00
parent b4440e2ca3
commit 370cc0170b
4 changed files with 72 additions and 74 deletions
+4
View File
@@ -35,6 +35,10 @@ export function createBatchPayload(options: {
throw new Error("batch submit batch name is required");
}
if (options.records.length === 0) {
throw new Error("当前没有可提交的达人");
}
return {
authors: options.records.map((record) => ({
authorId: record.authorId,
+32 -49
View File
@@ -12,6 +12,13 @@ export interface PluginToolbarHandlers {
onSubmitBatch(): Promise<void> | void;
}
type VisibleSpreadThresholdKey = Extract<
keyof SpreadThresholdFilter["thresholds"],
"finishRate" | "interactionRate"
>;
type SpreadThresholdInputMap = Record<VisibleSpreadThresholdKey, HTMLInputElement>;
export interface PluginToolbarDom {
audienceProfileByIdExportButton: HTMLButtonElement;
audienceProfileExportButton: HTMLButtonElement;
@@ -25,7 +32,7 @@ export interface PluginToolbarDom {
spreadFilterOnlyAssignSelect: HTMLSelectElement;
spreadFilterRangeSelect: HTMLSelectElement;
spreadFilterTypeSelect: HTMLSelectElement;
spreadThresholdInputs: Record<keyof SpreadThresholdFilter["thresholds"], HTMLInputElement>;
spreadThresholdInputs: SpreadThresholdInputMap;
root: HTMLElement;
}
@@ -67,7 +74,6 @@ export function ensurePluginToolbar(
const exportRangeSelect = document.createElement("select");
exportRangeSelect.dataset.pluginExportRange = "select";
exportRangeSelect.hidden = true;
appendOption(exportRangeSelect, "current", "当前页");
appendOption(exportRangeSelect, "first-5", "前5页");
appendOption(exportRangeSelect, "first-10", "前10页");
@@ -86,8 +92,8 @@ export function ensurePluginToolbar(
const exportButton = document.createElement("button");
exportButton.type = "button";
exportButton.dataset.pluginExport = "button";
exportButton.hidden = true;
exportButton.tabIndex = -1;
exportButton.textContent = "导出CSV";
exportButton.title = "按所选页数范围导出当前筛选结果";
const audienceProfileExportButton = document.createElement("button");
audienceProfileExportButton.type = "button";
@@ -162,6 +168,9 @@ export function ensurePluginToolbar(
dataGroup.dataset.pluginToolbarGroup = "data";
applyToolbarGroupStyles(dataGroup);
dataGroup.append(
exportButton,
exportRangeSelect,
exportCustomPagesInput,
audienceProfileExportButton,
audienceProfileByIdExportButton,
audienceProfileFieldButton,
@@ -191,12 +200,7 @@ export function ensurePluginToolbar(
secondRow.append(thresholdTitle, thresholdGroup);
panel.append(firstRow, secondRow);
root.append(
exportRangeSelect,
exportCustomPagesInput,
exportButton,
panel
);
root.append(panel);
document.body.appendChild(root);
applyNativeControlStyles(document, {
@@ -302,33 +306,16 @@ function appendOption(
function createSpreadThresholdInputs(
document: Document
): Record<keyof SpreadThresholdFilter["thresholds"], HTMLInputElement> {
): SpreadThresholdInputMap {
return {
averageCommentCount: createSpreadThresholdInput(
document,
"averageCommentCount"
),
averageDuration: createSpreadThresholdInput(
document,
"averageDuration"
),
averageLikeCount: createSpreadThresholdInput(
document,
"averageLikeCount"
),
averageShareCount: createSpreadThresholdInput(
document,
"averageShareCount"
),
finishRate: createSpreadThresholdInput(document, "finishRate"),
interactionRate: createSpreadThresholdInput(document, "interactionRate"),
playMedian: createSpreadThresholdInput(document, "playMedian")
interactionRate: createSpreadThresholdInput(document, "interactionRate")
};
}
function createSpreadThresholdInput(
document: Document,
key: keyof SpreadThresholdFilter["thresholds"]
key: VisibleSpreadThresholdKey
): HTMLInputElement {
const input = document.createElement("input");
input.type = "number";
@@ -339,24 +326,19 @@ function createSpreadThresholdInput(
}
function getSpreadThresholdStep(
key: keyof SpreadThresholdFilter["thresholds"]
key: VisibleSpreadThresholdKey
): string {
return key === "finishRate" || key === "interactionRate" ? "0.1" : "1";
}
function createSpreadThresholdControls(
document: Document,
inputs: Record<keyof SpreadThresholdFilter["thresholds"], HTMLInputElement>
inputs: SpreadThresholdInputMap
): HTMLElement[] {
const controls: HTMLElement[] = [];
const entries: Array<[string, string, HTMLInputElement]> = [
["评论", "条", inputs.averageCommentCount],
["时长", "秒", inputs.averageDuration],
["点赞", "次", inputs.averageLikeCount],
["转发", "次", inputs.averageShareCount],
["完播率", "%", inputs.finishRate],
["互动率", "%", inputs.interactionRate],
["播放中位数", "次", inputs.playMedian]
["互动率", "%", inputs.interactionRate]
];
entries.forEach(([label, unit, input], index) => {
@@ -396,21 +378,16 @@ function createSpreadThresholdConjunction(document: Document): HTMLElement {
function readSpreadThresholdInputs(
root: HTMLElement
): Record<keyof SpreadThresholdFilter["thresholds"], HTMLInputElement> {
): SpreadThresholdInputMap {
return {
averageCommentCount: readSpreadThresholdInput(root, "averageCommentCount"),
averageDuration: readSpreadThresholdInput(root, "averageDuration"),
averageLikeCount: readSpreadThresholdInput(root, "averageLikeCount"),
averageShareCount: readSpreadThresholdInput(root, "averageShareCount"),
finishRate: readSpreadThresholdInput(root, "finishRate"),
interactionRate: readSpreadThresholdInput(root, "interactionRate"),
playMedian: readSpreadThresholdInput(root, "playMedian")
interactionRate: readSpreadThresholdInput(root, "interactionRate")
};
}
function readSpreadThresholdInput(
root: HTMLElement,
key: keyof SpreadThresholdFilter["thresholds"]
key: VisibleSpreadThresholdKey
): HTMLInputElement {
return root.querySelector(
`[data-plugin-spread-threshold="${key}"]`
@@ -596,8 +573,8 @@ export function setToolbarExportStatus(
}
function syncCustomPagesInputVisibility(toolbar: PluginToolbarDom): void {
toolbar.exportRangeSelect.hidden = true;
toolbar.exportCustomPagesInput.hidden = true;
toolbar.exportRangeSelect.hidden = false;
toolbar.exportCustomPagesInput.hidden = toolbar.exportRangeSelect.value !== "custom";
}
function syncSpreadFilterControlState(toolbar: PluginToolbarDom): void {
@@ -862,7 +839,7 @@ function applyNativeControlStyles(
spreadFilterRangeSelect: HTMLSelectElement;
spreadFilterTypeSelect: HTMLSelectElement;
} & Record<
keyof SpreadThresholdFilter["thresholds"],
VisibleSpreadThresholdKey,
HTMLInputElement
>
): void {
@@ -875,6 +852,7 @@ function applyNativeControlStyles(
findNativeActionButton(document, "导出");
if (nativeButton) {
controls.exportButton.className = nativeButton.className;
controls.audienceProfileExportButton.className = nativeButton.className;
controls.audienceProfileByIdExportButton.className = nativeButton.className;
controls.audienceProfileFieldButton.className = nativeButton.className;
@@ -882,6 +860,7 @@ function applyNativeControlStyles(
}
[
controls.exportButton,
controls.audienceProfileExportButton,
controls.audienceProfileByIdExportButton,
controls.audienceProfileFieldButton,
@@ -993,6 +972,7 @@ function ensurePluginActionButtonTheme(document: Document): void {
const style = document.createElement("style");
style.id = PLUGIN_ACTION_BUTTON_STYLE_ID;
style.textContent = `
[data-plugin-export="button"]:hover:not(:disabled),
[data-plugin-export-audience-profile="button"]:hover:not(:disabled),
[data-plugin-export-audience-profile-by-id="button"]:hover:not(:disabled),
[data-plugin-audience-profile-fields="button"]:hover:not(:disabled),
@@ -1001,6 +981,7 @@ function ensurePluginActionButtonTheme(document: Document): void {
border-color: #6d1627 !important;
}
[data-plugin-export="button"]:active:not(:disabled),
[data-plugin-export-audience-profile="button"]:active:not(:disabled),
[data-plugin-export-audience-profile-by-id="button"]:active:not(:disabled),
[data-plugin-audience-profile-fields="button"]:active:not(:disabled),
@@ -1010,6 +991,7 @@ function ensurePluginActionButtonTheme(document: Document): void {
transform: translateY(1px);
}
[data-plugin-export="button"]:focus-visible,
[data-plugin-export-audience-profile="button"]:focus-visible,
[data-plugin-export-audience-profile-by-id="button"]:focus-visible,
[data-plugin-audience-profile-fields="button"]:focus-visible,
@@ -1018,6 +1000,7 @@ function ensurePluginActionButtonTheme(document: Document): void {
box-shadow: 0 0 0 3px rgba(127, 29, 45, 0.2) !important;
}
[data-plugin-export="button"]:disabled,
[data-plugin-export-audience-profile="button"]:disabled,
[data-plugin-export-audience-profile-by-id="button"]:disabled,
[data-plugin-audience-profile-fields="button"]:disabled,
+18
View File
@@ -55,4 +55,22 @@ describe("batch-payload", () => {
})
).toThrow(/user/i);
});
test("throws when no creator records are available", () => {
expect(() =>
createBatchPayload({
authState: {
isAuthenticated: true,
resource: "https://talent-search.intelligrow.cn",
userInfo: {
name: "王少卿",
sub: "p7pdhhtde8kj"
}
},
batchName: "空结果批次",
createdAt: "2026-07-03T03:05:52.432Z",
records: []
})
).toThrow("当前没有可提交的达人");
});
});
+18 -25
View File
@@ -331,7 +331,7 @@ describe("market-content-entry", () => {
expect(
(document.querySelector('[data-plugin-export-range="select"]') as HTMLSelectElement | null)
?.hidden
).toBe(true);
).toBe(false);
expect(
(
document.querySelector(
@@ -342,7 +342,7 @@ describe("market-content-entry", () => {
expect(
(document.querySelector('[data-plugin-export="button"]') as HTMLButtonElement | null)
?.hidden
).toBe(true);
).toBe(false);
expect(
document.querySelector('[data-plugin-export-audience-profile="button"]')
).not.toBeNull();
@@ -464,39 +464,32 @@ describe("market-content-entry", () => {
expect(titles[0]?.style.background).toBe("rgb(238, 245, 255)");
expect(titles[1]?.style.background).toBe("rgb(238, 245, 255)");
expect(document.querySelector("[data-plugin-spread-threshold-rule]")).toBeNull();
expect(operators).toEqual(["≥", "≥", "≥", "≥", "≥", "≥", "≥"]);
expect(conjunctions).toEqual(["且", "且", "且", "且", "且", "且"]);
expect(operators).toEqual(["≥", "≥"]);
expect(conjunctions).toEqual(["且"]);
expect(thresholdInputs.map((input) => input.placeholder)).toEqual([
"",
"",
"",
"",
"",
"",
""
]);
expect(thresholdInputs.map((input) => input.step)).toEqual([
"1",
"1",
"1",
"1",
"0.1",
"0.1",
"1"
"0.1"
]);
expect(thresholdControls.map((control) => control.textContent)).toEqual([
"评论≥条",
"时长≥秒",
"点赞≥次",
"转发≥次",
"完播率≥%",
"互动率≥%",
"播放中位数≥次"
"互动率≥%"
]);
expect(buttons.map((button) => button.textContent)).toEqual([
"导出CSV",
"导出选中达人数据",
"按星图ID导出",
"选择字段",
"提交批次"
]);
expect(buttons.map((button) => button.style.backgroundColor)).toEqual([
"rgb(127, 29, 45)",
"rgb(127, 29, 45)",
"rgb(127, 29, 45)",
"rgb(127, 29, 45)",
"rgb(127, 29, 45)"
]);
});
@@ -1253,7 +1246,7 @@ describe("market-content-entry", () => {
expect(readDivAuthorOrder()).toEqual(["达人 B", "达人 A"]);
});
test("toolbar keeps export range controls hidden while retaining the internal default range", async () => {
test("toolbar shows export range controls and reveals custom page input only for custom range", async () => {
document.body.innerHTML = buildMarketFixture();
const { createMarketController } = await import("../src/content/market/index");
@@ -1276,7 +1269,7 @@ describe("market-content-entry", () => {
) as HTMLInputElement | null;
expect(exportRangeSelect?.value).toBe("first-5");
expect(exportRangeSelect?.hidden).toBe(true);
expect(exportRangeSelect?.hidden).toBe(false);
expect(customPagesInput?.hidden).toBe(true);
expect(
document.querySelector('[data-plugin-batch-submit="button"]')
@@ -1285,8 +1278,8 @@ describe("market-content-entry", () => {
setSelectValue('[data-plugin-export-range="select"]', "custom");
dispatchChange('[data-plugin-export-range="select"]');
expect(exportRangeSelect?.hidden).toBe(true);
expect(customPagesInput?.hidden).toBe(true);
expect(exportRangeSelect?.hidden).toBe(false);
expect(customPagesInput?.hidden).toBe(false);
});
test("toolbar exposes spread threshold filters and disables fixed personal-video controls", async () => {