fix(admin-web): remove double-upload on app version page

- Remove auto-parse on file select (was uploading 48MB twice, took 100+ sec)
- Backend /upload already parses APK internally, version fields are now optional
- Show file name + size after selection
- Show progress hint during upload
- Better error extraction from API response
- Clear error when new file is selected

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-03-06 10:11:07 -08:00
parent 4309e9e645
commit 9a40769e0d
2 changed files with 29 additions and 31 deletions

View File

@ -152,6 +152,7 @@ const translations: Record<Locale, Record<string, string>> = {
'app_version_upload_file': '选择 APK/IPA 文件',
'app_version_parsing': '解析中...',
'app_version_uploading': '上传中...',
'optional_auto_from_apk': '选填,自动从安装包提取',
'app_version_confirm_delete': '确定删除此版本?此操作不可撤销。',
'app_version_no_versions': '暂无版本记录',
'app_version_edit': '编辑版本',
@ -918,6 +919,7 @@ const translations: Record<Locale, Record<string, string>> = {
'app_version_upload_file': 'Select APK/IPA File',
'app_version_parsing': 'Parsing...',
'app_version_uploading': 'Uploading...',
'optional_auto_from_apk': 'optional, auto-extracted from package',
'app_version_confirm_delete': 'Delete this version? This action cannot be undone.',
'app_version_no_versions': 'No versions found',
'app_version_edit': 'Edit Version',
@ -1684,6 +1686,7 @@ const translations: Record<Locale, Record<string, string>> = {
'app_version_upload_file': 'APK/IPAファイルを選択',
'app_version_parsing': '解析中...',
'app_version_uploading': 'アップロード中...',
'optional_auto_from_apk': '省略可、パッケージから自動抽出',
'app_version_confirm_delete': 'このバージョンを削除しますか?この操作は元に戻せません。',
'app_version_no_versions': 'バージョンが見つかりません',
'app_version_edit': 'バージョンを編集',

View File

@ -299,40 +299,21 @@ const UploadModal: React.FC<{
const [changelog, setChangelog] = useState('');
const [minOsVersion, setMinOsVersion] = useState('');
const [isForceUpdate, setIsForceUpdate] = useState(false);
const [parsing, setParsing] = useState(false);
const [uploading, setUploading] = useState(false);
const [error, setError] = useState('');
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const f = e.target.files?.[0];
if (!f) return;
setFile(f);
// Auto-detect platform
setError('');
// Auto-detect platform from extension
if (f.name.endsWith('.apk')) setPlatform('ANDROID');
else if (f.name.endsWith('.ipa')) setPlatform('IOS');
// Auto-parse
setParsing(true);
try {
const formData = new FormData();
formData.append('file', f);
const info = await apiClient.post<{
versionCode?: number; versionName?: string; minSdkVersion?: string;
}>('/api/v1/admin/versions/parse', formData, {
timeout: 120000,
});
if (info?.versionName) setVersionName(info.versionName);
if (info?.versionCode) setBuildNumber(String(info.versionCode));
if (info?.minSdkVersion) setMinOsVersion(info.minSdkVersion);
} catch {
// Parsing failed, allow manual entry
}
setParsing(false);
};
const handleSubmit = async () => {
if (!file || !versionName) {
if (!file) {
setError(t('app_version_upload_file'));
return;
}
@ -343,10 +324,10 @@ const UploadModal: React.FC<{
formData.append('file', file);
formData.append('appType', appType);
formData.append('platform', platform);
formData.append('versionName', versionName);
formData.append('buildNumber', buildNumber || '1');
formData.append('changelog', changelog);
formData.append('minOsVersion', minOsVersion);
if (versionName) formData.append('versionName', versionName);
if (buildNumber) formData.append('buildNumber', buildNumber);
if (changelog) formData.append('changelog', changelog);
if (minOsVersion) formData.append('minOsVersion', minOsVersion);
formData.append('isForceUpdate', String(isForceUpdate));
await apiClient.post('/api/v1/admin/versions/upload', formData, {
@ -354,7 +335,7 @@ const UploadModal: React.FC<{
});
onSuccess();
} catch (err: any) {
setError(err?.message || 'Upload failed');
setError(err?.response?.data?.message || err?.message || 'Upload failed');
}
setUploading(false);
};
@ -370,7 +351,11 @@ const UploadModal: React.FC<{
onChange={handleFileChange}
style={{ ...inputStyle, padding: '8px 12px', height: 'auto' }}
/>
{parsing && <div style={{ font: 'var(--text-caption)', color: 'var(--color-info)', marginTop: 4 }}>{t('app_version_parsing')}</div>}
{file && (
<div style={{ font: 'var(--text-caption)', color: 'var(--color-text-secondary)', marginTop: 4 }}>
{file.name} ({(file.size / 1024 / 1024).toFixed(1)} MB)
</div>
)}
<label style={labelStyle}>{t('app_version_platform')}</label>
<div style={{ display: 'flex', gap: 12 }}>
@ -382,7 +367,12 @@ const UploadModal: React.FC<{
))}
</div>
<label style={labelStyle}>{t('app_version_version_name')} *</label>
<label style={labelStyle}>
{t('app_version_version_name')}
<span style={{ font: 'var(--text-caption)', color: 'var(--color-text-secondary)', fontWeight: 400, marginLeft: 6 }}>
({t('optional_auto_from_apk')})
</span>
</label>
<input style={inputStyle} value={versionName} onChange={e => setVersionName(e.target.value)} placeholder="1.0.0" />
<label style={labelStyle}>{t('app_version_build_number')}</label>
@ -405,12 +395,17 @@ const UploadModal: React.FC<{
</label>
{error && <div style={{ color: 'var(--color-error)', font: 'var(--text-body-sm)', marginTop: 8 }}>{error}</div>}
{uploading && (
<div style={{ font: 'var(--text-caption)', color: 'var(--color-info)', marginTop: 4 }}>
{t('app_version_uploading_hint') || '正在上传,大文件需要较长时间,请耐心等待...'}
</div>
)}
<div style={{ display: 'flex', gap: 12, marginTop: 20, justifyContent: 'flex-end' }}>
<button onClick={onClose} style={{ padding: '8px 20px', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-full)', background: 'transparent', cursor: 'pointer', font: 'var(--text-label-sm)' }}>
{t('cancel')}
</button>
<button onClick={handleSubmit} disabled={uploading || parsing} style={{ ...primaryBtn, opacity: uploading || parsing ? 0.6 : 1 }}>
<button onClick={handleSubmit} disabled={uploading} style={{ ...primaryBtn, opacity: uploading ? 0.6 : 1 }}>
{uploading ? t('app_version_uploading') : t('confirm')}
</button>
</div>