From e86621aeed038571ff042c1fce0ef1c5b1a6f200 Mon Sep 17 00:00:00 2001 From: lapuda <577732344@qq.com> Date: Sat, 25 Jul 2026 22:16:55 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=8B=9B=E8=81=98=E4=BC=9A?= =?UTF-8?q?=E5=89=8D=E7=AB=AF=E4=BA=A4=E4=BA=92=E4=B8=8E=E6=97=A0=E9=9A=9C?= =?UTF-8?q?=E7=A2=8D=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.tsx | 40 +- src/pages/JobPortal/JobFair/index.tsx | 4 + src/pages/Jobfair/Crosscityfair/index.tsx | 379 +++++++++++------- src/pages/Jobfair/FairStatistics/index.tsx | 89 +++- src/pages/Jobfair/Institution/index.tsx | 24 +- .../Detail/components/AttendeeTab.tsx | 167 ++++---- .../components/CompanyAttendanceTab.tsx | 32 +- .../components/CompanyUnsubscribeTab.tsx | 6 + .../Detail/components/DeviceTab.tsx | 16 +- .../Detail/components/FairInfoTab.tsx | 20 +- .../components/ParticipatingCompaniesTab.tsx | 69 +++- .../components/ParticipatingJobsTab.tsx | 26 +- .../components/CompanyFairDetailModal.tsx | 17 +- .../Outdoorfair/components/EditModal.tsx | 3 +- src/pages/Jobfair/Outdoorfair/index.tsx | 128 +++++- .../Jobfair/PublicJobFair/Detail/index.tsx | 18 +- src/pages/Jobfair/PublicJobFair/index.tsx | 9 + .../Venueinfo/components/EditModal.tsx | 110 ++++- src/pages/Jobfair/Venueinfo/index.tsx | 11 + src/services/jobfair/publicJobFair.ts | 12 + src/services/jobportal/outdoorFair.ts | 2 + src/services/jobportal/outdoorFairAttendee.ts | 57 +-- 22 files changed, 880 insertions(+), 359 deletions(-) diff --git a/src/app.tsx b/src/app.tsx index 2824777..45c8c2d 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -7,10 +7,7 @@ import { history } from '@umijs/max'; import defaultSettings from '../config/defaultSettings'; import { errorConfig } from './requestErrorConfig'; import { clearSessionToken, getAccessToken, getRefreshToken, getTokenExpireTime } from './access'; -import { - exchangeThirdPartyCredential, - isThirdPartyTransitionPage, -} from './utils/thirdPartyLogin'; +import { exchangeThirdPartyCredential, isThirdPartyTransitionPage } from './utils/thirdPartyLogin'; import { shouldSkipManagementBootstrap } from './utils/routeContext'; import { saveGetInfoCache } from './utils/jobPortalAuth'; import { @@ -30,6 +27,7 @@ import { stringify } from 'querystring'; import { message } from 'antd'; import React from 'react'; import { getProductionApiBaseUrl } from './utils/publicUrl'; +import defaultAvatar from './assets/avatar.jpg'; const isDev = process.env.NODE_ENV === 'development'; @@ -53,8 +51,6 @@ const loginOut = async () => { } }; - - /** * @see https://umijs.org/zh-CN/plugins/plugin-initial-state * */ @@ -70,8 +66,7 @@ export async function getInitialState(): Promise<{ skipErrorHandler: true, }); if (response.user.avatar === '') { - response.user.avatar = - 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png'; + response.user.avatar = defaultAvatar; } saveGetInfoCache(response as unknown as Record); return { @@ -110,11 +105,13 @@ export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) = // actionsRender: () => [, ], actionsRender: () => [], avatarProps: { - src: initialState?.currentUser?.avatar, + src: initialState?.currentUser?.avatar || defaultAvatar, + fallback: defaultAvatar, + onError: () => true, title: , - render: (_, avatarChildren) => { - return {avatarChildren}; - }, + render: (_, avatarChildren) => { + return {avatarChildren}; + }, }, waterMarkProps: { // content: initialState?.currentUser?.nickName, @@ -138,10 +135,7 @@ export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) = onPageChange: () => { const { location } = history; // 如果没有登录,重定向到 login,但排除过渡页面和求职者页面(考虑基础路径 /shihezi/) - if ( - !initialState?.currentUser && - !shouldSkipManagementBootstrap(location.pathname) - ) { + if (!initialState?.currentUser && !shouldSkipManagementBootstrap(location.pathname)) { history.push(PageEnum.LOGIN); } }, @@ -203,14 +197,16 @@ export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) = }; }; -export async function onRouteChange({ clientRoutes, location }: { clientRoutes: any; location: any }) { +export async function onRouteChange({ + clientRoutes, + location, +}: { + clientRoutes: any; + location: any; +}) { const menus = getRemoteMenu(); const token = getAccessToken(); - if ( - menus === null && - token && - !shouldSkipManagementBootstrap(location.pathname) - ) { + if (menus === null && token && !shouldSkipManagementBootstrap(location.pathname)) { console.log('检测到路由信息未加载,正在重新获取路由信息...'); // 重新获取路由信息,而不是刷新页面 diff --git a/src/pages/JobPortal/JobFair/index.tsx b/src/pages/JobPortal/JobFair/index.tsx index 7cef9b9..d214560 100644 --- a/src/pages/JobPortal/JobFair/index.tsx +++ b/src/pages/JobPortal/JobFair/index.tsx @@ -255,6 +255,10 @@ const PortalJobFairPage: React.FC = () => { onChange={(event) => setSearchInput(event.target.value)} onSearch={handleSearch} placeholder="搜索招聘会名称" + id="portal-job-fair-search" + name="jobFairTitle" + aria-label="搜索招聘会名称" + autoComplete="off" value={searchInput} /> {selectedDate && ( diff --git a/src/pages/Jobfair/Crosscityfair/index.tsx b/src/pages/Jobfair/Crosscityfair/index.tsx index 0167c1a..edeedea 100644 --- a/src/pages/Jobfair/Crosscityfair/index.tsx +++ b/src/pages/Jobfair/Crosscityfair/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; import dayjs, { Dayjs } from 'dayjs'; import { useAccess } from '@umijs/max'; import { @@ -24,6 +24,7 @@ import { message, Modal, Spin, + Dropdown, } from 'antd'; import { EnvironmentOutlined, @@ -35,6 +36,9 @@ import { EditOutlined, SettingOutlined, EyeOutlined, + InfoCircleOutlined, + DownOutlined, + SearchOutlined, } from '@ant-design/icons'; import { getDictValueEnum } from '@/services/system/dict'; import { @@ -48,18 +52,29 @@ import type { CrossCityAllianceForm, } from '@/services/jobfair/crossCityAlliance'; import { getCrossDomainJobs, getCrossDomainStatistics } from '@/services/jobfair/publicJobFair'; -import type { CrossDomainJobItem } from '@/services/jobfair/publicJobFair'; +import type { + CrossDomainJobItem, + CrossDomainStatisticsFairItem, +} from '@/services/jobfair/publicJobFair'; const { Title, Text } = Typography; const { RangePicker } = DatePicker; -const PAGE_SIZE_FOR_STATS = 10000; +const getDefaultDateRange = (): [Dayjs, Dayjs] => [dayjs().subtract(1, 'year'), dayjs()]; -const getDefaultDateRange = (): [Dayjs, Dayjs] => [dayjs().subtract(1, 'month'), dayjs()]; +type QuickDateRangeKey = '7days' | '30days' | '1year'; -const getDateRangePresets = () => [ - { label: '近7天', value: [dayjs().subtract(6, 'day'), dayjs()] as [Dayjs, Dayjs] }, - { label: '近1个月', value: getDefaultDateRange() }, +const getQuickDateRange = (key: QuickDateRangeKey): [Dayjs, Dayjs] => { + const end = dayjs(); + const days = { '7days': 6, '30days': 29 } as const; + if (key === '1year') return [end.subtract(1, 'year'), end]; + return [end.subtract(days[key], 'day'), end]; +}; + +const quickDateRangeItems = [ + { key: '7days', label: '近7天' }, + { key: '30days', label: '近30天' }, + { key: '1year', label: '近1年' }, ]; const getValueEnumText = (valueEnum: any, value?: string | number) => { @@ -107,7 +122,17 @@ const AllianceManageModal: React.FC = ({ open, onClose }; const columns: ProColumns[] = [ - { title: '城市名称', dataIndex: 'name', ellipsis: true }, + { + title: '城市名称', + dataIndex: 'name', + ellipsis: true, + fieldProps: { + id: 'cross-city-alliance-name-search', + name: 'name', + 'aria-label': '城市名称', + autoComplete: 'off', + }, + }, { title: '操作', valueType: 'option', @@ -150,7 +175,7 @@ const AllianceManageModal: React.FC = ({ open, onClose onCancel={onClose} footer={null} width={860} - destroyOnClose + destroyOnHidden > actionRef={actionRef} @@ -189,7 +214,7 @@ const AllianceManageModal: React.FC = ({ open, onClose title={currentRow ? '编辑联盟城市' : '新增联盟城市'} open={formOpen} - modalProps={{ destroyOnClose: true, onCancel: () => setFormOpen(false) }} + modalProps={{ destroyOnHidden: true, onCancel: () => setFormOpen(false) }} initialValues={ currentRow ? { @@ -218,6 +243,12 @@ const AllianceManageModal: React.FC = ({ open, onClose name="name" label="城市名称" placeholder="请输入城市名称,如:乌鲁木齐市" + fieldProps={{ + id: 'cross-city-alliance-name-input', + name: 'name', + 'aria-label': '城市名称', + autoComplete: 'address-level2', + }} rules={[{ required: true, message: '请输入城市名称' }]} /> @@ -262,18 +293,33 @@ const CitySelectTab: React.FC = ({ {cities.map((city) => ( - onToggle(city.name)} + type="button" + aria-pressed={selectedCities.includes(city.name)} + aria-label={`${city.name}${selectedCities.includes(city.name) ? ',已选择' : ',未选择'}`} + onClick={() => onToggle(city.name)} + onKeyDown={(event) => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + onToggle(city.name); + } + }} style={{ + appearance: 'none', + border: selectedCities.includes(city.name) + ? '1px solid #1677ff' + : '1px solid #d9d9d9', + background: selectedCities.includes(city.name) ? '#e6f4ff' : '#fafafa', + color: selectedCities.includes(city.name) ? '#1677ff' : 'rgba(0, 0, 0, 0.88)', + cursor: 'pointer', padding: '4px 12px', fontSize: 14, borderRadius: 4, }} > {city.name} - + ))} {selectedCities.length > 0 && ( + + - - - - } - suffix="家" - /> - - - - - } - suffix="个" - /> - - - - - } - suffix="人" - /> - - - - - } - suffix="次" - /> - - - + + + + + } + suffix="家" + /> + + + + + } + suffix="个" + /> + + + + + } + suffix="人" + /> + + + + + } + suffix="次" + /> + + + + - - - - - - - - -
- - - + + + rowKey="fairId" + columns={fairColumns} + dataSource={stats.fairs} + pagination={false} + size="small" + scroll={{ x: 860 }} + /> + - - 仅统计时间范围内招聘会类型为“线上”且标记为“跨域”的招聘会;参会企业按有效报名企业统计,即使企业未选择岗位也计入。岗位、招聘需求人数和浏览量均按岗位 - ID 去重,招聘数为空、填写“若干”或非正数时按 1 人计算。 + + + 统计口径 + + } + > + + + 仅统计时间范围内招聘会类型为“线上”且标记为“跨域”的招聘会;参会企业按有效报名企业统计,即使企业未选择岗位也计入。 + + + 岗位、招聘需求人数和浏览量均按岗位 ID 去重,招聘数为空、填写“若干”或非正数时按 1 + 人计算。 + + diff --git a/src/pages/Jobfair/FairStatistics/index.tsx b/src/pages/Jobfair/FairStatistics/index.tsx index 10b560f..04f4e05 100644 --- a/src/pages/Jobfair/FairStatistics/index.tsx +++ b/src/pages/Jobfair/FairStatistics/index.tsx @@ -5,16 +5,18 @@ import { Card, Col, DatePicker, + Dropdown, Empty, Row, Segmented, Select, + Space, Spin, Statistic, Table, message, } from 'antd'; -import { InfoCircleOutlined } from '@ant-design/icons'; +import { DownOutlined, InfoCircleOutlined, SearchOutlined } from '@ant-design/icons'; import { PageContainer } from '@ant-design/pro-components'; import type { ColumnsType } from 'antd/es/table'; import { getPublicJobFairList } from '@/services/jobfair/publicJobFair'; @@ -29,6 +31,7 @@ import { const { RangePicker } = DatePicker; type StatisticsScope = 'single' | 'range'; +type QuickDateRangeKey = '7days' | '30days' | '1year'; interface FairOption { value: string; @@ -38,11 +41,25 @@ interface FairOption { const formatDate = (value: any): string | undefined => value && typeof value.format === 'function' ? value.format('YYYY-MM-DD') : undefined; -const getDefaultDateRange = (): [Dayjs, Dayjs] => [dayjs().subtract(1, 'month'), dayjs()]; +const getDefaultDateRange = (): [Dayjs, Dayjs] => [dayjs().subtract(1, 'year'), dayjs()]; const getDateRangePresets = () => [ { label: '近7天', value: [dayjs().subtract(6, 'day'), dayjs()] as [Dayjs, Dayjs] }, - { label: '近1个月', value: getDefaultDateRange() }, + { label: '近30天', value: [dayjs().subtract(29, 'day'), dayjs()] as [Dayjs, Dayjs] }, + { label: '近1年', value: getDefaultDateRange() }, +]; + +const getQuickDateRange = (key: QuickDateRangeKey): [Dayjs, Dayjs] => { + const end = dayjs(); + if (key === '7days') return [end.subtract(6, 'day'), end]; + if (key === '30days') return [end.subtract(29, 'day'), end]; + return [end.subtract(1, 'year'), end]; +}; + +const quickDateRangeItems = [ + { key: '7days', label: '近7天' }, + { key: '30days', label: '近30天' }, + { key: '1year', label: '近1年' }, ]; const FairStatistics: React.FC = () => { @@ -50,7 +67,8 @@ const FairStatistics: React.FC = () => { const [scope, setScope] = useState('range'); const [fairOptions, setFairOptions] = useState([]); const [selectedFairId, setSelectedFairId] = useState(); - const [dateRange, setDateRange] = useState(getDefaultDateRange()); + const [dateRange, setDateRange] = useState<[Dayjs, Dayjs]>(getDefaultDateRange()); + const [queryDateRange, setQueryDateRange] = useState<[Dayjs, Dayjs]>(getDefaultDateRange()); const [loadingFairs, setLoadingFairs] = useState(false); const [loadingSummary, setLoadingSummary] = useState(false); const [summary, setSummary] = useState(); @@ -106,6 +124,7 @@ const FairStatistics: React.FC = () => { // 切换类型时同步清空条件,避免新类型携带旧类型招聘会 ID 发起查询。 setSelectedFairId(undefined); setDateRange(getDefaultDateRange()); + setQueryDateRange(getDefaultDateRange()); setSummary(undefined); setFairOptions([]); setFairType(type); @@ -113,8 +132,8 @@ const FairStatistics: React.FC = () => { const queryReady = useMemo(() => { if (scope === 'single') return Boolean(selectedFairId); - return Boolean(dateRange?.[0] && dateRange?.[1]); - }, [dateRange, scope, selectedFairId]); + return Boolean(queryDateRange?.[0] && queryDateRange?.[1]); + }, [queryDateRange, scope, selectedFairId]); const loadSummary = useCallback(async () => { if (!queryReady) { @@ -126,8 +145,8 @@ const FairStatistics: React.FC = () => { const res = await getFairStatisticsSummary({ fairType, fairId: scope === 'single' ? selectedFairId : undefined, - startDate: scope === 'range' ? formatDate(dateRange?.[0]) : undefined, - endDate: scope === 'range' ? formatDate(dateRange?.[1]) : undefined, + startDate: scope === 'range' ? formatDate(queryDateRange?.[0]) : undefined, + endDate: scope === 'range' ? formatDate(queryDateRange?.[1]) : undefined, }); if (res.code === 200 && res.data) { setSummary(res.data); @@ -141,7 +160,7 @@ const FairStatistics: React.FC = () => { } finally { setLoadingSummary(false); } - }, [dateRange, fairType, queryReady, scope, selectedFairId]); + }, [fairType, queryDateRange, queryReady, scope, selectedFairId]); useEffect(() => { loadSummary(); @@ -174,7 +193,7 @@ const FairStatistics: React.FC = () => { - + - + 统计范围 @@ -221,14 +240,40 @@ const FairStatistics: React.FC = () => { style={{ width: '100%', minWidth: 320 }} /> ) : ( - setDateRange(value)} - style={{ width: '100%', minWidth: 320 }} - placeholder={['起始日期', '结束日期']} - /> + + { + if (value?.[0] && value?.[1]) { + setDateRange(value as [Dayjs, Dayjs]); + } + }} + style={{ minWidth: 320 }} + placeholder={['起始日期', '结束日期']} + /> + setDateRange(getQuickDateRange(key as QuickDateRangeKey)), + }} + > + + + + )} @@ -266,7 +311,11 @@ const FairStatistics: React.FC = () => { {summary && ( - + rowKey="fairId" columns={columns} diff --git a/src/pages/Jobfair/Institution/index.tsx b/src/pages/Jobfair/Institution/index.tsx index 6406264..bd837d9 100644 --- a/src/pages/Jobfair/Institution/index.tsx +++ b/src/pages/Jobfair/Institution/index.tsx @@ -50,6 +50,12 @@ const InstitutionManagement: React.FC = () => { dataIndex: 'institutionName', ellipsis: true, width: 260, + fieldProps: { + id: 'job-fair-institution-name-search', + name: 'institutionName', + 'aria-label': '机构名称', + autoComplete: 'off', + }, }, { title: '机构简介', @@ -155,13 +161,27 @@ const InstitutionManagement: React.FC = () => { name="institutionName" label="机构名称" rules={[{ required: true, message: '请输入机构名称' }]} - fieldProps={{ maxLength: 200, showCount: true }} + fieldProps={{ + id: 'job-fair-institution-name-input', + name: 'institutionName', + 'aria-label': '机构名称', + autoComplete: 'organization', + maxLength: 200, + showCount: true, + }} placeholder="请输入机构名称" /> diff --git a/src/pages/Jobfair/Outdoorfair/Detail/components/AttendeeTab.tsx b/src/pages/Jobfair/Outdoorfair/Detail/components/AttendeeTab.tsx index d58f267..5d1da7e 100644 --- a/src/pages/Jobfair/Outdoorfair/Detail/components/AttendeeTab.tsx +++ b/src/pages/Jobfair/Outdoorfair/Detail/components/AttendeeTab.tsx @@ -1,33 +1,81 @@ import React, { useRef, useState } from 'react'; import { useAccess } from '@umijs/max'; -import { Button, Modal, message } from 'antd'; -import { ActionType, ProColumns, ProTable, ModalForm, ProFormText, ProFormDateTimePicker } from '@ant-design/pro-components'; -import { PlusOutlined, DeleteOutlined, EditOutlined } from '@ant-design/icons'; +import { Button, Modal, Select, message } from 'antd'; +import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components'; +import { DeleteOutlined, PlusOutlined } from '@ant-design/icons'; import { - getOutdoorFairAttendeeList, addOutdoorFairAttendee, - updateOutdoorFairAttendee, deleteOutdoorFairAttendee, + getAvailableOutdoorFairAttendeeUsers, + getOutdoorFairAttendeeList, } from '@/services/jobportal/outdoorFairAttendee'; import type { OutdoorFairAttendeeItem, - OutdoorFairAttendeeForm, + OutdoorFairAttendeeUserOption, } from '@/services/jobportal/outdoorFairAttendee'; interface Props { fairId: number; } +const getUserLabel = (user: OutdoorFairAttendeeUserOption) => { + const name = user.name || '未填写姓名'; + return user.phone ? `${name}(${user.phone})` : name; +}; + const AttendeeTab: React.FC = ({ fairId }) => { const access = useAccess(); const actionRef = useRef(); const [modalVisible, setModalVisible] = useState(false); - const [currentRow, setCurrentRow] = useState(); + const [userLoading, setUserLoading] = useState(false); + const [submitLoading, setSubmitLoading] = useState(false); + const [userOptions, setUserOptions] = useState([]); + + const loadUserOptions = async (keyword?: string) => { + setUserLoading(true); + try { + const res = await getAvailableOutdoorFairAttendeeUsers({ fairId, keyword }); + if (res.code === 200) { + setUserOptions(res.data || []); + } else { + message.error(res.msg || '移动端用户加载失败'); + } + } catch { + setUserOptions([]); + message.error('移动端用户加载失败'); + } finally { + setUserLoading(false); + } + }; + + const openAddModal = () => { + setUserOptions([]); + setModalVisible(true); + loadUserOptions(); + }; + + const handleUserSelect = async (userId: number) => { + setSubmitLoading(true); + try { + const res = await addOutdoorFairAttendee({ fairId, userId }); + if (res.code === 200) { + message.success('签到成功'); + setModalVisible(false); + actionRef.current?.reload(); + return; + } + message.error(res.msg || '签到失败'); + } catch { + message.error('签到失败,请重试'); + } finally { + setSubmitLoading(false); + } + }; const handleDelete = (id: number) => { Modal.confirm({ title: '确认删除', - content: '确定要删除该签到人员吗?', + content: '确定要删除该签到记录吗?', onOk: async () => { const res = await deleteOutdoorFairAttendee(id); if (res.code === 200) { @@ -41,9 +89,19 @@ const AttendeeTab: React.FC = ({ fairId }) => { }; const columns: ProColumns[] = [ - { title: '人员', dataIndex: 'name', ellipsis: true }, + { + title: '姓名', + dataIndex: 'name', + ellipsis: true, + fieldProps: { + id: 'outdoor-attendee-name-search', + name: 'name', + 'aria-label': '参会人员姓名', + autoComplete: 'off', + }, + }, { title: '手机号', dataIndex: 'phone', ellipsis: true, hideInSearch: true }, - { title: '住址', dataIndex: 'address', ellipsis: true, hideInSearch: true }, + { title: '地址', dataIndex: 'address', ellipsis: true, hideInSearch: true }, { title: '签到时间', dataIndex: 'checkInTime', @@ -53,22 +111,9 @@ const AttendeeTab: React.FC = ({ fairId }) => { { title: '操作', valueType: 'option', - width: 160, + width: 100, fixed: 'right', render: (_, record) => [ - , , @@ -120,53 +162,34 @@ const AttendeeTab: React.FC = ({ fairId }) => { columns={columns} /> - - title={currentRow ? '编辑签到人员' : '录入签到人员'} + setModalVisible(false) }} - initialValues={ - currentRow - ? { - id: currentRow.id, - fairId, - name: currentRow.name, - phone: currentRow.phone, - address: currentRow.address, - checkInTime: currentRow.checkInTime, - } - : { fairId } - } - onFinish={async (values) => { - const payload = { ...values, fairId }; - const res = values.id - ? await updateOutdoorFairAttendee(payload) - : await addOutdoorFairAttendee(payload); - if (res.code === 200) { - message.success(values.id ? '修改成功' : '新增成功'); - setModalVisible(false); - setCurrentRow(undefined); - actionRef.current?.reload(); - return true; - } - message.error(res.msg || '操作失败'); - return false; - }} + destroyOnClose + footer={null} + onCancel={() => setModalVisible(false)} > - 请选择移动端用户,选择后将立即完成签到: + + showSearch + allowClear + autoFocus + loading={userLoading || submitLoading} + disabled={submitLoading} + placeholder="请选择移动端用户" + id="outdoor-attendee-user-select" + aria-label="选择移动端用户" + style={{ width: '100%' }} + optionFilterProp="label" + options={userOptions.map((user) => ({ + value: user.userId, + label: getUserLabel(user), + }))} + onSearch={loadUserOptions} + onSelect={handleUserSelect} + notFoundContent={userLoading ? '加载中...' : '暂无可签到用户'} /> - - - - + ); }; diff --git a/src/pages/Jobfair/Outdoorfair/Detail/components/CompanyAttendanceTab.tsx b/src/pages/Jobfair/Outdoorfair/Detail/components/CompanyAttendanceTab.tsx index c733f02..638b084 100644 --- a/src/pages/Jobfair/Outdoorfair/Detail/components/CompanyAttendanceTab.tsx +++ b/src/pages/Jobfair/Outdoorfair/Detail/components/CompanyAttendanceTab.tsx @@ -1,17 +1,25 @@ import React from 'react'; import { Card, Col, Row, Tag } from 'antd'; import { ProColumns, ProTable } from '@ant-design/pro-components'; -import { - getOutdoorFairCompanyAttendanceList, -} from '@/services/jobportal/outdoorFairDetail'; +import { getOutdoorFairCompanyAttendanceList } from '@/services/jobportal/outdoorFairDetail'; import type { OutdoorFairCompanyAttendanceItem } from '@/services/jobportal/outdoorFairDetail'; interface Props { fairId: number; } -const columns: ProColumns[] = [ - { title: '企业名称', dataIndex: 'companyName', ellipsis: true }, +const getColumns = (checkedIn: boolean): ProColumns[] => [ + { + title: '企业名称', + dataIndex: 'companyName', + ellipsis: true, + fieldProps: { + id: `outdoor-company-attendance-${checkedIn ? 'checked-in' : 'not-checked-in'}-search`, + name: `companyName${checkedIn ? 'CheckedIn' : 'NotCheckedIn'}`, + 'aria-label': `${checkedIn ? '已签到' : '未签到'}企业名称`, + autoComplete: 'off', + }, + }, { title: '行业', dataIndex: 'industry', @@ -39,7 +47,7 @@ const CompanyAttendanceList: React.FC<{ fairId: number; checkedIn: boolean }> = }) => ( rowKey="id" - columns={columns} + columns={getColumns(checkedIn)} search={{ labelWidth: 'auto' }} options={false} pagination={{ pageSize: 8 }} @@ -62,20 +70,12 @@ const CompanyAttendanceList: React.FC<{ fairId: number; checkedIn: boolean }> = const CompanyAttendanceTab: React.FC = ({ fairId }) => ( - 已签到} - bodyStyle={{ padding: 0 }} - > + 已签到} bodyStyle={{ padding: 0 }}> - 未签到} - bodyStyle={{ padding: 0 }} - > + 未签到} bodyStyle={{ padding: 0 }}> diff --git a/src/pages/Jobfair/Outdoorfair/Detail/components/CompanyUnsubscribeTab.tsx b/src/pages/Jobfair/Outdoorfair/Detail/components/CompanyUnsubscribeTab.tsx index 86406be..a050a48 100644 --- a/src/pages/Jobfair/Outdoorfair/Detail/components/CompanyUnsubscribeTab.tsx +++ b/src/pages/Jobfair/Outdoorfair/Detail/components/CompanyUnsubscribeTab.tsx @@ -15,6 +15,12 @@ const CompanyUnsubscribeTab: React.FC = ({ fairId }) => { dataIndex: 'companyName', ellipsis: true, order: 1, + fieldProps: { + id: 'outdoor-company-unsubscribe-name-search', + name: 'companyName', + 'aria-label': '企业名称', + autoComplete: 'off', + }, }, { title: '联系人', dataIndex: 'contactPerson', width: 120, hideInSearch: true }, { title: '联系电话', dataIndex: 'contactPhone', width: 150, hideInSearch: true }, diff --git a/src/pages/Jobfair/Outdoorfair/Detail/components/DeviceTab.tsx b/src/pages/Jobfair/Outdoorfair/Detail/components/DeviceTab.tsx index 1b9002a..51a3433 100644 --- a/src/pages/Jobfair/Outdoorfair/Detail/components/DeviceTab.tsx +++ b/src/pages/Jobfair/Outdoorfair/Detail/components/DeviceTab.tsx @@ -67,6 +67,12 @@ const DeviceTab: React.FC = ({ fairId }) => { title: '设备码', dataIndex: 'deviceCode', ellipsis: true, + fieldProps: { + id: 'outdoor-device-code-search', + name: 'deviceCode', + 'aria-label': '设备码', + autoComplete: 'off', + }, render: (_, record) => ( {record.deviceCode} @@ -191,7 +197,13 @@ const DeviceTab: React.FC = ({ fairId }) => { name="deviceCode" label="设备码" placeholder="请输入设备码" - fieldProps={{ maxLength: 64 }} + fieldProps={{ + id: 'outdoor-device-code-input', + name: 'deviceCode', + 'aria-label': '设备码', + autoComplete: 'off', + maxLength: 64, + }} rules={[ { required: true, message: '请输入设备码' }, { max: 64, message: '设备码长度不能超过64个字符' }, @@ -201,6 +213,7 @@ const DeviceTab: React.FC = ({ fairId }) => { name="companyId" label="绑定企业" placeholder="选填,不选则创建未绑定的设备码" + fieldProps={{ id: 'outdoor-device-company-input', 'aria-label': '绑定企业' }} showSearch allowClear request={async () => { @@ -247,6 +260,7 @@ const DeviceTab: React.FC = ({ fairId }) => { label="参会企业" placeholder="请选择参会企业" showSearch + fieldProps={{ id: 'outdoor-device-company-bind-select', 'aria-label': '参会企业' }} request={async () => { const [companies, devicesRes] = await Promise.all([ getOutdoorFairCompanyOptions(fairId), diff --git a/src/pages/Jobfair/Outdoorfair/Detail/components/FairInfoTab.tsx b/src/pages/Jobfair/Outdoorfair/Detail/components/FairInfoTab.tsx index 45d709c..58def7f 100644 --- a/src/pages/Jobfair/Outdoorfair/Detail/components/FairInfoTab.tsx +++ b/src/pages/Jobfair/Outdoorfair/Detail/components/FairInfoTab.tsx @@ -8,6 +8,7 @@ import { getVenueInfo, getVenueInfoList } from '@/services/jobportal/venueInfo'; import type { VenueInfoItem } from '@/services/jobportal/venueInfo'; import { getDictSelectOption, getDictValueEnum } from '@/services/system/dict'; import EditModal from '@/pages/Jobfair/Outdoorfair/components/EditModal'; +import { normalizePublicUrl } from '@/utils/publicUrl'; const OUTDOOR_FAIR_TYPE_DICT = 'outdoor_fair_type'; const OUTDOOR_FAIR_REGION_DICT = 'outdoor_fair_region'; @@ -138,6 +139,7 @@ const FairInfoTab: React.FC = ({ fairInfo, onFairUpdate }) => { {fairInfo.address} {fairInfo.boothCount} + {fairInfo.boothMapCount ?? 0} {fairInfo.holdTime} {fairInfo.endTime} {fairInfo.applyStartTime} @@ -148,7 +150,7 @@ const FairInfoTab: React.FC = ({ fairInfo, onFairUpdate }) => { {fairInfo.photoUrl ? ( = ({ fairInfo, onFairUpdate }) => { {venueDetail.venueArea != null ? `${venueDetail.venueArea} ㎡` : '--'} - - {venueDetail.floorCount ?? '--'} - + {venueDetail.floorCount ?? '--'} {venueDetail.venueAddress || '--'} @@ -204,15 +204,9 @@ const FairInfoTab: React.FC = ({ fairInfo, onFairUpdate }) => { {venueDetail.routeLineNames || '--'} - - {venueDetail.handleDate || '--'} - - - {venueDetail.handleOrg || '--'} - - - {venueDetail.handler || '--'} - + {venueDetail.handleDate || '--'} + {venueDetail.handleOrg || '--'} + {venueDetail.handler || '--'} {venueDetail.remark || '--'} diff --git a/src/pages/Jobfair/Outdoorfair/Detail/components/ParticipatingCompaniesTab.tsx b/src/pages/Jobfair/Outdoorfair/Detail/components/ParticipatingCompaniesTab.tsx index 2c21c64..61c427e 100644 --- a/src/pages/Jobfair/Outdoorfair/Detail/components/ParticipatingCompaniesTab.tsx +++ b/src/pages/Jobfair/Outdoorfair/Detail/components/ParticipatingCompaniesTab.tsx @@ -32,6 +32,15 @@ type ReviewTarget = record: API.OutdoorFairDetail.PostedJob; }; +const REVIEW_STATUS_ENUM = { + '0': { text: '待审核', status: 'Processing' }, + '1': { text: '已通过', status: 'Success' }, + '2': { text: '已驳回', status: 'Error' }, +}; + +const formatSearchDateTime = (value: any) => + value && typeof value.format === 'function' ? value.format('YYYY-MM-DD HH:mm:ss') : value; + const ParticipatingCompaniesTab: React.FC = ({ fairId }) => { const companyActionRef = useRef(); const jobActionRef = useRef(); @@ -264,11 +273,54 @@ const ParticipatingCompaniesTab: React.FC = ({ fairId }) => { current: params.current, pageSize: params.pageSize, companyName: params.companyName, + reviewStatus: params.reviewStatus, + startTime: formatSearchDateTime(params.startTime), + endTime: formatSearchDateTime(params.endTime), }); return { data: res.rows || [], total: res.total || 0, success: res.code === 200 }; }} columns={[ - { title: '企业名称', dataIndex: 'companyName', ellipsis: true }, + { + title: '企业名称', + dataIndex: 'companyName', + ellipsis: true, + fieldProps: { + id: 'outdoor-participating-company-name-search', + name: 'companyName', + 'aria-label': '企业名称', + autoComplete: 'off', + }, + }, + { + title: '报名开始时间', + dataIndex: 'startTime', + hideInTable: true, + valueType: 'dateTime', + fieldProps: { + id: 'outdoor-participating-company-start-time-search', + name: 'startTime', + 'aria-label': '报名开始时间', + format: 'YYYY-MM-DD HH:mm:ss', + }, + search: { + transform: (value) => ({ startTime: formatSearchDateTime(value) }), + }, + }, + { + title: '报名结束时间', + dataIndex: 'endTime', + hideInTable: true, + valueType: 'dateTime', + fieldProps: { + id: 'outdoor-participating-company-end-time-search', + name: 'endTime', + 'aria-label': '报名结束时间', + format: 'YYYY-MM-DD HH:mm:ss', + }, + search: { + transform: (value) => ({ endTime: formatSearchDateTime(value) }), + }, + }, { title: '行业', dataIndex: 'industry', @@ -298,9 +350,22 @@ const ParticipatingCompaniesTab: React.FC = ({ fairId }) => { { title: '审核状态', dataIndex: 'reviewStatus', - hideInSearch: true, + valueType: 'select', + valueEnum: REVIEW_STATUS_ENUM, + fieldProps: { + id: 'outdoor-participating-company-review-status-search', + name: 'reviewStatus', + 'aria-label': '审核状态', + }, render: (_, record) => renderReviewStatus(record.reviewStatus), }, + { + title: '报名时间', + dataIndex: 'registrationTime', + valueType: 'dateTime', + hideInSearch: true, + width: 170, + }, { title: '展位号', dataIndex: 'boothNumber', diff --git a/src/pages/Jobfair/Outdoorfair/Detail/components/ParticipatingJobsTab.tsx b/src/pages/Jobfair/Outdoorfair/Detail/components/ParticipatingJobsTab.tsx index 3d5df98..30f3a24 100644 --- a/src/pages/Jobfair/Outdoorfair/Detail/components/ParticipatingJobsTab.tsx +++ b/src/pages/Jobfair/Outdoorfair/Detail/components/ParticipatingJobsTab.tsx @@ -105,7 +105,17 @@ const ParticipatingJobsTab: React.FC = ({ fairId }) => { }; const columns: ProColumns[] = [ - { title: '岗位名称', dataIndex: 'jobTitle', ellipsis: true }, + { + title: '岗位名称', + dataIndex: 'jobTitle', + ellipsis: true, + fieldProps: { + id: 'outdoor-participating-job-title-search', + name: 'jobTitle', + 'aria-label': '岗位名称', + autoComplete: 'off', + }, + }, { title: '企业名称', dataIndex: 'companyId', @@ -126,6 +136,10 @@ const ParticipatingJobsTab: React.FC = ({ fairId }) => { ); }, fieldProps: { + id: 'outdoor-participating-job-company-search', + name: 'companyId', + 'aria-label': '企业名称', + autoComplete: 'off', showSearch: true, optionFilterProp: 'label', placeholder: '请选择或搜索企业', @@ -144,6 +158,11 @@ const ParticipatingJobsTab: React.FC = ({ fairId }) => { valueType: 'select', valueEnum: educationEnum, width: 110, + fieldProps: { + id: 'outdoor-participating-job-education-search', + name: 'education', + 'aria-label': '学历要求', + }, }, { title: '工作经验', @@ -151,6 +170,11 @@ const ParticipatingJobsTab: React.FC = ({ fairId }) => { valueType: 'select', valueEnum: experienceEnum, width: 110, + fieldProps: { + id: 'outdoor-participating-job-experience-search', + name: 'experience', + 'aria-label': '工作经验', + }, }, { title: '招聘人数', dataIndex: 'vacancies', hideInSearch: true, width: 90 }, { title: '工作地点', dataIndex: 'jobLocation', hideInSearch: true, ellipsis: true }, diff --git a/src/pages/Jobfair/Outdoorfair/components/CompanyFairDetailModal.tsx b/src/pages/Jobfair/Outdoorfair/components/CompanyFairDetailModal.tsx index c32d68c..3de1260 100644 --- a/src/pages/Jobfair/Outdoorfair/components/CompanyFairDetailModal.tsx +++ b/src/pages/Jobfair/Outdoorfair/components/CompanyFairDetailModal.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { Descriptions, Divider, Image, Modal, Tag } from 'antd'; import type { OutdoorFairItem } from '@/services/jobportal/outdoorFair'; +import { normalizePublicUrl } from '@/utils/publicUrl'; interface Props { fair?: OutdoorFairItem | null; @@ -33,17 +34,25 @@ const CompanyFairDetailModal: React.FC = ({ fair, open, onCancel }) => ( {fair.address || '--'} {fair.boothCount ?? '--'} + {fair.boothMapCount ?? 0} {fair.reservedBoothCount ?? 0} {fair.holdTime || '--'} {fair.endTime || '--'} {fair.applyStartTime || '--'} {fair.applyEndTime || '--'} - {fair.onlineApply ? '是' : '否'} + + {fair.onlineApply ? '是' : '否'} + {fair.photoUrl ? ( - + ) : ( '--' )} @@ -59,7 +68,9 @@ const CompanyFairDetailModal: React.FC = ({ fair, open, onCancel }) => ( 企业参会专属信息 - {content(fair.requiredMaterials)} + + {content(fair.requiredMaterials)} + {content(fair.attendanceNotes)} diff --git a/src/pages/Jobfair/Outdoorfair/components/EditModal.tsx b/src/pages/Jobfair/Outdoorfair/components/EditModal.tsx index 8fbb5d5..1a5205c 100644 --- a/src/pages/Jobfair/Outdoorfair/components/EditModal.tsx +++ b/src/pages/Jobfair/Outdoorfair/components/EditModal.tsx @@ -17,6 +17,7 @@ import type { OutdoorFairDictForm, } from '@/services/jobportal/outdoorFair'; import { uploadOutdoorFairPhoto } from '@/services/jobportal/outdoorFair'; +import { normalizePublicUrl } from '@/utils/publicUrl'; interface EditModalProps { open: boolean; @@ -356,7 +357,7 @@ const EditModal: React.FC = ({ {photoUrl ? ( + value && typeof value.format === 'function' ? value.format('YYYY-MM-DD HH:mm:ss') : value; + const OutdoorFairList: React.FC = () => { const access = useAccess(); const { initialState } = useModel('@@initialState'); @@ -185,14 +188,22 @@ const OutdoorFairList: React.FC = () => { jobCount: 0, }); - const handleSignup = async (record: OutdoorFairItem) => { - const res = await signupOutdoorFair(record.id); - if (res.code === 200) { - message.success('报名成功,等待审核'); - actionRef.current?.reload(); - } else { - message.error(res.msg || '报名失败'); - } + const handleSignup = (record: OutdoorFairItem) => { + Modal.confirm({ + title: '报名户外招聘会', + content: `确定报名「${record.title}」吗?报名后需要等待管理员审核。`, + okText: '确认报名', + cancelText: '取消', + onOk: async () => { + const res = await signupOutdoorFair(record.id); + if (res.code === 200) { + message.success('报名成功,等待审核'); + actionRef.current?.reload(); + } else { + message.error(res.msg || '报名失败'); + } + }, + }); }; const handleCheckIn = async (record: OutdoorFairItem) => { @@ -304,7 +315,17 @@ const OutdoorFairList: React.FC = () => { }; const jobColumns: ProColumns[] = [ - { title: '岗位名称', dataIndex: 'jobTitle', ellipsis: true }, + { + title: '岗位名称', + dataIndex: 'jobTitle', + ellipsis: true, + fieldProps: { + id: 'outdoor-company-job-title-search', + name: 'jobTitle', + 'aria-label': '岗位名称', + autoComplete: 'off', + }, + }, { title: '薪资范围', hideInSearch: true, @@ -316,6 +337,11 @@ const OutdoorFairList: React.FC = () => { valueType: 'select', valueEnum: educationEnum, width: 110, + fieldProps: { + id: 'outdoor-company-job-education-search', + name: 'education', + 'aria-label': '学历要求', + }, }, { title: '工作经验', @@ -323,6 +349,11 @@ const OutdoorFairList: React.FC = () => { valueType: 'select', valueEnum: experienceEnum, width: 110, + fieldProps: { + id: 'outdoor-company-job-experience-search', + name: 'experience', + 'aria-label': '工作经验', + }, }, { title: '招聘人数', dataIndex: 'vacancies', hideInSearch: true, width: 90 }, { @@ -340,11 +371,23 @@ const OutdoorFairList: React.FC = () => { title: '招聘会标题', dataIndex: 'title', ellipsis: true, + fieldProps: { + id: 'outdoor-fair-title-search', + name: 'title', + 'aria-label': '招聘会标题', + autoComplete: 'off', + }, }, { title: '举办单位', dataIndex: 'hostUnit', ellipsis: true, + fieldProps: { + id: 'outdoor-fair-host-unit-search', + name: 'hostUnit', + 'aria-label': '举办单位', + autoComplete: 'off', + }, }, { title: '招聘会类型', @@ -352,6 +395,11 @@ const OutdoorFairList: React.FC = () => { ellipsis: true, valueType: 'select', valueEnum: fairTypeValueEnum, + fieldProps: { + id: 'outdoor-fair-type-search', + name: 'fairType', + 'aria-label': '招聘会类型', + }, }, { title: '举办区域', @@ -359,6 +407,11 @@ const OutdoorFairList: React.FC = () => { ellipsis: true, valueType: 'select', valueEnum: regionValueEnum, + fieldProps: { + id: 'outdoor-fair-region-search', + name: 'region', + 'aria-label': '举办区域', + }, }, { title: '场地名称', @@ -366,6 +419,9 @@ const OutdoorFairList: React.FC = () => { ellipsis: true, valueType: 'select', fieldProps: { + id: 'outdoor-fair-venue-search', + name: 'venueId', + 'aria-label': '场地名称', options: venueOptions, allowClear: true, showSearch: true, @@ -387,6 +443,12 @@ const OutdoorFairList: React.FC = () => { dataIndex: 'boothCount', hideInSearch: true, }, + { + title: '展位图数量', + dataIndex: 'boothMapCount', + hideInSearch: true, + render: (_, record) => record.boothMapCount ?? 0, + }, { title: '已预定摊位数', dataIndex: 'reservedBoothCount', @@ -394,28 +456,60 @@ const OutdoorFairList: React.FC = () => { render: (_, record) => record.reservedBoothCount ?? 0, }, { - title: '举办时间', + title: '开始时间', dataIndex: 'holdTime', valueType: 'dateTime', - hideInSearch: true, + fieldProps: { + id: 'outdoor-fair-start-time-search', + name: 'startTime', + 'aria-label': '开始时间', + format: 'YYYY-MM-DD HH:mm:ss', + }, + search: { + transform: (value) => ({ startTime: formatSearchDateTime(value) }), + }, }, { title: '结束时间', dataIndex: 'endTime', valueType: 'dateTime', - hideInSearch: true, + fieldProps: { + id: 'outdoor-fair-end-time-search', + name: 'endTime', + 'aria-label': '结束时间', + format: 'YYYY-MM-DD HH:mm:ss', + }, + search: { + transform: (value) => ({ endTime: formatSearchDateTime(value) }), + }, }, { title: '报名开始时间', dataIndex: 'applyStartTime', valueType: 'dateTime', - hideInSearch: true, + fieldProps: { + id: 'outdoor-fair-apply-start-time-search', + name: 'applyStartTime', + 'aria-label': '报名开始时间', + format: 'YYYY-MM-DD HH:mm:ss', + }, + search: { + transform: (value) => ({ applyStartTime: formatSearchDateTime(value) }), + }, }, { title: '报名截止时间', dataIndex: 'applyEndTime', valueType: 'dateTime', - hideInSearch: true, + fieldProps: { + id: 'outdoor-fair-apply-end-time-search', + name: 'applyEndTime', + 'aria-label': '报名截止时间', + format: 'YYYY-MM-DD HH:mm:ss', + }, + search: { + transform: (value) => ({ applyEndTime: formatSearchDateTime(value) }), + }, }, { title: '线上申请', @@ -452,7 +546,7 @@ const OutdoorFairList: React.FC = () => { render: (_, record) => record.photoUrl ? ( {record.title} @@ -622,6 +716,10 @@ const OutdoorFairList: React.FC = () => { region: params.region, venueId: params.venueId, onlineApply: params.onlineApply, + startTime: formatSearchDateTime(params.startTime), + endTime: formatSearchDateTime(params.endTime), + applyStartTime: formatSearchDateTime(params.applyStartTime), + applyEndTime: formatSearchDateTime(params.applyEndTime), } as OutdoorFairListParams); return { data: res.rows, total: res.total, success: true }; }} diff --git a/src/pages/Jobfair/PublicJobFair/Detail/index.tsx b/src/pages/Jobfair/PublicJobFair/Detail/index.tsx index d717ca1..80fc9c0 100644 --- a/src/pages/Jobfair/PublicJobFair/Detail/index.tsx +++ b/src/pages/Jobfair/PublicJobFair/Detail/index.tsx @@ -54,6 +54,8 @@ const JobFairDetail: React.FC = () => { const [reviewCompany, setReviewCompany] = useState(); const [reviewSubmitting, setReviewSubmitting] = useState(false); const [jobFairRegionTypeEnum, setJobFairRegionTypeEnum] = useState({}); + const [educationEnum, setEducationEnum] = useState({}); + const [experienceEnum, setExperienceEnum] = useState({}); const [reviewForm] = Form.useForm(); const reviewStatus = Form.useWatch('reviewStatus', reviewForm); @@ -77,8 +79,16 @@ const JobFairDetail: React.FC = () => { useEffect(() => { getDictValueEnum('job_fair_region_type').then(setJobFairRegionTypeEnum); + getDictValueEnum('education', true, true).then(setEducationEnum); + getDictValueEnum('experience', true, true).then(setExperienceEnum); }, []); + const getJobDictLabel = (value: string | number | undefined, enums: any) => { + if (value === undefined || value === null || value === '') return '未说明'; + const item = enums[String(value)] || enums[value]; + return item?.label || item?.text || '待配置'; + }; + const handleRemoveCompany = (company: API.PublicJobFair.CompanyInfo) => { Modal.confirm({ title: '确认移除', @@ -320,8 +330,12 @@ const JobFairDetail: React.FC = () => { ? `${job.minSalary || 0}-${job.maxSalary || 0} 元/月` : '面议'} - {job.education && {job.education}} - {job.experience && {job.experience}} + {job.education && ( + {getJobDictLabel(job.education, educationEnum)} + )} + {job.experience && ( + {getJobDictLabel(job.experience, experienceEnum)} + )} {job.vacancies !== undefined && 招{job.vacancies}人} diff --git a/src/pages/Jobfair/PublicJobFair/index.tsx b/src/pages/Jobfair/PublicJobFair/index.tsx index dacc8a4..644cc18 100644 --- a/src/pages/Jobfair/PublicJobFair/index.tsx +++ b/src/pages/Jobfair/PublicJobFair/index.tsx @@ -103,6 +103,9 @@ const PublicJobFairList: React.FC = () => { hideInTable: true, valueType: 'dateRange', fieldProps: { + id: { start: 'public-job-fair-start-date', end: 'public-job-fair-end-date' }, + name: ['publicJobFairStartDate', 'publicJobFairEndDate'], + 'aria-label': '招聘会时间范围', format: 'YYYY-MM-DD', }, search: { @@ -116,6 +119,12 @@ const PublicJobFairList: React.FC = () => { title: '招聘会标题', dataIndex: 'jobFairTitle', ellipsis: true, + fieldProps: { + id: 'public-job-fair-title-search', + name: 'jobFairTitle', + 'aria-label': '招聘会标题', + autoComplete: 'off', + }, }, { title: '地址', diff --git a/src/pages/Jobfair/Venueinfo/components/EditModal.tsx b/src/pages/Jobfair/Venueinfo/components/EditModal.tsx index 1b52e72..8bf63be 100644 --- a/src/pages/Jobfair/Venueinfo/components/EditModal.tsx +++ b/src/pages/Jobfair/Venueinfo/components/EditModal.tsx @@ -195,6 +195,8 @@ const EditModal: React.FC = ({ width="md" options={venueTypeOptions} fieldProps={{ + id: 'venue-info-type-input', + 'aria-label': '场地类型', showSearch: true, dropdownRender: renderVenueTypeDropdown, }} @@ -207,13 +209,24 @@ const EditModal: React.FC = ({ width="md" rules={[{ required: true, message: '请输入场地名称' }]} placeholder="请输入场地名称" + fieldProps={{ + id: 'venue-info-name-input', + name: 'venueName', + 'aria-label': '场地名称', + autoComplete: 'off', + }} /> @@ -223,16 +236,38 @@ const EditModal: React.FC = ({ label="展位数量" width="md" min={0} - fieldProps={{ precision: 0 }} + fieldProps={{ + id: 'venue-info-booth-count-input', + name: 'floorCount', + 'aria-label': '展位数量', + precision: 0, + }} placeholder="请输入展位数量" /> - + = ({ mode="multiple" options={lineOptions} fieldProps={{ + id: 'venue-info-route-line-select', + 'aria-label': '乘坐线路', showSearch: true, optionFilterProp: 'label', }} @@ -254,6 +291,8 @@ const EditModal: React.FC = ({ value: item.id, }))} fieldProps={{ + id: 'venue-info-institution-select', + 'aria-label': '所属机构', showSearch: true, optionFilterProp: 'label', dropdownRender: renderInstitutionDropdown, @@ -263,7 +302,13 @@ const EditModal: React.FC = ({ @@ -271,10 +316,37 @@ const EditModal: React.FC = ({ name="handleDate" label="经办日期" width="md" - fieldProps={{ format: 'YYYY-MM-DD' }} + fieldProps={{ + id: 'venue-info-handle-date-input', + name: 'handleDate', + 'aria-label': '经办日期', + format: 'YYYY-MM-DD', + }} + /> + + - - = ({ maxLength={100} showCount placeholder="请输入场地类型" + id="venue-info-type-option-input" + name="venueTypeOption" + aria-label="新增场地类型" + autoComplete="off" onChange={(event) => setDictInputValue(event.target.value)} onPressEnter={handleDictModalOk} /> @@ -308,10 +384,26 @@ const EditModal: React.FC = ({ label="机构名称" rules={[{ required: true, message: '请输入机构名称' }]} > - + - + diff --git a/src/pages/Jobfair/Venueinfo/index.tsx b/src/pages/Jobfair/Venueinfo/index.tsx index 84d0f7e..5f1e23a 100644 --- a/src/pages/Jobfair/Venueinfo/index.tsx +++ b/src/pages/Jobfair/Venueinfo/index.tsx @@ -125,11 +125,22 @@ const VenueInfoList: React.FC = () => { ellipsis: true, valueType: 'select', valueEnum: venueTypeValueEnum, + fieldProps: { + id: 'venue-info-type-search', + name: 'venueType', + 'aria-label': '场地类型', + }, }, { title: '场地名称', dataIndex: 'venueName', ellipsis: true, + fieldProps: { + id: 'venue-info-name-search', + name: 'venueName', + 'aria-label': '场地名称', + autoComplete: 'off', + }, }, { title: '场地面积', diff --git a/src/services/jobfair/publicJobFair.ts b/src/services/jobfair/publicJobFair.ts index 6d8efad..2ca6aa0 100644 --- a/src/services/jobfair/publicJobFair.ts +++ b/src/services/jobfair/publicJobFair.ts @@ -86,6 +86,18 @@ export interface CrossDomainStatistics { jobCount: number; demandCount: number; viewCount: number; + fairs: CrossDomainStatisticsFairItem[]; +} + +/** 跨域线上招聘会统计中的单场明细 */ +export interface CrossDomainStatisticsFairItem { + fairId: string; + fairTitle: string; + startTime?: string; + companyCount: number; + jobCount: number; + demandCount: number; + viewCount: number; } export async function getCrossDomainStatistics(params: CrossDomainStatisticsParams) { diff --git a/src/services/jobportal/outdoorFair.ts b/src/services/jobportal/outdoorFair.ts index 8b07658..cb9abf7 100644 --- a/src/services/jobportal/outdoorFair.ts +++ b/src/services/jobportal/outdoorFair.ts @@ -24,6 +24,8 @@ export interface OutdoorFairItem { boothCount: number; /** 已预定摊位数 */ reservedBoothCount?: number; + /** 招聘会实际展位图中的展位数量 */ + boothMapCount?: number; /** 举办时间 */ holdTime: string; /** 招聘会结束时间 */ diff --git a/src/services/jobportal/outdoorFairAttendee.ts b/src/services/jobportal/outdoorFairAttendee.ts index cdbcb3c..321c879 100644 --- a/src/services/jobportal/outdoorFairAttendee.ts +++ b/src/services/jobportal/outdoorFairAttendee.ts @@ -4,6 +4,8 @@ import { request } from '@umijs/max'; export interface OutdoorFairAttendeeItem { id: number; fairId: number; + /** 关联的移动端用户 ID */ + userId?: number; /** 人员姓名 */ name: string; /** 手机号 */ @@ -36,14 +38,18 @@ export interface OutdoorFairAttendeeListResult { rows: OutdoorFairAttendeeItem[]; } -/** 签到人员新增/编辑表单 */ +/** 后台录入签到参数 */ export interface OutdoorFairAttendeeForm { - id?: number; fairId: number; - name: string; + userId: number; +} + +/** 后台录入签到时的移动端用户选项 */ +export interface OutdoorFairAttendeeUserOption { + userId: number; + name?: string; phone?: string; address?: string; - checkInTime?: string; } /** 通用响应 */ @@ -53,14 +59,14 @@ export interface OutdoorFairAttendeeCommonResult { data?: OutdoorFairAttendeeItem; } -const BASE = '/api/cms/outdoor-fair-attendee'; +/** 可录入签到的移动端用户列表响应 */ +export interface OutdoorFairAttendeeUserOptionResult { + code: number; + msg?: string; + data?: OutdoorFairAttendeeUserOption[]; +} -const formatDateTime = (value: any) => { - if (value && typeof value.format === 'function') { - return value.format('YYYY-MM-DD HH:mm:ss'); - } - return value; -}; +const BASE = '/api/cms/outdoor-fair-attendee'; /** * 获取签到人员列表 @@ -74,24 +80,27 @@ export async function getOutdoorFairAttendeeList(params?: OutdoorFairAttendeeLis } /** - * 新增签到人员 + * 获取当前招聘会尚未签到的移动端用户 + * GET /api/cms/outdoor-fair-attendee/available-users + */ +export async function getAvailableOutdoorFairAttendeeUsers(params: { + fairId: number; + keyword?: string; +}) { + return request(`${BASE}/available-users`, { + method: 'GET', + params, + }); +} + +/** + * 选择移动端用户并立即完成签到 * POST /api/cms/outdoor-fair-attendee */ export async function addOutdoorFairAttendee(data: OutdoorFairAttendeeForm) { return request(BASE, { method: 'POST', - data: { ...data, checkInTime: formatDateTime(data.checkInTime) }, - }); -} - -/** - * 修改签到人员 - * PUT /api/cms/outdoor-fair-attendee - */ -export async function updateOutdoorFairAttendee(data: OutdoorFairAttendeeForm) { - return request(BASE, { - method: 'PUT', - data: { ...data, checkInTime: formatDateTime(data.checkInTime) }, + data, }); }