fix(mobile): support Android system file picker

This commit is contained in:
key
2026-07-29 23:52:44 +08:00
parent 6a6b264f22
commit 5027bef32d
27 changed files with 680 additions and 130 deletions
+76 -12
View File
@@ -11,6 +11,8 @@ import type {
import { apiRequest, apiUrl, authHeaders, readTextPayload } from './api';
import { getSelectedPosition } from './auth';
import { fileSelectionFailure } from '../utils/file-selection';
import { withPlatformFileSelection } from './file-selection-lifecycle';
import { isAndroidAppRuntime } from './local-file-picker';
type RawKnowledgeSnippet = KnowledgeSnippet & {
title?: string;
@@ -20,7 +22,44 @@ type RawKnowledgeSnippet = KnowledgeSnippet & {
const mediaExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.mp4', '.mov', '.avi', '.mkv', '.webm', '.m4v'];
const videoExtensions = new Set(['.mp4', '.mov', '.avi', '.mkv', '.webm', '.m4v']);
export const chooseKnowledgeMedia = () =>
interface AppMediaFile {
tempFilePath?: string;
path?: string;
size?: number;
fileType?: 'image' | 'video';
}
interface AppChooseMediaResult {
tempFiles?: AppMediaFile[];
}
type UniWithChooseMedia = {
chooseMedia?: (options: {
count: number;
mediaType: Array<'image' | 'video'>;
sourceType: Array<'album'>;
success: (result: AppChooseMediaResult) => void;
fail: (error: unknown) => void;
}) => void;
};
const validateKnowledgeMedia = (
path: string,
name: string,
size: number,
declaredKind?: 'image' | 'video'
): KnowledgeMediaSelectedFile => {
if (!path) throw new Error('未获取到附件文件');
const extension = `.${name.split('.').pop()?.toLowerCase() || ''}`;
const kind = declaredKind || (videoExtensions.has(extension) ? 'video' : 'image');
const maxSize = kind === 'video' ? 100 * 1024 * 1024 : 10 * 1024 * 1024;
if (size > maxSize) {
throw new Error(kind === 'video' ? '视频不能超过 100MB' : '图片不能超过 10MB');
}
return { path, name, size, kind };
};
const chooseKnowledgeMediaWithFileInput = () =>
new Promise<KnowledgeMediaSelectedFile>((resolve, reject) => {
uni.chooseFile({
count: 1,
@@ -31,24 +70,49 @@ export const chooseKnowledgeMedia = () =>
const file = (tempFiles[0] || {}) as { path?: string; name?: string; size?: number };
const path = file.path || result.tempFilePaths?.[0] || '';
const name = file.name || path.split('/').pop() || '现场附件';
const extension = `.${name.split('.').pop()?.toLowerCase() || ''}`;
const kind = videoExtensions.has(extension) ? 'video' : 'image';
const size = Number(file.size || 0);
const maxSize = kind === 'video' ? 100 * 1024 * 1024 : 10 * 1024 * 1024;
if (!path) {
reject(new Error('未获取到附件文件'));
return;
try {
resolve(validateKnowledgeMedia(path, name, size));
} catch (error) {
reject(error);
}
if (size > maxSize) {
reject(new Error(kind === 'video' ? '视频不能超过 100MB' : '图片不能超过 10MB'));
return;
}
resolve({ path, name, size, kind });
},
fail: (error) => reject(fileSelectionFailure(error, '附件'))
});
});
const chooseKnowledgeMediaWithAppPicker = () =>
new Promise<KnowledgeMediaSelectedFile>((resolve, reject) => {
const chooseMedia = (uni as unknown as UniWithChooseMedia).chooseMedia;
if (!chooseMedia) {
reject(new Error('当前 App 版本无法选择图片或视频'));
return;
}
chooseMedia({
count: 1,
mediaType: ['image', 'video'],
sourceType: ['album'],
success: (result) => {
const file = result.tempFiles?.[0] || {};
const path = file.tempFilePath || file.path || '';
const name = path.split('/').pop() || (file.fileType === 'video' ? '现场视频.mp4' : '现场图片.jpg');
// App chooseMedia reports the selected file size in KiB.
const size = Math.max(0, Number(file.size || 0)) * 1024;
try {
resolve(validateKnowledgeMedia(path, name, size, file.fileType));
} catch (error) {
reject(error);
}
},
fail: (error) => reject(fileSelectionFailure(error, '附件'))
});
});
export const chooseKnowledgeMedia = () =>
isAndroidAppRuntime()
? withPlatformFileSelection(chooseKnowledgeMediaWithAppPicker)
: chooseKnowledgeMediaWithFileInput();
const normalizeSnippet = (snippet: RawKnowledgeSnippet): KnowledgeSnippet => ({
...snippet,
docTitle: snippet.docTitle || snippet.title || '',