Merge branch 'master' of http://124.243.245.42:3000/sh/shz-admin
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-23 12:32:33 +08:00
50 changed files with 4767 additions and 603 deletions

View File

@@ -0,0 +1,68 @@
import { request } from '@umijs/max';
import { normalizeNewsContent } from '@/utils/newsImageUrl';
const BASE_URL = '/api/cms/newsInfo';
export async function getNewsInfoList(params?: API.NewsInfo.NewsInfoListParams) {
return request<API.NewsInfo.NewsInfoPageResult>(`${BASE_URL}/list`, {
method: 'GET',
params,
});
}
export async function getNewsInfoDetail(id: number) {
return request<API.NewsInfo.NewsInfoDetailResult>(`${BASE_URL}/${id}`, {
method: 'GET',
});
}
export async function addNewsInfo(data: API.NewsInfo.NewsInfoPayload) {
return request<API.Result>(BASE_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json;charset=UTF-8' },
data: { ...data, content: normalizeNewsContent(data.content) },
});
}
export async function updateNewsInfo(data: API.NewsInfo.NewsInfoPayload) {
return request<API.Result>(BASE_URL, {
method: 'PUT',
headers: { 'Content-Type': 'application/json;charset=UTF-8' },
data: { ...data, content: normalizeNewsContent(data.content) },
});
}
export async function deleteNewsInfo(ids: string) {
return request<API.Result>(`${BASE_URL}/${ids}`, {
method: 'DELETE',
});
}
export async function changeNewsInfoStatus(id: number, status: '0' | '1') {
return request<API.Result>(`${BASE_URL}/changeStatus`, {
method: 'PUT',
params: { id, status },
});
}
export async function uploadNewsImage(file: File) {
const formData = new FormData();
formData.append('file', file);
return request<API.NewsInfo.NewsInfoUploadResult>('/api/common/upload', {
method: 'POST',
data: formData,
});
}
export async function getPublishedNewsInfoList(params?: API.NewsInfo.NewsInfoListParams) {
return request<API.NewsInfo.NewsInfoPageResult>('/api/app/newsInfo/list', {
method: 'GET',
params,
});
}
export async function getPublishedNewsInfoDetail(id: number) {
return request<API.NewsInfo.NewsInfoDetailResult>(`/api/app/newsInfo/${id}`, {
method: 'GET',
});
}

View File

@@ -58,3 +58,22 @@ export async function getCmsCompanyDetail(companyId: number | string) {
method: 'GET',
});
}
/** 获取当前企业用户关联的企业信息 */
export async function getCmsCurrentCompany() {
return request<{
code: number;
msg?: string;
data?: API.CompanyList.Company;
}>('/api/cms/company/current', {
method: 'GET',
});
}
/** 修改当前企业用户关联的企业信息 */
export async function putCmsCurrentCompany(params: API.CompanyList.Params) {
return request<API.CompanyList.CompanyListResult>('/api/cms/company/current', {
method: 'PUT',
data: params,
});
}

View File

@@ -0,0 +1,240 @@
import { request } from '@umijs/max';
export type JobFairChannel = 'online' | 'outdoor';
/** 招聘会公共列表项;线上与户外接口统一为该结构。 */
export interface PortalJobFairItem {
jobFairId: string;
jobFairTitle: string;
jobFairAddress?: string;
/** 线上接口通常为 1/2户外接口固定返回 outdoor。 */
jobFairType?: string;
jobFairStartTime?: string;
jobFairEndTime?: string;
jobFairStatus?: string;
jobFairHostUnit?: string;
jobFairOrganizeUnit?: string;
jobFairIntroduction?: string;
jobFairProcess?: string;
jobFairPhone?: string;
jobFairImage?: string;
enterpriseNum?: string;
boothNum?: string;
/** 户外招聘会展位图中的实际摊位数量 */
boothMapCount?: number;
divisionName?: string;
jobFairCategory?: string;
jobFairRegion?: string;
jobFairVenueName?: string;
}
export interface PortalJobFairPageParams {
pageNum?: number;
pageSize?: number;
jobFairTitle?: string;
/** 招聘会举办日期,格式 yyyy-MM-dd。 */
zphjbsj?: string;
}
export interface PortalJobFairPageData {
total: number;
list: PortalJobFairItem[];
pageNum?: number;
pageSize?: number;
pages?: number;
}
export interface PortalJobFairPageResult {
code: number;
msg?: string;
data?: PortalJobFairPageData;
}
export interface PortalJobFairListResult {
code: number;
msg?: string;
data?: PortalJobFairItem[];
}
export interface PortalJobFairDatesResult {
code: number;
msg?: string;
data?: string[];
}
interface PortalOutdoorFairDetailData extends Partial<PortalJobFairItem> {
id?: number;
title?: string;
hostUnit?: string;
fairType?: string;
region?: string;
venueName?: string;
address?: string;
boothCount?: number;
boothMapCount?: number;
holdTime?: string;
endTime?: string;
photoUrl?: string;
fairIntroduction?: string;
fairProcess?: string;
}
interface PortalOutdoorFairDetailResult {
code: number;
msg?: string;
/**
* 旧接口返回 { fair: {...} },当前公共接口直接返回招聘会对象。
* 两种格式均保留兼容,避免接口升级后户外详情页显示为空。
*/
data?: PortalOutdoorFairDetailData | { fair?: PortalOutdoorFairDetailData };
}
function isOutdoorFairDetailWrapper(
data: NonNullable<PortalOutdoorFairDetailResult['data']>,
): data is { fair?: PortalOutdoorFairDetailData } {
return 'fair' in data;
}
export interface PortalJobFairDetailResult {
code: number;
msg?: string;
data?: PortalJobFairItem;
}
/** 招聘会关联岗位;由公共接口中的企业岗位列表展平得到。 */
export interface PortalJobFairPosition {
jobId?: number | string;
fairRelationId?: string;
jobTitle?: string;
minSalary?: number;
maxSalary?: number;
education?: string;
experience?: string;
companyId?: number | string;
companyName?: string;
jobLocation?: string;
jobAddress?: string;
vacancies?: number | string;
isUrgent?: number;
}
interface PortalJobFairCompanyWithJobs {
companyId?: number | string;
companyName?: string;
jobInfoList?: PortalJobFairPosition[];
}
interface PortalJobFairCompaniesWithJobsResult {
code: number;
msg?: string;
data?: PortalJobFairCompanyWithJobs[];
}
export interface PortalJobFairPositionResult {
code: number;
msg?: string;
data?: PortalJobFairPosition[];
}
const FAIR_API = {
online: '/app/jobfair/public/jobfair',
outdoor: '/app/outdoor-fair',
} as const;
/** 获取线上或户外招聘会分页列表。 */
export async function getPortalJobFairPage(
channel: JobFairChannel,
params?: PortalJobFairPageParams,
) {
return request<PortalJobFairPageResult>(`${FAIR_API[channel]}/page`, {
method: 'GET',
params,
});
}
/** 获取有招聘会的日期,用于与小程序一致的日期筛选提示。 */
export async function getPortalJobFairDates(channel: JobFairChannel) {
return request<PortalJobFairDatesResult>(`${FAIR_API[channel]}/dates`, {
method: 'GET',
});
}
/** 获取当前月招聘会摘要。 */
export async function getPortalCurrentMonthJobFairs(channel: JobFairChannel) {
return request<PortalJobFairListResult>(`${FAIR_API[channel]}/currentMonth`, {
method: 'GET',
});
}
/** 获取当前季度招聘会摘要。 */
export async function getPortalCurrentQuarterJobFairs(channel: JobFairChannel) {
return request<PortalJobFairListResult>(`${FAIR_API[channel]}/currentQuarter`, {
method: 'GET',
});
}
/** 获取招聘会详情,并把户外招聘会详情转换为与列表一致的公共字段。 */
export async function getPortalJobFairDetail(channel: JobFairChannel, jobFairId: string) {
if (channel === 'online') {
return request<PortalJobFairDetailResult>(`${FAIR_API.online}/detail`, {
method: 'GET',
params: { jobFairId },
});
}
const response = await request<PortalOutdoorFairDetailResult>(
`${FAIR_API.outdoor}/${encodeURIComponent(jobFairId)}`,
{ method: 'GET' },
);
const payload = response.data;
const fair = payload && (isOutdoorFairDetailWrapper(payload) ? payload.fair : payload);
if (!fair) {
return { code: response.code, msg: response.msg } as PortalJobFairDetailResult;
}
return {
code: response.code,
msg: response.msg,
data: {
jobFairId: fair.jobFairId || String(fair.id ?? jobFairId),
jobFairTitle: fair.jobFairTitle || fair.title || '线下招聘会',
jobFairAddress: fair.jobFairAddress || fair.address || fair.venueName,
jobFairType: fair.jobFairType || fair.fairType || 'outdoor',
jobFairStartTime: fair.jobFairStartTime || fair.holdTime,
jobFairEndTime: fair.jobFairEndTime || fair.endTime,
jobFairHostUnit: fair.jobFairHostUnit || fair.hostUnit,
jobFairOrganizeUnit: fair.jobFairOrganizeUnit,
jobFairIntroduction: fair.jobFairIntroduction || fair.fairIntroduction,
jobFairProcess: fair.jobFairProcess || fair.fairProcess,
jobFairPhone: fair.jobFairPhone,
jobFairImage: fair.jobFairImage || fair.photoUrl,
boothNum:
fair.boothNum || (fair.boothCount === undefined ? undefined : String(fair.boothCount)),
boothMapCount: fair.boothMapCount,
jobFairRegion: fair.jobFairRegion || fair.region,
jobFairVenueName: fair.jobFairVenueName || fair.venueName,
},
} as PortalJobFairDetailResult;
}
/** 获取线上招聘会的关联岗位,并将企业分组数据转换为岗位卡片数据。 */
export async function getPortalJobFairPositions(jobFairId: string) {
const response = await request<PortalJobFairCompaniesWithJobsResult>(
`${FAIR_API.online}/enterprises-with-jobs-by-job-fair-id`,
{
method: 'GET',
params: { jobFairId },
},
);
return {
code: response.code,
msg: response.msg,
data: (response.data || []).flatMap((company) =>
(company.jobInfoList || []).map((position) => ({
...position,
companyId: position.companyId || company.companyId,
companyName: position.companyName || company.companyName,
})),
),
} as PortalJobFairPositionResult;
}

View File

@@ -1,4 +1,5 @@
import { request } from '@umijs/max';
import type { VenueBoothItem } from './venueInfo';
/** 户外招聘会列表项 */
export interface OutdoorFairItem {
@@ -19,6 +20,8 @@ export interface OutdoorFairItem {
address: string;
/** 展位数量 */
boothCount: number;
/** 已预定摊位数 */
reservedBoothCount?: number;
/** 举办时间 */
holdTime: string;
/** 截止时间 */
@@ -31,6 +34,14 @@ export interface OutdoorFairItem {
onlineApply: boolean;
/** 招聘会照片 */
photoUrl: string;
/** 招聘会简介 */
fairIntroduction?: string;
/** 招聘会整体流程 */
fairProcess?: string;
/** 企业参加招聘会需携带的资料 */
requiredMaterials?: string;
/** 企业到场须知 */
attendanceNotes?: string;
/** 当前企业ID */
companyId?: number;
/** 当前企业名称 */
@@ -86,6 +97,16 @@ export interface OutdoorFairForm {
applyEndTime: string;
onlineApply: boolean;
photoUrl: string;
/** 招聘会简介 */
fairIntroduction?: string;
/** 招聘会整体流程 */
fairProcess?: string;
/** 企业参加招聘会需携带的资料 */
requiredMaterials?: string;
/** 企业到场须知 */
attendanceNotes?: string;
/** 更换场地时确认清空旧展位图与预定,并按新模板重建 */
resetBoothMap?: boolean;
}
/** 通用响应 */
@@ -113,6 +134,12 @@ export interface OutdoorFairCompanyQrCodeInfo {
qrCodeContent: string;
}
/** 企业审核通过后可读取的只读展位图。 */
export interface OutdoorFairCompanyBoothMap {
currentCompanyId: number;
booths: VenueBoothItem[];
}
/** 户外招聘会字典新增表单 */
export interface OutdoorFairDictForm {
dictType: 'outdoor_fair_type' | 'outdoor_fair_region';
@@ -157,6 +184,17 @@ export async function getOutdoorFairInfo(id: number) {
});
}
/**
* 当前企业查看户外招聘会展位图。
* 服务端会校验当前企业已报名且审核通过,接口只提供读取能力。
*/
export async function getCurrentCompanyOutdoorFairBoothMap(fairId: number) {
return request<{ code: number; msg?: string; data?: OutdoorFairCompanyBoothMap }>(
`${CMS_OUTDOOR_FAIR_BASE}/${fairId}/company/booth-map`,
{ method: 'GET' },
);
}
/** 招聘会统计-时间桶序列项(曲线图用) */
export interface OutdoorFairStatisticsSeriesItem {
time: string;

View File

@@ -170,6 +170,22 @@ export async function addVenueInfoDictOption(data: VenueInfoDictForm) {
});
}
/** 查询场地的展位图模板。模板不含企业及预定状态。 */
export async function getVenueBoothMap(venueId: number) {
return request<{ code: number; msg?: string; data: VenueBoothItem[] }>(
`${CMS_VENUE_INFO_BASE}/${venueId}/booth-map`,
{ method: 'GET' },
);
}
/** 完整覆盖保存场地的展位图模板。 */
export async function saveVenueBoothMap(venueId: number, data: VenueBoothItem[]) {
return request<VenueInfoCommonResult>(`${CMS_VENUE_INFO_BASE}/${venueId}/booths`, {
method: 'POST',
data,
});
}
export async function getOutdoorFairBoothMap(fairId: number) {
return request<{ code: number; msg?: string; data: VenueBoothItem[] }>(
`${CMS_VENUE_INFO_BASE}/fair/${fairId}/booth-map`,

View File

@@ -6,6 +6,11 @@ import React, { lazy } from 'react';
let remoteMenu: any = null;
// Remote CMS menus are returned by the backend at runtime. Keep newly added
// pages with an explicit import so production webpack builds cannot tree-shake
// them out of the dynamic import context before the remote menu is available.
const newsInfoRemoteComponent = lazy(() => import('@/pages/NewsInfo/index'));
export function getRemoteMenu() {
return remoteMenu;
}
@@ -62,7 +67,11 @@ function patchRouteItems(route: any, menu: any, parentPath: string) {
route.children = [];
}
const newRoute = {
element: React.createElement(lazy(() => import('@/pages/' + path))),
element: React.createElement(
path === 'NewsInfo/index.tsx'
? newsInfoRemoteComponent
: lazy(() => import('@/pages/' + path)),
),
path: parentPath + menuItem.path,
}
route.children.push(newRoute);