企业屏蔽功能开发
Some checks failed
Node CI / build (14.x, macOS-latest) (push) Has been cancelled
Node CI / build (14.x, ubuntu-latest) (push) Has been cancelled
Node CI / build (14.x, windows-latest) (push) Has been cancelled
Node CI / build (16.x, macOS-latest) (push) Has been cancelled
Node CI / build (16.x, ubuntu-latest) (push) Has been cancelled
Node CI / build (16.x, windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
coverage CI / build (push) Has been cancelled
Node pnpm CI / build (16.x, macOS-latest) (push) Has been cancelled
Node pnpm CI / build (16.x, ubuntu-latest) (push) Has been cancelled
Node pnpm CI / build (16.x, windows-latest) (push) Has been cancelled
Some checks failed
Node CI / build (14.x, macOS-latest) (push) Has been cancelled
Node CI / build (14.x, ubuntu-latest) (push) Has been cancelled
Node CI / build (14.x, windows-latest) (push) Has been cancelled
Node CI / build (16.x, macOS-latest) (push) Has been cancelled
Node CI / build (16.x, ubuntu-latest) (push) Has been cancelled
Node CI / build (16.x, windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
coverage CI / build (push) Has been cancelled
Node pnpm CI / build (16.x, macOS-latest) (push) Has been cancelled
Node pnpm CI / build (16.x, ubuntu-latest) (push) Has been cancelled
Node pnpm CI / build (16.x, windows-latest) (push) Has been cancelled
This commit is contained in:
@@ -88,6 +88,10 @@ export default [
|
||||
path: '/job-portal/personal-center/complaints', // 我的投诉
|
||||
component: './JobPortal/PersonalCenter/Complaints',
|
||||
},
|
||||
{
|
||||
path: '/job-portal/personal-center/blocked-companies', // 屏蔽企业列表
|
||||
component: './JobPortal/PersonalCenter/BlockedCompanies',
|
||||
},
|
||||
{
|
||||
path: '/job-portal/message', // 消息通知页面
|
||||
component: './JobPortal/Message',
|
||||
|
||||
@@ -128,6 +128,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
.company-block-action {
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.company-section-divider {
|
||||
margin: 18px 0 16px;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@ import {
|
||||
Progress,
|
||||
Divider,
|
||||
Avatar,
|
||||
message
|
||||
message,
|
||||
Modal
|
||||
} from 'antd';
|
||||
import {
|
||||
ArrowLeftOutlined,
|
||||
@@ -23,7 +24,8 @@ import {
|
||||
TrophyOutlined,
|
||||
FireOutlined,
|
||||
StarOutlined,
|
||||
FlagOutlined
|
||||
FlagOutlined,
|
||||
StopOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { Radar } from '@ant-design/charts';
|
||||
import JobPortalHeader from '@/components/JobPortalHeader';
|
||||
@@ -33,7 +35,7 @@ import {
|
||||
getEmptyCompetitivenessRadar,
|
||||
} from '@/services/jobportal/competitiveness';
|
||||
import { isJobPortalLoggedIn } from '@/utils/jobPortalAuth';
|
||||
import { favoriteJob, unfavoriteJob, applyJob, browseJob, getJobDetail } from '@/services/jobportal/user';
|
||||
import { favoriteJob, unfavoriteJob, applyJob, browseJob, getJobDetail, blockCompany } from '@/services/jobportal/user';
|
||||
import { ensureJobPortalLogin, requireJobPortalUserId } from '@/utils/jobPortalAuth';
|
||||
import { getDictLabel, resolveIndustryLabel } from '@/utils/jobPortalDict';
|
||||
import JobComplaintModal from '@/components/JobComplaintModal';
|
||||
@@ -444,6 +446,37 @@ const JobDetailPage: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleBlockCompany = async () => {
|
||||
const companyId = originalJobData?.companyId || originalJobData?.companyVo?.companyId;
|
||||
if (!companyId) {
|
||||
message.error('未获取到企业信息');
|
||||
return;
|
||||
}
|
||||
const userId = await requireJobPortalUserId('屏蔽企业');
|
||||
if (!userId) {
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '屏蔽该企业',
|
||||
content: '确定要屏蔽该企业吗?屏蔽后将不再收到该企业的招聘信息。',
|
||||
okText: '确定屏蔽',
|
||||
cancelText: '取消',
|
||||
okButtonProps: { danger: true },
|
||||
onOk: async () => {
|
||||
try {
|
||||
const res = await blockCompany({ userId, companyId });
|
||||
if (res?.code === 200) {
|
||||
message.success('已屏蔽该企业');
|
||||
} else {
|
||||
message.error(res?.msg || '屏蔽失败,请稍后重试');
|
||||
}
|
||||
} catch {
|
||||
message.error('操作失败,请稍后重试');
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="job-detail-page">
|
||||
<JobPortalHeader showSearch={false} showHotJobs={false} />
|
||||
@@ -551,6 +584,15 @@ const JobDetailPage: React.FC = () => {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="company-block-action">
|
||||
<Button
|
||||
danger
|
||||
icon={<StopOutlined />}
|
||||
onClick={handleBlockCompany}
|
||||
>
|
||||
屏蔽该企业
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Divider className="company-section-divider" />
|
||||
<div className="company-description-section">
|
||||
|
||||
@@ -317,6 +317,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
.company-block-action {
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.company-section-divider {
|
||||
margin: 18px 0 16px;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@ import {
|
||||
Button,
|
||||
Select,
|
||||
Input,
|
||||
message
|
||||
message,
|
||||
Modal
|
||||
} from 'antd';
|
||||
import {
|
||||
EnvironmentOutlined,
|
||||
@@ -21,12 +22,13 @@ import {
|
||||
FlagOutlined,
|
||||
ArrowLeftOutlined,
|
||||
SearchOutlined,
|
||||
TrophyOutlined
|
||||
TrophyOutlined,
|
||||
StopOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { history, useLocation } from '@umijs/max';
|
||||
import { getJobList, getJobRecommend } from '@/services/common/jobTitle';
|
||||
import JobPortalHeader from '@/components/JobPortalHeader';
|
||||
import { favoriteJob, unfavoriteJob, applyJob, browseJob } from '@/services/jobportal/user';
|
||||
import { favoriteJob, unfavoriteJob, applyJob, browseJob, blockCompany } from '@/services/jobportal/user';
|
||||
import { ensureJobPortalLogin, requireJobPortalUserId } from '@/utils/jobPortalAuth';
|
||||
import { getDictValueEnum } from '@/services/system/dict';
|
||||
import { getDictLabel, findTreeLabelById, resolveIndustryLabel } from '@/utils/jobPortalDict';
|
||||
@@ -525,6 +527,37 @@ const JobListPage: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleBlockCompany = async () => {
|
||||
const companyId = selectedJob?.companyId || selectedJob?.companyVo?.companyId;
|
||||
if (!companyId) {
|
||||
message.error('未获取到企业信息');
|
||||
return;
|
||||
}
|
||||
const userId = await requireJobPortalUserId('屏蔽企业');
|
||||
if (!userId) {
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '屏蔽该企业',
|
||||
content: '确定要屏蔽该企业吗?屏蔽后将不再收到该企业的招聘信息。',
|
||||
okText: '确定屏蔽',
|
||||
cancelText: '取消',
|
||||
okButtonProps: { danger: true },
|
||||
onOk: async () => {
|
||||
try {
|
||||
const res = await blockCompany({ userId, companyId });
|
||||
if (res?.code === 200) {
|
||||
message.success('已屏蔽该企业');
|
||||
} else {
|
||||
message.error(res?.msg || '屏蔽失败,请稍后重试');
|
||||
}
|
||||
} catch {
|
||||
message.error('操作失败,请稍后重试');
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 处理搜索功能(服务端搜索,不再做客户端过滤)
|
||||
const handleSearch = async () => {
|
||||
const term = searchValue.trim();
|
||||
@@ -975,6 +1008,15 @@ const JobListPage: React.FC = () => {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="company-block-action">
|
||||
<Button
|
||||
danger
|
||||
icon={<StopOutlined />}
|
||||
onClick={handleBlockCompany}
|
||||
>
|
||||
屏蔽该企业
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Divider className="company-section-divider" />
|
||||
<div className="company-description-section">
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
@import '../../theme.less';
|
||||
|
||||
.blocked-companies-page {
|
||||
min-height: 100vh;
|
||||
background: @jp-bg-page;
|
||||
padding-bottom: 40px;
|
||||
|
||||
.blocked-companies-container {
|
||||
.jp-page-container();
|
||||
margin-top: 20px;
|
||||
padding: 0 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.back-btn {
|
||||
flex-shrink: 0;
|
||||
color: @jp-text-secondary;
|
||||
padding: 0 8px;
|
||||
|
||||
&:hover {
|
||||
color: @jp-primary;
|
||||
}
|
||||
}
|
||||
|
||||
.page-title {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: @jp-text-primary;
|
||||
}
|
||||
}
|
||||
|
||||
.list-card {
|
||||
.jp-card-base();
|
||||
|
||||
.blocked-item {
|
||||
padding: 16px 20px;
|
||||
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1px solid @jp-border;
|
||||
}
|
||||
|
||||
.company-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #fff2f0;
|
||||
border-radius: @jp-radius;
|
||||
color: #ff4d4f;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.company-name {
|
||||
font-size: 15px;
|
||||
color: @jp-text-primary;
|
||||
}
|
||||
|
||||
.block-time {
|
||||
font-size: 13px;
|
||||
color: @jp-text-muted;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.blocked-companies-page .blocked-companies-container {
|
||||
padding: 0 12px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
}
|
||||
153
src/pages/JobPortal/PersonalCenter/BlockedCompanies/index.tsx
Normal file
153
src/pages/JobPortal/PersonalCenter/BlockedCompanies/index.tsx
Normal file
@@ -0,0 +1,153 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Card,
|
||||
List,
|
||||
Typography,
|
||||
Button,
|
||||
message,
|
||||
Modal,
|
||||
Spin,
|
||||
Empty
|
||||
} from 'antd';
|
||||
import {
|
||||
StopOutlined,
|
||||
DeleteOutlined,
|
||||
ArrowLeftOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { history } from '@umijs/max';
|
||||
import JobPortalHeader from '@/components/JobPortalHeader';
|
||||
import { getBlockedCompanyList } from '@/services/jobportal/user';
|
||||
import { request } from '@umijs/max';
|
||||
import './index.less';
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
|
||||
interface BlockedCompany {
|
||||
id: number;
|
||||
userId: number;
|
||||
companyId: number;
|
||||
companyName: string;
|
||||
name: string;
|
||||
createTime: string;
|
||||
updateTime: string;
|
||||
}
|
||||
|
||||
const BlockedCompaniesPage: React.FC = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [dataSource, setDataSource] = useState<BlockedCompany[]>([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
|
||||
const fetchList = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await getBlockedCompanyList({ pageNum: 1, pageSize: 100 });
|
||||
if (res?.code === 200) {
|
||||
setDataSource((res as any)?.rows || []);
|
||||
setTotal((res as any)?.total || 0);
|
||||
} else {
|
||||
message.error(res?.msg || '获取屏蔽企业列表失败');
|
||||
}
|
||||
} catch {
|
||||
message.error('获取屏蔽企业列表失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchList();
|
||||
}, []);
|
||||
|
||||
const handleCancelBlock = (record: BlockedCompany) => {
|
||||
Modal.confirm({
|
||||
title: '取消屏蔽',
|
||||
content: `确定要取消屏蔽"${record.companyName}"吗?`,
|
||||
okText: '确定取消',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
try {
|
||||
const res = await request('/api/cms/blockCompany/' + record.id, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
if (res?.code === 200) {
|
||||
message.success('已取消屏蔽');
|
||||
setDataSource(prev => prev.filter(item => item.id !== record.id));
|
||||
setTotal(prev => prev - 1);
|
||||
} else {
|
||||
message.error(res?.msg || '取消屏蔽失败');
|
||||
}
|
||||
} catch {
|
||||
message.error('操作失败,请稍后重试');
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="blocked-companies-page">
|
||||
<JobPortalHeader showSearch={false} showHotJobs={false} />
|
||||
|
||||
<div className="blocked-companies-container">
|
||||
<div className="page-header">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ArrowLeftOutlined />}
|
||||
onClick={() => history.back()}
|
||||
className="back-btn"
|
||||
>
|
||||
返回
|
||||
</Button>
|
||||
<Title level={4} className="page-title">屏蔽企业</Title>
|
||||
</div>
|
||||
|
||||
<Card className="list-card">
|
||||
<Spin spinning={loading}>
|
||||
{dataSource.length === 0 && !loading ? (
|
||||
<Empty description="暂无屏蔽企业" />
|
||||
) : (
|
||||
<List
|
||||
dataSource={dataSource}
|
||||
renderItem={(item) => (
|
||||
<List.Item
|
||||
className="blocked-item"
|
||||
actions={[
|
||||
<Button
|
||||
key="cancel"
|
||||
type="link"
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => handleCancelBlock(item)}
|
||||
>
|
||||
取消屏蔽
|
||||
</Button>
|
||||
]}
|
||||
>
|
||||
<List.Item.Meta
|
||||
avatar={
|
||||
<div className="company-avatar">
|
||||
<StopOutlined />
|
||||
</div>
|
||||
}
|
||||
title={
|
||||
<Text strong className="company-name">
|
||||
{item.companyName}
|
||||
</Text>
|
||||
}
|
||||
description={
|
||||
<Text type="secondary" className="block-time">
|
||||
屏蔽时间:{item.createTime}
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</Spin>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BlockedCompaniesPage;
|
||||
@@ -97,6 +97,16 @@
|
||||
}
|
||||
|
||||
.stats-section {
|
||||
.stats-row {
|
||||
flex-wrap: nowrap;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.stat-col {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
|
||||
@@ -138,6 +138,7 @@ const PersonalCenter: React.FC = () => {
|
||||
footprints: 0,
|
||||
appointments: 0,
|
||||
complaints: 0,
|
||||
blockedCompanies: 0,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -151,6 +152,7 @@ const PersonalCenter: React.FC = () => {
|
||||
footprints: Number(response.data.yzj) || 0,
|
||||
appointments: Number(response.data.yyy) || 0,
|
||||
complaints: Number(response.data.yts) || 0,
|
||||
blockedCompanies: Number(response.data.ypbqys) || 0,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -269,8 +271,8 @@ const PersonalCenter: React.FC = () => {
|
||||
<Divider className="panel-divider" />
|
||||
|
||||
<section className="stats-section">
|
||||
<Row gutter={0}>
|
||||
<Col span={6}>
|
||||
<Row gutter={0} className="stats-row">
|
||||
<Col className="stat-col">
|
||||
<div
|
||||
className="stat-item"
|
||||
onClick={() => history.push('/job-portal/personal-center/applications')}
|
||||
@@ -279,7 +281,7 @@ const PersonalCenter: React.FC = () => {
|
||||
<div className="stat-label">投递</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Col className="stat-col">
|
||||
<div
|
||||
className="stat-item"
|
||||
onClick={() => history.push('/job-portal/personal-center/favorites')}
|
||||
@@ -288,7 +290,7 @@ const PersonalCenter: React.FC = () => {
|
||||
<div className="stat-label">收藏</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Col className="stat-col">
|
||||
<div
|
||||
className="stat-item"
|
||||
onClick={() => history.push('/job-portal/personal-center/footprints')}
|
||||
@@ -297,7 +299,7 @@ const PersonalCenter: React.FC = () => {
|
||||
<div className="stat-label">足迹</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Col className="stat-col">
|
||||
<div
|
||||
className="stat-item"
|
||||
onClick={() => history.push('/job-portal/personal-center/complaints')}
|
||||
@@ -306,6 +308,15 @@ const PersonalCenter: React.FC = () => {
|
||||
<div className="stat-label">投诉</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col className="stat-col">
|
||||
<div
|
||||
className="stat-item stat-item-last"
|
||||
onClick={() => history.push('/job-portal/personal-center/blocked-companies')}
|
||||
>
|
||||
<div className="stat-number">{statistics.blockedCompanies || 0}</div>
|
||||
<div className="stat-label">屏蔽企业</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -182,3 +182,22 @@ export async function submitJobComplaint(params: {
|
||||
});
|
||||
}
|
||||
|
||||
/** 屏蔽企业 POST /cms/blockCompany */
|
||||
export async function blockCompany(params: { userId: number; companyId: number }) {
|
||||
return request<API.Result>('/api/cms/blockCompany', {
|
||||
method: 'POST',
|
||||
data: params,
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取屏蔽企业列表 GET /cms/blockCompany/list */
|
||||
export async function getBlockedCompanyList(params?: { pageNum?: number; pageSize?: number }) {
|
||||
return request<API.Result>('/api/cms/blockCompany/list', {
|
||||
method: 'GET',
|
||||
params: {
|
||||
pageNum: params?.pageNum || 1,
|
||||
pageSize: params?.pageSize || 10,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
1
src/types/jobportal/user.d.ts
vendored
1
src/types/jobportal/user.d.ts
vendored
@@ -73,6 +73,7 @@ declare namespace API {
|
||||
yzj?: string; // 足迹
|
||||
yyy?: string; // 预约
|
||||
yts?: string; // 投诉
|
||||
ypbqys?: string; // 屏蔽企业数
|
||||
}
|
||||
|
||||
export interface AppUserYhscResult {
|
||||
|
||||
Reference in New Issue
Block a user