String(option.user_id) === String(value.user_id)}
noOptionsText="无匹配运营人员"
options={options}
size="small"
value={selected}
onChange={(_, operator) => updateFilter("operator", String(operator?.user_id || ALL))}
renderOption={(props, operator) => {
const { key, ...optionProps } = props;
const details = [operator.account, operator.team].filter(Boolean).join(" · ");
return (
{operatorIdentityLabel(operator)}
{details ? {details} : null}
);
}}
renderInput={(params) => (
)}
/>
);
}
function DatePresetControl({ filters, updateFilter }) {
return (
{DATE_PRESETS.map((preset) => (
))}
{filters.preset === "custom" ? (
{
updateFilter("customStart", msToDayText(range.startMs));
updateFilter("customEnd", msToDayText(range.endMs));
}}
/>
) : null}
);
}
// 自定义区间在筛选状态里以天字符串(YYYY-MM-DD)存储,选择器以本地时区毫秒交互,这里做双向换算。
function parseDayText(value) {
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(String(value || ""));
return match ? { day: Number(match[3]), month: Number(match[2]), year: Number(match[1]) } : null;
}
function dayTextToStartMs(dayText) {
const parts = parseDayText(dayText);
return parts ? new Date(parts.year, parts.month - 1, parts.day, 0, 0, 0, 0).getTime() : "";
}
function dayTextToEndMs(dayText) {
const parts = parseDayText(dayText);
return parts ? new Date(parts.year, parts.month - 1, parts.day, 23, 59, 59, 999).getTime() : "";
}
function msToDayText(ms) {
if (!ms) {
return "";
}
const date = new Date(Number(ms));
const pad = (value) => String(value).padStart(2, "0");
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
}
function GranularityControl({ filters, updateFilter }) {
return (
{GRANULARITIES.map((item) => (
))}
);
}
function AppChips({ data, filters, updateFilter, viewKey }) {
const apps = viewKey === "funnel"
? (data.master?.apps || []).filter((app) => SOCIAL_BI_FUNNEL_APP_CODES.includes(app.app_code))
: data.master?.apps || [];
const allCodes = apps.map((app) => app.app_code);
const hasSelectedVisibleApp = filters.apps.some((appCode) => allCodes.includes(appCode));
const isAll = filters.apps.includes(ALL) || (viewKey === "funnel" && !hasSelectedVisibleApp);
return (
{apps.map((app) => {
const selected = !isAll && filters.apps.includes(app.app_code);
return (
);
})}
);
}
function RegionSelect({ data, filters, updateFilter }) {
const [isOpen, setIsOpen] = useState(false);
const containerRef = useRef(null);
const apps = data.master?.apps || [];
const regions = data.master?.regions || [];
const groups = apps
.map((app) => ({
app,
regions: regions.filter((region) => region.app_code === app.app_code)
}))
.filter((group) => group.regions.length);
useEffect(() => {
const handlePointerDown = (event) => {
if (!containerRef.current?.contains(event.target)) {
setIsOpen(false);
}
};
document.addEventListener("pointerdown", handlePointerDown);
return () => document.removeEventListener("pointerdown", handlePointerDown);
}, []);
const selectedCount = filters.regions.includes(ALL) ? 0 : filters.regions.length;
return (
{isOpen ? (
{groups.map((group) => (
{group.regions.map((region) => {
const value = `${region.app_code}:${region.region_id}`;
return (
);
})}
))}
{!groups.length ?
暂无区域目录
: null}
) : null}
);
}