diff --git a/.DS_Store b/.DS_Store
deleted file mode 100644
index 352069c..0000000
Binary files a/.DS_Store and /dev/null differ
diff --git a/.gitignore b/.gitignore
index 61a7226..edc834e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,4 +13,6 @@ src/.umi-production/
# Archives
*.rar
-*.zip
\ No newline at end of file
+*.zip
+.DS_Store
+.local/
diff --git a/config/proxy.ts b/config/proxy.ts
index 6d6cc1e..e7ab963 100644
--- a/config/proxy.ts
+++ b/config/proxy.ts
@@ -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',
diff --git a/config/routes.ts b/config/routes.ts
index 48a75d5..7ee0c81 100644
--- a/config/routes.ts
+++ b/config/routes.ts
@@ -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: '户外招聘会管理',
diff --git a/local.sh b/local.sh
new file mode 100755
index 0000000..77f5808
--- /dev/null
+++ b/local.sh
@@ -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
diff --git a/src/pages/Jobfair/Outdoorfair/Detail/index.tsx b/src/pages/Jobfair/Outdoorfair/Detail/index.tsx
index c056554..59c9570 100644
--- a/src/pages/Jobfair/Outdoorfair/Detail/index.tsx
+++ b/src/pages/Jobfair/Outdoorfair/Detail/index.tsx
@@ -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 ;
+ return (
+
+ );
case 'booth':
return ;
case 'company-review':
diff --git a/src/pages/Jobfair/Outdoorfair/components/EditModal.tsx b/src/pages/Jobfair/Outdoorfair/components/EditModal.tsx
index f9a975f..b77aaf0 100644
--- a/src/pages/Jobfair/Outdoorfair/components/EditModal.tsx
+++ b/src/pages/Jobfair/Outdoorfair/components/EditModal.tsx
@@ -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;
onCancel: () => void;
onSubmit: (values: OutdoorFairForm) => Promise;
}
-/** 招聘会类型选项 */
-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;
+ 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 = ({ open, values, onCancel, onSubmit }) => {
+const EditModal: React.FC = ({
+ 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();
+ const photoUrl = Form.useWatch('photoUrl', form);
useEffect(() => {
if (open) {
@@ -60,6 +59,85 @@ const EditModal: React.FC = ({ 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}
+
+
+ }
+ onMouseDown={(event) => event.preventDefault()}
+ onClick={() => openDictAddModal(target)}
+ >
+ {target.title}
+
+
+ >
+ );
+
+ 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 (
= ({ open, values, onCancel, onSubmit
}}
>
+
= ({ 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 = ({ 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 = ({ open, values, onCancel, onSubmit
colProps={{ md: 24 }}
initialValue={false}
/>
-
+
+
+
+ }>上传照片
+
+ {photoUrl ? (
+
+ ) : (
+ 支持 jpg、jpeg、png、gif、bmp,大小不超过 10MB
+ )}
+
+
+ setDictModalOpen(false)}
+ okButtonProps={{ disabled: !dictInputValue.trim() }}
+ destroyOnClose
+ >
+ setDictInputValue(event.target.value)}
+ onPressEnter={handleDictModalOk}
+ />
+
);
};
diff --git a/src/pages/Jobfair/Outdoorfair/index.tsx b/src/pages/Jobfair/Outdoorfair/index.tsx
index 1719362..984929f 100644
--- a/src/pages/Jobfair/Outdoorfair/index.tsx
+++ b/src/pages/Jobfair/Outdoorfair/index.tsx
@@ -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();
const [modalVisible, setModalVisible] = useState(false);
const [currentRow, setCurrentRow] = useState();
+ const [fairTypeValueEnum, setFairTypeValueEnum] = useState>({});
+ const [fairTypeOptions, setFairTypeOptions] = useState([]);
+ const [regionValueEnum, setRegionValueEnum] = useState>({});
+ const [regionOptions, setRegionOptions] = useState([]);
+
+ 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 = () => {
{
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);
diff --git a/src/services/jobportal/outdoorFair.ts b/src/services/jobportal/outdoorFair.ts
index efc6f5d..336b6d7 100644
--- a/src/services/jobportal/outdoorFair.ts
+++ b/src/services/jobportal/outdoorFair.ts
@@ -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(`${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(`${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(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(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(`${CMS_OUTDOOR_FAIR_BASE}/${id}`, {
+ method: 'DELETE',
+ });
+}
+
+/**
+ * 新增户外招聘会页面使用的字典项
+ * POST /api/cms/outdoor-fair/dict-data
+ */
+export async function addOutdoorFairDictOption(data: OutdoorFairDictForm) {
+ return request(`${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('/api/common/upload', {
+ method: 'POST',
+ data: formData,
+ });
}