Files
qiming/qiming-xagi-frontend-templates/packages/vue3-vite/src/lib/services.ts
2026-06-01 13:43:09 +08:00

165 lines
3.9 KiB
TypeScript

import {
fetchEventSource,
type EventSourceMessage,
} from '@microsoft/fetch-event-source';
import { onMounted, ref, type Ref, watch, type WatchSource } from 'vue';
import { api, extractApiData, type ApiResponse } from './api';
export interface User {
id: number;
username: string;
email: string;
avatar?: string;
createdAt: string;
}
export interface ListParams {
page: number;
pageSize: number;
keyword?: string;
}
export interface ListResult<T> {
list: T[];
total: number;
page: number;
pageSize: number;
}
export const userApi = {
async getUserInfo(): Promise<User> {
const response = await api.get<ApiResponse<User>>('/user/info');
return extractApiData<User>(response);
},
async updateUserInfo(data: Partial<User>): Promise<User> {
const response = await api.put<ApiResponse<User>>('/user/info', data);
return extractApiData<User>(response);
},
};
export const exampleApi = {
async getList(params: ListParams): Promise<ListResult<unknown>> {
const response = await api.get<ApiResponse<ListResult<unknown>>>(
'/example/list',
{ params }
);
return extractApiData<ListResult<unknown>>(response);
},
async create(data: unknown): Promise<unknown> {
const response = await api.post<ApiResponse<unknown>>(
'/example/create',
data
);
return extractApiData(response);
},
async update(id: number, data: unknown): Promise<unknown> {
const response = await api.put<ApiResponse<unknown>>(
`/example/update/${id}`,
data
);
return extractApiData(response);
},
async delete(id: number): Promise<void> {
await api.delete(`/example/delete/${id}`);
},
async getDetail(id: number): Promise<unknown> {
const response = await api.get<ApiResponse<unknown>>(
`/example/detail/${id}`
);
return extractApiData(response);
},
};
interface UseApiOptions {
immediate?: boolean;
watchSources?: WatchSource[];
}
/**
* Vue-friendly async state helper aligned with react-vite semantics:
* - loading / error state
* - explicit execute()
* - refetch alias
*/
export function useApi<T>(
apiCall: () => Promise<T>,
options: UseApiOptions = {}
): {
data: Ref<T | null>;
loading: Ref<boolean>;
error: Ref<Error | null>;
execute: () => Promise<void>;
refetch: () => Promise<void>;
} {
const data = ref<T | null>(null) as Ref<T | null>;
const loading = ref(false);
const error = ref<Error | null>(null);
const execute = async (): Promise<void> => {
loading.value = true;
error.value = null;
try {
data.value = await apiCall();
} catch (err) {
error.value = err instanceof Error ? err : new Error('Unknown error');
} finally {
loading.value = false;
}
};
if (options.immediate ?? true) {
onMounted(execute);
}
if (options.watchSources && options.watchSources.length > 0) {
watch(options.watchSources, () => {
void execute();
});
}
return {
data,
loading,
error,
execute,
refetch: execute,
};
}
export type StreamResponse<T = unknown> = T;
export async function streamRequest<T>(
url: string,
payload: unknown,
onData: (response: StreamResponse<T>) => void,
onError?: (error: Error) => void
): Promise<void> {
try {
await fetchEventSource(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
onmessage(msg: EventSourceMessage) {
if (msg.event === 'FatalError') {
throw new Error(msg.data);
}
try {
onData(JSON.parse(msg.data) as StreamResponse<T>);
} catch {
console.warn('Failed to parse stream chunk:', msg.data);
}
},
onerror(err) {
if (onError) {
onError(err as Error);
}
throw err;
},
});
} catch (err) {
if (onError) {
onError(err as Error);
}
}
}