diff --git a/config/routes.ts b/config/routes.ts
index 0da0fa4..7252283 100644
--- a/config/routes.ts
+++ b/config/routes.ts
@@ -199,6 +199,11 @@ export default [
path: '/management/interview-list',
component: './Management/InterviewList',
},
+ {
+ name: '风采审核',
+ path: '/management/showcase-review',
+ component: './Management/Showcase',
+ },
],
},
{
diff --git a/src/pages/JobPortal/PersonalCenter/Showcase/index.tsx b/src/pages/JobPortal/PersonalCenter/Showcase/index.tsx
index f23a0ad..8d29c2d 100644
--- a/src/pages/JobPortal/PersonalCenter/Showcase/index.tsx
+++ b/src/pages/JobPortal/PersonalCenter/Showcase/index.tsx
@@ -9,6 +9,8 @@ import {
message,
Typography,
Modal,
+ Tag,
+ Tooltip,
} from 'antd';
import {
UploadOutlined,
@@ -29,6 +31,9 @@ interface ShowcaseItem {
type: string;
url: string;
createTime: string;
+ id: number;
+ reviewStatus: string; // '0' | '1' | '2'
+ reviewRemark?: string;
}
/** 从 getInfoCache 缓存获取 userId */
@@ -75,11 +80,16 @@ const serverFileToItem = (row: {
id: number;
fileUrl: string;
createTime?: string;
+ reviewStatus?: string;
+ reviewRemark?: string;
}): ShowcaseItem => ({
uid: `server-${row.id}`,
type: guessType(row.fileUrl),
url: getFileDisplayUrl(row.fileUrl),
createTime: formatTime(row.createTime),
+ id: row.id,
+ reviewStatus: row.reviewStatus || '0',
+ reviewRemark: row.reviewRemark,
});
const isVideo = (type: string): boolean => type.startsWith('video/');
@@ -214,34 +224,60 @@ const ShowcasePage: React.FC = () => {
共 {items.length} 条记录
- {items.map((item) => (
-
setPreviewItem(item)}>
-
-
-
+ {items.map((item) => {
+ const isPending = item.reviewStatus === '0';
+ const isRejected = item.reviewStatus === '2';
+ return (
+
!isPending && setPreviewItem(item)}
+ style={isPending || isRejected ? { pointerEvents: 'none', opacity: 0.5 } : undefined}
+ >
+
+
+
+
-
- ) : (
-
- }}
- />
-
- )
- }
- >
- 上传时间:{item.createTime}} />
-
- ))}
+ ) : (
+
+ }}
+ />
+
+ )
+ }
+ >
+
+ 上传时间:{item.createTime}
+ {isPending && (
+
+ 审核中
+
+ )}
+ {isRejected && (
+
+
+ 审核未通过
+
+
+ )}
+
+ }
+ />
+
+ );
+ })}
)}
diff --git a/src/pages/Management/EnterpriseShowcase/index.tsx b/src/pages/Management/EnterpriseShowcase/index.tsx
index aee8b9f..4199ffa 100644
--- a/src/pages/Management/EnterpriseShowcase/index.tsx
+++ b/src/pages/Management/EnterpriseShowcase/index.tsx
@@ -9,6 +9,8 @@ import {
message,
Typography,
Modal,
+ Tag,
+ Tooltip,
} from 'antd';
import {
UploadOutlined,
@@ -29,6 +31,9 @@ interface ShowcaseItem {
type: string;
url: string;
createTime: string;
+ id: number;
+ reviewStatus: string; // '0' | '1' | '2'
+ reviewRemark?: string;
}
/** 从 getInfoCache 缓存获取 userId */
@@ -75,11 +80,16 @@ const serverFileToItem = (row: {
id: number;
fileUrl: string;
createTime?: string;
+ reviewStatus?: string;
+ reviewRemark?: string;
}): ShowcaseItem => ({
uid: `server-${row.id}`,
type: guessType(row.fileUrl),
url: getFileDisplayUrl(row.fileUrl),
createTime: formatTime(row.createTime),
+ id: row.id,
+ reviewStatus: row.reviewStatus || '0',
+ reviewRemark: row.reviewRemark,
});
const isVideo = (type: string): boolean => type.startsWith('video/');
@@ -209,34 +219,60 @@ const EnterpriseShowcase: React.FC = () => {
共 {items.length} 条记录
- {items.map((item) => (
-
setPreviewItem(item)}>
-
-
-
+ {items.map((item) => {
+ const isPending = item.reviewStatus === '0';
+ const isRejected = item.reviewStatus === '2';
+ return (
+
!isPending && setPreviewItem(item)}
+ style={isPending || isRejected ? { pointerEvents: 'none', opacity: 0.5 } : undefined}
+ >
+
+
+
+
-
- ) : (
-
- }}
- />
-
- )
- }
- >
- 上传时间:{item.createTime}} />
-
- ))}
+ ) : (
+
+ }}
+ />
+
+ )
+ }
+ >
+
+ 上传时间:{item.createTime}
+ {isPending && (
+
+ 审核中
+
+ )}
+ {isRejected && (
+
+
+ 审核未通过
+
+
+ )}
+
+ }
+ />
+
+ );
+ })}
)}
diff --git a/src/pages/Management/Showcase/index.tsx b/src/pages/Management/Showcase/index.tsx
new file mode 100644
index 0000000..7fad1eb
--- /dev/null
+++ b/src/pages/Management/Showcase/index.tsx
@@ -0,0 +1,325 @@
+import React, { useRef, useState } from 'react';
+import { useAccess } from '@umijs/max';
+import { Button, Image, message, Modal, Tag, Tooltip } from 'antd';
+import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
+import { EyeOutlined } from '@ant-design/icons';
+import {
+ getShowcaseReviewList,
+ reviewShowcase,
+ type ShowcaseReviewItem,
+} from '@/services/Management/showcaseReview';
+import { getFileDisplayUrl } from '@/utils/fileUrl';
+
+/** 根据文件名后缀推断类型 */
+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';
+ return 'image';
+};
+
+const isVideo = (fileUrl: string): boolean => guessType(fileUrl) === 'video';
+
+const statusValueEnum = {
+ '0': { text: '待审核', status: 'Processing' },
+ '1': { text: '已通过', status: 'Success' },
+ '2': { text: '已驳回', status: 'Error' },
+};
+
+const ShowcaseReview: React.FC = () => {
+ const access = useAccess();
+ const actionRef = useRef();
+
+ const [approvalVisible, setApprovalVisible] = useState(false);
+ const [currentRow, setCurrentRow] = useState(null);
+ const [approvalStatus, setApprovalStatus] = useState<'1' | '2'>('1');
+ const [approvalRemark, setApprovalRemark] = useState('');
+ const [submitting, setSubmitting] = useState(false);
+ const [previewVisible, setPreviewVisible] = useState(false);
+ const [previewUrl, setPreviewUrl] = useState('');
+
+ const handleReview = (record: ShowcaseReviewItem) => {
+ setCurrentRow(record);
+ setApprovalStatus('1');
+ setApprovalRemark('');
+ setApprovalVisible(true);
+ };
+
+ const handleSubmit = async () => {
+ if (!currentRow) return;
+
+ if (approvalStatus === '2' && !approvalRemark.trim()) {
+ message.warning('请填写驳回原因');
+ return;
+ }
+
+ try {
+ setSubmitting(true);
+ const resp = await reviewShowcase({
+ id: currentRow.id,
+ reviewStatus: approvalStatus,
+ reviewRemark: approvalStatus === '2' ? approvalRemark.trim() : undefined,
+ });
+
+ if (resp?.code === 200) {
+ message.success('审核成功');
+ setApprovalVisible(false);
+ setCurrentRow(null);
+ setApprovalRemark('');
+ actionRef.current?.reload();
+ } else {
+ message.error(resp?.msg || '审核失败');
+ }
+ } catch (error) {
+ message.error('审核失败,请重试');
+ } finally {
+ setSubmitting(false);
+ }
+ };
+
+ const columns: ProColumns[] = [
+ {
+ title: 'ID',
+ dataIndex: 'id',
+ width: 80,
+ search: false,
+ },
+ {
+ title: '预览',
+ dataIndex: 'fileUrl',
+ width: 100,
+ search: false,
+ render: (_, record) => {
+ const url = getFileDisplayUrl(record.fileUrl);
+ if (isVideo(record.fileUrl)) {
+ return (
+ {
+ setPreviewUrl(url);
+ setPreviewVisible(true);
+ }}
+ >
+
+
+
+ );
+ }
+ return (
+ }}
+ />
+ );
+ },
+ },
+ {
+ title: '类型',
+ dataIndex: 'fileUrl',
+ width: 80,
+ search: false,
+ render: (_, record) => (
+ {isVideo(record.fileUrl) ? '视频' : '图片'}
+ ),
+ },
+ {
+ title: '上传用户ID',
+ dataIndex: 'bussinessid',
+ width: 120,
+ fieldProps: { placeholder: '输入用户ID' },
+ },
+ {
+ title: '审核状态',
+ dataIndex: 'reviewStatus',
+ width: 100,
+ valueType: 'select',
+ valueEnum: statusValueEnum,
+ render: (_, record) => {
+ const status = record.reviewStatus || '0';
+ const map: Record = {
+ '0': { color: 'orange', text: '待审核' },
+ '1': { color: 'green', text: '已通过' },
+ '2': { color: 'red', text: '已驳回' },
+ };
+ const info = map[status] || map['0'];
+ return {info.text};
+ },
+ },
+ {
+ title: '审核备注',
+ dataIndex: 'reviewRemark',
+ width: 200,
+ search: false,
+ ellipsis: true,
+ render: (_, record) => {
+ if (!record.reviewRemark) return '-';
+ return (
+
+ {record.reviewRemark}
+
+ );
+ },
+ },
+ {
+ title: '审核时间',
+ dataIndex: 'reviewTime',
+ width: 160,
+ search: false,
+ render: (_, record) => record.reviewTime || '-',
+ },
+ {
+ title: '上传时间',
+ dataIndex: 'createTime',
+ width: 160,
+ search: false,
+ },
+ {
+ title: '操作',
+ dataIndex: 'option',
+ valueType: 'option',
+ width: 100,
+ render: (_, record) => {
+ const isPending = (record.reviewStatus || '0') === '0';
+ if (!isPending) return '-';
+ return (
+
+ );
+ },
+ },
+ ];
+
+ return (
+ <>
+
+ headerTitle="风采审核管理"
+ actionRef={actionRef}
+ rowKey="id"
+ search={{ labelWidth: 100 }}
+ request={async (params) => {
+ const { current, pageSize, bussinessid, reviewStatus } = params;
+ const res = await getShowcaseReviewList({
+ current,
+ pageSize,
+ bussinessid: bussinessid ? Number(bussinessid) : undefined,
+ reviewStatus,
+ });
+ return {
+ data: res?.rows || [],
+ total: res?.total || 0,
+ success: res?.code === 200,
+ };
+ }}
+ columns={columns}
+ scroll={{ x: 'max-content' }}
+ />
+
+ {/* 审核弹窗 */}
+ {
+ setApprovalVisible(false);
+ setApprovalRemark('');
+ setCurrentRow(null);
+ }}
+ footer={null}
+ destroyOnClose
+ >
+
+
+ 审核结果:
+
+
+
+ {approvalStatus === '2' && (
+
+ )}
+
+
+
+
+
+
+
+ {/* 视频预览弹窗 */}
+ setPreviewVisible(false)}
+ width={800}
+ destroyOnClose
+ >
+ {previewUrl && (
+
+ )}
+
+ >
+ );
+};
+
+export default ShowcaseReview;
diff --git a/src/services/Management/showcaseReview.ts b/src/services/Management/showcaseReview.ts
new file mode 100644
index 0000000..e0d9f33
--- /dev/null
+++ b/src/services/Management/showcaseReview.ts
@@ -0,0 +1,46 @@
+import { request } from '@umijs/max';
+
+/** 风采审核列表项 */
+export interface ShowcaseReviewItem {
+ id: number;
+ fileUrl: string;
+ bussinessid: number;
+ reviewStatus: string; // '0'=待审核, '1'=审核通过, '2'=审核未通过
+ reviewRemark?: string;
+ reviewBy?: string;
+ reviewTime?: string;
+ createTime: string;
+ updateTime?: string;
+}
+
+/** 风采审核列表查询参数 */
+export interface ShowcaseReviewListParams {
+ current?: number;
+ pageSize?: number;
+ bussinessid?: number;
+ reviewStatus?: string;
+}
+
+/** 风采审核操作参数 */
+export interface ShowcaseApprovalParams {
+ id: number;
+ reviewStatus: '1' | '2'; // 1=通过, 2=驳回
+ reviewRemark?: string;
+}
+
+/** 获取风采审核列表 GET /api/cms/file/review/list */
+export async function getShowcaseReviewList(params: ShowcaseReviewListParams) {
+ return request<{ code: number; total: number; rows: ShowcaseReviewItem[] }>(
+ '/api/cms/file/review/list',
+ { method: 'GET', params },
+ );
+}
+
+/** 审核风采(通过/驳回) PUT /api/cms/file/review/{id} */
+export async function reviewShowcase(params: ShowcaseApprovalParams) {
+ const { id, ...data } = params;
+ return request<{ code: number; msg: string }>(`/api/cms/file/review/${id}`, {
+ method: 'PUT',
+ data,
+ });
+}