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 './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 fetchApps = async () => { setLoading(true); try { const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.EXTERNAL_APPS.LIST)); setApps(response.data.apps || []); } 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 (e) => { e.preventDefault(); // 验证必填字段 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 || {}; }; if (loading) { return (
加载中...
); } return (

外部应用管理

管理Android原生应用和Web应用

{/* 筛选和搜索 */}
setSearchQuery(e.target.value)} /> {searchQuery && ( )}
{/* 应用列表 */}
{filteredApps.length === 0 ? ( ) : ( filteredApps.map(app => { const appInfo = getAppInfo(app); return ( ); }) )}
应用名称 类型 版本 详细信息 描述 排序 状态 创建时间 操作
暂无数据
{app.icon_url && ( )} {app.app_name}
{app.app_type === 'native' ? ( <> 原生应用 ) : ( <> Web应用 )}
{appInfo.version_name || '-'} {app.app_type === 'native' ? (
包名: {appInfo.package_name || '-'}
{appInfo.apk_url && ( 下载APK )}
) : (
{appInfo.web_url && ( {appInfo.web_url} )}
)}
{app.description || '-'}
{app.sort_order} {app.is_active ? '启用' : '禁用'} {new Date(app.created_at).toLocaleDateString()}
{/* 添加/编辑应用模态框 */} {showAppModal && (
setShowAppModal(false)}>
e.stopPropagation()}>

{isEditing ? '编辑应用' : '添加应用'}

{/* 应用类型 */}
{/* 原生应用 - 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 && (
应用图标预览
)}
{/* 应用描述 */}