Files
halo-dark-mode-plugin/.agents/skills/halo-plugin-dev/references/ui-forms.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

481 lines
14 KiB
Markdown

# UI Forms (FormKit)
Halo uses [FormKit](https://formkit.com/) as its form solution. FormKit is **globally registered** in both Console and UC (User Center) — you do **not** need to install or import FormKit in plugin UI code. Use `<FormKit>` components directly, or define forms via Schema in `Setting` resources.
> **Critical**: Do NOT build custom form components from scratch (e.g. raw `<input>` elements) in plugin pages. Always use FormKit inputs so your UI stays consistent with the rest of Halo.
## Docs Routing
FormKit integration changes across Halo versions. Treat this file as a plugin
working guide, then verify exact input options in the official docs when using a
recent or version-sensitive field.
| Need | Official docs |
| ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| Core form schema and built-in inputs | https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/form-schema.md |
| Plugin custom FormKit inputs | https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/plugin/api-reference/ui/formkit.md |
| Plugin UI entry shape (`formkit.inputs`) | https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/plugin/basics/ui/entry.md |
| Plugin API changelog for version gates | https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/plugin/api-changelog.md |
| Business form components and directives | https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/plugin/api-reference/ui/components/index.md |
| Annotation forms for extension metadata | https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/annotations-form.md |
When working from a local docs checkout, use the same paths under
`docs/developer-guide/...`; versioned docs live under
`versioned_docs/version-2.25/...`.
## Quick Index
- Plugin setting forms: [Setting Schema](#1-setting-schema-plugin-config)
- Direct plugin page forms: [Vue Component](#2-vue-component-direct-formkit)
- Built-in and Halo custom inputs: [Available Inputs](#available-inputs)
- Version-sensitive fields: [Version Notes](#version-notes)
- Validation and schema patterns: [Validation](#validation)
- Submission patterns:
[Programmatic Form Submission](#programmatic-form-submission)
## Two Ways to Use Forms
### 1. Setting Schema (Plugin Config)
For plugin settings that users configure in the plugin detail page, define the form in a `Setting` resource using FormKit Schema syntax (written in YAML):
```yaml
# src/main/resources/extensions/settings.yaml
apiVersion: v1alpha1
kind: Setting
metadata:
name: my-plugin-settings # must match spec.settingName in plugin.yaml
spec:
forms:
- group: basic
label: Basic Settings
formSchema:
- $formkit: text
name: apiKey
label: API Key
value: ""
validation: required
- $formkit: switch
name: enabled
label: Enable Feature
value: true
```
Then reference it in `plugin.yaml`:
```yaml
spec:
settingName: my-plugin-settings
configMapName: my-plugin-configmap
```
See [plugin-manifest.md](plugin-manifest.md#settings--configmap) for full setup.
### 2. Vue Component (Direct `<FormKit>`)
For forms inside plugin pages (e.g. a custom admin page), use `<FormKit>` components directly:
```vue
<template>
<FormKit id="my-form" type="form" :actions="false" @submit="handleSubmit">
<FormKit type="text" name="title" label="Title" validation="required" />
<FormKit type="textarea" name="description" label="Description" :auto-height="true" />
<FormKit type="switch" name="published" label="Published" :value="true" />
<VButton type="primary" @click="$formkit.submit('my-form')"> Save </VButton>
</FormKit>
</template>
<script setup lang="ts">
import { Toast } from "@halo-dev/components";
function handleSubmit(values: Record<string, unknown>) {
console.log(values);
Toast.success("Saved");
}
</script>
```
No `import { FormKit } from "@formkit/vue"` is needed — FormKit is globally registered.
## Available Inputs
### FormKit Built-ins (Official)
All standard FormKit inputs work out of the box:
| Input | Type | Description |
| ------------------------- | ---------------------- | ------------------------------------------ |
| `text` | `string` | Single-line text |
| `textarea` | `string` | Multi-line text (with `auto-height` addon) |
| `email` | `string` | Email with validation |
| `number` | `number` | Numeric input |
| `password` | `string` | Password (Halo disables autocomplete) |
| `date` / `datetime-local` | `string` | Date pickers |
| `checkbox` | `boolean` / `string[]` | Single or multi checkbox |
| `radio` | `string` | Radio group |
| `range` | `number` | Slider |
| `file` | `FileList` | File input |
| `group` | `object` | Nested object container |
### Halo Custom Inputs
Halo registers additional inputs for common CMS use cases. Use them exactly like built-ins:
#### `select` — Enhanced Select
Custom select with static or remote data source, multi-select, sorting, and search.
```yaml
- $formkit: select
name: country
label: Country
searchable: true
clearable: true
options:
- label: China
value: cn
icon: /assets/flags/cn.svg
description: Chinese cuisine with rich regional styles
- label: USA
value: us
```
Remote data source:
```yaml
- $formkit: select
name: post
label: Post
clearable: true
action: /apis/api.console.halo.run/v1alpha1/posts
requestOption:
method: GET
labelField: post.spec.title
valueField: post.metadata.name
iconField: post.spec.cover
descriptionField: post.status.excerpt
```
Key props: `options`, `action`, `requestOption`, `multiple`, `searchable`, `clearable`, `sortable`, `maxCount`.
Halo 2.25+ supports `icon` and `description` in static options, plus
`requestOption.iconField` and `requestOption.descriptionField` for remote
options.
#### `switch` — Toggle Switch
```yaml
- $formkit: switch
name: enabled
label: Enable
value: false
onValue: "active"
offValue: "inactive"
```
#### `attachment` / `attachmentInput` — Attachment Picker
`attachment` (Halo 2.22+): supports preview, direct upload, and library selection.
```yaml
- $formkit: attachment
name: logo
label: Logo
accepts:
- "image/png"
- "image/jpeg"
width: "200px"
aspectRatio: "1/1"
```
`attachmentInput`: simpler input that opens the attachment library modal.
```yaml
- $formkit: attachmentInput
name: cover
label: Cover
accepts: ["image/*"]
min: 1
max: 1
```
#### `code` — Code Editor
Integrated with CodeMirror. Supports `yaml`, `html`, `javascript`, `css`, `json`.
```yaml
- $formkit: code
name: custom_css
label: Custom CSS
language: css
height: "300px"
```
#### `iconify` — Icon Selector
Based on [Iconify](https://iconify.design/).
```yaml
- $formkit: iconify
name: social_icon
label: Social Icon
format: svg # svg | dataurl | url | name
```
With sizing options:
```yaml
- $formkit: iconify
name: icon
label: Icon
format: svg
sizing:
enabled: true
default: "24"
presets: ["16", "24", "32", "48"]
```
#### `toggle` — Visual Toggle
For image, color, or text option toggling.
```yaml
- $formkit: toggle
name: theme
label: Theme
render-type: color
options:
- label: Dark
value: dark
render: "#1a1a1a"
- label: Light
value: light
render: "#ffffff"
```
#### `array` — Object Array (Recommended over `repeater`)
For defining arrays of objects with add/remove/reorder.
```yaml
- $formkit: array
name: socials
label: Social Accounts
value: []
max: 5
min: 1
itemLabels:
- type: image
label: $value.logo
- type: text
label: $value.name
children:
- $formkit: attachment
name: logo
label: Icon
- $formkit: text
name: name
label: Name
validation: required
- $formkit: text
name: url
label: URL
validation: required|url
```
> Use `itemLabels` to show preview content on collapsed array items. `$value` refers to the current item.
#### `list` — Primitive Array
For arrays of primitives (strings, numbers, booleans).
```yaml
- $formkit: list
name: tags
label: Tags
itemType: string
min: 1
max: 10
addLabel: Add Tag
children:
- $formkit: text
index: "$index"
validation: required
```
#### `verificationForm` — Remote Validation
Wraps a group of fields and validates them against a remote endpoint.
```yaml
- $formkit: verificationForm
action: /apis/console.api.halo.run/v1alpha1/verify/verify-password
label: Verify Account
children:
- $formkit: text
name: username
label: Username
validation: required
- $formkit: password
name: password
label: Password
validation: required
```
> Unlike other inputs, `verificationForm` does NOT wrap values in its own key. The saved values stay flat: `{ "username": "...", "password": "..." }`.
#### CMS Entity Selectors
Halo provides dedicated selectors for core CMS entities. All return the resource's `metadata.name`.
| Input | Description | Multi-select |
| ------------------------ | -------------------------- | ------------ |
| `menuSelect` | Navigation menu selector | Yes |
| `menuCheckbox` | Menu checkbox group | Yes (array) |
| `menuRadio` | Menu radio selection | No |
| `postSelect` | Post selector | No |
| `singlePageSelect` | Single page selector | No |
| `categorySelect` | Category selector | No |
| `categoryCheckbox` | Category checkbox | Yes (array) |
| `tagSelect` | Tag selector | No |
| `tagCheckbox` | Tag checkbox | Yes (array) |
| `userSelect` | User selector | Yes |
| `roleSelect` | Role selector | Yes |
| `attachmentGroupSelect` | Attachment group selector | Yes |
| `attachmentPolicySelect` | Attachment policy selector | Yes |
Example:
```yaml
- $formkit: postSelect
name: featuredPost
label: Featured Post
value: ""
- $formkit: categoryCheckbox
name: categories
label: Categories
value: []
```
#### `secret` — Secret Resource Selector
For selecting a Halo Secret resource (stores sensitive data like API keys).
```yaml
- $formkit: secret
name: apiSecret
label: API Secret
descriptionPreset: "Token for My Plugin"
requiredKeys:
- key: apiKey
help: API Key
- key: secretKey
help: Secret Key
```
#### `color` — Color Picker
```yaml
- $formkit: color
name: themeColor
label: Theme Color
value: "#1890ff"
```
## Custom Inputs from Plugin UI
Halo 2.25+ lets a plugin register custom FormKit inputs from `ui/src/index.ts`
through `definePlugin({ formkit: { inputs } })`. See [ui-entry.md](ui-entry.md#custom-formkit-inputs).
Use these only when built-in FormKit and Halo inputs cannot express the
interaction. Prefix names with the plugin identifier to avoid collisions, for
example `myPluginTokenPicker`.
## Version Notes
- Halo 2.25+ supports `select` option `icon` / `description`, remote
`iconField` / `descriptionField`, and plugin-registered custom FormKit inputs.
- When using 2.25-only FormKit features, raise `spec.requires` in `plugin.yaml`
and keep related UI packages on a compatible version.
## Programmatic Form Submission
In Vue components, trigger form submission programmatically:
```vue
<VButton type="primary" @click="$formkit.submit('my-form-id')">
Submit
</VButton>
```
Or using `@formkit/core`:
```ts
import { submitForm } from "@formkit/core";
submitForm("my-form-id");
```
## Validation
FormKit supports built-in validation rules. Use them in Schema or Vue components:
```yaml
- $formkit: text
name: email
label: Email
validation: required|email
```
```vue
<FormKit
type="text"
name="slug"
label="Slug"
:validation="[['required'], ['matches', /^[a-z0-9-]+$/]]"
/>
```
Common rules: `required`, `email`, `url`, `number`, `min`, `max`, `matches`, `confirm`.
## Conditional Rendering
Use `if` in Schema to conditionally show fields:
```yaml
- $formkit: select
name: type
label: Type
options:
- label: Internal
value: internal
- label: External
value: external
- $formkit: text
name: url
label: URL
if: "$value.type === 'external'"
validation: required|url
```
> In `if` expressions, `$value` refers to the current form values object.
## Schema vs Vue Component: When to Use Which
| Scenario | Approach |
| ------------------------------------ | ---------------------------------------------- |
| Plugin settings (config page) | `Setting` resource with Schema |
| Custom admin page with dynamic logic | Vue `<FormKit>` components |
| Simple CRUD form in a modal | Vue `<FormKit>` components |
| Reusable form across plugins | Vue `<FormKit>` components in a shared package |
## Important Notes
- **Do NOT install FormKit in your plugin** — it's already globally registered. Installing it again can cause conflicts.
- **Do NOT use FormKit Pro inputs** — they are not included in Halo.
- Schema is JSON format natively, but Halo uses YAML for `Setting` resources. Write Schema in YAML syntax.
- When using `array` or `list`, always provide `value: []` as default to avoid undefined issues.
- For `attachment` with `multiple: true`, the value is a `string[]` of attachment URLs/names.