Keystone 6 Multiselect 字段完整指南:类型、选项与 GraphQL 集成实战
2026/9/24 17:22:01 网站建设 项目流程
  • 后端

【免费下载链接】keystone

The superpowered headless CMS for Node.js — built with GraphQL and React

项目地址:https://gitcode.com/gh_mirrors/key/keystone
点击查看免费下载

导读

multiselect是 Keystone 内置的“多选”字段类型,用于让一条记录从你预先定义的一组options中选择一个值的集合(数组)。它非常适合标签(Tags)、多分类、多权限标记等场景,与单选的select字段互补。本文将基于 Keystone 源码与官方文档,完整讲解 multiselect 的配置项、三种值类型(string / enum / integer)、数据库与 GraphQL 层的存储行为、Admin UI 的两种交互模式,并结合仓库内的实现与测试用例给出可复制的实战配置。

Multiselect 是什么

根据 官方字段参考文档,一个multiselect字段表示从定义的options中选取一组值。与单选的select字段不同:

  • 值的类型可以是字符串(string)、整数(integer)或枚举(enum),由type选项决定;
  • type决定的是该字段在GraphQL API 中的数据类型
  • select不同,type不会改变数据库类型——multiselect字段在数据库中始终以 JSON 数组存储(Prisma 的Json标量)。

这一点可以在仓库中找到直接证据:在 测试沙箱的 schema.prisma 中,multiselect 字段被生成为multiselect Json @default("[]");而在 测试沙箱的 schema.graphql 中,对应的 GraphQL 输出类型为[String!](可空数组、元素非空)。

配置选项总览

以下是 multiselect 支持的全部配置项(源自官方文档,并结合源码注释补充细节):

配置项默认值说明
type'string'字段值的类型,必须是['string', 'enum', 'integer']之一
options必填一个{ label, value }数组;label是 Admin UI 中显示的文本,value用于 GraphQL API 并存储到数据库
defaultValue[]创建条目时未显式传值时使用的默认值
db.map为字段添加 Prisma@map属性,改变数据库中的列名
graphql.read.isNonNullfalse在无读访问控制时可将输出字段设为非空
graphql.create.isNonNullfalse在无创建访问控制时可将 create 输入字段设为非空并带默认值
ui.displayMode'select'Admin UI 交互模式:'checkboxes'(复选框组)或'select'(组合框 + 标签)
db.isNullablefalse数据库层面是否可空
db.extendPrismaSchema用于扩展生成的 Prisma schema 字段定义

说明:ui.displayModedb.isNullabledb.extendPrismaSchema在官方参考文档正文中没有展开,但明确存在于 multiselect 字段源码类型定义 中,本文一并纳入讲解。

关于options的两种写法

从 MultiselectFieldConfig 类型定义 可以看到,options的写法取决于type

  • type'string''enum'时,options可以是{ label: string; value: string }对象数组,也可以是纯字符串数组。当传入纯字符串时,源码会调用humanize(option)自动生成展示用的 label(configToOptionsAndGraphQLType);
  • type'integer'时,options必须{ label: string; value: number }数组,且defaultValue相应地为数字数组。

完整配置示例

官方文档给出的最小可用配置如下,它展示了typeoptionsdefaultValuedb.map的组合用法:

import { config, list } from '@keystone-6/core'; import { multiselect } from '@keystone-6/core/fields'; export default config({ lists: { SomeListName: list({ fields: { someFieldName: multiselect({ type: 'enum', options: [ { label: '...', value: '...' }, /* ... */ ], defaultValue: ['...'], db: { map: 'my_multiselect' }, }), /* ... */ }, }), /* ... */ }, /* ... */ });

一个更贴近实战的完整例子

以“文章标签”为例,把三种type的典型场景都展示出来,便于直接复制修改:

import { config, list } from '@keystone-6/core'; import { multiselect } from '@keystone-6/core/fields'; export default config({ lists: { Post: list({ fields: { // 枚举类型:value 只能是预定义枚举值,GraphQL 层会生成独立的枚举类型 tags: multiselect({ type: 'enum', options: [ { label: 'News', value: 'news' }, { label: 'Tutorial', value: 'tutorial' }, { label: 'Release', value: 'release' }, ], defaultValue: ['news'], ui: { displayMode: 'checkboxes' }, }), // 字符串类型:value 是自由字符串,GraphQL 层是 [String!] categories: multiselect({ type: 'string', options: [ { label: '前端', value: 'frontend' }, { label: '后端', value: 'backend' }, ], }), // 整数类型:value 是 32 位有符号整数,GraphQL 层是 [Int!] priorityLevels: multiselect({ type: 'integer', options: [ { label: 'Level 1', value: 1 }, { label: 'Level 2', value: 2 }, { label: 'Level 3', value: 3 }, ], defaultValue: [1], db: { map: 'priority_levels' }, }), }, }), }, });

options的便捷简写

如果你不关心 Admin UI 中显示的自定义 label,可以直接传字符串数组,源码会自动将字符串 humanize 为 label(实现位置):

multiselect({ type: 'string', options: ['red', 'green', 'blue'], })

三种值类型与 GraphQL 数据形态

type决定 GraphQL 层的数据类型,这在 configToOptionsAndGraphQLType 中实现:

  • type: 'string':GraphQL 类型为[String!]value是任意字符串,如'a string'
  • type: 'integer':GraphQL 类型为[Int!]value必须是 32 位有符号整数范围内的数字(-21474836482147483647)。源码在 index.ts 中对此做了显式校验:一旦发现value不是整数或超出范围,会在启动时抛出TypeError
  • type: 'enum':GraphQL 层会为字段自动生成一个命名枚举类型,其名称格式为${listKey}${FieldName}Type(例如列表Post的字段tags会生成PostTagsType),枚举值即各 option 的value(生成逻辑)。

无论哪种type,字段在 GraphQL 输出层都是元素非空的列表g.list(g.nonNull(...))(index.ts)。在数据库中则统一以 JSON 数组存储,且默认值为[](sandbox schema.prisma)。

select字段的关键差异

从实现对比可以更清楚地理解 multiselect 的定位:

  • select字段在 Prisma 层使用StringInt标量(见 select 字段源码),是单值列;
  • multiselect字段在 Prisma 层固定使用Json标量(index.ts),存储的是整个数组。

因此两者的type选项含义不同:对select而言type影响数据库列类型,而对multiselect而言type只影响 GraphQL 层的类型呈现。

校验规则与源码级约束

multiselect 内置了严格的运行时校验,均在 字段实现 中可见:

  1. 选项去重:所有 option 的value必须唯一,否则启动时报错has duplicate options, this is not allowed(index.ts);
  2. 值必须在白名单内:create/update 时,传入数组中的每个值都必须是已定义 option 的value,否则校验钩子抛出'<value>' is not an accepted option(index.ts);
  3. 不允许重复选择:同一字段的取值数组内不能有重复值,否则抛出non-unique set of options selected(index.ts);
  4. 不支持唯一索引isIndexed: 'unique'不被支持,配置后会直接抛出TypeError(index.ts)。

这些约束同样体现在测试矩阵中:在 multiselect 测试夹具 中,supportsUnique = falsesupportsNullInput = falsenonNullableDefault = truesupportsDbMap = true,意味着该字段不可唯一索引、不可传 null、默认非空、支持db.map

默认值(defaultValue)的两种语义

defaultValue默认是空数组[]。需要注意它的作用范围与平台差异:

  • 创建条目时:若没有显式传值,字段会使用defaultValue。这由 create 输入解析器resolveCreate保证:当传入值为undefined时回退到defaultValue(index.ts);
  • Prisma 默认值:在 PostgreSQL 等数据库上,源码会给字段生成字面量默认值JSON.stringify(defaultValue ?? null);而在SQLite 上则不生成 Prisma 默认值(index.ts),因为 SQLite 的 Json 默认值由 create 输入逻辑管理;
  • db.isNullable会影响defaultValue的解析:当字段不可空时,defaultValue缺省为[](index.ts)。

测试夹具也印证了“未显式赋值时默认存储为空数组”的行为:storedValues中未提供company字段的记录最终存储为company: [](test-fixtures.ts)。

graphql.isNonNull 的使用前提

graphql.read.isNonNullgraphql.create.isNonNull两个选项有严格的前提条件——只能在对应方向没有访问控制时启用:

  • graphql.read.isNonNull:仅当你没有读访问控制且不打算未来添加时才可设为true。原因在官方文档中解释得很清楚:一旦存在访问控制,被拒绝访问时字段会返回null,而非空字段收到null会引发错误并向上传播,直到遇到可空字段为止——这会导致整个条目不可读,甚至在items查询中所有条目都不可读。源码中 assertReadIsNonNullAllowed 也会在“字段未设置validation.isRequired/db.isNullable: false却开启非空读”时直接抛错;
  • graphql.create.isNonNull:仅当你没有创建访问控制时才能设为true,此时 create 输入字段在 GraphQL 层变为非空并带有默认值。如果存在创建访问控制,用户无权创建该字段时条目会始终通过不了访问控制校验。

简单归纳:这两个选项是“性能/类型严谨性”优化,前提是确认对应方向永远放行,否则会破坏字段与条目的可读/可写性。

Admin UI 的两种展示模式

在 multiselect 的 Admin UI 视图 中,字段支持两种ui.displayMode

  1. 'select'(默认):渲染为一个可过滤的组合框(Combobox)+ 已选标签组(TagGroup)。用户键入文字过滤候选,点选后以标签形式展示,标签可逐个移除,最多展示两行(maxRows={2})(SelectModeField 实现);
  2. 'checkboxes':渲染为一个复选框组(CheckboxGroup),所有 option 平铺为复选框,勾选即选中(CheckboxesModeField 实现)。

列表页的单元格展示也有优化:当选中项超过 3 个时,只显示第一个标签并追加N more(例如 “News, 4 more”),避免表格过宽(Cell 组件)。

Admin UI 内部统一把 option 的value转为字符串处理(valuesToOptionsWithStringValues),序列化回 API 时再按字段typeparseInt还原为整数(controller 实现)。这也是为什么type: 'integer'的 option value 必须落在 32 位有符号整数范围内。

测试验证与延伸阅读

仓库为 multiselect 提供了完整的 API 测试矩阵(enum / string / integer 三种类型全覆盖),见 multiselect 测试夹具:它定义了初始数据、期望存储值、exampleValue等,可用于理解字段在各种操作(创建、查询、过滤、更新)下的期望行为。

如果你正在对比选择字段类型,建议同时阅读 select 字段参考文档 与 字段总览文档;深入理解字段底层机制可阅读 multiselect 源码 以及 非空 GraphQL 校验逻辑。

小结

multiselect是 Keystone 处理“一对多的值集合”场景的首选字段:它把类型约束(string / enum / integer)、选项白名单校验、JSON 数组存储与两套 Admin UI 交互模式封装在一个配置对象中。使用时牢记三个要点:options的 value 必须唯一且(整数类型下)在 32 位有符号整数范围内;defaultValue默认[],在 SQLite 上不生成 Prisma 层默认值;graphql.*.isNonNull仅在确认无访问控制时才启用。理解这些细节后,你就能在真实项目中安全、高效地使用 multiselect 建模标签、分类与多选属性。

  • 后端

【免费下载链接】keystone

The superpowered headless CMS for Node.js — built with GraphQL and React

项目地址:https://gitcode.com/gh_mirrors/key/keystone
点击查看免费下载
上一篇:wezterm.GLOBAL 全局状态存储指南:让配置重载不再丢失 Lua 变量
下一篇:MaaAssistantArknights 小工具全解析:公招识别、干员识别、仓库识别、抽卡监控与活动小游戏

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询