feat: complete V1 phases 0-7
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
import type { ApiErrorResponse, ApiSuccessResponse } from '@lehuo/contracts';
|
||||
|
||||
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL as string | undefined;
|
||||
|
||||
export class ApiClientError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
readonly code: string,
|
||||
readonly requestId: string | null,
|
||||
) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function apiRequest<T>(path: string, init: RequestInit = {}): Promise<T> {
|
||||
if (!API_BASE_URL) {
|
||||
throw new ApiClientError('管理后台尚未配置服务地址', 'CONFIGURATION_ERROR', null);
|
||||
}
|
||||
let response: Response;
|
||||
try {
|
||||
response = await fetch(new URL(path, API_BASE_URL), {
|
||||
...init,
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
...init.headers,
|
||||
},
|
||||
});
|
||||
} catch {
|
||||
throw new ApiClientError('网络连接失败,请检查网络后重试', 'NETWORK_ERROR', null);
|
||||
}
|
||||
const payload = (await response.json()) as ApiSuccessResponse<T> | ApiErrorResponse;
|
||||
if (!response.ok || !payload.success) {
|
||||
const error = payload as ApiErrorResponse;
|
||||
throw new ApiClientError(
|
||||
error.error?.message || '请求失败,请稍后重试',
|
||||
error.error?.code || 'UNKNOWN_ERROR',
|
||||
error.requestId || response.headers.get('x-request-id'),
|
||||
);
|
||||
}
|
||||
return payload.data;
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
import type {
|
||||
AcceptServiceApplicationRequest,
|
||||
AcceptServiceApplicationResponse,
|
||||
AssigneeOption,
|
||||
BeneficiaryDetailResponse,
|
||||
BeneficiaryTagOption,
|
||||
ConfirmUploadResponse,
|
||||
CompleteServiceCaseRequest,
|
||||
CompleteServiceCaseResponse,
|
||||
CreateServiceRecordRequest,
|
||||
CreateUploadIntentRequest,
|
||||
CreateUploadIntentResponse,
|
||||
DownloadUrlResponse,
|
||||
PaginatedBeneficiariesResponse,
|
||||
PaginatedServiceCasesResponse,
|
||||
ReplaceCaseAssigneesRequest,
|
||||
ServiceCaseDetailResponse,
|
||||
ServiceCategory,
|
||||
ServiceCaseStatus,
|
||||
ServiceRecordResponse,
|
||||
TransitionServiceApplicationRequest,
|
||||
UpdateBeneficiaryRequest,
|
||||
UpdateServiceRecordRequest,
|
||||
} from '@lehuo/contracts';
|
||||
|
||||
import { apiRequest } from './api-client';
|
||||
|
||||
function queryString(values: Record<string, string | number | boolean | undefined>): string {
|
||||
const params = new URLSearchParams();
|
||||
Object.entries(values).forEach(([key, value]) => {
|
||||
if (value !== undefined && value !== '') params.set(key, String(value));
|
||||
});
|
||||
return params.toString();
|
||||
}
|
||||
|
||||
export function transitionServiceApplication(
|
||||
publicId: string,
|
||||
input: TransitionServiceApplicationRequest,
|
||||
): Promise<{ status: string }> {
|
||||
return apiRequest(
|
||||
`/api/v1/admin/service-applications/${encodeURIComponent(publicId)}/transition`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify(input),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function acceptServiceApplication(
|
||||
publicId: string,
|
||||
input: AcceptServiceApplicationRequest,
|
||||
): Promise<AcceptServiceApplicationResponse> {
|
||||
return apiRequest(`/api/v1/admin/service-applications/${encodeURIComponent(publicId)}/accept`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchBeneficiaryTags(): Promise<BeneficiaryTagOption[]> {
|
||||
return apiRequest('/api/v1/admin/beneficiaries/tags');
|
||||
}
|
||||
|
||||
export function fetchBeneficiaries(query: {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
primaryCategory?: ServiceCategory;
|
||||
status?: string;
|
||||
keyCareFlag?: boolean;
|
||||
}): Promise<PaginatedBeneficiariesResponse> {
|
||||
return apiRequest(`/api/v1/admin/beneficiaries?${queryString(query)}`);
|
||||
}
|
||||
|
||||
export function fetchBeneficiary(publicId: string): Promise<BeneficiaryDetailResponse> {
|
||||
return apiRequest(`/api/v1/admin/beneficiaries/${encodeURIComponent(publicId)}`);
|
||||
}
|
||||
|
||||
export function updateBeneficiary(
|
||||
publicId: string,
|
||||
input: UpdateBeneficiaryRequest,
|
||||
): Promise<BeneficiaryDetailResponse> {
|
||||
return apiRequest(`/api/v1/admin/beneficiaries/${encodeURIComponent(publicId)}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchAssigneeOptions(): Promise<AssigneeOption[]> {
|
||||
return apiRequest('/api/v1/admin/service-cases/assignee-options');
|
||||
}
|
||||
|
||||
export function fetchServiceCases(query: {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
status?: ServiceCaseStatus;
|
||||
category?: ServiceCategory;
|
||||
caseNo?: string;
|
||||
}): Promise<PaginatedServiceCasesResponse> {
|
||||
return apiRequest(`/api/v1/admin/service-cases?${queryString(query)}`);
|
||||
}
|
||||
|
||||
export function fetchServiceCase(publicId: string): Promise<ServiceCaseDetailResponse> {
|
||||
return apiRequest(`/api/v1/admin/service-cases/${encodeURIComponent(publicId)}`);
|
||||
}
|
||||
|
||||
export function startServiceCase(publicId: string): Promise<ServiceCaseDetailResponse> {
|
||||
return apiRequest(`/api/v1/admin/service-cases/${encodeURIComponent(publicId)}/start`, {
|
||||
method: 'POST',
|
||||
body: '{}',
|
||||
});
|
||||
}
|
||||
|
||||
export function completeServiceCase(
|
||||
publicId: string,
|
||||
input: CompleteServiceCaseRequest,
|
||||
): Promise<CompleteServiceCaseResponse> {
|
||||
return apiRequest(`/api/v1/admin/service-cases/${encodeURIComponent(publicId)}/complete`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
}
|
||||
|
||||
export function replaceCaseAssignees(
|
||||
publicId: string,
|
||||
input: ReplaceCaseAssigneesRequest,
|
||||
): Promise<ServiceCaseDetailResponse> {
|
||||
return apiRequest(`/api/v1/admin/service-cases/${encodeURIComponent(publicId)}/assignees`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
}
|
||||
|
||||
export function createServiceRecord(
|
||||
publicId: string,
|
||||
input: CreateServiceRecordRequest,
|
||||
): Promise<ServiceRecordResponse> {
|
||||
return apiRequest(`/api/v1/admin/service-cases/${encodeURIComponent(publicId)}/records`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
}
|
||||
|
||||
export function updateServiceRecord(
|
||||
casePublicId: string,
|
||||
recordPublicId: string,
|
||||
input: UpdateServiceRecordRequest,
|
||||
): Promise<ServiceRecordResponse> {
|
||||
return apiRequest(
|
||||
`/api/v1/admin/service-cases/${encodeURIComponent(casePublicId)}/records/${encodeURIComponent(recordPublicId)}`,
|
||||
{ method: 'PATCH', body: JSON.stringify(input) },
|
||||
);
|
||||
}
|
||||
|
||||
export function createUploadIntent(
|
||||
casePublicId: string,
|
||||
input: CreateUploadIntentRequest,
|
||||
): Promise<CreateUploadIntentResponse> {
|
||||
return apiRequest(
|
||||
`/api/v1/service-cases/${encodeURIComponent(casePublicId)}/files/upload-intents`,
|
||||
{ method: 'POST', body: JSON.stringify(input) },
|
||||
);
|
||||
}
|
||||
|
||||
export function confirmUpload(publicId: string): Promise<ConfirmUploadResponse> {
|
||||
return apiRequest(`/api/v1/files/${encodeURIComponent(publicId)}/confirm`, {
|
||||
method: 'POST',
|
||||
body: '{}',
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchDownloadUrl(publicId: string): Promise<DownloadUrlResponse> {
|
||||
return apiRequest(`/api/v1/files/${encodeURIComponent(publicId)}/download-url`);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import type {
|
||||
ContentPostDetailResponse,
|
||||
CreateContentPostRequest,
|
||||
PaginatedContentPostsResponse,
|
||||
PaginatedNotificationDeliveriesResponse,
|
||||
TransitionContentPostRequest,
|
||||
} from '@lehuo/contracts';
|
||||
import { apiRequest } from './api-client';
|
||||
const query = (v: Record<string, string | number | undefined>) => {
|
||||
const p = new URLSearchParams();
|
||||
Object.entries(v).forEach(([k, x]) => x !== undefined && p.set(k, String(x)));
|
||||
return p.toString();
|
||||
};
|
||||
export const fetchContent = (v: {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
status?: string;
|
||||
type?: string;
|
||||
}) => apiRequest<PaginatedContentPostsResponse>(`/api/v1/admin/content?${query(v)}`);
|
||||
export const createContent = (input: CreateContentPostRequest) =>
|
||||
apiRequest<ContentPostDetailResponse>('/api/v1/admin/content', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
export const transitionContent = (id: string, input: TransitionContentPostRequest) =>
|
||||
apiRequest<ContentPostDetailResponse>(`/api/v1/admin/content/${id}/transition`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
export const fetchDeliveries = (v: { page: number; pageSize: number; status?: string }) =>
|
||||
apiRequest<PaginatedNotificationDeliveriesResponse>(
|
||||
`/api/v1/admin/notification-deliveries?${query(v)}`,
|
||||
);
|
||||
export const retryDelivery = (id: string) =>
|
||||
apiRequest<{ queued: true }>(`/api/v1/admin/notification-deliveries/${id}/retry`, {
|
||||
method: 'POST',
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
import type {
|
||||
CompleteFollowUpRequest,
|
||||
CreateFollowUpRequest,
|
||||
FollowUpDetailResponse,
|
||||
FollowUpMutationResponse,
|
||||
FollowUpStatus,
|
||||
FollowUpType,
|
||||
FollowUpWorkbenchSummary,
|
||||
PaginatedFollowUpsResponse,
|
||||
} from '@lehuo/contracts';
|
||||
|
||||
import { apiRequest } from './api-client';
|
||||
|
||||
function queryString(values: Record<string, string | number | boolean | undefined>): string {
|
||||
const params = new URLSearchParams();
|
||||
Object.entries(values).forEach(([key, value]) => {
|
||||
if (value !== undefined && value !== '') params.set(key, String(value));
|
||||
});
|
||||
return params.toString();
|
||||
}
|
||||
|
||||
export function fetchFollowUpSummary(): Promise<FollowUpWorkbenchSummary> {
|
||||
return apiRequest('/api/v1/admin/follow-ups/summary');
|
||||
}
|
||||
|
||||
export function fetchFollowUps(query: {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
status?: FollowUpStatus;
|
||||
followUpType?: FollowUpType;
|
||||
overdue?: boolean;
|
||||
}): Promise<PaginatedFollowUpsResponse> {
|
||||
return apiRequest(`/api/v1/admin/follow-ups?${queryString(query)}`);
|
||||
}
|
||||
|
||||
export function fetchFollowUp(publicId: string): Promise<FollowUpDetailResponse> {
|
||||
return apiRequest(`/api/v1/admin/follow-ups/${encodeURIComponent(publicId)}`);
|
||||
}
|
||||
|
||||
export function createFollowUp(input: CreateFollowUpRequest): Promise<FollowUpMutationResponse> {
|
||||
return apiRequest('/api/v1/admin/follow-ups', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
}
|
||||
|
||||
export function completeFollowUp(
|
||||
publicId: string,
|
||||
input: CompleteFollowUpRequest,
|
||||
): Promise<FollowUpMutationResponse> {
|
||||
return apiRequest(`/api/v1/admin/follow-ups/${encodeURIComponent(publicId)}/complete`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import type {
|
||||
ActivityDetailResponse,
|
||||
ActivityRegistrationResponse,
|
||||
AssigneeOption,
|
||||
CheckInActivityRegistrationRequest,
|
||||
CreateActivityRequest,
|
||||
CreateProjectRequest,
|
||||
PaginatedActivitiesResponse,
|
||||
PaginatedActivityRegistrationsResponse,
|
||||
PaginatedProjectsResponse,
|
||||
PaginatedVolunteersResponse,
|
||||
ProjectDetailResponse,
|
||||
RecordServiceMinutesRequest,
|
||||
ReviewActivityRegistrationRequest,
|
||||
ReviewVolunteerRequest,
|
||||
TransitionActivityRequest,
|
||||
TransitionProjectRequest,
|
||||
} from '@lehuo/contracts';
|
||||
|
||||
import { apiRequest } from './api-client';
|
||||
|
||||
const query = (values: Record<string, string | number | undefined>) => {
|
||||
const params = new URLSearchParams();
|
||||
Object.entries(values).forEach(
|
||||
([key, value]) => value !== undefined && params.set(key, String(value)),
|
||||
);
|
||||
return params.toString();
|
||||
};
|
||||
|
||||
export const fetchVolunteers = (values: { page: number; pageSize: number; status?: string }) =>
|
||||
apiRequest<PaginatedVolunteersResponse>(`/api/v1/admin/volunteers?${query(values)}`);
|
||||
export const reviewVolunteer = (publicId: string, input: ReviewVolunteerRequest) =>
|
||||
apiRequest(`/api/v1/admin/volunteers/${publicId}/review`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
|
||||
export const fetchProjects = (values: { page: number; pageSize: number; status?: string }) =>
|
||||
apiRequest<PaginatedProjectsResponse>(`/api/v1/admin/projects?${query(values)}`);
|
||||
export const fetchProjectManagerOptions = () =>
|
||||
apiRequest<AssigneeOption[]>('/api/v1/admin/project-manager-options');
|
||||
export const createProject = (input: CreateProjectRequest) =>
|
||||
apiRequest<ProjectDetailResponse>('/api/v1/admin/projects', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
export const transitionProject = (publicId: string, input: TransitionProjectRequest) =>
|
||||
apiRequest<ProjectDetailResponse>(`/api/v1/admin/projects/${publicId}/transition`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
|
||||
export const fetchActivities = (values: { page: number; pageSize: number; status?: string }) =>
|
||||
apiRequest<PaginatedActivitiesResponse>(`/api/v1/admin/activities?${query(values)}`);
|
||||
export const createActivity = (input: CreateActivityRequest) =>
|
||||
apiRequest<ActivityDetailResponse>('/api/v1/admin/activities', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
export const transitionActivity = (publicId: string, input: TransitionActivityRequest) =>
|
||||
apiRequest<ActivityDetailResponse>(`/api/v1/admin/activities/${publicId}/transition`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
|
||||
export const fetchRegistrations = (values: {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
status?: string;
|
||||
activityPublicId?: string;
|
||||
}) =>
|
||||
apiRequest<PaginatedActivityRegistrationsResponse>(
|
||||
`/api/v1/admin/activity-registrations?${query(values)}`,
|
||||
);
|
||||
export const reviewRegistration = (publicId: string, input: ReviewActivityRegistrationRequest) =>
|
||||
apiRequest<ActivityRegistrationResponse>(
|
||||
`/api/v1/admin/activity-registrations/${publicId}/review`,
|
||||
{ method: 'POST', body: JSON.stringify(input) },
|
||||
);
|
||||
export const checkInRegistration = (publicId: string, input: CheckInActivityRegistrationRequest) =>
|
||||
apiRequest<ActivityRegistrationResponse>(
|
||||
`/api/v1/admin/activity-registrations/${publicId}/check-in`,
|
||||
{ method: 'POST', body: JSON.stringify(input) },
|
||||
);
|
||||
export const recordServiceMinutes = (publicId: string, input: RecordServiceMinutesRequest) =>
|
||||
apiRequest<ActivityRegistrationResponse>(
|
||||
`/api/v1/admin/activity-registrations/${publicId}/service-minutes`,
|
||||
{ method: 'POST', body: JSON.stringify(input) },
|
||||
);
|
||||
@@ -0,0 +1,25 @@
|
||||
import type {
|
||||
AdminServiceApplicationQuery,
|
||||
PaginatedServiceApplicationsResponse,
|
||||
ServiceApplicationDetailResponse,
|
||||
} from '@lehuo/contracts';
|
||||
|
||||
import { apiRequest } from './api-client';
|
||||
|
||||
export function fetchAdminServiceApplications(
|
||||
query: AdminServiceApplicationQuery,
|
||||
): Promise<PaginatedServiceApplicationsResponse> {
|
||||
const params = new URLSearchParams();
|
||||
if (query.page) params.set('page', String(query.page));
|
||||
if (query.pageSize) params.set('pageSize', String(query.pageSize));
|
||||
if (query.applicationNo) params.set('applicationNo', query.applicationNo);
|
||||
if (query.serviceCategory) params.set('serviceCategory', query.serviceCategory);
|
||||
if (query.status) params.set('status', query.status);
|
||||
return apiRequest(`/api/v1/admin/service-applications?${params.toString()}`);
|
||||
}
|
||||
|
||||
export function fetchAdminServiceApplication(
|
||||
publicId: string,
|
||||
): Promise<ServiceApplicationDetailResponse> {
|
||||
return apiRequest(`/api/v1/admin/service-applications/${encodeURIComponent(publicId)}`);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import type { StatisticsOverview } from '@lehuo/contracts';
|
||||
import { apiRequest } from './api-client';
|
||||
export const fetchStatistics = (startDate?: string, endDate?: string) => {
|
||||
const p = new URLSearchParams();
|
||||
if (startDate) p.set('startDate', startDate);
|
||||
if (endDate) p.set('endDate', endDate);
|
||||
return apiRequest<StatisticsOverview>(`/api/v1/admin/statistics/overview?${p.toString()}`);
|
||||
};
|
||||
Reference in New Issue
Block a user