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:
parent
4309e9e645
commit
9a40769e0d
|
|
@ -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': 'バージョンを編集',
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue