import React, { useState, useEffect } from 'react'; import { Plus, Edit, Trash2, Search, X, Package, Globe, Upload, ExternalLink, Smartphone } from 'lucide-react'; import apiClient from '../../utils/apiClient'; import { buildApiUrl, API_ENDPOINTS } from '../../config/api'; import ConfirmDialog from '../../components/ConfirmDialog'; import Toast from '../../components/Toast'; import FormModal from '../../components/FormModal'; import ToggleSwitch from '../../components/ToggleSwitch'; import ListTable from '../../components/ListTable'; import './ExternalAppManagement.css'; const ExternalAppManagement = ({ user }) => { const [apps, setApps] = useState([]); const [loading, setLoading] = useState(true); const [showAppModal, setShowAppModal] = useState(false); const [isEditing, setIsEditing] = useState(false); const [deleteConfirmInfo, setDeleteConfirmInfo] = useState(null); const [selectedApp, setSelectedApp] = useState(null); const [filterAppType, setFilterAppType] = useState(''); const [searchQuery, setSearchQuery] = useState(''); const [toasts, setToasts] = useState([]); const [uploadingApk, setUploadingApk] = useState(false); const [uploadingIcon, setUploadingIcon] = useState(false); const [formData, setFormData] = useState({ app_name: '', app_type: 'native', app_info: { version_name: '', package_name: '', apk_url: '', web_url: '' }, icon_url: '', description: '', sort_order: 0, is_active: true }); // 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(() => { fetchApps(); }, []); const handleToggleAppStatus = async (app) => { const newActive = app.is_active ? false : true; try { await apiClient.put( buildApiUrl(API_ENDPOINTS.EXTERNAL_APPS.UPDATE(app.id)), { is_active: newActive } ); setApps(prev => prev.map(a => a.id === app.id ? { ...a, is_active: newActive } : a )); showToast(`已${newActive ? '启用' : '禁用'}应用`, 'success'); } catch (error) { showToast('状态更新失败', 'error'); } }; const fetchApps = async () => { setLoading(true); try { const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.EXTERNAL_APPS.LIST)); setApps(response.data || []); } catch (error) { console.error('获取外部应用列表失败:', error); showToast('获取外部应用列表失败', 'error'); } finally { setLoading(false); } }; const handleAddApp = () => { setIsEditing(false); setSelectedApp(null); setFormData({ app_name: '', app_type: 'native', app_info: { version_name: '', package_name: '', apk_url: '', web_url: '' }, icon_url: '', description: '', sort_order: 0, is_active: true }); setShowAppModal(true); }; const handleEditApp = (app) => { setIsEditing(true); setSelectedApp(app); // 解析 app_info let appInfo = { version_name: '', package_name: '', apk_url: '', web_url: '' }; if (typeof app.app_info === 'string') { try { appInfo = { ...appInfo, ...JSON.parse(app.app_info) }; } catch (e) { console.error('解析 app_info 失败:', e); } } else if (typeof app.app_info === 'object') { appInfo = { ...appInfo, ...app.app_info }; } setFormData({ app_name: app.app_name || '', app_type: app.app_type || 'native', app_info: appInfo, icon_url: app.icon_url || '', description: app.description || '', sort_order: app.sort_order || 0, is_active: app.is_active === 1 || app.is_active === true }); setShowAppModal(true); }; const handleDeleteApp = async () => { if (!deleteConfirmInfo) return; try { await apiClient.delete(buildApiUrl(API_ENDPOINTS.EXTERNAL_APPS.DELETE(deleteConfirmInfo.id))); showToast('删除成功', 'success'); fetchApps(); } catch (error) { console.error('删除失败:', error); const errorMsg = error.response?.data?.message || error.message || '删除失败'; showToast(errorMsg, 'error'); } finally { setDeleteConfirmInfo(null); } }; const handleFormChange = (field, value) => { if (field.startsWith('app_info.')) { const infoField = field.split('.')[1]; setFormData(prev => ({ ...prev, app_info: { ...prev.app_info, [infoField]: value } })); } else { setFormData(prev => ({ ...prev, [field]: value })); } }; const handleApkUpload = async (e) => { const file = e.target.files?.[0]; if (!file) return; if (!file.name.endsWith('.apk')) { showToast('请选择APK文件', 'error'); return; } setUploadingApk(true); try { const formDataObj = new FormData(); formDataObj.append('apk_file', file); const response = await apiClient.post( buildApiUrl(API_ENDPOINTS.EXTERNAL_APPS.UPLOAD_APK), formDataObj, { headers: { 'Content-Type': 'multipart/form-data' } } ); const apkData = response.data; // 自动填充表单 setFormData(prev => ({ ...prev, app_name: apkData.app_name || prev.app_name, app_info: { ...prev.app_info, version_name: apkData.version_name || '', package_name: apkData.package_name || '', apk_url: apkData.apk_url || '' } })); showToast('APK上传并解析成功', 'success'); } catch (error) { console.error('上传APK失败:', error); const errorMsg = error.response?.data?.message || error.message || '上传失败'; showToast(errorMsg, 'error'); } finally { setUploadingApk(false); e.target.value = ''; // 重置input } }; const handleIconUpload = async (e) => { const file = e.target.files?.[0]; if (!file) return; // 验证文件类型 const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp']; if (!allowedTypes.includes(file.type)) { showToast('请选择图片文件(JPG、PNG、GIF、WEBP)', 'error'); return; } setUploadingIcon(true); try { const formDataObj = new FormData(); formDataObj.append('icon_file', file); const response = await apiClient.post( buildApiUrl(API_ENDPOINTS.EXTERNAL_APPS.UPLOAD_ICON), formDataObj, { headers: { 'Content-Type': 'multipart/form-data' } } ); const iconData = response.data; // 更新表单中的图标URL setFormData(prev => ({ ...prev, icon_url: iconData.icon_url || '' })); showToast('图标上传成功', 'success'); } catch (error) { console.error('上传图标失败:', error); const errorMsg = error.response?.data?.message || error.message || '上传失败'; showToast(errorMsg, 'error'); } finally { setUploadingIcon(false); e.target.value = ''; // 重置input } }; const handleSubmit = async () => { // 验证必填字段 if (!formData.app_name) { showToast('请输入应用名称', 'error'); return; } // 根据应用类型构建 app_info let appInfoObj = {}; if (formData.app_type === 'native') { if (!formData.app_info.version_name || !formData.app_info.package_name || !formData.app_info.apk_url) { showToast('请填写完整的原生应用信息', 'error'); return; } appInfoObj = { version_name: formData.app_info.version_name, package_name: formData.app_info.package_name, apk_url: formData.app_info.apk_url }; } else { if (!formData.app_info.web_url) { showToast('请输入Web应用URL', 'error'); return; } appInfoObj = { version_name: formData.app_info.version_name || '1.0', web_url: formData.app_info.web_url }; } const submitData = { app_name: formData.app_name, app_type: formData.app_type, app_info: JSON.stringify(appInfoObj), icon_url: formData.icon_url, description: formData.description, sort_order: parseInt(formData.sort_order) || 0, is_active: formData.is_active }; try { if (isEditing && selectedApp) { await apiClient.put( buildApiUrl(API_ENDPOINTS.EXTERNAL_APPS.UPDATE(selectedApp.id)), submitData ); showToast('更新成功', 'success'); } else { await apiClient.post(buildApiUrl(API_ENDPOINTS.EXTERNAL_APPS.CREATE), submitData); showToast('创建成功', 'success'); } setShowAppModal(false); fetchApps(); } catch (error) { console.error('操作失败:', error); const errorMsg = error.response?.data?.message || error.message || '操作失败'; showToast(errorMsg, 'error'); } }; // 过滤应用列表 const filteredApps = apps.filter(app => { const matchesType = !filterAppType || app.app_type === filterAppType; const matchesSearch = !searchQuery || app.app_name.toLowerCase().includes(searchQuery.toLowerCase()) || (app.description && app.description.toLowerCase().includes(searchQuery.toLowerCase())); return matchesType && matchesSearch; }); // 获取应用信息 const getAppInfo = (app) => { if (typeof app.app_info === 'string') { try { return JSON.parse(app.app_info); } catch { return {}; } } return app.app_info || {}; }; const columns = [ { title: '应用名称', key: 'app_name', render: (app) => (
{app.icon_url && ( )} {app.app_name}
) }, { title: '类型', key: 'app_type', render: (app) => (
{app.app_type === 'native' ? ( <> 原生应用 ) : ( <> Web应用 )}
) }, { title: '版本', key: 'version', render: (app) => getAppInfo(app).version_name || '-' }, { title: '详细信息', key: 'info', render: (app) => { const appInfo = getAppInfo(app); return (
{app.app_type === 'native' ? (
包名: {appInfo.package_name || '-'}
{appInfo.apk_url && ( 下载APK )}
) : (
{appInfo.web_url && ( {appInfo.web_url} )}
)}
); } }, { title: '描述', dataIndex: 'description', key: 'description', render: (item) =>
{item.description || '-'}
}, { title: '排序', dataIndex: 'sort_order', key: 'sort_order' }, { title: '状态', key: 'status', render: (app) => ( handleToggleAppStatus(app)} size="medium" /> ) }, { title: '创建时间', dataIndex: 'created_at', key: 'created_at', render: (item) => new Date(item.created_at).toLocaleDateString() }, { title: '操作', key: 'action', fixed: 'right', width: '110px', render: (app) => (
) } ]; return (

外部应用管理

管理Android原生应用和Web应用

{/* 筛选和搜索 */}
setSearchQuery(e.target.value)} /> {searchQuery && ( )}
{/* 应用列表 */} {/* 添加/编辑应用模态框 */} setShowAppModal(false)} title={isEditing ? '编辑应用' : '添加应用'} size="medium" actions={ <> } > {/* 应用类型 */}
{/* 原生应用 - APK上传 */} {formData.app_type === 'native' && (
上传后自动解析包名、版本等信息
)} {/* 应用名称 */}
handleFormChange('app_name', e.target.value)} placeholder="请输入应用名称" required />
{/* 原生应用字段 */} {formData.app_type === 'native' && ( <>
handleFormChange('app_info.version_name', e.target.value)} placeholder="例如: 1.0.0" required />
handleFormChange('app_info.package_name', e.target.value)} placeholder="例如: com.example.app" required />
handleFormChange('app_info.apk_url', e.target.value)} placeholder="APK文件的下载URL" required />
)} {/* Web应用字段 */} {formData.app_type === 'web' && ( <>
handleFormChange('app_info.version_name', e.target.value)} placeholder="例如: 1.0(可选)" />
handleFormChange('app_info.web_url', e.target.value)} placeholder="https://example.com" required />
)} {/* 应用图标 */}
支持JPG、PNG、GIF、WEBP格式
{formData.icon_url && (
应用图标预览
)}
{/* 应用描述 */}