import React, { useEffect, useMemo, useState } from 'react';
import {
Table,
Button,
Input,
Space,
Modal,
Form,
Select,
App,
Tooltip,
Switch,
InputNumber,
Upload,
Tag,
Avatar,
Row,
Col,
Typography,
} from 'antd';
import {
PlusOutlined,
DeleteOutlined,
EditOutlined,
SearchOutlined,
GlobalOutlined,
RobotOutlined,
UploadOutlined,
LinkOutlined,
AppstoreOutlined,
ReloadOutlined,
ExportOutlined,
PictureOutlined,
CheckCircleOutlined,
BlockOutlined,
} from '@ant-design/icons';
import apiClient from '../../utils/apiClient';
import { buildApiUrl, API_ENDPOINTS } from '../../config/api';
import AdminModuleShell from '../../components/AdminModuleShell';
import StatusTag from '../../components/StatusTag';
const { Text } = Typography;
const { TextArea } = Input;
const APP_TYPE_OPTIONS = [
{ label: '全部类型', value: 'all' },
{ label: '原生应用', value: 'native' },
{ label: 'Web 应用', value: 'web' },
];
const STATUS_OPTIONS = [
{ label: '全部状态', value: 'all' },
{ label: '已启用', value: 'active' },
{ label: '已停用', value: 'inactive' },
];
const parseAppInfo = (appInfo) => {
if (!appInfo) return {};
if (typeof appInfo === 'object') return appInfo;
try {
return JSON.parse(appInfo);
} catch {
return {};
}
};
const getAppEntryUrl = (app) => {
const info = parseAppInfo(app.app_info);
return app.app_type === 'native' ? info.apk_url : info.web_url;
};
const ExternalAppManagement = () => {
const { message, modal } = App.useApp();
const [form] = Form.useForm();
const [apps, setApps] = useState([]);
const [loading, setLoading] = useState(true);
const [showModal, setShowModal] = useState(false);
const [isEditing, setIsEditing] = useState(false);
const [selectedApp, setSelectedApp] = useState(null);
const [filterAppType, setFilterAppType] = useState('all');
const [filterStatus, setFilterStatus] = useState('all');
const [searchQuery, setSearchQuery] = useState('');
const [uploading, setUploading] = useState(false);
useEffect(() => {
fetchApps();
}, []);
const fetchApps = async () => {
setLoading(true);
try {
const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.EXTERNAL_APPS.LIST));
if (response.code === '200') {
setApps(response.data || []);
}
} catch {
message.error('获取外部应用列表失败');
} finally {
setLoading(false);
}
};
const handleOpenModal = (app = null) => {
if (app) {
setIsEditing(true);
setSelectedApp(app);
form.setFieldsValue({
...app,
app_info: parseAppInfo(app.app_info),
is_active: app.is_active === 1 || app.is_active === true,
});
} else {
setIsEditing(false);
setSelectedApp(null);
form.resetFields();
form.setFieldsValue({
app_type: 'native',
is_active: true,
sort_order: 0,
app_info: {},
});
}
setShowModal(true);
};
const handleSave = async () => {
try {
const values = await form.validateFields();
const payload = {
...values,
app_info: JSON.stringify(values.app_info || {}),
is_active: values.is_active ? 1 : 0,
};
if (isEditing) {
await apiClient.put(buildApiUrl(API_ENDPOINTS.EXTERNAL_APPS.UPDATE(selectedApp.id)), payload);
message.success('应用更新成功');
} else {
await apiClient.post(buildApiUrl(API_ENDPOINTS.EXTERNAL_APPS.CREATE), payload);
message.success('应用创建成功');
}
setShowModal(false);
fetchApps();
} catch (error) {
if (!error.errorFields) {
message.error('保存失败');
}
}
};
const handleDelete = (item) => {
modal.confirm({
title: '删除外部应用',
content: `确定要删除应用“${item.app_name}”吗?此操作不可恢复。`,
okText: '确定',
okType: 'danger',
onOk: async () => {
try {
await apiClient.delete(buildApiUrl(API_ENDPOINTS.EXTERNAL_APPS.DELETE(item.id)));
message.success('删除成功');
fetchApps();
} catch {
message.error('删除失败');
}
},
});
};
const handleFileUpload = async (options, type) => {
const { file, onSuccess, onError } = options;
setUploading(true);
const uploadFormData = new FormData();
const endpoint = type === 'apk' ? API_ENDPOINTS.EXTERNAL_APPS.UPLOAD_APK : API_ENDPOINTS.EXTERNAL_APPS.UPLOAD_ICON;
const fieldName = type === 'apk' ? 'apk_file' : 'icon_file';
uploadFormData.append(fieldName, file);
try {
const response = await apiClient.post(buildApiUrl(endpoint), uploadFormData, {
headers: { 'Content-Type': 'multipart/form-data' },
});
if (response.code === '200') {
if (type === 'apk') {
const apkData = response.data;
const currentInfo = form.getFieldValue('app_info') || {};
form.setFieldsValue({
app_name: apkData.app_name || form.getFieldValue('app_name'),
app_info: {
...currentInfo,
version_name: apkData.version_name,
package_name: apkData.package_name,
apk_url: apkData.apk_url,
},
});
message.success('APK 上传并解析成功');
} else {
form.setFieldsValue({ icon_url: response.data.icon_url });
message.success('应用图标上传成功');
}
onSuccess(response.data);
}
} catch (error) {
message.error('上传失败');
onError(error);
} finally {
setUploading(false);
}
};
const handleToggleStatus = async (item, checked) => {
try {
const newActive = checked ? 1 : 0;
await apiClient.put(buildApiUrl(API_ENDPOINTS.EXTERNAL_APPS.UPDATE(item.id)), { is_active: newActive });
setApps((prev) => prev.map((app) => (
app.id === item.id ? { ...app, is_active: newActive } : app
)));
message.success(`已${newActive ? '启用' : '禁用'}应用`);
} catch {
message.error('状态更新失败');
}
};
const filteredApps = useMemo(() => apps.filter((app) => {
if (filterAppType !== 'all' && app.app_type !== filterAppType) {
return false;
}
const enabled = app.is_active === 1 || app.is_active === true;
if (filterStatus === 'active' && !enabled) {
return false;
}
if (filterStatus === 'inactive' && enabled) {
return false;
}
if (!searchQuery) {
return true;
}
const query = searchQuery.toLowerCase();
const info = parseAppInfo(app.app_info);
return [
app.app_name,
app.description,
info.package_name,
info.web_url,
info.apk_url,
info.version_name,
].some((field) => String(field || '').toLowerCase().includes(query));
}), [apps, filterAppType, filterStatus, searchQuery]);
const nativeCount = useMemo(() => apps.filter((app) => app.app_type === 'native').length, [apps]);
const webCount = useMemo(() => apps.filter((app) => app.app_type === 'web').length, [apps]);
const activeCount = useMemo(() => apps.filter((app) => app.is_active === 1 || app.is_active === true).length, [apps]);
const iconMissingCount = useMemo(() => apps.filter((app) => !app.icon_url).length, [apps]);
const columns = [
{
title: '应用',
key: 'app',
width: 240,
render: (_, record) => (