refactor(CompactSearchBox): 提取常量和工具函数到子模块
- constants.js: SORT_OPTIONS, IMPORTANCE_OPTIONS 选项配置 - utils.js: findIndustryPath, inferTimeRangeFromFilters, buildFilterParams - index.js: 模块统一导出 主文件逻辑更清晰,工具函数可复用 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,45 +1,55 @@
|
|||||||
// src/views/Community/components/SearchFilters/CompactSearchBox.js
|
// src/views/Community/components/SearchFilters/CompactSearchBox.js
|
||||||
// 紧凑版搜索和筛选组件 - 优化布局
|
// 紧凑版搜索和筛选组件 - 优化布局
|
||||||
|
|
||||||
import React, { useState, useMemo, useEffect, useCallback, useRef } from 'react';
|
import React, { useState, useMemo, useEffect, useCallback, useRef } from "react";
|
||||||
import {
|
import {
|
||||||
Input, Cascader, Button, Space, Tag, AutoComplete, Select as AntSelect,
|
Input,
|
||||||
Tooltip, Divider, Flex
|
Cascader,
|
||||||
} from 'antd';
|
Button,
|
||||||
|
Space,
|
||||||
|
AutoComplete,
|
||||||
|
Select as AntSelect,
|
||||||
|
Tooltip,
|
||||||
|
Divider,
|
||||||
|
Flex,
|
||||||
|
} from "antd";
|
||||||
import {
|
import {
|
||||||
SearchOutlined, CloseCircleOutlined, StockOutlined, FilterOutlined,
|
SearchOutlined,
|
||||||
CalendarOutlined, SortAscendingOutlined, ReloadOutlined, ThunderboltOutlined
|
CloseCircleOutlined,
|
||||||
} from '@ant-design/icons';
|
StockOutlined,
|
||||||
import dayjs from 'dayjs';
|
FilterOutlined,
|
||||||
import debounce from 'lodash/debounce';
|
CalendarOutlined,
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
SortAscendingOutlined,
|
||||||
import { fetchIndustryData, selectIndustryData, selectIndustryLoading } from '@store/slices/industrySlice';
|
ReloadOutlined,
|
||||||
import { loadAllStocks } from '@store/slices/stockSlice';
|
ThunderboltOutlined,
|
||||||
import { stockService } from '@services/stockService';
|
} from "@ant-design/icons";
|
||||||
import { logger } from '@utils/logger';
|
import debounce from "lodash/debounce";
|
||||||
import TradingTimeFilter from './TradingTimeFilter';
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
import { PROFESSIONAL_COLORS } from '@constants/professionalTheme';
|
import {
|
||||||
import './CompactSearchBox.css';
|
fetchIndustryData,
|
||||||
|
selectIndustryData,
|
||||||
|
selectIndustryLoading,
|
||||||
|
} from "@store/slices/industrySlice";
|
||||||
|
import { loadAllStocks } from "@store/slices/stockSlice";
|
||||||
|
import { stockService } from "@services/stockService";
|
||||||
|
import { logger } from "@utils/logger";
|
||||||
|
import TradingTimeFilter from "./TradingTimeFilter";
|
||||||
|
import { PROFESSIONAL_COLORS } from "@constants/professionalTheme";
|
||||||
|
import "./CompactSearchBox.css";
|
||||||
|
|
||||||
|
// 模块化导入
|
||||||
|
import {
|
||||||
|
SORT_OPTIONS,
|
||||||
|
IMPORTANCE_OPTIONS,
|
||||||
|
} from "./CompactSearchBox/constants";
|
||||||
|
import {
|
||||||
|
findIndustryPath,
|
||||||
|
inferTimeRangeFromFilters,
|
||||||
|
buildFilterParams,
|
||||||
|
} from "./CompactSearchBox/utils";
|
||||||
|
|
||||||
const { Option } = AntSelect;
|
const { Option } = AntSelect;
|
||||||
|
|
||||||
// 排序选项常量
|
|
||||||
const SORT_OPTIONS = [
|
|
||||||
{ value: 'new', label: '最新排序', mobileLabel: '最新' },
|
|
||||||
{ value: 'hot', label: '最热排序', mobileLabel: '热门' },
|
|
||||||
{ value: 'importance', label: '重要性排序', mobileLabel: '重要' },
|
|
||||||
{ value: 'returns_avg', label: '平均收益', mobileLabel: '均收' },
|
|
||||||
{ value: 'returns_week', label: '周收益', mobileLabel: '周收' },
|
|
||||||
];
|
|
||||||
|
|
||||||
// 重要性等级常量
|
|
||||||
const IMPORTANCE_OPTIONS = [
|
|
||||||
{ value: 'S', label: 'S级' },
|
|
||||||
{ value: 'A', label: 'A级' },
|
|
||||||
{ value: 'B', label: 'B级' },
|
|
||||||
{ value: 'C', label: 'C级' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const CompactSearchBox = ({
|
const CompactSearchBox = ({
|
||||||
onSearch,
|
onSearch,
|
||||||
onSearchFocus,
|
onSearchFocus,
|
||||||
@@ -47,16 +57,16 @@ const CompactSearchBox = ({
|
|||||||
mode,
|
mode,
|
||||||
pageSize,
|
pageSize,
|
||||||
trackingFunctions = {},
|
trackingFunctions = {},
|
||||||
isMobile = false
|
isMobile = false,
|
||||||
}) => {
|
}) => {
|
||||||
// 状态
|
// 状态
|
||||||
const [stockOptions, setStockOptions] = useState([]);
|
const [stockOptions, setStockOptions] = useState([]);
|
||||||
const [allStocks, setAllStocks] = useState([]);
|
const [allStocks, setAllStocks] = useState([]);
|
||||||
const [industryValue, setIndustryValue] = useState([]);
|
const [industryValue, setIndustryValue] = useState([]);
|
||||||
const [sort, setSort] = useState('new');
|
const [sort, setSort] = useState("new");
|
||||||
const [importance, setImportance] = useState([]);
|
const [importance, setImportance] = useState([]);
|
||||||
const [tradingTimeRange, setTradingTimeRange] = useState(null);
|
const [tradingTimeRange, setTradingTimeRange] = useState(null);
|
||||||
const [inputValue, setInputValue] = useState('');
|
const [inputValue, setInputValue] = useState("");
|
||||||
|
|
||||||
// Redux
|
// Redux
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@@ -64,16 +74,35 @@ const CompactSearchBox = ({
|
|||||||
const industryLoading = useSelector(selectIndustryLoading);
|
const industryLoading = useSelector(selectIndustryLoading);
|
||||||
const reduxAllStocks = useSelector((state) => state.stock.allStocks);
|
const reduxAllStocks = useSelector((state) => state.stock.allStocks);
|
||||||
|
|
||||||
// 防抖搜索
|
// Refs
|
||||||
const debouncedSearchRef = useRef(null);
|
const debouncedSearchRef = useRef(null);
|
||||||
// 存储股票选择时的显示值(代码+名称),用于 useEffect 同步时显示完整信息
|
|
||||||
const stockDisplayValueRef = useRef(null);
|
const stockDisplayValueRef = useRef(null);
|
||||||
|
|
||||||
const triggerSearch = useCallback((params) => {
|
const triggerSearch = useCallback(
|
||||||
logger.debug('CompactSearchBox', '触发搜索', { params });
|
(params) => {
|
||||||
|
logger.debug("CompactSearchBox", "触发搜索", { params });
|
||||||
onSearch(params);
|
onSearch(params);
|
||||||
}, [onSearch]);
|
},
|
||||||
|
[onSearch]
|
||||||
|
);
|
||||||
|
|
||||||
|
// 创建构建参数的封装函数
|
||||||
|
const createFilterParams = useCallback(
|
||||||
|
(overrides = {}) =>
|
||||||
|
buildFilterParams({
|
||||||
|
overrides,
|
||||||
|
sort,
|
||||||
|
importance,
|
||||||
|
filtersQ: filters.q,
|
||||||
|
industryValue,
|
||||||
|
tradingTimeRange,
|
||||||
|
mode,
|
||||||
|
pageSize,
|
||||||
|
}),
|
||||||
|
[sort, importance, filters.q, industryValue, tradingTimeRange, mode, pageSize]
|
||||||
|
);
|
||||||
|
|
||||||
|
// 防抖搜索初始化
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
debouncedSearchRef.current = debounce((params) => {
|
debouncedSearchRef.current = debounce((params) => {
|
||||||
triggerSearch(params);
|
triggerSearch(params);
|
||||||
@@ -86,128 +115,102 @@ const CompactSearchBox = ({
|
|||||||
};
|
};
|
||||||
}, [triggerSearch]);
|
}, [triggerSearch]);
|
||||||
|
|
||||||
// 加载股票数据(从 Redux 获取)
|
// 加载股票数据
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!reduxAllStocks || reduxAllStocks.length === 0) {
|
if (!reduxAllStocks || reduxAllStocks.length === 0) {
|
||||||
dispatch(loadAllStocks());
|
dispatch(loadAllStocks());
|
||||||
}
|
}
|
||||||
}, [dispatch, reduxAllStocks]);
|
}, [dispatch, reduxAllStocks]);
|
||||||
|
|
||||||
// 同步 Redux 数据到本地状态
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (reduxAllStocks && reduxAllStocks.length > 0) {
|
if (reduxAllStocks && reduxAllStocks.length > 0) {
|
||||||
setAllStocks(reduxAllStocks);
|
setAllStocks(reduxAllStocks);
|
||||||
}
|
}
|
||||||
}, [reduxAllStocks]);
|
}, [reduxAllStocks]);
|
||||||
|
|
||||||
// 预加载行业数据(解决第一次点击无数据问题)
|
// 预加载行业数据
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!industryData || industryData.length === 0) {
|
if (!industryData || industryData.length === 0) {
|
||||||
dispatch(fetchIndustryData());
|
dispatch(fetchIndustryData());
|
||||||
}
|
}
|
||||||
}, [dispatch, industryData]);
|
}, [dispatch, industryData]);
|
||||||
|
|
||||||
// 初始化筛选条件
|
// 同步外部 filters 到本地状态
|
||||||
const findIndustryPath = useCallback((targetCode, data, currentPath = []) => {
|
|
||||||
if (!data || data.length === 0) return null;
|
|
||||||
|
|
||||||
for (const item of data) {
|
|
||||||
const newPath = [...currentPath, item.value];
|
|
||||||
|
|
||||||
if (item.value === targetCode) {
|
|
||||||
return newPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.children && item.children.length > 0) {
|
|
||||||
const found = findIndustryPath(targetCode, item.children, newPath);
|
|
||||||
if (found) return found;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!filters) return;
|
if (!filters) return;
|
||||||
|
|
||||||
// 优先使用 _sortDisplay(原始排序值),否则回退到 sort
|
// 排序
|
||||||
// 这样可以正确显示 returns_avg, returns_week 等复合排序选项
|
|
||||||
if (filters._sortDisplay || filters.sort) {
|
if (filters._sortDisplay || filters.sort) {
|
||||||
setSort(filters._sortDisplay || filters.sort);
|
setSort(filters._sortDisplay || filters.sort);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 重要性
|
||||||
if (filters.importance) {
|
if (filters.importance) {
|
||||||
const importanceArray = filters.importance === 'all'
|
const importanceArray =
|
||||||
|
filters.importance === "all"
|
||||||
? []
|
? []
|
||||||
: filters.importance.split(',').map(v => v.trim()).filter(Boolean);
|
: filters.importance
|
||||||
|
.split(",")
|
||||||
|
.map((v) => v.trim())
|
||||||
|
.filter(Boolean);
|
||||||
setImportance(importanceArray);
|
setImportance(importanceArray);
|
||||||
} else {
|
} else {
|
||||||
setImportance([]);
|
setImportance([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filters.industry_code && industryData && industryData.length > 0 && (!industryValue || industryValue.length === 0)) {
|
// 行业
|
||||||
|
if (
|
||||||
|
filters.industry_code &&
|
||||||
|
industryData &&
|
||||||
|
industryData.length > 0 &&
|
||||||
|
(!industryValue || industryValue.length === 0)
|
||||||
|
) {
|
||||||
const path = findIndustryPath(filters.industry_code, industryData);
|
const path = findIndustryPath(filters.industry_code, industryData);
|
||||||
if (path) {
|
if (path) {
|
||||||
setIndustryValue(path);
|
setIndustryValue(path);
|
||||||
}
|
}
|
||||||
} else if (!filters.industry_code && industryValue && industryValue.length > 0) {
|
} else if (
|
||||||
|
!filters.industry_code &&
|
||||||
|
industryValue &&
|
||||||
|
industryValue.length > 0
|
||||||
|
) {
|
||||||
setIndustryValue([]);
|
setIndustryValue([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 搜索关键词
|
||||||
if (filters.q) {
|
if (filters.q) {
|
||||||
// 如果是股票选择触发的搜索,使用存储的显示值(代码+名称)
|
if (
|
||||||
if (stockDisplayValueRef.current && stockDisplayValueRef.current.code === filters.q) {
|
stockDisplayValueRef.current &&
|
||||||
|
stockDisplayValueRef.current.code === filters.q
|
||||||
|
) {
|
||||||
setInputValue(stockDisplayValueRef.current.displayValue);
|
setInputValue(stockDisplayValueRef.current.displayValue);
|
||||||
} else {
|
} else {
|
||||||
setInputValue(filters.q);
|
setInputValue(filters.q);
|
||||||
// 清除已失效的显示值缓存
|
|
||||||
stockDisplayValueRef.current = null;
|
stockDisplayValueRef.current = null;
|
||||||
}
|
}
|
||||||
} else if (!filters.q) {
|
} else if (!filters.q) {
|
||||||
setInputValue('');
|
setInputValue("");
|
||||||
stockDisplayValueRef.current = null;
|
stockDisplayValueRef.current = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasTimeInFilters = filters.start_date || filters.end_date || filters.recent_days || filters.time_filter_key;
|
// 时间范围
|
||||||
|
const hasTimeInFilters =
|
||||||
|
filters.start_date ||
|
||||||
|
filters.end_date ||
|
||||||
|
filters.recent_days ||
|
||||||
|
filters.time_filter_key;
|
||||||
|
|
||||||
if (hasTimeInFilters && (!tradingTimeRange || !tradingTimeRange.key)) {
|
if (hasTimeInFilters && (!tradingTimeRange || !tradingTimeRange.key)) {
|
||||||
// 优先使用 time_filter_key(来自 useEventFilters 的默认值)
|
const timeRange = inferTimeRangeFromFilters(filters);
|
||||||
let inferredKey = filters.time_filter_key || 'custom';
|
if (timeRange) {
|
||||||
let inferredLabel = '';
|
|
||||||
|
|
||||||
if (filters.time_filter_key === 'current-trading-day') {
|
|
||||||
inferredKey = 'current-trading-day';
|
|
||||||
inferredLabel = '当前交易日';
|
|
||||||
} else if (filters.time_filter_key === 'all') {
|
|
||||||
inferredKey = 'all';
|
|
||||||
inferredLabel = '全部';
|
|
||||||
} else if (filters.recent_days) {
|
|
||||||
if (filters.recent_days === '7') {
|
|
||||||
inferredKey = 'week';
|
|
||||||
inferredLabel = '近一周';
|
|
||||||
} else if (filters.recent_days === '30') {
|
|
||||||
inferredKey = 'month';
|
|
||||||
inferredLabel = '近一月';
|
|
||||||
} else {
|
|
||||||
inferredLabel = `近${filters.recent_days}天`;
|
|
||||||
}
|
|
||||||
} else if (filters.start_date && filters.end_date) {
|
|
||||||
inferredLabel = `${dayjs(filters.start_date).format('MM-DD HH:mm')} - ${dayjs(filters.end_date).format('MM-DD HH:mm')}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const timeRange = {
|
|
||||||
start_date: filters.start_date || '',
|
|
||||||
end_date: filters.end_date || '',
|
|
||||||
recent_days: filters.recent_days || '',
|
|
||||||
label: inferredLabel,
|
|
||||||
key: inferredKey
|
|
||||||
};
|
|
||||||
setTradingTimeRange(timeRange);
|
setTradingTimeRange(timeRange);
|
||||||
|
}
|
||||||
} else if (!hasTimeInFilters && tradingTimeRange) {
|
} else if (!hasTimeInFilters && tradingTimeRange) {
|
||||||
setTradingTimeRange(null);
|
setTradingTimeRange(null);
|
||||||
}
|
}
|
||||||
}, [filters, industryData, findIndustryPath, industryValue, tradingTimeRange]);
|
}, [filters, industryData, industryValue, tradingTimeRange]);
|
||||||
|
|
||||||
// 搜索股票
|
// 股票搜索
|
||||||
const handleSearch = (value) => {
|
const handleSearch = (value) => {
|
||||||
if (!value || !allStocks || allStocks.length === 0) {
|
if (!value || !allStocks || allStocks.length === 0) {
|
||||||
setStockOptions([]);
|
setStockOptions([]);
|
||||||
@@ -215,231 +218,132 @@ const CompactSearchBox = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const results = stockService.fuzzySearch(value, allStocks, 10);
|
const results = stockService.fuzzySearch(value, allStocks, 10);
|
||||||
const options = results.map(stock => ({
|
const options = results.map((stock) => ({
|
||||||
value: stock.code,
|
value: stock.code,
|
||||||
label: (
|
label: (
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
||||||
<StockOutlined style={{ color: '#1890ff' }} />
|
<StockOutlined style={{ color: "#1890ff" }} />
|
||||||
<span style={{ fontWeight: 500, color: '#333' }}>{stock.code}</span>
|
<span style={{ fontWeight: 500, color: "#333" }}>{stock.code}</span>
|
||||||
<span style={{ color: '#666' }}>{stock.name}</span>
|
<span style={{ color: "#666" }}>{stock.name}</span>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
stockInfo: stock
|
stockInfo: stock,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
setStockOptions(options);
|
setStockOptions(options);
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildFilterParams = useCallback((overrides = {}) => {
|
// 股票选择
|
||||||
const sortValue = overrides.sort ?? sort;
|
|
||||||
let actualSort = sortValue;
|
|
||||||
let returnType;
|
|
||||||
|
|
||||||
if (sortValue === 'returns_avg') {
|
|
||||||
actualSort = 'returns';
|
|
||||||
returnType = 'avg';
|
|
||||||
} else if (sortValue === 'returns_week') {
|
|
||||||
actualSort = 'returns';
|
|
||||||
returnType = 'week';
|
|
||||||
}
|
|
||||||
|
|
||||||
let importanceValue = overrides.importance ?? importance;
|
|
||||||
if (Array.isArray(importanceValue)) {
|
|
||||||
importanceValue = importanceValue.length === 0
|
|
||||||
? 'all'
|
|
||||||
: importanceValue.join(',');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 先展开 overrides,再用处理后的值覆盖,避免 overrides.sort 覆盖 actualSort
|
|
||||||
const result = {
|
|
||||||
...overrides,
|
|
||||||
sort: actualSort,
|
|
||||||
// 保留原始排序值用于 UI 显示(如 returns_avg, returns_week)
|
|
||||||
_sortDisplay: sortValue,
|
|
||||||
importance: importanceValue,
|
|
||||||
q: (overrides.q ?? filters.q) ?? '',
|
|
||||||
industry_code: overrides.industry_code ?? (industryValue?.join(',') || ''),
|
|
||||||
start_date: overrides.start_date ?? (tradingTimeRange?.start_date || ''),
|
|
||||||
end_date: overrides.end_date ?? (tradingTimeRange?.end_date || ''),
|
|
||||||
recent_days: overrides.recent_days ?? (tradingTimeRange?.recent_days || ''),
|
|
||||||
page: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
// 移除不需要的字段
|
|
||||||
delete result.per_page;
|
|
||||||
|
|
||||||
// 添加 return_type 参数(用于收益排序)
|
|
||||||
if (returnType) {
|
|
||||||
result.return_type = returnType;
|
|
||||||
} else {
|
|
||||||
// 确保非收益排序时不带 return_type
|
|
||||||
delete result.return_type;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mode !== undefined && mode !== null) {
|
|
||||||
result.mode = mode;
|
|
||||||
}
|
|
||||||
if (pageSize !== undefined && pageSize !== null) {
|
|
||||||
result.per_page = pageSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}, [sort, importance, filters.q, industryValue, tradingTimeRange, mode, pageSize]);
|
|
||||||
|
|
||||||
const handleStockSelect = (_value, option) => {
|
const handleStockSelect = (_value, option) => {
|
||||||
const stockInfo = option.stockInfo;
|
const stockInfo = option.stockInfo;
|
||||||
if (stockInfo) {
|
if (stockInfo) {
|
||||||
if (trackingFunctions.trackRelatedStockClicked) {
|
trackingFunctions.trackRelatedStockClicked?.({
|
||||||
trackingFunctions.trackRelatedStockClicked({
|
|
||||||
stockCode: stockInfo.code,
|
stockCode: stockInfo.code,
|
||||||
stockName: stockInfo.name,
|
stockName: stockInfo.name,
|
||||||
source: 'search_box_autocomplete',
|
source: "search_box_autocomplete",
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
const displayValue = `${stockInfo.code} ${stockInfo.name}`;
|
const displayValue = `${stockInfo.code} ${stockInfo.name}`;
|
||||||
setInputValue(displayValue);
|
setInputValue(displayValue);
|
||||||
// 存储显示值,供 useEffect 同步时使用
|
|
||||||
stockDisplayValueRef.current = { code: stockInfo.code, displayValue };
|
stockDisplayValueRef.current = { code: stockInfo.code, displayValue };
|
||||||
|
|
||||||
const params = buildFilterParams({
|
const params = createFilterParams({ q: stockInfo.code, industry_code: "" });
|
||||||
q: stockInfo.code, // 接口只传代码
|
|
||||||
industry_code: ''
|
|
||||||
});
|
|
||||||
triggerSearch(params);
|
triggerSearch(params);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 重要性变更
|
||||||
const handleImportanceChange = (value) => {
|
const handleImportanceChange = (value) => {
|
||||||
setImportance(value);
|
setImportance(value);
|
||||||
|
debouncedSearchRef.current?.cancel();
|
||||||
|
|
||||||
if (debouncedSearchRef.current) {
|
const importanceStr = value.length === 0 ? "all" : value.join(",");
|
||||||
debouncedSearchRef.current.cancel();
|
trackingFunctions.trackNewsFilterApplied?.({
|
||||||
}
|
filterType: "importance",
|
||||||
|
|
||||||
const importanceStr = value.length === 0 ? 'all' : value.join(',');
|
|
||||||
|
|
||||||
if (trackingFunctions.trackNewsFilterApplied) {
|
|
||||||
trackingFunctions.trackNewsFilterApplied({
|
|
||||||
filterType: 'importance',
|
|
||||||
filterValue: importanceStr,
|
filterValue: importanceStr,
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
const params = buildFilterParams({ importance: importanceStr });
|
triggerSearch(createFilterParams({ importance: importanceStr }));
|
||||||
triggerSearch(params);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 排序变更
|
||||||
const handleSortChange = (value) => {
|
const handleSortChange = (value) => {
|
||||||
setSort(value);
|
setSort(value);
|
||||||
|
debouncedSearchRef.current?.cancel();
|
||||||
|
|
||||||
if (debouncedSearchRef.current) {
|
trackingFunctions.trackNewsSorted?.({
|
||||||
debouncedSearchRef.current.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (trackingFunctions.trackNewsSorted) {
|
|
||||||
trackingFunctions.trackNewsSorted({
|
|
||||||
sortBy: value,
|
sortBy: value,
|
||||||
previousSortBy: sort,
|
previousSortBy: sort,
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
const params = buildFilterParams({ sort: value });
|
triggerSearch(createFilterParams({ sort: value }));
|
||||||
triggerSearch(params);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 行业变更
|
||||||
const handleIndustryChange = (value) => {
|
const handleIndustryChange = (value) => {
|
||||||
setIndustryValue(value);
|
setIndustryValue(value);
|
||||||
|
debouncedSearchRef.current?.cancel();
|
||||||
|
|
||||||
if (debouncedSearchRef.current) {
|
trackingFunctions.trackNewsFilterApplied?.({
|
||||||
debouncedSearchRef.current.cancel();
|
filterType: "industry",
|
||||||
}
|
filterValue: value?.[value.length - 1] || "",
|
||||||
|
|
||||||
if (trackingFunctions.trackNewsFilterApplied) {
|
|
||||||
trackingFunctions.trackNewsFilterApplied({
|
|
||||||
filterType: 'industry',
|
|
||||||
filterValue: value?.[value.length - 1] || '',
|
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
const params = buildFilterParams({
|
triggerSearch(createFilterParams({ industry_code: value?.[value.length - 1] || "" }));
|
||||||
industry_code: value?.[value.length - 1] || ''
|
|
||||||
});
|
|
||||||
triggerSearch(params);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 时间筛选变更
|
||||||
const handleTradingTimeChange = (timeConfig) => {
|
const handleTradingTimeChange = (timeConfig) => {
|
||||||
if (!timeConfig) {
|
if (!timeConfig) {
|
||||||
setTradingTimeRange(null);
|
setTradingTimeRange(null);
|
||||||
|
trackingFunctions.trackNewsFilterApplied?.({
|
||||||
if (trackingFunctions.trackNewsFilterApplied) {
|
filterType: "time_range",
|
||||||
trackingFunctions.trackNewsFilterApplied({
|
filterValue: "cleared",
|
||||||
filterType: 'time_range',
|
|
||||||
filterValue: 'cleared',
|
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
}
|
triggerSearch(createFilterParams({ start_date: "", end_date: "", recent_days: "" }));
|
||||||
|
|
||||||
const params = buildFilterParams({
|
|
||||||
start_date: '',
|
|
||||||
end_date: '',
|
|
||||||
recent_days: ''
|
|
||||||
});
|
|
||||||
triggerSearch(params);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { range, type, label, key } = timeConfig;
|
const { range, type, label, key } = timeConfig;
|
||||||
let params = {};
|
let params = {};
|
||||||
|
|
||||||
if (type === 'all') {
|
if (type === "all") {
|
||||||
// "全部"按钮:清除所有时间限制
|
params = { start_date: "", end_date: "", recent_days: "" };
|
||||||
params.start_date = '';
|
} else if (type === "recent_days") {
|
||||||
params.end_date = '';
|
params = { recent_days: range, start_date: "", end_date: "" };
|
||||||
params.recent_days = '';
|
|
||||||
} else if (type === 'recent_days') {
|
|
||||||
params.recent_days = range;
|
|
||||||
params.start_date = '';
|
|
||||||
params.end_date = '';
|
|
||||||
} else {
|
} else {
|
||||||
params.start_date = range[0].format('YYYY-MM-DD HH:mm:ss');
|
params = {
|
||||||
params.end_date = range[1].format('YYYY-MM-DD HH:mm:ss');
|
start_date: range[0].format("YYYY-MM-DD HH:mm:ss"),
|
||||||
params.recent_days = '';
|
end_date: range[1].format("YYYY-MM-DD HH:mm:ss"),
|
||||||
|
recent_days: "",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
setTradingTimeRange({ ...params, label, key });
|
setTradingTimeRange({ ...params, label, key });
|
||||||
|
trackingFunctions.trackNewsFilterApplied?.({
|
||||||
if (trackingFunctions.trackNewsFilterApplied) {
|
filterType: "time_range",
|
||||||
trackingFunctions.trackNewsFilterApplied({
|
|
||||||
filterType: 'time_range',
|
|
||||||
filterValue: label,
|
filterValue: label,
|
||||||
timeRangeType: type,
|
timeRangeType: type,
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
const searchParams = buildFilterParams({ ...params, mode });
|
triggerSearch(createFilterParams({ ...params, mode }));
|
||||||
triggerSearch(searchParams);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 主搜索
|
||||||
const handleMainSearch = () => {
|
const handleMainSearch = () => {
|
||||||
if (debouncedSearchRef.current) {
|
debouncedSearchRef.current?.cancel();
|
||||||
debouncedSearchRef.current.cancel();
|
const params = createFilterParams({ q: inputValue, industry_code: "" });
|
||||||
}
|
|
||||||
|
|
||||||
const params = buildFilterParams({
|
if (inputValue) {
|
||||||
q: inputValue,
|
trackingFunctions.trackNewsSearched?.({
|
||||||
industry_code: ''
|
|
||||||
});
|
|
||||||
|
|
||||||
if (trackingFunctions.trackNewsSearched && inputValue) {
|
|
||||||
trackingFunctions.trackNewsSearched({
|
|
||||||
searchQuery: inputValue,
|
searchQuery: inputValue,
|
||||||
searchType: 'main_search',
|
searchType: "main_search",
|
||||||
filters: params,
|
filters: params,
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
@@ -448,39 +352,32 @@ const CompactSearchBox = ({
|
|||||||
triggerSearch(params);
|
triggerSearch(params);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleInputChange = (value) => {
|
// 重置
|
||||||
setInputValue(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
setInputValue('');
|
setInputValue("");
|
||||||
setStockOptions([]);
|
setStockOptions([]);
|
||||||
setIndustryValue([]);
|
setIndustryValue([]);
|
||||||
setSort('new');
|
setSort("new");
|
||||||
setImportance([]);
|
setImportance([]);
|
||||||
setTradingTimeRange(null);
|
setTradingTimeRange(null);
|
||||||
|
|
||||||
if (trackingFunctions.trackNewsFilterApplied) {
|
trackingFunctions.trackNewsFilterApplied?.({
|
||||||
trackingFunctions.trackNewsFilterApplied({
|
filterType: "reset",
|
||||||
filterType: 'reset',
|
filterValue: "all_filters_cleared",
|
||||||
filterValue: 'all_filters_cleared',
|
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
const resetParams = {
|
onSearch({
|
||||||
q: '',
|
q: "",
|
||||||
industry_code: '',
|
industry_code: "",
|
||||||
sort: 'new',
|
sort: "new",
|
||||||
importance: 'all',
|
importance: "all",
|
||||||
start_date: '',
|
start_date: "",
|
||||||
end_date: '',
|
end_date: "",
|
||||||
recent_days: '',
|
recent_days: "",
|
||||||
page: 1,
|
page: 1,
|
||||||
_forceRefresh: Date.now()
|
_forceRefresh: Date.now(),
|
||||||
};
|
});
|
||||||
|
|
||||||
onSearch(resetParams);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCascaderFocus = async () => {
|
const handleCascaderFocus = async () => {
|
||||||
@@ -488,57 +385,62 @@ const CompactSearchBox = ({
|
|||||||
dispatch(fetchIndustryData());
|
dispatch(fetchIndustryData());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: 0, background: 'transparent' }}>
|
<div style={{ padding: 0, background: "transparent" }}>
|
||||||
{/* 第一行:搜索框 + 日期筛选 */}
|
{/* 第一行:搜索框 + 日期筛选 */}
|
||||||
<Flex
|
<Flex
|
||||||
align="center"
|
align="center"
|
||||||
gap={isMobile ? 8 : 12}
|
gap={isMobile ? 8 : 12}
|
||||||
style={{
|
style={{
|
||||||
background: 'rgba(255, 255, 255, 0.03)',
|
background: "rgba(255, 255, 255, 0.03)",
|
||||||
border: `1px solid ${PROFESSIONAL_COLORS.gold[500]}`,
|
border: `1px solid ${PROFESSIONAL_COLORS.gold[500]}`,
|
||||||
borderRadius: '24px',
|
borderRadius: "24px",
|
||||||
padding: isMobile ? '2px 4px' : '8px 16px',
|
padding: isMobile ? "2px 4px" : "8px 16px",
|
||||||
marginBottom: isMobile ? 8 : 12
|
marginBottom: isMobile ? 8 : 12,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* 搜索框 - flex: 1 占满剩余空间 */}
|
|
||||||
<AutoComplete
|
<AutoComplete
|
||||||
value={inputValue}
|
value={inputValue}
|
||||||
onChange={handleInputChange}
|
onChange={setInputValue}
|
||||||
onSearch={handleSearch}
|
onSearch={handleSearch}
|
||||||
onSelect={handleStockSelect}
|
onSelect={handleStockSelect}
|
||||||
onFocus={onSearchFocus}
|
onFocus={onSearchFocus}
|
||||||
options={stockOptions}
|
options={stockOptions}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => e.key === "Enter" && handleMainSearch()}
|
||||||
if (e.key === 'Enter') {
|
|
||||||
handleMainSearch();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
style={{ flex: 1, minWidth: isMobile ? 100 : 200 }}
|
style={{ flex: 1, minWidth: isMobile ? 100 : 200 }}
|
||||||
className="gold-placeholder"
|
className="gold-placeholder"
|
||||||
allowClear={{
|
allowClear={{
|
||||||
clearIcon: <CloseCircleOutlined style={{ color: PROFESSIONAL_COLORS.text.muted, fontSize: 14 }} />
|
clearIcon: (
|
||||||
|
<CloseCircleOutlined
|
||||||
|
style={{ color: PROFESSIONAL_COLORS.text.muted, fontSize: 14 }}
|
||||||
|
/>
|
||||||
|
),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
prefix={<SearchOutlined style={{ color: PROFESSIONAL_COLORS.gold[500] }} />}
|
prefix={<SearchOutlined style={{ color: PROFESSIONAL_COLORS.gold[500] }} />}
|
||||||
placeholder="搜索股票/话题..."
|
placeholder="搜索股票/话题..."
|
||||||
style={{
|
style={{
|
||||||
border: 'none',
|
border: "none",
|
||||||
background: 'transparent',
|
background: "transparent",
|
||||||
color: PROFESSIONAL_COLORS.text.primary,
|
color: PROFESSIONAL_COLORS.text.primary,
|
||||||
boxShadow: 'none'
|
boxShadow: "none",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</AutoComplete>
|
</AutoComplete>
|
||||||
|
|
||||||
{/* 分隔线 - H5 时隐藏 */}
|
{!isMobile && (
|
||||||
{!isMobile && <Divider type="vertical" style={{ height: 24, margin: '0 8px', borderColor: 'rgba(255,255,255,0.15)' }} />}
|
<Divider
|
||||||
|
type="vertical"
|
||||||
|
style={{ height: 24, margin: "0 8px", borderColor: "rgba(255,255,255,0.15)" }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 日期筛选按钮组 */}
|
<div style={{ display: "flex", alignItems: "center", gap: 0 }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 0 }}>
|
<CalendarOutlined
|
||||||
<CalendarOutlined style={{ color: PROFESSIONAL_COLORS.gold[500], fontSize: 14, marginRight: 8 }} />
|
style={{ color: PROFESSIONAL_COLORS.gold[500], fontSize: 14, marginRight: 8 }}
|
||||||
|
/>
|
||||||
<TradingTimeFilter
|
<TradingTimeFilter
|
||||||
value={tradingTimeRange?.key || null}
|
value={tradingTimeRange?.key || null}
|
||||||
onChange={handleTradingTimeChange}
|
onChange={handleTradingTimeChange}
|
||||||
@@ -548,73 +450,74 @@ const CompactSearchBox = ({
|
|||||||
</div>
|
</div>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
{/* 第二行:筛选条件 - 主线模式下隐藏(主线模式有自己的筛选器) */}
|
{/* 第二行:筛选条件 */}
|
||||||
{mode !== 'mainline' && (
|
{mode !== "mainline" && (
|
||||||
<Flex justify="space-between" align="center">
|
<Flex justify="space-between" align="center">
|
||||||
{/* 左侧筛选 */}
|
|
||||||
<Space size={isMobile ? 4 : 8}>
|
<Space size={isMobile ? 4 : 8}>
|
||||||
{/* 行业筛选 */}
|
|
||||||
<Cascader
|
<Cascader
|
||||||
value={industryValue}
|
value={industryValue}
|
||||||
onChange={handleIndustryChange}
|
onChange={handleIndustryChange}
|
||||||
onFocus={handleCascaderFocus}
|
onFocus={handleCascaderFocus}
|
||||||
options={industryData || []}
|
options={industryData || []}
|
||||||
placeholder={
|
placeholder={
|
||||||
<span style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
<span style={{ display: "flex", alignItems: "center", gap: 4 }}>
|
||||||
<FilterOutlined style={{ fontSize: 12 }} />
|
<FilterOutlined style={{ fontSize: 12 }} />
|
||||||
{isMobile ? '行业' : '行业筛选'}
|
{isMobile ? "行业" : "行业筛选"}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
changeOnSelect
|
changeOnSelect
|
||||||
showSearch={{
|
showSearch={{
|
||||||
filter: (inputValue, path) =>
|
filter: (inputValue, path) =>
|
||||||
path.some(option =>
|
path.some((option) =>
|
||||||
option.label.toLowerCase().includes(inputValue.toLowerCase())
|
option.label.toLowerCase().includes(inputValue.toLowerCase())
|
||||||
)
|
),
|
||||||
}}
|
}}
|
||||||
allowClear
|
allowClear
|
||||||
expandTrigger="hover"
|
expandTrigger="hover"
|
||||||
displayRender={(labels) => labels[labels.length - 1] || (isMobile ? '行业' : '行业筛选')}
|
displayRender={(labels) =>
|
||||||
|
labels[labels.length - 1] || (isMobile ? "行业" : "行业筛选")
|
||||||
|
}
|
||||||
disabled={industryLoading}
|
disabled={industryLoading}
|
||||||
style={{ minWidth: isMobile ? 70 : 80 }}
|
style={{ minWidth: isMobile ? 70 : 80 }}
|
||||||
suffixIcon={null}
|
suffixIcon={null}
|
||||||
className="transparent-cascader"
|
className="transparent-cascader"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 事件等级 */}
|
|
||||||
<AntSelect
|
<AntSelect
|
||||||
mode="multiple"
|
mode="multiple"
|
||||||
value={importance}
|
value={importance}
|
||||||
onChange={handleImportanceChange}
|
onChange={handleImportanceChange}
|
||||||
style={{ minWidth: isMobile ? 100 : 120 }}
|
style={{ minWidth: isMobile ? 100 : 120 }}
|
||||||
placeholder={
|
placeholder={
|
||||||
<span style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
<span style={{ display: "flex", alignItems: "center", gap: 4 }}>
|
||||||
<ThunderboltOutlined style={{ fontSize: 12 }} />
|
<ThunderboltOutlined style={{ fontSize: 12 }} />
|
||||||
{isMobile ? '等级' : '事件等级'}
|
{isMobile ? "等级" : "事件等级"}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
maxTagCount={0}
|
maxTagCount={0}
|
||||||
maxTagPlaceholder={(omittedValues) => isMobile ? `${omittedValues.length}项` : `已选 ${omittedValues.length} 项`}
|
maxTagPlaceholder={(omittedValues) =>
|
||||||
|
isMobile ? `${omittedValues.length}项` : `已选 ${omittedValues.length} 项`
|
||||||
|
}
|
||||||
className="bracket-select"
|
className="bracket-select"
|
||||||
>
|
>
|
||||||
{IMPORTANCE_OPTIONS.map(opt => (
|
{IMPORTANCE_OPTIONS.map((opt) => (
|
||||||
<Option key={opt.value} value={opt.value}>{opt.label}</Option>
|
<Option key={opt.value} value={opt.value}>
|
||||||
|
{opt.label}
|
||||||
|
</Option>
|
||||||
))}
|
))}
|
||||||
</AntSelect>
|
</AntSelect>
|
||||||
</Space>
|
</Space>
|
||||||
|
|
||||||
{/* 右侧排序和重置 */}
|
|
||||||
<Space size={isMobile ? 4 : 8}>
|
<Space size={isMobile ? 4 : 8}>
|
||||||
{/* 排序 */}
|
|
||||||
<AntSelect
|
<AntSelect
|
||||||
value={sort}
|
value={sort}
|
||||||
onChange={handleSortChange}
|
onChange={handleSortChange}
|
||||||
style={{ minWidth: isMobile ? 55 : 120 }}
|
style={{ minWidth: isMobile ? 55 : 120 }}
|
||||||
className="bracket-select"
|
className="bracket-select"
|
||||||
>
|
>
|
||||||
{SORT_OPTIONS.map(opt => (
|
{SORT_OPTIONS.map((opt) => (
|
||||||
<Option key={opt.value} value={opt.value}>
|
<Option key={opt.value} value={opt.value}>
|
||||||
<span style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
<span style={{ display: "flex", alignItems: "center", gap: 4 }}>
|
||||||
<SortAscendingOutlined style={{ fontSize: 12 }} />
|
<SortAscendingOutlined style={{ fontSize: 12 }} />
|
||||||
{isMobile ? opt.mobileLabel : opt.label}
|
{isMobile ? opt.mobileLabel : opt.label}
|
||||||
</span>
|
</span>
|
||||||
@@ -622,14 +525,13 @@ const CompactSearchBox = ({
|
|||||||
))}
|
))}
|
||||||
</AntSelect>
|
</AntSelect>
|
||||||
|
|
||||||
{/* 重置按钮 */}
|
|
||||||
<Button
|
<Button
|
||||||
icon={<ReloadOutlined />}
|
icon={<ReloadOutlined />}
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
type="text"
|
type="text"
|
||||||
style={{ color: PROFESSIONAL_COLORS.text.secondary }}
|
style={{ color: PROFESSIONAL_COLORS.text.secondary }}
|
||||||
>
|
>
|
||||||
{!isMobile && '重置筛选'}
|
{!isMobile && "重置筛选"}
|
||||||
</Button>
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
// CompactSearchBox 常量定义
|
||||||
|
|
||||||
|
// 排序选项常量
|
||||||
|
export const SORT_OPTIONS = [
|
||||||
|
{ value: "new", label: "最新排序", mobileLabel: "最新" },
|
||||||
|
{ value: "hot", label: "最热排序", mobileLabel: "热门" },
|
||||||
|
{ value: "importance", label: "重要性排序", mobileLabel: "重要" },
|
||||||
|
{ value: "returns_avg", label: "平均收益", mobileLabel: "均收" },
|
||||||
|
{ value: "returns_week", label: "周收益", mobileLabel: "周收" },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 重要性等级常量
|
||||||
|
export const IMPORTANCE_OPTIONS = [
|
||||||
|
{ value: "S", label: "S级" },
|
||||||
|
{ value: "A", label: "A级" },
|
||||||
|
{ value: "B", label: "B级" },
|
||||||
|
{ value: "C", label: "C级" },
|
||||||
|
];
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// CompactSearchBox 模块导出
|
||||||
|
|
||||||
|
export { SORT_OPTIONS, IMPORTANCE_OPTIONS } from "./constants";
|
||||||
|
export { findIndustryPath, inferTimeRangeFromFilters, buildFilterParams } from "./utils";
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
// CompactSearchBox 工具函数
|
||||||
|
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在行业树中查找指定代码的完整路径
|
||||||
|
* @param {string} targetCode - 目标行业代码
|
||||||
|
* @param {Array} data - 行业数据树
|
||||||
|
* @param {Array} currentPath - 当前路径(递归用)
|
||||||
|
* @returns {Array|null} - 找到的完整路径,未找到返回 null
|
||||||
|
*/
|
||||||
|
export const findIndustryPath = (targetCode, data, currentPath = []) => {
|
||||||
|
if (!data || data.length === 0) return null;
|
||||||
|
|
||||||
|
for (const item of data) {
|
||||||
|
const newPath = [...currentPath, item.value];
|
||||||
|
|
||||||
|
if (item.value === targetCode) {
|
||||||
|
return newPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.children && item.children.length > 0) {
|
||||||
|
const found = findIndustryPath(targetCode, item.children, newPath);
|
||||||
|
if (found) return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 filters 中推断时间范围配置
|
||||||
|
* @param {Object} filters - 筛选条件
|
||||||
|
* @returns {Object|null} - 时间范围配置
|
||||||
|
*/
|
||||||
|
export const inferTimeRangeFromFilters = (filters) => {
|
||||||
|
if (!filters) return null;
|
||||||
|
|
||||||
|
const hasTimeInFilters =
|
||||||
|
filters.start_date ||
|
||||||
|
filters.end_date ||
|
||||||
|
filters.recent_days ||
|
||||||
|
filters.time_filter_key;
|
||||||
|
|
||||||
|
if (!hasTimeInFilters) return null;
|
||||||
|
|
||||||
|
let inferredKey = filters.time_filter_key || "custom";
|
||||||
|
let inferredLabel = "";
|
||||||
|
|
||||||
|
if (filters.time_filter_key === "current-trading-day") {
|
||||||
|
inferredKey = "current-trading-day";
|
||||||
|
inferredLabel = "当前交易日";
|
||||||
|
} else if (filters.time_filter_key === "all") {
|
||||||
|
inferredKey = "all";
|
||||||
|
inferredLabel = "全部";
|
||||||
|
} else if (filters.recent_days) {
|
||||||
|
if (filters.recent_days === "7") {
|
||||||
|
inferredKey = "week";
|
||||||
|
inferredLabel = "近一周";
|
||||||
|
} else if (filters.recent_days === "30") {
|
||||||
|
inferredKey = "month";
|
||||||
|
inferredLabel = "近一月";
|
||||||
|
} else {
|
||||||
|
inferredLabel = `近${filters.recent_days}天`;
|
||||||
|
}
|
||||||
|
} else if (filters.start_date && filters.end_date) {
|
||||||
|
inferredLabel = `${dayjs(filters.start_date).format("MM-DD HH:mm")} - ${dayjs(filters.end_date).format("MM-DD HH:mm")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
start_date: filters.start_date || "",
|
||||||
|
end_date: filters.end_date || "",
|
||||||
|
recent_days: filters.recent_days || "",
|
||||||
|
label: inferredLabel,
|
||||||
|
key: inferredKey,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建筛选参数
|
||||||
|
* @param {Object} options - 配置选项
|
||||||
|
* @returns {Object} - 构建的参数对象
|
||||||
|
*/
|
||||||
|
export const buildFilterParams = ({
|
||||||
|
overrides = {},
|
||||||
|
sort,
|
||||||
|
importance,
|
||||||
|
filtersQ,
|
||||||
|
industryValue,
|
||||||
|
tradingTimeRange,
|
||||||
|
mode,
|
||||||
|
pageSize,
|
||||||
|
}) => {
|
||||||
|
const sortValue = overrides.sort ?? sort;
|
||||||
|
let actualSort = sortValue;
|
||||||
|
let returnType;
|
||||||
|
|
||||||
|
if (sortValue === "returns_avg") {
|
||||||
|
actualSort = "returns";
|
||||||
|
returnType = "avg";
|
||||||
|
} else if (sortValue === "returns_week") {
|
||||||
|
actualSort = "returns";
|
||||||
|
returnType = "week";
|
||||||
|
}
|
||||||
|
|
||||||
|
let importanceValue = overrides.importance ?? importance;
|
||||||
|
if (Array.isArray(importanceValue)) {
|
||||||
|
importanceValue =
|
||||||
|
importanceValue.length === 0 ? "all" : importanceValue.join(",");
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
...overrides,
|
||||||
|
sort: actualSort,
|
||||||
|
_sortDisplay: sortValue,
|
||||||
|
importance: importanceValue,
|
||||||
|
q: overrides.q ?? filtersQ ?? "",
|
||||||
|
industry_code: overrides.industry_code ?? (industryValue?.join(",") || ""),
|
||||||
|
start_date: overrides.start_date ?? (tradingTimeRange?.start_date || ""),
|
||||||
|
end_date: overrides.end_date ?? (tradingTimeRange?.end_date || ""),
|
||||||
|
recent_days: overrides.recent_days ?? (tradingTimeRange?.recent_days || ""),
|
||||||
|
page: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
delete result.per_page;
|
||||||
|
|
||||||
|
if (returnType) {
|
||||||
|
result.return_type = returnType;
|
||||||
|
} else {
|
||||||
|
delete result.return_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode !== undefined && mode !== null) {
|
||||||
|
result.mode = mode;
|
||||||
|
}
|
||||||
|
if (pageSize !== undefined && pageSize !== null) {
|
||||||
|
result.per_page = pageSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user