新增 用户行为记录 新增岗位信息管理
This commit is contained in:
@@ -55,7 +55,7 @@ export default function AreaStatics({ data }) {
|
|||||||
height: 220,
|
height: 220,
|
||||||
autoFit: true,
|
autoFit: true,
|
||||||
style:{
|
style:{
|
||||||
columnWidthRatio: 0.4, // 统一柱状图宽度比例
|
columnWidthRatio: 0.4,
|
||||||
},
|
},
|
||||||
color: color,
|
color: color,
|
||||||
xAxis: {
|
xAxis: {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { EyeOutlined, UserOutlined } from '@ant-design/icons';
|
|||||||
import { getDictValueEnum } from '@/services/system/dict';
|
import { getDictValueEnum } from '@/services/system/dict';
|
||||||
import DictTag from '@/components/DictTag';
|
import DictTag from '@/components/DictTag';
|
||||||
import ResumeDetail from '../components/detail';
|
import ResumeDetail from '../components/detail';
|
||||||
|
import BehaviorLogList from '../components/logList';
|
||||||
|
|
||||||
function MobileUserList() {
|
function MobileUserList() {
|
||||||
const access = useAccess();
|
const access = useAccess();
|
||||||
@@ -19,13 +20,18 @@ function MobileUserList() {
|
|||||||
const [modalVisible, setModalVisible] = useState<boolean>(false);
|
const [modalVisible, setModalVisible] = useState<boolean>(false);
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
|
// 用户行为
|
||||||
|
const [behaviorModalVisible, setBehaviorModalVisible] = useState<boolean>(false);
|
||||||
|
const [selectedUserId, setSelectedUserId] = useState<string | number>('');
|
||||||
|
const [selectedUserName, setSelectedUserName] = useState<string>('');
|
||||||
|
|
||||||
// 字典枚举值
|
// 字典枚举值
|
||||||
const [sexEnum, setSexEnum] = useState<any>([]);
|
const [sexEnum, setSexEnum] = useState<any>([]);
|
||||||
const [educationEnum, setEducationEnum] = useState<any>([]);
|
const [educationEnum, setEducationEnum] = useState<any>([]);
|
||||||
const [politicalEnum, setPoliticalEnum] = useState<any>([]);
|
const [politicalEnum, setPoliticalEnum] = useState<any>([]);
|
||||||
const [areaEnum, setAreaEnum] = useState<any>([]);
|
const [areaEnum, setAreaEnum] = useState<any>([]);
|
||||||
|
|
||||||
// 获取字典数据
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getDictValueEnum('sys_user_sex', true).then((data) => {
|
getDictValueEnum('sys_user_sex', true).then((data) => {
|
||||||
setSexEnum(data);
|
setSexEnum(data);
|
||||||
@@ -59,10 +65,11 @@ function MobileUserList() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 查看用户行为(待定功能)
|
// 查看用户行为(
|
||||||
const handleViewBehavior = (userId: any) => {
|
const handleViewBehavior = (userId: any, userName: string) => {
|
||||||
message.info('用户行为查看功能开发中');
|
setSelectedUserId(userId);
|
||||||
// TODO: 待后续开发
|
setSelectedUserName(userName || '未知用户');
|
||||||
|
setBehaviorModalVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const columns: ProColumns<API.MobileUser.ListRow>[] = [
|
const columns: ProColumns<API.MobileUser.ListRow>[] = [
|
||||||
@@ -72,6 +79,13 @@ function MobileUserList() {
|
|||||||
valueType: 'text',
|
valueType: 'text',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '手机号',
|
||||||
|
dataIndex: 'phone',
|
||||||
|
valueType: 'text',
|
||||||
|
align: 'center',
|
||||||
|
hideInSearch: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '出生日期',
|
title: '出生日期',
|
||||||
dataIndex: 'birthDate',
|
dataIndex: 'birthDate',
|
||||||
@@ -155,7 +169,7 @@ function MobileUserList() {
|
|||||||
key="behavior"
|
key="behavior"
|
||||||
icon={<UserOutlined />}
|
icon={<UserOutlined />}
|
||||||
hidden={!access.hasPerms('mobileusers:list:viewBehavior')}
|
hidden={!access.hasPerms('mobileusers:list:viewBehavior')}
|
||||||
onClick={() => handleViewBehavior(userId)}
|
onClick={() => handleViewBehavior(userId, record.name)}
|
||||||
>
|
>
|
||||||
用户行为
|
用户行为
|
||||||
</Button>
|
</Button>
|
||||||
@@ -191,6 +205,8 @@ function MobileUserList() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 简历详情弹窗 */}
|
||||||
<ResumeDetail
|
<ResumeDetail
|
||||||
open={modalVisible}
|
open={modalVisible}
|
||||||
values={currentRow}
|
values={currentRow}
|
||||||
@@ -199,6 +215,18 @@ function MobileUserList() {
|
|||||||
setCurrentRow(undefined);
|
setCurrentRow(undefined);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* 用户行为记录弹窗 */}
|
||||||
|
<BehaviorLogList
|
||||||
|
userId={selectedUserId}
|
||||||
|
userName={selectedUserName}
|
||||||
|
open={behaviorModalVisible}
|
||||||
|
onCancel={() => {
|
||||||
|
setBehaviorModalVisible(false);
|
||||||
|
setSelectedUserId('');
|
||||||
|
setSelectedUserName('');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
288
src/pages/Mobileusers/components/logList.tsx
Normal file
288
src/pages/Mobileusers/components/logList.tsx
Normal file
@@ -0,0 +1,288 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Modal, Table, Card, Tag, Space, Typography, Spin, Empty } from 'antd';
|
||||||
|
import { getBehaviorLog } from '@/services/mobileusers/list';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
export type BehaviorLogProps = {
|
||||||
|
userId: string | number;
|
||||||
|
userName: string;
|
||||||
|
open: boolean;
|
||||||
|
onCancel: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface BehaviorLogItem {
|
||||||
|
id: string;
|
||||||
|
opTime: string;
|
||||||
|
content: string;
|
||||||
|
userId: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BehaviorLogList: React.FC<BehaviorLogProps> = (props) => {
|
||||||
|
const { userId, userName, open, onCancel } = props;
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
const [dataSource, setDataSource] = useState<BehaviorLogItem[]>([]);
|
||||||
|
const [total, setTotal] = useState<number>(0);
|
||||||
|
const [currentPage, setCurrentPage] = useState<number>(1);
|
||||||
|
const [pageSize, setPageSize] = useState<number>(10);
|
||||||
|
|
||||||
|
// 获取行为日志数据
|
||||||
|
const fetchBehaviorLog = async (page: number = 1, size: number = 10) => {
|
||||||
|
if (!userId) return;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
userId: String(userId),
|
||||||
|
current: page,
|
||||||
|
pageSize: size,
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await getBehaviorLog(params);
|
||||||
|
if (res.code === 200) {
|
||||||
|
setDataSource(res.rows || []);
|
||||||
|
setTotal(Number(res.total) || 0);
|
||||||
|
} else {
|
||||||
|
setDataSource([]);
|
||||||
|
setTotal(0);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取用户行为日志失败:', error);
|
||||||
|
setDataSource([]);
|
||||||
|
setTotal(0);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 当userId或open变化时重新获取数据
|
||||||
|
useEffect(() => {
|
||||||
|
if (open && userId) {
|
||||||
|
fetchBehaviorLog(currentPage, pageSize);
|
||||||
|
}
|
||||||
|
}, [open, userId, currentPage, pageSize]);
|
||||||
|
|
||||||
|
// 处理分页变化
|
||||||
|
const handleTableChange = (pagination: any) => {
|
||||||
|
setCurrentPage(pagination.current);
|
||||||
|
setPageSize(pagination.pageSize);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 格式化时间显示
|
||||||
|
const formatTime = (timeStr: string) => {
|
||||||
|
if (!timeStr) return '-';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const date = new Date(timeStr);
|
||||||
|
return date.toLocaleString('zh-CN', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
hour12: false,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return timeStr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 根据内容获取标签颜色
|
||||||
|
const getTagColor = (content: string) => {
|
||||||
|
if (!content) return 'default';
|
||||||
|
|
||||||
|
const lowerContent = content.toLowerCase();
|
||||||
|
if (lowerContent.includes('登录') || lowerContent.includes('登出')) {
|
||||||
|
return 'blue';
|
||||||
|
} else if (lowerContent.includes('浏览') || lowerContent.includes('查看')) {
|
||||||
|
return 'green';
|
||||||
|
} else if (lowerContent.includes('提交') || lowerContent.includes('申请')) {
|
||||||
|
return 'purple';
|
||||||
|
} else if (lowerContent.includes('修改') || lowerContent.includes('更新')) {
|
||||||
|
return 'orange';
|
||||||
|
} else if (lowerContent.includes('删除') || lowerContent.includes('取消')) {
|
||||||
|
return 'red';
|
||||||
|
} else {
|
||||||
|
return 'default';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 表格列定义
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '序号',
|
||||||
|
key: 'index',
|
||||||
|
width: 100,
|
||||||
|
align: 'center',
|
||||||
|
render: (_: any, __: any, index: number) => {
|
||||||
|
return (currentPage - 1) * pageSize + index + 1;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作时间',
|
||||||
|
dataIndex: 'opTime',
|
||||||
|
key: 'opTime',
|
||||||
|
align: 'center',
|
||||||
|
width: 180,
|
||||||
|
render: (text: string) => (
|
||||||
|
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||||
|
{formatTime(text)}
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
|
sorter: (a: BehaviorLogItem, b: BehaviorLogItem) => {
|
||||||
|
return new Date(a.opTime).getTime() - new Date(b.opTime).getTime();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '行为类型',
|
||||||
|
key: 'actionType',
|
||||||
|
align: 'center',
|
||||||
|
width: 180,
|
||||||
|
render: (_: any, record: BehaviorLogItem) => {
|
||||||
|
const content = record.content || '';
|
||||||
|
let type = '其他';
|
||||||
|
|
||||||
|
if (content.includes('登录')) type = '登录';
|
||||||
|
else if (content.includes('浏览')) type = '浏览';
|
||||||
|
else if (content.includes('提交')) type = '提交';
|
||||||
|
else if (content.includes('修改')) type = '修改';
|
||||||
|
else if (content.includes('删除')) type = '删除';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tag color={getTagColor(content)} style={{ margin: 0 }}>
|
||||||
|
{type}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作行为',
|
||||||
|
dataIndex: 'content',
|
||||||
|
key: 'content',
|
||||||
|
render: (text: string) => (
|
||||||
|
<div>
|
||||||
|
<Text type="secondary" style={{ lineHeight: 1.5 }}>
|
||||||
|
{text}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={
|
||||||
|
<Space>
|
||||||
|
<span>用户行为记录</span>
|
||||||
|
<Text type="secondary" style={{ fontSize: 14 }}>
|
||||||
|
({userName || '未知用户'})
|
||||||
|
</Text>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
open={open}
|
||||||
|
onCancel={onCancel}
|
||||||
|
width={1100}
|
||||||
|
footer={null}
|
||||||
|
bodyStyle={{ padding: 0 }}
|
||||||
|
>
|
||||||
|
<Spin spinning={loading}>
|
||||||
|
<Card
|
||||||
|
size="small"
|
||||||
|
style={{ border: 'none' }}
|
||||||
|
styles={{
|
||||||
|
body: { padding: '16px 0 0 0' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* 统计信息 */}
|
||||||
|
{dataSource.length > 0 && (
|
||||||
|
<Card
|
||||||
|
size="small"
|
||||||
|
style={{
|
||||||
|
marginBottom: 16,
|
||||||
|
backgroundColor: '#fafafa',
|
||||||
|
border: '1px solid #f0f0f0',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Space size={24} wrap>
|
||||||
|
<div>
|
||||||
|
<Text type="secondary">用户ID:</Text>
|
||||||
|
<Text strong>{userId}</Text>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Text type="secondary">用户名:</Text>
|
||||||
|
<Text strong>{userName}</Text>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Text type="secondary">记录总数:</Text>
|
||||||
|
<Text strong style={{ color: '#1890ff' }}>
|
||||||
|
{total} 条
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Text type="secondary">最近活动:</Text>
|
||||||
|
<Text strong>
|
||||||
|
{dataSource.length > 0 ? formatTime(dataSource[0].opTime) : '暂无'}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Space>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
{dataSource.length > 0 ? (
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
dataSource={dataSource}
|
||||||
|
rowKey="id"
|
||||||
|
pagination={{
|
||||||
|
current: currentPage,
|
||||||
|
pageSize: pageSize,
|
||||||
|
total: total,
|
||||||
|
showSizeChanger: true,
|
||||||
|
showQuickJumper: true,
|
||||||
|
showTotal: (total, range) => `共 ${total} 条记录,显示 ${range[0]}-${range[1]} 条`,
|
||||||
|
pageSizeOptions: ['10', '20', '30', '50'],
|
||||||
|
}}
|
||||||
|
onChange={handleTableChange}
|
||||||
|
size="middle"
|
||||||
|
scroll={{ y: 400 }}
|
||||||
|
locale={{
|
||||||
|
emptyText: (
|
||||||
|
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="暂无行为记录" />
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
onRow={(record) => {
|
||||||
|
return {
|
||||||
|
style: {
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
},
|
||||||
|
onMouseEnter: (e) => {
|
||||||
|
e.currentTarget.style.backgroundColor = '#f5f5f5';
|
||||||
|
},
|
||||||
|
onMouseLeave: (e) => {
|
||||||
|
e.currentTarget.style.backgroundColor = 'inherit';
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Empty
|
||||||
|
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||||
|
description={
|
||||||
|
<div>
|
||||||
|
<p>暂无行为记录</p>
|
||||||
|
<Text type="secondary">该用户目前没有记录的操作行为</Text>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
style={{ margin: '40px 0' }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
</Spin>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BehaviorLogList;
|
||||||
119
src/pages/RecruitmentDataCollection/JobInfo/detail.tsx
Normal file
119
src/pages/RecruitmentDataCollection/JobInfo/detail.tsx
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { Modal, Descriptions,Button } from 'antd';
|
||||||
|
import { DictValueEnumObj } from '@/components/DictTag';
|
||||||
|
|
||||||
|
export type DetailModalProps = {
|
||||||
|
onCancel: () => void;
|
||||||
|
open: boolean;
|
||||||
|
values?: Partial<API.ManagementList.Manage>;
|
||||||
|
educationEnum?: DictValueEnumObj;
|
||||||
|
experienceEnum?: DictValueEnumObj;
|
||||||
|
areaEnum?: DictValueEnumObj;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DetailModal: React.FC<DetailModalProps> = (props) => {
|
||||||
|
const { values } = props;
|
||||||
|
|
||||||
|
// 可以在这里获取字典数据,如果传入的话
|
||||||
|
// 或者通过 useEffect 请求
|
||||||
|
|
||||||
|
// 如果没有传入字典数据,可以在这里获取
|
||||||
|
useEffect(() => {
|
||||||
|
if (props.open && !props.educationEnum) {
|
||||||
|
// 这里可以获取字典数据
|
||||||
|
// getDictValueEnum('education', true, true).then(setEducationEnum);
|
||||||
|
}
|
||||||
|
}, [props.open]);
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
props.onCancel();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 如果没有数据,显示加载状态
|
||||||
|
if (!values) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title="岗位详情"
|
||||||
|
open={props.open}
|
||||||
|
width={800}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
footer={[
|
||||||
|
<Button key="close" onClick={handleCancel}>
|
||||||
|
关闭
|
||||||
|
</Button>
|
||||||
|
]}
|
||||||
|
destroyOnHidden
|
||||||
|
>
|
||||||
|
<Descriptions column={2} bordered>
|
||||||
|
<Descriptions.Item label="岗位名称" span={2}>
|
||||||
|
{values.jobTitle || '-'}
|
||||||
|
</Descriptions.Item>
|
||||||
|
|
||||||
|
<Descriptions.Item label="公司名称">
|
||||||
|
{values.companyName || '-'}
|
||||||
|
</Descriptions.Item>
|
||||||
|
|
||||||
|
<Descriptions.Item label="薪资范围">
|
||||||
|
{`${values.minSalary || 0} - ${values.maxSalary || 0} 元/月`}
|
||||||
|
</Descriptions.Item>
|
||||||
|
|
||||||
|
<Descriptions.Item label="学历要求">
|
||||||
|
{props.educationEnum ? (
|
||||||
|
<span>{props.educationEnum[values.education as string] || values.education || '-'}</span>
|
||||||
|
) : (
|
||||||
|
values.education || '-'
|
||||||
|
)}
|
||||||
|
</Descriptions.Item>
|
||||||
|
|
||||||
|
<Descriptions.Item label="工作经验">
|
||||||
|
{props.experienceEnum ? (
|
||||||
|
<span>{props.experienceEnum[values.experience as string] || values.experience || '-'}</span>
|
||||||
|
) : (
|
||||||
|
values.experience || '-'
|
||||||
|
)}
|
||||||
|
</Descriptions.Item>
|
||||||
|
|
||||||
|
<Descriptions.Item label="工作区县">
|
||||||
|
{props.areaEnum ? (
|
||||||
|
<span>{props.areaEnum[values.jobLocationAreaCode as string] || values.jobLocationAreaCode || '-'}</span>
|
||||||
|
) : (
|
||||||
|
values.jobLocationAreaCode || '-'
|
||||||
|
)}
|
||||||
|
</Descriptions.Item>
|
||||||
|
|
||||||
|
<Descriptions.Item label="招聘人数">
|
||||||
|
{values.vacancies || 0} 人
|
||||||
|
</Descriptions.Item>
|
||||||
|
|
||||||
|
<Descriptions.Item label="工作地点" span={2}>
|
||||||
|
{values.jobLocation || '-'}
|
||||||
|
</Descriptions.Item>
|
||||||
|
|
||||||
|
<Descriptions.Item label="岗位描述" span={2}>
|
||||||
|
<div style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
|
||||||
|
{values.description || '-'}
|
||||||
|
</div>
|
||||||
|
</Descriptions.Item>
|
||||||
|
|
||||||
|
<Descriptions.Item label="浏览量">
|
||||||
|
{values.view || 0}
|
||||||
|
</Descriptions.Item>
|
||||||
|
|
||||||
|
<Descriptions.Item label="发布时间">
|
||||||
|
{values.createTime || '-'}
|
||||||
|
</Descriptions.Item>
|
||||||
|
|
||||||
|
{values.isPublish !== undefined && (
|
||||||
|
<Descriptions.Item label="发布状态">
|
||||||
|
{values.isPublish === 1 ? '已发布' : '未发布'}
|
||||||
|
</Descriptions.Item>
|
||||||
|
)}
|
||||||
|
</Descriptions>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DetailModal;
|
||||||
286
src/pages/RecruitmentDataCollection/JobInfo/index.tsx
Normal file
286
src/pages/RecruitmentDataCollection/JobInfo/index.tsx
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
import React, { Fragment, useRef, useState, useEffect } from 'react';
|
||||||
|
import { history, useAccess } from '@umijs/max';
|
||||||
|
import { getCmsJobList, getJobTrend } from '@/services/Management/list';
|
||||||
|
import { Button, DatePicker, FormInstance, message, Space, Card } from 'antd';
|
||||||
|
import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
|
||||||
|
import { AlignLeftOutlined } from '@ant-design/icons';
|
||||||
|
import { Line } from '@ant-design/charts';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { getDictValueEnum } from '@/services/system/dict';
|
||||||
|
|
||||||
|
import DictTag from '@/components/DictTag';
|
||||||
|
import DetailModal from './detail';
|
||||||
|
|
||||||
|
const { RangePicker } = DatePicker;
|
||||||
|
|
||||||
|
function JobStatistics() {
|
||||||
|
const access = useAccess();
|
||||||
|
const formTableRef = useRef<FormInstance>();
|
||||||
|
const actionRef = useRef<ActionType>();
|
||||||
|
|
||||||
|
const [chartData, setChartData] = useState<any[]>([]);
|
||||||
|
const [currentRow, setCurrentRow] = useState<API.ManagementList.Manage>();
|
||||||
|
const [detailVisible, setDetailVisible] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const [educationEnum, setEducationEnum] = useState<any>([]);
|
||||||
|
const [experienceEnum, setExperienceEnum] = useState<any>([]);
|
||||||
|
const [areaEnum, setAreaEnum] = useState<any>([]);
|
||||||
|
const [isPublishEnum, setIsPublishEnum] = useState<any>([]);
|
||||||
|
|
||||||
|
const [params, setParams] = useState({
|
||||||
|
beginTime: dayjs().subtract(13, 'day').format('YYYY-MM-DD'),
|
||||||
|
endTime: dayjs().format('YYYY-MM-DD'),
|
||||||
|
});
|
||||||
|
|
||||||
|
const getPickerValue = () => {
|
||||||
|
return [dayjs(params.beginTime), dayjs(params.endTime)];
|
||||||
|
};
|
||||||
|
|
||||||
|
const disabledDate = (current: dayjs.Dayjs) => {
|
||||||
|
const now = dayjs();
|
||||||
|
return current.isAfter(now.endOf('day'));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化图表数据
|
||||||
|
useEffect(() => {
|
||||||
|
getChartData();
|
||||||
|
}, [params.beginTime, params.endTime]);
|
||||||
|
|
||||||
|
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('is_publish', true).then((data) => {
|
||||||
|
setIsPublishEnum(data);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleDateRangeChange = (dates: any, dateStrings: [string, string]) => {
|
||||||
|
if (dates && dates[0] && dates[1]) {
|
||||||
|
setParams(() => ({
|
||||||
|
beginTime: dateStrings[0],
|
||||||
|
endTime: dateStrings[1],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
async function getChartData() {
|
||||||
|
let { data } = await getJobTrend(params);
|
||||||
|
setChartData(data); // 暂时使用模拟数据
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图表配置
|
||||||
|
const chartConfig = {
|
||||||
|
data: chartData,
|
||||||
|
xField: 'storageTime',
|
||||||
|
yField: 'insertCount',
|
||||||
|
style: {
|
||||||
|
columnWidthRatio: 0.6,
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
label: {
|
||||||
|
autoHide: true,
|
||||||
|
autoRotate: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
point: {
|
||||||
|
size: 4,
|
||||||
|
shape: 'circle',
|
||||||
|
},
|
||||||
|
line: {
|
||||||
|
size: 3,
|
||||||
|
style: {
|
||||||
|
lineCap: 'round',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
field: 'insertCount',
|
||||||
|
name: '录入数量',
|
||||||
|
valueFormatter: (v) => `${v}个`,
|
||||||
|
title: '日期',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns: ProColumns<API.ManagementList.Manage>[] = [
|
||||||
|
{
|
||||||
|
title: '岗位采集日期',
|
||||||
|
dataIndex: 'collectionDate1',
|
||||||
|
hideInTable: true,
|
||||||
|
valueType: 'dateRange',
|
||||||
|
search: {
|
||||||
|
transform: (value) => {
|
||||||
|
return {
|
||||||
|
beginTime: value[0],
|
||||||
|
endTime: value[1],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '岗位名称',
|
||||||
|
dataIndex: 'jobTitle',
|
||||||
|
valueType: 'text',
|
||||||
|
align: 'center',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '最小薪资(元/月)',
|
||||||
|
dataIndex: 'minSalary',
|
||||||
|
valueType: 'text',
|
||||||
|
align: 'center',
|
||||||
|
hideInSearch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '最大薪资(元/月)',
|
||||||
|
dataIndex: 'maxSalary',
|
||||||
|
valueType: 'text',
|
||||||
|
align: 'center',
|
||||||
|
hideInSearch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '单位名称',
|
||||||
|
dataIndex: 'companyName',
|
||||||
|
valueType: 'text',
|
||||||
|
align: 'center',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '学历要求',
|
||||||
|
dataIndex: 'education',
|
||||||
|
valueType: 'select',
|
||||||
|
align: 'center',
|
||||||
|
valueEnum: educationEnum,
|
||||||
|
render: (_, record) => {
|
||||||
|
return <DictTag enums={educationEnum} value={record.education} />;
|
||||||
|
},
|
||||||
|
hideInSearch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '经验要求',
|
||||||
|
dataIndex: 'experience',
|
||||||
|
valueType: 'select',
|
||||||
|
align: 'center',
|
||||||
|
valueEnum: experienceEnum,
|
||||||
|
render: (_, record) => {
|
||||||
|
return <DictTag enums={experienceEnum} value={record.experience} />;
|
||||||
|
},
|
||||||
|
hideInSearch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '是否发布',
|
||||||
|
dataIndex: 'isPublish',
|
||||||
|
valueType: 'select',
|
||||||
|
align: 'center',
|
||||||
|
valueEnum: isPublishEnum,
|
||||||
|
render: (_, record) => {
|
||||||
|
return <DictTag enums={isPublishEnum} value={record.isPublish} />;
|
||||||
|
},
|
||||||
|
hideInSearch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '招聘人数',
|
||||||
|
dataIndex: 'vacancies',
|
||||||
|
valueType: 'text',
|
||||||
|
align: 'center',
|
||||||
|
hideInSearch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '浏览量',
|
||||||
|
dataIndex: 'view',
|
||||||
|
valueType: 'text',
|
||||||
|
align: 'center',
|
||||||
|
hideInSearch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
hideInSearch: true,
|
||||||
|
align: 'center',
|
||||||
|
dataIndex: 'jobId',
|
||||||
|
width: 100,
|
||||||
|
render: (jobId, record) => (
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
icon={<AlignLeftOutlined />}
|
||||||
|
onClick={() => {
|
||||||
|
setCurrentRow(record);
|
||||||
|
setDetailVisible(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
查看详情
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<Card
|
||||||
|
title={<span>📊 采集趋势分析</span>}
|
||||||
|
style={{
|
||||||
|
marginBottom: 16,
|
||||||
|
}}
|
||||||
|
extra={
|
||||||
|
<div>
|
||||||
|
<span>统计日期范围:</span>
|
||||||
|
<RangePicker
|
||||||
|
picker="date"
|
||||||
|
value={getPickerValue()}
|
||||||
|
onChange={handleDateRangeChange}
|
||||||
|
disabledDate={disabledDate}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div style={{ height: 300 }}>
|
||||||
|
<Line {...chartConfig} />
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<div style={{ background: '#fff', padding: 24, borderRadius: 8 }}>
|
||||||
|
<ProTable<API.ManagementList.Manage>
|
||||||
|
actionRef={actionRef}
|
||||||
|
formRef={formTableRef}
|
||||||
|
rowKey="jobId"
|
||||||
|
key="jobStatisticsList"
|
||||||
|
columns={columns}
|
||||||
|
request={(params) => {
|
||||||
|
return getCmsJobList(params as API.ManagementList.ListParams).then((res) => {
|
||||||
|
const result = {
|
||||||
|
data: res.rows,
|
||||||
|
total: res.total,
|
||||||
|
success: true,
|
||||||
|
};
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
search={{
|
||||||
|
labelWidth: 120,
|
||||||
|
}}
|
||||||
|
toolBarRender={false}
|
||||||
|
pagination={{
|
||||||
|
pageSize: 10,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DetailModal
|
||||||
|
open={detailVisible}
|
||||||
|
onCancel={() => {
|
||||||
|
setDetailVisible(false);
|
||||||
|
setCurrentRow(undefined);
|
||||||
|
}}
|
||||||
|
values={currentRow}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default JobStatistics;
|
||||||
@@ -182,7 +182,7 @@ function ResumeList() {
|
|||||||
hidden={!access.hasPerms('resumeLibrary:resumeList:view')}
|
hidden={!access.hasPerms('resumeLibrary:resumeList:view')}
|
||||||
onClick={() => handleViewDetail(userId)}
|
onClick={() => handleViewDetail(userId)}
|
||||||
>
|
>
|
||||||
查看简历
|
查看人才信息
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -51,3 +51,9 @@ export async function exportCmsJobCandidates(ids: string) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
export async function getJobTrend(params) {
|
||||||
|
return request(`/api/cms/jobTrend/list`, {
|
||||||
|
method: 'GET',
|
||||||
|
params: params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,6 +14,12 @@ export async function getCmsAppUser(userId?: string) {
|
|||||||
method: 'GET',
|
method: 'GET',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
export async function getBehaviorLog(params?:API.MobileUser.LogParams) {
|
||||||
|
return request<API.MobileUser.LogResult>(`/api/cms/behaviorLog/getList`, {
|
||||||
|
method: 'GET',
|
||||||
|
params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export async function exportCmsAppUserExport(params?: API.MobileUser.ListParams) {
|
export async function exportCmsAppUserExport(params?: API.MobileUser.ListParams) {
|
||||||
return downLoadXlsx(
|
return downLoadXlsx(
|
||||||
|
|||||||
17
src/types/mobileusers/list.d.ts
vendored
17
src/types/mobileusers/list.d.ts
vendored
@@ -35,4 +35,21 @@ declare namespace API.MobileUser {
|
|||||||
education?: string;
|
education?: string;
|
||||||
politicalAffiliation?: string;
|
politicalAffiliation?: string;
|
||||||
}
|
}
|
||||||
|
export interface LogParams {
|
||||||
|
userId?: string;
|
||||||
|
current?: string;
|
||||||
|
pageSize?: string;
|
||||||
|
}
|
||||||
|
export interface LogRow {
|
||||||
|
opTime?: string;
|
||||||
|
content?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LogResult {
|
||||||
|
total: number;
|
||||||
|
rows: LogRow[];
|
||||||
|
code: number;
|
||||||
|
msg: string;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user