flat: 暂存
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Card, Select, Spin, Empty, Row, Col } from 'antd';
|
import { Card, Empty, Select, Spin } from 'antd';
|
||||||
import { Line, Bar, Pie, Heatmap } from '@ant-design/charts';
|
import { Bar, Heatmap, Line, Pie } from '@ant-design/charts';
|
||||||
|
|
||||||
export const IndustryTrendCard = ({
|
export const IndustryTrendCard = ({
|
||||||
loading,
|
loading,
|
||||||
@@ -38,11 +38,7 @@ export const IndustryTrendCard = ({
|
|||||||
<Empty
|
<Empty
|
||||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||||
description={
|
description={
|
||||||
loading
|
loading ? '数据加载中...' : selectedIndustry ? '当前时间段无数据' : '请先选择行业'
|
||||||
? '数据加载中...'
|
|
||||||
: selectedIndustry
|
|
||||||
? '当前时间段无数据'
|
|
||||||
: '请先选择行业'
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -192,7 +188,6 @@ export const EducationCard = ({
|
|||||||
placeholder="选择学历要求"
|
placeholder="选择学历要求"
|
||||||
disabled={availableEducationLevels.length === 0}
|
disabled={availableEducationLevels.length === 0}
|
||||||
>
|
>
|
||||||
<Option value="">全部学历</Option>
|
|
||||||
{availableEducationLevels.map((level) => (
|
{availableEducationLevels.map((level) => (
|
||||||
<Option key={level} value={level}>
|
<Option key={level} value={level}>
|
||||||
{level}
|
{level}
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ export const getSalaryChartConfig = (currentSalaryData: any[]): ChartConfig => (
|
|||||||
|
|
||||||
export const getWorkYearPieConfig = (workYearData: any[], selectedWorkYearRange: string) => {
|
export const getWorkYearPieConfig = (workYearData: any[], selectedWorkYearRange: string) => {
|
||||||
const filteredData = workYearData
|
const filteredData = workYearData
|
||||||
.filter((item) => item.category === selectedWorkYearRange)
|
.filter((item) => selectedWorkYearRange === '全部' || item.category === selectedWorkYearRange)
|
||||||
.map((item) => ({
|
.map((item) => ({
|
||||||
...item,
|
...item,
|
||||||
date: formatDateForDisplay(item.date, '月'),
|
date: formatDateForDisplay(item.date, '月'),
|
||||||
@@ -394,7 +394,7 @@ export const getEducationBarConfig = (educationData: any[], selectedEducationLev
|
|||||||
}
|
}
|
||||||
|
|
||||||
const timeData = cleanEducationData
|
const timeData = cleanEducationData
|
||||||
.filter((item) => item.category === selectedEducationLevel)
|
.filter((item) => selectedEducationLevel === '全部' || item.category === selectedEducationLevel)
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
const dateA = dayjs(a.date, 'YYYY年MM月').valueOf();
|
const dateA = dayjs(a.date, 'YYYY年MM月').valueOf();
|
||||||
const dateB = dayjs(b.date, 'YYYY年MM月').valueOf();
|
const dateB = dayjs(b.date, 'YYYY年MM月').valueOf();
|
||||||
|
|||||||
@@ -1,43 +1,36 @@
|
|||||||
import React, { useEffect, useState, useMemo, useRef } from 'react';
|
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { Card, Select, Button, Space, Spin, Empty, Row, Col, message, DatePicker } from 'antd';
|
import { Button, Card, Col, DatePicker, message, Row, Select, Space } from 'antd';
|
||||||
import { Line, Bar, Pie, Heatmap } from '@ant-design/charts';
|
|
||||||
import {
|
import {
|
||||||
getIndustryTrend,
|
getEducationTrend,
|
||||||
getIndustryAreaTrend,
|
getIndustryAreaTrend,
|
||||||
|
getIndustryTrend,
|
||||||
getSalaryTrend,
|
getSalaryTrend,
|
||||||
getWorkYearTrend,
|
getWorkYearTrend,
|
||||||
getEducationTrend,
|
|
||||||
} from '@/services/analysis/industry';
|
} from '@/services/analysis/industry';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { useRequest } from '@umijs/max';
|
import { useRequest } from '@umijs/max';
|
||||||
|
import { IndustryDataItem, IndustryTrendState, TimeDimension } from '@/types/analysis/industry';
|
||||||
import {
|
import {
|
||||||
TimeDimension,
|
|
||||||
AnalysisType,
|
|
||||||
IndustryTrendState,
|
|
||||||
IndustryDataItem,
|
|
||||||
ChartConfig,
|
|
||||||
} from '@/types/analysis/industry';
|
|
||||||
import {
|
|
||||||
formatQuarter,
|
|
||||||
formatDateForDisplay,
|
|
||||||
convertApiData,
|
convertApiData,
|
||||||
|
convertEducationData,
|
||||||
convertSalaryData,
|
convertSalaryData,
|
||||||
convertWorkYearData,
|
convertWorkYearData,
|
||||||
convertEducationData,
|
formatDateForDisplay,
|
||||||
|
formatQuarter,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
import {
|
import {
|
||||||
|
getEducationBarConfig,
|
||||||
getHeatmapConfig,
|
getHeatmapConfig,
|
||||||
getIndustryChartConfig,
|
getIndustryChartConfig,
|
||||||
getSalaryChartConfig,
|
getSalaryChartConfig,
|
||||||
getWorkYearPieConfig,
|
getWorkYearPieConfig,
|
||||||
getEducationBarConfig,
|
|
||||||
} from './components/chartconfigs';
|
} from './components/chartconfigs';
|
||||||
import {
|
import {
|
||||||
IndustryTrendCard,
|
|
||||||
AreaAnalysisCard,
|
AreaAnalysisCard,
|
||||||
|
EducationCard,
|
||||||
|
IndustryTrendCard,
|
||||||
SalaryTrendCard,
|
SalaryTrendCard,
|
||||||
WorkYearCard,
|
WorkYearCard,
|
||||||
EducationCard,
|
|
||||||
} from './components/chartcards';
|
} from './components/chartcards';
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
@@ -186,7 +179,7 @@ const IndustryTrendPage: React.FC = () => {
|
|||||||
const extractNumber = (str: string) => parseInt(str.replace(/[^0-9]/g, '') || 0);
|
const extractNumber = (str: string) => parseInt(str.replace(/[^0-9]/g, '') || 0);
|
||||||
return extractNumber(a) - extractNumber(b);
|
return extractNumber(a) - extractNumber(b);
|
||||||
});
|
});
|
||||||
|
ranges.unshift('全部');
|
||||||
setAvailableSalaryRanges(ranges);
|
setAvailableSalaryRanges(ranges);
|
||||||
|
|
||||||
if (ranges.length > 0 && !ranges.includes(params.selectedSalaryRange)) {
|
if (ranges.length > 0 && !ranges.includes(params.selectedSalaryRange)) {
|
||||||
@@ -226,7 +219,7 @@ const IndustryTrendPage: React.FC = () => {
|
|||||||
const order = ['应届', '1-3年', '3-5年', '5年以上'];
|
const order = ['应届', '1-3年', '3-5年', '5年以上'];
|
||||||
return order.indexOf(a) - order.indexOf(b);
|
return order.indexOf(a) - order.indexOf(b);
|
||||||
});
|
});
|
||||||
|
ranges.unshift('全部');
|
||||||
setAvailableWorkYearRanges(ranges);
|
setAvailableWorkYearRanges(ranges);
|
||||||
if (ranges.length > 0 && !ranges.includes(params.selectedWorkYearRange)) {
|
if (ranges.length > 0 && !ranges.includes(params.selectedWorkYearRange)) {
|
||||||
setParams((p) => ({ ...p, selectedWorkYearRange: ranges[0] }));
|
setParams((p) => ({ ...p, selectedWorkYearRange: ranges[0] }));
|
||||||
@@ -265,7 +258,7 @@ const IndustryTrendPage: React.FC = () => {
|
|||||||
const order = ['不限', '初中及以下', '中专/中技', '大专', '本科', '硕士', '博士'];
|
const order = ['不限', '初中及以下', '中专/中技', '大专', '本科', '硕士', '博士'];
|
||||||
return order.indexOf(a) - order.indexOf(b);
|
return order.indexOf(a) - order.indexOf(b);
|
||||||
});
|
});
|
||||||
|
levels.unshift('全部');
|
||||||
setAvailableEducationLevels(levels);
|
setAvailableEducationLevels(levels);
|
||||||
if (levels.length > 0 && !levels.includes(selectedEducationLevel)) {
|
if (levels.length > 0 && !levels.includes(selectedEducationLevel)) {
|
||||||
setSelectedEducationLevel(levels[0]);
|
setSelectedEducationLevel(levels[0]);
|
||||||
@@ -326,9 +319,11 @@ const IndustryTrendPage: React.FC = () => {
|
|||||||
|
|
||||||
const currentSalaryData = useMemo(() => {
|
const currentSalaryData = useMemo(() => {
|
||||||
if (!params.selectedSalaryRange || salaryData.length === 0) return [];
|
if (!params.selectedSalaryRange || salaryData.length === 0) return [];
|
||||||
|
|
||||||
return salaryData
|
return salaryData
|
||||||
.filter((item) => item.category === params.selectedSalaryRange)
|
.filter(
|
||||||
|
(item) =>
|
||||||
|
params.selectedSalaryRange === '全部' || item.category === params.selectedSalaryRange,
|
||||||
|
)
|
||||||
.map((item) => ({
|
.map((item) => ({
|
||||||
...item,
|
...item,
|
||||||
originalDate: item.date,
|
originalDate: item.date,
|
||||||
@@ -537,7 +532,6 @@ const IndustryTrendPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
{/* 工作经验和学历要求 - 中等屏幕下分成两列 */}
|
{/* 工作经验和学历要求 - 中等屏幕下分成两列 */}
|
||||||
<Col xs={24} md={12} lg={12}>
|
<Col xs={24} md={12} lg={12}>
|
||||||
<WorkYearCard
|
<WorkYearCard
|
||||||
@@ -546,9 +540,9 @@ const IndustryTrendPage: React.FC = () => {
|
|||||||
config={workYearPieConfig}
|
config={workYearPieConfig}
|
||||||
availableWorkYearRanges={availableWorkYearRanges}
|
availableWorkYearRanges={availableWorkYearRanges}
|
||||||
selectedWorkYearRange={params.selectedWorkYearRange}
|
selectedWorkYearRange={params.selectedWorkYearRange}
|
||||||
onWorkYearRangeChange={(value) =>
|
onWorkYearRangeChange={(value) => {
|
||||||
setParams((p) => ({ ...p, selectedWorkYearRange: value }))
|
return setParams((p) => ({ ...p, selectedWorkYearRange: value }));
|
||||||
}
|
}}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} md={12} lg={12}>
|
<Col xs={24} md={12} lg={12}>
|
||||||
|
|||||||
197
src/pages/Analysis/User/components/AreaStatics.tsx
Normal file
197
src/pages/Analysis/User/components/AreaStatics.tsx
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
|
import { Table, Card, Select } from 'antd';
|
||||||
|
import { getUserStaticsByArea } from '@/services/analysis/user';
|
||||||
|
import { Pie, Column } from '@ant-design/charts';
|
||||||
|
export default function AreaStatics({ data }) {
|
||||||
|
const [selectedArea, setSelectedArea] = useState('');
|
||||||
|
const [byArea, setByArea] = useState({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (data.length > 0) {
|
||||||
|
setSelectedArea(data[0].dictValue);
|
||||||
|
fetchAreaStatics(data[0].dictValue);
|
||||||
|
}
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
const fetchAreaStatics = async (areaCode) => {
|
||||||
|
const resData = await getUserStaticsByArea(areaCode);
|
||||||
|
if (resData.code === 200) {
|
||||||
|
const data = {
|
||||||
|
gender: transformNum(resData.data.gender),
|
||||||
|
education: transformNum(resData.data.education),
|
||||||
|
position: transformNum(resData.data.position),
|
||||||
|
salary: transformNum(resData.data.salary),
|
||||||
|
age: transformNum(resData.data.age),
|
||||||
|
};
|
||||||
|
console.log(data.education);
|
||||||
|
setByArea(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const transformNum = useCallback((datas) => {
|
||||||
|
return datas.map((item) => ({ ...item, data: parseInt(item.data) }));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleAreaChange = (value) => {
|
||||||
|
setSelectedArea(value);
|
||||||
|
fetchAreaStatics(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const educationColumns = [
|
||||||
|
{ title: '学历', dataIndex: 'name', key: 'name' },
|
||||||
|
{ title: '数量', dataIndex: 'data', key: 'data' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const genderColumns = [
|
||||||
|
{ title: '性别', dataIndex: 'name', key: 'name' },
|
||||||
|
{ title: '数量', dataIndex: 'data', key: 'data' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const positionColumns = [
|
||||||
|
{ title: '求职岗位', dataIndex: 'name', key: 'name' },
|
||||||
|
{ title: '数量', dataIndex: 'data', key: 'data' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const salaryColumns = [
|
||||||
|
{ title: '期望薪资', dataIndex: 'name', key: 'name' },
|
||||||
|
{ title: '数量', dataIndex: 'data', key: 'data' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const ageColumns = [
|
||||||
|
{ title: '年龄阶段', dataIndex: 'name', key: 'name' },
|
||||||
|
{ title: '数量', dataIndex: 'data', key: 'data' },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ padding: '8px' }}>
|
||||||
|
<Card title="区域用户统计" style={{ marginBottom: '8px' }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
gap: '16px',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<Select
|
||||||
|
style={{ width: '200px', marginBottom: '8px' }}
|
||||||
|
placeholder="选择区域"
|
||||||
|
onChange={handleAreaChange}
|
||||||
|
value={selectedArea}
|
||||||
|
>
|
||||||
|
{data.map((item) => (
|
||||||
|
<Select.Option key={item.dictValue} value={item.dictValue}>
|
||||||
|
{item.dictLabel}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{byArea.gender && byArea.gender.length > 0 ? (
|
||||||
|
<div style={{ display: 'flex', flexWrap: 'nowrap', gap: '16px' }}>
|
||||||
|
<div style={{ display: 'flex', flexWrap: 'nowrap' }}>
|
||||||
|
<h4>用户数量: </h4> {byArea.gender.reduce((acc, cur) => acc + cur.data, 0)}
|
||||||
|
</div>
|
||||||
|
<div>男性: {byArea.gender.find((item) => item.name === '男')?.data || 0}</div>
|
||||||
|
<div>女性: {byArea.gender.find((item) => item.name === '女')?.data || 0}</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div>暂无数据</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '8px' }}>
|
||||||
|
<Card title="学历分布" style={{ marginBottom: '8px' }}>
|
||||||
|
<Column
|
||||||
|
data={byArea.education}
|
||||||
|
xField="name"
|
||||||
|
yField="data"
|
||||||
|
label={{
|
||||||
|
text: (d) => `${d.data}人`,
|
||||||
|
textBaseline: 'bottom',
|
||||||
|
}}
|
||||||
|
axis={{
|
||||||
|
y: {
|
||||||
|
label: {
|
||||||
|
formatter: (v) => `${v}k`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
radiusTopLeft: 3,
|
||||||
|
radiusTopRight: 3,
|
||||||
|
columnWidthRatio: 0.6,
|
||||||
|
inset: 0.5,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card
|
||||||
|
title="求职岗位排行"
|
||||||
|
style={{ marginBottom: '8px', height: '600px', overflowY: 'auto' }}
|
||||||
|
>
|
||||||
|
<Table
|
||||||
|
columns={positionColumns}
|
||||||
|
dataSource={byArea.position}
|
||||||
|
pagination={false}
|
||||||
|
rowKey="name"
|
||||||
|
size="small"
|
||||||
|
scroll={{ y: 440 }}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card title="期望薪资阶段" style={{ marginBottom: '8px' }}>
|
||||||
|
<Column
|
||||||
|
data={byArea.salary}
|
||||||
|
xField="name"
|
||||||
|
yField="data"
|
||||||
|
label={{
|
||||||
|
text: (d) => `${d.data}人`,
|
||||||
|
textBaseline: 'bottom',
|
||||||
|
}}
|
||||||
|
axis={{
|
||||||
|
y: {
|
||||||
|
label: {
|
||||||
|
formatter: (v) => `${v}k`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
radiusTopLeft: 3,
|
||||||
|
radiusTopRight: 3,
|
||||||
|
columnWidthRatio: 0.6,
|
||||||
|
inset: 0.5,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card title="年龄阶段分布">
|
||||||
|
<Column
|
||||||
|
data={byArea.age}
|
||||||
|
xField="name"
|
||||||
|
yField="data"
|
||||||
|
label={{
|
||||||
|
text: (d) => `${d.data}人`,
|
||||||
|
textBaseline: 'bottom',
|
||||||
|
}}
|
||||||
|
axis={{
|
||||||
|
y: {
|
||||||
|
label: {
|
||||||
|
formatter: (v) => `${v}k`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
radiusTopLeft: 5,
|
||||||
|
radiusTopRight: 5,
|
||||||
|
columnWidthRatio: 0.3,
|
||||||
|
inset: 0.5,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
98
src/pages/Analysis/User/components/KeywordChart.tsx
Normal file
98
src/pages/Analysis/User/components/KeywordChart.tsx
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
|
import { Column } from '@ant-design/charts';
|
||||||
|
import { getKeyWordSearchRank } from '@/services/analysis/user';
|
||||||
|
|
||||||
|
interface KeywordChartProps {
|
||||||
|
data?: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function KeywordChart({ data: initialData }: KeywordChartProps) {
|
||||||
|
const [data, setData] = useState(initialData || []);
|
||||||
|
|
||||||
|
const fetchData = useCallback(
|
||||||
|
async (params?: { searchDate?: string; searchCount?: number; searchType?: 1 | 2 | 3 }) => {
|
||||||
|
const resData = await getKeyWordSearchRank({
|
||||||
|
searchDate: params?.searchDate || '2025-11-03',
|
||||||
|
// searchCount: params?.searchCount || 4,
|
||||||
|
searchType: params?.searchType || 3,
|
||||||
|
});
|
||||||
|
if (resData.code === 200) {
|
||||||
|
setData(resData.rows);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchData();
|
||||||
|
}, [fetchData]);
|
||||||
|
const config = {
|
||||||
|
data,
|
||||||
|
xField: 'keyWord',
|
||||||
|
yField: 'searchCount',
|
||||||
|
height: 350,
|
||||||
|
label: {
|
||||||
|
position: 'middle',
|
||||||
|
style: {
|
||||||
|
fill: '#FFFFFF',
|
||||||
|
opacity: 0.6,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
label: {
|
||||||
|
autoHide: true,
|
||||||
|
autoRotate: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
keyWord: { alias: '关键词' },
|
||||||
|
searchCount: { alias: '搜索次数' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', marginBottom: 16 }}>
|
||||||
|
<h3 style={{ marginRight: 16 }}>查询关键词搜索排行</h3>
|
||||||
|
<select
|
||||||
|
onChange={(e) => {
|
||||||
|
fetchData({
|
||||||
|
searchDate: '2025-11-03',
|
||||||
|
searchCount: 10,
|
||||||
|
searchType: Number(e.target.value) as 1 | 2 | 3,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
defaultValue="3"
|
||||||
|
>
|
||||||
|
<option value="1">日</option>
|
||||||
|
<option value="2">周</option>
|
||||||
|
<option value="3">月</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: '400px',
|
||||||
|
overflowY: 'auto',
|
||||||
|
border: '1px solid #eee',
|
||||||
|
borderRadius: '4px',
|
||||||
|
padding: '8px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{data?.map((item, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
padding: '8px',
|
||||||
|
borderBottom: '1px solid #eee',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-around',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>关键词:{item.keyWord}</span>
|
||||||
|
<span>搜索次数:{item.searchCount}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
84
src/pages/Analysis/User/components/WantedPositionStatics.tsx
Normal file
84
src/pages/Analysis/User/components/WantedPositionStatics.tsx
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
|
import { getWantedPositionStaticsCount } from '@/services/analysis/user';
|
||||||
|
import { Card, Table } from 'antd';
|
||||||
|
|
||||||
|
interface PositionStatics {
|
||||||
|
jobName: string;
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface getWantedPositionStaticsCountStaticsResult {
|
||||||
|
male: PositionStatics[];
|
||||||
|
female: PositionStatics[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const WantedPositionStatics: React.FC = () => {
|
||||||
|
const [staticsData, setStaticsData] = useState<StaticsResult>({ male: [], female: [] });
|
||||||
|
|
||||||
|
const fetchStatics = useCallback(async () => {
|
||||||
|
const resData = await getWantedPositionStaticsCount({ sexCode: 0 });
|
||||||
|
if (resData.code === 200) {
|
||||||
|
const malePositions: Record<string, number> = {};
|
||||||
|
const femalePositions: Record<string, number> = {};
|
||||||
|
|
||||||
|
resData.data.forEach((item) => {
|
||||||
|
if (item.sexCode === '男') {
|
||||||
|
malePositions[item.jobName] = (malePositions[item.jobName] || 0) + 1;
|
||||||
|
} else if (item.sexCode === '女') {
|
||||||
|
femalePositions[item.jobName] = (femalePositions[item.jobName] || 0) + 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const result: StaticsResult = {
|
||||||
|
male: Object.entries(malePositions).map(([jobName, count]) => ({ jobName, count })),
|
||||||
|
female: Object.entries(femalePositions).map(([jobName, count]) => ({ jobName, count })),
|
||||||
|
};
|
||||||
|
|
||||||
|
setStaticsData(result);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchStatics();
|
||||||
|
}, [fetchStatics]);
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '岗位名称',
|
||||||
|
dataIndex: 'jobName',
|
||||||
|
key: 'jobName',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '数量',
|
||||||
|
dataIndex: 'count',
|
||||||
|
key: 'count',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ padding: '8px', display: 'flex', width: '100%', height: '100%' }}>
|
||||||
|
<Card title="男性用户期望岗位统计" style={{ width: '50%' }}>
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
dataSource={staticsData.male}
|
||||||
|
pagination={false}
|
||||||
|
rowKey="jobName"
|
||||||
|
size="small"
|
||||||
|
scroll={{ y: 440 }}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
<Card title="女性用户期望岗位统计" style={{ width: '50%' }}>
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
dataSource={staticsData.female}
|
||||||
|
pagination={false}
|
||||||
|
rowKey="jobName"
|
||||||
|
size="small"
|
||||||
|
scroll={{ y: 440 }}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WantedPositionStatics;
|
||||||
@@ -1 +1,60 @@
|
|||||||
<button>111111111111</button>
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
|
import {
|
||||||
|
getUserLoginCountStatics,
|
||||||
|
getWantedPositionStaticsCount,
|
||||||
|
getUserStaticsByArea,
|
||||||
|
getPercentOfResumeCompletionRate,
|
||||||
|
} from '@/services/analysis/user';
|
||||||
|
import KeywordChart from './components/KeywordChart';
|
||||||
|
import { getDictDataList } from '@/services/system/dictdata';
|
||||||
|
import AreaStatics from './components/AreaStatics';
|
||||||
|
import WantedPositionStatics from './components/WantedPositionStatics';
|
||||||
|
|
||||||
|
export default function userAnalysis() {
|
||||||
|
const [data, setData] = useState([]);
|
||||||
|
|
||||||
|
const [PofrcRate, setPofrcRate] = useState([]);
|
||||||
|
const [areaDict, setAreaDict] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// loginCounts();
|
||||||
|
dictDataList();
|
||||||
|
percentOfResumeCompletionRate();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const loginCounts = useCallback(async () => {
|
||||||
|
let resData = await getUserLoginCountStatics();
|
||||||
|
console.log(resData);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const dictDataList = useCallback(async () => {
|
||||||
|
let resData = await getDictDataList({ dictType: 'area' });
|
||||||
|
if (resData.code === 200) {
|
||||||
|
setAreaDict(resData.rows);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const percentOfResumeCompletionRate = useCallback(async () => {
|
||||||
|
let resData = await getPercentOfResumeCompletionRate();
|
||||||
|
if (resData.code === 200) {
|
||||||
|
console.log(resData.data);
|
||||||
|
setPofrcRate(resData.data);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ padding: '16px' }}>
|
||||||
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '16px' }}>
|
||||||
|
<div style={{ width: '100%' }}>
|
||||||
|
<KeywordChart />
|
||||||
|
</div>
|
||||||
|
<div style={{ width: '100%' }}>
|
||||||
|
<WantedPositionStatics />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style={{ width: '100%' }}>
|
||||||
|
<AreaStatics data={areaDict} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ const SubWayEdit: React.FC<ListFormProps> = (props) => {
|
|||||||
company: string;
|
company: string;
|
||||||
stationOrder: number;
|
stationOrder: number;
|
||||||
}>
|
}>
|
||||||
title={`${props.values ? '编辑' : '新增'}站点`}
|
title={`${props.values ? '编辑' : '新增'}商圈`}
|
||||||
form={form}
|
form={form}
|
||||||
// layout="inline"
|
// layout="inline"
|
||||||
autoFocusFirstInput
|
autoFocusFirstInput
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ const SubWayEdit: React.FC<ListFormProps> = (props) => {
|
|||||||
name: string;
|
name: string;
|
||||||
company: string;
|
company: string;
|
||||||
}>
|
}>
|
||||||
title={`${props.values ? '编辑' : '新增'}线路`}
|
title={`${props.values ? '编辑' : '新增'}区域`}
|
||||||
form={form}
|
form={form}
|
||||||
// layout="inline"
|
// layout="inline"
|
||||||
autoFocusFirstInput
|
autoFocusFirstInput
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React, { Fragment, useEffect, useRef, useState } from 'react';
|
import React, { Fragment, useEffect, useRef, useState } from 'react';
|
||||||
import { FormattedMessage, useAccess } from '@umijs/max';
|
import { FormattedMessage, useAccess } from '@umijs/max';
|
||||||
import { Button, FormInstance, message, Modal, Descriptions } from 'antd';
|
import { Button, Descriptions, FormInstance, message, Modal } from 'antd';
|
||||||
import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
|
import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
|
||||||
import { DeleteOutlined, FormOutlined, PlusOutlined, EyeOutlined, AlignLeftOutlined } from '@ant-design/icons';
|
import { AlignLeftOutlined, DeleteOutlined, FormOutlined, PlusOutlined } from '@ant-design/icons';
|
||||||
import EditCompanyListRow from './edit';
|
import EditCompanyListRow from './edit';
|
||||||
import {
|
import {
|
||||||
addCmsCompanyList,
|
addCmsCompanyList,
|
||||||
@@ -19,7 +19,7 @@ const CompanyDetailModal = ({
|
|||||||
visible,
|
visible,
|
||||||
onCancel,
|
onCancel,
|
||||||
record,
|
record,
|
||||||
scaleEnum
|
scaleEnum,
|
||||||
}: {
|
}: {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
@@ -107,12 +107,13 @@ function ManagementList() {
|
|||||||
valueType: 'text',
|
valueType: 'text',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
title: '公司行业',
|
// title: '公司行业',
|
||||||
dataIndex: 'industry',
|
// hideInSearch: true,
|
||||||
valueType: 'text',
|
// dataIndex: 'industry',
|
||||||
align: 'center',
|
// valueType: 'text',
|
||||||
},
|
// align: 'center',
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
title: '公司规模',
|
title: '公司规模',
|
||||||
dataIndex: 'scale',
|
dataIndex: 'scale',
|
||||||
|
|||||||
@@ -106,13 +106,13 @@ function ManagementList() {
|
|||||||
align: 'center',
|
align: 'center',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '最小薪资',
|
title: '最小薪资(元/月)',
|
||||||
dataIndex: 'minSalary',
|
dataIndex: 'minSalary',
|
||||||
valueType: 'text',
|
valueType: 'text',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '最大薪资',
|
title: '最大薪资(元/月)',
|
||||||
dataIndex: 'maxSalary',
|
dataIndex: 'maxSalary',
|
||||||
valueType: 'text',
|
valueType: 'text',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
|
|||||||
43
src/services/analysis/user.ts
Normal file
43
src/services/analysis/user.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { request } from '@umijs/max';
|
||||||
|
|
||||||
|
// 行业
|
||||||
|
export async function getUserLoginCountStatics(params?: API.Analysis.IndustryParams) {
|
||||||
|
return request<API.Analysis.User>('/api/cms/statics/getUserLoginCountStatics', {
|
||||||
|
method: 'GET',
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 查询关键词搜索排行
|
||||||
|
export async function getKeyWordSearchRank(params?: {
|
||||||
|
searchDate?: string;
|
||||||
|
searchCount?: number;
|
||||||
|
searchType?: 1 | 2 | 3;
|
||||||
|
}) {
|
||||||
|
return request<API.Analysis.User>('/api/cms/statics/getKeyWordSearchRank', {
|
||||||
|
method: 'GET',
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 统计男性和女性用户偏好的期望岗位
|
||||||
|
export async function getWantedPositionStaticsCount(params?: API.Analysis.IndustryParams) {
|
||||||
|
return request<API.Analysis.User>('/api/cms/statics/getWantedPositionStaticsCount', {
|
||||||
|
method: 'GET',
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 获取区域内的用户类别统计
|
||||||
|
export async function getUserStaticsByArea(area: string) {
|
||||||
|
return request<API.Analysis.User>(`/api/cms/statics/getUserStaticsByArea/${area}`, {
|
||||||
|
method: 'GET',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 获取区域内的用户类别统计
|
||||||
|
export async function getPercentOfResumeCompletionRate(params?: API.Analysis.IndustryParams) {
|
||||||
|
return request<API.Analysis.User>(`/api/app/user/getPercentOfResumeCompletionRate`, {
|
||||||
|
method: 'GET',
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user