fix: respect selected audience export fields
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
||||
listRateCsvHeaders,
|
||||
type CsvColumn
|
||||
} from "./csv-exporter";
|
||||
import type { MarketRecord } from "./types";
|
||||
import type {
|
||||
AudienceProfileDistributionItem,
|
||||
AudienceProfileExportRow,
|
||||
@@ -60,6 +61,12 @@ const CROWD_LABELS = [
|
||||
];
|
||||
|
||||
const BUSINESS_ESTIMATE_SECTION_LABEL = "效果预估";
|
||||
const FIXED_AUDIENCE_PROFILE_HEADERS = new Set([
|
||||
"达人ID",
|
||||
"达人名称",
|
||||
"导出状态",
|
||||
"失败原因"
|
||||
]);
|
||||
|
||||
const BUSINESS_ESTIMATE_LAYOUTS: Array<{
|
||||
key: BusinessAbilityDurationKind;
|
||||
@@ -85,11 +92,20 @@ export function buildAudienceProfileCsv(
|
||||
options: AudienceProfileCsvOptions = {}
|
||||
): string {
|
||||
const marketColumns = buildMarketCsvColumns(rows.map((row) => row.record));
|
||||
const csvColumns = filterAudienceProfileCsvColumns([
|
||||
const optionalColumns = [
|
||||
...marketColumns.map(toMarketColumn),
|
||||
...buildBusinessAbilityColumns(),
|
||||
...PROFILE_LAYOUTS.flatMap((layout) => buildProfileColumns(layout))
|
||||
], options.selectedHeaders);
|
||||
];
|
||||
const csvColumns = filterAudienceProfileCsvColumns(
|
||||
options.selectedHeaders === undefined
|
||||
? optionalColumns
|
||||
: deduplicateCsvColumns([
|
||||
...buildFixedAudienceProfileColumns(),
|
||||
...optionalColumns
|
||||
]),
|
||||
options.selectedHeaders
|
||||
);
|
||||
const headerLine = csvColumns.map((column) => column.header).join(",");
|
||||
const rowLines = rows.map((row) =>
|
||||
csvColumns.map((column) => escapeCsvCell(column.readValue(row))).join(",")
|
||||
@@ -111,8 +127,22 @@ export function listAudienceProfileCsvHeaders(
|
||||
];
|
||||
}
|
||||
|
||||
export function listAudienceProfileSelectableFieldGroups(): AudienceProfileCsvFieldGroup[] {
|
||||
export function listAudienceProfileSelectableFieldGroups(
|
||||
marketListHeaders: string[] = []
|
||||
): AudienceProfileCsvFieldGroup[] {
|
||||
const selectableMarketListHeaders = marketListHeaders.filter(
|
||||
(header) => !FIXED_AUDIENCE_PROFILE_HEADERS.has(header)
|
||||
);
|
||||
|
||||
return [
|
||||
...(selectableMarketListHeaders.length > 0
|
||||
? [
|
||||
{
|
||||
headers: selectableMarketListHeaders,
|
||||
label: "列表字段"
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
headers: listRateCsvHeaders(),
|
||||
label: "看后搜率"
|
||||
@@ -144,19 +174,63 @@ function filterAudienceProfileCsvColumns(
|
||||
return columns;
|
||||
}
|
||||
|
||||
const selectableHeaderSet = new Set(listAudienceProfileSelectableHeaders());
|
||||
const selectedHeaderSet = new Set(selectedHeaders);
|
||||
return columns.filter(
|
||||
(column) =>
|
||||
!selectableHeaderSet.has(column.header) ||
|
||||
FIXED_AUDIENCE_PROFILE_HEADERS.has(column.header) ||
|
||||
selectedHeaderSet.has(column.header)
|
||||
);
|
||||
}
|
||||
|
||||
function listAudienceProfileSelectableHeaders(): string[] {
|
||||
return listAudienceProfileSelectableFieldGroups().flatMap(
|
||||
(group) => group.headers
|
||||
);
|
||||
function buildFixedAudienceProfileColumns(): AudienceProfileCsvColumn[] {
|
||||
return [
|
||||
{
|
||||
header: "达人ID",
|
||||
readValue: (row) => row.record.exportFields?.达人ID ?? row.record.authorId
|
||||
},
|
||||
{
|
||||
header: "达人名称",
|
||||
readValue: (row) => row.record.exportFields?.达人名称 ?? row.record.authorName
|
||||
},
|
||||
{
|
||||
header: "导出状态",
|
||||
readValue: (row) => row.record.exportFields?.导出状态 ?? readExportStatus(row.record)
|
||||
},
|
||||
{
|
||||
header: "失败原因",
|
||||
readValue: (row) => row.record.exportFields?.失败原因 ?? row.record.failureReason ?? ""
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
function readExportStatus(record: MarketRecord): string {
|
||||
if (record.status === "failed") {
|
||||
return "失败";
|
||||
}
|
||||
|
||||
if (record.status === "loading") {
|
||||
return "加载中";
|
||||
}
|
||||
|
||||
if (record.status === "idle") {
|
||||
return "待处理";
|
||||
}
|
||||
|
||||
return "成功";
|
||||
}
|
||||
|
||||
function deduplicateCsvColumns(
|
||||
columns: AudienceProfileCsvColumn[]
|
||||
): AudienceProfileCsvColumn[] {
|
||||
const seenHeaders = new Set<string>();
|
||||
return columns.filter((column) => {
|
||||
if (seenHeaders.has(column.header)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
seenHeaders.add(column.header);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
function buildBusinessAbilityColumns(): AudienceProfileCsvColumn[] {
|
||||
|
||||
@@ -3,16 +3,15 @@ import type { AudienceProfileCsvFieldGroup } from "./audience-profile-csv";
|
||||
export function promptForAudienceProfileFields(
|
||||
document: Document,
|
||||
groups: AudienceProfileCsvFieldGroup[],
|
||||
selectedHeaders: string[]
|
||||
selectedHeaders: string[] | undefined
|
||||
): Promise<string[] | null> {
|
||||
return new Promise((resolve) => {
|
||||
const selectableHeaders = groups.flatMap((group) => group.headers);
|
||||
const selectedHeaderSet = new Set(
|
||||
selectedHeaders.filter((header) => selectableHeaders.includes(header))
|
||||
selectedHeaders === undefined
|
||||
? selectableHeaders
|
||||
: selectedHeaders.filter((header) => selectableHeaders.includes(header))
|
||||
);
|
||||
if (selectedHeaderSet.size === 0) {
|
||||
selectableHeaders.forEach((header) => selectedHeaderSet.add(header));
|
||||
}
|
||||
|
||||
const overlay = document.createElement("div");
|
||||
overlay.dataset.audienceProfileFieldDialog = "overlay";
|
||||
|
||||
+15
-20
@@ -1,4 +1,4 @@
|
||||
import { buildMarketCsv } from "./csv-exporter";
|
||||
import { buildBaseColumns, buildMarketCsv } from "./csv-exporter";
|
||||
import {
|
||||
buildAudienceProfileCsv,
|
||||
listAudienceProfileSelectableFieldGroups,
|
||||
@@ -475,7 +475,10 @@ export function createMarketController(options: CreateMarketControllerOptions) {
|
||||
}
|
||||
},
|
||||
onConfigureAudienceProfileFields: async () => {
|
||||
const groups = listAudienceProfileSelectableFieldGroups();
|
||||
const marketListHeaders = buildBaseColumns(
|
||||
readCurrentPageRecords(syncMarketTable(options.document))
|
||||
).map((column) => column.header);
|
||||
const groups = listAudienceProfileSelectableFieldGroups(marketListHeaders);
|
||||
const selectedHeaders = readAudienceProfileSelectedHeaders();
|
||||
const nextHeaders = await promptForAudienceProfileFields(
|
||||
options.document,
|
||||
@@ -489,7 +492,7 @@ export function createMarketController(options: CreateMarketControllerOptions) {
|
||||
saveAudienceProfileSelectedHeaders(nextHeaders);
|
||||
setToolbarExportStatus(
|
||||
toolbar,
|
||||
`字段已保存(已选 ${nextHeaders.length}/${readAudienceProfileSelectableHeaders().length} 个字段)`
|
||||
`字段已保存(已选 ${nextHeaders.length}/${readAudienceProfileSelectableHeaders(marketListHeaders).length} 个字段)`
|
||||
);
|
||||
},
|
||||
onSubmitBatch: async () => {
|
||||
@@ -1355,44 +1358,36 @@ export function createMarketController(options: CreateMarketControllerOptions) {
|
||||
return "铁粉画像";
|
||||
}
|
||||
|
||||
function readAudienceProfileSelectableHeaders(): string[] {
|
||||
return listAudienceProfileSelectableFieldGroups().flatMap(
|
||||
function readAudienceProfileSelectableHeaders(marketListHeaders: string[] = []): string[] {
|
||||
return listAudienceProfileSelectableFieldGroups(marketListHeaders).flatMap(
|
||||
(group) => group.headers
|
||||
);
|
||||
}
|
||||
|
||||
function readAudienceProfileSelectedHeaders(): string[] {
|
||||
const selectableHeaders = readAudienceProfileSelectableHeaders();
|
||||
const selectableHeaderSet = new Set(selectableHeaders);
|
||||
|
||||
function readAudienceProfileSelectedHeaders(): string[] | undefined {
|
||||
try {
|
||||
const rawValue = options.window.localStorage?.getItem(
|
||||
AUDIENCE_PROFILE_FIELD_SELECTION_STORAGE_KEY
|
||||
);
|
||||
if (!rawValue) {
|
||||
return selectableHeaders;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const parsedValue = JSON.parse(rawValue) as unknown;
|
||||
if (!Array.isArray(parsedValue)) {
|
||||
return selectableHeaders;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const selectedHeaders = parsedValue.filter(
|
||||
(header): header is string =>
|
||||
typeof header === "string" && selectableHeaderSet.has(header)
|
||||
return parsedValue.filter(
|
||||
(header): header is string => typeof header === "string"
|
||||
);
|
||||
return selectedHeaders.length > 0 ? selectedHeaders : selectableHeaders;
|
||||
} catch {
|
||||
return selectableHeaders;
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function saveAudienceProfileSelectedHeaders(headers: string[]): void {
|
||||
const selectableHeaderSet = new Set(readAudienceProfileSelectableHeaders());
|
||||
const selectedHeaders = headers.filter((header) =>
|
||||
selectableHeaderSet.has(header)
|
||||
);
|
||||
const selectedHeaders = Array.from(new Set(headers));
|
||||
|
||||
try {
|
||||
options.window.localStorage?.setItem(
|
||||
|
||||
Reference in New Issue
Block a user