diff --git a/app.py b/app.py index b98b378d..b5b31b6b 100755 --- a/app.py +++ b/app.py @@ -6521,10 +6521,25 @@ def api_get_events(): # 新增:行业代码过滤(申银万国行业分类) if industry_code: # related_industries 格式: [{"申银万国行业分类": "S370502"}, ...] + # 支持多个行业代码,用逗号分隔 json_path = '$[*]."申银万国行业分类"' - query = query.filter( - text("JSON_CONTAINS(JSON_EXTRACT(related_industries, :json_path), :industry_code)") - ).params(json_path=json_path, industry_code=json.dumps(industry_code)) + + # 如果包含逗号,说明是多个行业代码 + if ',' in industry_code: + codes = [code.strip() for code in industry_code.split(',') if code.strip()] + # 使用 OR 条件匹配任意一个行业代码 + conditions = [] + for code in codes: + conditions.append( + text("JSON_CONTAINS(JSON_EXTRACT(related_industries, :json_path), :code)") + .bindparams(json_path=json_path, code=json.dumps(code)) + ) + query = query.filter(db.or_(*conditions)) + else: + # 单个行业代码 + query = query.filter( + text("JSON_CONTAINS(JSON_EXTRACT(related_industries, :json_path), :industry_code)") + ).params(json_path=json_path, industry_code=json.dumps(industry_code)) # 新增:关键词/全文搜索过滤(MySQL JSON) if search_query: like_pattern = f"%{search_query}%" diff --git a/src/views/Community/components/EventFilters.js b/src/views/Community/components/EventFilters.js index dd40adfc..76861277 100644 --- a/src/views/Community/components/EventFilters.js +++ b/src/views/Community/components/EventFilters.js @@ -1,6 +1,6 @@ // src/views/Community/components/EventFilters.js import React, { useState, useEffect } from 'react'; -import { Card, Row, Col, DatePicker, Button, Select, Form, Input } from 'antd'; +import { Card, Row, Col, DatePicker, Button, Select, Form, Input, Cascader } from 'antd'; import { FilterOutlined } from '@ant-design/icons'; import moment from 'moment'; import locale from 'antd/es/date-picker/locale/zh_CN'; @@ -12,13 +12,7 @@ const { Option } = Select; const EventFilters = ({ filters, onFilterChange, loading }) => { const [form] = Form.useForm(); - const [industryData, setIndustryData] = useState({ - classifications: [], - level1: [], - level2: [], - level3: [], - level4: [] - }); + const [industryOptions, setIndustryOptions] = useState([]); // 初始化表单值 useEffect(() => { @@ -26,43 +20,26 @@ const EventFilters = ({ filters, onFilterChange, loading }) => { date_range: filters.date_range ? filters.date_range.split(' 至 ').map(d => moment(d)) : null, sort: filters.sort, importance: filters.importance, - industry_classification: filters.industry_classification, - industry_code: filters.industry_code + industry_code: filters.industry_code ? filters.industry_code.split(',') : [] }; form.setFieldsValue(initialValues); }, [filters, form]); - // 加载行业分类数据 + // 加载申银万国行业分类树形数据 const loadIndustryClassifications = async () => { try { const response = await industryService.getClassifications(); - setIndustryData(prev => ({ ...prev, classifications: response.data })); - logger.debug('EventFilters', '行业分类加载成功', { - count: response.data?.length || 0 - }); + if (response.success && response.data) { + setIndustryOptions(response.data); + logger.debug('EventFilters', '申银万国行业分类加载成功', { + count: response.data?.length || 0 + }); + } } catch (error) { logger.error('EventFilters', 'loadIndustryClassifications', error); } }; - // 加载行业层级数据 - const loadIndustryLevels = async (level, params) => { - try { - const response = await industryService.getLevels(params); - setIndustryData(prev => ({ ...prev, [`level${level}`]: response.data })); - // 清空下级 - for (let l = level + 1; l <= 4; l++) { - setIndustryData(prev => ({ ...prev, [`level${l}`]: [] })); - } - logger.debug('EventFilters', '行业层级数据加载成功', { - level, - count: response.data?.length || 0 - }); - } catch (error) { - logger.error('EventFilters', 'loadIndustryLevels', error, { level, params }); - } - }; - useEffect(() => { loadIndustryClassifications(); }, []); @@ -84,54 +61,61 @@ const EventFilters = ({ filters, onFilterChange, loading }) => { onFilterChange('importance', value); }; - // 行业分类体系变化时,加载一级行业 - const handleIndustryClassificationChange = (value) => { - form.setFieldsValue({ industry_code: '' }); - onFilterChange('industry_classification', value); - setIndustryData(prev => ({ ...prev, level1: [], level2: [], level3: [], level4: [] })); - if (value) { - loadIndustryLevels(1, { classification: value, level: 1 }); + // 收集所有叶子节点的 value(递归) + const collectLeafValues = (node) => { + // 如果没有子节点,说明是叶子节点 + if (!node.children || node.children.length === 0) { + return [node.value]; } + + // 有子节点,递归收集所有子节点的叶子节点 + let leafValues = []; + node.children.forEach(child => { + leafValues = leafValues.concat(collectLeafValues(child)); + }); + return leafValues; }; - // 级联选择行业 - const handleLevelChange = (level, value) => { - // 直接从state里查找name - let name = ''; - if (level === 1) { - const found = industryData.level1.find(item => item.code === value); - name = found ? found.name : ''; - } else if (level === 2) { - const found = industryData.level2.find(item => item.code === value); - name = found ? found.name : ''; - } else if (level === 3) { - const found = industryData.level3.find(item => item.code === value); - name = found ? found.name : ''; - } else if (level === 4) { - const found = industryData.level4.find(item => item.code === value); - name = found ? found.name : ''; + // 根据级联路径找到对应的节点 + const findNodeByPath = (options, path) => { + let current = options; + let node = null; + + for (let i = 0; i < path.length; i++) { + node = current.find(item => item.value === path[i]); + if (!node) return null; + if (i < path.length - 1) { + current = node.children || []; + } } - form.setFieldsValue({ [`level${level}`]: value }); - form.setFieldsValue({ industry_code: value }); - onFilterChange('industry_code', value); - for (let l = level + 1; l <= 4; l++) { - form.setFieldsValue({ [`level${l}`]: undefined }); + return node; + }; + + // 行业级联选择变化 + const handleIndustryChange = (value, selectedOptions) => { + if (!value || value.length === 0) { + onFilterChange('industry_code', ''); + return; } - const params = { classification: form.getFieldValue('industry_classification'), level: level + 1 }; - if (level === 1) params.level1_name = name; - if (level === 2) { - params.level1_name = form.getFieldValue('level1_name'); - params.level2_name = name; + + // 获取选中的节点 + const selectedNode = findNodeByPath(industryOptions, value); + + if (!selectedNode) { + // 如果找不到节点,使用最后一个值 + onFilterChange('industry_code', value[value.length - 1]); + return; } - if (level === 3) { - params.level1_name = form.getFieldValue('level1_name'); - params.level2_name = form.getFieldValue('level2_name'); - params.level3_name = name; + + // 如果选中的节点有子节点,收集所有叶子节点的 value + // 这样可以匹配该级别下的所有事件 + if (selectedNode.children && selectedNode.children.length > 0) { + const leafValues = collectLeafValues(selectedNode); + onFilterChange('industry_code', leafValues.join(',')); + } else { + // 叶子节点,直接使用该 value + onFilterChange('industry_code', selectedNode.value); } - if (level < 4 && name) { - loadIndustryLevels(level + 1, params); - } - form.setFieldsValue({ [`level${level}_name`]: name }); }; return ( @@ -177,74 +161,22 @@ const EventFilters = ({ filters, onFilterChange, loading }) => { - - - - - - - - - - - - - - - - - - - - - - - + style={{ width: '100%' }} + displayRender={(labels) => labels.join(' / ')} + />