import React, { useEffect, useMemo, useState } from 'react'; import { Table, Button, Input, Space, Drawer, Form, Select, App, Tooltip, Switch, InputNumber, Badge, Typography, Upload, Tabs, Tag, Row, Col, } from 'antd'; import { PlusOutlined, DownloadOutlined, DeleteOutlined, EditOutlined, SearchOutlined, MobileOutlined, DesktopOutlined, PartitionOutlined, UploadOutlined, InfoCircleOutlined, ReloadOutlined, CloudUploadOutlined, CheckCircleOutlined, RocketOutlined, SaveOutlined, } from '@ant-design/icons'; import apiClient from '../utils/apiClient'; import { buildApiUrl, API_ENDPOINTS } from '../config/api'; import AdminModuleShell from '../components/AdminModuleShell'; import ActionButton from '../components/ActionButton'; const { Text } = Typography; const { TextArea } = Input; const CLIENT_TABS = [ { key: 'all', label: '全部' }, { key: 'mobile', label: 移动端 }, { key: 'desktop', label: 桌面端 }, { key: 'terminal', label: 专用终端 }, ]; const STATUS_OPTIONS = [ { label: '全部状态', value: 'all' }, { label: '已启用', value: 'active' }, { label: '已停用', value: 'inactive' }, { label: '最新版本', value: 'latest' }, ]; const isTruthy = (value) => value === true || value === 1 || value === '1'; const ClientManagement = () => { const { message, modal } = App.useApp(); const [form] = Form.useForm(); const [clients, setClients] = useState([]); const [loading, setLoading] = useState(true); const [showDrawer, setShowDrawer] = useState(false); const [isEditing, setIsEditing] = useState(false); const [selectedClient, setSelectedClient] = useState(null); const [searchQuery, setSearchQuery] = useState(''); const [statusFilter, setStatusFilter] = useState('all'); const [activeTab, setActiveTab] = useState('all'); const [uploadingFile, setUploadingFile] = useState(false); const [updatingStatusId, setUpdatingStatusId] = useState(null); const [platforms, setPlatforms] = useState({ tree: [], items: [] }); const [platformsMap, setPlatformsMap] = useState({}); useEffect(() => { fetchPlatforms(); fetchClients(); }, []); const fetchPlatforms = async () => { try { const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.DICT_DATA.BY_TYPE('client_platform'))); if (response.code === '200') { const { tree = [], items = [] } = response.data || {}; setPlatforms({ tree, items }); const nextMap = {}; items.forEach((item) => { nextMap[item.dict_code] = item; }); setPlatformsMap(nextMap); } } catch { message.error('获取平台列表失败'); } }; const fetchClients = async () => { setLoading(true); try { const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.CLIENT_DOWNLOADS.LIST)); if (response.code === '200') { setClients(response.data.clients || []); } } catch { message.error('获取客户端列表失败'); } finally { setLoading(false); } }; const getPlatformLabel = (platformCode) => platformsMap[platformCode]?.label_cn || platformCode || '-'; const openModal = (client = null) => { if (client) { setIsEditing(true); setSelectedClient(client); form.setFieldsValue({ ...client, version_code: client.version_code, file_size: client.file_size, is_active: isTruthy(client.is_active), is_latest: isTruthy(client.is_latest), }); } else { setIsEditing(false); setSelectedClient(null); form.resetFields(); form.setFieldsValue({ is_active: true, is_latest: false, }); } setShowDrawer(true); }; const handleSave = async () => { try { const values = await form.validateFields(); const platformInfo = platformsMap[values.platform_code]; const parentCode = platformInfo?.parent_code; let platformType = 'desktop'; if (parentCode === 'MOBILE') { platformType = 'mobile'; } else if (parentCode === 'TERMINAL') { platformType = 'terminal'; } const payload = { ...values, platform_type: platformType, platform_name: String(values.platform_code || '').toLowerCase(), }; if (isEditing) { await apiClient.put(buildApiUrl(API_ENDPOINTS.CLIENT_DOWNLOADS.UPDATE(selectedClient.id)), payload); message.success('版本更新成功'); } else { await apiClient.post(buildApiUrl(API_ENDPOINTS.CLIENT_DOWNLOADS.CREATE), payload); message.success('版本创建成功'); } setShowDrawer(false); fetchClients(); } catch (error) { if (!error?.errorFields) { message.error('保存失败'); } } }; const handleDelete = (item) => { modal.confirm({ title: '删除客户端版本', content: `确定要删除 ${getPlatformLabel(item.platform_code)} ${item.version} 吗?`, okText: '确定', okType: 'danger', onOk: async () => { try { await apiClient.delete(buildApiUrl(API_ENDPOINTS.CLIENT_DOWNLOADS.DELETE(item.id))); message.success('删除成功'); fetchClients(); } catch { message.error('删除失败'); } }, }); }; const handleToggleActive = async (item, checked) => { setUpdatingStatusId(item.id); try { await apiClient.put(buildApiUrl(API_ENDPOINTS.CLIENT_DOWNLOADS.UPDATE(item.id)), { is_active: checked, }); setClients((prev) => prev.map((client) => ( client.id === item.id ? { ...client, is_active: checked } : client ))); message.success(`已${checked ? '启用' : '停用'}版本`); } catch { message.error('状态更新失败'); } finally { setUpdatingStatusId(null); } }; const handleFileUpload = async (options) => { const { file, onSuccess, onError } = options; const platformCode = form.getFieldValue('platform_code'); if (!platformCode) { message.warning('请先选择发布平台'); onError(new Error('No platform selected')); return; } setUploadingFile(true); const uploadFormData = new FormData(); uploadFormData.append('file', file); uploadFormData.append('platform_code', platformCode); try { const response = await apiClient.post( buildApiUrl(API_ENDPOINTS.CLIENT_DOWNLOADS.UPLOAD), uploadFormData, { headers: { 'Content-Type': 'multipart/form-data' } }, ); if (response.code === '200') { const { file_size: fileSize, download_url: downloadUrl, version_code: versionCode, version_name: versionName, } = response.data; form.setFieldsValue({ file_size: fileSize, download_url: downloadUrl, version_code: versionCode, version: versionName, }); message.success('安装包上传成功,已自动填充版本信息'); onSuccess(response.data); } } catch (error) { message.error('文件上传失败'); onError(error); } finally { setUploadingFile(false); } }; const formatFileSize = (bytes) => { if (!bytes) return '-'; return `${(bytes / (1024 * 1024)).toFixed(2)} MB`; }; const filteredClients = useMemo(() => clients.filter((client) => { if (activeTab !== 'all' && client.platform_type !== activeTab) { return false; } const enabled = isTruthy(client.is_active); const latest = isTruthy(client.is_latest); if (statusFilter === 'active' && !enabled) { return false; } if (statusFilter === 'inactive' && enabled) { return false; } if (statusFilter === 'latest' && !latest) { return false; } if (!searchQuery) { return true; } const query = searchQuery.toLowerCase(); return [ client.version, client.platform_code, getPlatformLabel(client.platform_code), client.min_system_version, client.download_url, client.release_notes, ].some((field) => String(field || '').toLowerCase().includes(query)); }), [activeTab, clients, searchQuery, statusFilter, platformsMap]); const publishedCount = clients.length; const activeCount = clients.filter((item) => isTruthy(item.is_active)).length; const latestCount = clients.filter((item) => isTruthy(item.is_latest)).length; const terminalCount = clients.filter((item) => item.platform_type === 'terminal').length; const columns = [ { title: '平台', dataIndex: 'platform_code', key: 'platform_code', width: 140, render: (code) => {getPlatformLabel(code)}, }, { title: '版本信息', key: 'version', width: 200, render: (_, record) => ( {record.version} 版本码:{record.version_code} ), }, { title: '安装包信息', key: 'package', width: 220, render: (_, record) => ( 大小:{formatFileSize(record.file_size)} 系统要求:{record.min_system_version || '-'} ), }, { title: '发布状态', key: 'status', width: 140, render: (_, record) => ( handleToggleActive(record, checked)} /> {isTruthy(record.is_latest) ? : } ), }, { title: '更新时间', dataIndex: 'updated_at', key: 'updated_at', width: 180, render: (value) => (value ? new Date(value).toLocaleString() : '-'), }, { title: '操作', key: 'action', fixed: 'right', width: 140, render: (_, record) => ( } onClick={() => openModal(record)} /> } href={record.download_url} target="_blank" /> } onClick={() => handleDelete(record)} /> ), }, ]; return (
} title="客户端管理" subtitle="统一维护移动端、桌面端与专用终端的版本发布、安装包上传、启停控制与最新版本标记。" rightActions={( )} stats={[ { label: '发布总数', value: publishedCount, icon: , tone: 'blue', desc: '当前系统登记的全部客户端版本', }, { label: '启用版本', value: activeCount, icon: , tone: 'green', desc: '对外可见、允许分发的版本数量', }, { label: '最新版本', value: latestCount, icon: , tone: 'violet', desc: '被标记为最新的各平台主推版本', }, { label: '终端发布', value: terminalCount, icon: , tone: 'amber', desc: '面向专用终端设备的独立发布包数量', }, ]} toolbar={( } value={searchQuery} onChange={(event) => setSearchQuery(event.target.value)} style={{ width: 300 }} allowClear /> {platforms.tree.map((parentNode) => ( {parentNode.children?.map((childNode) => ( {childNode.label_cn} ))} ))}