import React, { useState, useEffect } from 'react'; import { Download, Plus, Edit, Trash2, Smartphone, Monitor, Apple, Search, X, ChevronDown, ChevronUp, Package, Hash, Link, FileText, HardDrive, Cpu, Upload } from 'lucide-react'; import apiClient from '../utils/apiClient'; import { buildApiUrl, API_ENDPOINTS } from '../config/api'; import ConfirmDialog from '../components/ConfirmDialog'; import FormModal from '../components/FormModal'; import Toast from '../components/Toast'; import './ClientManagement.css'; const ClientManagement = ({ user }) => { const [clients, setClients] = useState([]); const [loading, setLoading] = useState(true); const [showClientModal, setShowClientModal] = useState(false); const [isEditing, setIsEditing] = useState(false); const [deleteConfirmInfo, setDeleteConfirmInfo] = useState(null); const [selectedClient, setSelectedClient] = useState(null); const [filterPlatformType, setFilterPlatformType] = useState(''); const [searchQuery, setSearchQuery] = useState(''); const [expandedNotes, setExpandedNotes] = useState({}); const [toasts, setToasts] = useState([]); const [uploadingFile, setUploadingFile] = useState(false); // 码表数据 const [platforms, setPlatforms] = useState({ tree: [], items: [] }); const [platformsMap, setPlatformsMap] = useState({}); const [formData, setFormData] = useState({ platform_code: '', version: '', version_code: '', download_url: '', file_size: '', release_notes: '', is_active: true, is_latest: false, min_system_version: '' }); // Toast helper functions const showToast = (message, type = 'info') => { const id = Date.now(); setToasts(prev => [...prev, { id, message, type }]); }; const removeToast = (id) => { setToasts(prev => prev.filter(toast => toast.id !== id)); }; useEffect(() => { fetchPlatforms(); fetchClients(); }, []); const fetchPlatforms = async () => { try { const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.DICT_DATA.BY_TYPE('client_platform'))); const { tree, items } = response.data; setPlatforms({ tree, items }); // 构建快速查找map const map = {}; items.forEach(item => { map[item.dict_code] = item; }); setPlatformsMap(map); } catch (error) { console.error('获取平台列表失败:', error); showToast('获取平台列表失败', 'error'); } }; const fetchClients = async () => { setLoading(true); try { const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.CLIENT_DOWNLOADS.LIST)); console.log('Client downloads response:', response); setClients(response.data.clients || []); } catch (error) { console.error('获取客户端列表失败:', error); } finally { setLoading(false); } }; const handleCreate = async () => { try { // 验证必填字段 if (!formData.platform_code || !formData.version_code || !formData.version || !formData.download_url) { showToast('请填写所有必填字段', 'warning'); return; } // 根据platform_code映射到旧字段platform_type和platform_name以保持向后兼容 const platformInfo = platformsMap[formData.platform_code]; const parentCode = platformInfo?.parent_code; const parentInfo = parentCode && parentCode !== 'ROOT' ? platformsMap[parentCode] : null; // 映射platform_type let platform_type = 'desktop'; // 默认 if (parentInfo) { const parentCodeUpper = parentCode.toUpperCase(); if (parentCodeUpper === 'MOBILE') platform_type = 'mobile'; else if (parentCodeUpper === 'DESKTOP') platform_type = 'desktop'; else if (parentCodeUpper === 'TERMINAL') platform_type = 'terminal'; } // 映射platform_name (简化映射,用小写的dict_code) const platform_name = formData.platform_code.toLowerCase(); const payload = { ...formData, platform_type, platform_name, version_code: parseInt(formData.version_code, 10), file_size: formData.file_size ? parseInt(formData.file_size, 10) : null }; // 验证转换后的数字 if (isNaN(payload.version_code)) { showToast('版本代码必须是有效的数字', 'warning'); return; } if (formData.file_size && isNaN(payload.file_size)) { showToast('文件大小必须是有效的数字', 'warning'); return; } await apiClient.post(buildApiUrl(API_ENDPOINTS.CLIENT_DOWNLOADS.CREATE), payload); handleCloseModal(); fetchClients(); showToast('客户端创建成功', 'success'); } catch (error) { console.error('创建客户端失败:', error); showToast(error.response?.data?.message || '创建失败,请重试', 'error'); } }; const handleUpdate = async () => { try { // 验证必填字段 if (!formData.platform_code || !formData.version_code || !formData.version || !formData.download_url) { showToast('请填写所有必填字段', 'warning'); return; } const payload = { platform_code: formData.platform_code, version: formData.version, version_code: parseInt(formData.version_code, 10), download_url: formData.download_url, file_size: formData.file_size ? parseInt(formData.file_size, 10) : null, release_notes: formData.release_notes, is_active: formData.is_active, is_latest: formData.is_latest, min_system_version: formData.min_system_version }; // 验证转换后的数字 if (isNaN(payload.version_code)) { showToast('版本代码必须是有效的数字', 'warning'); return; } if (formData.file_size && isNaN(payload.file_size)) { showToast('文件大小必须是有效的数字', 'warning'); return; } await apiClient.put( buildApiUrl(API_ENDPOINTS.CLIENT_DOWNLOADS.UPDATE(selectedClient.id)), payload ); handleCloseModal(); fetchClients(); showToast('客户端更新成功', 'success'); } catch (error) { console.error('更新客户端失败:', error); showToast(error.response?.data?.message || '更新失败,请重试', 'error'); } }; const handleDelete = async () => { try { await apiClient.delete(buildApiUrl(API_ENDPOINTS.CLIENT_DOWNLOADS.DELETE(deleteConfirmInfo.id))); setDeleteConfirmInfo(null); setSelectedClient(null); showToast('删除成功', 'success'); fetchClients(); } catch (error) { console.error('删除客户端失败:', error); showToast('删除失败,请重试', 'error'); setDeleteConfirmInfo(null); } }; const handleOpenModal = (client = null) => { if (client) { setIsEditing(true); setSelectedClient(client); setFormData({ platform_code: client.platform_code || '', version: client.version, version_code: String(client.version_code), download_url: client.download_url, file_size: client.file_size ? String(client.file_size) : '', release_notes: client.release_notes || '', is_active: client.is_active, is_latest: client.is_latest, min_system_version: client.min_system_version || '' }); } else { setIsEditing(false); setSelectedClient(null); // 默认选择第一个可用的平台 const defaultPlatformCode = platforms.items.length > 0 && platforms.items[0].parent_code !== 'ROOT' ? platforms.items[0].dict_code : ''; setFormData({ platform_code: defaultPlatformCode, version: '', version_code: '', download_url: '', file_size: '', release_notes: '', is_active: true, is_latest: false, min_system_version: '' }); } setShowClientModal(true); }; const handleCloseModal = () => { setShowClientModal(false); setIsEditing(false); setSelectedClient(null); resetForm(); }; const handleSave = async () => { if (isEditing) { await handleUpdate(); } else { await handleCreate(); } }; const handleInputChange = (field, value) => { setFormData(prev => ({ ...prev, [field]: value })); }; const handleFileUpload = async (event) => { const file = event.target.files[0]; if (!file) return; if (!formData.platform_code) { showToast('请先选择平台', 'warning'); event.target.value = ''; return; } setUploadingFile(true); try { const uploadFormData = new FormData(); uploadFormData.append('file', file); uploadFormData.append('platform_code', formData.platform_code); const response = await apiClient.post( buildApiUrl(API_ENDPOINTS.CLIENT_DOWNLOADS.UPLOAD), uploadFormData, { headers: { 'Content-Type': 'multipart/form-data' } } ); const { file_size, download_url, version_code, version_name } = response.data; // 自动填充表单 setFormData(prev => ({ ...prev, file_size: file_size ? String(file_size) : prev.file_size, download_url: download_url || prev.download_url, version_code: version_code ? String(version_code) : prev.version_code, version: version_name || prev.version })); showToast('文件上传成功,已自动填充相关字段', 'success'); } catch (error) { console.error('文件上传失败:', error); showToast(error.response?.data?.message || '文件上传失败', 'error'); } finally { setUploadingFile(false); event.target.value = ''; } }; const openEditModal = (client) => { handleOpenModal(client); }; const openDeleteModal = (client) => { setDeleteConfirmInfo({ id: client.id, platform_name: getPlatformLabel(client.platform_code), version: client.version }); }; const resetForm = () => { const defaultPlatformCode = platforms.items.length > 0 && platforms.items[0].parent_code !== 'ROOT' ? platforms.items[0].dict_code : ''; setFormData({ platform_code: defaultPlatformCode, version: '', version_code: '', download_url: '', file_size: '', release_notes: '', is_active: true, is_latest: false, min_system_version: '' }); setSelectedClient(null); }; const getPlatformLabel = (platformCode) => { const platform = platformsMap[platformCode]; return platform ? platform.label_cn : platformCode; }; const formatFileSize = (bytes) => { if (!bytes) return '-'; const mb = bytes / (1024 * 1024); return `${mb.toFixed(2)} MB`; }; const toggleNotes = (clientId) => { setExpandedNotes(prev => ({ ...prev, [clientId]: !prev[clientId] })); }; const filteredClients = clients.filter(client => { if (filterPlatformType && client.platform_type !== filterPlatformType) { return false; } if (searchQuery) { const query = searchQuery.toLowerCase(); return ( client.version.toLowerCase().includes(query) || getPlatformLabel(client.platform_code).toLowerCase().includes(query) || (client.release_notes && client.release_notes.toLowerCase().includes(query)) ); } return true; }); const groupedClients = { mobile: filteredClients.filter(c => c.platform_type === 'mobile'), desktop: filteredClients.filter(c => c.platform_type === 'desktop'), terminal: filteredClients.filter(c => c.platform_type === 'terminal') }; if (loading) { return
加载中...
; } return (

客户端下载管理

setSearchQuery(e.target.value)} /> {searchQuery && ( )}
{['mobile', 'desktop', 'terminal'].map(type => { const typeClients = groupedClients[type]; if (typeClients.length === 0 && filterPlatformType && filterPlatformType !== type) { return null; } const typeConfig = { mobile: { icon: , label: '移动端' }, desktop: { icon: , label: '桌面端' }, terminal: { icon: , label: '专用终端' } }; return (

{typeConfig[type].icon} {typeConfig[type].label} ({typeClients.length})

{typeClients.length === 0 ? (
暂无客户端
) : (
{typeClients.map(client => (

{getPlatformLabel(client.platform_code)}

{client.is_latest === true && 最新} {client.is_active === false && 未启用}
版本: {client.version}
版本代码: {client.version_code}
文件大小: {formatFileSize(client.file_size)}
{client.release_notes && (
toggleNotes(client.id)} style={{ cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }} > 更新说明 {expandedNotes[client.id] ? : }
{expandedNotes[client.id] && (

{client.release_notes}

)}
)}
))}
)}
); })}
{/* 客户端表单模态框 */} } > {formData && ( <>
{/* 文件上传区域 */}

{!formData.platform_code ? '请先选择平台' : 'APK文件将自动读取版本信息,其他文件只读取文件大小'}

handleInputChange('version', e.target.value)} />
{ const value = e.target.value; if (value === '' || /^\d+$/.test(value)) { handleInputChange('version_code', value); } }} min="0" step="1" />
handleInputChange('download_url', e.target.value)} />
{ const value = e.target.value; if (value === '' || /^\d+$/.test(value)) { handleInputChange('file_size', value); } }} min="0" step="1" />
handleInputChange('min_system_version', e.target.value)} />