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 ¶
- type Condition
- type Field
- func Bool(key string) *Field
- func Code(key string) *Field
- func Color(key string) *Field
- func Date(key string) *Field
- func Float(key string) *Field
- func Group(key string, fields ...*Field) *Field
- func Icon(key string) *Field
- func Image(key string) *Field
- func Images(key string) *Field
- func Int(key string) *Field
- func List(key string) *Field
- func MultiSelect(key string, opts ...Option) *Field
- func Radio(key string, opts ...Option) *Field
- func Repeater(key string, fields ...*Field) *Field
- func Secret(key string) *Field
- func Select(key string, opts ...Option) *Field
- func Slider(key string) *Field
- func Text(key string) *Field
- func Textarea(key string) *Field
- func (f *Field) Default(v any) *Field
- func (f *Field) Help(s string) *Field
- func (f *Field) ItemLabel(s string) *Field
- func (f *Field) Key() string
- func (f *Field) Label(s string) *Field
- func (f *Field) Max(n float64) *Field
- func (f *Field) MaxLen(n int) *Field
- func (f *Field) Min(n float64) *Field
- func (f *Field) MinLen(n int) *Field
- func (f *Field) Pattern(re string) *Field
- func (f *Field) Placeholder(s string) *Field
- func (f *Field) Required() *Field
- func (f *Field) Rows(n int) *Field
- func (f *Field) ShowIf(conds ...Condition) *Field
- func (f *Field) Type() Type
- func (f *Field) Unit(s string) *Field
- func (f *Field) Widget(w Widget) *Field
- type Form
- func (f *Form) Build() (doc, defaults map[string]any, err error)
- func (f *Form) DefaultValues() map[string]any
- func (f *Form) Defaults() json.RawMessage
- func (f *Form) Doc() map[string]any
- func (f *Form) Field(key string) (*Field, bool)
- func (f *Form) Fields() []*Field
- func (f *Form) Missing(values map[string]any) []string
- func (f *Form) Named(name string) *Form
- func (f *Form) Schema() json.RawMessage
- func (f *Form) SecretKeys() []string
- func (f *Form) Sections() []Section
- func (f *Form) Visible(key string, values map[string]any) bool
- type Option
- type Section
- type Type
- type Widget
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 再切回来,填过的地址应当还在。
type Field ¶
type Field struct {
// contains filtered or unexported fields
}
Field 是一个表单字段的声明。
所有配置方法都返回自身以便链式书写。Field 只在声明期使用, 构建完成后即冻进 *Form,运行时不改。
func MultiSelect ¶
MultiSelect 是多选,值为数组。
候选项记在字段自己身上、序列化时落到 items 里:取值的范围是「条目」的属性, 而选项列表在声明与校验两处都要用,放在顶层才不必两边各存一份。
func Secret ¶
Secret 是需要长期留存的口令或密钥:已存的值不回传、进库前加密、界面上留空即不改动。
它与 Text 的差别不在形态而在语义,而语义要由接口与存储两侧一起兑现 (见 internal/settings 的 Group.Mask)。此处只负责把「这是口令」 这件事写进声明,让两边都有据可依——没有这个声明,口令只能靠字段名去猜。
func (*Field) Pattern ¶
Pattern 设置正则约束。写法必须是 ECMA-262 与 RE2 都认的形态: 前端用 new RegExp、后端用 Go 的 regexp,两边都要能编译。
type Form ¶
type Form struct {
// contains filtered or unexported fields
}
Form 是一份完整的表单声明。
它有两个来源,产出同一种形态:
- New:Go 代码声明,模块与插件走这条;
- Parse:已有一份 JSON Schema 与缺省值,主题包的 settings.yaml 走这条。
无论哪条来源,条件依赖(x-show-if)的语义都一致:条件不成立的字段不显示、 不参与必填校验,值仍保留。
func New ¶
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 ¶
Build 编译表单,返回 JSON Schema 文档与缺省值。
结果是缓存的:同一份声明可能被 Build 多次(契约测试、启动注册、接口输出各一次), 而声明在构造后不再变化。
func (*Form) Missing ¶
Missing 返回在当前值下「应当显示却没有填」的必填字段路径。
这是条件必填的服务端判定方。Schema 里的 required 只收无条件必填的字段, 带 x-show-if 的字段在条件不成立时压根不该存在,把它写进 required 会让「隐藏起来所以没填」变成一次校验失败——那正是条件依赖要消灭的东西。
路径用点号连接,与接口错误明细的 body.<路径> 对得上,因此前端能把每一条 落回对应字段。嵌套字段的路径形如 parent.child。
func (*Form) SecretKeys ¶
SecretKeys 返回以 Secret 声明的顶层字段名。
只认顶层:口令字段没有嵌套在对象里的用法,而支持嵌套会让「路径 → 值」的 加解密与掩码都要跟着走点号,收益为零而分支翻倍。
type Option ¶
Option 是枚举项:值进 schema 的 enum,显示名进 enumNames。
显示名必须显式给出而不是由值推导——值多半是 local / s3 这种机器标识, 直接显示给站长看等于把内部约定漏到界面上。
type Section ¶
Section 是表单里的一段,带标题与说明。
分节是纯呈现:它不影响值、不影响校验,只决定字段在页面上怎么分组。 设置项多起来以后,一长条没有分隔的表单会让人找不到东西。
func NewSection ¶
NewSection 构造一段表单。没被任何一段收下的字段不会被静默藏起来, 构建时会报错——声明了却不显示,是最难排查的一类问题。
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 的文章编辑器是「初始内容 + 变化回调」的形态, 塞进受控的设置表单会与每次按键的回写互相打架,而设置项里几乎没有长到需要富文本的字段。 真要做,得先给编辑器加一个受控模式,那是另一件事。