From 1ea8f6b609dde99ced38877c6c8164b6f3b11b87 Mon Sep 17 00:00:00 2001
From: bin <719488417@qq.com>
Date: Mon, 1 Dec 2025 17:35:58 +0800
Subject: [PATCH] =?UTF-8?q?=E5=B2=97=E4=BD=8D=E5=88=86=E6=9E=90/=20?=
=?UTF-8?q?=E7=94=A8=E6=88=B7=E5=88=86=E6=9E=90=20style=20=E7=94=A8?=
=?UTF-8?q?=E6=88=B7=E7=AE=A1=E7=90=86=E6=9F=A5=E7=9C=8B=E7=AE=80=E5=8E=86?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Industrytrend/components/chartcards.tsx | 144 ++++--
.../Industrytrend/components/chartconfigs.tsx | 385 ++++++--------
src/pages/Analysis/Industrytrend/index.tsx | 39 +-
.../Analysis/User/components/AreaStatics.tsx | 418 +++++++++------
.../Analysis/User/components/KeywordChart.tsx | 285 ++++++++---
.../User/components/WantedPositionStatics.tsx | 475 +++++++++++++++---
src/pages/Analysis/User/index.tsx | 19 +-
src/pages/Mobileusers/List/index.tsx | 195 ++++---
src/pages/Mobileusers/components/detail.tsx | 402 +++++++++++++++
9 files changed, 1711 insertions(+), 651 deletions(-)
create mode 100644 src/pages/Mobileusers/components/detail.tsx
diff --git a/src/pages/Analysis/Industrytrend/components/chartcards.tsx b/src/pages/Analysis/Industrytrend/components/chartcards.tsx
index 2cf3f32..9771c73 100644
--- a/src/pages/Analysis/Industrytrend/components/chartcards.tsx
+++ b/src/pages/Analysis/Industrytrend/components/chartcards.tsx
@@ -1,7 +1,9 @@
import React from 'react';
-import { Card, Empty, Select, Spin } from 'antd';
+import { Card, Empty, Select, Spin, Tag } from 'antd';
import { Bar, Heatmap, Line, Pie } from '@ant-design/charts';
+const { Option } = Select;
+
export const IndustryTrendCard = ({
loading,
currentIndustryData,
@@ -11,17 +13,33 @@ export const IndustryTrendCard = ({
onIndustryChange,
}) => (
+ 📊 行业趋势分析
+
+ 趋势图
+
+
+ }
+ style={{
+ borderRadius: 12,
+ boxShadow: '0 4px 12px rgba(0,0,0,0.1)'
+ }}
+ bodyStyle={{
+ padding: '16px',
+ height: 280,
+ background: 'linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%)',
+ borderRadius: 8
+ }}
extra={
}
>
-
+
{currentIndustryData.length > 0 ? (
) : (
@@ -40,6 +58,7 @@ export const IndustryTrendCard = ({
description={
loading ? '数据加载中...' : selectedIndustry ? '当前时间段无数据' : '请先选择行业'
}
+ style={{ margin: '40px 0' }}
/>
)}
@@ -48,23 +67,36 @@ export const IndustryTrendCard = ({
export const AreaAnalysisCard = ({ loading, areaData, config }) => (
+ 🌍 区域分析
+ 热力图
+
+ }
+ style={{
+ borderRadius: 12,
+ boxShadow: '0 4px 12px rgba(0,0,0,0.1)'
+ }}
bodyStyle={{
- padding: 12,
- height: 250,
+ padding: 16,
+ height: 280,
+ background: 'linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%)',
+ borderRadius: 8,
position: 'relative',
}}
>
- {loading ? (
-
- ) : areaData.length > 0 ? (
-
-
-
- ) : (
-
- )}
+
+ {areaData.length > 0 ? (
+
+
+
+ ) : (
+
+ )}
+
);
@@ -77,9 +109,22 @@ export const SalaryTrendCard = ({
onSalaryRangeChange,
}) => (
+ 💰 薪资区间趋势分析
+ 趋势图
+
+ }
+ style={{
+ borderRadius: 12,
+ boxShadow: '0 4px 12px rgba(0,0,0,0.1)'
+ }}
+ bodyStyle={{
+ padding: '16px',
+ height: 280,
+ background: 'linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%)',
+ borderRadius: 8
+ }}
extra={
}
>
-
+
{currentSalaryData.length > 0 ? (
) : (
@@ -110,6 +156,7 @@ export const SalaryTrendCard = ({
? '当前时间段无数据'
: '请先选择薪资区间'
}
+ style={{ margin: '40px 0' }}
/>
)}
@@ -125,11 +172,21 @@ export const WorkYearCard = ({
onWorkYearRangeChange,
}) => (
+ ⏳ 工作经验要求分布
+ 饼图
+
+ }
+ style={{
+ borderRadius: 12,
+ boxShadow: '0 4px 12px rgba(0,0,0,0.1)'
+ }}
bodyStyle={{
- padding: '12px',
- height: 250,
+ padding: '16px',
+ height: 280,
+ background: 'linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%)',
+ borderRadius: 8,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
@@ -139,10 +196,11 @@ export const WorkYearCard = ({
}
>
-
+
{workYearData && workYearData.length > 0 ? (
@@ -161,6 +219,7 @@ export const WorkYearCard = ({
)}
@@ -176,17 +235,31 @@ export const EducationCard = ({
onEducationLevelChange,
}) => (
+ 🎓 学历要求分布
+ 柱状图
+
+ }
+ style={{
+ borderRadius: 12,
+ boxShadow: '0 4px 12px rgba(0,0,0,0.1)'
+ }}
+ bodyStyle={{
+ padding: '16px',
+ height: 280,
+ background: 'linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%)',
+ borderRadius: 8
+ }}
extra={
}
>
-
+
{educationData.length > 0 ? (
) : (
)}
-);
+);
\ No newline at end of file
diff --git a/src/pages/Analysis/Industrytrend/components/chartconfigs.tsx b/src/pages/Analysis/Industrytrend/components/chartconfigs.tsx
index 75f9dc7..cd1de64 100644
--- a/src/pages/Analysis/Industrytrend/components/chartconfigs.tsx
+++ b/src/pages/Analysis/Industrytrend/components/chartconfigs.tsx
@@ -2,6 +2,20 @@ import { ChartConfig } from '@/types/analysis/industry';
import dayjs from 'dayjs';
import { formatDateForDisplay } from '../utils';
+// 统一的颜色方案
+const COLOR_PALETTE = [
+ '#1890ff',
+ '#36cfc9',
+ '#faad14',
+ '#f5222d',
+ '#722ed1',
+ '#fa8c16',
+ '#52c41a',
+ '#eb2f96',
+ '#13c2c2',
+ '#eb2f96',
+];
+
export const getHeatmapConfig = (areaData: any[], timeDimension: string) => {
const sortedData = [...areaData].sort((a, b) => {
if (timeDimension === '年') {
@@ -19,17 +33,20 @@ export const getHeatmapConfig = (areaData: any[], timeDimension: string) => {
return {
data: sortedData,
- height: 240,
+ height: 280,
autoFit: true,
xField: 'name',
yField: 'time',
- colorField: 'value',
- shapeField: 'square',
- sizeField: 'value',
+ colorField: 'value',
+ shapeField: 'square',
+ sizeField: 'value',
xAxis: {
title: {
text: '区域',
- style: { fontSize: 12 },
+ style: {
+ fontSize: 12,
+ fill: '#666',
+ },
},
label: {
style: {
@@ -44,7 +61,10 @@ export const getHeatmapConfig = (areaData: any[], timeDimension: string) => {
yAxis: {
title: {
text: '时间',
- style: { fontSize: 12 },
+ style: {
+ fontSize: 12,
+ fill: '#666',
+ },
},
label: {
formatter: (text: string) => {
@@ -59,39 +79,56 @@ export const getHeatmapConfig = (areaData: any[], timeDimension: string) => {
},
},
label: {
- text: (d: { value: number }) => d.value.toString(),
+ text: (d: { value: number }) => (d.value > 0 ? d.value.toString() : ''),
position: 'inside',
style: {
fill: '#fff',
- pointerEvents: 'none',
+ fontSize: 10,
+ fontWeight: 'bold',
},
},
+ // 关键配置:值越大,颜色越深;值越大,方块越大
scale: {
- size: { range: [14, 14] },
- color: { range: ['#dddddd', '#9ec8e0', '#5fa4cd', '#2e7ab6', '#114d90'] },
+ size: {
+ range: [12, 20], // 小值对应小方块,大值对应大方块
+ },
+ color: {
+ range: [
+ '#b7c2c3ff', // 浅色,对应小值
+ '#afd4fbff',
+ '#8ecaf0ff',
+ '#40a9ff',
+ '#1890ff',
+ '#0050b3', // 深色,对应大值
+ ],
+ },
},
tooltip: {
- title: (d: { name: any; time: any }) => `${d.name} - ${d.time}`,
- field: 'value',
- valueFormatter: (v: number) => v.toString(),
- domStyles: {
- 'g2-tooltip': {
- padding: '8px 12px',
- borderRadius: '4px',
- },
- },
+ title: '区域岗位分布',
+ items: [
+ { field: 'name', name: '区域' },
+ { field: 'time', name: '时间' },
+ { field: 'value', name: '岗位数量', valueFormatter: (v) => `${v} 个` },
+ ],
},
interactions: [{ type: 'element-active' }],
responsive: true,
+ animation: {
+ appear: {
+ animation: 'scale-in',
+ duration: 1000,
+ },
+ },
};
};
export const getIndustryChartConfig = (currentIndustryData: any[], type: string): ChartConfig => ({
data: currentIndustryData,
- height: 200,
+ height: 280,
xField: 'date',
yField: 'value',
seriesField: 'category',
+ color: COLOR_PALETTE,
xAxis: {
type: 'cat',
label: {
@@ -107,66 +144,37 @@ export const getIndustryChartConfig = (currentIndustryData: any[], type: string)
size: 4,
shape: 'circle',
},
+ line: {
+ size: 3,
+ style: {
+ lineCap: 'round',
+ },
+ },
animation: {
appear: {
animation: 'path-in',
- duration: 1000,
+ duration: 1500,
},
},
smooth: true,
- interactions: [
- {
- type: 'tooltip',
- cfg: {
- render: (e, { title, items }) => {
- const list = items.filter((item) => item.value);
- return (
-
-
{title}
- {list.map((item, index) => {
- const { name, value, color } = item;
- return (
-
-
-
- {name}
-
-
- {value}
- {type === '招聘增长率' ? '%' : ''}
-
-
- );
- })}
-
- );
- },
- },
- },
- ],
legend: false,
tooltip: {
- showTitle: undefined,
- title: undefined,
- customContent: undefined,
+ title: '行业趋势',
+ items: [
+ { field: 'category', name: '行业' },
+ { field: 'date', name: '时间' },
+ {
+ field: 'value',
+ name: type === '招聘增长率' ? '增长率' : '岗位数量',
+ valueFormatter: (v) => `${v}${type === '招聘增长率' ? '%' : ''}`,
+ },
+ ],
},
});
export const getSalaryChartConfig = (currentSalaryData: any[]): ChartConfig => ({
data: currentSalaryData,
- height: 240,
+ height: 280,
xField: 'date',
yField: 'value',
seriesField: 'category',
@@ -252,26 +260,41 @@ export const getWorkYearPieConfig = (workYearData: any[], selectedWorkYearRange:
return {
data: filteredData,
angleField: 'value',
- colorField: 'type',
+ colorField: 'category',
radius: 0.2,
innerRadius: 0.5,
label: {
text: (d: { type: any; value: any }) => `${d.type}\n ${d.value}`,
position: 'spider',
+ transform: [
+ {
+ type: 'overlapDodgeY',
+ },
+ ],
},
legend: false,
tooltip: {
- showTitle: true,
title: '工作经验分布',
- fields: ['type', 'value'],
- formatter: (datum: { type: any; value: any }) => ({
- name: datum.type,
- value: datum.value,
- }),
+ items: [
+ { field: 'date', name: '时间' },
+ { field: 'category', name: '工作经验' },
+ {
+ field: 'value',
+ name: '岗位数量',
+ valueFormatter: (v) => `${v} 个`,
+ },
+ ],
},
interactions: [{ type: 'element-active' }],
- padding: 'auto',
+ padding: [20, 0, 40, 0],
autoFit: true,
+ color: COLOR_PALETTE,
+ animation: {
+ appear: {
+ animation: 'wave-in',
+ duration: 1000,
+ },
+ },
};
};
@@ -285,172 +308,86 @@ export const getEducationBarConfig = (educationData: any[], selectedEducationLev
'本科',
'硕士',
'博士',
- 'MBA/EMBA',
- '留学-学士',
- '留学-硕士',
- '留学-博士',
];
- const educationColorMap: Record = {
- 不限: '#8884d8',
- 初中及以下: '#82ca9d',
- '中专/中技': '#ffc658',
- 高中: '#ff8042',
- 大专: '#0088FE',
- 本科: '#00C49F',
- 硕士: '#FFBB28',
- 博士: '#FF8042',
- 'MBA/EMBA': '#8884d8',
- '留学-学士': '#82ca9d',
- '留学-硕士': '#ffc658',
- '留学-博士': '#ff8042',
- };
+ // 汇总数据
+ const summaryData = educationData.reduce((acc, item) => {
+ const existing = acc.find((d: any) => d.name === item.category);
+ if (existing) {
+ existing.value += item.value;
+ } else {
+ acc.push({
+ name: item.category,
+ value: item.value,
+ });
+ }
+ return acc;
+ }, [] as any[]);
- const cleanEducationData = educationData
- .filter((item) => item && item.category && item.date && !isNaN(item.value))
- .map((item) => ({
- ...item,
- date: formatDateForDisplay(item.date, '月'),
- category: item.category || '不限',
- value: Number(item.value) || 0,
- }));
-
- if (!selectedEducationLevel) {
- const educationSummary: Record = {};
-
- cleanEducationData.forEach((item) => {
- if (!educationSummary[item.category]) {
- educationSummary[item.category] = 0;
- }
- educationSummary[item.category] += item.value;
- });
-
- const barData = Object.entries(educationSummary)
- .filter(([_, value]) => value > 0)
- .map(([name, value]) => ({
- name,
- value,
- color: educationColorMap[name] || '#999',
- }))
- .sort((a, b) => educationLevelOrder.indexOf(a.name) - educationLevelOrder.indexOf(b.name));
-
- return {
- data: barData,
- height: 200,
- xField: 'value',
- yField: 'name',
- seriesField: 'name',
- color: ({ name }: { name: string }) => educationColorMap[name] || '#999',
- meta: {
- name: { alias: '学历要求' },
- value: { alias: '岗位数量' },
- },
- xAxis: {
- label: {
- formatter: (val: string) => `${val}`,
- },
- grid: {
- line: {
- style: {
- stroke: '#f0f0f0',
- lineDash: [4, 4],
- },
- },
- },
- },
- yAxis: {
- label: {
- formatter: (text: string) => text,
- },
- },
- barStyle: {
- radius: [2, 2, 0, 0],
- },
- tooltip: {
- showTitle: true,
- title: '学历要求分布',
- fields: ['name', 'value'],
- formatter: (datum: { name: any; value: any }) => ({
- name: datum.name,
- value: datum.value,
- }),
- domStyles: {
- 'g2-tooltip': {
- background: 'rgba(255, 255, 255, 0.9)',
- boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)',
- borderRadius: '4px',
- },
- },
- },
- interactions: [{ type: 'element-active' }],
- legend: false,
- animation: {
- appear: {
- animation: 'scale-in-y',
- duration: 1000,
- },
- },
- };
- }
-
- const timeData = cleanEducationData
- .filter((item) => selectedEducationLevel === '全部' || item.category === selectedEducationLevel)
- .sort((a, b) => {
- const dateA = dayjs(a.date, 'YYYY年MM月').valueOf();
- const dateB = dayjs(b.date, 'YYYY年MM月').valueOf();
- return dateA - dateB;
- });
+ const barData = summaryData
+ .filter((item) => item.value > 0)
+ .sort((a, b) => educationLevelOrder.indexOf(a.name) - educationLevelOrder.indexOf(b.name));
return {
- data: timeData,
- height: 200,
- xField: 'date',
- yField: 'value',
- seriesField: 'category',
- color: educationColorMap[selectedEducationLevel] || '#999',
+ data: barData,
+ height: 280,
+ xField: 'value',
+ yField: 'name',
+ seriesField: 'name',
+ color: COLOR_PALETTE,
meta: {
- date: {
- alias: '时间',
- type: 'cat',
- values: timeData
- .map((item) => item.date)
- .sort((a, b) => {
- const dateA = dayjs(a, 'YYYY年MM月').valueOf();
- const dateB = dayjs(b, 'YYYY年MM月').valueOf();
- return dateA - dateB;
- }),
+ name: {
+ alias: '学历要求',
+ },
+ value: {
+ alias: '岗位数量',
},
- value: { alias: '岗位数量' },
},
- barStyle: {
- radius: [2, 2, 0, 0],
- },
- tooltip: {
- showTitle: true,
- title: `${selectedEducationLevel}趋势`,
- fields: ['date', 'value'],
- formatter: (datum: { date: any; value: any }) => ({ name: datum.date, value: datum.value }),
- domStyles: {
- 'g2-tooltip': {
- background: 'rgba(255, 255, 255, 0.9)',
- boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)',
- borderRadius: '4px',
+ xAxis: {
+ label: {
+ style: {
+ fill: '#666',
+ fontSize: 11,
},
},
+ grid: {
+ line: {
+ style: {
+ stroke: '#f0f0f0',
+ lineDash: [3, 3],
+ },
+ },
+ },
+ },
+ yAxis: {
+ label: {
+ style: {
+ fill: '#666',
+ fontSize: 11,
+ },
+ },
+ },
+ barStyle: {
+ radius: [4, 4, 0, 0],
+ },
+ tooltip: {
+ title: '学历要求分布',
+ items: [
+ { field: 'name', name: '学历要求' },
+ {
+ field: 'value',
+ name: '岗位数量',
+ valueFormatter: (v) => `${v} 个`,
+ },
+ ],
},
interactions: [{ type: 'element-active' }],
legend: false,
animation: {
appear: {
- animation: 'scale-in-y',
+ animation: 'scale-in-x',
duration: 1000,
},
},
- xAxis: {
- type: 'cat',
- label: {
- formatter: (text: string) => text,
- },
- },
};
};
diff --git a/src/pages/Analysis/Industrytrend/index.tsx b/src/pages/Analysis/Industrytrend/index.tsx
index e61e37c..a76af4f 100644
--- a/src/pages/Analysis/Industrytrend/index.tsx
+++ b/src/pages/Analysis/Industrytrend/index.tsx
@@ -64,7 +64,8 @@ const IndustryTrendPage: React.FC = () => {
const [params, setParams] = useState({
timeDimension: '月',
type: '岗位发布数量',
- startTime: dayjs().subtract(5, 'month').format('YYYY-MM'),
+ // startTime: dayjs().subtract(5, 'month').format('YYYY-MM'),
+ startTime:dayjs().month(0).format('YYYY-MM'),
endTime: dayjs().format('YYYY-MM'),
selectedIndustry: '',
selectedSalaryRange: '',
@@ -374,7 +375,8 @@ const IndustryTrendPage: React.FC = () => {
let newStartTime = '';
if (value === '月') {
- newStartTime = now.subtract(5, 'month').format('YYYY-MM');
+ // newStartTime = now.subtract(5, 'month').format('YYYY-MM');
+ newStartTime = dayjs().month(0).format('YYYY-MM');
} else if (value === '季度') {
newStartTime = now.subtract(6, 'quarter').format('YYYY-Q');
} else {
@@ -451,14 +453,14 @@ const IndustryTrendPage: React.FC = () => {
}, [params.timeDimension, params.startTime, params.endTime, params.type]);
return (
-
-
-
+
+
-
+
{/* 行业趋势图表 - 全宽显示 */}
{
/>
- {/* 区域分析和薪资趋势 - 中等屏幕下分成两列 */}
+ {/* 区域分析和薪资趋势 */}
-
+
{
}
/>
- {/* 工作经验和学历要求 - 中等屏幕下分成两列 */}
+
+ {/* 工作经验和学历要求 */}
{
/>
-
+
);
};
diff --git a/src/pages/Analysis/User/components/AreaStatics.tsx b/src/pages/Analysis/User/components/AreaStatics.tsx
index 91172ab..752f0f1 100644
--- a/src/pages/Analysis/User/components/AreaStatics.tsx
+++ b/src/pages/Analysis/User/components/AreaStatics.tsx
@@ -1,10 +1,14 @@
import React, { useState, useEffect, useCallback } from 'react';
-import { Table, Card, Select } from 'antd';
+import { Card, Select, Row, Col, Statistic, Tag, Empty, Spin } from 'antd';
import { getUserStaticsByArea } from '@/services/analysis/user';
-import { Pie, Column } from '@ant-design/charts';
+import { Column, Pie } from '@ant-design/charts';
+
+const { Option } = Select;
+
export default function AreaStatics({ data }) {
const [selectedArea, setSelectedArea] = useState('');
const [byArea, setByArea] = useState({});
+ const [loading, setLoading] = useState(false);
useEffect(() => {
if (data.length > 0) {
@@ -14,22 +18,28 @@ export default function AreaStatics({ data }) {
}, [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);
+ setLoading(true);
+ try {
+ const resData = await getUserStaticsByArea(areaCode);
+ if (resData.code === 200) {
+ const processedData = {
+ 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),
+ };
+ setByArea(processedData);
+ }
+ } catch (error) {
+ console.error('获取区域数据失败:', error);
+ } finally {
+ setLoading(false);
}
};
const transformNum = useCallback((datas) => {
- return datas.map((item) => ({ ...item, data: parseInt(item.data) }));
+ return (datas || []).map((item) => ({ ...item, data: parseInt(item.data) || 0 }));
}, []);
const handleAreaChange = (value) => {
@@ -37,161 +47,249 @@ export default function AreaStatics({ data }) {
fetchAreaStatics(value);
};
- const educationColumns = [
- { title: '学历', dataIndex: 'name', key: 'name' },
- { title: '数量', dataIndex: 'data', key: 'data' },
- ];
+ // 统一的柱状图配置
+ const getColumnConfig = (data: any[], color: string, title: string) => ({
+ data: data || [],
+ xField: 'name',
+ yField: 'data',
+ height: 220,
+ autoFit: true,
+ style:{
+ columnWidthRatio: 0.4, // 统一柱状图宽度比例
+ },
+ color: color,
+ xAxis: {
+ label: {
+ style: {
+ fontSize: 12,
+ fill: '#666',
+ },
+ formatter: (text: string) => {
+ return text.length > 4 ? `${text.substring(0, 4)}...` : text;
+ },
+ },
+ grid: {
+ line: {
+ style: {
+ stroke: '#e8e8e8',
+ lineWidth: 1,
+ lineDash: [3, 3],
+ },
+ },
+ alignTick: true,
+ },
+ },
+ yAxis: {
+ label: {
+ style: {
+ fontSize: 12,
+ fill: '#666',
+ },
+ },
+ grid: {
+ line: {
+ style: {
+ stroke: '#e8e8e8',
+ lineWidth: 1,
+ lineDash: [3, 3],
+ },
+ },
+ alignTick: true,
+ },
+ },
+ tooltip: {
+ title: title,
+ items: [
+ { field: 'name', name: '分类' },
+ { field: 'data', name: '人数', valueFormatter: (v) => `${v} 人` },
+ ],
+ },
+ interactions: [{ type: 'element-active' }],
+ animation: {
+ appear: {
+ animation: 'scale-in-y',
+ duration: 1000,
+ },
+ },
+ });
- const genderColumns = [
- { title: '性别', dataIndex: 'name', key: 'name' },
- { title: '数量', dataIndex: 'data', key: 'data' },
- ];
+ // 教育程度柱状图配置
+ const educationConfig = getColumnConfig(byArea.education, '#36cfc9', '学历分布');
- const positionColumns = [
- { title: '求职岗位', dataIndex: 'name', key: 'name' },
- { title: '数量', dataIndex: 'data', key: 'data' },
- ];
+ // 期望薪资柱状图配置
+ const salaryConfig = getColumnConfig(byArea.salary, '#faad14', '期望薪资分布');
- const salaryColumns = [
- { title: '期望薪资', dataIndex: 'name', key: 'name' },
- { title: '数量', dataIndex: 'data', key: 'data' },
- ];
+ // 年龄分布柱状图配置
+ const ageConfig = getColumnConfig(byArea.age, '#722ed1', '年龄分布');
- const ageColumns = [
- { title: '年龄阶段', dataIndex: 'name', key: 'name' },
- { title: '数量', dataIndex: 'data', key: 'data' },
- ];
+ // 性别饼图配置
+ const genderConfig = {
+ data: byArea.gender || [],
+ angleField: 'data',
+ colorField: 'name',
+ radius: 0.6,
+ height: 220,
+ label: {
+ text: (d) => `${d.name}\n${d.data}人`,
+ position: 'spider',
+ style: {
+ fontSize: 12,
+ textAlign: 'center',
+ },
+ transform: [
+ {
+ type: 'overlapDodgeY',
+ },
+ ],
+ },
+ tooltip: {
+ title: '性别分布',
+ items: [
+ { field: 'name', name: '性别' },
+ { field: 'data', name: '人数', valueFormatter: (v) => `${v} 人` },
+ ],
+ },
+ interactions: [{ type: 'element-active' }],
+ animation: {
+ appear: {
+ animation: 'wave-in',
+ duration: 1000,
+ },
+ },
+ };
+
+ // 计算用户总数
+ const totalUsers = byArea.gender ? byArea.gender.reduce((acc, cur) => acc + cur.data, 0) : 0;
return (
-
-
-
-
-
- {data.map((item) => (
-
- {item.dictLabel}
-
- ))}
-
-
-
- {byArea.gender && byArea.gender.length > 0 ? (
-
-
-
用户数量:
{byArea.gender.reduce((acc, cur) => acc + cur.data, 0)}
+
+ 🌍 区域用户统计
+ 区域分析
+
+ }
+ style={{
+ borderRadius: 12,
+ boxShadow: '0 4px 12px rgba(0,0,0,0.1)'
+ }}
+ bodyStyle={{
+ padding: '16px',
+ background: 'linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%)',
+ borderRadius: 8
+ }}
+ >
+
+
+
+
+
+ {data.map((item) => (
+
+ ))}
+
+
+
+ {byArea.gender && byArea.gender.length > 0 ? (
+
+
+
+
+
+
+
+
+ item.name === '男')?.data || 0}
+ suffix="人"
+ />
+
+
+
+
+ item.name === '女')?.data || 0}
+ suffix="人"
+ />
+
+
+
+ ) : (
+
+ 暂无用户数据
- 男性: {byArea.gender.find((item) => item.name === '男')?.data || 0}
- 女性: {byArea.gender.find((item) => item.name === '女')?.data || 0}
-
- ) : (
- 暂无数据
- )}
-
+ )}
+
+
-
-
- `${d.data}人`,
- textBaseline: 'bottom',
- }}
- axis={{
- y: {
- label: {
- formatter: (v) => `${v}k`,
- },
- },
- }}
- style={{
- radiusTopLeft: 3,
- radiusTopRight: 3,
- columnWidthRatio: 0.6,
- inset: 0.5,
- }}
- />
-
-
-
-
+ {byArea.education && byArea.education.length > 0 ? (
+
+ {/* 学历分布 */}
+
+
+
+
+
-
- `${d.data}人`,
- textBaseline: 'bottom',
- }}
- axis={{
- y: {
- label: {
- formatter: (v) => `${v}k`,
- },
- },
- }}
- style={{
- radiusTopLeft: 3,
- radiusTopRight: 3,
- columnWidthRatio: 0.6,
- inset: 0.5,
- }}
- />
-
+ {/* 性别分布 */}
+
+
+
+
+
-
- `${d.data}人`,
- textBaseline: 'bottom',
- }}
- axis={{
- y: {
- label: {
- formatter: (v) => `${v}k`,
- },
- },
- }}
- style={{
- radiusTopLeft: 5,
- radiusTopRight: 5,
- columnWidthRatio: 0.3,
- inset: 0.5,
- }}
- />
-
-
-
-
+ {/* 年龄分布 */}
+
+
+
+
+
+
+ {/* 期望薪资 */}
+
+
+
+
+
+
+ ) : (
+
+ )}
+
+
);
-}
+}
\ No newline at end of file
diff --git a/src/pages/Analysis/User/components/KeywordChart.tsx b/src/pages/Analysis/User/components/KeywordChart.tsx
index b3b743a..e67c165 100644
--- a/src/pages/Analysis/User/components/KeywordChart.tsx
+++ b/src/pages/Analysis/User/components/KeywordChart.tsx
@@ -1,98 +1,239 @@
import React, { useCallback, useEffect, useState } from 'react';
-import { Column } from '@ant-design/charts';
+import { Column, WordCloud } from '@ant-design/charts';
import { getKeyWordSearchRank } from '@/services/analysis/user';
+import { Card, Select, Empty, Spin, Tag, Row, Col, Statistic } from 'antd';
-interface KeywordChartProps {
- data?: any[];
+const { Option } = Select;
+
+interface KeywordData {
+ keyWord: string;
+ searchCount: number;
+ rank?: number;
}
-export default function KeywordChart({ data: initialData }: KeywordChartProps) {
- const [data, setData] = useState(initialData || []);
+export default function KeywordChart() {
+ const [data, setData] = useState
([]);
+ const [loading, setLoading] = useState(false);
+ const [timeType, setTimeType] = useState<'1' | '2' | '3'>('3');
+ const [displayMode, setDisplayMode] = useState<'column' | 'wordcloud'>('column');
+ const [totalSearches, setTotalSearches] = useState(0);
- const fetchData = useCallback(
- async (params?: { searchDate?: string; searchCount?: number; searchType?: 1 | 2 | 3 }) => {
+ const fetchData = useCallback(async (searchType: '1' | '2' | '3' = '3') => {
+ setLoading(true);
+ try {
const resData = await getKeyWordSearchRank({
- searchDate: params?.searchDate || '2025-11-03',
- // searchCount: params?.searchCount || 4,
- searchType: params?.searchType || 3,
+ searchDate: '2025-11-03',
+ searchType: searchType,
});
if (resData.code === 200) {
- setData(resData.rows);
+ const formattedData = resData.rows
+ .sort((a, b) => b.searchCount - a.searchCount)
+ .slice(0, 15)
+ .map((item, index) => ({
+ ...item,
+ searchCount: Number(item.searchCount) || 0,
+ rank: index + 1,
+ }));
+
+ setData(formattedData);
+
+ // 计算总搜索次数
+ const total = formattedData.reduce((sum, item) => sum + item.searchCount, 0);
+ setTotalSearches(total);
}
- },
- [],
- );
+ } catch (error) {
+ console.error('获取关键词数据失败:', error);
+ } finally {
+ setLoading(false);
+ }
+ }, []);
useEffect(() => {
- fetchData();
- }, [fetchData]);
- const config = {
- data,
+ fetchData(timeType);
+ }, [timeType]);
+
+ //
+ const columnConfig = {
+ data: data,
+ stack:true,
xField: 'keyWord',
yField: 'searchCount',
- height: 350,
- label: {
- position: 'middle',
- style: {
- fill: '#FFFFFF',
- opacity: 0.6,
- },
+ height: 280,
+ autoFit: true,
+ style:{
+ columnWidthRatio:0.3,
},
+ // labels: [
+ // {
+ // text: 'searchCount',
+ // style: {
+ // fill: '#666',
+ // fontSize: 12,
+ // fontWeight: 600,
+ // textBaseline: 'bottom',
+ // dy: -6, // 向上偏移,避免与柱状图顶部重叠
+ // },
+ // position: 'top',
+ // }
+ // ],
xAxis: {
label: {
autoHide: true,
autoRotate: false,
+ style: {
+ fontSize: 12,
+ fill: '#666',
+ },
+ formatter: (text: string) => {
+ return text.length > 6 ? `${text.substring(0, 6)}...` : text;
+ },
+ },
+ title: {
+ text: '关键词',
+ style: {
+ fontSize: 14,
+ fill: '#333',
+ fontWeight: 'bold',
+ },
+ },
+ grid: {
+ line: {
+ style: {
+ stroke: '#e8e8e8',
+ lineWidth: 1,
+ lineDash: [3, 3],
+ },
+ },
+ alignTick: true,
},
},
- meta: {
- keyWord: { alias: '关键词' },
- searchCount: { alias: '搜索次数' },
+ yAxis: {
+ title: {
+ text: '搜索次数',
+ style: {
+ fontSize: 14,
+ fill: '#333',
+ fontWeight: 'bold',
+ },
+ },
+ label: {
+ style: {
+ fontSize: 12,
+ fill: '#666',
+ },
+ },
+ grid: {
+ line: {
+ style: {
+ stroke: '#e8e8e8',
+ lineWidth: 1,
+ lineDash: [3, 3],
+ },
+ },
+ alignTick: true,
+ },
+ },
+ tooltip: {
+ title: '关键词搜索统计',
+ items: [
+ { field: 'keyWord', name: '关键词' },
+ { field: 'rank', name: '排名' },
+ { field: 'searchCount', name: '搜索次数', valueFormatter: (v) => `${v} 次` },
+ ],
+ },
+ interactions: [{ type: 'element-active' }],
+ animation: {
+ appear: {
+ animation: 'scale-in-y',
+ duration: 1000,
+ },
},
};
+
+
+ const getTimeText = () => {
+ switch(timeType) {
+ case '1': return '今日';
+ case '2': return '本周';
+ case '3': return '本月';
+ default: return '';
+ }
+ };
+
return (
- <>
-
-
查询关键词搜索排行
- {
- fetchData({
- searchDate: '2025-11-03',
- searchCount: 10,
- searchType: Number(e.target.value) as 1 | 2 | 3,
- });
- }}
- defaultValue="3"
- >
-
-
-
-
-
-
- {data?.map((item, index) => (
-
- 关键词:{item.keyWord}
- 搜索次数:{item.searchCount}
-
- ))}
-
- >
+
+ 🔍 查询关键词搜索排行
+ 搜索分析
+
+ }
+ style={{
+ borderRadius: 12,
+ boxShadow: '0 4px 12px rgba(0,0,0,0.1)'
+ }}
+ bodyStyle={{
+ padding: '16px',
+ borderRadius: 8
+ }}
+ extra={
+
+
+
+
+
+
+
+
+
+ }
+ >
+
+ {/* 统计信息 */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* 图表显示区域 */}
+ {data.length > 0 ? (
+
+ ) : (
+
+ )}
+
+
);
-}
+}
\ No newline at end of file
diff --git a/src/pages/Analysis/User/components/WantedPositionStatics.tsx b/src/pages/Analysis/User/components/WantedPositionStatics.tsx
index 6c61227..f8cad03 100644
--- a/src/pages/Analysis/User/components/WantedPositionStatics.tsx
+++ b/src/pages/Analysis/User/components/WantedPositionStatics.tsx
@@ -1,84 +1,447 @@
import React, { useState, useEffect, useCallback } from 'react';
import { getWantedPositionStaticsCount } from '@/services/analysis/user';
-import { Card, Table } from 'antd';
+import { Card, Row, Col, Select, Empty, Spin, Tag, Statistic } from 'antd';
+import { Column } from '@ant-design/charts';
-interface PositionStatics {
+const { Option } = Select;
+
+interface PositionItem {
+ jobId: number;
jobName: string;
- count: number;
+ sexCode?: string; // 可能为 '男' 或 '女'
+ staticsCount?: number;
}
-interface getWantedPositionStaticsCountStaticsResult {
- male: PositionStatics[];
- female: PositionStatics[];
+interface PositionStatics {
+ jobId: number;
+ jobName: string;
+ count: number;
+ percentage: number;
}
const WantedPositionStatics: React.FC = () => {
- const [staticsData, setStaticsData] = useState
({ male: [], female: [] });
+ const [malePositions, setMalePositions] = useState([]);
+ const [femalePositions, setFemalePositions] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [topCount, setTopCount] = useState<10 | 15 | 20>(10);
+ const [totalMale, setTotalMale] = useState(0);
+ const [totalFemale, setTotalFemale] = useState(0);
+ const [allPositions, setAllPositions] = useState([]);
const fetchStatics = useCallback(async () => {
- const resData = await getWantedPositionStaticsCount({ sexCode: 0 });
- if (resData.code === 200) {
- const malePositions: Record = {};
- const femalePositions: Record = {};
+ setLoading(true);
+ try {
+ const resData = await getWantedPositionStaticsCount();
+ if (resData.code === 200) {
+ const positions = resData.data as PositionItem[];
+ setAllPositions(positions);
+
+ // 统计每个岗位的男女数量
+ const maleCounts: Record = {};
+ const femaleCounts: Record = {};
+
+ positions.forEach((item,index) => {
+ if (item.sexCode === '男') {
+ maleCounts[item.jobId] = (maleCounts[item.jobId] || 0) + 1;
+ } else if (item.sexCode === '女') {
+ femaleCounts[item.jobId] = (femaleCounts[item.jobId] || 0) + 1;
+ }
+ });
- 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 maleTotal = Object.values(maleCounts).reduce((a, b) => a + b, 0);
+ const femaleTotal = Object.values(femaleCounts).reduce((a, b) => a + b, 0);
+
+ setTotalMale(maleTotal);
+ setTotalFemale(femaleTotal);
- const result: StaticsResult = {
- male: Object.entries(malePositions).map(([jobName, count]) => ({ jobName, count })),
- female: Object.entries(femalePositions).map(([jobName, count]) => ({ jobName, count })),
- };
+ // 获取所有岗位的唯一列表
+ const uniqueJobs = Array.from(
+ new Map(positions.map(item => [item.jobId, item])).values()
+ );
- setStaticsData(result);
+ // 处理男性数据
+ const processedMale = uniqueJobs
+ .map(job => ({
+ jobId: job.jobId,
+ jobName: job.jobName,
+ count: maleCounts[job.jobId] || 0,
+ percentage: maleTotal > 0 ? Math.round(((maleCounts[job.jobId] || 0) / maleTotal) * 100) : 0
+ }))
+ .filter(item => item.count > 0) // 只显示有数据的岗位
+ .sort((a, b) => b.count - a.count)
+ .slice(0, topCount);
+
+ // 处理女性数据
+ const processedFemale = uniqueJobs
+ .map(job => ({
+ jobId: job.jobId,
+ jobName: job.jobName,
+ count: femaleCounts[job.jobId] || 0,
+ percentage: femaleTotal > 0 ? Math.round(((femaleCounts[job.jobId] || 0) / femaleTotal) * 100) : 0
+ }))
+ .filter(item => item.count > 0) // 只显示有数据的岗位
+ .sort((a, b) => b.count - a.count)
+ .slice(0, topCount);
+
+ console.log(processedMale,processedFemale,'++++++++')
+
+ setMalePositions(processedMale);
+ setFemalePositions(processedFemale);
+ }
+ } catch (error) {
+ console.error('获取期望岗位数据失败:', error);
+ } finally {
+ setLoading(false);
}
- }, []);
+ }, [topCount]);
useEffect(() => {
fetchStatics();
}, [fetchStatics]);
- const columns = [
- {
- title: '岗位名称',
- dataIndex: 'jobName',
- key: 'jobName',
+ // 横向柱状图配置 - 男性
+ const maleColumnConfig = {
+ data: malePositions,
+ xField: 'count',
+ yField: 'jobName',
+ seriesField: 'jobName',
+ isGroup: false,
+ isStack: false,
+ height: 280,
+ autoFit: true,
+ columnWidthRatio: 0.6,
+ color: '#1890ff',
+ labels: [
+ {
+ text: 'count',
+ style: {
+ fill: '#666',
+ fontSize: 12,
+ fontWeight: 600,
+ },
+ position: 'right',
+ formatter: (datum: any) => `${datum.count}人`,
+ }
+ ],
+ xAxis: {
+ title: {
+ text: '人数',
+ style: {
+ fontSize: 14,
+ fill: '#333',
+ fontWeight: 'bold',
+ },
+ },
+ label: {
+ style: {
+ fontSize: 12,
+ fill: '#666',
+ },
+ },
+ grid: {
+ line: {
+ style: {
+ stroke: '#e8e8e8',
+ lineWidth: 1,
+ lineDash: [3, 3],
+ },
+ },
+ alignTick: true,
+ },
},
- {
- title: '数量',
- dataIndex: 'count',
- key: 'count',
+ yAxis: {
+ title: null,
+ label: {
+ style: {
+ fontSize: 12,
+ fill: '#666',
+ },
+ formatter: (text: string) => {
+ return text.length > 10 ? `${text.substring(0, 10)}...` : text;
+ },
+ },
+ grid: {
+ line: {
+ style: {
+ stroke: '#e8e8e8',
+ lineWidth: 1,
+ lineDash: [3, 3],
+ },
+ },
+ alignTick: true,
+ },
},
- ];
+ tooltip: {
+ title: '男性期望岗位',
+ items: [
+ { field: 'jobName', name: '岗位名称' },
+ { field: 'count', name: '人数', valueFormatter: (v) => `${v} 人` },
+ { field: 'percentage', name: '占比', valueFormatter: (v) => `${v}%` },
+ ],
+ },
+ interactions: [{ type: 'element-active' }],
+ animation: {
+ appear: {
+ animation: 'scale-in-x',
+ duration: 1000,
+ },
+ },
+ };
+
+ // 横向柱状图配置 - 女性
+ const femaleColumnConfig = {
+ data: femalePositions,
+ xField: 'count',
+ yField: 'jobName',
+ seriesField: 'jobName',
+ isGroup: false,
+ isStack: false,
+ height: 280,
+ autoFit: true,
+ columnWidthRatio: 0.6,
+ color: '#eb2f96',
+ labels: [
+ {
+ text: 'count',
+ style: {
+ fill: '#666',
+ fontSize: 12,
+ fontWeight: 600,
+ },
+ position: 'right',
+ formatter: (datum: any) => `${datum.count}人`,
+ }
+ ],
+ xAxis: {
+ title: {
+ text: '人数',
+ style: {
+ fontSize: 14,
+ fill: '#333',
+ fontWeight: 'bold',
+ },
+ },
+ label: {
+ style: {
+ fontSize: 12,
+ fill: '#666',
+ },
+ },
+ grid: {
+ line: {
+ style: {
+ stroke: '#e8e8e8',
+ lineWidth: 1,
+ lineDash: [3, 3],
+ },
+ },
+ alignTick: true,
+ },
+ },
+ yAxis: {
+ title: null,
+ label: {
+ style: {
+ fontSize: 12,
+ fill: '#666',
+ },
+ formatter: (text: string) => {
+ return text.length > 10 ? `${text.substring(0, 10)}...` : text;
+ },
+ },
+ grid: {
+ line: {
+ style: {
+ stroke: '#e8e8e8',
+ lineWidth: 1,
+ lineDash: [3, 3],
+ },
+ },
+ alignTick: true,
+ },
+ },
+ tooltip: {
+ title: '女性期望岗位',
+ items: [
+ { field: 'jobName', name: '岗位名称' },
+ { field: 'count', name: '人数', valueFormatter: (v) => `${v} 人` },
+ { field: 'percentage', name: '占比', valueFormatter: (v) => `${v}%` },
+ ],
+ },
+ interactions: [{ type: 'element-active' }],
+ animation: {
+ appear: {
+ animation: 'scale-in-x',
+ duration: 1000,
+ },
+ },
+ };
+
+ // 如果没有数据,显示所有岗位的占位图
+ const showPlaceholder = malePositions.length === 0 && femalePositions.length === 0;
return (
-
-
-
+
+
+ 🎯 期望岗位统计分析
+ 岗位分析
+
+ }
+ style={{
+ borderRadius: 12,
+ boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
+ marginBottom: 16
+ }}
+ bodyStyle={{
+ padding: '16px',
+ background: 'linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%)',
+ borderRadius: 8
+ }}
+ extra={
+
+
+
+
+
+ }
+ >
+ {/* 统计数据行 */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {showPlaceholder ? (
+
+
+
+ 👨 男性热门岗位
+ TOP {topCount}
+
+ }
+ size="small"
+ style={{ height: '100%' }}
+ >
+
+
+
+
+
+ 👩 女性热门岗位
+ TOP {topCount}
+
+ }
+ size="small"
+ style={{ height: '100%' }}
+ >
+
+
+
+
+ ) : (
+
+ {/* 男性期望岗位 - 横向柱状图 */}
+
+
+ 👨 男性热门岗位
+ TOP {topCount}
+
+ }
+ size="small"
+ style={{ height: '100%' }}
+ bodyStyle={{ padding: '12px', height: '100%' }}
+ >
+ {malePositions.length > 0 ? (
+
+
+
+ ) : (
+
+ )}
+
+
+
+ {/* 女性期望岗位 - 横向柱状图 */}
+
+
+ 👩 女性热门岗位
+ TOP {topCount}
+
+ }
+ size="small"
+ style={{ height: '100%' }}
+ bodyStyle={{ padding: '12px', height: '100%' }}
+ >
+ {femalePositions.length > 0 ? (
+
+
+
+ ) : (
+
+ )}
+
+
+
+ )}
-
-
-
-
+
);
};
-export default WantedPositionStatics;
+export default WantedPositionStatics;
\ No newline at end of file
diff --git a/src/pages/Analysis/User/index.tsx b/src/pages/Analysis/User/index.tsx
index 34faffd..74716c3 100644
--- a/src/pages/Analysis/User/index.tsx
+++ b/src/pages/Analysis/User/index.tsx
@@ -43,18 +43,19 @@ export default function userAnalysis() {
}, []);
return (
-
-
+
);
}
diff --git a/src/pages/Mobileusers/List/index.tsx b/src/pages/Mobileusers/List/index.tsx
index 6bab3f4..d1f3852 100644
--- a/src/pages/Mobileusers/List/index.tsx
+++ b/src/pages/Mobileusers/List/index.tsx
@@ -1,81 +1,77 @@
-import React, { Fragment, useEffect, useRef, useState } from 'react';
-import { FormattedMessage, useAccess } from '@umijs/max';
+import React, { Fragment, useRef, useState, useEffect } from 'react';
+import { useAccess } from '@umijs/max';
+import { getCmsAppUserList } from '@/services/mobileusers/list';
+import { getResumeDetail } from '@/services/resumeLibrary/resumeList'; // 简历库的简历详情接口
import { Button, FormInstance, message } from 'antd';
import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
-import { PlusOutlined } from '@ant-design/icons';
+import { EyeOutlined, UserOutlined } from '@ant-design/icons';
import { getDictValueEnum } from '@/services/system/dict';
import DictTag from '@/components/DictTag';
-import { exportCmsAppUserExport, getCmsAppUserList } from '@/services/mobileusers/list';
+import ResumeDetail from '../components/detail';
-const handleExport = async (values: API.MobileUser.ListParams) => {
- const hide = message.loading('正在导出');
- try {
- await exportCmsAppUserExport(values);
- hide();
- message.success('导出成功');
- return true;
- } catch (error) {
- hide();
- message.error('导出失败,请重试');
- return false;
- }
-};
-
-function ManagementList() {
+function MobileUserList() {
const access = useAccess();
const formTableRef = useRef
();
const actionRef = useRef();
- const [educationEnum, setEducationEnum] = useState([]);
- const [experienceEnum, setExperienceEnum] = useState([]);
- const [areaEnum, setAreaEnum] = useState([]);
- const [sexEnum, setSexEnum] = useState([]);
- const [hotEnum, setHotEnum] = useState([]);
- const [politicalEnum, setPoliticalEnum] = useState([]);
const [currentRow, setCurrentRow] = useState();
const [modalVisible, setModalVisible] = useState(false);
+ const [loading, setLoading] = useState(false);
+ // 字典枚举值
+ const [sexEnum, setSexEnum] = useState([]);
+ const [educationEnum, setEducationEnum] = useState([]);
+ const [politicalEnum, setPoliticalEnum] = useState([]);
+ const [areaEnum, setAreaEnum] = useState([]);
+
+ // 获取字典数据
useEffect(() => {
- getDictValueEnum('education', true, true).then((data) => {
- setEducationEnum(data);
- });
- getDictValueEnum('experience', true, true).then((data) => {
- setExperienceEnum(data);
- });
- getDictValueEnum('area', true, true).then((data) => {
- setAreaEnum(data);
- });
getDictValueEnum('sys_user_sex', true).then((data) => {
setSexEnum(data);
});
+ getDictValueEnum('education', true, true).then((data) => {
+ setEducationEnum(data);
+ });
getDictValueEnum('political_affiliation', true, true).then((data) => {
setPoliticalEnum(data);
});
- // getDictValueEnum('job_hot',true).then((data) => {
- // setHotEnum(data)
- // })
+ getDictValueEnum('area', true, true).then((data) => {
+ setAreaEnum(data);
+ });
}, []);
+ // 查看简历详情
+ const handleViewResume = async (userId: any) => {
+ setLoading(true);
+ try {
+ const res = await getResumeDetail(userId);
+ if (res.code === 200) {
+ setCurrentRow(res.data);
+ setModalVisible(true);
+ } else {
+ message.error(res.msg);
+ }
+ } catch (error) {
+ message.error('获取简历详情失败');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ // 查看用户行为(待定功能)
+ const handleViewBehavior = (userId: any) => {
+ message.info('用户行为查看功能开发中');
+ // TODO: 待后续开发
+ };
+
const columns: ProColumns[] = [
- {
+ {
title: '用户名',
dataIndex: 'name',
valueType: 'text',
align: 'center',
},
- {
- title: '期望薪资',
- dataIndex: 'minSalary',
- valueType: 'text',
- hideInSearch: true,
- align: 'center',
- render: (_, record) => (
- <>
- {record.salaryMin}-{record.salaryMax}
- >
- ),
- },
{
title: '出生日期',
dataIndex: 'birthDate',
@@ -122,13 +118,49 @@ function ManagementList() {
return ;
},
},
+ {
+ title: '期望薪资',
+ dataIndex: 'minSalary',
+ valueType: 'text',
+ hideInSearch: true,
+ align: 'center',
+ render: (_, record) => (
+
+ {record.salaryMin || '面议'} - {record.salaryMax || '面议'}
+
+ ),
+ },
{
title: '操作',
hideInSearch: true,
align: 'center',
- dataIndex: 'jobId',
- width: 200,
- render: (jobId, record) => [],
+ dataIndex: 'userId',
+ width: 240,
+ render: (userId, record) => (
+
+ }
+ loading={loading && currentRow?.userId === userId}
+ hidden={!access.hasPerms('mobileusers:list:viewResume')}
+ onClick={() => handleViewResume(userId)}
+ >
+ 用户简历
+
+ }
+ hidden={!access.hasPerms('mobileusers:list:viewBehavior')}
+ onClick={() => handleViewBehavior(userId)}
+ >
+ 用户行为
+
+
+ ),
},
];
@@ -136,42 +168,39 @@ function ManagementList() {
- // params 是需要自带的参数
- // 这个参数优先级更高,会覆盖查询表单的参数
actionRef={actionRef}
formRef={formTableRef}
- rowKey="jobId"
- key="index"
+ rowKey="userId"
+ key="mobileUserIndex"
columns={columns}
- request={(params) =>
- getCmsAppUserList({ ...params } as API.MobileUser.ListParams).then((res) => {
- console.log(params);
- const result = {
- data: res.rows,
- total: res.total,
- success: true,
- };
- return result;
- })
- }
- toolBarRender={() => [
- ,
- ]}
+ search={{
+ labelWidth: 120,
+ }}
+ request={async (
+ params: API.MobileUser.ListParams & {
+ pageSize?: number;
+ current?: number;
+ },
+ ) => {
+ const res = await getCmsAppUserList({ ...params } as API.MobileUser.ListParams);
+ return {
+ data: res.rows,
+ total: res.total,
+ success: true,
+ };
+ }}
/>
+ {
+ setModalVisible(false);
+ setCurrentRow(undefined);
+ }}
+ />
);
}
-export default ManagementList;
+export default MobileUserList;
\ No newline at end of file
diff --git a/src/pages/Mobileusers/components/detail.tsx b/src/pages/Mobileusers/components/detail.tsx
new file mode 100644
index 0000000..3e4691a
--- /dev/null
+++ b/src/pages/Mobileusers/components/detail.tsx
@@ -0,0 +1,402 @@
+import { Modal, Card, Row, Col, Divider, Tag, Typography, Avatar, Empty } from 'antd';
+import React, { useEffect, useState } from 'react';
+import { getDictValueEnum } from '@/services/system/dict';
+import DictTag from '@/components/DictTag';
+
+const { Text, Paragraph, Title } = Typography;
+
+export type ResumeDetailProps = {
+ onCancel: (flag?: boolean, formVals?: unknown) => void;
+ open: boolean;
+ values?: any;
+};
+
+const ResumeDetail: React.FC = (props) => {
+ const [sexEnum, setSexEnum] = useState([]);
+ const [educationEnum, setEducationEnum] = useState([]);
+ const [politicalEnum, setPoliticalEnum] = useState([]);
+ const [areaEnum, setAreaEnum] = useState([]);
+
+ // 获取字典数据
+ useEffect(() => {
+ getDictValueEnum('sys_user_sex', true).then((data) => {
+ setSexEnum(data);
+ });
+ getDictValueEnum('education', true, true).then((data) => {
+ setEducationEnum(data);
+ });
+ getDictValueEnum('political_affiliation', true, true).then((data) => {
+ setPoliticalEnum(data);
+ });
+ getDictValueEnum('area', true, true).then((data) => {
+ setAreaEnum(data);
+ });
+ }, []);
+
+ const handleCancel = () => {
+ props.onCancel();
+ };
+
+ const { values } = props;
+
+ // 格式化时间显示
+ const formatTimeRange = (start: string, end: string) => {
+ if (!start && !end) return '';
+ return `${start || ''} - ${end || '至今'}`;
+ };
+
+ // 渲染空状态
+ const renderEmpty = (description: string) => (
+
+ );
+
+ return (
+
+ {values?.name || '用户'} - 简历详情
+
+ }
+ open={props.open}
+ width={1100}
+ onCancel={handleCancel}
+ footer={null}
+ bodyStyle={{ maxHeight: '80vh', overflowY: 'auto', padding: '24px' }}
+ >
+ {/* 头部基本信息卡片 */}
+
+
+ {/* 头像区域 */}
+
+
+ {values?.avatar ? (
+
+ ) : (
+
+ {values?.name?.charAt(0) || 'U'}
+
+ )}
+
+ {values?.name || '未填写'}
+
+
+ {values?.jobTitle?.join(' / ') || '暂无期望岗位'}
+
+
+
+
+ {/* 详细信息区域 */}
+
+
+
+
+
+
+
+
年龄:
+
{values?.age || '未填写'}
+
+
+
+
+
+
+
+
手机号:
+
{values?.phone || '未填写'}
+
+
+
+
+
+
+
+
出生日期:
+
{values?.birthDate || '未填写'}
+
+
+
+
+
工作年限:
+
{values?.workYears || '未填写'}
+
+
+
+
+
+
+
+
期望薪资:
+
+ {values?.salaryMin || '面议'} - {values?.salaryMax || '面议'} 元/月
+
+
+
+
+
+ {/* 附加信息 */}
+
+
+
+
+
毕业院校:
+
{values?.graduationSchool || '未填写'}
+
+
+
+
+
就读专业:
+
{values?.major || '未填写'}
+
+
+
+
+
居住地址:
+
{values?.residenceAddress || '未填写'}
+
+
+
+
+
+
+
+ {/* 个人介绍与求职意向 - 新布局 */}
+
+ 📋 个人介绍与求职意向
+
+ }
+ style={{
+ marginBottom: 24,
+ borderRadius: 8,
+ border: '1px solid #f0f0f0',
+ boxShadow: '0 2px 8px rgba(0,0,0,0.06)',
+ }}
+ >
+
+
+
+ ●
+ 个人简要介绍
+
+
+ {values?.introduction || '暂无个人介绍'}
+
+
+
+
+
+ ●
+ 求职意向岗位
+
+
+ {values?.jobTitle?.length > 0 ? (
+
+ {values.jobTitle.map((job: string, index: number) => (
+
+ {job}
+
+ ))}
+
+ ) : (
+
+ {values?.jobIntention || '暂无求职意向'}
+
+ )}
+
+
+
+
+
+ ●
+ 自我评价
+
+
+ {values?.selfEvaluation || '暂无自我评价'}
+
+
+
+
+
+ {/* 工作经历 - 新布局 */}
+
+ 💼 工作经历 ({values?.workExp?.length || 0})
+
+ }
+ style={{
+ marginBottom: 24,
+ borderRadius: 8,
+ border: '1px solid #f0f0f0',
+ boxShadow: '0 2px 8px rgba(0,0,0,0.06)',
+ }}
+ >
+ {values?.workExp?.length > 0 ? (
+
+ {values.workExp.map((work: any, index: number) => (
+
+
+
+
+
+ {work.company || '未填写公司'}
+
+ {work.isFullTime !== null && (
+
+ {work.isFullTime ? '全职' : '兼职'}
+
+ )}
+
+
+
+ {work.position || '未填写职位'}
+
+ 部门:{work.department || '未填写'}
+
+ {work.duty && (
+
+ {work.duty}
+
+ )}
+
+
+
+
+ {formatTimeRange(work.startTime, work.endTime)}
+
+ {work.salary && (
+
+ 薪资:{work.salary}
+
+ )}
+
+
+
+
+ ))}
+
+ ) : (
+ renderEmpty('暂无工作经历')
+ )}
+
+
+ );
+};
+
+export default ResumeDetail;
\ No newline at end of file