fix: refine market toolbar filters
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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("当前没有可提交的达人");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
Reference in New Issue
Block a user