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={(
} className="btn-soft-blue" onClick={fetchClients} loading={loading}>刷新
} onClick={() => openModal()}>新增发布
)}
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
/>
)}
>
({ ...tab, children: null }))}
/>