返回首页
diff --git a/src/pages/JobPortal/PersonalCenter/Showcase/index.less b/src/pages/JobPortal/PersonalCenter/Showcase/index.less
index cb4f35f..f8584e8 100644
--- a/src/pages/JobPortal/PersonalCenter/Showcase/index.less
+++ b/src/pages/JobPortal/PersonalCenter/Showcase/index.less
@@ -109,25 +109,30 @@
height: 200px;
overflow: hidden;
background: #000;
+ position: relative;
+ cursor: pointer;
.gallery-video {
width: 100%;
height: 100%;
object-fit: contain;
}
- }
- .ant-card-actions {
- border-radius: 0 0 @jp-radius @jp-radius;
+ .video-play-mask {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: rgba(0, 0, 0, 0.35);
+ opacity: 0;
+ transition: opacity 0.3s;
+ color: #fff;
+ font-size: 40px;
+ }
- > li > span {
- cursor: pointer;
-
- &:hover {
- .anticon {
- color: #ff7875;
- }
- }
+ &:hover .video-play-mask {
+ opacity: 1;
}
}
}
diff --git a/src/pages/JobPortal/PersonalCenter/Showcase/index.tsx b/src/pages/JobPortal/PersonalCenter/Showcase/index.tsx
index 11f4923..f23a0ad 100644
--- a/src/pages/JobPortal/PersonalCenter/Showcase/index.tsx
+++ b/src/pages/JobPortal/PersonalCenter/Showcase/index.tsx
@@ -1,4 +1,4 @@
-import React, { useState } from 'react';
+import React, { useState, useEffect, useCallback } from 'react';
import {
Upload,
Button,
@@ -7,30 +7,37 @@ import {
Spin,
Empty,
message,
+ Typography,
Modal,
- Typography
} from 'antd';
import {
UploadOutlined,
ArrowLeftOutlined,
CameraOutlined,
VideoCameraOutlined,
- DeleteOutlined
} from '@ant-design/icons';
-import type { UploadFile } from 'antd/es/upload/interface';
import { history } from '@umijs/max';
import JobPortalHeader from '@/components/JobPortalHeader';
+import { uploadShowcaseFile, getShowcaseFileList } from '@/services/jobportal/user';
+import { getFileDisplayUrl } from '@/utils/fileUrl';
import './index.less';
const { Title, Text } = Typography;
-/** 从缓存获取用户ID */
+interface ShowcaseItem {
+ uid: string;
+ type: string;
+ url: string;
+ createTime: string;
+}
+
+/** 从 getInfoCache 缓存获取 userId */
const getUserIdFromCache = (): number | null => {
try {
- const cached = localStorage.getItem('userInfo');
+ const cached = localStorage.getItem('getInfoCache');
if (cached) {
- const userInfo = JSON.parse(cached);
- return userInfo?.userId || null;
+ const data = JSON.parse(cached);
+ return data?.user?.userId || null;
}
} catch (error) {
console.error('读取缓存用户信息失败:', error);
@@ -38,27 +45,78 @@ const getUserIdFromCache = (): number | null => {
return null;
};
+/** 根据文件名后缀推断类型 */
+const guessType = (filename: string): string => {
+ const ext = filename.split('.').pop()?.toLowerCase() || '';
+ const videoExts = ['mp4', 'mov', 'avi', 'wmv', 'flv', 'mkv', 'webm', 'm4v'];
+ if (videoExts.includes(ext)) return 'video/mp4';
+ return 'image/jpeg';
+};
+
+/** 格式化时间 */
+const formatTime = (time?: string): string => {
+ if (!time) return '';
+ try {
+ const date = new Date(time);
+ return date.toLocaleString('zh-CN', {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit',
+ });
+ } catch {
+ return time;
+ }
+};
+
+/** 服务器文件 → 展示条目 */
+const serverFileToItem = (row: {
+ id: number;
+ fileUrl: string;
+ createTime?: string;
+}): ShowcaseItem => ({
+ uid: `server-${row.id}`,
+ type: guessType(row.fileUrl),
+ url: getFileDisplayUrl(row.fileUrl),
+ createTime: formatTime(row.createTime),
+});
+
+const isVideo = (type: string): boolean => type.startsWith('video/');
+
const ShowcasePage: React.FC = () => {
- const [uploadedFiles, setUploadedFiles] = useState
([]);
+ const [items, setItems] = useState([]);
const [uploading, setUploading] = useState(false);
- const [previewVisible, setPreviewVisible] = useState(false);
- const [previewFile, setPreviewFile] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [previewItem, setPreviewItem] = useState(null);
const userId = getUserIdFromCache();
- const isVideo = (file: UploadFile): boolean => {
- const type = file.type || '';
- return type.startsWith('video/');
- };
+ const fetchFileList = useCallback(async () => {
+ if (!userId) {
+ setLoading(false);
+ return;
+ }
+ try {
+ setLoading(true);
+ const response = await getShowcaseFileList(userId);
+ if (response?.code === 200 || response?.code === 0) {
+ const rows = response.rows || response.data || [];
+ setItems(rows.map(serverFileToItem));
+ }
+ } catch (error) {
+ console.error('获取文件列表失败:', error);
+ } finally {
+ setLoading(false);
+ }
+ }, [userId]);
- const isImage = (file: UploadFile): boolean => {
- const type = file.type || '';
- return type.startsWith('image/');
- };
+ useEffect(() => {
+ fetchFileList();
+ }, [fetchFileList]);
- /** 自定义上传逻辑 */
const customUpload = async (options: any) => {
- const { file, onSuccess, onError, onProgress } = options;
+ const { file, onSuccess, onError } = options;
if (!userId) {
message.error('请先登录');
@@ -66,33 +124,17 @@ const ShowcasePage: React.FC = () => {
return;
}
- const formData = new FormData();
- formData.append('file', file as File);
-
try {
setUploading(true);
- const response = await fetch(
- `/app/file/upload?bussinessid=${userId}`,
- {
- method: 'POST',
- body: formData,
- }
- );
+ const response = await uploadShowcaseFile(userId, file as File);
- if (response.ok) {
- const result = await response.json();
- if (result.code === 200 || result.code === 0) {
- message.success(`${file.name} 上传成功`);
- onSuccess(result, file);
- // 将上传成功的文件添加到展示列表
- setUploadedFiles((prev) => [...prev, file]);
- } else {
- message.error(result.msg || `${file.name} 上传失败`);
- onError(new Error(result.msg || '上传失败'));
- }
+ if (response?.code === 200 || response?.code === 0) {
+ message.success(`${file.name} 上传成功`);
+ onSuccess(response, file);
+ await fetchFileList();
} else {
- message.error(`${file.name} 上传失败`);
- onError(new Error('上传失败'));
+ message.error(response?.msg || `${file.name} 上传失败`);
+ onError(new Error(response?.msg || '上传失败'));
}
} catch (error) {
console.error('上传失败:', error);
@@ -103,26 +145,12 @@ const ShowcasePage: React.FC = () => {
}
};
- /** 上传前校验:仅允许图片和视频 */
const beforeUpload = (file: File): boolean => {
- const isImageOrVideo = file.type.startsWith('image/') || file.type.startsWith('video/');
- if (!isImageOrVideo) {
+ const isValid = file.type.startsWith('image/') || file.type.startsWith('video/');
+ if (!isValid) {
message.error('只支持上传图片和视频文件!');
- return false;
}
- return true;
- };
-
- /** 移除已上传文件 */
- const handleRemove = (file: UploadFile) => {
- setUploadedFiles((prev) => prev.filter((f) => f.uid !== file.uid));
- message.info('已移除');
- };
-
- /** 预览文件 */
- const handlePreview = (file: UploadFile) => {
- setPreviewFile(file);
- setPreviewVisible(true);
+ return isValid;
};
const handleBack = () => {
@@ -133,27 +161,21 @@ const ShowcasePage: React.FC = () => {
-
+
- {/* 返回按钮 */}
- }
- onClick={handleBack}
- >
+ } onClick={handleBack}>
返回个人中心
- {/* 页面标题 */}
我的风采
上传你的个人照片和视频,展示你的风采
{/* 上传区域 */}
-
+
{
{/* 已上传内容展示 */}
- {uploadedFiles.length === 0 ? (
+ {items.length === 0 ? (
{
已上传内容
- 共 {uploadedFiles.length} 个文件
+ 共 {items.length} 条记录
- {uploadedFiles.map((file, index) => (
+ {items.map((item) => (
-
+ isVideo(item.type) ? (
+ setPreviewItem(item)}>
+
+
+
+
) : (
,
- }}
+ preview={{ mask: }}
/>
)
}
- actions={[
- handleRemove(file)}
- style={{ color: '#ff4d4f' }}
- />,
- ]}
>
-
- {file.size
- ? file.size > 1024 * 1024
- ? `${(file.size / 1024 / 1024).toFixed(2)} MB`
- : `${(file.size / 1024).toFixed(2)} KB`
- : ''}
-
- }
- />
+ 上传时间:{item.createTime}} />
))}
)}
-
- {/* 视频预览弹窗 */}
- setPreviewVisible(false)}
- width={800}
- title={previewFile?.name || '预览'}
- >
- {previewFile && isVideo(previewFile) && (
-
- )}
- {previewFile && isImage(previewFile) && (
-
- )}
-
+
+ {/* 视频放大预览 */}
+ setPreviewItem(null)}
+ width={900}
+ >
+ {previewItem && (
+
+ )}
+
);
};
diff --git a/src/pages/Management/EnterpriseShowcase/index.less b/src/pages/Management/EnterpriseShowcase/index.less
new file mode 100644
index 0000000..f6f0df4
--- /dev/null
+++ b/src/pages/Management/EnterpriseShowcase/index.less
@@ -0,0 +1,153 @@
+.enterprise-showcase-page {
+ min-height: 100vh;
+ padding-top: 72px;
+ padding-bottom: 40px;
+ background: #f0f2f5;
+
+ .es-page-content {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 0 24px;
+ box-sizing: border-box;
+ }
+
+ .es-back-button {
+ margin-bottom: 8px;
+ }
+
+ .es-page-header {
+ margin-bottom: 24px;
+
+ h2 {
+ margin-bottom: 8px;
+ }
+ }
+
+ .es-upload-card {
+ margin-bottom: 24px;
+ border-radius: 8px;
+ }
+
+ .es-upload-section {
+ .es-upload-area {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 40px 20px;
+ border: 2px dashed #d9d9d9;
+ border-radius: 12px;
+ background: #fafafa;
+ cursor: pointer;
+ transition: all 0.3s;
+
+ &:hover {
+ border-color: #1890ff;
+ background: #e6f4ff;
+ }
+
+ .es-upload-icons {
+ display: flex;
+ gap: 24px;
+ margin-bottom: 16px;
+
+ .es-upload-icon {
+ font-size: 40px;
+ color: #1890ff;
+ }
+ }
+
+ .es-upload-text {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 4px;
+ margin-bottom: 20px;
+ }
+ }
+ }
+
+ .es-showcase-gallery {
+ .es-gallery-header {
+ display: flex;
+ align-items: baseline;
+ gap: 12px;
+ margin-bottom: 16px;
+
+ h4 {
+ margin-bottom: 0;
+ }
+ }
+
+ .es-gallery-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+ gap: 16px;
+ }
+
+ .es-gallery-card {
+ border-radius: 8px;
+ overflow: hidden;
+
+ .es-image-cover {
+ height: 200px;
+ overflow: hidden;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: #f5f5f5;
+
+ .es-gallery-image {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ }
+ }
+
+ .es-video-cover {
+ height: 200px;
+ overflow: hidden;
+ background: #000;
+ position: relative;
+ cursor: pointer;
+
+ .es-gallery-video {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ }
+
+ .es-video-play-mask {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: rgba(0, 0, 0, 0.35);
+ opacity: 0;
+ transition: opacity 0.3s;
+ color: #fff;
+ font-size: 40px;
+ }
+
+ &:hover .es-video-play-mask {
+ opacity: 1;
+ }
+ }
+ }
+ }
+}
+
+@media (max-width: 768px) {
+ .enterprise-showcase-page {
+ padding-top: 64px;
+
+ .es-page-content {
+ padding: 0 12px;
+ }
+
+ .es-gallery-grid {
+ grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)) !important;
+ }
+ }
+}
diff --git a/src/pages/Management/EnterpriseShowcase/index.tsx b/src/pages/Management/EnterpriseShowcase/index.tsx
new file mode 100644
index 0000000..aee8b9f
--- /dev/null
+++ b/src/pages/Management/EnterpriseShowcase/index.tsx
@@ -0,0 +1,266 @@
+import React, { useState, useEffect, useCallback } from 'react';
+import {
+ Upload,
+ Button,
+ Card,
+ Image,
+ Spin,
+ Empty,
+ message,
+ Typography,
+ Modal,
+} from 'antd';
+import {
+ UploadOutlined,
+ CameraOutlined,
+ VideoCameraOutlined,
+ ArrowLeftOutlined,
+} from '@ant-design/icons';
+import { history } from '@umijs/max';
+import { uploadShowcaseFile, getShowcaseFileList } from '@/services/jobportal/user';
+import { getFileDisplayUrl } from '@/utils/fileUrl';
+import EnterpriseHeader from '@/components/EnterpriseHeader';
+import './index.less';
+
+const { Title, Text } = Typography;
+
+interface ShowcaseItem {
+ uid: string;
+ type: string;
+ url: string;
+ createTime: string;
+}
+
+/** 从 getInfoCache 缓存获取 userId */
+const getUserIdFromCache = (): number | null => {
+ try {
+ const cached = localStorage.getItem('getInfoCache');
+ if (cached) {
+ const data = JSON.parse(cached);
+ return data?.user?.userId || null;
+ }
+ } catch (error) {
+ console.error('读取缓存用户信息失败:', error);
+ }
+ return null;
+};
+
+/** 根据文件名后缀推断类型 */
+const guessType = (filename: string): string => {
+ const ext = filename.split('.').pop()?.toLowerCase() || '';
+ const videoExts = ['mp4', 'mov', 'avi', 'wmv', 'flv', 'mkv', 'webm', 'm4v'];
+ if (videoExts.includes(ext)) return 'video/mp4';
+ return 'image/jpeg';
+};
+
+/** 格式化时间 */
+const formatTime = (time?: string): string => {
+ if (!time) return '';
+ try {
+ const date = new Date(time);
+ return date.toLocaleString('zh-CN', {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit',
+ });
+ } catch {
+ return time;
+ }
+};
+
+/** 服务器文件 → 展示条目 */
+const serverFileToItem = (row: {
+ id: number;
+ fileUrl: string;
+ createTime?: string;
+}): ShowcaseItem => ({
+ uid: `server-${row.id}`,
+ type: guessType(row.fileUrl),
+ url: getFileDisplayUrl(row.fileUrl),
+ createTime: formatTime(row.createTime),
+});
+
+const isVideo = (type: string): boolean => type.startsWith('video/');
+
+const EnterpriseShowcase: React.FC = () => {
+ const [items, setItems] = useState([]);
+ const [uploading, setUploading] = useState(false);
+ const [loading, setLoading] = useState(true);
+ const [previewItem, setPreviewItem] = useState(null);
+
+ const userId = getUserIdFromCache();
+
+ const fetchFileList = useCallback(async () => {
+ if (!userId) {
+ setLoading(false);
+ return;
+ }
+ try {
+ setLoading(true);
+ const response = await getShowcaseFileList(userId);
+ if (response?.code === 200 || response?.code === 0) {
+ const rows = response.rows || response.data || [];
+ setItems(rows.map(serverFileToItem));
+ }
+ } catch (error) {
+ console.error('获取文件列表失败:', error);
+ } finally {
+ setLoading(false);
+ }
+ }, [userId]);
+
+ useEffect(() => {
+ fetchFileList();
+ }, [fetchFileList]);
+
+ const customUpload = async (options: any) => {
+ const { file, onSuccess, onError } = options;
+
+ if (!userId) {
+ message.error('请先登录');
+ onError(new Error('未登录'));
+ return;
+ }
+
+ try {
+ setUploading(true);
+ const response = await uploadShowcaseFile(userId, file as File);
+
+ if (response?.code === 200 || response?.code === 0) {
+ message.success(`${file.name} 上传成功`);
+ onSuccess(response, file);
+ await fetchFileList();
+ } else {
+ message.error(response?.msg || `${file.name} 上传失败`);
+ onError(new Error(response?.msg || '上传失败'));
+ }
+ } catch (error) {
+ console.error('上传失败:', error);
+ message.error(`${file.name} 上传失败`);
+ onError(error as Error);
+ } finally {
+ setUploading(false);
+ }
+ };
+
+ const beforeUpload = (file: File): boolean => {
+ const isValid = file.type.startsWith('image/') || file.type.startsWith('video/');
+ if (!isValid) {
+ message.error('只支持上传图片和视频文件!');
+ }
+ return isValid;
+ };
+
+ return (
+
+
+
+
+
+ } onClick={() => history.go(-1)}>
+ 返回上一页
+
+
+
+
+
企业风采
+ 上传企业照片和视频,展示企业风采
+
+
+ {/* 上传区域 */}
+
+
+
+
+
+
+
+
+
+ 点击或拖拽上传
+ 支持图片和视频文件,大小不限
+
+
}>
+ 选择文件
+
+
+
+
+
+
+ {/* 已上传内容展示 */}
+ {items.length === 0 ? (
+
+ ) : (
+
+
+
已上传内容
+ 共 {items.length} 条记录
+
+
+ {items.map((item) => (
+
setPreviewItem(item)}>
+
+
+
+
+
+ ) : (
+
+ }}
+ />
+
+ )
+ }
+ >
+
上传时间:{item.createTime}} />
+
+ ))}
+
+
+ )}
+
+
+
+ {/* 视频放大预览 */}
+ setPreviewItem(null)}
+ width={900}
+ >
+ {previewItem && (
+
+ )}
+
+
+ );
+};
+
+export default EnterpriseShowcase;
diff --git a/src/services/jobportal/user.ts b/src/services/jobportal/user.ts
index df64aef..49812b3 100644
--- a/src/services/jobportal/user.ts
+++ b/src/services/jobportal/user.ts
@@ -136,3 +136,21 @@ export async function deleteMessage(ids: number, options?: Record)
});
}
+/** 上传我的风采文件 POST /app/file/upload */
+export async function uploadShowcaseFile(userId: number, file: File) {
+ const formData = new FormData();
+ formData.append('file', file);
+ return request(`/app/file/upload?bussinessid=${userId}`, {
+ method: 'POST',
+ data: formData,
+ });
+}
+
+/** 获取我的风采文件列表 GET /app/file/list */
+export async function getShowcaseFileList(userId: number) {
+ return request('/app/file/list', {
+ method: 'GET',
+ params: { bussinessid: userId },
+ });
+}
+
diff --git a/src/utils/fileUrl.ts b/src/utils/fileUrl.ts
new file mode 100644
index 0000000..673c11e
--- /dev/null
+++ b/src/utils/fileUrl.ts
@@ -0,0 +1,15 @@
+/**
+ * 文件展示 URL 拼接工具
+ * 开发环境走相对路径,生产环境拼接固定域名
+ */
+
+const PROD_DOMAIN = 'http://39.98.44.136:6024';
+
+/** 根据服务端返回的 fileUrl 字段拼出可用于
/