Initial commit: expo-popcore project
This commit is contained in:
29
lib/api/activities.ts
Normal file
29
lib/api/activities.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { getApiActivities } from '@repo/loomart-sdk';
|
||||
import { loomartClient } from './loomart-client';
|
||||
|
||||
export interface GetActivitiesParams {
|
||||
isActive?: string;
|
||||
}
|
||||
|
||||
export interface Activity {
|
||||
id: string;
|
||||
title: string;
|
||||
titleEn: string;
|
||||
desc: string;
|
||||
descEn: string;
|
||||
coverUrl: string;
|
||||
videoUrl: string;
|
||||
link: string;
|
||||
isActive: boolean;
|
||||
sortOrder: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export const getActivities = async (params: GetActivitiesParams = {}) => {
|
||||
const { data } = await getApiActivities({
|
||||
client: loomartClient,
|
||||
query: params,
|
||||
});
|
||||
return data?.data || [];
|
||||
};
|
||||
214
lib/api/balance.ts
Normal file
214
lib/api/balance.ts
Normal file
@@ -0,0 +1,214 @@
|
||||
import { router } from 'expo-router';
|
||||
import { Alert } from 'react-native';
|
||||
import { authClient } from '../auth/client';
|
||||
|
||||
export interface UserBalance {
|
||||
remainingTokenBalance: number;
|
||||
totalTokenBalance: number;
|
||||
usedTokenBalance: number;
|
||||
}
|
||||
|
||||
export interface BalanceResponse {
|
||||
success: boolean;
|
||||
data: UserBalance;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export interface TokenUsageRequest {
|
||||
price: number;
|
||||
name: string;
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface TokenUsageResponse {
|
||||
success: boolean;
|
||||
data?: {
|
||||
identifier: string;
|
||||
remainingBalance: number;
|
||||
};
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export interface BalanceCheckResult {
|
||||
hasEnough: boolean;
|
||||
currentBalance: number;
|
||||
isLoading: boolean;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户余额
|
||||
* 从 metered 类型的订阅中获取 creditBalance
|
||||
*/
|
||||
export async function getUserBalance(): Promise<BalanceResponse> {
|
||||
try {
|
||||
const { data, error } = await authClient.subscription.list({});
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
success: false,
|
||||
data: {
|
||||
remainingTokenBalance: 0,
|
||||
totalTokenBalance: 0,
|
||||
usedTokenBalance: 0,
|
||||
},
|
||||
message: error.message || '获取余额失败',
|
||||
};
|
||||
}
|
||||
|
||||
// 找到 metered 类型的订阅
|
||||
const meteredSubscriptions = data?.filter((sub: any) => sub.type === 'metered') || [];
|
||||
|
||||
if (meteredSubscriptions.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
data: {
|
||||
remainingTokenBalance: 0,
|
||||
totalTokenBalance: 0,
|
||||
usedTokenBalance: 0,
|
||||
},
|
||||
message: '未找到计费订阅',
|
||||
};
|
||||
}
|
||||
|
||||
const creditBalance = (meteredSubscriptions[0] as any)?.creditBalance || {};
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
remainingTokenBalance: creditBalance.remainingTokenBalance || 0,
|
||||
totalTokenBalance: creditBalance.totalTokenBalance || 0,
|
||||
usedTokenBalance: creditBalance.usedTokenBalance || 0,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Failed to get user balance:', error);
|
||||
return {
|
||||
success: false,
|
||||
data: {
|
||||
remainingTokenBalance: 0,
|
||||
totalTokenBalance: 0,
|
||||
usedTokenBalance: 0,
|
||||
},
|
||||
message: '网络错误,请稍后重试',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户余额是否足够
|
||||
*/
|
||||
export async function checkTokenBalance(tokens: number): Promise<BalanceCheckResult> {
|
||||
const balanceResponse = await getUserBalance();
|
||||
|
||||
if (!balanceResponse.success) {
|
||||
return {
|
||||
hasEnough: false,
|
||||
currentBalance: 0,
|
||||
isLoading: false,
|
||||
message: balanceResponse.message || '无法获取余额',
|
||||
};
|
||||
}
|
||||
|
||||
const currentBalance = balanceResponse.data.remainingTokenBalance;
|
||||
const hasEnough = currentBalance >= tokens;
|
||||
|
||||
return {
|
||||
hasEnough,
|
||||
currentBalance,
|
||||
isLoading: false,
|
||||
message: hasEnough
|
||||
? undefined
|
||||
: `余额不足,当前: ${currentBalance.toLocaleString()}, 需要: ${tokens.toLocaleString()}`,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 扣除 Token 使用量
|
||||
* 参考 usePricing 中的 recordTokenUsage 方法
|
||||
*/
|
||||
export async function recordTokenUsage(
|
||||
request: TokenUsageRequest
|
||||
): Promise<TokenUsageResponse> {
|
||||
const { price, name, metadata } = request;
|
||||
|
||||
// price 的单位就是 token
|
||||
const tokens = Math.ceil(price);
|
||||
|
||||
try {
|
||||
// 1. 检查余额是否足够
|
||||
const balanceCheck = await checkTokenBalance(tokens);
|
||||
|
||||
if (balanceCheck.isLoading) {
|
||||
return {
|
||||
success: false,
|
||||
message: '正在检查余额...',
|
||||
};
|
||||
}
|
||||
|
||||
if (!balanceCheck.hasEnough) {
|
||||
// 余额不足,提示用户并引导充值
|
||||
Alert.alert(
|
||||
'余额不足',
|
||||
`当前余额: ${balanceCheck.currentBalance}\n需要费用: ${tokens}\n请先充值`,
|
||||
[
|
||||
{ text: '取消', style: 'cancel' },
|
||||
{
|
||||
text: '去充值',
|
||||
onPress: () => {
|
||||
router.push('/exchange');
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
return {
|
||||
success: false,
|
||||
message: balanceCheck.message || '余额不足',
|
||||
};
|
||||
}
|
||||
|
||||
// 2. 余额充足,执行消费操作
|
||||
const { data, error } = await authClient.subscription.meterEvent({
|
||||
event_name: 'token_usage',
|
||||
payload: {
|
||||
value: tokens.toString(),
|
||||
...(metadata && { metadata }),
|
||||
} as any,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
Alert.alert('扣费失败', error.message || '请稍后重试');
|
||||
return {
|
||||
success: false,
|
||||
message: error.message || '扣费失败',
|
||||
};
|
||||
}
|
||||
|
||||
// 3. 扣费成功,返回结果
|
||||
const balanceAfter = await getUserBalance();
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
identifier: data?.identifier || '',
|
||||
remainingBalance: balanceAfter.data.remainingTokenBalance,
|
||||
},
|
||||
message: '扣费成功',
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Failed to record token usage:', error);
|
||||
const errorMessage = error instanceof Error ? error.message : '扣费失败,请稍后重试';
|
||||
Alert.alert('错误', errorMessage);
|
||||
return {
|
||||
success: false,
|
||||
message: errorMessage,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转到充值页面
|
||||
*/
|
||||
export function redirectToPricePage() {
|
||||
router.push('/exchange');
|
||||
}
|
||||
9
lib/api/categories-with-children.ts
Normal file
9
lib/api/categories-with-children.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { getApiCategoriesWithChildrenV2 } from '@repo/loomart-sdk';
|
||||
import { loomartClient } from './loomart-client';
|
||||
|
||||
export const categoriesWithChildren = async () => {
|
||||
const { data } = await getApiCategoriesWithChildrenV2({
|
||||
client: loomartClient,
|
||||
});
|
||||
return data;
|
||||
};
|
||||
15
lib/api/client.ts
Normal file
15
lib/api/client.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
type UnauthorizedListener = () => void;
|
||||
const unauthorizedListeners: UnauthorizedListener[] = [];
|
||||
|
||||
export const authEvents = {
|
||||
onUnauthorized(listener: UnauthorizedListener) {
|
||||
unauthorizedListeners.push(listener);
|
||||
return () => {
|
||||
const index = unauthorizedListeners.indexOf(listener);
|
||||
if (index > -1) unauthorizedListeners.splice(index, 1);
|
||||
};
|
||||
},
|
||||
emitUnauthorized() {
|
||||
unauthorizedListeners.forEach(listener => listener());
|
||||
}
|
||||
};
|
||||
34
lib/api/loomart-client.ts
Normal file
34
lib/api/loomart-client.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { createClient, createConfig } from '@repo/loomart-sdk';
|
||||
import { storage } from '../storage';
|
||||
import { authEvents } from './client';
|
||||
|
||||
export const getAuthToken = async () =>
|
||||
(await storage.getItem('bestaibest.better-auth.session_token')) || '';
|
||||
|
||||
export const loomartClient = createClient(
|
||||
createConfig({
|
||||
baseUrl: 'https://api.mixvideo.bowong.cc'
|
||||
})
|
||||
);
|
||||
|
||||
loomartClient.interceptors.request.use(async (request) => {
|
||||
const Authorization = `Bearer ${await getAuthToken()}`
|
||||
request.headers.append(`Authorization`, Authorization)
|
||||
|
||||
if (__DEV__) {
|
||||
console.log('[API]', request.method, request.url);
|
||||
}
|
||||
|
||||
return request;
|
||||
})
|
||||
|
||||
loomartClient.interceptors.response.use(async (response) => {
|
||||
if (__DEV__) {
|
||||
console.log('[API]', response.status, response.url);
|
||||
}
|
||||
|
||||
if (response.status === 401) {
|
||||
authEvents.emitUnauthorized();
|
||||
}
|
||||
return response;
|
||||
});
|
||||
53
lib/api/pricing.ts
Normal file
53
lib/api/pricing.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { getApiStripePlans } from '@repo/loomart-sdk';
|
||||
import { loomartClient } from './loomart-client';
|
||||
|
||||
export interface CreditPlan {
|
||||
name: string;
|
||||
recurring?: {
|
||||
interval: string;
|
||||
};
|
||||
credits?: number;
|
||||
price_id?: string;
|
||||
product_id?: string;
|
||||
metered_price_id?: string;
|
||||
}
|
||||
|
||||
export interface StripePricingTableResponse {
|
||||
pricing_table_items: CreditPlan[];
|
||||
}
|
||||
|
||||
export const getStripePlans = async () => {
|
||||
try {
|
||||
const { data } = await getApiStripePlans({
|
||||
client: loomartClient,
|
||||
});
|
||||
|
||||
// 后端返回 { success: true, data: pricingTable }
|
||||
// SDK 包装为 { data: { success: true, data: pricingTable } }
|
||||
// 需要解包嵌套的 data
|
||||
if (data && typeof data === 'object' && 'success' in data && data.success && 'data' in data) {
|
||||
return {
|
||||
success: true,
|
||||
data: data.data,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
message: 'Invalid response format',
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch Stripe plans:', error);
|
||||
return {
|
||||
success: false,
|
||||
message: error instanceof Error ? error.message : 'Unknown error',
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const getPlanNames = (pricingData?: StripePricingTableResponse): string[] => {
|
||||
if (!pricingData?.pricing_table_items) return [];
|
||||
return pricingData.pricing_table_items
|
||||
.filter((item) => item.recurring?.interval === 'month')
|
||||
.map((item) => item.name || 'basic');
|
||||
};
|
||||
10
lib/api/tags.ts
Normal file
10
lib/api/tags.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { getApiTagsByNameByName } from '@repo/loomart-sdk';
|
||||
import { loomartClient } from './loomart-client';
|
||||
|
||||
export const getTagByName = async (name: string) => {
|
||||
const { data } = await getApiTagsByNameByName({
|
||||
client: loomartClient,
|
||||
path: { name },
|
||||
});
|
||||
return data;
|
||||
};
|
||||
33
lib/api/template-generations.ts
Normal file
33
lib/api/template-generations.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { getApiTemplateGenerations } from '@repo/loomart-sdk';
|
||||
import { loomartClient } from './loomart-client';
|
||||
|
||||
export interface GetTemplateGenerationsParams {
|
||||
page?: string;
|
||||
limit?: string;
|
||||
type?: 'VIDEO' | 'IMAGE' | 'TEXT';
|
||||
}
|
||||
|
||||
export interface TemplateGeneration {
|
||||
id: string;
|
||||
userId: string;
|
||||
templateId: string;
|
||||
type: 'TEXT' | 'IMAGE' | 'VIDEO';
|
||||
status: string;
|
||||
resultUrl: string[];
|
||||
originalUrl?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
template?: {
|
||||
id: string;
|
||||
title: string;
|
||||
titleEn: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const getTemplateGenerations = async (params: GetTemplateGenerationsParams = {}) => {
|
||||
const { data } = await getApiTemplateGenerations({
|
||||
client: loomartClient,
|
||||
query: params,
|
||||
});
|
||||
return data;
|
||||
};
|
||||
93
lib/api/template-runs.ts
Normal file
93
lib/api/template-runs.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { postApiTemplatesByIdRun, getApiTemplateGenerationsById } from '@repo/loomart-sdk';
|
||||
import { loomartClient } from './loomart-client';
|
||||
import { RunTemplateData } from '../types/template-run';
|
||||
|
||||
export const runTemplate = async (
|
||||
templateId: string,
|
||||
data: RunTemplateData,
|
||||
identifier: string
|
||||
) => {
|
||||
if (!identifier) {
|
||||
throw new Error('支付凭证缺失,无法创建生成任务');
|
||||
}
|
||||
|
||||
const result = await postApiTemplatesByIdRun({
|
||||
client: loomartClient,
|
||||
path: { id: templateId },
|
||||
body: { data, identifier },
|
||||
});
|
||||
return result.data;
|
||||
};
|
||||
|
||||
export const getTemplateGeneration = async (generationId: string) => {
|
||||
const { data } = await getApiTemplateGenerationsById({
|
||||
client: loomartClient,
|
||||
path: { id: generationId },
|
||||
});
|
||||
return data;
|
||||
};
|
||||
|
||||
export function pollTemplateGeneration(
|
||||
generationId: string,
|
||||
onComplete: (result: any) => void,
|
||||
onError: (error: Error) => void,
|
||||
maxAttempts: number = 100,
|
||||
intervalMs: number = 3000
|
||||
): () => void {
|
||||
let attempts = 0;
|
||||
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
||||
let isCancelled = false;
|
||||
|
||||
const poll = async () => {
|
||||
if (isCancelled) return;
|
||||
|
||||
try {
|
||||
attempts++;
|
||||
|
||||
if (attempts > maxAttempts) {
|
||||
throw new Error('轮询超时,请稍后重试');
|
||||
}
|
||||
|
||||
const response = await getTemplateGeneration(generationId);
|
||||
if (!response) {
|
||||
throw new Error('请稍后重试');
|
||||
}
|
||||
const generation = response.data;
|
||||
|
||||
if (isCancelled) return;
|
||||
|
||||
// 检查是否完成
|
||||
if (generation.status === 'completed') {
|
||||
onComplete(generation);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否失败
|
||||
if (generation.status === 'failed') {
|
||||
throw new Error('任务执行失败');
|
||||
}
|
||||
|
||||
// 继续轮询
|
||||
if (!isCancelled) {
|
||||
timeoutId = setTimeout(poll, intervalMs);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
if (!isCancelled) {
|
||||
onError(error as Error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 开始轮询
|
||||
timeoutId = setTimeout(poll, intervalMs);
|
||||
|
||||
// 返回取消函数
|
||||
return () => {
|
||||
isCancelled = true;
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
32
lib/api/templates.ts
Normal file
32
lib/api/templates.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { getApiTemplates, getApiTemplatesById } from '@repo/loomart-sdk';
|
||||
import { loomartClient } from './loomart-client';
|
||||
|
||||
export interface GetTemplatesParams {
|
||||
page?: number;
|
||||
size?: number;
|
||||
search?: string;
|
||||
categoryId?: string;
|
||||
status?: 'AUDITING' | 'RELEASE' | 'EDITING';
|
||||
}
|
||||
|
||||
export const getTemplates = async (params: GetTemplatesParams = {}) => {
|
||||
const { data } = await getApiTemplates({
|
||||
client: loomartClient,
|
||||
query: {
|
||||
page: params.page || 1,
|
||||
size: params.size || 10,
|
||||
search: params.search,
|
||||
categoryId: params.categoryId,
|
||||
status: params.status,
|
||||
},
|
||||
});
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getTemplateById = async (id: string) => {
|
||||
const { data } = await getApiTemplatesById({
|
||||
client: loomartClient,
|
||||
path: { id },
|
||||
});
|
||||
return data;
|
||||
};
|
||||
86
lib/api/transactions.ts
Normal file
86
lib/api/transactions.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { authClient } from '../auth/client';
|
||||
|
||||
export type TransactionKind = 'obtained' | 'consumed';
|
||||
|
||||
export interface Transaction {
|
||||
id: string;
|
||||
title: string;
|
||||
happenedAt: string;
|
||||
amount: number;
|
||||
kind: TransactionKind;
|
||||
}
|
||||
|
||||
export interface TransactionsResponse {
|
||||
success: boolean;
|
||||
data: Transaction[];
|
||||
message?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户积分交易记录
|
||||
* 通过 subscription.meterEvent 获取消费记录,通过订阅历史获取充值记录
|
||||
*/
|
||||
export async function getUserTransactions(): Promise<TransactionsResponse> {
|
||||
try {
|
||||
const { data: subscriptions, error } = await authClient.subscription.list({});
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
success: false,
|
||||
data: [],
|
||||
message: error.message || '获取交易记录失败',
|
||||
};
|
||||
}
|
||||
|
||||
const transactions: Transaction[] = [];
|
||||
|
||||
// 从订阅中提取 meter events (消费记录)
|
||||
subscriptions?.forEach((sub: any) => {
|
||||
if (sub.type === 'metered' && sub.meterEvents) {
|
||||
sub.meterEvents.forEach((event: any) => {
|
||||
const value = parseInt(event.payload?.value || '0');
|
||||
if (value > 0) {
|
||||
transactions.push({
|
||||
id: event.id || `event-${Date.now()}-${Math.random()}`,
|
||||
title: event.event_name === 'token_usage' ? 'Token消费' : '积分消费',
|
||||
happenedAt: event.createdAt || new Date().toISOString(),
|
||||
amount: -value,
|
||||
kind: 'consumed',
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 提取充值记录 (从订阅创建或续费事件)
|
||||
if (sub.createdAt) {
|
||||
const grantToken = parseInt(sub.plan?.metadata?.grant_token || '0');
|
||||
if (grantToken > 0) {
|
||||
transactions.push({
|
||||
id: `sub-${sub.id}`,
|
||||
title: `订阅充值 - ${sub.plan?.name || '未知套餐'}`,
|
||||
happenedAt: sub.createdAt,
|
||||
amount: grantToken,
|
||||
kind: 'obtained',
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 按时间倒序排序
|
||||
transactions.sort((a, b) =>
|
||||
new Date(b.happenedAt).getTime() - new Date(a.happenedAt).getTime()
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: transactions,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Failed to get user transactions:', error);
|
||||
return {
|
||||
success: false,
|
||||
data: [],
|
||||
message: '网络错误,请稍后重试',
|
||||
};
|
||||
}
|
||||
}
|
||||
77
lib/api/upload.ts
Normal file
77
lib/api/upload.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { postApiFileUploadS3 } from '@repo/loomart-sdk';
|
||||
import { loomartClient } from './loomart-client';
|
||||
|
||||
export interface UploadResponse {
|
||||
success: boolean;
|
||||
data?: {
|
||||
url: string;
|
||||
filename: string;
|
||||
size: number;
|
||||
mimeType: string;
|
||||
};
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export async function uploadFile(uri: string, type: 'image' | 'video'): Promise<UploadResponse> {
|
||||
try {
|
||||
if (uri.startsWith('https://') || uri.startsWith('http://')) {
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
url: uri,
|
||||
filename: uri.split('/').pop() || 'file',
|
||||
size: 0,
|
||||
mimeType: type === 'image' ? 'image/jpeg' : 'video/mp4',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const response = await fetch(uri);
|
||||
const blob = await response.blob();
|
||||
let mimeType = blob.type || (type === 'image' ? 'image/jpeg' : 'video/mp4');
|
||||
|
||||
const extensions: Record<string, string> = {
|
||||
'image/jpeg': 'jpg', 'image/png': 'png', 'image/gif': 'gif', 'image/webp': 'webp',
|
||||
'video/mp4': 'mp4', 'video/webm': 'webm', 'video/avi': 'avi', 'video/quicktime': 'mov',
|
||||
};
|
||||
const extension = extensions[mimeType] || (type === 'image' ? 'jpg' : 'mp4');
|
||||
const filename = `${type}_${Date.now()}.${extension}`;
|
||||
const file = new File([blob], filename, { type: mimeType });
|
||||
|
||||
const result = await postApiFileUploadS3({
|
||||
client: loomartClient,
|
||||
body: { file },
|
||||
});
|
||||
|
||||
const data = result.data as any;
|
||||
if (data?.status === true && data?.data) {
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
url: data.data,
|
||||
filename,
|
||||
size: file.size,
|
||||
mimeType,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(data?.error?.message || data?.msg || 'Upload failed');
|
||||
} catch (error) {
|
||||
console.error('Failed to upload file:', error);
|
||||
return {
|
||||
success: false,
|
||||
message: error instanceof Error ? error.message : '上传失败,请稍后重试',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量上传文件
|
||||
*/
|
||||
export async function uploadFiles(
|
||||
files: Array<{ uri: string; type: 'image' | 'video' }>
|
||||
): Promise<UploadResponse[]> {
|
||||
const uploadPromises = files.map(file => uploadFile(file.uri, file.type));
|
||||
return Promise.all(uploadPromises);
|
||||
}
|
||||
Reference in New Issue
Block a user