/**
|
* @author 韩天尊
|
* @time 2024-01-15
|
* @version 1.0.0
|
* @description 管理端发布活动页面,用于创建新的志愿服务活动
|
*/
|
import React, { useState } from 'react';
|
import { useNavigate } from 'react-router-dom';
|
import { useAppContext } from '../context/AppContext';
|
import PageHeader from '../components/PageHeader';
|
import { adminAPI } from '../services/api';
|
|
// 活动表单数据接口定义
|
interface ActivityFormData {
|
title: string;
|
category: string;
|
startTime: string;
|
endTime: string;
|
location: string;
|
content: string;
|
maxParticipants: number;
|
deadline: string;
|
points: number;
|
cover?: File;
|
}
|
|
const AdminActivityCreatePage: React.FC = () => {
|
const navigate = useNavigate();
|
const { state } = useAppContext();
|
|
// 表单数据状态
|
const [formData, setFormData] = useState<ActivityFormData>({
|
title: '',
|
category: '',
|
startTime: '',
|
endTime: '',
|
location: '',
|
content: '',
|
maxParticipants: 0,
|
deadline: '',
|
points: 0
|
});
|
|
// 照片文件状态 - 支持多张照片
|
interface PhotoItem {
|
id: string;
|
file: File;
|
preview: string;
|
uploadUrl: string;
|
fileId: string;
|
}
|
|
const [photos, setPhotos] = useState<PhotoItem[]>([]);
|
const [uploading, setUploading] = useState(false);
|
|
// 生成固定的ownerId,确保多次上传使用相同值
|
const [fixedOwnerId] = useState(() => Date.now().toString());
|
|
// 处理输入框变化
|
const handleInputChange = (field: keyof ActivityFormData, value: string | number) => {
|
setFormData(prev => ({
|
...prev,
|
[field]: value
|
}));
|
};
|
|
// 重置文件输入框
|
const resetFileInput = () => {
|
const fileInput = document.querySelector<HTMLInputElement>('.file-input-hidden');
|
if (fileInput) {
|
fileInput.value = '';
|
}
|
};
|
|
// 处理文件上传 - 支持多张照片
|
const handleFileUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
const files = event.target.files;
|
if (!files || files.length === 0) return;
|
|
setUploading(true);
|
|
try {
|
// 处理每个文件
|
const uploadPromises = Array.from(files).map(async (file) => {
|
// 创建预览
|
const preview = await new Promise<string>((resolve) => {
|
const reader = new FileReader();
|
reader.onload = (e) => resolve(e.target?.result as string);
|
reader.readAsDataURL(file);
|
});
|
|
// 使用固定的ownerId,确保多次上传使用相同值
|
const ownerId = fixedOwnerId;
|
|
// 准备FormData
|
const formData = new FormData();
|
formData.append('file', file);
|
|
// 调用上传接口
|
const response = await adminAPI.uploadFile(formData, ownerId, '35001');
|
|
if (response.code === 0) {
|
const fileData = response.data[0];
|
if (fileData) {
|
return {
|
id: Date.now().toString() + Math.random().toString(36).substr(2, 9),
|
file,
|
preview,
|
uploadUrl: fileData.showUrl || fileData.downUrl || '',
|
fileId: fileData.id
|
};
|
}
|
}
|
throw new Error(response.msg || '上传失败');
|
});
|
|
// 等待所有文件上传完成
|
const uploadedPhotos = await Promise.all(uploadPromises);
|
|
// 添加到照片列表
|
setPhotos(prev => [...prev, ...uploadedPhotos]);
|
|
console.log('多张照片上传成功:', uploadedPhotos);
|
|
} catch (error) {
|
console.error('照片上传失败:', error);
|
alert('照片上传失败,请重试');
|
} finally {
|
setUploading(false);
|
// 重置文件输入框
|
resetFileInput();
|
}
|
};
|
|
// 删除单张照片
|
const handleDeletePhoto = async (photoId: string) => {
|
const photo = photos.find(p => p.id === photoId);
|
if (!photo) {
|
alert('照片不存在');
|
return;
|
}
|
|
const confirmDelete = window.confirm('确认删除此照片吗?');
|
if (confirmDelete) {
|
try {
|
const response = await adminAPI.deleteFile(photo.fileId);
|
if (response.code === 0) {
|
alert('照片删除成功');
|
// 从照片列表中移除
|
setPhotos(prev => prev.filter(p => p.id !== photoId));
|
} else {
|
alert('照片删除失败:' + response.msg);
|
}
|
} catch (error) {
|
console.error('删除照片失败:', error);
|
alert('删除照片失败,请重试');
|
}
|
}
|
};
|
|
// 表单验证
|
const validateForm = (): string | null => {
|
if (!formData.title.trim()) return '请输入活动名称';
|
if (!formData.category) return '请选择活动分类';
|
if (!formData.startTime) return '请选择活动开始时间';
|
if (!formData.endTime) return '请选择活动结束时间';
|
if (!formData.location.trim()) return '请输入活动地点';
|
if (!formData.content.trim()) return '请输入活动内容';
|
if (!formData.maxParticipants || formData.maxParticipants <= 0) return '请输入有效的最大参与人数';
|
if (!formData.deadline) return '请选择报名截止时间';
|
if (!formData.points || formData.points <= 0) return '请输入有效的活动积分';
|
|
// 验证时间逻辑
|
const startTime = new Date(formData.startTime);
|
const endTime = new Date(formData.endTime);
|
const deadline = new Date(formData.deadline);
|
const now = new Date();
|
|
if (startTime <= now) return '活动开始时间必须晚于当前时间';
|
if (endTime <= startTime) return '活动结束时间必须晚于开始时间';
|
if (deadline >= startTime) return '报名截止时间必须早于活动开始时间';
|
|
return null;
|
};
|
|
// 提交活动创建
|
const handleSubmitActivity = async () => {
|
const error = validateForm();
|
if (error) {
|
alert(error);
|
return;
|
}
|
|
// 检查管理员信息是否存在
|
if (!state.adminInfo) {
|
alert('管理员信息不存在,请重新登录');
|
return;
|
}
|
|
// 提交处理
|
const confirmSubmit = window.confirm('确认发布此活动吗?');
|
if (confirmSubmit) {
|
try {
|
// 准备提交数据
|
const submitData = {
|
title: formData.title,
|
category: formData.category,
|
categoryDesc: getCategoryText(formData.category),
|
startTime: formData.startTime,
|
endTime: formData.endTime,
|
location: formData.location,
|
content: formData.content,
|
maxParticipants: formData.maxParticipants,
|
deadline: formData.deadline,
|
points: formData.points,
|
ownerId: fixedOwnerId, // 使用固定的ownerId,确保与上传文件使用相同值
|
img: photos.length > 0 ? photos[0].uploadUrl : '', // 使用第一张照片作为封面
|
// 从管理员缓存获取的字段
|
communityCode: state.adminInfo.communityCode || '',
|
communityName: state.adminInfo.communityName || '',
|
subdistrictCode: state.adminInfo.subdistrictCode || '',
|
subdistrictName: state.adminInfo.subdistrictName || '',
|
districtCode: state.adminInfo.districtCode || '',
|
districtName: state.adminInfo.districtName || '',
|
creatorId: state.adminInfo.id || 0,
|
creatorName: state.adminInfo.name || ''
|
};
|
|
// 调用API创建活动
|
const response = await adminAPI.createActivity(submitData);
|
|
if (response.code === 0) {
|
alert('活动发布成功!');
|
navigate('/admin');
|
} else {
|
alert('活动发布失败:' + response.msg);
|
}
|
} catch (error) {
|
console.error('创建活动失败:', error);
|
alert('活动发布失败,请重试');
|
}
|
}
|
};
|
|
// 获取分类文本
|
const getCategoryText = (category: string) => {
|
const categoryMap: { [key: string]: string } = {
|
'party': '党的建设',
|
'economy': '经济发展',
|
'security': '平安法治',
|
'service': '民生服务',
|
'illegal': '失信违法'
|
};
|
return categoryMap[category] || category;
|
};
|
|
return (
|
<div className="page admin-activity-create-page">
|
<PageHeader title="发布活动" showBack={true} />
|
|
<div className="activity-create-form">
|
{/* 活动基本信息 */}
|
<div className="form-section">
|
<h3 className="section-title">活动基本信息</h3>
|
|
<div className="form-group">
|
<label>活动名称</label>
|
<input
|
type="text"
|
placeholder="请输入活动名称"
|
className="form-input"
|
value={formData.title}
|
onChange={(e) => handleInputChange('title', e.target.value)}
|
maxLength={50}
|
/>
|
</div>
|
|
<div className="form-group">
|
<label>活动分类</label>
|
<select
|
className="form-select"
|
value={formData.category}
|
onChange={(e) => handleInputChange('category', e.target.value)}
|
>
|
<option value="">请选择活动分类</option>
|
<option value="party">党的建设</option>
|
<option value="economy">经济发展</option>
|
<option value="security">平安法治</option>
|
<option value="service">民生服务</option>
|
<option value="illegal">失信违法</option>
|
</select>
|
</div>
|
|
<div className="form-group">
|
<div className="time-range-container">
|
<div className="time-input-group">
|
<label className="time-label">活动开始时间</label>
|
<input
|
type="datetime-local"
|
className="form-input time-input"
|
value={formData.startTime}
|
onChange={(e) => handleInputChange('startTime', e.target.value)}
|
/>
|
</div>
|
<div className="time-input-group">
|
<label className="time-label">活动结束时间</label>
|
<input
|
type="datetime-local"
|
className="form-input time-input"
|
value={formData.endTime}
|
onChange={(e) => handleInputChange('endTime', e.target.value)}
|
/>
|
</div>
|
</div>
|
</div>
|
|
<div className="form-group">
|
<label>活动地点</label>
|
<input
|
type="text"
|
placeholder="请输入活动地点"
|
className="form-input"
|
value={formData.location}
|
onChange={(e) => handleInputChange('location', e.target.value)}
|
maxLength={100}
|
/>
|
</div>
|
|
<div className="form-group">
|
<label>活动内容</label>
|
<textarea
|
placeholder="请输入活动详细内容"
|
className="form-input"
|
rows={4}
|
value={formData.content}
|
onChange={(e) => handleInputChange('content', e.target.value)}
|
maxLength={500}
|
/>
|
</div>
|
</div>
|
|
{/* 活动设置 */}
|
<div className="form-section">
|
<h3 className="section-title">活动设置</h3>
|
|
<div className="form-group">
|
<label>最大参与人数</label>
|
<input
|
type="number"
|
placeholder="请输入最大参与人数"
|
className="form-input"
|
value={formData.maxParticipants || ''}
|
onChange={(e) => handleInputChange('maxParticipants', parseInt(e.target.value) || 0)}
|
min="1"
|
max="1000"
|
/>
|
</div>
|
|
<div className="form-group">
|
<label>报名截止时间</label>
|
<input
|
type="datetime-local"
|
className="form-input"
|
value={formData.deadline}
|
onChange={(e) => handleInputChange('deadline', e.target.value)}
|
/>
|
</div>
|
|
<div className="form-group">
|
<label>活动积分</label>
|
<input
|
type="number"
|
placeholder="请输入活动积分"
|
className="form-input"
|
value={formData.points || ''}
|
onChange={(e) => handleInputChange('points', parseInt(e.target.value) || 0)}
|
min="1"
|
max="100"
|
/>
|
</div>
|
|
<div className="form-group">
|
<label>活动照片</label>
|
<div className="file-upload">
|
<input
|
type="file"
|
accept="image/*"
|
multiple
|
className="file-input-hidden"
|
onChange={handleFileUpload}
|
/>
|
<button
|
type="button"
|
className="upload-btn"
|
onClick={() => document.querySelector<HTMLInputElement>('.file-input-hidden')?.click()}
|
disabled={uploading}
|
>
|
<i className={uploading ? "fas fa-spinner fa-spin" : "fas fa-cloud-upload-alt"}></i>
|
<span>
|
{uploading ? '上传中...' : photos.length > 0 ? '继续添加照片' : '点击上传活动照片'}
|
</span>
|
</button>
|
|
{/* 照片预览列表 */}
|
{photos.length > 0 && (
|
<div className="photos-preview" style={{ marginTop: '16px' }}>
|
<div
|
className="photos-grid"
|
style={{
|
display: 'grid',
|
gridTemplateColumns: 'repeat(auto-fill, minmax(80px, 1fr))',
|
gap: '8px',
|
maxHeight: '200px',
|
overflowY: 'auto',
|
padding: '8px',
|
border: '1px solid #e9ecef',
|
borderRadius: '8px',
|
background: '#f8f9fa'
|
}}
|
>
|
{photos.map((photo) => (
|
<div
|
key={photo.id}
|
className="photo-item"
|
style={{
|
position: 'relative',
|
aspectRatio: '1',
|
borderRadius: '6px',
|
overflow: 'hidden',
|
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)',
|
transition: 'transform 0.2s ease'
|
}}
|
>
|
<img
|
src={photo.preview}
|
alt="照片预览"
|
style={{
|
width: '100%',
|
height: '100%',
|
objectFit: 'cover',
|
display: 'block'
|
}}
|
/>
|
<button
|
type="button"
|
className="remove-photo"
|
onClick={() => handleDeletePhoto(photo.id)}
|
style={{
|
position: 'absolute',
|
top: '2px',
|
right: '2px',
|
width: '18px',
|
height: '18px',
|
border: 'none',
|
borderRadius: '50%',
|
background: 'rgba(255, 0, 0, 0.8)',
|
color: 'white',
|
cursor: 'pointer',
|
display: 'flex',
|
alignItems: 'center',
|
justifyContent: 'center',
|
fontSize: '10px',
|
transition: 'all 0.2s ease'
|
}}
|
>
|
<i className="fas fa-times"></i>
|
</button>
|
</div>
|
))}
|
</div>
|
<div
|
className="photos-info"
|
style={{
|
marginTop: '8px',
|
textAlign: 'center',
|
fontSize: '12px',
|
color: '#6c757d',
|
fontWeight: '500'
|
}}
|
>
|
<span>已上传 {photos.length} 张照片</span>
|
</div>
|
</div>
|
)}
|
</div>
|
</div>
|
</div>
|
|
{/* 提交按钮 */}
|
<div className="form-actions">
|
<button
|
className="submit-btn"
|
onClick={handleSubmitActivity}
|
>
|
<i className="fas fa-paper-plane"></i>
|
发布活动
|
</button>
|
</div>
|
</div>
|
</div>
|
);
|
};
|
|
export default AdminActivityCreatePage;
|