form

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 26, 2026 License: AGPL-3.0 Imports: 6 Imported by: 0

Documentation

Overview

Package form 提供声明式表单:用 Go 代码描述一组设置项,产出 JSON Schema 2020-12 子集 + 缺省值,供 Console 的通用表单引擎渲染。

为什么要有这一层:此前每个模块手写一段 JSON 字符串当 Schema(见 internal/settings/site.go 的 siteSchema),字段名在 struct、Schema、Defaults、Public 四处各写一遍,没有任何 编译期约束,拼错一个键要到有人打开那一页才发现。本包把这些声明收进 Go 类型: 字段名是标识符、约束是方法调用、条件依赖是表达式,编译器于是能帮上忙。

声明读起来是这样:

form.New(
    form.NewSection("基础",
        form.Text("title").Label("站点标题").Required().MaxLen(128),
    ),
    form.NewSection("存储",
        form.Select("driver", form.Opt("local", "本地")).Label("存储驱动"),
        form.Text("s3Endpoint").Label("服务地址").Required().ShowIf(form.Eq("driver", "s3")),
    ),
)

产出的仍是 JSON Schema:渲染方是 TS、校验方是 JSON Schema 校验器, 这个格式已经是两端之间的既成契约,不另起一套。

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Condition

type Condition struct {
	Field string `json:"field"`
	Op    string `json:"op"`
	Value any    `json:"value,omitempty"`
}

Condition 是一条条件依赖,表达「本字段在什么情况下才有意义」。

序列化为 x-show-if。语义只影响界面:条件不成立时字段不显示、不参与必填校验, 但它的值不会被清掉——站长把驱动从 s3 切回 local 再切回来,填过的地址应当还在。

func Empty

func Empty(field string) Condition

Empty 表达「另一字段为空」。

func Eq

func Eq(field string, value any) Condition

Eq 表达「另一字段等于某值」。

func In

func In(field string, values ...any) Condition

In 表达「另一字段取值为所列之一」。

func Ne

func Ne(field string, value any) Condition

Ne 表达「另一字段不等于某值」。

func NotEmpty

func NotEmpty(field string) Condition

NotEmpty 表达「另一字段已填」。

type Field

type Field struct {
	// contains filtered or unexported fields
}

Field 是一个表单字段的声明。

所有配置方法都返回自身以便链式书写。Field 只在声明期使用, 构建完成后即冻进 *Form,运行时不改。

func Bool

func Bool(key string) *Field

Bool 是开关。

func Code

func Code(key string) *Field

Code 是代码,等宽字体、保留缩进。

func Color

func Color(key string) *Field

Color 是颜色,值为 #rrggbb 一类字符串。

func Date

func Date(key string) *Field

Date 是日期,值为 YYYY-MM-DD;需要时刻时写 YYYY-MM-DDTHH:mm。

func Float

func Float(key string) *Field

Float 是小数。

func Group

func Group(key string, fields ...*Field) *Field

Group 是嵌套的对象,用于把相关字段收成一层。

func Icon

func Icon(key string) *Field

Icon 是图标名,取值来自 Console 的图标登记表。

func Image

func Image(key string) *Field

Image 是单张图片的地址。

func Images

func Images(key string) *Field

Images 是多张图片的地址列表。

func Int

func Int(key string) *Field

Int 是整数。

func List

func List(key string) *Field

List 是字符串列表,条目是自由文本。

func MultiSelect

func MultiSelect(key string, opts ...Option) *Field

MultiSelect 是多选,值为数组。

候选项记在字段自己身上、序列化时落到 items 里:取值的范围是「条目」的属性, 而选项列表在声明与校验两处都要用,放在顶层才不必两边各存一份。

func Radio

func Radio(key string, opts ...Option) *Field

Radio 是平铺的单选。选项较少时比下拉更好——一眼看全,少一次点击。

func Repeater

func Repeater(key string, fields ...*Field) *Field

Repeater 是可增删的重复组,每条由若干字段组成。

func Secret

func Secret(key string) *Field

Secret 是需要长期留存的口令或密钥:已存的值不回传、进库前加密、界面上留空即不改动。

它与 Text 的差别不在形态而在语义,而语义要由接口与存储两侧一起兑现 (见 internal/settings 的 Group.Mask)。此处只负责把「这是口令」 这件事写进声明,让两边都有据可依——没有这个声明,口令只能靠字段名去猜。

func Select

func Select(key string, opts ...Option) *Field

Select 是下拉选择,选项由枚举给出。

func Slider

func Slider(key string) *Field

Slider 是拖动条,适用于有明确区间的数值。

func Text

func Text(key string) *Field

Text 是单行文本。

func Textarea

func Textarea(key string) *Field

Textarea 是多行文本。

func (*Field) Default

func (f *Field) Default(v any) *Field

Default 设置缺省值。每个字段都应当有一个——没有缺省值时, 表单首次打开该字段是空的,而「空」与「未设置」在多数控件上分不出来。

func (*Field) Help

func (f *Field) Help(s string) *Field

Help 设置字段说明,显示在标签与控件之间。

func (*Field) ItemLabel

func (f *Field) ItemLabel(s string) *Field

ItemLabel 是重复组的条目名,用于「添加一项」的按钮文案。

func (*Field) Key

func (f *Field) Key() string

Key 返回字段名。

func (*Field) Label

func (f *Field) Label(s string) *Field

Label 设置显示名。缺省时表单上会显示原始键名,中文界面里那几乎总是遗漏。

func (*Field) Max

func (f *Field) Max(n float64) *Field

Max 设置数值上限。

func (*Field) MaxLen

func (f *Field) MaxLen(n int) *Field

MaxLen 设置最长长度。

func (*Field) Min

func (f *Field) Min(n float64) *Field

Min 设置数值下限。

func (*Field) MinLen

func (f *Field) MinLen(n int) *Field

MinLen 设置最短长度。

func (*Field) Pattern

func (f *Field) Pattern(re string) *Field

Pattern 设置正则约束。写法必须是 ECMA-262 与 RE2 都认的形态: 前端用 new RegExp、后端用 Go 的 regexp,两边都要能编译。

func (*Field) Placeholder

func (f *Field) Placeholder(s string) *Field

Placeholder 设置占位提示。

func (*Field) Required

func (f *Field) Required() *Field

Required 标记为必填。与 ShowIf 同用时是「条件必填」。

func (*Field) Rows

func (f *Field) Rows(n int) *Field

Rows 是文本域可见行数。

func (*Field) ShowIf

func (f *Field) ShowIf(conds ...Condition) *Field

ShowIf 声明条件依赖:全部条件成立时字段才显示。

多处传参即「与」。需要「或」时用 In 把取值列进一条条件, 而不是再加一个组合算子——设置表单里需要或关系的场景, 多半是被声明方式绕进去的。

func (*Field) Type added in v0.2.0

func (f *Field) Type() Type

Type 返回字段的 JSON Schema 类型。

func (*Field) Unit

func (f *Field) Unit(s string) *Field

Unit 是数值字段后面的单位,如「秒」「条」。

func (*Field) Widget

func (f *Field) Widget(w Widget) *Field

Widget 覆盖推断出的控件。类型已由构造函数定下,此处只改呈现方式。

type Form

type Form struct {
	// contains filtered or unexported fields
}

Form 是一份完整的表单声明。

它有两个来源,产出同一种形态:

  • New:Go 代码声明,模块与插件走这条;
  • Parse:已有一份 JSON Schema 与缺省值,主题包的 settings.yaml 走这条。

无论哪条来源,条件依赖(x-show-if)的语义都一致:条件不成立的字段不显示、 不参与必填校验,值仍保留。

func New

func New(sections ...Section) *Form

New 构造一份表单声明。

刻意不返回 error:调用它的地方是模块的 Settings(),那个签名是固定形状的。 声明错误(字段没写标题、缺省值缺失、条件指向不存在的字段)记在表单上, 由 Build 在注册设置分组时一并抛出——那是模块第一次被检查的时刻,也是它该失败的地方。

func Parse

func Parse(name string, schema, defaults json.RawMessage) (*Form, error)

Parse 从一份已有的 JSON Schema 与缺省值构造表单。

这条路径供主题包的 settings.yaml 使用:主题设置是第三方上传的资产, 声明写在 YAML 里,不可能用 Go 的 DSL 表达。插件声明自己的设置时也走这里。

之所以不直接把原始 Schema 交给校验器了事:条件依赖需要结构化的信息才能判定 「这个字段此刻是否可见、是否该参与必填校验」,而那是 x-show-if 在 JSON 里的形态 看不出来的。解析一次、两边共用同一套语义,主题设置与站点设置才不会分家。

解析会规范化 required:带 x-show-if 的字段从 Schema 的 required 数组里移出, 改由 RequiredMissing 在条件成立时判定。不移出的话,「条件不成立所以字段没显示、 因此没填」会被校验器判成失败——而这正是条件依赖要解决的问题。

func (*Form) Build

func (f *Form) Build() (doc, defaults map[string]any, err error)

Build 编译表单,返回 JSON Schema 文档与缺省值。

结果是缓存的:同一份声明可能被 Build 多次(契约测试、启动注册、接口输出各一次), 而声明在构造后不再变化。

func (*Form) DefaultValues

func (f *Form) DefaultValues() map[string]any

DefaultValues 返回缺省值的副本。

func (*Form) Defaults

func (f *Form) Defaults() json.RawMessage

Defaults 返回缺省值的 JSON 表示。

func (*Form) Doc

func (f *Form) Doc() map[string]any

Doc 返回编译后的 Schema 文档;编译失败时为 nil,错误经 Build 取。

func (*Form) Field

func (f *Form) Field(key string) (*Field, bool)

Field 按名字取顶层字段。

func (*Form) Fields

func (f *Form) Fields() []*Field

Fields 返回顶层字段,顺序即声明顺序。

func (*Form) Missing

func (f *Form) Missing(values map[string]any) []string

Missing 返回在当前值下「应当显示却没有填」的必填字段路径。

这是条件必填的服务端判定方。Schema 里的 required 只收无条件必填的字段, 带 x-show-if 的字段在条件不成立时压根不该存在,把它写进 required 会让「隐藏起来所以没填」变成一次校验失败——那正是条件依赖要消灭的东西。

路径用点号连接,与接口错误明细的 body.<路径> 对得上,因此前端能把每一条 落回对应字段。嵌套字段的路径形如 parent.child。

func (*Form) Named

func (f *Form) Named(name string) *Form

Named 给表单起个名字,只用于错误信息与内部资源定位。

func (*Form) Schema

func (f *Form) Schema() json.RawMessage

Schema 返回 Schema 文档的 JSON 表示。

func (*Form) SecretKeys

func (f *Form) SecretKeys() []string

SecretKeys 返回以 Secret 声明的顶层字段名。

只认顶层:口令字段没有嵌套在对象里的用法,而支持嵌套会让「路径 → 值」的 加解密与掩码都要跟着走点号,收益为零而分支翻倍。

func (*Form) Sections

func (f *Form) Sections() []Section

Sections 返回声明时的分节。

func (*Form) Visible

func (f *Form) Visible(key string, values map[string]any) bool

Visible 报告某个顶层字段在当前值下是否显示。

type Option

type Option struct {
	Value string
	Label string
}

Option 是枚举项:值进 schema 的 enum,显示名进 enumNames。

显示名必须显式给出而不是由值推导——值多半是 local / s3 这种机器标识, 直接显示给站长看等于把内部约定漏到界面上。

func Opt

func Opt(value, label string) Option

Opt 构造一个枚举项。

type Section

type Section struct {
	Title       string
	Description string
	Fields      []*Field
}

Section 是表单里的一段,带标题与说明。

分节是纯呈现:它不影响值、不影响校验,只决定字段在页面上怎么分组。 设置项多起来以后,一长条没有分隔的表单会让人找不到东西。

func NewSection

func NewSection(title string, fields ...*Field) Section

NewSection 构造一段表单。没被任何一段收下的字段不会被静默藏起来, 构建时会报错——声明了却不显示,是最难排查的一类问题。

func (Section) Describe

func (s Section) Describe(desc string) Section

Describe 给这一段加上说明文字。

type Type

type Type string

Type 是字段的 JSON Schema 类型。

const (
	TypeString  Type = "string"
	TypeInteger Type = "integer"
	TypeNumber  Type = "number"
	TypeBoolean Type = "boolean"
	TypeObject  Type = "object"
	TypeArray   Type = "array"
)

支持的字段类型,与 Console 表单引擎的 SchemaType 一一对应。

type Widget

type Widget string

Widget 是控件的具体形态,序列化为 Schema 里的 x-widget 提示。

类型与控件是两件事:同为 string,可以是单行文本、多行文本、颜色或图片。 类型决定「什么值算合法」,控件决定「用什么方式让人填」。 每个 Widget 都对应 console/src/components/form/controls.tsx 里的一个控件, 两边对不上时由 cmd/lumo 的契约测试拦下。

const (
	WidgetText        Widget = "text"
	WidgetTextarea    Widget = "textarea"
	WidgetCode        Widget = "code"
	WidgetSecret      Widget = "secret"
	WidgetSelect      Widget = "select"
	WidgetRadio       Widget = "radio"
	WidgetMultiselect Widget = "multiselect"
	WidgetColor       Widget = "color"
	WidgetImage       Widget = "image"
	WidgetImages      Widget = "images"
	WidgetDate        Widget = "date"
	WidgetIcon        Widget = "icon"
	WidgetNumber      Widget = "number"
	WidgetSlider      Widget = "slider"
	WidgetSwitch      Widget = "switch"
	WidgetList        Widget = "list"
	WidgetRepeater    Widget = "repeater"
	WidgetGroup       Widget = "group"
)

全部控件。前 11 个是 Console 表单引擎第二版就有的,其余为本次扩充。

刻意不含富文本:Console 的文章编辑器是「初始内容 + 变化回调」的形态, 塞进受控的设置表单会与每次按键的回写互相打架,而设置项里几乎没有长到需要富文本的字段。 真要做,得先给编辑器加一个受控模式,那是另一件事。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL