企业屏蔽功能开发
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:
francis-fh
2026-07-12 10:49:52 +08:00
parent 0cff24aab1
commit 9f5c3c2c10
11 changed files with 384 additions and 11 deletions

View File

@@ -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',

View File

@@ -128,6 +128,12 @@
}
}
.company-block-action {
flex-shrink: 0;
margin-left: auto;
align-self: center;
}
.company-section-divider {
margin: 18px 0 16px;
}

View File

@@ -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">

View File

@@ -317,6 +317,12 @@
}
}
.company-block-action {
flex-shrink: 0;
margin-left: auto;
align-self: center;
}
.company-section-divider {
margin: 18px 0 16px;
}

View File

@@ -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">

View File

@@ -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;
}
}

View 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;

View File

@@ -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;

View File

@@ -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>

View File

@@ -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,
},
});
}

View File

@@ -73,6 +73,7 @@ declare namespace API {
yzj?: string; // 足迹
yyy?: string; // 预约
yts?: string; // 投诉
ypbqys?: string; // 屏蔽企业数
}
export interface AppUserYhscResult {