diff --git a/src/pages/Analysis/Industrytrend/index.tsx b/src/pages/Analysis/Industrytrend/index.tsx index 28da5fe..46bbe89 100644 --- a/src/pages/Analysis/Industrytrend/index.tsx +++ b/src/pages/Analysis/Industrytrend/index.tsx @@ -4,37 +4,26 @@ import { Line } from '@ant-design/charts'; import { getIndustryTrend } from '@/services/analysis/industry'; import dayjs from 'dayjs'; import { useRequest } from '@umijs/max'; +import { + TimeDimension, + AnalysisType, + IndustryTrendState, + IndustryDataItem, + ChartConfig +} from '@/types/analysis/industry'; +import { + formatQuarter, + formatDateForDisplay, + convertApiData +} from './utils'; const { Option } = Select; const { RangePicker } = DatePicker; -type IndustryDataItem = { - date: string; - category: string; - value: number; -}; -const formatDateForDisplay = (dateStr: string, dimension: string): string => { - try { - if (dimension === '年') { - return dateStr.split('-')[0]; - } - if (dimension === '季度') { - const [year, quarter] = dateStr.split('-Q'); - return `${year}年Q${quarter}`; - } - // 默认按月显示 - const [year, month] = dateStr.split('-'); - return `${year}年${month}月`; - } catch (e) { - console.error('日期格式化错误:', e); - return dateStr; - } -}; - const IndustryTrendPage: React.FC = () => { - const [params, setParams] = useState({ - timeDimension: '月' as '月' | '季度' | '年', - type: '岗位发布数量' as '岗位发布数量' | '招聘增长率', + const [params, setParams] = useState({ + timeDimension: '月', + type: '岗位发布数量', startTime: dayjs().subtract(5, 'month').format('YYYY-MM'), endTime: dayjs().format('YYYY-MM'), selectedIndustry: '' @@ -42,102 +31,55 @@ const IndustryTrendPage: React.FC = () => { const [allData, setAllData] = useState([]); const [availableIndustries, setAvailableIndustries] = useState([]); + const { loading, run: fetchData } = useRequest( async () => { - try { - const resp = await getIndustryTrend({ - timeDimension: params.timeDimension, - type: params.type, - startTime: formatTimeParam(params.startTime, params.timeDimension), - endTime: formatTimeParam(params.endTime, params.timeDimension) - }); - return resp; - } catch (error) { - message.error('数据加载失败'); - throw error; + let { startTime, endTime, timeDimension, type } = params; + + if (timeDimension === '季度') { + startTime = formatQuarter(startTime); + endTime = formatQuarter(endTime); } + + return await getIndustryTrend({ + timeDimension, + type, + startTime, + endTime + }); }, { manual: true, onSuccess: (data) => { const formattedData = convertApiData(data); setAllData(formattedData); + const industries = Array.from( - new Set(formattedData.map(item => item.category)) + new Set(formattedData.map((item: { category: any; }) => item.category)) ).filter(Boolean).sort(); setAvailableIndustries(industries); + if (industries.length > 0 && !industries.includes(params.selectedIndustry)) { setParams(p => ({ ...p, selectedIndustry: industries[0] })); } - } + }, + onError: () => message.error('数据加载失败') } ); - const formatTimeParam = (dateStr: string, dimension: string): string => { - try { - const date = dayjs(dateStr); - switch (dimension) { - case '季度': - return `${date.year()}-Q${Math.ceil((date.month() + 1) / 3)}`; - case '年': - return `${date.year()}`; - default: - return date.format('YYYY-MM'); - } - } catch (e) { - console.error('日期参数格式化错误:', e); - return dateStr; - } - }; - - const convertApiData = (apiData: any): IndustryDataItem[] => { - if (!apiData) return []; - - try { - if (Array.isArray(apiData)) { - return apiData.map(item => ({ - date: item.time || item.date, - category: item.name || item.category || '未知行业', - value: Number(item.data || item.value) || 0 - })); - } - if (typeof apiData === 'object') { - const result: IndustryDataItem[] = []; - Object.entries(apiData).forEach(([date, items]) => { - if (Array.isArray(items)) { - items.forEach((item: any) => { - result.push({ - date, - category: item.name || item.category || '未知行业', - value: Number(item.data || item.value) || 0 - }); - }); - } - }); - return result; - } - - return []; - } catch (error) { - console.error('数据转换错误:', error); - return []; - } - }; - const handleTimeDimensionChange = (value: '月' | '季度' | '年') => { + const handleTimeDimensionChange = (value: TimeDimension) => { const now = dayjs(); let newStartTime = ''; - switch (value) { - case '月': - newStartTime = now.subtract(6, 'month').format('YYYY-MM'); - break; - case '季度': - newStartTime = now.subtract(6, 'quarter').format('YYYY-Q'); - break; - case '年': - default: - newStartTime = now.subtract(5, 'year').format('YYYY'); + + if (value === '月') { + newStartTime = now.subtract(6, 'month').format('YYYY-MM'); + } else if (value === '季度') { + newStartTime = now.subtract(6, 'quarter').format('YYYY-Q'); + } else { + newStartTime = now.subtract(5, 'year').format('YYYY'); } + setParams(p => ({ ...p, timeDimension: value, @@ -148,6 +90,7 @@ const IndustryTrendPage: React.FC = () => { selectedIndustry: '' })); }; + const handleDateRangeChange = (dates: any, dateStrings: [string, string]) => { if (dates && dates[0] && dates[1]) { setParams(p => ({ @@ -157,6 +100,7 @@ const IndustryTrendPage: React.FC = () => { })); } }; + const currentIndustryData = useMemo(() => { if (!params.selectedIndustry || allData.length === 0) return []; @@ -164,10 +108,19 @@ const IndustryTrendPage: React.FC = () => { .filter(item => item.category === params.selectedIndustry) .map(item => ({ ...item, - date: formatDateForDisplay(item.date, params.timeDimension) + date: formatDateForDisplay(item.date, params.timeDimension) })) - .sort((a, b) => dayjs(a.date).valueOf() - dayjs(b.date).valueOf()); + .sort((a, b) => { + const dateA = a.date.includes('第') + ? parseInt(a.date.split('第')[1]) + : dayjs(a.date).valueOf(); + const dateB = b.date.includes('第') + ? parseInt(b.date.split('第')[1]) + : dayjs(b.date).valueOf(); + return dateA - dateB; + }); }, [allData, params.selectedIndustry, params.timeDimension]); + const getPickerValue = () => { try { return [ @@ -182,8 +135,7 @@ const IndustryTrendPage: React.FC = () => { } }; - // 图表配置 - const chartConfig = { + const chartConfig: ChartConfig = { data: currentIndustryData, height: 180, xField: 'date', @@ -192,7 +144,7 @@ const IndustryTrendPage: React.FC = () => { xAxis: { type: 'cat', label: { - formatter: (text: string) => text, + formatter: (text: string) => text, }, }, yAxis: { @@ -214,7 +166,6 @@ const IndustryTrendPage: React.FC = () => { smooth: true, }; - // 初始化加载数据 useEffect(() => { fetchData(); }, [params.timeDimension, params.startTime, params.endTime, params.type]); @@ -279,7 +230,7 @@ const IndustryTrendPage: React.FC = () => { {currentIndustryData.length > 0 ? ( @@ -296,25 +247,60 @@ const IndustryTrendPage: React.FC = () => { - - - + + - - + + - - + + - - + + @@ -324,4 +310,6 @@ const IndustryTrendPage: React.FC = () => { ); }; + + export default IndustryTrendPage; \ No newline at end of file diff --git a/src/pages/Analysis/Industrytrend/utils.ts b/src/pages/Analysis/Industrytrend/utils.ts new file mode 100644 index 0000000..47c1605 --- /dev/null +++ b/src/pages/Analysis/Industrytrend/utils.ts @@ -0,0 +1,80 @@ + +import { TimeDimension, QuarterFormat, DateFormatter, QuarterFormatter, ApiDataConverter, IndustryDataItem } from '@/types/analysis/industry'; + +export const formatQuarter: QuarterFormatter = (dateStr: string): QuarterFormat => { + if (dateStr.includes('第')) return dateStr as QuarterFormat; + + const [year, quarterPart] = dateStr.includes('-Q') + ? dateStr.split('-Q') + : dateStr.split('-'); + + const quarterNum = quarterPart.replace('季度', ''); + const quarterMap: Record = { + '1': '第一季度', + '2': '第二季度', + '3': '第三季度', + '4': '第四季度' + }; + return `${year}-${quarterMap[quarterNum] || quarterPart}` as QuarterFormat; +}; + +export const formatDateForDisplay: DateFormatter = (dateStr: string, dimension: TimeDimension): string => { + try { + if (dimension === '年') return dateStr.split('-')[0]; + if (dimension === '季度') { + if (dateStr.includes('第')) { + const [year, quarter] = dateStr.split('-'); + return `${year}年${quarter}`; + } + const [year, quarterNum] = dateStr.split('-'); + const quarterMap: Record = { + '1': '第一季度', + '2': '第二季度', + '3': '第三季度', + '4': '第四季度', + 'Q1': '第一季度', + 'Q2': '第二季度', + 'Q3': '第三季度', + 'Q4': '第四季度' + }; + return `${year}年${quarterMap[quarterNum] || quarterNum}`; + } + const [year, month] = dateStr.split('-'); + return `${year}年${month}月`; + } catch (e) { + console.error('日期格式化错误:', e); + return dateStr; + } +}; + +export const convertApiData: ApiDataConverter = (apiData: any) => { + if (!apiData) return []; + try { + if (Array.isArray(apiData)) { + return apiData.map(item => ({ + date: item.time || item.date, + category: item.name || item.category || '未知行业', + value: Number(item.data || item.value) || 0 + })); + } + if (typeof apiData === 'object') { + const result: IndustryDataItem[] = []; + Object.entries(apiData).forEach(([date, items]) => { + if (Array.isArray(items)) { + items.forEach((item: any) => { + result.push({ + date, + category: item.name || item.category || '未知行业', + value: Number(item.data || item.value) || 0 + }); + }); + } + }); + return result; + } + return []; + } catch (error) { + console.error('数据转换错误:', error); + return []; + } +}; \ No newline at end of file diff --git a/src/pages/Analysis/User/index.tsx b/src/pages/Analysis/User/index.tsx index e69de29..adddf9c 100644 --- a/src/pages/Analysis/User/index.tsx +++ b/src/pages/Analysis/User/index.tsx @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/types/analysis/industry.d.ts b/src/types/analysis/industry.d.ts index dcb266e..2a26174 100644 --- a/src/types/analysis/industry.d.ts +++ b/src/types/analysis/industry.d.ts @@ -1,20 +1,63 @@ -declare namespace API.Analysis { - export interface IndustryResult { - data: TrendItem[]; - code: number; - msg: string; - } - - export interface TrendItem { - date: string; - category: string; - value: number; - } - - export interface IndustryParams { - timeDimension: '月' | '季度' | '年'; - type: '岗位发布数量' | '招聘增长率'; - startTime: string; - endTime: string; - } - } \ No newline at end of file +import dayjs from 'dayjs'; + +export type TimeDimension = '月' | '季度' | '年'; +export type AnalysisType = '岗位发布数量' | '招聘增长率'; +export type QuarterFormat = `${number}-${'Q1'|'Q2'|'Q3'|'Q4'|'第一季度'|'第二季度'|'第三季度'|'第四季度'}`; + +export interface IndustryDataItem { + date: string; + category: string; + value: number; +} + +export interface IndustryTrendParams { + timeDimension: TimeDimension; + type: AnalysisType; + startTime: string; + endTime: string; +} + +export interface IndustryTrendState extends IndustryTrendParams { + selectedIndustry: string; +} + +export interface IndustryTrendApiResult { + data: IndustryDataItem[]; + code: number; + msg: string; +} + +export interface ChartConfig { + data: IndustryDataItem[]; + height: number; + xField: string; + yField: string; + seriesField: string; + xAxis: { + type: string; + label: { + formatter: (text: string) => string; + }; + }; + yAxis: { + label: { + formatter: (val: string) => string; + }; + }; + tooltip: false; + point: { + size: number; + shape: string; + }; + animation: { + appear: { + animation: string; + duration: number; + }; + }; + smooth: boolean; +} + +export type DateFormatter = (dateStr: string, dimension: TimeDimension) => string; +export type QuarterFormatter = (dateStr: string) => QuarterFormat; +export type ApiDataConverter = (apiData: any) => IndustryDataItem[]; \ No newline at end of file