diff --git a/.codegraph/daemon.pid b/.codegraph/daemon.pid index a11dbe7..f1c1c84 100644 --- a/.codegraph/daemon.pid +++ b/.codegraph/daemon.pid @@ -1,6 +1,6 @@ { - "pid": 15780, + "pid": 17132, "version": "0.9.9", "socketPath": "\\\\.\\pipe\\codegraph-ae68fa88b3ffb91a", - "startedAt": 1781687361498 + "startedAt": 1781719809693 } diff --git a/config/routes.ts b/config/routes.ts index ad448ae..8df8495 100644 --- a/config/routes.ts +++ b/config/routes.ts @@ -205,17 +205,42 @@ export default [ { name: '公共招聘会列表', path: '/jobfair/public-job-fair', - component: './Jobfair/PublicJobFair', + component: './Jobfair/Publicjobfair', }, { name: '招聘会详情', path: '/jobfair/public-job-fair/detail', - component: './Jobfair/PublicJobFair/Detail', + component: './Jobfair/Publicjobfair/Detail', }, { name: '报名人员', path: '/jobfair/public-job-fair/signups', - component: './Jobfair/PublicJobFair/Signups', + component: './Jobfair/Publicjobfair/Signups', + }, + { + name: '户外招聘会管理', + path: '/jobfair/outdoor-fair', + component: './Jobfair/Outdoorfair', + }, + { + name: '户外招聘会详情', + path: '/jobfair/outdoor-fair/detail', + component: './Jobfair/Outdoorfair/Detail', + }, + { + name: '跨域联合招聘会管理', + path: '/jobfair/cross-city-fair', + component: './Jobfair/Crosscityfair', + }, + { + name: '室内固定摊位招聘会管理', + path: '/jobfair/indoor-job-fair', + component: './Jobfair/Indoorjobfair', + }, + { + name: '室内固定摊位招聘会详情', + path: '/jobfair/indoor-job-fair/detail', + component: './Jobfair/Indoorjobfair/Detail', }, ], }, diff --git a/src/pages/Analysis/Industrytrend/components/chartcards.tsx b/src/pages/Analysis/Industrytrend/components/chartcards.tsx index 6460259..beb67e8 100644 --- a/src/pages/Analysis/Industrytrend/components/chartcards.tsx +++ b/src/pages/Analysis/Industrytrend/components/chartcards.tsx @@ -52,7 +52,7 @@ export const IndustryTrendCard = ({ export const AreaAnalysisCard = ({ loading, areaData, config }) => ( -); \ No newline at end of file +); diff --git a/src/pages/Jobfair/Crosscityfair/index.tsx b/src/pages/Jobfair/Crosscityfair/index.tsx new file mode 100644 index 0000000..2fbd139 --- /dev/null +++ b/src/pages/Jobfair/Crosscityfair/index.tsx @@ -0,0 +1,807 @@ +import React, { useState } from 'react'; +import { PageContainer } from '@ant-design/pro-components'; +import { + Tabs, + Card, + Row, + Col, + Statistic, + Table, + Tag, + Button, + Space, + Descriptions, + Typography, + message, + Tooltip, +} from 'antd'; +import { + EnvironmentOutlined, + StarOutlined, + StarFilled, + SendOutlined, + TeamOutlined, + BankOutlined, + FileTextOutlined, + RiseOutlined, + CheckCircleOutlined, + EyeOutlined, +} from '@ant-design/icons'; + +const { Title, Paragraph, Text } = Typography; + +// ==================== Mock Data ==================== + +/** 联盟城市 */ +const ALLIANCE_CITIES = [ + { key: 'wulumuqi', name: '乌鲁木齐市', code: '650100', region: '北疆' }, + { key: 'kelamayi', name: '克拉玛依市', code: '650200', region: '北疆' }, + { key: 'changji', name: '昌吉回族自治州', code: '652300', region: '北疆' }, + { key: 'hami', name: '哈密市', code: '650500', region: '东疆' }, + { key: 'tulufan', name: '吐鲁番市', code: '650400', region: '东疆' }, + { key: 'kashgar', name: '喀什地区', code: '653100', region: '南疆' }, + { key: 'akesu', name: '阿克苏地区', code: '652900', region: '南疆' }, + { key: 'yili', name: '伊犁哈萨克自治州', code: '654000', region: '北疆' }, +]; + +/** 各城市招聘岗位 */ +interface CityJob { + id: number; + cityName: string; + cityKey: string; + companyName: string; + jobTitle: string; + salary: string; + education: string; + experience: string; + vacancies: number; + postDate: string; + industry: string; + saved: boolean; +} + +const MOCK_CITY_JOBS: CityJob[] = [ + { id: 1, cityName: '乌鲁木齐市', cityKey: 'wulumuqi', companyName: '新疆特变电工股份有限公司', jobTitle: '电气工程师', salary: '8K-12K', education: '本科', experience: '3-5年', vacancies: 5, postDate: '2026-06-15', industry: '制造业', saved: false }, + { id: 2, cityName: '乌鲁木齐市', cityKey: 'wulumuqi', companyName: '新疆广汇实业投资集团', jobTitle: '财务主管', salary: '10K-15K', education: '本科', experience: '5年以上', vacancies: 2, postDate: '2026-06-14', industry: '金融/投资', saved: false }, + { id: 3, cityName: '乌鲁木齐市', cityKey: 'wulumuqi', companyName: '中国石油新疆油田分公司', jobTitle: '石油工程技术员', salary: '9K-14K', education: '本科', experience: '1-3年', vacancies: 10, postDate: '2026-06-13', industry: '能源/矿产', saved: true }, + { id: 4, cityName: '克拉玛依市', cityKey: 'kelamayi', companyName: '克拉玛依石化公司', jobTitle: '化工工艺工程师', salary: '10K-16K', education: '硕士', experience: '3-5年', vacancies: 3, postDate: '2026-06-16', industry: '石化', saved: false }, + { id: 5, cityName: '克拉玛依市', cityKey: 'kelamayi', companyName: '新疆油田公司勘探开发研究院', jobTitle: '地质研究员', salary: '12K-18K', education: '博士', experience: '不限', vacancies: 2, postDate: '2026-06-12', industry: '能源/矿产', saved: false }, + { id: 6, cityName: '克拉玛依市', cityKey: 'kelamayi', companyName: '克拉玛依职业技术学院', jobTitle: '机械专业教师', salary: '7K-10K', education: '硕士', experience: '1-3年', vacancies: 4, postDate: '2026-06-10', industry: '教育', saved: true }, + { id: 7, cityName: '昌吉回族自治州', cityKey: 'changji', companyName: '特变电工新疆新能源股份有限公司', jobTitle: '光伏系统工程师', salary: '8K-13K', education: '本科', experience: '3-5年', vacancies: 6, postDate: '2026-06-15', industry: '新能源', saved: false }, + { id: 8, cityName: '昌吉回族自治州', cityKey: 'changji', companyName: '新疆蓝山屯河化工股份有限公司', jobTitle: '高分子材料研发', salary: '9K-14K', education: '硕士', experience: '1-3年', vacancies: 3, postDate: '2026-06-11', industry: '新材料', saved: false }, + { id: 9, cityName: '哈密市', cityKey: 'hami', companyName: '哈密市城市建设投资集团', jobTitle: '工程项目经理', salary: '12K-18K', education: '本科', experience: '5年以上', vacancies: 2, postDate: '2026-06-14', industry: '建筑/工程', saved: false }, + { id: 10, cityName: '哈密市', cityKey: 'hami', companyName: '新疆哈密风电基地', jobTitle: '风电运维工程师', salary: '7K-11K', education: '大专', experience: '1-3年', vacancies: 8, postDate: '2026-06-13', industry: '新能源', saved: true }, + { id: 11, cityName: '吐鲁番市', cityKey: 'tulufan', companyName: '吐鲁番市旅游发展集团', jobTitle: '文旅策划专员', salary: '5K-8K', education: '本科', experience: '1-3年', vacancies: 3, postDate: '2026-06-12', industry: '旅游/文化', saved: false }, + { id: 12, cityName: '喀什地区', cityKey: 'kashgar', companyName: '喀什经济开发区投资集团', jobTitle: '国际贸易专员', salary: '6K-10K', education: '本科', experience: '1-3年', vacancies: 5, postDate: '2026-06-16', industry: '贸易/进出口', saved: false }, + { id: 13, cityName: '喀什地区', cityKey: 'kashgar', companyName: '喀什地区人民医院', jobTitle: '临床医生', salary: '8K-13K', education: '硕士', experience: '不限', vacancies: 15, postDate: '2026-06-10', industry: '医疗/卫生', saved: false }, + { id: 14, cityName: '阿克苏地区', cityKey: 'akesu', companyName: '阿克苏纺织工业城', jobTitle: '纺织技术主管', salary: '7K-10K', education: '本科', experience: '3-5年', vacancies: 4, postDate: '2026-06-09', industry: '纺织/服装', saved: true }, + { id: 15, cityName: '阿克苏地区', cityKey: 'akesu', companyName: '阿克苏苹果产业集团', jobTitle: '食品加工工程师', salary: '6K-9K', education: '本科', experience: '1-3年', vacancies: 6, postDate: '2026-06-08', industry: '食品加工', saved: false }, + { id: 16, cityName: '伊犁哈萨克自治州', cityKey: 'yili', companyName: '伊犁州霍尔果斯口岸管委会', jobTitle: '跨境物流经理', salary: '10K-15K', education: '本科', experience: '5年以上', vacancies: 1, postDate: '2026-06-15', industry: '物流/交通', saved: false }, + { id: 17, cityName: '伊犁哈萨克自治州', cityKey: 'yili', companyName: '伊犁州文旅集团', jobTitle: '景区运营管理', salary: '6K-9K', education: '大专', experience: '3-5年', vacancies: 3, postDate: '2026-06-07', industry: '旅游/文化', saved: false }, + { id: 18, cityName: '乌鲁木齐市', cityKey: 'wulumuqi', companyName: '新疆医科大学附属医院', jobTitle: '护士长', salary: '8K-11K', education: '本科', experience: '5年以上', vacancies: 3, postDate: '2026-06-16', industry: '医疗/卫生', saved: true }, +]; + +/** 已收藏/投递的岗位 */ +interface SavedJob { + id: number; + cityName: string; + companyName: string; + jobTitle: string; + salary: string; + savedDate: string; + deliverStatus: 'pending' | 'delivered' | 'interview' | 'offer'; + deliverDate?: string; +} + +const MOCK_SAVED_JOBS: SavedJob[] = [ + { id: 1, cityName: '乌鲁木齐市', companyName: '中国石油新疆油田分公司', jobTitle: '石油工程技术员', salary: '9K-14K', savedDate: '2026-06-15', deliverStatus: 'delivered', deliverDate: '2026-06-16' }, + { id: 2, cityName: '克拉玛依市', companyName: '克拉玛依职业技术学院', jobTitle: '机械专业教师', salary: '7K-10K', savedDate: '2026-06-14', deliverStatus: 'interview', deliverDate: '2026-06-17' }, + { id: 3, cityName: '哈密市', companyName: '新疆哈密风电基地', jobTitle: '风电运维工程师', salary: '7K-11K', savedDate: '2026-06-13', deliverStatus: 'offer', deliverDate: '2026-06-18' }, + { id: 4, cityName: '阿克苏地区', companyName: '阿克苏纺织工业城', jobTitle: '纺织技术主管', salary: '7K-10K', savedDate: '2026-06-12', deliverStatus: 'pending' }, + { id: 5, cityName: '乌鲁木齐市', companyName: '新疆医科大学附属医院', jobTitle: '护士长', salary: '8K-11K', savedDate: '2026-06-16', deliverStatus: 'pending' }, + { id: 6, cityName: '昌吉回族自治州', companyName: '新疆蓝山屯河化工股份有限公司', jobTitle: '高分子材料研发', salary: '9K-14K', savedDate: '2026-06-11', deliverStatus: 'delivered', deliverDate: '2026-06-12' }, +]; + +/** 统计数据 */ +const STATISTICS_DATA = { + totalCities: 8, + totalCompanies: 156, + totalJobs: 2340, + totalJobSeekers: 12860, + totalResumeDelivered: 4560, + matchSuccessRate: 38.5, + industryDistribution: [ + { industry: '制造业', count: 45, percentage: 28.8 }, + { industry: '能源/矿产', count: 28, percentage: 17.9 }, + { industry: '医疗/卫生', count: 22, percentage: 14.1 }, + { industry: '教育/科研', count: 18, percentage: 11.5 }, + { industry: '建筑/工程', count: 15, percentage: 9.6 }, + { industry: '新能源', count: 12, percentage: 7.7 }, + { industry: '金融/投资', count: 10, percentage: 6.4 }, + { industry: '其他', count: 6, percentage: 3.8 }, + ], + cityJobDistribution: [ + { city: '乌鲁木齐市', jobs: 680, companies: 45 }, + { city: '克拉玛依市', jobs: 420, companies: 28 }, + { city: '昌吉回族自治州', jobs: 350, companies: 22 }, + { city: '哈密市', jobs: 280, companies: 18 }, + { city: '吐鲁番市', jobs: 160, companies: 12 }, + { city: '喀什地区', jobs: 200, companies: 15 }, + { city: '阿克苏地区', jobs: 150, companies: 10 }, + { city: '伊犁哈萨克自治州', jobs: 100, companies: 6 }, + ], + monthlyDelivery: [ + { month: '2026-01', count: 320 }, + { month: '2026-02', count: 280 }, + { month: '2026-03', count: 450 }, + { month: '2026-04', count: 520 }, + { month: '2026-05', count: 610 }, + { month: '2026-06', count: 780 }, + ], +}; + +const STATUS_MAP: Record = { + pending: { color: 'default', label: '待投递' }, + delivered: { color: 'processing', label: '已投递' }, + interview: { color: 'warning', label: '面试中' }, + offer: { color: 'success', label: '已获Offer' }, +}; + +// ==================== 功能背景 Tab ==================== + +const BackgroundTab: React.FC = () => ( +
+ + + <TeamOutlined style={{ marginRight: 8 }} /> + 功能背景 + + + 石河子市人力资源和社会保障局为深入贯彻国家关于促进高质量充分就业的决策部署, + 积极与新疆维吾尔自治区其他地市联合举办跨域联合招聘活动,整合各地市就业服务资源, + 实现资源优势互补,为广大求职者提供更多地区的职位信息,为企业推送更多的优秀人才。 + + + 活动结束后,在当地投递的属于石河子地区的岗位数据、人才简历等相关数据 + 将统一回流至"石城智慧就业"一体化平台,实现数据有效归集和深度应用。 + + + + + + + } + suffix="个" + /> +
+ {ALLIANCE_CITIES.map((city) => ( + + {city.name} + + ))} +
+
+ + + + } + suffix="家" + /> +
+ 覆盖制造业、能源、医疗、教育、建筑等多个重点行业 +
+
+ + + + } + suffix="个" + /> +
+ 涵盖技术类、管理类、一线工人等多种岗位类型 +
+
+ +
+ + + 核心功能 + + + + + 一站式获取各地市就业信息,自由选择意向就业城市,查看招聘企业信息和用工信息。 + 聚合各地市就业资源,便捷获取全国范围内的工作机会。 + + + + + + + 一键收藏其他城市招聘企业和职位信息,在个人中心随时查看, + 跨区域快速投递简历,简化投递流程,提高求职效率。 + + + + + + + 自动汇总招聘会相关数据,包含参会企业数量、岗位数量、求职者人数、 + 简历投递数量、匹配成功率等,形成详细数据报表。 + + + + + + + 活动结束后,石河子地区岗位数据和人才简历自动回流至 + "石城智慧就业"一体化平台,实现数据归集和深度应用。 + + + + + +
+); + +// ==================== 各地市就业信息 Tab ==================== + +const CityJobsTab: React.FC = () => { + const [jobs, setJobs] = useState(MOCK_CITY_JOBS); + const [selectedCities, setSelectedCities] = useState([]); + + const handleToggleSave = (id: number) => { + setJobs((prev) => + prev.map((job) => + job.id === id ? { ...job, saved: !job.saved } : job + ) + ); + const job = jobs.find((j) => j.id === id); + if (job) { + message.success(job.saved ? '已取消收藏' : '已收藏到个人中心'); + } + }; + + const handleQuickApply = (record: CityJob) => { + message.success(`已向「${record.companyName}」投递简历,岗位:${record.jobTitle}`); + }; + + const filteredJobs = selectedCities.length > 0 + ? jobs.filter((j) => selectedCities.includes(j.cityKey)) + : jobs; + + const cityColumns = [ + { + title: '城市', + dataIndex: 'cityName', + width: 120, + render: (text: string) => ( + + + {text} + + ), + }, + { + title: '企业名称', + dataIndex: 'companyName', + ellipsis: true, + width: 200, + }, + { + title: '职位名称', + dataIndex: 'jobTitle', + width: 160, + render: (text: string) => {text}, + }, + { + title: '薪资范围', + dataIndex: 'salary', + width: 100, + render: (text: string) => {text}, + }, + { + title: '学历要求', + dataIndex: 'education', + width: 90, + }, + { + title: '经验要求', + dataIndex: 'experience', + width: 90, + }, + { + title: '招聘人数', + dataIndex: 'vacancies', + width: 80, + align: 'center' as const, + }, + { + title: '行业', + dataIndex: 'industry', + width: 100, + render: (text: string) => {text}, + }, + { + title: '发布日期', + dataIndex: 'postDate', + width: 100, + }, + { + title: '操作', + width: 200, + fixed: 'right' as const, + render: (_: unknown, record: CityJob) => ( + + + + + ), + }, + ]; + + return ( +
+ + + <EnvironmentOutlined style={{ marginRight: 8 }} /> + 选择联盟城市 + + + {ALLIANCE_CITIES.map((city) => ( + { + setSelectedCities((prev) => + checked + ? [...prev, city.key] + : prev.filter((k) => k !== city.key) + ); + }} + style={{ + padding: '4px 12px', + fontSize: 14, + borderRadius: 4, + }} + > + {city.name} + + ))} + {selectedCities.length > 0 && ( + + )} + +
+ 已选择 {selectedCities.length > 0 ? selectedCities.length + ' 个城市' : '全部城市'}, + 共 {filteredJobs.length} 个职位 +
+
+ + `共 ${total} 个职位`, + }} + /> + + ); +}; + +// ==================== 跨区简历投递 Tab ==================== + +const ResumeDeliveryTab: React.FC = () => { + const [savedJobs, setSavedJobs] = useState(MOCK_SAVED_JOBS); + + const handleDeliver = (record: SavedJob) => { + setSavedJobs((prev) => + prev.map((job) => + job.id === record.id + ? { ...job, deliverStatus: 'delivered' as const, deliverDate: '2026-06-18' } + : job + ) + ); + message.success(`简历已投递至「${record.companyName}」- ${record.jobTitle}`); + }; + + const columns = [ + { + title: '城市', + dataIndex: 'cityName', + width: 100, + render: (text: string) => ( + + + {text} + + ), + }, + { + title: '企业名称', + dataIndex: 'companyName', + ellipsis: true, + width: 180, + }, + { + title: '职位名称', + dataIndex: 'jobTitle', + width: 150, + render: (text: string) => {text}, + }, + { + title: '薪资', + dataIndex: 'salary', + width: 90, + render: (text: string) => {text}, + }, + { + title: '收藏时间', + dataIndex: 'savedDate', + width: 100, + }, + { + title: '投递状态', + dataIndex: 'deliverStatus', + width: 110, + render: (status: string) => { + const s = STATUS_MAP[status]; + return {s?.label}; + }, + }, + { + title: '投递时间', + dataIndex: 'deliverDate', + width: 100, + render: (text: string | undefined) => text || '--', + }, + { + title: '操作', + width: 140, + fixed: 'right' as const, + render: (_: unknown, record: SavedJob) => ( + + {record.deliverStatus === 'pending' && ( + + )} + {record.deliverStatus === 'delivered' && ( + + )} + {record.deliverStatus === 'interview' && ( + + )} + {record.deliverStatus === 'offer' && ( + + )} + + ), + }, + ]; + + return ( +
+ +
+ + } + suffix="个" + /> + + + + + j.deliverStatus !== 'pending').length} + prefix={} + suffix="份" + /> + + + + + j.deliverStatus === 'interview').length} + prefix={} + suffix="个" + /> + + + + + j.deliverStatus === 'offer').length} + prefix={} + suffix="个" + valueStyle={{ color: '#52c41a' }} + /> + + + + + + + + 在"各地市就业信息"中点击星标即可收藏心仪职位 + + + 收藏后在此页面一键投递简历,无需重复填写 + + + 实时查看简历投递状态:待投递 → 已投递 → 面试中 → 已获Offer + + + 投递石河子地区岗位的数据将自动回流至一体化平台 + + + + +
+ + ); +}; + +// ==================== 数据统计 Tab ==================== + +const DataStatsTab: React.FC = () => { + const industryColumns = [ + { title: '行业', dataIndex: 'industry', key: 'industry' }, + { title: '企业数量', dataIndex: 'count', key: 'count', align: 'center' as const }, + { + title: '占比', + dataIndex: 'percentage', + key: 'percentage', + align: 'center' as const, + render: (val: number) => `${val}%`, + }, + ]; + + const cityColumns = [ + { title: '城市', dataIndex: 'city', key: 'city' }, + { title: '岗位数量', dataIndex: 'jobs', key: 'jobs', align: 'center' as const }, + { title: '企业数量', dataIndex: 'companies', key: 'companies', align: 'center' as const }, + ]; + + const deliveryColumns = [ + { title: '月份', dataIndex: 'month', key: 'month' }, + { title: '简历投递量', dataIndex: 'count', key: 'count', align: 'center' as const }, + ]; + + return ( +
+ +
+ + } + suffix="个" + /> + + + + + } + suffix="家" + /> + + + + + } + suffix="个" + /> + + + + + + + + } + suffix="人" + /> + + + + + } + suffix="份" + /> + + + + + } + suffix="次" + /> + + + + + } + suffix="%" + valueStyle={{ color: '#52c41a' }} + /> + + + + + + + +
+ + + + +
+ + + + + +
+ + + ); +}; + +// ==================== 主页面 ==================== + +const CrossCityFair: React.FC = () => { + const tabItems = [ + { + key: 'background', + label: ( + + + 功能背景 + + ), + children: , + }, + { + key: 'city-jobs', + label: ( + + + 各地市就业信息 + + ), + children: , + }, + { + key: 'resume-delivery', + label: ( + + + 跨区简历投递 + + ), + children: , + }, + { + key: 'statistics', + label: ( + + + 数据统计 + + ), + children: , + }, + ]; + + return ( + + + + + + ); +}; + +export default CrossCityFair; diff --git a/src/pages/Jobfair/Indoorjobfair/Detail/components/BoothTab.tsx b/src/pages/Jobfair/Indoorjobfair/Detail/components/BoothTab.tsx new file mode 100644 index 0000000..70562dd --- /dev/null +++ b/src/pages/Jobfair/Indoorjobfair/Detail/components/BoothTab.tsx @@ -0,0 +1,140 @@ +import React, { useRef, useState } from 'react'; +import { useAccess } from '@umijs/max'; +import { Button, message, Modal, Tag } from 'antd'; +import { ActionType, ProColumns, ProTable, ModalForm, ProFormText, ProFormSelect } from '@ant-design/pro-components'; +import { PlusOutlined, DeleteOutlined, FormOutlined, LockOutlined, UnlockOutlined, PaperClipOutlined } from '@ant-design/icons'; +import { + getBoothList, + addBooth, + updateBooth, + fixBooth, + reserveBooth, + releaseBooth, +} from '@/services/jobportal/indoorJobFair'; +import type { BoothItem } from '@/services/jobportal/indoorJobFair'; + +interface Props { + fairId: number; +} + +const BoothTab: React.FC = () => { + const access = useAccess(); + const actionRef = useRef(); + const [modalOpen, setModalOpen] = useState(false); + const [editingBooth, setEditingBooth] = useState(null); + + const statusMap: Record = { + available: { text: '可用', color: 'green' }, + fixed: { text: '固定', color: 'blue' }, + reserved: { text: '预留', color: 'orange' }, + locked: { text: '锁定', color: 'red' }, + }; + + const columns: ProColumns[] = [ + { title: '展位号', dataIndex: 'boothNumber', width: 100 }, + { title: '企业名称', dataIndex: 'companyName', ellipsis: true, render: (_, r) => r.companyName || '--' }, + { + title: '状态', + dataIndex: 'status', + width: 80, + render: (_, record) => { + const s = statusMap[record.status] || { text: record.status, color: 'default' }; + return {s.text}; + }, + }, + { title: '操作人', dataIndex: 'operator', width: 100, render: (_, r) => r.operator || '--' }, + { title: '操作时间', dataIndex: 'operateTime', width: 170, render: (_, r) => r.operateTime || '--' }, + { title: '备注', dataIndex: 'remark', ellipsis: true, render: (_, r) => r.remark || '--' }, + { + title: '操作', + valueType: 'option', + width: 250, + render: (_, record) => [ + , + , + , + , + ], + }, + ]; + + return ( +
+ { + const res = await getBoothList({ + current: params.current, + pageSize: params.pageSize, + boothNumber: params.boothNumber, + companyName: params.companyName, + }); + return { data: res.rows, total: res.total, success: true }; + }} + toolBarRender={() => [ + , + ]} + /> + + setModalOpen(false) }} + onFinish={async (values) => { + const res = editingBooth + ? await updateBooth({ id: editingBooth.id, ...values }) + : await addBooth({ venueId: 1, ...values } as any); + if (res.code === 200) { message.success(res.msg); setModalOpen(false); actionRef.current?.reload(); return true; } + message.error(res.msg); return false; + }} + initialValues={editingBooth} + > + + + + + +
+ ); +}; + +export default BoothTab; diff --git a/src/pages/Jobfair/Indoorjobfair/Detail/components/CompanyQueryTab.tsx b/src/pages/Jobfair/Indoorjobfair/Detail/components/CompanyQueryTab.tsx new file mode 100644 index 0000000..c5ded06 --- /dev/null +++ b/src/pages/Jobfair/Indoorjobfair/Detail/components/CompanyQueryTab.tsx @@ -0,0 +1,77 @@ +import React, { useRef, useState } from 'react'; +import { Button, message } from 'antd'; +import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components'; +import { ExportOutlined } from '@ant-design/icons'; +import { getCompanyQueryList } from '@/services/jobportal/indoorJobFair'; +import type { CompanyQueryItem } from '@/services/jobportal/indoorJobFair'; + +interface Props { + fairId: number; +} + +const CompanyQueryTab: React.FC = () => { + const actionRef = useRef(); + const [selectedRowKeys, setSelectedRowKeys] = useState([]); + + const handleExport = async (type: string) => { + if (type === 'selected' && selectedRowKeys.length === 0) { + message.warning('请先选择要导出的数据'); + return; + } + message.success(`已导出${type === 'selected' ? '选中' : type === 'page' ? '当前页' : '全部'}数据(模拟)`); + }; + + const columns: ProColumns[] = [ + { title: '招聘会名称', dataIndex: 'fairName', ellipsis: true, hideInSearch: true }, + { + title: '招聘会名称', + dataIndex: 'fairName', + hideInTable: true, + order: 1, + }, + { title: '单位名称', dataIndex: 'companyName', ellipsis: true }, + { title: '行业', dataIndex: 'industry', width: 120, hideInSearch: true }, + { title: '规模', dataIndex: 'scale', width: 120, hideInSearch: true }, + { title: '联系人', dataIndex: 'contactPerson', width: 100, hideInSearch: true }, + { title: '联系电话', dataIndex: 'contactPhone', width: 130, hideInSearch: true }, + { title: '报名时间', dataIndex: 'registrationTime', valueType: 'dateTime', width: 170, hideInSearch: true }, + ]; + + return ( +
+ { + const res = await getCompanyQueryList({ + current: params.current, + pageSize: params.pageSize, + fairName: params.fairName, + }); + return { data: res.rows, total: res.total, success: true }; + }} + toolBarRender={() => [ + , + , + , + ]} + /> +
+ ); +}; + +export default CompanyQueryTab; diff --git a/src/pages/Jobfair/Indoorjobfair/Detail/components/CompanyRecordTab.tsx b/src/pages/Jobfair/Indoorjobfair/Detail/components/CompanyRecordTab.tsx new file mode 100644 index 0000000..d2f6f68 --- /dev/null +++ b/src/pages/Jobfair/Indoorjobfair/Detail/components/CompanyRecordTab.tsx @@ -0,0 +1,153 @@ +import React, { useRef, useState } from 'react'; +import { useAccess } from '@umijs/max'; +import { Button, message, Modal, Tag } from 'antd'; +import { ActionType, ProColumns, ProTable, ModalForm, ProFormText, ProFormSelect } from '@ant-design/pro-components'; +import { EyeOutlined } from '@ant-design/icons'; +import { getCompanyRecordList, updateCompanyRecordStatus, updateCompanyCreditStatus } from '@/services/jobportal/indoorJobFair'; +import type { CompanyRecordItem } from '@/services/jobportal/indoorJobFair'; + +interface Props { + fairId: number; +} + +const CompanyRecordTab: React.FC = () => { + const access = useAccess(); + const actionRef = useRef(); + const [modalOpen, setModalOpen] = useState(false); + const [currentRecord, setCurrentRecord] = useState(null); + const [modalType, setModalType] = useState<'status' | 'credit'>('status'); + + const statusMap: Record = { + normal: { text: '正常', color: 'green' }, + abnormal: { text: '异常', color: 'red' }, + }; + + const creditStatusMap: Record = { + good: { text: '良好', color: 'green' }, + poor: { text: '失信', color: 'red' }, + }; + + const handleUpdateStatus = async (values: any) => { + if (!currentRecord) return false; + const res = await updateCompanyRecordStatus(currentRecord.id, values.status); + if (res.code === 200) { + message.success('状态修改成功'); + setModalOpen(false); + actionRef.current?.reload(); + return true; + } + message.error(res.msg); + return false; + }; + + const handleUpdateCredit = async (values: any) => { + if (!currentRecord) return false; + const res = await updateCompanyCreditStatus(currentRecord.id, values.creditStatus); + if (res.code === 200) { + message.success('失信记录更新成功'); + setModalOpen(false); + actionRef.current?.reload(); + return true; + } + message.error(res.msg); + return false; + }; + + const columns: ProColumns[] = [ + { title: '单位名称', dataIndex: 'companyName', ellipsis: true }, + { title: '行业', dataIndex: 'industry', width: 100, hideInSearch: true }, + { title: '规模', dataIndex: 'scale', width: 100, hideInSearch: true }, + { title: '联系人', dataIndex: 'contactPerson', width: 100, hideInSearch: true }, + { title: '联系电话', dataIndex: 'contactPhone', width: 130, hideInSearch: true }, + { + title: '状态', + dataIndex: 'status', + width: 80, + hideInSearch: true, + render: (_, record) => { + const s = statusMap[record.status] || { text: record.status, color: 'default' }; + return {s.text}; + }, + }, + { + title: '失信情况', + dataIndex: 'creditStatus', + width: 80, + hideInSearch: true, + render: (_, record) => { + const s = creditStatusMap[record.creditStatus] || { text: record.creditStatus, color: 'default' }; + return {s.text}; + }, + }, + { title: '参会次数', dataIndex: 'fairCount', width: 80, hideInSearch: true }, + { title: '创建时间', dataIndex: 'createTime', valueType: 'dateTime', width: 170, hideInSearch: true }, + { + title: '操作', + valueType: 'option', + width: 150, + render: (_, record) => [ + , + , + , + ], + }, + ]; + + return ( +
+ { + const res = await getCompanyRecordList({ + current: params.current, + pageSize: params.pageSize, + companyName: params.companyName, + contactPerson: params.contactPerson, + contactPhone: params.contactPhone, + }); + return { data: res.rows, total: res.total, success: true }; + }} + /> + + setModalOpen(false) }} + onFinish={modalType === 'status' ? handleUpdateStatus : handleUpdateCredit} + initialValues={currentRecord} + > + {modalType === 'status' ? ( + + ) : ( + + )} + +
+ ); +}; + +export default CompanyRecordTab; diff --git a/src/pages/Jobfair/Indoorjobfair/Detail/components/CompanyReviewTab.tsx b/src/pages/Jobfair/Indoorjobfair/Detail/components/CompanyReviewTab.tsx new file mode 100644 index 0000000..3a7bf3a --- /dev/null +++ b/src/pages/Jobfair/Indoorjobfair/Detail/components/CompanyReviewTab.tsx @@ -0,0 +1,153 @@ +import React, { useRef, useState } from 'react'; +import { useAccess } from '@umijs/max'; +import { Button, message, Modal, Tag } from 'antd'; +import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components'; +import { ExportOutlined, EyeOutlined, CheckOutlined, CloseOutlined } from '@ant-design/icons'; +import { getCompanyReviewList, batchApproveCompany, batchRejectCompany } from '@/services/jobportal/indoorJobFair'; +import type { CompanyReviewItem } from '@/services/jobportal/indoorJobFair'; + +interface Props { + fairId: number; +} + +const CompanyReviewTab: React.FC = () => { + const access = useAccess(); + const actionRef = useRef(); + const [selectedRowKeys, setSelectedRowKeys] = useState([]); + + const statusMap: Record = { + pending: { text: '待审核', color: 'default' }, + approved: { text: '已通过', color: 'green' }, + rejected: { text: '已拒绝', color: 'red' }, + }; + + const handleBatchApprove = async () => { + if (selectedRowKeys.length === 0) { + message.warning('请先选择要审核的数据'); + return; + } + const res = await batchApproveCompany(selectedRowKeys as number[]); + if (res.code === 200) { + message.success('批量审核成功'); + setSelectedRowKeys([]); + actionRef.current?.reload(); + } else { + message.error(res.msg); + } + }; + + const handleBatchReject = async () => { + if (selectedRowKeys.length === 0) { + message.warning('请先选择要审核的数据'); + return; + } + const res = await batchRejectCompany(selectedRowKeys as number[]); + if (res.code === 200) { + message.success('批量审核失败'); + setSelectedRowKeys([]); + actionRef.current?.reload(); + } else { + message.error(res.msg); + } + }; + + const handleExport = async (type: string) => { + if (type === 'selected' && selectedRowKeys.length === 0) { + message.warning('请先选择要导出的数据'); + return; + } + message.success(`已导出${type === 'selected' ? '选中' : type === 'page' ? '当前页' : '全部'}数据(模拟)`); + }; + + const columns: ProColumns[] = [ + { title: '招聘会名称', dataIndex: 'fairName', ellipsis: true, hideInSearch: true }, + { + title: '招聘会名称', + dataIndex: 'fairName', + hideInTable: true, + order: 1, + }, + { + title: '单位名称', + dataIndex: 'companyName', + order: 2, + }, + { title: '单位名称', dataIndex: 'companyName', ellipsis: true, hideInSearch: true }, + { title: '行业', dataIndex: 'industry', width: 100, hideInSearch: true }, + { title: '规模', dataIndex: 'scale', width: 100, hideInSearch: true }, + { title: '联系人', dataIndex: 'contactPerson', width: 100, hideInSearch: true }, + { title: '联系电话', dataIndex: 'contactPhone', width: 130, hideInSearch: true }, + { + title: '审核状态', + dataIndex: 'reviewStatus', + valueType: 'select', + width: 100, + hideInSearch: true, + render: (_, record) => { + const s = statusMap[record.reviewStatus] || { text: record.reviewStatus, color: 'default' }; + return {s.text}; + }, + }, + { + title: '操作', + valueType: 'option', + width: 150, + render: (_, record) => [ + , + , + ], + }, + ]; + + return ( +
+ { + const res = await getCompanyReviewList({ + current: params.current, + pageSize: params.pageSize, + fairName: params.fairName, + companyName: params.companyName, + reviewStatus: params.reviewStatus, + }); + return { data: res.rows, total: res.total, success: true }; + }} + toolBarRender={() => [ + , + , + , + , + , + ]} + /> +
+ ); +}; + +export default CompanyReviewTab; diff --git a/src/pages/Jobfair/Indoorjobfair/Detail/components/FairManageTab.tsx b/src/pages/Jobfair/Indoorjobfair/Detail/components/FairManageTab.tsx new file mode 100644 index 0000000..626bdc2 --- /dev/null +++ b/src/pages/Jobfair/Indoorjobfair/Detail/components/FairManageTab.tsx @@ -0,0 +1,147 @@ +import React, { useRef, useState } from 'react'; +import { useAccess } from '@umijs/max'; +import { Button, message, Modal, Tag } from 'antd'; +import { ActionType, ProColumns, ProTable, ModalForm, ProFormText, ProFormSelect } from '@ant-design/pro-components'; +import { PlusOutlined, DeleteOutlined, FormOutlined, ApartmentOutlined } from '@ant-design/icons'; +import { + getJobFairList, + addJobFair, + updateJobFair, + deleteJobFair, +} from '@/services/jobportal/indoorJobFair'; +import type { JobFairItem, JobFairForm } from '@/services/jobportal/indoorJobFair'; + +interface Props { + fairId: number; + fairInfo: JobFairItem; + onFairUpdate: () => void; +} + +const FairManageTab: React.FC = ({ fairInfo }) => { + const access = useAccess(); + const actionRef = useRef(); + const [modalOpen, setModalOpen] = useState(false); + const [editingFair, setEditingFair] = useState(null); + + const statusMap: Record = { + pending: { text: '筹备中', color: 'default' }, + ongoing: { text: '进行中', color: 'green' }, + finished: { text: '已结束', color: 'red' }, + }; + + const columns: ProColumns[] = [ + { title: '招聘会名称', dataIndex: 'fairName', ellipsis: true }, + { title: '场地名称', dataIndex: 'venueName', ellipsis: true, hideInSearch: true }, + { title: '开始日期', dataIndex: 'startDate', hideInSearch: true }, + { title: '结束日期', dataIndex: 'endDate', hideInSearch: true }, + { title: '使用展位数', dataIndex: 'boothUseCount', hideInSearch: true }, + { + title: '状态', + dataIndex: 'status', + hideInSearch: true, + render: (_, record) => { + const s = statusMap[record.status] || { text: record.status, color: 'default' }; + return {s.text}; + }, + }, + { title: '创建时间', dataIndex: 'createTime', valueType: 'dateTime', hideInSearch: true }, + { + title: '操作', + valueType: 'option', + width: 200, + render: (_, record) => [ + , + , + , + ], + }, + ]; + + const venueOptions = [ + { label: '石河子大学体育馆', value: 1 }, + { label: '第八师人力资源市场', value: 2 }, + { label: '阿拉尔市人才交流中心', value: 3 }, + { label: '图木舒克市职业介绍所', value: 4 }, + ]; + + const venueNameMap: Record = { + 1: '石河子大学体育馆', + 2: '第八师人力资源市场', + 3: '阿拉尔市人才交流中心', + 4: '图木舒克市职业介绍所', + }; + + return ( +
+ { + const res = await getJobFairList({ + current: params.current, + pageSize: params.pageSize, + fairName: params.fairName, + startDate: params.startDate, + endDate: params.endDate, + } as any); + return { data: res.rows, total: res.total, success: true }; + }} + toolBarRender={() => [ + , + ]} + /> + + setModalOpen(false) }} + onFinish={async (values) => { + const submitValues = { + ...values, + venueName: venueNameMap[values.venueId as number] || '', + }; + const res = editingFair + ? await updateJobFair({ id: editingFair.id, ...submitValues } as JobFairForm) + : await addJobFair(submitValues as JobFairForm); + if (res.code === 200) { message.success(res.msg); setModalOpen(false); actionRef.current?.reload(); return true; } + message.error(res.msg); return false; + }} + initialValues={editingFair} + > + + + + + +
+ ); +}; + +export default FairManageTab; diff --git a/src/pages/Jobfair/Indoorjobfair/Detail/components/JobQueryTab.tsx b/src/pages/Jobfair/Indoorjobfair/Detail/components/JobQueryTab.tsx new file mode 100644 index 0000000..2f04d72 --- /dev/null +++ b/src/pages/Jobfair/Indoorjobfair/Detail/components/JobQueryTab.tsx @@ -0,0 +1,90 @@ +import React, { useRef, useState } from 'react'; +import { Button, message } from 'antd'; +import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components'; +import { ExportOutlined } from '@ant-design/icons'; +import { getJobQueryList } from '@/services/jobportal/indoorJobFair'; +import type { JobQueryItem } from '@/services/jobportal/indoorJobFair'; + +interface Props { + fairId: number; +} + +const JobQueryTab: React.FC = () => { + const actionRef = useRef(); + const [selectedRowKeys, setSelectedRowKeys] = useState([]); + + const handleExport = async (type: string) => { + if (type === 'selected' && selectedRowKeys.length === 0) { + message.warning('请先选择要导出的数据'); + return; + } + message.success(`已导出${type === 'selected' ? '选中' : type === 'page' ? '当前页' : '全部'}数据(模拟)`); + }; + + const columns: ProColumns[] = [ + { title: '招聘会名称', dataIndex: 'fairName', ellipsis: true, hideInSearch: true }, + { + title: '招聘会名称', + dataIndex: 'fairName', + hideInTable: true, + order: 1, + }, + { + title: '单位名称', + dataIndex: 'companyName', + hideInTable: true, + order: 2, + }, + { title: '单位名称', dataIndex: 'companyName', ellipsis: true, hideInSearch: true }, + { title: '职位名称', dataIndex: 'jobTitle', ellipsis: true }, + { + title: '薪资范围', + dataIndex: 'salary', + hideInSearch: true, + render: (_, record) => `${record.minSalary}-${record.maxSalary}`, + }, + { title: '学历要求', dataIndex: 'education', width: 100, hideInSearch: true }, + { title: '经验要求', dataIndex: 'experience', width: 100, hideInSearch: true }, + { title: '招聘人数', dataIndex: 'vacancies', width: 80, hideInSearch: true }, + { title: '状态', dataIndex: 'status', width: 80, hideInSearch: true, render: (_, r) => r.status === 'open' ? '招聘中' : '已结束' }, + ]; + + return ( +
+ { + const res = await getJobQueryList({ + current: params.current, + pageSize: params.pageSize, + fairName: params.fairName, + companyName: params.companyName, + }); + return { data: res.rows, total: res.total, success: true }; + }} + toolBarRender={() => [ + , + , + , + ]} + /> +
+ ); +}; + +export default JobQueryTab; diff --git a/src/pages/Jobfair/Indoorjobfair/Detail/components/JobReviewTab.tsx b/src/pages/Jobfair/Indoorjobfair/Detail/components/JobReviewTab.tsx new file mode 100644 index 0000000..44db4ec --- /dev/null +++ b/src/pages/Jobfair/Indoorjobfair/Detail/components/JobReviewTab.tsx @@ -0,0 +1,140 @@ +import React, { useRef, useState } from 'react'; +import { useAccess } from '@umijs/max'; +import { Button, message, Tag } from 'antd'; +import { ActionType, ProColumns, ProTable, ModalForm, ProFormText } from '@ant-design/pro-components'; +import { EyeOutlined, CheckOutlined, CloseOutlined } from '@ant-design/icons'; +import { getJobReviewList, approveJob, rejectJob } from '@/services/jobportal/indoorJobFair'; +import type { JobReviewItem } from '@/services/jobportal/indoorJobFair'; + +interface Props { + fairId: number; +} + +const JobReviewTab: React.FC = () => { + const access = useAccess(); + const actionRef = useRef(); + const [reviewModalOpen, setReviewModalOpen] = useState(false); + const [currentJob, setCurrentJob] = useState(null); + + const statusMap: Record = { + pending: { text: '待审核', color: 'default' }, + approved: { text: '已通过', color: 'green' }, + rejected: { text: '已拒绝', color: 'red' }, + }; + + const handleApprove = async (values: any) => { + if (!currentJob) return false; + const res = await approveJob(currentJob.id, values.reviewRemark); + if (res.code === 200) { + message.success('审核通过'); + setReviewModalOpen(false); + actionRef.current?.reload(); + return true; + } + message.error(res.msg); + return false; + }; + + const handleReject = async (values: any) => { + if (!currentJob) return false; + const res = await rejectJob(currentJob.id, values.reviewRemark); + if (res.code === 200) { + message.success('审核不通过'); + setReviewModalOpen(false); + actionRef.current?.reload(); + return true; + } + message.error(res.msg); + return false; + }; + + const columns: ProColumns[] = [ + { title: '招聘会名称', dataIndex: 'fairName', ellipsis: true, hideInSearch: true }, + { + title: '单位名称', + dataIndex: 'companyName', + order: 1, + }, + { title: '单位名称', dataIndex: 'companyName', ellipsis: true, hideInSearch: true }, + { + title: '职位名称', + dataIndex: 'jobTitle', + order: 2, + }, + { title: '职位名称', dataIndex: 'jobTitle', ellipsis: true, hideInSearch: true }, + { title: '学历要求', dataIndex: 'education', width: 100, hideInSearch: true }, + { title: '经验要求', dataIndex: 'experience', width: 100, hideInSearch: true }, + { title: '招聘人数', dataIndex: 'vacancies', width: 80, hideInSearch: true }, + { + title: '审核状态', + dataIndex: 'reviewStatus', + valueType: 'select', + width: 100, + hideInSearch: true, + render: (_, record) => { + const s = statusMap[record.reviewStatus] || { text: record.reviewStatus, color: 'default' }; + return {s.text}; + }, + }, + { + title: '操作', + valueType: 'option', + width: 200, + render: (_, record) => [ + , + record.reviewStatus === 'pending' && ( + + ), + ], + }, + ]; + + return ( +
+ { + const res = await getJobReviewList({ + current: params.current, + pageSize: params.pageSize, + companyName: params.companyName, + jobTitle: params.jobTitle, + reviewStatus: params.reviewStatus, + }); + return { data: res.rows, total: res.total, success: true }; + }} + /> + + setReviewModalOpen(false) }} + onFinish={async (values) => { + if (values.action === 'approve') { + return handleApprove(values); + } else { + return handleReject(values); + } + }} + initialValues={currentJob} + > + + + + +
+ ); +}; + +export default JobReviewTab; diff --git a/src/pages/Jobfair/Indoorjobfair/Detail/components/ReservationRecordTab.tsx b/src/pages/Jobfair/Indoorjobfair/Detail/components/ReservationRecordTab.tsx new file mode 100644 index 0000000..33a8c0b --- /dev/null +++ b/src/pages/Jobfair/Indoorjobfair/Detail/components/ReservationRecordTab.tsx @@ -0,0 +1,51 @@ +import React, { useRef } from 'react'; +import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components'; +import { getReservationRecordList } from '@/services/jobportal/indoorJobFair'; +import type { ReservationRecordItem } from '@/services/jobportal/indoorJobFair'; + +interface Props { + fairId: number; +} + +const ReservationRecordTab: React.FC = () => { + const actionRef = useRef(); + + const columns: ProColumns[] = [ + { title: '招聘会名称', dataIndex: 'fairName', ellipsis: true, hideInSearch: true }, + { + title: '操作人', + dataIndex: 'operator', + order: 1, + }, + { title: '操作人', dataIndex: 'operator', width: 100, hideInSearch: true }, + { title: '招聘会名称', dataIndex: 'fairName', ellipsis: true, hideInSearch: true }, + { title: '展位号', dataIndex: 'boothNumber', width: 100, hideInSearch: true }, + { title: '企业名称', dataIndex: 'companyName', ellipsis: true, hideInSearch: true, render: (_, r) => r.companyName || '--' }, + { title: '操作类型', dataIndex: 'operateType', width: 100, hideInSearch: true }, + { title: '操作时间', dataIndex: 'operateTime', valueType: 'dateTime', width: 170, hideInSearch: true }, + { title: '备注', dataIndex: 'remark', ellipsis: true, hideInSearch: true, render: (_, r) => r.remark || '--' }, + ]; + + return ( +
+ { + const res = await getReservationRecordList({ + current: params.current, + pageSize: params.pageSize, + operator: params.operator, + }); + return { data: res.rows, total: res.total, success: true }; + }} + /> +
+ ); +}; + +export default ReservationRecordTab; diff --git a/src/pages/Jobfair/Indoorjobfair/Detail/components/UnsubscribeTab.tsx b/src/pages/Jobfair/Indoorjobfair/Detail/components/UnsubscribeTab.tsx new file mode 100644 index 0000000..edc830b --- /dev/null +++ b/src/pages/Jobfair/Indoorjobfair/Detail/components/UnsubscribeTab.tsx @@ -0,0 +1,60 @@ +import React, { useRef } from 'react'; +import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components'; +import { getUnsubscribeList } from '@/services/jobportal/indoorJobFair'; +import type { UnsubscribeItem } from '@/services/jobportal/indoorJobFair'; + +interface Props { + fairId: number; +} + +const UnsubscribeTab: React.FC = () => { + const actionRef = useRef(); + + const columns: ProColumns[] = [ + { title: '招聘会名称', dataIndex: 'fairName', ellipsis: true, hideInSearch: true }, + { + title: '招聘会名称', + dataIndex: 'fairName', + hideInTable: true, + order: 1, + }, + { + title: '单位名称', + dataIndex: 'companyName', + order: 2, + }, + { title: '单位名称', dataIndex: 'companyName', ellipsis: true, hideInSearch: true }, + { title: '展位号', dataIndex: 'boothNumber', width: 100, hideInSearch: true }, + { title: '联系人', dataIndex: 'contactPerson', width: 100, hideInSearch: true }, + { title: '联系电话', dataIndex: 'contactPhone', width: 130, hideInSearch: true }, + { title: '退订时间', dataIndex: 'unsubscribeTime', valueType: 'dateTime', width: 170, hideInSearch: true }, + { title: '退订原因', dataIndex: 'reason', ellipsis: true, hideInSearch: true }, + ]; + + return ( +
+ { + const res = await getUnsubscribeList({ + current: params.current, + pageSize: params.pageSize, + fairName: params.fairName, + companyName: params.companyName, + boothNumber: params.boothNumber, + contactPerson: params.contactPerson, + contactPhone: params.contactPhone, + }); + return { data: res.rows, total: res.total, success: true }; + }} + /> +
+ ); +}; + +export default UnsubscribeTab; diff --git a/src/pages/Jobfair/Indoorjobfair/Detail/components/VenueTab.tsx b/src/pages/Jobfair/Indoorjobfair/Detail/components/VenueTab.tsx new file mode 100644 index 0000000..d4925fa --- /dev/null +++ b/src/pages/Jobfair/Indoorjobfair/Detail/components/VenueTab.tsx @@ -0,0 +1,131 @@ +import React, { useRef, useState } from 'react'; +import { useAccess } from '@umijs/max'; +import { Button, message, Modal, Tag } from 'antd'; +import { ActionType, ProColumns, ProTable, ModalForm, ProFormText, ProFormSelect } from '@ant-design/pro-components'; +import { PlusOutlined, DeleteOutlined, FormOutlined, LayoutOutlined } from '@ant-design/icons'; +import { + getVenueList, + addVenue, + updateVenue, + deleteVenue, +} from '@/services/jobportal/indoorJobFair'; +import type { VenueItem, VenueForm } from '@/services/jobportal/indoorJobFair'; + +interface Props { + fairId: number; +} + +const VenueTab: React.FC = () => { + const access = useAccess(); + const actionRef = useRef(); + const [modalOpen, setModalOpen] = useState(false); + const [editingVenue, setEditingVenue] = useState(null); + + const statusMap: Record = { + active: { text: '启用', color: 'green' }, + inactive: { text: '停用', color: 'red' }, + }; + + const columns: ProColumns[] = [ + { title: '场地名称', dataIndex: 'venueName', ellipsis: true }, + { title: '就业机构', dataIndex: 'employmentAgency', ellipsis: true }, + { title: '场地类型', dataIndex: 'venueType', width: 100 }, + { title: '场地地址', dataIndex: 'venueAddress', ellipsis: true, hideInSearch: true }, + { title: '联系人', dataIndex: 'contactPerson', width: 100, hideInSearch: true }, + { title: '联系电话', dataIndex: 'contactPhone', width: 130, hideInSearch: true }, + { title: '展位数', dataIndex: 'boothCount', width: 80, hideInSearch: true }, + { + title: '状态', + dataIndex: 'status', + width: 80, + hideInSearch: true, + render: (_, record) => { + const s = statusMap[record.status] || { text: record.status, color: 'default' }; + return {s.text}; + }, + }, + { + title: '操作', + valueType: 'option', + width: 150, + render: (_, record) => [ + , + , + , + ], + }, + ]; + + return ( +
+ { + const res = await getVenueList({ + current: params.current, + pageSize: params.pageSize, + employmentAgency: params.employmentAgency, + venueName: params.venueName, + venueType: params.venueType, + }); + return { data: res.rows, total: res.total, success: true }; + }} + toolBarRender={() => [ + , + ]} + /> + + setModalOpen(false) }} + onFinish={async (values) => { + const res = editingVenue + ? await updateVenue({ id: editingVenue.id, ...values }) + : await addVenue(values as VenueForm); + if (res.code === 200) { message.success(res.msg); setModalOpen(false); actionRef.current?.reload(); return true; } + message.error(res.msg); return false; + }} + initialValues={editingVenue} + > + + + + + + + + +
+ ); +}; + +export default VenueTab; diff --git a/src/pages/Jobfair/Indoorjobfair/Detail/index.tsx b/src/pages/Jobfair/Indoorjobfair/Detail/index.tsx new file mode 100644 index 0000000..88e95ad --- /dev/null +++ b/src/pages/Jobfair/Indoorjobfair/Detail/index.tsx @@ -0,0 +1,114 @@ +import React, { useState, useEffect } from 'react'; +import { history, useSearchParams } from '@umijs/max'; +import { PageContainer } from '@ant-design/pro-components'; +import { Tabs, Button, Spin, message } from 'antd'; +import { ArrowLeftOutlined } from '@ant-design/icons'; +import { getJobFairList } from '@/services/jobportal/indoorJobFair'; +import type { JobFairItem } from '@/services/jobportal/indoorJobFair'; +import VenueTab from './components/VenueTab'; +import BoothTab from './components/BoothTab'; +import FairManageTab from './components/FairManageTab'; +import CompanyQueryTab from './components/CompanyQueryTab'; +import JobQueryTab from './components/JobQueryTab'; +import CompanyReviewTab from './components/CompanyReviewTab'; +import UnsubscribeTab from './components/UnsubscribeTab'; +import CompanyRecordTab from './components/CompanyRecordTab'; +import JobReviewTab from './components/JobReviewTab'; +import ReservationRecordTab from './components/ReservationRecordTab'; + +const IndoorJobFairDetail: React.FC = () => { + const [searchParams] = useSearchParams(); + const fairId = Number(searchParams.get('id')); + const [fairInfo, setFairInfo] = useState(null); + const [loading, setLoading] = useState(true); + const [activeTab, setActiveTab] = useState('venue'); + + const fetchFairInfo = async () => { + setLoading(true); + try { + const res = await getJobFairList({ current: 1, pageSize: 100 }); + const found = res.rows.find((item) => item.id === fairId); + if (found) { + setFairInfo(found); + } else { + message.error('未找到该招聘会信息'); + } + } finally { + setLoading(false); + } + }; + + useEffect(() => { + if (fairId) { + fetchFairInfo(); + } + }, [fairId]); + + const tabItems = [ + { key: 'venue', label: '场地信息维护' }, + { key: 'booth', label: '摊位信息维护' }, + { key: 'fair-manage', label: '招聘会管理' }, + { key: 'company-query', label: '参会单位查询' }, + { key: 'job-query', label: '参会岗位查询' }, + { key: 'company-review', label: '参会单位审核' }, + { key: 'unsubscribe', label: '退订信息管理' }, + { key: 'company-record', label: '单位参会查询' }, + { key: 'job-review', label: '招聘会职位审核' }, + { key: 'reservation-record', label: '预留操作记录' }, + ]; + + return ( + history.back(), + extra: [ + , + ], + }} + > + + {fairInfo && ( + ({ + key: tab.key, + label: tab.label, + children: (() => { + switch (tab.key) { + case 'venue': + return ; + case 'booth': + return ; + case 'fair-manage': + return ; + case 'company-query': + return ; + case 'job-query': + return ; + case 'company-review': + return ; + case 'unsubscribe': + return ; + case 'company-record': + return ; + case 'job-review': + return ; + case 'reservation-record': + return ; + default: + return null; + } + })(), + }))} + /> + )} + + + ); +}; + +export default IndoorJobFairDetail; diff --git a/src/pages/Jobfair/Indoorjobfair/components/EditModal.tsx b/src/pages/Jobfair/Indoorjobfair/components/EditModal.tsx new file mode 100644 index 0000000..01c3627 --- /dev/null +++ b/src/pages/Jobfair/Indoorjobfair/components/EditModal.tsx @@ -0,0 +1,76 @@ +import React from 'react'; +import { Modal } from 'antd'; +import { ModalForm, ProFormText, ProFormSelect } from '@ant-design/pro-components'; +import type { JobFairForm } from '@/services/jobportal/indoorJobFair'; + +interface Props { + open: boolean; + values?: JobFairForm; + onCancel: () => void; + onSubmit: (values: JobFairForm) => Promise; +} + +const venueOptions = [ + { label: '石河子大学体育馆', value: 1 }, + { label: '第八师人力资源市场', value: 2 }, + { label: '阿拉尔市人才交流中心', value: 3 }, + { label: '图木舒克市职业介绍所', value: 4 }, +]; + +const venueNameMap: Record = { + 1: '石河子大学体育馆', + 2: '第八师人力资源市场', + 3: '阿拉尔市人才交流中心', + 4: '图木舒克市职业介绍所', +}; + +const EditModal: React.FC = ({ open, values, onCancel, onSubmit }) => { + return ( + { + const submitValues = { + ...formValues, + venueName: venueNameMap[formValues.venueId as number] || '', + }; + await onSubmit(submitValues as JobFairForm); + return true; + }} + initialValues={{ + ...values, + venueId: values?.venueId || undefined, + }} + > + + + + + + ); +}; + +export default EditModal; diff --git a/src/pages/Jobfair/Indoorjobfair/index.tsx b/src/pages/Jobfair/Indoorjobfair/index.tsx new file mode 100644 index 0000000..782a506 --- /dev/null +++ b/src/pages/Jobfair/Indoorjobfair/index.tsx @@ -0,0 +1,188 @@ +import React, { useRef, useState } from 'react'; +import { useAccess, history } from '@umijs/max'; +import { Button, message, Modal, Tag } from 'antd'; +import { ActionType, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components'; +import { PlusOutlined, DeleteOutlined, FormOutlined, EyeOutlined } from '@ant-design/icons'; +import { + getJobFairList, + addJobFair, + updateJobFair, + deleteJobFair, +} from '@/services/jobportal/indoorJobFair'; +import type { JobFairItem, JobFairForm } from '@/services/jobportal/indoorJobFair'; +import EditModal from './components/EditModal'; + +const IndoorJobFairList: React.FC = () => { + const access = useAccess(); + const actionRef = useRef(); + const [modalVisible, setModalVisible] = useState(false); + const [currentRow, setCurrentRow] = useState(); + + const handleDelete = async (id: number) => { + Modal.confirm({ + title: '确认删除', + content: '确定要删除该室内招聘会吗?', + onOk: async () => { + const res = await deleteJobFair(id); + if (res.code === 200) { + message.success('删除成功'); + actionRef.current?.reload(); + } else { + message.error(res.msg || '删除失败'); + } + }, + }); + }; + + const statusMap: Record = { + pending: { text: '筹备中', color: 'default' }, + ongoing: { text: '进行中', color: 'green' }, + finished: { text: '已结束', color: 'red' }, + }; + + const columns: ProColumns[] = [ + { + title: '招聘会名称', + dataIndex: 'fairName', + ellipsis: true, + }, + { + title: '场地名称', + dataIndex: 'venueName', + ellipsis: true, + hideInSearch: true, + }, + { + title: '开始日期', + dataIndex: 'startDate', + valueType: 'date', + hideInSearch: true, + }, + { + title: '结束日期', + dataIndex: 'endDate', + valueType: 'date', + hideInSearch: true, + }, + { + title: '使用展位数', + dataIndex: 'boothUseCount', + hideInSearch: true, + }, + { + title: '状态', + dataIndex: 'status', + hideInSearch: true, + render: (_, record) => { + const status = statusMap[record.status] || { text: record.status, color: 'default' }; + return {status.text}; + }, + }, + { + title: '创建时间', + dataIndex: 'createTime', + valueType: 'dateTime', + hideInSearch: true, + }, + { + title: '操作', + valueType: 'option', + width: 160, + fixed: 'right', + render: (_, record) => [ + , + , + , + ], + }, + ]; + + return ( + + + headerTitle="室内固定摊位招聘会列表" + actionRef={actionRef} + rowKey="id" + columns={columns} + scroll={{ x: 'max-content' }} + request={async (params) => { + const res = await getJobFairList({ + current: params.current, + pageSize: params.pageSize, + fairName: params.fairName, + startDate: params.startDate, + endDate: params.endDate, + } as any); + return { data: res.rows, total: res.total, success: true }; + }} + toolBarRender={() => [ + , + ]} + /> + { + setModalVisible(false); + setCurrentRow(undefined); + }} + onSubmit={async (values: JobFairForm) => { + const res = values.id + ? await updateJobFair(values) + : await addJobFair(values); + if (res.code === 200) { + message.success(values.id ? '修改成功' : '新增成功'); + setModalVisible(false); + setCurrentRow(undefined); + actionRef.current?.reload(); + } else { + message.error(res.msg || '操作失败'); + } + }} + /> + + ); +}; + +export default IndoorJobFairList; diff --git a/src/pages/Jobfair/Outdoorfair/Detail/components/AttendeeTab.tsx b/src/pages/Jobfair/Outdoorfair/Detail/components/AttendeeTab.tsx new file mode 100644 index 0000000..ba8acf8 --- /dev/null +++ b/src/pages/Jobfair/Outdoorfair/Detail/components/AttendeeTab.tsx @@ -0,0 +1,190 @@ +import React, { useRef, useState } from 'react'; +import { useAccess } from '@umijs/max'; +import { Button, message, Modal, Tag, Drawer, Space } from 'antd'; +import { ActionType, ProColumns, ProTable, ModalForm, ProFormText, ProFormSelect, ProFormDateTimePicker, ProFormTextArea } from '@ant-design/pro-components'; +import { ScanOutlined, EditOutlined, ScheduleOutlined, ExportOutlined } from '@ant-design/icons'; +import { + getAttendeeList, + scanAttendeeEntry, + updateAttendeeResume, + trackInterview, +} from '@/services/jobportal/outdoorFairDetail'; + +interface Props { + fairId: number; +} + +const checkInMethodMap: Record = { + qr_scan: '扫码入场', id_card: '刷身份证', manual: '手动登记', +}; + +const interviewStatusMap: Record = { + none: { text: '未面试', color: 'default' }, + scheduled: { text: '已预约', color: 'blue' }, + in_progress: { text: '面试中', color: 'processing' }, + completed: { text: '已完成', color: 'green' }, + passed: { text: '已通过', color: 'green' }, + failed: { text: '未通过', color: 'red' }, +}; + +const AttendeeTab: React.FC = ({ fairId }) => { + const access = useAccess(); + const actionRef = useRef(); + const [scanModalOpen, setScanModalOpen] = useState(false); + const [resumeDrawerOpen, setResumeDrawerOpen] = useState(false); + const [currentAttendee, setCurrentAttendee] = useState(null); + const [interviewModalOpen, setInterviewModalOpen] = useState(false); + + const columns: ProColumns[] = [ + { title: '姓名', dataIndex: 'name', width: 100 }, + { title: '性别', dataIndex: 'gender', width: 60, hideInSearch: true }, + { title: '手机号', dataIndex: 'phone', width: 130 }, + { title: '身份证号', dataIndex: 'idCard', width: 180, hideInSearch: true }, + { title: '学历', dataIndex: 'education', width: 80, hideInSearch: true }, + { title: '毕业院校', dataIndex: 'graduationSchool', width: 150, hideInSearch: true, ellipsis: true }, + { title: '入场时间', dataIndex: 'checkInTime', valueType: 'dateTime', width: 170, render: (_, r) => r.checkInTime || '未入场' }, + { title: '入场方式', dataIndex: 'checkInMethod', width: 100, hideInSearch: true, + render: (_, r) => checkInMethodMap[r.checkInMethod] || r.checkInMethod }, + { title: '意向企业', dataIndex: 'targetCompanies', width: 180, ellipsis: true, hideInSearch: true }, + { title: '面试状态', dataIndex: 'interviewStatus', width: 100, + render: (_, r) => { + const s = interviewStatusMap[r.interviewStatus] || { text: r.interviewStatus, color: 'default' }; + return {s.text}; + }, + }, + { + title: '操作', valueType: 'option', width: 220, + render: (_, record) => [ + , + , + , + ], + }, + ]; + + return ( +
+ { + const res = await getAttendeeList(fairId, { + current: params.current, pageSize: params.pageSize, + name: params.name, phone: params.phone, interviewStatus: params.interviewStatus, + }); + return { data: res.rows, total: res.total, success: true }; + }} + toolBarRender={() => [ + , + , + ]} + /> + + {/* 模拟扫码入场弹窗 */} + setScanModalOpen(false) }} + onFinish={async (values) => { + const res = await scanAttendeeEntry(values); + if (res.code === 200) { message.success(res.msg); setScanModalOpen(false); actionRef.current?.reload(); return true; } + message.error(res.msg); return false; + }} + > + + + + + + + {/* 简历编辑抽屉 */} + setResumeDrawerOpen(false)} + destroyOnClose + > + { + if (!currentAttendee) return; + const res = await updateAttendeeResume(currentAttendee.id, values); + if (res.code === 200) { message.success('简历更新成功'); setResumeDrawerOpen(false); actionRef.current?.reload(); return true; } + message.error(res.msg); return false; + }} + > + ({ label: v, value: v }))} + /> + + + + + + + {/* 面试跟踪弹窗 */} + setInterviewModalOpen(false) }} + initialValues={currentAttendee} + onFinish={async (values) => { + if (!currentAttendee) return; + const res = await trackInterview({ attendeeId: currentAttendee.id, ...values }); + if (res.code === 200) { message.success('面试信息更新成功'); setInterviewModalOpen(false); actionRef.current?.reload(); return true; } + message.error(res.msg); return false; + }} + > + + + + + +
+ ); +}; + +export default AttendeeTab; diff --git a/src/pages/Jobfair/Outdoorfair/Detail/components/BoothTab.tsx b/src/pages/Jobfair/Outdoorfair/Detail/components/BoothTab.tsx new file mode 100644 index 0000000..fbd7710 --- /dev/null +++ b/src/pages/Jobfair/Outdoorfair/Detail/components/BoothTab.tsx @@ -0,0 +1,181 @@ +import React, { useRef, useState } from 'react'; +import { useAccess } from '@umijs/max'; +import { Button, message, Modal, Tag, Dropdown } from 'antd'; +import { ActionType, ProColumns, ProTable, ModalForm, ProFormText, ProFormSelect } from '@ant-design/pro-components'; +import { PlusOutlined, DeleteOutlined, FormOutlined, CheckOutlined, CloseOutlined, SendOutlined, EllipsisOutlined, ApartmentOutlined } from '@ant-design/icons'; +import { + getBoothList, + addBooth, + updateBooth, + deleteBooth, + reviewBooth, + assignBooth, +} from '@/services/jobportal/outdoorFairDetail'; +import { getCompanyRegistrationList } from '@/services/jobportal/outdoorFairDetail'; + +interface Props { + fairId: number; +} + +const reviewStatusMap: Record = { + pending: { text: '待审核', color: 'default' }, + approved: { text: '已通过', color: 'green' }, + rejected: { text: '已拒绝', color: 'red' }, +}; + +const BoothTab: React.FC = ({ fairId }) => { + const access = useAccess(); + const actionRef = useRef(); + const [modalOpen, setModalOpen] = useState(false); + const [editingBooth, setEditingBooth] = useState(null); + const [assignModalOpen, setAssignModalOpen] = useState(false); + const [assigningBooth, setAssigningBooth] = useState(null); + + const columns: ProColumns[] = [ + { title: '展位号', dataIndex: 'boothNumber', width: 100 }, + { title: '企业名称', dataIndex: 'companyName', ellipsis: true, render: (_, r) => r.companyName || '--' }, + { title: '岗位数', dataIndex: 'jobCount', width: 80, hideInSearch: true }, + { title: '审核状态', dataIndex: 'reviewStatus', width: 100, + render: (_, r) => { + const s = reviewStatusMap[r.reviewStatus] || { text: r.reviewStatus, color: 'default' }; + return {s.text}; + }, + }, + { title: '分配时间', dataIndex: 'assignedTime', valueType: 'dateTime', width: 170, hideInSearch: true, render: (_, r) => r.assignedTime || '--' }, + { + title: '操作', valueType: 'option', width: 150, + render: (_, record) => { + const menuItems = [ + { + key: 'assign', + icon: , + label: '分配展位', + onClick: () => { setAssigningBooth(record); setAssignModalOpen(true); }, + }, + { + key: 'edit', + icon: , + label: '编辑', + onClick: () => { setEditingBooth(record); setModalOpen(true); }, + }, + { + key: 'notify', + icon: , + label: '发送通知', + disabled: !record.companyName, + onClick: () => { + if (!record.companyName) { message.warning('该展位尚未分配企业'); return; } + message.success(`已向「${record.companyName}」发送展位信息通知(模拟)`); + }, + }, + { type: 'divider' as const }, + { + key: 'delete', + icon: , + label: '删除', + danger: true, + onClick: () => { + Modal.confirm({ + title: '确认删除', content: `确定删除展位「${record.boothNumber}」吗?`, + onOk: async () => { + const res = await deleteBooth(record.id); + if (res.code === 200) { message.success('删除成功'); actionRef.current?.reload(); } + }, + }); + }, + }, + ]; + return [ + record.reviewStatus === 'pending' && ( + + ), + record.reviewStatus === 'pending' && ( + + ), + + , + ]} + /> + + {/* 新增/编辑展位 */} + setModalOpen(false) }} + onFinish={async (values) => { + const res = editingBooth + ? await updateBooth({ id: editingBooth.id, ...values }) + : await addBooth({ fairId, ...values }); + if (res.code === 200) { message.success(res.msg); setModalOpen(false); actionRef.current?.reload(); return true; } + message.error(res.msg); return false; + }} + initialValues={editingBooth} + > + + + + {/* 分配展位给企业 */} + setAssignModalOpen(false) }} + onFinish={async (values) => { + if (!assigningBooth) return false; + const res = await assignBooth({ + fairId, boothId: assigningBooth.id, companyId: values.companyId, boothNumber: assigningBooth.boothNumber, + }); + if (res.code === 200) { message.success(res.msg); setAssignModalOpen(false); actionRef.current?.reload(); return true; } + message.error(res.msg); return false; + }} + > + { + const res = await getCompanyRegistrationList(fairId, { reviewStatus: 'approved' }); + return res.rows.map((r: any) => ({ label: r.companyName, value: r.companyId })); + }} + placeholder="请搜索并选择企业" + /> + + + ); +}; + +export default BoothTab; diff --git a/src/pages/Jobfair/Outdoorfair/Detail/components/CompanyAddModal.tsx b/src/pages/Jobfair/Outdoorfair/Detail/components/CompanyAddModal.tsx new file mode 100644 index 0000000..8c4bc7f --- /dev/null +++ b/src/pages/Jobfair/Outdoorfair/Detail/components/CompanyAddModal.tsx @@ -0,0 +1,80 @@ +import React, { useRef, useState } from 'react'; +import { Modal, message } from 'antd'; +import { ActionType, ProTable, ProColumns } from '@ant-design/pro-components'; +import { addParticipatingCompany } from '@/services/jobportal/outdoorFairDetail'; + +interface Props { + open: boolean; + fairId: number; + onCancel: () => void; + onSuccess: () => void; +} + +/** 模拟可添加的企业列表 */ +const MOCK_AVAILABLE_COMPANIES = [ + { companyId: 1013, companyName: '石河子市天富集团', industry: '能源', scale: '1000人以上', contactPerson: '黄经理', contactPhone: '13309930001' }, + { companyId: 1014, companyName: '新疆兵团勘察设计院', industry: '建筑/工程', scale: '200-500人', contactPerson: '曹院长', contactPhone: '13309930002' }, + { companyId: 1015, companyName: '石河子市商业银行', industry: '金融', scale: '500-1000人', contactPerson: '钱行长', contactPhone: '13309930003' }, + { companyId: 1016, companyName: '兵团广播电视大学', industry: '教育/科研', scale: '200-500人', contactPerson: '杨校长', contactPhone: '13309930004' }, + { companyId: 1017, companyName: '新疆如意纺织服装有限公司', industry: '纺织/服装', scale: '1000人以上', contactPerson: '林经理', contactPhone: '13309930005' }, + { companyId: 1018, companyName: '石河子市人民医院', industry: '医疗/卫生', scale: '1000人以上', contactPerson: '郝主任', contactPhone: '13309930006' }, + { companyId: 1019, companyName: '兵团信息技术服务中心', industry: 'IT/互联网', scale: '100-200人', contactPerson: '孙主管', contactPhone: '13309930007' }, + { companyId: 1020, companyName: '新疆燕京啤酒有限公司', industry: '快消/食品', scale: '500-1000人', contactPerson: '吕厂长', contactPhone: '13309930008' }, +]; + +const CompanyAddModal: React.FC = ({ open, fairId, onCancel, onSuccess }) => { + const actionRef = useRef(); + const [selectedRows, setSelectedRows] = useState([]); + + const columns: ProColumns[] = [ + { title: '企业名称', dataIndex: 'companyName', ellipsis: true }, + { title: '行业', dataIndex: 'industry', width: 80 }, + { title: '规模', dataIndex: 'scale', width: 100 }, + { title: '联系人', dataIndex: 'contactPerson', width: 80 }, + { title: '联系电话', dataIndex: 'contactPhone', width: 130 }, + ]; + + const handleOk = async () => { + if (selectedRows.length === 0) { + message.warning('请选择至少一家企业'); + return; + } + let success = 0; + for (const row of selectedRows) { + const res = await addParticipatingCompany({ fairId, ...row }); + if (res.code === 200) success++; + } + message.success(`成功添加 ${success} 家企业`); + setSelectedRows([]); + onSuccess(); + }; + + return ( + { setSelectedRows([]); onCancel(); }} + onOk={handleOk} + okText="确认添加" + > + r.companyId), + onChange: (_, rows) => setSelectedRows(rows), + }} + tableAlertRender={false} + /> + + ); +}; + +export default CompanyAddModal; diff --git a/src/pages/Jobfair/Outdoorfair/Detail/components/CompanyReviewTab.tsx b/src/pages/Jobfair/Outdoorfair/Detail/components/CompanyReviewTab.tsx new file mode 100644 index 0000000..574f470 --- /dev/null +++ b/src/pages/Jobfair/Outdoorfair/Detail/components/CompanyReviewTab.tsx @@ -0,0 +1,178 @@ +import React, { useRef, useState } from 'react'; +import { useAccess } from '@umijs/max'; +import { Button, message, Modal, Tag, Space } from 'antd'; +import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components'; +import { CheckOutlined, CloseOutlined, QrcodeOutlined, SendOutlined } from '@ant-design/icons'; +import { + getCompanyRegistrationList, + reviewCompanyRegistration, + reviewRegisteredJob, + qrCheckIn, +} from '@/services/jobportal/outdoorFairDetail'; + +interface Props { + fairId: number; +} + +const reviewStatusMap: Record = { + pending: { text: '待审核', color: 'default' }, + approved: { text: '已通过', color: 'green' }, + rejected: { text: '已拒绝', color: 'red' }, +}; + +const CompanyReviewTab: React.FC = ({ fairId }) => { + const access = useAccess(); + const actionRef = useRef(); + const [rejectModalOpen, setRejectModalOpen] = useState(false); + const [rejectTarget, setRejectTarget] = useState(null); + + const handleReviewCompany = async (id: number, status: string) => { + if (status === 'rejected') { + Modal.confirm({ + title: '确认拒绝', content: '确定拒绝该单位的参会申请吗?', + onOk: async () => { + const res = await reviewCompanyRegistration(id, status); + if (res.code === 200) { message.success(res.msg); actionRef.current?.reload(); } + }, + }); + } else { + const res = await reviewCompanyRegistration(id, status); + if (res.code === 200) { message.success(res.msg); actionRef.current?.reload(); } + } + }; + + const handleReviewJob = async (regId: number, jobId: number, status: string) => { + const res = await reviewRegisteredJob(regId, jobId, status); + if (res.code === 200) { message.success(res.msg); actionRef.current?.reload(); } + }; + + const handleQrCheckIn = async (id: number) => { + Modal.confirm({ + title: '模拟扫码签到', + content: '确认该单位已完成现场扫码签到吗?(模拟操作)', + onOk: async () => { + const res = await qrCheckIn(id); + if (res.code === 200) { message.success(res.msg); actionRef.current?.reload(); } + }, + }); + }; + + const handleNotify = (companyName: string, method: string) => { + message.success(`已通过${method}向「${companyName}」发送审核结果通知(模拟)`); + }; + + const columns: ProColumns[] = [ + { title: '企业名称', dataIndex: 'companyName', ellipsis: true }, + { title: '行业', dataIndex: 'industry', width: 80, hideInSearch: true, render: (_, r) => {r.industry} }, + { title: '规模', dataIndex: 'scale', width: 100, hideInSearch: true }, + { title: '联系人', dataIndex: 'contactPerson', width: 100, hideInSearch: true }, + { title: '联系电话', dataIndex: 'contactPhone', width: 130, hideInSearch: true }, + { title: '审核状态', dataIndex: 'reviewStatus', width: 100, + render: (_, r) => { + const s = reviewStatusMap[r.reviewStatus] || { text: r.reviewStatus, color: 'default' }; + return {s.text}; + }, + }, + { title: '签到状态', dataIndex: 'qrCheckInStatus', width: 100, + render: (_, r) => + {r.qrCheckInStatus === 'checked_in' ? '已签到' : '未签到'} + , + }, + { title: '签到时间', dataIndex: 'checkInTime', valueType: 'dateTime', width: 170, hideInSearch: true, render: (_, r) => r.checkInTime || '--' }, + { + title: '操作', valueType: 'option', width: 300, + render: (_, record) => [ + record.reviewStatus === 'pending' && ( + + ), + record.reviewStatus === 'pending' && ( + + ), + record.reviewStatus === 'approved' && ( + <> + + + {record.qrCheckInStatus !== 'checked_in' && ( + + )} + + ), + ], + }, + ]; + + // 展开行:岗位审核 + const expandedRowRender = (record: any) => { + if (!record.registeredJobs || record.registeredJobs.length === 0) { + return
该单位未发布岗位
; + } + return ( + `${r.minSalary}K-${r.maxSalary}K` }, + { title: '学历要求', dataIndex: 'education', width: 80 }, + { title: '招聘人数', dataIndex: 'vacancies', width: 80 }, + { title: '审核状态', dataIndex: 'reviewStatus', width: 100, + render: (_, r: any) => { + const s = reviewStatusMap[r.reviewStatus] || { text: r.reviewStatus, color: 'default' }; + return {s.text}; + }, + }, + { + title: '操作', valueType: 'option', width: 150, + render: (_, jobRecord: any) => [ + jobRecord.reviewStatus === 'pending' && ( + + ), + jobRecord.reviewStatus === 'pending' && ( + + ), + ], + }, + ]} + /> + ); + }; + + return ( + { + const res = await getCompanyRegistrationList(fairId, { + current: params.current, pageSize: params.pageSize, + companyName: params.companyName, reviewStatus: params.reviewStatus, + }); + return { data: res.rows, total: res.total, success: true }; + }} + /> + ); +}; + +export default CompanyReviewTab; diff --git a/src/pages/Jobfair/Outdoorfair/Detail/components/DeviceTab.tsx b/src/pages/Jobfair/Outdoorfair/Detail/components/DeviceTab.tsx new file mode 100644 index 0000000..7c0e473 --- /dev/null +++ b/src/pages/Jobfair/Outdoorfair/Detail/components/DeviceTab.tsx @@ -0,0 +1,291 @@ +import React, { useRef, useState, useEffect } from 'react'; +import { useAccess } from '@umijs/max'; +import { Button, message, Modal, Tag, Row, Col, Card, Switch, Statistic, Badge, Dropdown, Space } from 'antd'; +import { ActionType, ProColumns, ProTable, ModalForm, ProFormText, ProFormSelect } from '@ant-design/pro-components'; +import { PlusOutlined, DeleteOutlined, FormOutlined, QrcodeOutlined, SwapOutlined, ApartmentOutlined, ScanOutlined, EllipsisOutlined } from '@ant-design/icons'; +import { + getDeviceList, + addDevice, + updateDevice, + deleteDevice, + assignDevice, + toggleDeviceStatus, + getGateStatuses, + toggleGate, + getGateEntryLogs, + simulateGateScan, +} from '@/services/jobportal/outdoorFairDetail'; +import { getBoothList } from '@/services/jobportal/outdoorFairDetail'; + +interface Props { + fairId: number; +} + +const deviceTypeMap: Record = { + mobile_terminal: '移动终端', qr_sticker: '展位二维码', +}; + +const DeviceTab: React.FC = ({ fairId }) => { + const access = useAccess(); + const mobileActionRef = useRef(); + const qrActionRef = useRef(); + const gateLogActionRef = useRef(); + const [deviceModalOpen, setDeviceModalOpen] = useState(false); + const [editingDevice, setEditingDevice] = useState(null); + const [defaultType, setDefaultType] = useState('mobile_terminal'); + const [assignModalOpen, setAssignModalOpen] = useState(false); + const [assigningDevice, setAssigningDevice] = useState(null); + const [gateStatuses, setGateStatuses] = useState([]); + const [scanModalOpen, setScanModalOpen] = useState(false); + const [scanGate, setScanGate] = useState(null); + + const fetchGateStatuses = async () => { + const res = await getGateStatuses(fairId); + setGateStatuses(res.rows || []); + }; + + useEffect(() => { + fetchGateStatuses(); + }, [fairId]); + + const handleToggleGate = async (id: number) => { + const res = await toggleGate(id); + if (res.code === 200) { message.success(res.msg); fetchGateStatuses(); } + }; + + const deviceColumns = (type: string): ProColumns[] => [ + { title: '设备名称', dataIndex: 'deviceName', width: 120, ellipsis: true }, + { title: '设备编号', dataIndex: 'deviceCode', width: 180, hideInSearch: true }, + { title: '分配展位', dataIndex: 'assignedBoothNumber', width: 100, hideInSearch: true, render: (_, r) => r.assignedBoothNumber || '--' }, + { title: '分配企业', dataIndex: 'assignedCompanyName', width: 180, ellipsis: true, hideInSearch: true, render: (_, r) => r.assignedCompanyName || '--' }, + { title: '状态', dataIndex: 'status', width: 80, hideInSearch: true, + render: (_, r) => , + }, + { title: '最后活跃', dataIndex: 'lastActiveTime', width: 170, hideInSearch: true, render: (_, r) => r.lastActiveTime || '--' }, + { + title: '操作', valueType: 'option', width: 120, + render: (_, record) => { + const isQrType = type === 'qr_sticker'; + const menuItems = [ + { + key: 'toggle', + icon: record.status === 'online' ? : , + label: record.status === 'online' ? '禁用' : '启用', + onClick: async () => { + const res = await toggleDeviceStatus(record.id); + if (res.code === 200) { message.success(res.msg); type === 'mobile_terminal' ? mobileActionRef.current?.reload() : qrActionRef.current?.reload(); } + }, + }, + { + key: 'assign', + icon: , + label: '分配展位', + onClick: () => { setAssigningDevice(record); setAssignModalOpen(true); }, + }, + ...(isQrType ? [{ + key: 'qrcode', + icon: , + label: '重新生成二维码', + onClick: () => message.success(`已为「${record.deviceName}」重新生成二维码(模拟)`), + }] : []), + { + key: 'edit', + icon: , + label: '编辑', + onClick: () => { setEditingDevice(record); setDefaultType(record.type); setDeviceModalOpen(true); }, + }, + { type: 'divider' as const }, + { + key: 'delete', + icon: , + label: '删除', + danger: true, + onClick: () => { + Modal.confirm({ + title: '确认删除', content: `确定删除「${record.deviceName}」吗?`, + onOk: async () => { + const res = await deleteDevice(record.id); + if (res.code === 200) { message.success('删除成功'); type === 'mobile_terminal' ? mobileActionRef.current?.reload() : qrActionRef.current?.reload(); } + }, + }); + }, + }, + ]; + return ( + + + + ); + }, + }, + ]; + + return ( +
+ +
+ } + onClick={() => { setEditingDevice(null); setDefaultType('mobile_terminal'); setDeviceModalOpen(true); }} + >新增 + } + bodyStyle={{ padding: 0 }} + > + { + const res = await getDeviceList(fairId, { current: params.current, pageSize: params.pageSize, type: 'mobile_terminal' }); + return { data: res.rows, total: res.total, success: true }; + }} + /> + + + + } + onClick={() => { setEditingDevice(null); setDefaultType('qr_sticker'); setDeviceModalOpen(true); }} + >新增 + } + bodyStyle={{ padding: 0 }} + > + { + const res = await getDeviceList(fairId, { current: params.current, pageSize: params.pageSize, type: 'qr_sticker' }); + return { data: res.rows, total: res.total, success: true }; + }} + /> + + + + + {/* 模拟闸机 */} + + + {gateStatuses.map((gate: any) => ( + + + + + {gate.gateName} + handleToggleGate(gate.id)} + checkedChildren="开" unCheckedChildren="关" /> + {gate.isOpen ? '已开启' : '已关闭'} + + + + + + + ))} + + r.scanMethod === 'qr' ? '二维码' : '身份证' }, + ]} + request={async (params) => { + const res = await getGateEntryLogs(fairId, { current: params.current, pageSize: params.pageSize }); + return { data: res.rows, total: res.total, success: true }; + }} + /> + + + {/* 新增/编辑设备 */} + setDeviceModalOpen(false) }} + initialValues={editingDevice || { type: defaultType }} + onFinish={async (values) => { + const res = editingDevice + ? await updateDevice({ id: editingDevice.id, ...values }) + : await addDevice({ fairId, ...values }); + if (res.code === 200) { message.success(res.msg); setDeviceModalOpen(false); mobileActionRef.current?.reload(); qrActionRef.current?.reload(); return true; } + message.error(res.msg); return false; + }} + > + + + + + + {/* 分配展位 */} + setAssignModalOpen(false) }} + onFinish={async (values) => { + if (!assigningDevice) return; + const booth = (await getBoothList(fairId, { current: 1, pageSize: 100 })).rows.find((b: any) => b.id === values.boothId); + const res = await assignDevice(assigningDevice.id, values.boothId, booth?.boothNumber || '', booth?.companyName || ''); + if (res.code === 200) { message.success(res.msg); setAssignModalOpen(false); mobileActionRef.current?.reload(); qrActionRef.current?.reload(); return true; } + message.error(res.msg); return false; + }} + > + { + const res = await getBoothList(fairId); + return res.rows.map((b: any) => ({ label: `${b.boothNumber} (${b.companyName || '未分配'})`, value: b.id })); + }} + placeholder="请搜索并选择展位" + /> + + + {/* 模拟闸机扫码 */} + setScanModalOpen(false) }} + onFinish={async (values) => { + if (!scanGate) return; + const res = await simulateGateScan({ fairId, gateName: scanGate.gateName, ...values }); + if (res.code === 200) { message.success(res.msg); setScanModalOpen(false); gateLogActionRef.current?.reload(); fetchGateStatuses(); return true; } + message.error(res.msg); return false; + }} + > + + + + + ); +}; + +export default DeviceTab; diff --git a/src/pages/Jobfair/Outdoorfair/Detail/components/FairInfoTab.tsx b/src/pages/Jobfair/Outdoorfair/Detail/components/FairInfoTab.tsx new file mode 100644 index 0000000..be2c183 --- /dev/null +++ b/src/pages/Jobfair/Outdoorfair/Detail/components/FairInfoTab.tsx @@ -0,0 +1,234 @@ +import React, { useRef, useState } from 'react'; +import { useAccess } from '@umijs/max'; +import { Button, Card, Descriptions, List, Tag, message, Modal, Space } from 'antd'; +import { ActionType, ProTable } from '@ant-design/pro-components'; +import { PlusOutlined, DeleteOutlined, FormOutlined } from '@ant-design/icons'; +import type { OutdoorFairItem } from '@/services/jobportal/outdoorFair'; +import { updateOutdoorFair } from '@/services/jobportal/outdoorFair'; +import { + getParticipatingCompanies, + addParticipatingCompany, + removeParticipatingCompany, + addJobForCompany, + updateJobForCompany, + removeJobFromCompany, +} from '@/services/jobportal/outdoorFairDetail'; +import EditModal from '@/pages/Jobfair/Outdoorfair/components/EditModal'; +import CompanyAddModal from './CompanyAddModal'; +import JobEditModal from './JobEditModal'; + +interface Props { + fairId: number; + fairInfo: OutdoorFairItem; + onFairUpdate: () => void; +} + +const FairInfoTab: React.FC = ({ fairId, fairInfo, onFairUpdate }) => { + const access = useAccess(); + const actionRef = useRef(); + const [editModalOpen, setEditModalOpen] = useState(false); + const [companyModalOpen, setCompanyModalOpen] = useState(false); + const [jobModalOpen, setJobModalOpen] = useState(false); + const [selectedCompany, setSelectedCompany] = useState(null); + const [editingJob, setEditingJob] = useState(null); + + const fairTypeColors: Record = { + '综合类': 'blue', '校园招聘': 'green', '行业专场': 'orange', + '高新技术专场': 'purple', '智能制造专场': 'cyan', '文旅专场': 'magenta', + '其他': 'default', + }; + + return ( +
+ {/* 基本信息卡片 */} + } + hidden={!access.hasPerms('cms:outdoorFair:edit')} + onClick={() => setEditModalOpen(true)} + > + 编辑信息 + + } + style={{ marginBottom: 16 }} + > + + {fairInfo.title} + {fairInfo.hostUnit} + + {fairInfo.fairType} + + {fairInfo.region} + {fairInfo.address} + {fairInfo.boothCount} + {fairInfo.holdTime} + {fairInfo.endTime} + {fairInfo.applyStartTime} + {fairInfo.applyEndTime} + {fairInfo.onlineApply ? '是' : '否'} + + + + {/* 参会企业及岗位 */} + } + onClick={() => setCompanyModalOpen(true)} + > + 添加企业 + + } + > + { + const res = await getParticipatingCompanies(fairId, { + current: params.current, + pageSize: params.pageSize, + companyName: params.companyName, + }); + return { data: res.rows, total: res.total, success: true }; + }} + expandable={{ + expandedRowRender: (record) => ( +
+ {record.jobList.length === 0 ? ( +

暂无岗位

+ ) : ( + ( + { setEditingJob(job); setSelectedCompany(record); setJobModalOpen(true); }} + > + 编辑 + , + , + ]} + > + + 薪资 {job.minSalary}-{job.maxSalary}K + {job.education} + {job.experience} + 招聘 {job.vacancies} 人 + + {job.status === 'active' ? '在招' : '已关闭'} + + + } + /> + + )} + /> + )} +
+ ), + }} + columns={[ + { title: '企业名称', dataIndex: 'companyName', ellipsis: true }, + { title: '行业', dataIndex: 'industry', width: 100, hideInSearch: true, + render: (_, r: any) => {r.industry} }, + { title: '规模', dataIndex: 'scale', width: 110, hideInSearch: true }, + { title: '联系人', dataIndex: 'contactPerson', width: 100, hideInSearch: true }, + { title: '联系电话', dataIndex: 'contactPhone', width: 130, hideInSearch: true }, + { title: '展位号', dataIndex: 'boothNumber', width: 80, hideInSearch: true, + render: (_, r: any) => r.boothNumber || '--' }, + { title: '岗位数', width: 80, hideInSearch: true, + render: (_, r: any) => r.jobList?.length || 0 }, + { + title: '操作', valueType: 'option', width: 200, + render: (_, record: any) => [ + , + , + ], + }, + ]} + /> +
+ + {/* 编辑招聘会基本信息弹窗 */} + setEditModalOpen(false)} + onSubmit={async (values) => { + const res = await updateOutdoorFair(values as any); + if (res.code === 200) { + message.success('修改成功'); + setEditModalOpen(false); + onFairUpdate(); + } else { + message.error(res.msg || '操作失败'); + } + }} + /> + + {/* 添加企业弹窗 */} + setCompanyModalOpen(false)} + onSuccess={() => { setCompanyModalOpen(false); actionRef.current?.reload(); }} + /> + + {/* 添加/编辑岗位弹窗 */} + { setJobModalOpen(false); setEditingJob(null); setSelectedCompany(null); }} + onSuccess={() => { setJobModalOpen(false); setEditingJob(null); actionRef.current?.reload(); }} + /> +
+ ); +}; + +export default FairInfoTab; diff --git a/src/pages/Jobfair/Outdoorfair/Detail/components/JobEditModal.tsx b/src/pages/Jobfair/Outdoorfair/Detail/components/JobEditModal.tsx new file mode 100644 index 0000000..ad8aecc --- /dev/null +++ b/src/pages/Jobfair/Outdoorfair/Detail/components/JobEditModal.tsx @@ -0,0 +1,65 @@ +import React, { useEffect } from 'react'; +import { ModalForm, ProForm, ProFormText, ProFormDigit, ProFormSelect } from '@ant-design/pro-components'; +import { Form } from 'antd'; +import { addJobForCompany, updateJobForCompany } from '@/services/jobportal/outdoorFairDetail'; + +interface Props { + open: boolean; + fairId: number; + company: any; + job: any; + onCancel: () => void; + onSuccess: () => void; +} + +const JobEditModal: React.FC = ({ open, fairId, company, job, onCancel, onSuccess }) => { + const [form] = Form.useForm(); + + useEffect(() => { + if (open) { + form.resetFields(); + if (job) { + form.setFieldsValue(job); + } + } + }, [open, job, form]); + + return ( + { + if (job) { + const res = await updateJobForCompany({ ...job, ...values }); + if (res.code === 200) { onSuccess(); return true; } + } else if (company) { + const res = await addJobForCompany({ fairId, companyId: company.id, ...values }); + if (res.code === 200) { onSuccess(); return true; } + } + return false; + }} + > + + + + + + + ({ label: v, value: v }))} + rules={[{ required: true }]} + /> + ({ label: v, value: v }))} + rules={[{ required: true }]} + /> + + + + ); +}; + +export default JobEditModal; diff --git a/src/pages/Jobfair/Outdoorfair/Detail/components/StatisticsTab.tsx b/src/pages/Jobfair/Outdoorfair/Detail/components/StatisticsTab.tsx new file mode 100644 index 0000000..4e6262e --- /dev/null +++ b/src/pages/Jobfair/Outdoorfair/Detail/components/StatisticsTab.tsx @@ -0,0 +1,108 @@ +import React from 'react'; +import { Card, Col, Row, Statistic, Empty, Spin, Table } from 'antd'; +import { TeamOutlined, FileTextOutlined, UserOutlined, LinkOutlined } from '@ant-design/icons'; + +interface Props { + fairId: number; +} + +const StatisticsTab: React.FC = () => { + // 静态演示数据 + const companyCount = 25; + const jobCount = 86; + const demandCount = 520; + const intentionCount = 180; + + const dailyData = [ + { key: '1', date: '2026-03-15', count: 320 }, + ]; + + const industryData = [ + { key: '1', industry: '化工', count: 5 }, + { key: '2', industry: '政府', count: 2 }, + { key: '3', industry: '农业', count: 4 }, + { key: '4', industry: '医疗', count: 3 }, + { key: '5', industry: '教育', count: 3 }, + { key: '6', industry: '制造', count: 4 }, + ]; + + const jobTypeData = [ + { key: '1', type: '技术类', count: 30 }, + { key: '2', type: '管理类', count: 15 }, + { key: '3', type: '销售类', count: 12 }, + { key: '4', type: '行政类', count: 10 }, + { key: '5', type: '一线工人', count: 14 }, + { key: '6', type: '其他', count: 5 }, + ]; + + const timeSlotData = [ + { key: '1', slot: '08:00-09:00', count: 80 }, + { key: '2', slot: '09:00-10:00', count: 120 }, + { key: '3', slot: '10:00-11:00', count: 65 }, + { key: '4', slot: '11:00-12:00', count: 30 }, + ]; + + return ( +
+ +
+ + } suffix="家" /> + + + + + } suffix="个" /> + + + + + } suffix="人" /> + + + + + } suffix="人" /> + + + + + + + +
`${v} 人` }, + ]} dataSource={dailyData} pagination={false} size="small" /> + + + + +
`${v} 家` }, + ]} dataSource={industryData} pagination={false} size="small" /> + + + + +
`${v} 个` }, + ]} dataSource={jobTypeData} pagination={false} size="small" /> + + + + +
`${v} 人` }, + ]} dataSource={timeSlotData} pagination={false} size="small" /> + + + + + ); +}; + +export default StatisticsTab; diff --git a/src/pages/Jobfair/Outdoorfair/Detail/index.tsx b/src/pages/Jobfair/Outdoorfair/Detail/index.tsx new file mode 100644 index 0000000..c056554 --- /dev/null +++ b/src/pages/Jobfair/Outdoorfair/Detail/index.tsx @@ -0,0 +1,98 @@ +import React, { useState, useEffect } from 'react'; +import { history, useSearchParams } from '@umijs/max'; +import { PageContainer } from '@ant-design/pro-components'; +import { Tabs, Button, Spin, message } from 'antd'; +import { ArrowLeftOutlined } from '@ant-design/icons'; +import { getOutdoorFairList } from '@/services/jobportal/outdoorFair'; +import type { OutdoorFairItem } from '@/services/jobportal/outdoorFair'; +import FairInfoTab from './components/FairInfoTab'; +import BoothTab from './components/BoothTab'; +import CompanyReviewTab from './components/CompanyReviewTab'; +import AttendeeTab from './components/AttendeeTab'; +import DeviceTab from './components/DeviceTab'; +import StatisticsTab from './components/StatisticsTab'; + +const OutdoorFairDetail: React.FC = () => { + const [searchParams] = useSearchParams(); + const fairId = Number(searchParams.get('id')); + const [fairInfo, setFairInfo] = useState(null); + const [loading, setLoading] = useState(true); + const [activeTab, setActiveTab] = useState('fair-info'); + + const fetchFairInfo = async () => { + setLoading(true); + try { + const res = await getOutdoorFairList({ current: 1, pageSize: 100 }); + const found = res.rows.find((item) => item.id === fairId); + if (found) { + setFairInfo(found); + } else { + message.error('未找到该招聘会信息'); + } + } finally { + setLoading(false); + } + }; + + useEffect(() => { + if (fairId) { + fetchFairInfo(); + } + }, [fairId]); + + const tabItems = [ + { key: 'fair-info', label: '招聘会管理' }, + { key: 'booth', label: '展位管理' }, + { key: 'company-review', label: '参会单位管理' }, + { key: 'attendee', label: '参会人员管理' }, + { key: 'device', label: '入场设备管理' }, + { key: 'statistics', label: '数据统计' }, + ]; + + return ( + history.back(), + extra: [ + , + ], + }} + > + + {fairInfo && ( + ({ + key: tab.key, + label: tab.label, + children: (() => { + switch (tab.key) { + case 'fair-info': + return ; + case 'booth': + return ; + case 'company-review': + return ; + case 'attendee': + return ; + case 'device': + return ; + case 'statistics': + return ; + default: + return null; + } + })(), + }))} + /> + )} + + + ); +}; + +export default OutdoorFairDetail; diff --git a/src/pages/Jobfair/Outdoorfair/components/EditModal.tsx b/src/pages/Jobfair/Outdoorfair/components/EditModal.tsx new file mode 100644 index 0000000..f9a975f --- /dev/null +++ b/src/pages/Jobfair/Outdoorfair/components/EditModal.tsx @@ -0,0 +1,170 @@ +import React, { useEffect } from 'react'; +import { + ModalForm, + ProForm, + ProFormText, + ProFormDigit, + ProFormDateTimePicker, + ProFormSwitch, + ProFormSelect, +} from '@ant-design/pro-components'; +import { Form } from 'antd'; +import type { OutdoorFairItem, OutdoorFairForm } from '@/services/jobportal/outdoorFair'; + +interface EditModalProps { + open: boolean; + values?: OutdoorFairItem; + onCancel: () => void; + onSubmit: (values: OutdoorFairForm) => Promise; +} + +/** 招聘会类型选项 */ +const FAIR_TYPE_OPTIONS = [ + { label: '综合类', value: '综合类' }, + { label: '校园招聘', value: '校园招聘' }, + { label: '行业专场', value: '行业专场' }, + { label: '高新技术专场', value: '高新技术专场' }, + { label: '智能制造专场', value: '智能制造专场' }, + { label: '文旅专场', value: '文旅专场' }, + { label: '其他', value: '其他' }, +]; + +/** 举办区域选项 */ +const REGION_OPTIONS = [ + { label: '石河子市', value: '石河子市' }, + { label: '第一师(阿拉尔市)', value: '第一师(阿拉尔市)' }, + { label: '第二师(铁门关市)', value: '第二师(铁门关市)' }, + { label: '第三师(图木舒克市)', value: '第三师(图木舒克市)' }, + { label: '第四师(可克达拉市)', value: '第四师(可克达拉市)' }, + { label: '第五师(双河市)', value: '第五师(双河市)' }, + { label: '第六师(五家渠市)', value: '第六师(五家渠市)' }, + { label: '第七师(胡杨河市)', value: '第七师(胡杨河市)' }, + { label: '第八师(石河子市)', value: '第八师(石河子市)' }, + { label: '第九师(白杨市)', value: '第九师(白杨市)' }, + { label: '第十师(北屯市)', value: '第十师(北屯市)' }, + { label: '第十一师(建工师)', value: '第十一师(建工师)' }, + { label: '第十二师', value: '第十二师' }, + { label: '第十三师(新星市)', value: '第十三师(新星市)' }, + { label: '第十四师(昆玉市)', value: '第十四师(昆玉市)' }, +]; + +const EditModal: React.FC = ({ open, values, onCancel, onSubmit }) => { + const [form] = Form.useForm(); + + useEffect(() => { + if (open) { + form.resetFields(); + if (values) { + form.setFieldsValue(values); + } + } + }, [open, values, form]); + + return ( + { + await onSubmit({ ...formValues, id: values?.id } as OutdoorFairForm); + }} + > + + ); +}; + +export default EditModal; diff --git a/src/pages/Jobfair/Outdoorfair/index.tsx b/src/pages/Jobfair/Outdoorfair/index.tsx new file mode 100644 index 0000000..1719362 --- /dev/null +++ b/src/pages/Jobfair/Outdoorfair/index.tsx @@ -0,0 +1,221 @@ +import React, { useRef, useState } from 'react'; +import { useAccess, history } from '@umijs/max'; +import { Button, message, Modal } from 'antd'; +import { ActionType, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components'; +import { PlusOutlined, DeleteOutlined, FormOutlined, EyeOutlined } from '@ant-design/icons'; +import { + getOutdoorFairList, + addOutdoorFair, + updateOutdoorFair, + deleteOutdoorFair, +} from '@/services/jobportal/outdoorFair'; +import type { + OutdoorFairItem, + OutdoorFairListParams, + OutdoorFairForm, +} from '@/services/jobportal/outdoorFair'; +import EditModal from './components/EditModal'; + +const OutdoorFairList: React.FC = () => { + const access = useAccess(); + const actionRef = useRef(); + const [modalVisible, setModalVisible] = useState(false); + const [currentRow, setCurrentRow] = useState(); + + const handleDelete = async (id: number) => { + Modal.confirm({ + title: '确认删除', + content: '确定要删除该户外招聘会吗?', + onOk: async () => { + const res = await deleteOutdoorFair(id); + if (res.code === 200) { + message.success('删除成功'); + actionRef.current?.reload(); + } else { + message.error(res.msg || '删除失败'); + } + }, + }); + }; + + const columns: ProColumns[] = [ + { + title: '招聘会标题', + dataIndex: 'title', + ellipsis: true, + }, + { + title: '举办单位', + dataIndex: 'hostUnit', + ellipsis: true, + }, + { + title: '招聘会类型', + dataIndex: 'fairType', + ellipsis: true, + hideInSearch: true, + }, + { + title: '举办区域', + dataIndex: 'region', + ellipsis: true, + hideInSearch: true, + }, + { + title: '举办地址', + dataIndex: 'address', + ellipsis: true, + hideInSearch: true, + }, + { + title: '展位数量', + dataIndex: 'boothCount', + hideInSearch: true, + }, + { + title: '举办时间', + dataIndex: 'holdTime', + valueType: 'dateTime', + hideInSearch: true, + }, + { + title: '截止时间', + dataIndex: 'endTime', + valueType: 'dateTime', + hideInSearch: true, + }, + { + title: '开放申请', + dataIndex: 'applyStartTime', + valueType: 'dateTime', + hideInSearch: true, + }, + { + title: '截止申请', + dataIndex: 'applyEndTime', + valueType: 'dateTime', + hideInSearch: true, + }, + { + title: '线上申请', + dataIndex: 'onlineApply', + hideInSearch: true, + render: (_, record) => (record.onlineApply ? '是' : '否'), + }, + { + title: '招聘会照片', + dataIndex: 'photoUrl', + hideInSearch: true, + render: (_, record) => + record.photoUrl ? ( + {record.title} + ) : ( + '--' + ), + }, + { + title: '操作', + valueType: 'option', + width: 160, + fixed: 'right', + render: (_, record) => [ + , + , + , + ], + }, + ]; + + return ( + + + headerTitle="户外招聘会管理列表" + actionRef={actionRef} + rowKey="id" + columns={columns} + scroll={{ x: 'max-content' }} + request={async (params) => { + const res = await getOutdoorFairList({ + current: params.current, + pageSize: params.pageSize, + title: params.title, + hostUnit: params.hostUnit, + fairType: params.fairType, + } as OutdoorFairListParams); + return { data: res.rows, total: res.total, success: true }; + }} + toolBarRender={() => [ + , + ]} + /> + { + setModalVisible(false); + setCurrentRow(undefined); + }} + onSubmit={async (values: OutdoorFairForm) => { + const res = values.id + ? await updateOutdoorFair(values) + : await addOutdoorFair(values); + if (res.code === 200) { + message.success(values.id ? '修改成功' : '新增成功'); + setModalVisible(false); + setCurrentRow(undefined); + actionRef.current?.reload(); + } else { + message.error(res.msg || '操作失败'); + } + }} + /> + + ); +}; + +export default OutdoorFairList; diff --git a/src/pages/Livemgmt/components/EditModal.tsx b/src/pages/Livemgmt/components/EditModal.tsx index 0227c28..5aaafb8 100644 --- a/src/pages/Livemgmt/components/EditModal.tsx +++ b/src/pages/Livemgmt/components/EditModal.tsx @@ -51,16 +51,6 @@ const EditModal: React.FC = ({ open, values, onCancel, onSubmit rules={[{ required: true, message: '请输入直播链接' }]} placeholder="请输入直播链接" /> - - (arr: T[], current: number, pageSize: number) { + const start = (current - 1) * pageSize; + return arr.slice(start, start + pageSize); +} + +// ==================== 模块1: 场地信息管理 API ==================== + +export async function getVenueList(params?: API.IndoorJobFair.VenueListParams) { + let list = [...MOCK_VENUES]; + if (params?.employmentAgency) list = list.filter(v => v.employmentAgency.includes(params.employmentAgency!)); + if (params?.venueName) list = list.filter(v => v.venueName.includes(params.venueName!)); + if (params?.venueType) list = list.filter(v => v.venueType === params.venueType); + const total = list.length; + const rows = paginate(list, params?.current || 1, params?.pageSize || 10); + return { code: 200, total, rows }; +} + +export async function addVenue(data: API.IndoorJobFair.VenueForm) { + const venue: API.IndoorJobFair.VenueItem = { + ...data, + id: nextVenueId++, + status: 'active', + createTime: new Date().toISOString().replace('T', ' ').substring(0, 19), + }; + MOCK_VENUES.unshift(venue); + return { code: 200, msg: '添加成功', data: venue }; +} + +export async function updateVenue(data: API.IndoorJobFair.VenueForm) { + const idx = MOCK_VENUES.findIndex(v => v.id === data.id); + if (idx !== -1) { MOCK_VENUES[idx] = { ...MOCK_VENUES[idx], ...data }; return { code: 200, msg: '修改成功' }; } + return { code: 404, msg: '未找到' }; +} + +export async function deleteVenue(id: number) { + const idx = MOCK_VENUES.findIndex(v => v.id === id); + if (idx !== -1) { MOCK_VENUES.splice(idx, 1); return { code: 200, msg: '删除成功' }; } + return { code: 404, msg: '未找到' }; +} + +// ==================== 模块2: 摊位信息管理 API ==================== + +export async function getBoothList(params?: API.IndoorJobFair.BoothListParams) { + let list = [...MOCK_BOOTHS]; + if (params?.venueId) list = list.filter(b => b.venueId === params.venueId); + if (params?.boothNumber) list = list.filter(b => b.boothNumber.includes(params.boothNumber!)); + if (params?.companyName) list = list.filter(b => b.companyName?.includes(params.companyName!)); + const total = list.length; + const rows = paginate(list, params?.current || 1, params?.pageSize || 10); + return { code: 200, total, rows }; +} + +export async function addBooth(data: API.IndoorJobFair.BoothForm) { + const booth: API.IndoorJobFair.BoothItem = { + ...data, + id: nextBoothId++, + status: 'available', + operator: undefined, + operateTime: undefined, + remark: '', + }; + MOCK_BOOTHS.unshift(booth); + return { code: 200, msg: '添加成功', data: booth }; +} + +export async function updateBooth(data: API.IndoorJobFair.BoothForm) { + const idx = MOCK_BOOTHS.findIndex(b => b.id === data.id); + if (idx !== -1) { MOCK_BOOTHS[idx] = { ...MOCK_BOOTHS[idx], ...data }; return { code: 200, msg: '修改成功' }; } + return { code: 404, msg: '未找到' }; +} + +export async function fixBooth(id: number, operator: string) { + const booth = MOCK_BOOTHS.find(b => b.id === id); + if (!booth) return { code: 404, msg: '未找到' }; + booth.status = 'fixed'; + booth.operator = operator; + booth.operateTime = new Date().toISOString().replace('T', ' ').substring(0, 19); + return { code: 200, msg: '固定成功' }; +} + +export async function reserveBooth(id: number, operator: string) { + const booth = MOCK_BOOTHS.find(b => b.id === id); + if (!booth) return { code: 404, msg: '未找到' }; + booth.status = 'reserved'; + booth.operator = operator; + booth.operateTime = new Date().toISOString().replace('T', ' ').substring(0, 19); + return { code: 200, msg: '预留成功' }; +} + +export async function releaseBooth(id: number) { + const booth = MOCK_BOOTHS.find(b => b.id === id); + if (!booth) return { code: 404, msg: '未找到' }; + booth.status = 'available'; + booth.companyId = undefined; + booth.companyName = undefined; + booth.operator = undefined; + booth.operateTime = undefined; + return { code: 200, msg: '解除成功' }; +} + +// ==================== 模块3: 招聘会管理 API ==================== + +export async function getJobFairList(params?: API.IndoorJobFair.JobFairListParams) { + let list = [...MOCK_JOBFAIRS]; + if (params?.fairName) list = list.filter(f => f.fairName.includes(params.fairName!)); + if (params?.startDate) list = list.filter(f => f.startDate >= params.startDate!); + if (params?.endDate) list = list.filter(f => f.endDate <= params.endDate!); + const total = list.length; + const rows = paginate(list, params?.current || 1, params?.pageSize || 10); + return { code: 200, total, rows }; +} + +export async function addJobFair(data: API.IndoorJobFair.JobFairForm) { + const jobfair: API.IndoorJobFair.JobFairItem = { + ...data, + id: nextFairId++, + status: 'pending', + boothUseCount: 0, + createTime: new Date().toISOString().replace('T', ' ').substring(0, 19), + }; + MOCK_JOBFAIRS.unshift(jobfair); + return { code: 200, msg: '添加成功', data: jobfair }; +} + +export async function updateJobFair(data: API.IndoorJobFair.JobFairForm) { + const idx = MOCK_JOBFAIRS.findIndex(f => f.id === data.id); + if (idx !== -1) { MOCK_JOBFAIRS[idx] = { ...MOCK_JOBFAIRS[idx], ...data }; return { code: 200, msg: '修改成功' }; } + return { code: 404, msg: '未找到' }; +} + +export async function deleteJobFair(id: number) { + const idx = MOCK_JOBFAIRS.findIndex(f => f.id === id); + if (idx !== -1) { MOCK_JOBFAIRS.splice(idx, 1); return { code: 200, msg: '删除成功' }; } + return { code: 404, msg: '未找到' }; +} + +// ==================== 模块4: 参会单位查询 API ==================== + +export async function getCompanyQueryList(params?: API.IndoorJobFair.CompanyQueryParams) { + let list = [...MOCK_COMPANIES]; + if (params?.fairName) list = list.filter(c => c.fairName.includes(params.fairName!)); + const total = list.length; + const rows = paginate(list, params?.current || 1, params?.pageSize || 10); + return { code: 200, total, rows }; +} + +// ==================== 模块5: 参会岗位查询 API ==================== + +export async function getJobQueryList(params?: API.IndoorJobFair.JobQueryParams) { + let list = [...MOCK_JOBS]; + if (params?.fairName) list = list.filter(j => j.fairName.includes(params.fairName!)); + if (params?.companyName) list = list.filter(j => j.companyName?.includes(params.companyName!)); + const total = list.length; + const rows = paginate(list, params?.current || 1, params?.pageSize || 10); + return { code: 200, total, rows }; +} + +// ==================== 模块6: 参会单位审核 API ==================== + +export async function getCompanyReviewList(params?: API.IndoorJobFair.CompanyReviewParams) { + let list = [...MOCK_COMPANY_REVIEWS]; + if (params?.fairName) list = list.filter(c => c.fairName.includes(params.fairName!)); + if (params?.companyName) list = list.filter(c => c.companyName?.includes(params.companyName!)); + if (params?.reviewStatus) list = list.filter(c => c.reviewStatus === params.reviewStatus); + if (params?.reservationStart) list = list.filter(c => c.reservationStart >= params.reservationStart!); + if (params?.reservationEnd) list = list.filter(c => c.reservationEnd <= params.reservationEnd!); + const total = list.length; + const rows = paginate(list, params?.current || 1, params?.pageSize || 10); + return { code: 200, total, rows }; +} + +export async function batchApproveCompany(ids: number[]) { + ids.forEach(id => { + const item = MOCK_COMPANY_REVIEWS.find(c => c.id === id); + if (item) { item.reviewStatus = 'approved'; item.reviewTime = new Date().toISOString().replace('T', ' ').substring(0, 19); } + }); + return { code: 200, msg: '批量审核成功' }; +} + +export async function batchRejectCompany(ids: number[]) { + ids.forEach(id => { + const item = MOCK_COMPANY_REVIEWS.find(c => c.id === id); + if (item) { item.reviewStatus = 'rejected'; item.reviewTime = new Date().toISOString().replace('T', ' ').substring(0, 19); } + }); + return { code: 200, msg: '批量审核失败' }; +} + +// ==================== 模块7: 退订信息管理 API ==================== + +export async function getUnsubscribeList(params?: API.IndoorJobFair.UnsubscribeParams) { + let list = [...MOCK_UNSUBSCRIBES]; + if (params?.fairName) list = list.filter(u => u.fairName.includes(params.fairName!)); + if (params?.companyName) list = list.filter(u => u.companyName?.includes(params.companyName!)); + if (params?.boothNumber) list = list.filter(u => u.boothNumber.includes(params.boothNumber!)); + if (params?.contactPerson) list = list.filter(u => u.contactPerson.includes(params.contactPerson!)); + if (params?.contactPhone) list = list.filter(u => u.contactPhone.includes(params.contactPhone!)); + const total = list.length; + const rows = paginate(list, params?.current || 1, params?.pageSize || 10); + return { code: 200, total, rows }; +} + +// ==================== 模块8: 单位参会查询 API ==================== + +export async function getCompanyRecordList(params?: API.IndoorJobFair.CompanyRecordParams) { + let list = [...MOCK_COMPANY_RECORDS]; + if (params?.companyName) list = list.filter(c => c.companyName.includes(params.companyName!)); + if (params?.contactPerson) list = list.filter(c => c.contactPerson.includes(params.contactPerson!)); + if (params?.contactPhone) list = list.filter(c => c.contactPhone.includes(params.contactPhone!)); + const total = list.length; + const rows = paginate(list, params?.current || 1, params?.pageSize || 10); + return { code: 200, total, rows }; +} + +export async function updateCompanyRecordStatus(id: number, status: string) { + const record = MOCK_COMPANY_RECORDS.find(c => c.id === id); + if (!record) return { code: 404, msg: '未找到' }; + record.status = status; + return { code: 200, msg: '状态修改成功' }; +} + +export async function updateCompanyCreditStatus(id: number, creditStatus: string) { + const record = MOCK_COMPANY_RECORDS.find(c => c.id === id); + if (!record) return { code: 404, msg: '未找到' }; + record.creditStatus = creditStatus; + return { code: 200, msg: '失信记录更新成功' }; +} + +// ==================== 模块9: 招聘会岗位审核 API ==================== + +export async function getJobReviewList(params?: API.IndoorJobFair.JobReviewParams) { + let list = [...MOCK_JOB_REVIEWS]; + if (params?.companyName) list = list.filter(j => j.companyName?.includes(params.companyName!)); + if (params?.jobTitle) list = list.filter(j => j.jobTitle.includes(params.jobTitle!)); + if (params?.reviewStatus) list = list.filter(j => j.reviewStatus === params.reviewStatus); + const total = list.length; + const rows = paginate(list, params?.current || 1, params?.pageSize || 10); + return { code: 200, total, rows }; +} + +export async function approveJob(id: number, remark?: string) { + const job = MOCK_JOB_REVIEWS.find(j => j.id === id); + if (!job) return { code: 404, msg: '未找到' }; + job.reviewStatus = 'approved'; + job.reviewTime = new Date().toISOString().replace('T', ' ').substring(0, 19); + if (remark) job.reviewRemark = remark; + return { code: 200, msg: '审核通过' }; +} + +export async function rejectJob(id: number, remark?: string) { + const job = MOCK_JOB_REVIEWS.find(j => j.id === id); + if (!job) return { code: 404, msg: '未找到' }; + job.reviewStatus = 'rejected'; + job.reviewTime = new Date().toISOString().replace('T', ' ').substring(0, 19); + if (remark) job.reviewRemark = remark; + return { code: 200, msg: '审核不通过' }; +} + +// ==================== 模块10: 预留操作记录 API ==================== + +export async function getReservationRecordList(params?: API.IndoorJobFair.ReservationRecordParams) { + let list = [...MOCK_RESERVATION_RECORDS]; + if (params?.operator) list = list.filter(r => r.operator.includes(params.operator!)); + const total = list.length; + const rows = paginate(list, params?.current || 1, params?.pageSize || 10); + return { code: 200, total, rows }; +} diff --git a/src/services/jobportal/live.ts b/src/services/jobportal/live.ts index e5977d4..c9f3f47 100644 --- a/src/services/jobportal/live.ts +++ b/src/services/jobportal/live.ts @@ -74,8 +74,6 @@ export interface CmsLiveForm { description?: string; startTime?: string; endTime?: string; - companyId?: string; - companyName?: string; } /** 获取直播带岗管理列表 GET /api/cms/live/list */ diff --git a/src/services/jobportal/outdoorFair.ts b/src/services/jobportal/outdoorFair.ts new file mode 100644 index 0000000..9442666 --- /dev/null +++ b/src/services/jobportal/outdoorFair.ts @@ -0,0 +1,288 @@ +import { request } from '@umijs/max'; + +/** 户外招聘会列表项 */ +export interface OutdoorFairItem { + id: number; + /** 招聘会标题 */ + title: string; + /** 举办单位 */ + hostUnit: string; + /** 招聘会类型 */ + fairType: string; + /** 举办区域 */ + region: string; + /** 举办地址 */ + address: string; + /** 展位数量 */ + boothCount: number; + /** 举办时间 */ + holdTime: string; + /** 截止时间 */ + endTime: string; + /** 开放申请 */ + applyStartTime: string; + /** 截止申请 */ + applyEndTime: string; + /** 是否开启线上申请 */ + onlineApply: boolean; + /** 招聘会照片 */ + photoUrl: string; + createTime?: string; + updateTime?: string; +} + +/** 户外招聘会查询参数 */ +export interface OutdoorFairListParams { + title?: string; + hostUnit?: string; + fairType?: string; + current?: number; + pageSize?: number; +} + +/** 户外招聘会列表响应 */ +export interface OutdoorFairListResult { + code: number; + msg?: string; + total: number; + rows: OutdoorFairItem[]; +} + +/** 户外招聘会新增/编辑表单 */ +export interface OutdoorFairForm { + id?: number; + title: string; + hostUnit: string; + fairType: string; + region: string; + address: string; + boothCount: number; + holdTime: string; + endTime: string; + applyStartTime: string; + applyEndTime: string; + onlineApply: boolean; + photoUrl: string; +} + +/** 通用响应 */ +export interface OutdoorFairCommonResult { + code: number; + msg?: string; + data?: any; +} + +// ==================== 静态假数据 ==================== + +const MOCK_DATA: OutdoorFairItem[] = [ + { + id: 1, + title: '2026年石河子市春季户外招聘会', + hostUnit: '第八师石河子市人力资源和社会保障局', + fairType: '综合类', + region: '第八师(石河子市)', + address: '石河子市北三路石河子大学中区图书馆前广场', + boothCount: 80, + holdTime: '2026-03-15 09:00:00', + endTime: '2026-03-15 17:00:00', + applyStartTime: '2026-03-01 00:00:00', + applyEndTime: '2026-03-10 23:59:59', + onlineApply: true, + photoUrl: 'https://picsum.photos/seed/outdoor1/400/300', + createTime: '2026-02-20 10:30:00', + }, + { + id: 2, + title: '2026年阿拉尔市高校户外专场招聘会', + hostUnit: '第一师阿拉尔市人力资源和社会保障局', + fairType: '校园招聘', + region: '第一师(阿拉尔市)', + address: '阿拉尔市塔里木大道塔里木大学综合教学楼前广场', + boothCount: 60, + holdTime: '2026-04-20 09:00:00', + endTime: '2026-04-20 16:00:00', + applyStartTime: '2026-04-01 00:00:00', + applyEndTime: '2026-04-15 23:59:59', + onlineApply: true, + photoUrl: 'https://picsum.photos/seed/outdoor2/400/300', + createTime: '2026-03-01 14:00:00', + }, + { + id: 3, + title: '2026年图木舒克市制造业户外招聘会', + hostUnit: '第三师图木舒克市人力资源和社会保障局', + fairType: '行业专场', + region: '第三师(图木舒克市)', + address: '图木舒克市迎宾大道市政广场', + boothCount: 120, + holdTime: '2026-05-10 08:30:00', + endTime: '2026-05-10 17:30:00', + applyStartTime: '2026-04-25 00:00:00', + applyEndTime: '2026-05-05 23:59:59', + onlineApply: false, + photoUrl: 'https://picsum.photos/seed/outdoor3/400/300', + createTime: '2026-03-10 09:00:00', + }, + { + id: 4, + title: '2026年可克达拉市高新技术户外招聘会', + hostUnit: '第四师可克达拉市人力资源和社会保障局', + fairType: '高新技术专场', + region: '第四师(可克达拉市)', + address: '可克达拉市镇江路市民服务中心广场', + boothCount: 50, + holdTime: '2026-06-18 09:00:00', + endTime: '2026-06-18 16:30:00', + applyStartTime: '2026-06-01 00:00:00', + applyEndTime: '2026-06-12 23:59:59', + onlineApply: true, + photoUrl: 'https://picsum.photos/seed/outdoor4/400/300', + createTime: '2026-03-20 11:00:00', + }, + { + id: 5, + title: '2026年双河市农业行业户外招聘会', + hostUnit: '第五师双河市人力资源和社会保障局', + fairType: '行业专场', + region: '第五师(双河市)', + address: '双河市人民路双河市文化广场', + boothCount: 40, + holdTime: '2026-07-08 09:00:00', + endTime: '2026-07-08 17:00:00', + applyStartTime: '2026-06-20 00:00:00', + applyEndTime: '2026-07-03 23:59:59', + onlineApply: false, + photoUrl: 'https://picsum.photos/seed/outdoor5/400/300', + createTime: '2026-04-01 08:30:00', + }, + { + id: 6, + title: '2026年五家渠市电子产业户外招聘会', + hostUnit: '第六师五家渠市人力资源和社会保障局', + fairType: '行业专场', + region: '第六师(五家渠市)', + address: '五家渠市长安东街五家渠市人民广场', + boothCount: 70, + holdTime: '2026-08-15 08:30:00', + endTime: '2026-08-15 17:30:00', + applyStartTime: '2026-08-01 00:00:00', + applyEndTime: '2026-08-10 23:59:59', + onlineApply: true, + photoUrl: 'https://picsum.photos/seed/outdoor6/400/300', + createTime: '2026-04-15 10:00:00', + }, + { + id: 7, + title: '2026年胡杨河市智能制造户外招聘会', + hostUnit: '第七师胡杨河市人力资源和社会保障局', + fairType: '智能制造专场', + region: '第七师(胡杨河市)', + address: '胡杨河市团结路胡杨河市创业广场', + boothCount: 55, + holdTime: '2026-09-12 09:00:00', + endTime: '2026-09-12 16:00:00', + applyStartTime: '2026-08-25 00:00:00', + applyEndTime: '2026-09-05 23:59:59', + onlineApply: true, + photoUrl: 'https://picsum.photos/seed/outdoor7/400/300', + createTime: '2026-05-01 09:30:00', + }, + { + id: 8, + title: '2026年北屯市文旅行业户外招聘会', + hostUnit: '第十师北屯市人力资源和社会保障局', + fairType: '文旅专场', + region: '第十师(北屯市)', + address: '北屯市博望路北屯市文化广场', + boothCount: 35, + holdTime: '2026-10-20 09:00:00', + endTime: '2026-10-20 17:00:00', + applyStartTime: '2026-10-01 00:00:00', + applyEndTime: '2026-10-15 23:59:59', + onlineApply: false, + photoUrl: 'https://picsum.photos/seed/outdoor8/400/300', + createTime: '2026-05-10 16:00:00', + }, +]; + +// 模拟自增 ID +let nextId = MOCK_DATA.length + 1; + +// ==================== API 接口(使用假数据) ==================== + +const CMS_OUTDOOR_FAIR_BASE = '/api/cms/outdoor-fair'; + +/** + * 获取户外招聘会管理列表 + * GET /api/cms/outdoor-fair/list + */ +export async function getOutdoorFairList(params?: OutdoorFairListParams) { + let filtered = [...MOCK_DATA]; + + if (params?.title) { + filtered = filtered.filter((item) => item.title.includes(params.title!)); + } + if (params?.hostUnit) { + filtered = filtered.filter((item) => item.hostUnit.includes(params.hostUnit!)); + } + if (params?.fairType) { + filtered = filtered.filter((item) => item.fairType.includes(params.fairType!)); + } + + const total = filtered.length; + const current = params?.current || 1; + const pageSize = params?.pageSize || 10; + const start = (current - 1) * pageSize; + const rows = filtered.slice(start, start + pageSize); + + return { + code: 200, + msg: '操作成功', + total, + rows, + }; +} + +/** + * 新增户外招聘会 + * POST /api/cms/outdoor-fair + */ +export async function addOutdoorFair(data: OutdoorFairForm) { + const newItem: OutdoorFairItem = { + ...data, + id: nextId++, + createTime: new Date().toISOString().replace('T', ' ').substring(0, 19), + }; + MOCK_DATA.unshift(newItem); + return { code: 200, msg: '新增成功', data: newItem }; +} + +/** + * 修改户外招聘会 + * PUT /api/cms/outdoor-fair + */ +export async function updateOutdoorFair(data: OutdoorFairForm) { + const index = MOCK_DATA.findIndex((item) => item.id === data.id); + if (index !== -1) { + MOCK_DATA[index] = { + ...MOCK_DATA[index], + ...data, + updateTime: new Date().toISOString().replace('T', ' ').substring(0, 19), + }; + return { code: 200, msg: '修改成功', data: MOCK_DATA[index] }; + } + return { code: 404, msg: '未找到该记录' }; +} + +/** + * 删除户外招聘会 + * DELETE /api/cms/outdoor-fair/{id} + */ +export async function deleteOutdoorFair(id: number) { + const index = MOCK_DATA.findIndex((item) => item.id === id); + if (index !== -1) { + MOCK_DATA.splice(index, 1); + return { code: 200, msg: '删除成功' }; + } + return { code: 404, msg: '未找到该记录' }; +} diff --git a/src/services/jobportal/outdoorFairDetail.ts b/src/services/jobportal/outdoorFairDetail.ts new file mode 100644 index 0000000..480a9fc --- /dev/null +++ b/src/services/jobportal/outdoorFairDetail.ts @@ -0,0 +1,526 @@ +/** + * 户外招聘会详情管理 - 假数据服务 + * 包含模块1-6的全部 mock API + */ + +import type { + API as OutdoorFairDetailAPI, +} from '@/types/jobfair/outdoorFairDetail'; // eslint-disable-line + +type ParticipatingCompany = API.OutdoorFairDetail.ParticipatingCompany; +type PostedJob = API.OutdoorFairDetail.PostedJob; +type BoothItem = API.OutdoorFairDetail.BoothItem; +type BoothAssignParams = API.OutdoorFairDetail.BoothAssignParams; +type CompanyRegistration = API.OutdoorFairDetail.CompanyRegistration; +type RegisteredJob = API.OutdoorFairDetail.RegisteredJob; +type AttendeeItem = API.OutdoorFairDetail.AttendeeItem; +type InterviewTrackParams = API.OutdoorFairDetail.InterviewTrackParams; +type DeviceItem = API.OutdoorFairDetail.DeviceItem; +type GateStatus = API.OutdoorFairDetail.GateStatus; +type GateEntryLog = API.OutdoorFairDetail.GateEntryLog; +type FairStatistics = API.OutdoorFairDetail.FairStatistics; + +// ==================== 模块1: 招聘会管理 假数据 ==================== + +const MOCK_COMPANIES: ParticipatingCompany[] = [ + { id: 1, fairId: 1, companyId: 1001, companyName: '新疆天业(集团)有限公司', industry: '化工', scale: '1000人以上', contactPerson: '张经理', contactPhone: '13909930001', boothId: 1, boothNumber: 'A01', jobList: [ + { id: 1, fairId: 1, companyId: 1001, jobTitle: '化工工程师', minSalary: 8000, maxSalary: 12000, education: '本科', experience: '3-5年', vacancies: 5, status: 'active' }, + { id: 2, fairId: 1, companyId: 1001, jobTitle: '安全主管', minSalary: 10000, maxSalary: 15000, education: '本科', experience: '5年以上', vacancies: 2, status: 'active' }, + ]}, + { id: 2, fairId: 1, companyId: 1002, companyName: '石河子经济技术开发区管委会', industry: '政府/公共事业', scale: '500-1000人', contactPerson: '李主任', contactPhone: '13909930002', boothId: 2, boothNumber: 'A02', jobList: [ + { id: 3, fairId: 1, companyId: 1002, jobTitle: '产业招商专员', minSalary: 6000, maxSalary: 9000, education: '本科', experience: '1-3年', vacancies: 10, status: 'active' }, + ]}, + { id: 3, fairId: 1, companyId: 1003, companyName: '新疆西部牧业股份有限公司', industry: '农业/畜牧', scale: '500-1000人', contactPerson: '王部长', contactPhone: '13909930003', boothId: 3, boothNumber: 'A03', jobList: [ + { id: 4, fairId: 1, companyId: 1003, jobTitle: '畜牧技术员', minSalary: 5000, maxSalary: 8000, education: '大专', experience: '1-3年', vacancies: 8, status: 'active' }, + { id: 5, fairId: 1, companyId: 1003, jobTitle: '兽医', minSalary: 7000, maxSalary: 11000, education: '本科', experience: '3-5年', vacancies: 3, status: 'active' }, + ]}, + { id: 4, fairId: 1, companyId: 1004, companyName: '新疆生产建设兵团第八师医院', industry: '医疗/卫生', scale: '1000人以上', contactPerson: '赵处长', contactPhone: '13909930004', jobList: [ + { id: 6, fairId: 1, companyId: 1004, jobTitle: '临床医生', minSalary: 10000, maxSalary: 18000, education: '硕士', experience: '不限', vacancies: 15, status: 'active' }, + { id: 7, fairId: 1, companyId: 1004, jobTitle: '护士', minSalary: 5000, maxSalary: 8000, education: '大专', experience: '1-3年', vacancies: 20, status: 'active' }, + ]}, + { id: 5, fairId: 1, companyId: 1005, companyName: '石河子大学', industry: '教育/科研', scale: '1000人以上', contactPerson: '周老师', contactPhone: '13909930005', jobList: [ + { id: 8, fairId: 1, companyId: 1005, jobTitle: '讲师', minSalary: 9000, maxSalary: 13000, education: '博士', experience: '不限', vacancies: 5, status: 'active' }, + ]}, + { id: 6, fairId: 1, companyId: 1006, companyName: '新疆天山铝业有限公司', industry: '制造/能源', scale: '1000人以上', contactPerson: '吴主管', contactPhone: '13909930006', boothId: 6, boothNumber: 'A06', jobList: [ + { id: 9, fairId: 1, companyId: 1006, jobTitle: '电气工程师', minSalary: 8000, maxSalary: 14000, education: '本科', experience: '3-5年', vacancies: 6, status: 'active' }, + { id: 10, fairId: 1, companyId: 1006, jobTitle: '操作工', minSalary: 4500, maxSalary: 7000, education: '高中', experience: '不限', vacancies: 30, status: 'active' }, + ]}, +]; + +let nextCompanyId = 7; +let nextPostedJobId = 11; + +// ==================== 模块2: 展位管理 假数据 ==================== + +const MOCK_BOOTHS: BoothItem[] = [ + { id: 1, fairId: 1, boothNumber: 'A01', companyId: 1001, companyName: '新疆天业(集团)有限公司', jobCount: 2, reviewStatus: 'approved', assignedTime: '2026-03-10 09:00:00', createTime: '2026-03-01 10:00:00' }, + { id: 2, fairId: 1, boothNumber: 'A02', companyId: 1002, companyName: '石河子经济技术开发区管委会', jobCount: 1, reviewStatus: 'approved', assignedTime: '2026-03-10 09:30:00', createTime: '2026-03-01 10:00:00' }, + { id: 3, fairId: 1, boothNumber: 'A03', companyId: 1003, companyName: '新疆西部牧业股份有限公司', jobCount: 2, reviewStatus: 'approved', assignedTime: '2026-03-10 10:00:00', createTime: '2026-03-01 10:00:00' }, + { id: 4, fairId: 1, boothNumber: 'A04', companyId: 1004, companyName: '新疆生产建设兵团第八师医院', jobCount: 2, reviewStatus: 'approved', assignedTime: '2026-03-10 10:30:00', createTime: '2026-03-01 10:00:00' }, + { id: 5, fairId: 1, boothNumber: 'A05', companyId: undefined, companyName: undefined, jobCount: 0, reviewStatus: 'pending', createTime: '2026-03-01 10:00:00' }, + { id: 6, fairId: 1, boothNumber: 'A06', companyId: 1006, companyName: '新疆天山铝业有限公司', jobCount: 2, reviewStatus: 'approved', assignedTime: '2026-03-11 09:00:00', createTime: '2026-03-01 10:00:00' }, + { id: 7, fairId: 1, boothNumber: 'A07', companyId: undefined, companyName: undefined, jobCount: 0, reviewStatus: 'pending', createTime: '2026-03-01 10:00:00' }, + { id: 8, fairId: 1, boothNumber: 'A08', companyId: undefined, companyName: undefined, jobCount: 0, reviewStatus: 'pending', createTime: '2026-03-01 10:00:00' }, + { id: 9, fairId: 1, boothNumber: 'B01', companyId: 1007, companyName: '兵团棉麻公司', jobCount: 1, reviewStatus: 'pending', createTime: '2026-03-02 10:00:00' }, + { id: 10, fairId: 1, boothNumber: 'B02', companyId: undefined, companyName: undefined, jobCount: 0, reviewStatus: 'pending', createTime: '2026-03-02 10:00:00' }, + { id: 11, fairId: 1, boothNumber: 'B03', companyId: undefined, companyName: undefined, jobCount: 0, reviewStatus: 'pending', createTime: '2026-03-02 10:00:00' }, + { id: 12, fairId: 1, boothNumber: 'B04', companyId: 1008, companyName: '新疆西部绿珠果蔬有限公司', jobCount: 3, reviewStatus: 'pending', createTime: '2026-03-03 10:00:00' }, +]; + +let nextBoothId = 13; + +// ==================== 模块3: 参会单位管理 假数据 ==================== + +const MOCK_REGISTRATIONS: CompanyRegistration[] = [ + { id: 1, fairId: 1, companyId: 1001, companyName: '新疆天业(集团)有限公司', industry: '化工', scale: '1000人以上', contactPerson: '张经理', contactPhone: '13909930001', reviewStatus: 'approved', reviewTime: '2026-03-12 10:00:00', qrCheckInStatus: 'checked_in', checkInTime: '2026-03-15 08:30:00', registeredJobs: [ + { id: 1, regId: 1, jobTitle: '化工工程师', minSalary: 8000, maxSalary: 12000, education: '本科', vacancies: 5, reviewStatus: 'approved' }, + { id: 2, regId: 1, jobTitle: '安全主管', minSalary: 10000, maxSalary: 15000, education: '本科', vacancies: 2, reviewStatus: 'approved' }, + ]}, + { id: 2, fairId: 1, companyId: 1002, companyName: '石河子经济技术开发区管委会', industry: '政府/公共事业', scale: '500-1000人', contactPerson: '李主任', contactPhone: '13909930002', reviewStatus: 'approved', reviewTime: '2026-03-12 11:00:00', qrCheckInStatus: 'checked_in', checkInTime: '2026-03-15 08:45:00', registeredJobs: [ + { id: 3, regId: 2, jobTitle: '产业招商专员', minSalary: 6000, maxSalary: 9000, education: '本科', vacancies: 10, reviewStatus: 'approved' }, + ]}, + { id: 3, fairId: 1, companyId: 1009, companyName: '兵团水利水电工程集团', industry: '建筑/工程', scale: '1000人以上', contactPerson: '刘部长', contactPhone: '13609930001', reviewStatus: 'pending', qrCheckInStatus: 'not_checked_in', registeredJobs: [ + { id: 4, regId: 3, jobTitle: '水利工程师', minSalary: 9000, maxSalary: 14000, education: '本科', vacancies: 8, reviewStatus: 'pending' }, + { id: 5, regId: 3, jobTitle: '造价员', minSalary: 6000, maxSalary: 10000, education: '大专', vacancies: 3, reviewStatus: 'pending' }, + ]}, + { id: 4, fairId: 1, companyId: 1010, companyName: '新疆通用航空有限责任公司', industry: '航空/交通', scale: '200-500人', contactPerson: '陈经理', contactPhone: '13809930001', reviewStatus: 'pending', qrCheckInStatus: 'not_checked_in', registeredJobs: [ + { id: 6, regId: 4, jobTitle: '飞机维修工程师', minSalary: 12000, maxSalary: 18000, education: '本科', vacancies: 4, reviewStatus: 'pending' }, + ]}, + { id: 5, fairId: 1, companyId: 1011, companyName: '石河子市公共交通有限责任公司', industry: '交通/运输', scale: '500-1000人', contactPerson: '马科长', contactPhone: '13709930001', reviewStatus: 'pending', qrCheckInStatus: 'not_checked_in', registeredJobs: [ + { id: 7, regId: 5, jobTitle: '调度员', minSalary: 4500, maxSalary: 6500, education: '大专', vacancies: 5, reviewStatus: 'pending' }, + ]}, + { id: 6, fairId: 1, companyId: 1012, companyName: '兵团棉麻公司', industry: '农业/贸易', scale: '200-500人', contactPerson: '彭经理', contactPhone: '13509930001', reviewStatus: 'rejected', reviewTime: '2026-03-13 15:00:00', reviewRemark: '资料不全,请补充企业营业执照', qrCheckInStatus: 'not_checked_in', registeredJobs: []}, +]; + +let nextRegId = 7; +let nextRegJobId = 8; + +// ==================== 模块4: 参会人员管理 假数据 ==================== + +const MOCK_ATTENDEES: AttendeeItem[] = [ + { id: 1, fairId: 1, name: '阿依古丽·买买提', gender: '女', phone: '15009930001', idCard: '659001199805010012', education: '本科', graduationSchool: '石河子大学', major: '化学工程', checkInTime: '2026-03-15 08:30:00', checkInMethod: 'qr_scan', targetCompanies: '新疆天业(集团)有限公司', mobileInteraction: true, resumeUrl: 'http://example.com/resume/1', interviewStatus: 'passed', interviewCompany: '新疆天业(集团)有限公司', interviewTime: '2026-03-15 14:00:00', interviewResult: '通过,已发offer' }, + { id: 2, fairId: 1, name: '张伟', gender: '男', phone: '15009930002', idCard: '659001199706150018', education: '硕士', graduationSchool: '新疆大学', major: '经济学', checkInTime: '2026-03-15 08:35:00', checkInMethod: 'qr_scan', targetCompanies: '石河子经济技术开发区管委会', mobileInteraction: true, interviewStatus: 'completed', interviewCompany: '石河子经济技术开发区管委会', interviewTime: '2026-03-15 15:30:00', interviewResult: '二面待通知' }, + { id: 3, fairId: 1, name: '李军', gender: '男', phone: '15009930003', idCard: '659001199409200011', education: '大专', graduationSchool: '新疆石河子职业技术学院', major: '畜牧兽医', checkInTime: '2026-03-15 08:40:00', checkInMethod: 'id_card', targetCompanies: '新疆西部牧业股份有限公司', mobileInteraction: false, interviewStatus: 'scheduled', interviewCompany: '新疆西部牧业股份有限公司', interviewTime: '2026-03-15 16:00:00' }, + { id: 4, fairId: 1, name: '王芳', gender: '女', phone: '15009930004', idCard: '659001200005280022', education: '本科', graduationSchool: '石河子大学', major: '临床医学', checkInTime: '2026-03-15 08:50:00', checkInMethod: 'qr_scan', targetCompanies: '新疆生产建设兵团第八师医院', mobileInteraction: true, interviewStatus: 'in_progress', interviewCompany: '新疆生产建设兵团第八师医院', interviewTime: '2026-03-15 11:00:00' }, + { id: 5, fairId: 1, name: '赵强', gender: '男', phone: '15009930005', idCard: '659001199301100016', education: '本科', graduationSchool: '塔里木大学', major: '电气工程', checkInTime: '2026-03-15 09:00:00', checkInMethod: 'manual', targetCompanies: '新疆天山铝业有限公司', mobileInteraction: false, interviewStatus: 'none' }, + { id: 6, fairId: 1, name: '热娜古丽·艾山', gender: '女', phone: '15009930006', idCard: '659001199812030020', education: '硕士', graduationSchool: '新疆大学', major: '会计学', checkInTime: '2026-03-15 09:10:00', checkInMethod: 'qr_scan', targetCompanies: '新疆天业(集团)有限公司', mobileInteraction: true, resumeUrl: 'http://example.com/resume/6', interviewStatus: 'scheduled', interviewCompany: '新疆天业(集团)有限公司', interviewTime: '2026-03-15 15:00:00' }, + { id: 7, fairId: 1, name: '陈明', gender: '男', phone: '15009930007', idCard: '659001199510150013', education: '大专', graduationSchool: '新疆石河子职业技术学院', major: '机电一体化', checkInTime: '2026-03-15 09:20:00', checkInMethod: 'qr_scan', targetCompanies: '新疆天山铝业有限公司', mobileInteraction: true, interviewStatus: 'completed', interviewCompany: '新疆天山铝业有限公司', interviewTime: '2026-03-15 10:30:00', interviewResult: '待定' }, + { id: 8, fairId: 1, name: '孙丽', gender: '女', phone: '15009930008', idCard: '659001199908220025', education: '本科', graduationSchool: '石河子大学', major: '市场营销', checkInTime: undefined, checkInMethod: 'qr_scan', targetCompanies: '石河子经济技术开发区管委会', mobileInteraction: false, interviewStatus: 'none' }, + { id: 9, fairId: 1, name: '周亮', gender: '男', phone: '15009930009', idCard: '659001200103150019', education: '本科', graduationSchool: '石河子大学', major: '金融学', checkInTime: undefined, checkInMethod: 'id_card', targetCompanies: '石河子经济技术开发区管委会', mobileInteraction: false, interviewStatus: 'none' }, + { id: 10, fairId: 1, name: '吴娜', gender: '女', phone: '15009930010', idCard: '659001199704080026', education: '硕士', graduationSchool: '新疆大学', major: '药学', checkInTime: '2026-03-15 09:30:00', checkInMethod: 'qr_scan', targetCompanies: '新疆生产建设兵团第八师医院', mobileInteraction: true, resumeUrl: 'http://example.com/resume/10', interviewStatus: 'in_progress', interviewCompany: '新疆生产建设兵团第八师医院', interviewTime: '2026-03-15 14:30:00' }, + { id: 11, fairId: 1, name: '郑涛', gender: '男', phone: '15009930011', idCard: '659001199305120010', education: '本科', graduationSchool: '塔里木大学', major: '农学', checkInTime: '2026-03-15 09:40:00', checkInMethod: 'manual', targetCompanies: '新疆西部牧业股份有限公司', mobileInteraction: false, interviewStatus: 'failed', interviewCompany: '新疆西部牧业股份有限公司', interviewTime: '2026-03-15 11:30:00', interviewResult: '岗位不匹配' }, + { id: 12, fairId: 1, name: '马丽娜', gender: '女', phone: '15009930012', idCard: '659001199906180028', education: '本科', graduationSchool: '石河子大学', major: '人力资源管理', checkInTime: '2026-03-15 09:50:00', checkInMethod: 'qr_scan', targetCompanies: '新疆通用航空有限责任公司', mobileInteraction: true, interviewStatus: 'scheduled', interviewCompany: '新疆通用航空有限责任公司', interviewTime: '2026-03-15 16:30:00' }, +]; + +let nextAttendeeId = 13; + +// ==================== 模块5: 入场设备管理 假数据 ==================== + +const MOCK_DEVICES: DeviceItem[] = [ + { id: 1, fairId: 1, type: 'mobile_terminal', deviceName: '移动终端-01', deviceCode: 'MT-20260315-001', assignedBoothId: 1, assignedBoothNumber: 'A01', assignedCompanyName: '新疆天业(集团)有限公司', status: 'online', lastActiveTime: '2026-03-15 10:30:00' }, + { id: 2, fairId: 1, type: 'mobile_terminal', deviceName: '移动终端-02', deviceCode: 'MT-20260315-002', assignedBoothId: 2, assignedBoothNumber: 'A02', assignedCompanyName: '石河子经济技术开发区管委会', status: 'online', lastActiveTime: '2026-03-15 10:25:00' }, + { id: 3, fairId: 1, type: 'mobile_terminal', deviceName: '移动终端-03', deviceCode: 'MT-20260315-003', assignedBoothId: 3, assignedBoothNumber: 'A03', assignedCompanyName: '新疆西部牧业股份有限公司', status: 'online', lastActiveTime: '2026-03-15 10:20:00' }, + { id: 4, fairId: 1, type: 'mobile_terminal', deviceName: '移动终端-04', deviceCode: 'MT-20260315-004', assignedBoothId: 6, assignedBoothNumber: 'A06', assignedCompanyName: '新疆天山铝业有限公司', status: 'offline', lastActiveTime: '2026-03-15 09:00:00' }, + { id: 5, fairId: 1, type: 'mobile_terminal', deviceName: '移动终端-05', deviceCode: 'MT-20260315-005', status: 'offline' }, + { id: 6, fairId: 1, type: 'mobile_terminal', deviceName: '移动终端-06', deviceCode: 'MT-20260315-006', status: 'offline' }, + { id: 7, fairId: 1, type: 'qr_sticker', deviceName: '展位二维码-A01', deviceCode: 'QR-20260315-A01', assignedBoothId: 1, assignedBoothNumber: 'A01', assignedCompanyName: '新疆天业(集团)有限公司', status: 'online' }, + { id: 8, fairId: 1, type: 'qr_sticker', deviceName: '展位二维码-A02', deviceCode: 'QR-20260315-A02', assignedBoothId: 2, assignedBoothNumber: 'A02', assignedCompanyName: '石河子经济技术开发区管委会', status: 'online' }, + { id: 9, fairId: 1, type: 'qr_sticker', deviceName: '展位二维码-A03', deviceCode: 'QR-20260315-A03', assignedBoothId: 3, assignedBoothNumber: 'A03', assignedCompanyName: '新疆西部牧业股份有限公司', status: 'online' }, + { id: 10, fairId: 1, type: 'qr_sticker', deviceName: '展位二维码-A04', deviceCode: 'QR-20260315-A04', assignedBoothId: 4, assignedBoothNumber: 'A04', assignedCompanyName: '新疆生产建设兵团第八师医院', status: 'online' }, + { id: 11, fairId: 1, type: 'qr_sticker', deviceName: '展位二维码-A05', deviceCode: 'QR-20260315-A05', status: 'offline' }, + { id: 12, fairId: 1, type: 'qr_sticker', deviceName: '展位二维码-A06', deviceCode: 'QR-20260315-A06', assignedBoothId: 6, assignedBoothNumber: 'A06', assignedCompanyName: '新疆天山铝业有限公司', status: 'online' }, +]; + +let nextDeviceId = 13; + +const MOCK_GATE_STATUSES: GateStatus[] = [ + { id: 1, fairId: 1, gateName: '主入口闸机', isOpen: true, totalScans: 45 }, + { id: 2, fairId: 1, gateName: '侧入口闸机', isOpen: false, totalScans: 12 }, +]; + +const MOCK_GATE_LOGS: GateEntryLog[] = [ + { id: 1, fairId: 1, attendeeName: '阿依古丽·买买提', scanTime: '2026-03-15 08:30:00', gateName: '主入口闸机', scanMethod: 'qr' }, + { id: 2, fairId: 1, attendeeName: '张伟', scanTime: '2026-03-15 08:35:00', gateName: '主入口闸机', scanMethod: 'qr' }, + { id: 3, fairId: 1, attendeeName: '李军', scanTime: '2026-03-15 08:40:00', gateName: '主入口闸机', scanMethod: 'id_card' }, + { id: 4, fairId: 1, attendeeName: '王芳', scanTime: '2026-03-15 08:50:00', gateName: '侧入口闸机', scanMethod: 'qr' }, + { id: 5, fairId: 1, attendeeName: '赵强', scanTime: '2026-03-15 09:00:00', gateName: '主入口闸机', scanMethod: 'id_card' }, + { id: 6, fairId: 1, attendeeName: '热娜古丽·艾山', scanTime: '2026-03-15 09:10:00', gateName: '主入口闸机', scanMethod: 'qr' }, + { id: 7, fairId: 1, attendeeName: '陈明', scanTime: '2026-03-15 09:20:00', gateName: '侧入口闸机', scanMethod: 'qr' }, + { id: 8, fairId: 1, attendeeName: '吴娜', scanTime: '2026-03-15 09:30:00', gateName: '主入口闸机', scanMethod: 'qr' }, +]; + +let nextGateLogId = 9; + +// ==================== 模块6: 数据统计 假数据 ==================== + +const MOCK_STATISTICS: Record = { + 1: { + fairId: 1, + companyCount: 25, + jobCount: 86, + demandCount: 520, + intentionCount: 180, + industryDistribution: [ + { industry: '化工', count: 5 }, { industry: '政府', count: 2 }, { industry: '农业', count: 4 }, + { industry: '医疗', count: 3 }, { industry: '教育', count: 3 }, { industry: '制造', count: 4 }, + { industry: '建筑', count: 2 }, { industry: '航空', count: 1 }, { industry: '交通', count: 1 }, + ], + jobTypeDistribution: [ + { type: '技术类', count: 30 }, { type: '管理类', count: 15 }, { type: '销售类', count: 12 }, + { type: '行政类', count: 10 }, { type: '一线工人', count: 14 }, { type: '其他', count: 5 }, + ], + dailyAttendees: [ + { date: '2026-03-15', count: 320 }, + ], + timeSlotDistribution: [ + { slot: '08:00-09:00', count: 80 }, { slot: '09:00-10:00', count: 120 }, + { slot: '10:00-11:00', count: 65 }, { slot: '11:00-12:00', count: 30 }, + { slot: '12:00-13:00', count: 10 }, { slot: '13:00-14:00', count: 5 }, + { slot: '14:00-15:00', count: 5 }, { slot: '15:00-16:00', count: 5 }, + ], + }, +}; + +// ==================== 通用工具函数 ==================== + +function paginate(arr: T[], current: number, pageSize: number) { + const start = (current - 1) * pageSize; + return arr.slice(start, start + pageSize); +} + +// ==================== 模块1: 招聘会管理 API ==================== + +/** 获取参会企业列表 */ +export async function getParticipatingCompanies(fairId: number, params?: { current?: number; pageSize?: number; companyName?: string }) { + let list = MOCK_COMPANIES.filter(c => c.fairId === fairId); + if (params?.companyName) { + list = list.filter(c => c.companyName.includes(params.companyName!)); + } + const total = list.length; + const rows = paginate(list, params?.current || 1, params?.pageSize || 10); + return { code: 200, total, rows }; +} + +/** 添加参会企业 */ +export async function addParticipatingCompany(data: { fairId: number; companyId: number; companyName: string; industry: string; scale: string; contactPerson: string; contactPhone: string }) { + const company: ParticipatingCompany = { + ...data, + id: nextCompanyId++, + jobList: [], + }; + MOCK_COMPANIES.unshift(company); + return { code: 200, msg: '添加成功', data: company }; +} + +/** 移除参会企业 */ +export async function removeParticipatingCompany(id: number) { + const idx = MOCK_COMPANIES.findIndex(c => c.id === id); + if (idx !== -1) { MOCK_COMPANIES.splice(idx, 1); return { code: 200, msg: '移除成功' }; } + return { code: 404, msg: '未找到' }; +} + +/** 替企业发布岗位 */ +export async function addJobForCompany(data: { fairId: number; companyId: number; jobTitle: string; minSalary: number; maxSalary: number; education: string; experience: string; vacancies: number }) { + const company = MOCK_COMPANIES.find(c => c.id === data.companyId); + if (!company) return { code: 404, msg: '企业未找到' }; + const job: PostedJob = { + ...data, + id: nextPostedJobId++, + status: 'active', + }; + company.jobList.push(job); + return { code: 200, msg: '岗位添加成功', data: job }; +} + +/** 编辑岗位 */ +export async function updateJobForCompany(data: PostedJob) { + for (const c of MOCK_COMPANIES) { + const idx = c.jobList.findIndex(j => j.id === data.id); + if (idx !== -1) { c.jobList[idx] = { ...c.jobList[idx], ...data }; return { code: 200, msg: '修改成功' }; } + } + return { code: 404, msg: '未找到' }; +} + +/** 删除岗位 */ +export async function removeJobFromCompany(jobId: number) { + for (const c of MOCK_COMPANIES) { + const idx = c.jobList.findIndex(j => j.id === jobId); + if (idx !== -1) { c.jobList.splice(idx, 1); return { code: 200, msg: '删除成功' }; } + } + return { code: 404, msg: '未找到' }; +} + +// ==================== 模块2: 展位管理 API ==================== + +/** 获取展位列表 */ +export async function getBoothList(fairId: number, params?: { current?: number; pageSize?: number; boothNumber?: string; reviewStatus?: string }) { + let list = MOCK_BOOTHS.filter(b => b.fairId === fairId); + if (params?.boothNumber) list = list.filter(b => b.boothNumber.includes(params.boothNumber!)); + if (params?.reviewStatus) list = list.filter(b => b.reviewStatus === params.reviewStatus); + const total = list.length; + const rows = paginate(list, params?.current || 1, params?.pageSize || 10); + return { code: 200, total, rows }; +} + +/** 新增展位 */ +export async function addBooth(data: { fairId: number; boothNumber: string }) { + const booth: BoothItem = { + id: nextBoothId++, + fairId: data.fairId, + boothNumber: data.boothNumber, + companyId: undefined, + companyName: undefined, + jobCount: 0, + reviewStatus: 'pending', + createTime: new Date().toISOString().replace('T', ' ').substring(0, 19), + }; + MOCK_BOOTHS.push(booth); + return { code: 200, msg: '新增成功', data: booth }; +} + +/** 修改展位 */ +export async function updateBooth(data: { id: number; boothNumber: string }) { + const idx = MOCK_BOOTHS.findIndex(b => b.id === data.id); + if (idx !== -1) { MOCK_BOOTHS[idx].boothNumber = data.boothNumber; return { code: 200, msg: '修改成功' }; } + return { code: 404, msg: '未找到' }; +} + +/** 删除展位 */ +export async function deleteBooth(id: number) { + const idx = MOCK_BOOTHS.findIndex(b => b.id === id); + if (idx !== -1) { MOCK_BOOTHS.splice(idx, 1); return { code: 200, msg: '删除成功' }; } + return { code: 404, msg: '未找到' }; +} + +/** 审核展位 */ +export async function reviewBooth(id: number, status: string) { + const booth = MOCK_BOOTHS.find(b => b.id === id); + if (!booth) return { code: 404, msg: '未找到' }; + booth.reviewStatus = status; + if (status === 'approved') booth.assignedTime = new Date().toISOString().replace('T', ' ').substring(0, 19); + return { code: 200, msg: status === 'approved' ? '审核通过' : '审核拒绝' }; +} + +/** 分配展位给企业 */ +export async function assignBooth(params: BoothAssignParams) { + const booth = MOCK_BOOTHS.find(b => b.id === params.boothId); + if (!booth) return { code: 404, msg: '展位未找到' }; + booth.companyId = params.companyId; + booth.companyName = MOCK_REGISTRATIONS.find(r => r.companyId === params.companyId)?.companyName || ''; + booth.boothNumber = params.boothNumber; + booth.assignedTime = new Date().toISOString().replace('T', ' ').substring(0, 19); + return { code: 200, msg: '展位分配成功' }; +} + +// ==================== 模块3: 参会单位管理 API ==================== + +/** 获取单位报名列表 */ +export async function getCompanyRegistrationList(fairId: number, params?: { current?: number; pageSize?: number; companyName?: string; reviewStatus?: string }) { + let list = MOCK_REGISTRATIONS.filter(r => r.fairId === fairId); + if (params?.companyName) list = list.filter(r => r.companyName.includes(params.companyName!)); + if (params?.reviewStatus) list = list.filter(r => r.reviewStatus === params.reviewStatus); + const total = list.length; + const rows = paginate(list, params?.current || 1, params?.pageSize || 10); + return { code: 200, total, rows }; +} + +/** 审核参会单位 */ +export async function reviewCompanyRegistration(id: number, status: string, remark?: string) { + const reg = MOCK_REGISTRATIONS.find(r => r.id === id); + if (!reg) return { code: 404, msg: '未找到' }; + reg.reviewStatus = status; + reg.reviewTime = new Date().toISOString().replace('T', ' ').substring(0, 19); + if (remark) reg.reviewRemark = remark; + return { code: 200, msg: status === 'approved' ? '审核通过' : '审核拒绝' }; +} + +/** 审核岗位 */ +export async function reviewRegisteredJob(regId: number, jobId: number, status: string) { + const reg = MOCK_REGISTRATIONS.find(r => r.id === regId); + if (!reg) return { code: 404, msg: '未找到报名记录' }; + const job = reg.registeredJobs.find(j => j.id === jobId); + if (!job) return { code: 404, msg: '岗位未找到' }; + job.reviewStatus = status; + return { code: 200, msg: status === 'approved' ? '岗位审核通过' : '岗位审核拒绝' }; +} + +/** 模拟扫码签到 */ +export async function qrCheckIn(regId: number) { + const reg = MOCK_REGISTRATIONS.find(r => r.id === regId); + if (!reg) return { code: 404, msg: '未找到' }; + reg.qrCheckInStatus = 'checked_in'; + reg.checkInTime = new Date().toISOString().replace('T', ' ').substring(0, 19); + return { code: 200, msg: '签到成功' }; +} + +// ==================== 模块4: 参会人员管理 API ==================== + +/** 获取参会人员列表 */ +export async function getAttendeeList(fairId: number, params?: { current?: number; pageSize?: number; name?: string; phone?: string; interviewStatus?: string }) { + let list = MOCK_ATTENDEES.filter(a => a.fairId === fairId); + if (params?.name) list = list.filter(a => a.name.includes(params.name!)); + if (params?.phone) list = list.filter(a => a.phone.includes(params.phone!)); + if (params?.interviewStatus) list = list.filter(a => a.interviewStatus === params.interviewStatus); + const total = list.length; + const rows = paginate(list, params?.current || 1, params?.pageSize || 10); + return { code: 200, total, rows }; +} + +/** 模拟扫码入场 */ +export async function scanAttendeeEntry(data: { name: string; phone: string; idCard: string; checkInMethod: string }) { + const attendee: AttendeeItem = { + id: nextAttendeeId++, + fairId: 1, + name: data.name, + gender: '男', + phone: data.phone, + idCard: data.idCard, + education: '本科', + graduationSchool: '', + major: '', + checkInTime: new Date().toISOString().replace('T', ' ').substring(0, 19), + checkInMethod: data.checkInMethod, + targetCompanies: '', + mobileInteraction: false, + interviewStatus: 'none', + }; + MOCK_ATTENDEES.unshift(attendee); + return { code: 200, msg: '入场登记成功', data: attendee }; +} + +/** 编辑参会人员简历 */ +export async function updateAttendeeResume(id: number, data: { education: string; graduationSchool: string; major: string; resumeContent: string }) { + const attendee = MOCK_ATTENDEES.find(a => a.id === id); + if (!attendee) return { code: 404, msg: '未找到' }; + Object.assign(attendee, data); + return { code: 200, msg: '简历更新成功' }; +} + +/** 面试跟踪 */ +export async function trackInterview(params: InterviewTrackParams) { + const attendee = MOCK_ATTENDEES.find(a => a.id === params.attendeeId); + if (!attendee) return { code: 404, msg: '未找到' }; + attendee.interviewStatus = params.interviewStatus; + attendee.interviewCompany = params.interviewCompany; + attendee.interviewTime = params.interviewTime; + if (params.interviewResult) attendee.interviewResult = params.interviewResult; + return { code: 200, msg: '面试信息更新成功' }; +} + +// ==================== 模块5: 入场设备管理 API ==================== + +/** 获取设备列表 */ +export async function getDeviceList(fairId: number, params?: { current?: number; pageSize?: number; type?: string }) { + let list = MOCK_DEVICES.filter(d => d.fairId === fairId); + if (params?.type) list = list.filter(d => d.type === params.type); + const total = list.length; + const rows = paginate(list, params?.current || 1, params?.pageSize || 10); + return { code: 200, total, rows }; +} + +/** 新增设备 */ +export async function addDevice(data: { fairId: number; type: string; deviceName: string; deviceCode: string }) { + const device: DeviceItem = { + id: nextDeviceId++, + fairId: data.fairId, + type: data.type, + deviceName: data.deviceName, + deviceCode: data.deviceCode, + status: 'offline', + }; + MOCK_DEVICES.push(device); + return { code: 200, msg: '新增成功', data: device }; +} + +/** 修改设备 */ +export async function updateDevice(data: { id: number; deviceName: string; deviceCode: string }) { + const idx = MOCK_DEVICES.findIndex(d => d.id === data.id); + if (idx !== -1) { MOCK_DEVICES[idx].deviceName = data.deviceName; MOCK_DEVICES[idx].deviceCode = data.deviceCode; return { code: 200, msg: '修改成功' }; } + return { code: 404, msg: '未找到' }; +} + +/** 删除设备 */ +export async function deleteDevice(id: number) { + const idx = MOCK_DEVICES.findIndex(d => d.id === id); + if (idx !== -1) { MOCK_DEVICES.splice(idx, 1); return { code: 200, msg: '删除成功' }; } + return { code: 404, msg: '未找到' }; +} + +/** 分配设备到展位 */ +export async function assignDevice(deviceId: number, boothId: number, boothNumber: string, companyName: string) { + const device = MOCK_DEVICES.find(d => d.id === deviceId); + if (!device) return { code: 404, msg: '设备未找到' }; + device.assignedBoothId = boothId; + device.assignedBoothNumber = boothNumber; + device.assignedCompanyName = companyName; + return { code: 200, msg: '设备分配成功' }; +} + +/** 切换设备状态 */ +export async function toggleDeviceStatus(id: number) { + const device = MOCK_DEVICES.find(d => d.id === id); + if (!device) return { code: 404, msg: '设备未找到' }; + device.status = device.status === 'online' ? 'offline' : 'online'; + device.lastActiveTime = device.status === 'online' ? new Date().toISOString().replace('T', ' ').substring(0, 19) : undefined; + return { code: 200, msg: device.status === 'online' ? '设备已启用' : '设备已禁用' }; +} + +/** 获取闸机状态 */ +export async function getGateStatuses(fairId: number) { + return { code: 200, rows: MOCK_GATE_STATUSES.filter(g => g.fairId === fairId) }; +} + +/** 切换闸机开关 */ +export async function toggleGate(id: number) { + const gate = MOCK_GATE_STATUSES.find(g => g.id === id); + if (!gate) return { code: 404, msg: '闸机未找到' }; + gate.isOpen = !gate.isOpen; + return { code: 200, msg: gate.isOpen ? '闸机已开启' : '闸机已关闭' }; +} + +/** 获取闸机入场日志 */ +export async function getGateEntryLogs(fairId: number, params?: { current?: number; pageSize?: number }) { + const list = MOCK_GATE_LOGS.filter(l => l.fairId === fairId); + const total = list.length; + const rows = paginate(list, params?.current || 1, params?.pageSize || 10); + return { code: 200, total, rows }; +} + +/** 模拟闸机扫码 */ +export async function simulateGateScan(data: { fairId: number; gateName: string; attendeeName: string; scanMethod: string }) { + const gate = MOCK_GATE_STATUSES.find(g => g.fairId === data.fairId && g.gateName === data.gateName); + if (!gate) return { code: 404, msg: '闸机未找到' }; + if (!gate.isOpen) return { code: 400, msg: '闸机未开启' }; + gate.totalScans++; + const log: GateEntryLog = { + id: nextGateLogId++, + fairId: data.fairId, + attendeeName: data.attendeeName, + scanTime: new Date().toISOString().replace('T', ' ').substring(0, 19), + gateName: data.gateName, + scanMethod: data.scanMethod, + }; + MOCK_GATE_LOGS.unshift(log); + return { code: 200, msg: '扫码成功,闸机已放行', data: log }; +} + +// ==================== 模块6: 数据统计 API ==================== + +/** 获取招聘会统计数据 */ +export async function getFairStatistics(fairId: number) { + const data = MOCK_STATISTICS[fairId]; + if (data) return { code: 200, data }; + // 如果没有该招聘会统计,生成默认数据 + const defaultData: FairStatistics = { + fairId, + companyCount: 0, + jobCount: 0, + demandCount: 0, + intentionCount: 0, + industryDistribution: [], + jobTypeDistribution: [], + dailyAttendees: [], + timeSlotDistribution: [], + }; + return { code: 200, data: defaultData }; +} diff --git a/src/types/jobfair/indoorJobFair.d.ts b/src/types/jobfair/indoorJobFair.d.ts new file mode 100644 index 0000000..03ae73a --- /dev/null +++ b/src/types/jobfair/indoorJobFair.d.ts @@ -0,0 +1,263 @@ +/** + * 室内固定摊位招聘会管理 类型定义 + */ + +declare namespace API.IndoorJobFair { + // ==================== 模块1: 场地信息 ==================== + export interface VenueItem { + id: number; + venueName: string; + employmentAgency: string; + venueType: string; + venueAddress: string; + contactPerson: string; + contactPhone: string; + boothCount: number; + status: string; + createTime: string; + } + + export interface VenueListParams { + employmentAgency?: string; + venueName?: string; + venueType?: string; + current?: number; + pageSize?: number; + } + + export interface VenueForm { + id?: number; + venueName: string; + employmentAgency: string; + venueType: string; + venueAddress: string; + contactPerson: string; + contactPhone: string; + boothCount: number; + } + + // ==================== 模块2: 摊位信息 ==================== + export interface BoothItem { + id: number; + venueId: number; + boothNumber: string; + companyId?: number; + companyName?: string; + status: string; + operator?: string; + operateTime?: string; + remark?: string; + } + + export interface BoothListParams { + venueId?: number; + boothNumber?: string; + companyName?: string; + current?: number; + pageSize?: number; + } + + export interface BoothForm { + id?: number; + venueId: number; + boothNumber: string; + companyId?: number; + companyName?: string; + status?: string; + operator?: string; + operateTime?: string; + remark?: string; + } + + // ==================== 模块3: 招聘会信息 ==================== + export interface JobFairItem { + id: number; + fairName: string; + venueId: number; + venueName: string; + startDate: string; + endDate: string; + status: string; + boothUseCount: number; + createTime: string; + } + + export interface JobFairListParams { + fairName?: string; + startDate?: string; + endDate?: string; + current?: number; + pageSize?: number; + } + + export interface JobFairForm { + id?: number; + fairName: string; + venueId: number; + venueName: string; + startDate: string; + endDate: string; + } + + // ==================== 模块4: 参会单位查询 ==================== + export interface CompanyQueryItem { + id: number; + fairId: number; + fairName: string; + companyId: number; + companyName: string; + industry: string; + scale: string; + contactPerson: string; + contactPhone: string; + registrationTime: string; + } + + export interface CompanyQueryParams { + fairName?: string; + current?: number; + pageSize?: number; + } + + // ==================== 模块5: 参会岗位查询 ==================== + export interface JobQueryItem { + id: number; + fairId: number; + fairName: string; + companyId: number; + companyName: string; + jobTitle: string; + minSalary: number; + maxSalary: number; + education: string; + experience: string; + vacancies: number; + status: string; + } + + export interface JobQueryParams { + fairName?: string; + companyName?: string; + current?: number; + pageSize?: number; + } + + // ==================== 模块6: 参会单位审核 ==================== + export interface CompanyReviewItem { + id: number; + fairId: number; + fairName: string; + companyId: number; + companyName: string; + industry: string; + scale: string; + contactPerson: string; + contactPhone: string; + reservationStart: string; + reservationEnd: string; + reviewStatus: string; + reviewTime?: string; + reviewRemark?: string; + } + + export interface CompanyReviewParams { + fairName?: string; + companyName?: string; + reviewStatus?: string; + reservationStart?: string; + reservationEnd?: string; + current?: number; + pageSize?: number; + } + + // ==================== 模块7: 退订信息管理 ==================== + export interface UnsubscribeItem { + id: number; + fairId: number; + fairName: string; + companyId: number; + companyName: string; + boothNumber: string; + contactPerson: string; + contactPhone: string; + unsubscribeTime: string; + reason: string; + } + + export interface UnsubscribeParams { + fairName?: string; + companyName?: string; + boothNumber?: string; + contactPerson?: string; + contactPhone?: string; + current?: number; + pageSize?: number; + } + + // ==================== 模块8: 单位参会查询 ==================== + export interface CompanyRecordItem { + id: number; + companyId: number; + companyName: string; + industry: string; + scale: string; + contactPerson: string; + contactPhone: string; + status: string; + creditStatus: string; + fairCount: number; + createTime: string; + } + + export interface CompanyRecordParams { + companyName?: string; + contactPerson?: string; + contactPhone?: string; + current?: number; + pageSize?: number; + } + + // ==================== 模块9: 招聘会岗位审核 ==================== + export interface JobReviewItem { + id: number; + fairId: number; + fairName: string; + companyId: number; + companyName: string; + jobId: number; + jobTitle: string; + education: string; + experience: string; + vacancies: number; + reviewStatus: string; + reviewTime?: string; + reviewRemark?: string; + } + + export interface JobReviewParams { + companyName?: string; + jobTitle?: string; + reviewStatus?: string; + current?: number; + pageSize?: number; + } + + // ==================== 模块10: 预留操作记录 ==================== + export interface ReservationRecordItem { + id: number; + fairId: number; + fairName: string; + boothNumber: string; + companyName?: string; + operator: string; + operateTime: string; + operateType: string; + remark?: string; + } + + export interface ReservationRecordParams { + operator?: string; + current?: number; + pageSize?: number; + } +} diff --git a/src/types/jobfair/outdoorFairDetail.d.ts b/src/types/jobfair/outdoorFairDetail.d.ts new file mode 100644 index 0000000..f555d17 --- /dev/null +++ b/src/types/jobfair/outdoorFairDetail.d.ts @@ -0,0 +1,192 @@ +/** + * 户外招聘会详情管理类型定义 + */ +declare namespace API.OutdoorFairDetail { + + // ==================== 模块1: 招聘会管理 ==================== + + /** 参会企业 */ + export interface ParticipatingCompany { + id: number; + fairId: number; + companyId: number; + companyName: string; + industry: string; + scale: string; + contactPerson: string; + contactPhone: string; + boothId?: number; + boothNumber?: string; + jobList: PostedJob[]; + } + + /** 已发布岗位 */ + export interface PostedJob { + id: number; + fairId: number; + companyId: number; + jobTitle: string; + minSalary: number; + maxSalary: number; + education: string; + experience: string; + vacancies: number; + status: string; // 'active' | 'closed' + } + + // ==================== 模块2: 展位管理 ==================== + + /** 展位信息 */ + export interface BoothItem { + id: number; + fairId: number; + boothNumber: string; + companyId?: number; + companyName?: string; + jobCount: number; + reviewStatus: string; // 'pending' | 'approved' | 'rejected' + assignedTime?: string; + createTime: string; + } + + /** 展位分配参数 */ + export interface BoothAssignParams { + fairId: number; + boothId: number; + companyId: number; + boothNumber: string; + } + + // ==================== 模块3: 参会单位管理 ==================== + + /** 单位报名记录 */ + export interface CompanyRegistration { + id: number; + fairId: number; + companyId: number; + companyName: string; + industry: string; + scale: string; + contactPerson: string; + contactPhone: string; + reviewStatus: string; // 'pending' | 'approved' | 'rejected' + reviewTime?: string; + reviewRemark?: string; + qrCheckInStatus: string; // 'checked_in' | 'not_checked_in' + checkInTime?: string; + registeredJobs: RegisteredJob[]; + } + + /** 报名岗位 */ + export interface RegisteredJob { + id: number; + regId: number; + jobTitle: string; + minSalary: number; + maxSalary: number; + education: string; + vacancies: number; + reviewStatus: string; // 'pending' | 'approved' | 'rejected' + } + + // ==================== 模块4: 参会人员管理 ==================== + + /** 参会人员 */ + export interface AttendeeItem { + id: number; + fairId: number; + name: string; + gender: string; + phone: string; + idCard: string; + education: string; + graduationSchool: string; + major: string; + checkInTime?: string; + checkInMethod: string; // 'qr_scan' | 'id_card' | 'manual' + targetCompanies: string; + mobileInteraction: boolean; + resumeUrl?: string; + resumeContent?: string; + interviewStatus: string; // 'none' | 'scheduled' | 'in_progress' | 'completed' | 'passed' | 'failed' + interviewCompany?: string; + interviewTime?: string; + interviewResult?: string; + } + + /** 面试跟踪参数 */ + export interface InterviewTrackParams { + attendeeId: number; + interviewStatus: string; + interviewCompany: string; + interviewTime: string; + interviewResult?: string; + } + + // ==================== 模块5: 入场设备管理 ==================== + + /** 设备信息 */ + export interface DeviceItem { + id: number; + fairId: number; + type: string; // 'mobile_terminal' | 'qr_sticker' + deviceName: string; + deviceCode: string; + assignedBoothId?: number; + assignedBoothNumber?: string; + assignedCompanyName?: string; + status: string; // 'online' | 'offline' + lastActiveTime?: string; + } + + /** 闸机状态 */ + export interface GateStatus { + id: number; + fairId: number; + gateName: string; + isOpen: boolean; + totalScans: number; + } + + /** 闸机入场记录 */ + export interface GateEntryLog { + id: number; + fairId: number; + attendeeName: string; + scanTime: string; + gateName: string; + scanMethod: string; // 'qr' | 'id_card' + } + + // ==================== 模块6: 数据统计 ==================== + + /** 招聘会统计数据 */ + export interface FairStatistics { + fairId: number; + companyCount: number; + jobCount: number; + demandCount: number; + intentionCount: number; + industryDistribution: { industry: string; count: number }[]; + jobTypeDistribution: { type: string; count: number }[]; + dailyAttendees: { date: string; count: number }[]; + timeSlotDistribution: { slot: string; count: number }[]; + } + + // ==================== 通用响应类型 ==================== + + /** 分页结果 */ + export interface PageResult { + code: number; + msg?: string; + total: number; + rows: T[]; + } + + /** 通用响应 */ + export interface CommonResult { + code: number; + msg?: string; + data?: any; + } +}