Files
halo-dark-mode-plugin/.agents/skills/halo-plugin-dev/references/ui-api-request.md
T
Serendipity edbf78f236 feat: 初始化 Halo 暗色模式插件
- Halo Plugin 后端(Java/Gradle),含 DarkModePlugin 主类和测试
- Vue 3 + TypeScript 前端 UI,包含主题切换组件和设置页面
- 暗色模式 CSS 变量和覆盖样式(布局/编辑器/表单/滚动条等)
- 设计文档和调查文档
- Halo 插件/主题开发 Agent Skills

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-06 21:03:00 +08:00

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-client and axios from the bundle — the final build will use Halo's own copies. If using these versions, set spec.requires: ">=2.17.0" in plugin.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, or useMutation.onError handlers that call Toast.error / Toast.warning for 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 isAxiosError and 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 },
});

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 axiosInstance as 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 useQuery for read operations and useMutation + 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