Refactor outdoor fair management:

- Remove .DS_Store file.
- Update .gitignore to include .DS_Store.
- Modify proxy configuration to use environment variable for backend URL.
- Correct component paths in routes.
- Implement outdoor fair detail fetching with improved API call.
- Enhance EditModal for outdoor fair with dynamic dictionary options.
- Add functionality to upload outdoor fair photos.
- Update service layer to handle outdoor fair dictionary options.
This commit is contained in:
2026-06-25 11:30:40 +08:00
parent 5ecce431db
commit 93f39886d9
9 changed files with 555 additions and 245 deletions

BIN
.DS_Store vendored

Binary file not shown.

4
.gitignore vendored
View File

@@ -13,4 +13,6 @@ src/.umi-production/
# Archives
*.rar
*.zip
*.zip
.DS_Store
.local/

View File

@@ -9,13 +9,15 @@
*
* @doc https://umijs.org/docs/guides/proxy
*/
const localBackendUrl = process.env.LOCAL_BACKEND_URL || 'http://127.0.0.1:9091';
export default {
// 如果需要自定义本地开发服务器 请取消注释按需调整
dev: {
// localhost:8000/api/** -> https://preview.pro.ant.design/api/**
'/api/': {
// 要代理的地址
target: 'http://localhost:9091', // 本地开发环境
target: localBackendUrl,
// target: 'http://39.98.44.136:6024/api/shihezi/', // 线上环境
// target: 'http://wykj.cdwsx.com/api',// 后端
// target: 'http://ks.zhaopinzao8dian.com/api/ks',
@@ -26,7 +28,7 @@ export default {
// logLevel: 'debug',
},
'/profile/avatar/': {
target: 'http://localhost:9091', // 本地开发环境
target: localBackendUrl,
// target: 'http://36.105.163.21:30081/api/ks', // 发版
// target: 'http://wykj.cdwsx.com/api',
// target: 'http://ks.zhaopinzao8dian.com/api/ks',
@@ -34,7 +36,7 @@ export default {
},
'/app/': {
// 要代理的地址
target: 'http://localhost:9091', // 本地开发环境
target: localBackendUrl,
// target: 'http://36.105.163.21:30081/api/ks', // 发版
// target: 'http://wykj.cdwsx.com/api',
// target: 'http://ks.zhaopinzao8dian.com/api/ks',

View File

@@ -209,17 +209,17 @@ 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: '户外招聘会管理',

231
local.sh Executable file
View File

@@ -0,0 +1,231 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
RUNTIME_DIR="${RUNTIME_DIR:-${ROOT_DIR}/.local}"
LOG_DIR="${RUNTIME_DIR}/logs"
LOG_FILE="${LOG_FILE:-${LOG_DIR}/frontend.log}"
PID_FILE="${PID_FILE:-${RUNTIME_DIR}/frontend.pid}"
BACKEND_URL="${LOCAL_BACKEND_URL:-http://127.0.0.1:9091}"
FRONTEND_PORT="${PORT:-8000}"
NPM_CMD="${NPM_CMD:-npm}"
MAX_CMD="${ROOT_DIR}/node_modules/.bin/max"
info() {
printf '\033[1;32m%s\033[0m\n' "$*"
}
error() {
printf '\033[1;31m%s\033[0m\n' "$*" >&2
}
usage() {
cat <<'EOF'
用法:./local.sh <命令> [参数]
命令:
install 使用 package-lock.json 安装依赖
start [--skip-install] 检查本地后端并在后台启动前端
stop 停止本脚本启动的前端进程
restart [--skip-install] 重启前端
status 查看前端和本地后端状态
logs [行数] 查看并持续跟踪日志,默认显示最后 200 行
help 显示帮助
可选环境变量:
LOCAL_BACKEND_URL 本地后端地址,默认 http://127.0.0.1:9091
PORT 前端端口,默认 8000
NPM_CMD npm 命令,默认 npm
EOF
}
prepare_runtime() {
mkdir -p "${LOG_DIR}"
}
read_pid() {
if [[ -f "${PID_FILE}" ]]; then
tr -d '[:space:]' < "${PID_FILE}"
fi
}
is_running() {
local pid
pid="$(read_pid)"
[[ -n "${pid}" ]] && kill -0 "${pid}" 2>/dev/null
}
install_dependencies() {
command -v "${NPM_CMD}" >/dev/null 2>&1 || {
error "未找到 npm 命令:${NPM_CMD}"
exit 1
}
info "正在根据 package-lock.json 安装依赖..."
(
cd "${ROOT_DIR}"
"${NPM_CMD}" ci
)
}
check_backend() {
local status
command -v curl >/dev/null 2>&1 || {
error "未找到 curl无法检查本地后端"
exit 1
}
status="$(curl -s -o /dev/null --max-time 3 -w '%{http_code}' "${BACKEND_URL}/" || true)"
if [[ -z "${status}" || "${status}" == "000" ]]; then
error "本地后端不可访问:${BACKEND_URL}"
error "请先在 shz-backend 中执行:./local.sh start"
exit 1
fi
info "本地后端已连接:${BACKEND_URL}HTTP ${status}"
}
start_frontend() {
local skip_install="${1:-}"
prepare_runtime
if is_running; then
info "前端已在运行PID$(read_pid)"
return 0
fi
rm -f "${PID_FILE}"
check_backend
if [[ ! -x "${MAX_CMD}" ]]; then
if [[ "${skip_install}" == "--skip-install" ]]; then
error "依赖尚未安装,不能跳过安装:${MAX_CMD} 不存在"
exit 1
fi
install_dependencies
elif [[ -n "${skip_install}" && "${skip_install}" != "--skip-install" ]]; then
error "未知参数:${skip_install}"
exit 2
fi
export REACT_APP_ENV=dev
export MOCK=none
export UMI_ENV=dev
export LOCAL_BACKEND_URL="${BACKEND_URL}"
export PORT="${FRONTEND_PORT}"
info "正在启动前端http://localhost:${FRONTEND_PORT}"
info "后端代理目标:${BACKEND_URL}"
(
cd "${ROOT_DIR}"
nohup "${MAX_CMD}" dev >> "${LOG_FILE}" 2>&1 &
printf '%s\n' "$!" > "${PID_FILE}"
)
local frontend_status="000"
for _ in {1..60}; do
if ! is_running; then
break
fi
frontend_status="$(curl -s -o /dev/null --max-time 1 -w '%{http_code}' \
"http://127.0.0.1:${FRONTEND_PORT}/" || true)"
if [[ -n "${frontend_status}" && "${frontend_status}" != "000" ]]; then
info "前端已启动PID$(read_pid)HTTP ${frontend_status}"
info "日志命令:./local.sh logs"
return 0
fi
sleep 1
done
rm -f "${PID_FILE}"
error "前端未能在 60 秒内启动,最近日志如下:"
tail -n 80 "${LOG_FILE}" >&2 || true
exit 1
}
stop_frontend() {
local pid
pid="$(read_pid)"
if ! is_running; then
rm -f "${PID_FILE}"
info "前端未运行"
return 0
fi
info "正在停止前端PID${pid}"
kill "${pid}"
for _ in {1..15}; do
if ! kill -0 "${pid}" 2>/dev/null; then
rm -f "${PID_FILE}"
info "前端已停止"
return 0
fi
sleep 1
done
error "前端未在 15 秒内停止,请检查 PID${pid}"
exit 1
}
show_status() {
local backend_status
if is_running; then
info "前端正在运行PID$(read_pid)地址http://localhost:${FRONTEND_PORT}"
else
rm -f "${PID_FILE}"
info "前端未运行"
fi
backend_status="$(curl -s -o /dev/null --max-time 2 -w '%{http_code}' "${BACKEND_URL}/" || true)"
if [[ -n "${backend_status}" && "${backend_status}" != "000" ]]; then
info "本地后端可访问:${BACKEND_URL}HTTP ${backend_status}"
else
error "本地后端不可访问:${BACKEND_URL}"
fi
info "日志文件:${LOG_FILE}"
}
show_logs() {
local lines="${1:-200}"
if [[ ! "${lines}" =~ ^[0-9]+$ ]]; then
error "日志行数必须是非负整数:${lines}"
exit 2
fi
prepare_runtime
touch "${LOG_FILE}"
info "正在跟踪日志:${LOG_FILE}Ctrl+C 退出)"
tail -n "${lines}" -f "${LOG_FILE}"
}
command="${1:-help}"
case "${command}" in
install)
install_dependencies
;;
start)
start_frontend "${2:-}"
;;
stop)
stop_frontend
;;
restart)
stop_frontend
start_frontend "${2:-}"
;;
status)
show_status
;;
logs)
show_logs "${2:-200}"
;;
help|-h|--help)
usage
;;
*)
error "未知命令:${command}"
usage >&2
exit 2
;;
esac

View File

@@ -3,7 +3,7 @@ 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 { getOutdoorFairInfo } from '@/services/jobportal/outdoorFair';
import type { OutdoorFairItem } from '@/services/jobportal/outdoorFair';
import FairInfoTab from './components/FairInfoTab';
import BoothTab from './components/BoothTab';
@@ -22,12 +22,11 @@ const OutdoorFairDetail: React.FC = () => {
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);
const res = await getOutdoorFairInfo(fairId);
if (res.code === 200 && res.data) {
setFairInfo(res.data);
} else {
message.error('未找到该招聘会信息');
message.error(res.msg || '未找到该招聘会信息');
}
} finally {
setLoading(false);
@@ -72,7 +71,13 @@ const OutdoorFairDetail: React.FC = () => {
children: (() => {
switch (tab.key) {
case 'fair-info':
return <FairInfoTab fairId={fairId} fairInfo={fairInfo} onFairUpdate={fetchFairInfo} />;
return (
<FairInfoTab
fairId={fairId}
fairInfo={fairInfo}
onFairUpdate={fetchFairInfo}
/>
);
case 'booth':
return <BoothTab fairId={fairId} />;
case 'company-review':

View File

@@ -1,55 +1,54 @@
import React, { useEffect } from 'react';
import React, { useEffect, useState } from 'react';
import {
ModalForm,
ProForm,
ProFormText,
ProFormDigit,
ProFormDateTimePicker,
ProFormSwitch,
ProFormSelect,
ProFormText,
} from '@ant-design/pro-components';
import { Form } from 'antd';
import type { OutdoorFairItem, OutdoorFairForm } from '@/services/jobportal/outdoorFair';
import { Button, Divider, Form, Image, Input, message, Modal, Space, Upload } from 'antd';
import { PlusOutlined, UploadOutlined } from '@ant-design/icons';
import type {
OutdoorFairItem,
OutdoorFairForm,
OutdoorFairDictForm,
} from '@/services/jobportal/outdoorFair';
import { uploadOutdoorFairPhoto } from '@/services/jobportal/outdoorFair';
interface EditModalProps {
open: boolean;
values?: OutdoorFairItem;
fairTypeOptions: any[];
regionOptions: any[];
onCreateDictOption: (values: OutdoorFairDictForm) => Promise<boolean>;
onCancel: () => void;
onSubmit: (values: OutdoorFairForm) => Promise<void>;
}
/** 招聘会类型选项 */
const FAIR_TYPE_OPTIONS = [
{ label: '综合类', value: '综合类' },
{ label: '校园招聘', value: '校园招聘' },
{ label: '行业专场', value: '行业专场' },
{ label: '高新技术专场', value: '高新技术专场' },
{ label: '智能制造专场', value: '智能制造专场' },
{ label: '文旅专场', value: '文旅专场' },
{ label: '其他', value: '其他' },
];
type DictAddTarget = {
dictType: OutdoorFairDictForm['dictType'];
fieldName: keyof Pick<OutdoorFairForm, 'fairType' | 'region'>;
title: string;
placeholder: string;
};
/** 举办区域选项 */
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<EditModalProps> = ({ open, values, onCancel, onSubmit }) => {
const EditModal: React.FC<EditModalProps> = ({
open,
values,
fairTypeOptions,
regionOptions,
onCreateDictOption,
onCancel,
onSubmit,
}) => {
const [form] = Form.useForm();
const [dictModalOpen, setDictModalOpen] = useState(false);
const [dictInputValue, setDictInputValue] = useState('');
const [dictSubmitLoading, setDictSubmitLoading] = useState(false);
const [dictAddTarget, setDictAddTarget] = useState<DictAddTarget>();
const photoUrl = Form.useWatch('photoUrl', form);
useEffect(() => {
if (open) {
@@ -60,6 +59,85 @@ const EditModal: React.FC<EditModalProps> = ({ open, values, onCancel, onSubmit
}
}, [open, values, form]);
const openDictAddModal = (target: DictAddTarget) => {
setDictAddTarget(target);
setDictInputValue('');
setDictModalOpen(true);
};
const renderDropdownWithCreate = (
menu: React.ReactNode,
target: DictAddTarget,
): React.ReactNode => (
<>
{menu}
<Divider style={{ margin: '8px 0' }} />
<Space style={{ padding: '0 8px 8px' }}>
<Button
type="link"
icon={<PlusOutlined />}
onMouseDown={(event) => event.preventDefault()}
onClick={() => openDictAddModal(target)}
>
{target.title}
</Button>
</Space>
</>
);
const handleDictModalOk = async () => {
const dictLabel = dictInputValue.trim();
if (!dictAddTarget || !dictLabel) {
return;
}
setDictSubmitLoading(true);
try {
const success = await onCreateDictOption({
dictType: dictAddTarget.dictType,
dictLabel,
});
if (success) {
form.setFieldValue(dictAddTarget.fieldName, dictLabel);
setDictModalOpen(false);
}
} finally {
setDictSubmitLoading(false);
}
};
const beforePhotoUpload = (file: File) => {
const isImage = file.type.startsWith('image/');
if (!isImage) {
message.error('只能上传图片文件');
return Upload.LIST_IGNORE;
}
const isLt10M = file.size / 1024 / 1024 < 10;
if (!isLt10M) {
message.error('图片大小不能超过 10MB');
return Upload.LIST_IGNORE;
}
return true;
};
const handlePhotoUpload = async (options: any) => {
const { file, onSuccess, onError } = options;
try {
const res = await uploadOutdoorFairPhoto(file as File);
if (res.code === 200 && res.url) {
form.setFieldValue('photoUrl', res.url);
message.success('照片上传成功');
onSuccess?.(res);
} else {
const error = new Error(res.msg || '照片上传失败');
message.error(error.message);
onError?.(error);
}
} catch (error: any) {
message.error(error?.message || '照片上传失败');
onError?.(error);
}
};
return (
<ModalForm
title={values ? '编辑户外招聘会' : '新增户外招聘会'}
@@ -72,6 +150,11 @@ const EditModal: React.FC<EditModalProps> = ({ open, values, onCancel, onSubmit
}}
>
<ProFormText name="id" hidden />
<ProFormText
name="photoUrl"
hidden
rules={[{ required: true, message: '请上传招聘会照片' }]}
/>
<ProFormText
name="title"
label="招聘会标题"
@@ -90,7 +173,17 @@ const EditModal: React.FC<EditModalProps> = ({ open, values, onCancel, onSubmit
name="fairType"
label="招聘会类型"
width="md"
options={FAIR_TYPE_OPTIONS}
options={fairTypeOptions}
fieldProps={{
showSearch: true,
dropdownRender: (menu) =>
renderDropdownWithCreate(menu, {
dictType: 'outdoor_fair_type',
fieldName: 'fairType',
title: '新增招聘会类型',
placeholder: '请输入招聘会类型',
}),
}}
rules={[{ required: true, message: '请选择招聘会类型' }]}
placeholder="请选择招聘会类型"
/>
@@ -100,7 +193,17 @@ const EditModal: React.FC<EditModalProps> = ({ open, values, onCancel, onSubmit
name="region"
label="举办区域"
width="md"
options={REGION_OPTIONS}
options={regionOptions}
fieldProps={{
showSearch: true,
dropdownRender: (menu) =>
renderDropdownWithCreate(menu, {
dictType: 'outdoor_fair_region',
fieldName: 'region',
title: '新增举办区域',
placeholder: '请输入举办区域',
}),
}}
rules={[{ required: true, message: '请选择举办区域' }]}
placeholder="请选择举办区域"
/>
@@ -157,12 +260,47 @@ const EditModal: React.FC<EditModalProps> = ({ open, values, onCancel, onSubmit
colProps={{ md: 24 }}
initialValue={false}
/>
<ProFormText
name="photoUrl"
label="招聘会照片链接"
placeholder="请输入照片URL地址"
rules={[{ required: true, message: '请输入招聘会照片链接' }]}
/>
<ProForm.Item label="招聘会照片" required>
<Space direction="vertical" size={12}>
<Upload
accept="image/*"
maxCount={1}
showUploadList={false}
beforeUpload={beforePhotoUpload}
customRequest={handlePhotoUpload}
>
<Button icon={<UploadOutlined />}></Button>
</Upload>
{photoUrl ? (
<Image
src={photoUrl}
width={180}
height={120}
style={{ objectFit: 'cover', borderRadius: 6 }}
/>
) : (
<span style={{ color: '#999' }}> jpgjpegpnggifbmp 10MB</span>
)}
</Space>
</ProForm.Item>
<Modal
title={dictAddTarget?.title}
open={dictModalOpen}
confirmLoading={dictSubmitLoading}
onOk={handleDictModalOk}
onCancel={() => setDictModalOpen(false)}
okButtonProps={{ disabled: !dictInputValue.trim() }}
destroyOnClose
>
<Input
value={dictInputValue}
maxLength={100}
showCount
placeholder={dictAddTarget?.placeholder}
onChange={(event) => setDictInputValue(event.target.value)}
onPressEnter={handleDictModalOk}
/>
</Modal>
</ModalForm>
);
};

View File

@@ -1,4 +1,4 @@
import React, { useRef, useState } from 'react';
import React, { useCallback, useEffect, 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';
@@ -8,19 +8,57 @@ import {
addOutdoorFair,
updateOutdoorFair,
deleteOutdoorFair,
addOutdoorFairDictOption,
} from '@/services/jobportal/outdoorFair';
import type {
OutdoorFairItem,
OutdoorFairListParams,
OutdoorFairForm,
OutdoorFairDictForm,
} from '@/services/jobportal/outdoorFair';
import { getDictSelectOption, getDictValueEnum } from '@/services/system/dict';
import EditModal from './components/EditModal';
const OUTDOOR_FAIR_TYPE_DICT = 'outdoor_fair_type';
const OUTDOOR_FAIR_REGION_DICT = 'outdoor_fair_region';
const OutdoorFairList: React.FC = () => {
const access = useAccess();
const actionRef = useRef<ActionType>();
const [modalVisible, setModalVisible] = useState(false);
const [currentRow, setCurrentRow] = useState<OutdoorFairItem>();
const [fairTypeValueEnum, setFairTypeValueEnum] = useState<Record<string, any>>({});
const [fairTypeOptions, setFairTypeOptions] = useState<any[]>([]);
const [regionValueEnum, setRegionValueEnum] = useState<Record<string, any>>({});
const [regionOptions, setRegionOptions] = useState<any[]>([]);
const refreshDictOptions = useCallback(async () => {
const [typeValueEnum, typeOptions, regionEnum, regionSelectOptions] = await Promise.all([
getDictValueEnum(OUTDOOR_FAIR_TYPE_DICT),
getDictSelectOption(OUTDOOR_FAIR_TYPE_DICT),
getDictValueEnum(OUTDOOR_FAIR_REGION_DICT),
getDictSelectOption(OUTDOOR_FAIR_REGION_DICT),
]);
setFairTypeValueEnum(typeValueEnum);
setFairTypeOptions(typeOptions);
setRegionValueEnum(regionEnum);
setRegionOptions(regionSelectOptions);
}, []);
useEffect(() => {
refreshDictOptions();
}, [refreshDictOptions]);
const handleCreateDictOption = async (values: OutdoorFairDictForm) => {
const res = await addOutdoorFairDictOption(values);
if (res.code === 200) {
await refreshDictOptions();
message.success('字典项新增成功');
return true;
}
message.error(res.msg || '字典项新增失败');
return false;
};
const handleDelete = async (id: number) => {
Modal.confirm({
@@ -53,13 +91,15 @@ const OutdoorFairList: React.FC = () => {
title: '招聘会类型',
dataIndex: 'fairType',
ellipsis: true,
hideInSearch: true,
valueType: 'select',
valueEnum: fairTypeValueEnum,
},
{
title: '举办区域',
dataIndex: 'region',
ellipsis: true,
hideInSearch: true,
valueType: 'select',
valueEnum: regionValueEnum,
},
{
title: '举办地址',
@@ -175,6 +215,7 @@ const OutdoorFairList: React.FC = () => {
title: params.title,
hostUnit: params.hostUnit,
fairType: params.fairType,
region: params.region,
} as OutdoorFairListParams);
return { data: res.rows, total: res.total, success: true };
}}
@@ -196,14 +237,15 @@ const OutdoorFairList: React.FC = () => {
<EditModal
open={modalVisible}
values={currentRow}
fairTypeOptions={fairTypeOptions}
regionOptions={regionOptions}
onCreateDictOption={handleCreateDictOption}
onCancel={() => {
setModalVisible(false);
setCurrentRow(undefined);
}}
onSubmit={async (values: OutdoorFairForm) => {
const res = values.id
? await updateOutdoorFair(values)
: await addOutdoorFair(values);
const res = values.id ? await updateOutdoorFair(values) : await addOutdoorFair(values);
if (res.code === 200) {
message.success(values.id ? '修改成功' : '新增成功');
setModalVisible(false);

View File

@@ -36,6 +36,7 @@ export interface OutdoorFairListParams {
title?: string;
hostUnit?: string;
fairType?: string;
region?: string;
current?: number;
pageSize?: number;
}
@@ -70,177 +71,54 @@ export interface OutdoorFairCommonResult {
code: number;
msg?: string;
data?: any;
url?: string;
fileName?: string;
newFileName?: string;
originalFilename?: string;
}
// ==================== 静态假数据 ====================
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 接口(使用假数据) ====================
/** 户外招聘会字典新增表单 */
export interface OutdoorFairDictForm {
dictType: 'outdoor_fair_type' | 'outdoor_fair_region';
dictLabel: string;
}
const CMS_OUTDOOR_FAIR_BASE = '/api/cms/outdoor-fair';
const formatDateTime = (value: any) => {
if (value && typeof value.format === 'function') {
return value.format('YYYY-MM-DD HH:mm:ss');
}
return value;
};
const toApiPayload = (data: OutdoorFairForm) => ({
...data,
holdTime: formatDateTime(data.holdTime),
endTime: formatDateTime(data.endTime),
applyStartTime: formatDateTime(data.applyStartTime),
applyEndTime: formatDateTime(data.applyEndTime),
});
/**
* 获取户外招聘会管理列表
* GET /api/cms/outdoor-fair/list
*/
export async function getOutdoorFairList(params?: OutdoorFairListParams) {
let filtered = [...MOCK_DATA];
return request<OutdoorFairListResult>(`${CMS_OUTDOOR_FAIR_BASE}/list`, {
method: 'GET',
params,
});
}
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,
};
/**
* 获取户外招聘会详情
* GET /api/cms/outdoor-fair/{id}
*/
export async function getOutdoorFairInfo(id: number) {
return request<OutdoorFairCommonResult>(`${CMS_OUTDOOR_FAIR_BASE}/${id}`, {
method: 'GET',
});
}
/**
@@ -248,13 +126,10 @@ export async function getOutdoorFairList(params?: OutdoorFairListParams) {
* 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 };
return request<OutdoorFairCommonResult>(CMS_OUTDOOR_FAIR_BASE, {
method: 'POST',
data: toApiPayload(data),
});
}
/**
@@ -262,16 +137,10 @@ export async function addOutdoorFair(data: OutdoorFairForm) {
* 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: '未找到该记录' };
return request<OutdoorFairCommonResult>(CMS_OUTDOOR_FAIR_BASE, {
method: 'PUT',
data: toApiPayload(data),
});
}
/**
@@ -279,10 +148,31 @@ export async function updateOutdoorFair(data: OutdoorFairForm) {
* 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: '未找到该记录' };
return request<OutdoorFairCommonResult>(`${CMS_OUTDOOR_FAIR_BASE}/${id}`, {
method: 'DELETE',
});
}
/**
* 新增户外招聘会页面使用的字典项
* POST /api/cms/outdoor-fair/dict-data
*/
export async function addOutdoorFairDictOption(data: OutdoorFairDictForm) {
return request<OutdoorFairCommonResult>(`${CMS_OUTDOOR_FAIR_BASE}/dict-data`, {
method: 'POST',
data,
});
}
/**
* 上传户外招聘会照片
* POST /api/common/upload
*/
export async function uploadOutdoorFairPhoto(file: File) {
const formData = new FormData();
formData.append('file', file);
return request<OutdoorFairCommonResult>('/api/common/upload', {
method: 'POST',
data: formData,
});
}