- Halo Plugin 后端(Java/Gradle),含 DarkModePlugin 主类和测试 - Vue 3 + TypeScript 前端 UI,包含主题切换组件和设置页面 - 暗色模式 CSS 变量和覆盖样式(布局/编辑器/表单/滚动条等) - 设计文档和调查文档 - Halo 插件/主题开发 Agent Skills Co-Authored-By: Claude <noreply@anthropic.com>
6.6 KiB
API Requests in Plugin UI
Halo provides @halo-dev/api-client for making API calls from plugin Vue/TypeScript code.
Installation
pnpm install @halo-dev/api-client axios
@halo-dev/ui-plugin-bundler-kit@2.17.0+already excludes@halo-dev/api-clientandaxiosfrom the bundle — the final build will use Halo's own copies. If using these versions, setspec.requires: ">=2.17.0"inplugin.yaml.
Built-in API Clients
@halo-dev/api-client exports pre-configured clients for Halo's built-in APIs. They handle base URL, auth, error handling (login expiry, permission denied), etc.
import {
coreApiClient, // CRUD for all Extensions
consoleApiClient, // Console APIs
ucApiClient, // User Center APIs
publicApiClient, // Public APIs
axiosInstance, // Raw axios instance
} from "@halo-dev/api-client";
Error Handling
Halo adds global response interceptors to the shared Axios instance used by @halo-dev/api-client. Request failures from coreApiClient, consoleApiClient, ucApiClient, publicApiClient, generated clients constructed with axiosInstance, or direct axiosInstance calls already show Halo-managed error toasts.
- Do not add local
try/catch,catch, oruseMutation.onErrorhandlers that callToast.error/Toast.warningfor those Axios request failures. Doing so can show duplicate toasts. - Keep local toasts for non-Axios errors such as client-side validation, parsing failures, missing local prerequisites, or domain-specific messages created before a request is sent.
- If a handler must run cleanup or custom logic after a failed request, guard Axios errors with
isAxiosErrorand do not toast them locally.
import { Toast } from "@halo-dev/components";
import { isAxiosError } from "axios";
function toastNonAxiosError(error: unknown) {
if (isAxiosError(error)) {
return;
}
Toast.error(error instanceof Error ? error.message : "Operation failed");
}
coreApiClient (Extension CRUD)
// List posts
const { data } = await coreApiClient.content.post.listPost({
page: 1,
size: 10,
sort: ["spec.publishTime,desc"],
});
// Get a config map
const { data: configMap } = await coreApiClient.extension.configMap.getv1alpha1ConfigMap({
name: "my-plugin-configmap",
});
consoleApiClient / ucApiClient / publicApiClient
// Console: list attachments
const { data } = await consoleApiClient.attachment.listAttachments({
page: 1,
size: 20,
});
// UC: get current user notifications
const { data } = await ucApiClient.notification.listNotifications();
// Public: search
const { data } = await publicApiClient.post.searchPost({ keyword: "halo" });
Calling Plugin Custom APIs
For APIs defined by your plugin (CustomEndpoint, @Controller, etc.), use the raw axiosInstance:
import { axiosInstance } from "@halo-dev/api-client";
// GET custom endpoint
const { data } = await axiosInstance.get("/apis/console.api.my-plugin.halo.run/v1alpha1/items");
// POST with body
await axiosInstance.post("/apis/console.api.my-plugin.halo.run/v1alpha1/items", {
name: "new-item",
});
// Custom query params
const { data } = await axiosInstance.get("/apis/api.my-plugin.halo.run/v1alpha1/public/items", {
params: { page: 1, size: 10 },
});
Generated API Client (Recommended for Plugin APIs)
For plugin-defined APIs, use the DevTools generateApiClient Gradle task to generate a typed TypeScript client from your OpenAPI spec.
1. Configure OpenAPI grouping in build.gradle
haloPlugin {
openApi {
groupingRules {
extensionApis {
displayName = 'Extension API for MyPlugin'
pathsToMatch = ['/apis/my-plugin.halo.run/v1alpha1/**']
}
}
groupedApiMappings = [
'/v3/api-docs/extensionApis': 'extensionApis.json'
]
generator {
outputDir = file("${projectDir}/ui/src/api/generated")
additionalProperties = [
useES6: true,
useSingleRequestParameter: true,
withSeparateModelsAndApi: true,
apiPackage: "api",
modelPackage: "models"
]
typeMappings = [
set: "Array"
]
}
}
}
2. Generate the client
./gradlew generateApiClient
3. Use the generated client with axiosInstance
import { axiosInstance } from "@halo-dev/api-client";
import { MyResourceV1alpha1Api } from "./api/generated";
const api = new MyResourceV1alpha1Api(undefined, "", axiosInstance);
// List with typed parameters
const { data } = await api.listMyResources({ page: 1, size: 10 });
// Create with typed body
await api.createMyResource({ myResource: { ... } });
The generated client needs
axiosInstanceas its third constructor argument so it uses Halo's pre-configured axios (with auth, base URL, error handling).
Data Fetching with @tanstack/vue-query
For managing server state (caching, refetching, mutations) in plugin Vue components, use @tanstack/vue-query.
Version warning: Halo plugins currently use v4 of
@tanstack/vue-query(e.g.^4.44.0). Do not install v5 — the API is incompatible.
pnpm install @tanstack/vue-query@^4.44.0
Halo's plugin runtime already provides VueQueryPlugin setup — plugin code can use useQuery / useMutation directly without additional configuration.
Usage Example
<script setup lang="ts">
import { consoleApiClient } from "@halo-dev/api-client";
import { useQuery } from "@tanstack/vue-query";
const { data, isLoading } = useQuery({
queryKey: ["attachments"],
queryFn: async () => {
const { data } = await consoleApiClient.attachment.listAttachments({
page: 1,
size: 20,
});
return data;
},
});
</script>
Use
useQueryfor read operations anduseMutation+queryClient.invalidateQueries()for create/update/delete operations.
When to Use Which
| Approach | Use for | Example |
|---|---|---|
coreApiClient / consoleApiClient |
Halo built-in APIs | List posts, fetch users |
axiosInstance directly |
Ad-hoc plugin API calls | Simple GET/POST to custom endpoints |
generateApiClient + axiosInstance |
Plugin APIs with type safety | Full CRUD on your custom Extension |
@tanstack/vue-query + API clients |
Server state in Vue UI | Cached lists, mutations, loading UI |