设计器
<xh-report-designer> 可视化编辑报表定义:从物料拖出卡片,把字段拖进卡片,在属性面板里改配置,问题列表随时指出哪里不对。报表存在哪里、谁能改,都由宿主决定。全部属性、方法与事件见参考 · 设计器。
注册与样式
设计器要的样式多一份设计器零件(designer-shell)的,也要查看器的,因为画布与预览用的就是查看器的绘制:
import type { ReportDesignerEventMap } from '@xihan-report/designer'
import { defineReportDesigner } from '@xihan-report/designer'
import { createExpressionEngine } from '@xihan-ui/expression'
import { rows, schema } from '../reports/dashboard.ts'
import '@xihan-ui/styles/index.css'
import '@xihan-ui/designer-shell/style.css'
import '@xihan-report/runtime/style.css'
import '@xihan-report/designer/style.css'
defineReportDesigner()打开与保存
把报表定义写进 schema 就装载它,清空撤销历史与选择;之后读 schema 得到当前的报表。取数回调与查看器是同一个,画布与预览用它显示示例数据:
const designer = document.createElement('xh-report-designer')
designer.expressionEngine = createExpressionEngine()
designer.dataProvider = async () => ({ kind: 'rows', rows })
designer.schema = schema每改一步派发 schema-change,detail 带新的报表、这一步的操作与来由(执行、撤销、重做或装载)。设计器不自己存:在画布上按 Ctrl+S(macOS 上 ⌘S),或点「文件」菜单的「保存」(命令 save)时派发 save,宿主把报表存好后调 markSaved(),dirty 与 dirty-change 据此判断有没有未保存的改动:
designer.addEventListener('dirty-change', (event) => {
const { dirty } = (event as ReportDesignerEventMap['dirty-change']).detail
status.textContent = dirty ? '有未保存的改动' : '已保存'
})
designer.addEventListener('save', () => {
const saved = JSON.stringify(designer.schema)
// 宿主在这里把 saved 存进数据库或文件,存好之后:
designer.markSaved()
status.textContent = `已保存(${saved.length} 个字符)`
})设置「基础」页里开着自动保存(general.autoSave,缺省关)时,未保存的修改停手约一秒存成这台浏览器里的草稿,保存(markSaved())后删掉。下次装进同一份报表、和草稿不一样时,弹确认框问恢复还是丢弃:恢复是整份换上草稿、记一步撤销,丢弃即删。不同报表用 draftKey(attribute draft-key,宿主写报表 id)区分,不写时按页面地址区分。草稿只存在本机,宿主不用存、也不用接新事件;要固定开或关,在 settings 里预先写或用 settingsPolicy 藏起、锁住这一条。只读时不问。
没给取数回调时,画布上取数的卡片画占位,说明给了取数回调后会显示示例数据。用到计算字段或公式度量时还要给 expressionEngine,校验公式也用它。
界面
外框从上到下是菜单栏、工具栏、中间三栏(左边数据集、报表参数、物料与页面结构,中间画布,右边字段栏与属性)、画布下方的问题列表与最底下的状态栏。各块面板停在哪一栏、显不显示、收没收起、浮没浮出,记在一份面板布局里,见下文「面板布局」;预览态只留菜单栏、工具栏与画布。
| 部分 | 位置 | 面板 id | 作用 |
|---|---|---|---|
| 菜单栏 | 最上面 | — | 文件、编辑、视图、数据、帮助五张菜单;正中放宿主的模板标题(slot="menubar-center"),右边一段放宿主别的常显内容(slot="menubar-end"),见下文「命令与菜单」 |
| 工具栏 | 菜单栏下面 | — | 撤销重做;剪切、复制、粘贴、创建副本、删除;拖动工具与缩放;设计与预览;扩展登记的命令;末组是面板 |
| 数据集 | 左栏最上面 | datasets | 数据集与字段,下面是新增数据集与新增字段 |
| 报表参数 | 左栏,数据集下面 | parameters | 报表参数,下面是新增参数;没有参数又不能新增时收起 |
| 物料 | 左栏,报表参数下面 | palette | 图表、指标、表格,每种带种类图标;拖到看板上或点一下新建卡片。扩展的物料来源(paletteSources)接在后面,见扩展 |
| 页面结构 | 左栏最下面 | outline | 按树列出卡片、卡片的列表与列表项,见下文「页面结构」 |
| 画布 | 中间 | — | 看板的卡片可以框选、拖动与改大小,落在网格上;可以缩放,见下文「缩放」 |
| 选中工具条 | 选中框上方 | — | 选中卡片时浮出:创建副本、复制与删除,见下文「命令与菜单」 |
| 尺寸徽标 | 选中框下方 | — | 选中卡片时常驻,写宽 × 高(格);选中一张时可以直接改,见下文 |
| 问题 | 画布下方 | issues | 校验问题,点一条选中出问题的对象;错误与警告的条数在面板头上,点一下只看或不看这一级 |
| 字段栏 | 右栏上面 | wells | 选中卡片(或主体视图)的维度、度量、系列、列与分组,可以拖进字段、拖动换序 |
| 属性 | 右栏中间 | properties | 选中对象的属性;选中多张卡片时编辑共同的属性 |
| 历史记录 | 右栏下面,缺省隐藏 | history | 打开报表以来的每一步,见下文「撤销历史」 |
| 状态栏 | 最下面 | — | 左边写选中的对象:选中一张卡片是种类与所在的格,选中几张是个数,选中字段、参数等对象是它的种类与名字,没有选中时是数据集数与卡片数;右边写缩放倍数,点一下回到实际大小,宿主的保存状态(slot="status-end")排在它左边。窄时左边一段先收窄。设置里可以关掉 |
数据集、报表参数、物料、页面结构、问题、字段栏、属性与历史记录是外框里的卡片:面板头写图标与标题,属性卡片的标题下面写正在编辑的对象,「?」里是这块面板的用法。
面板布局
- 面板头上的「×」隐藏这块面板,收起钮(或双击面板头)收成只剩标题条,「更多」菜单里换到别的栏、调先后、浮出成小窗或停靠回去。按住面板头拖:拖到外框边上换栏,拖到两块面板之间调先后,拖到画布上浮出成小窗;小窗不出外框。
- 工具栏与「视图」菜单里的「面板」列出这八块卡片,勾选的显示;末尾是「显示 / 隐藏所有面板」(Ctrl / ⌘ + \)、「还原面板显隐」(Ctrl / ⌘ + Shift + \,位置与大小不动)与「恢复默认布局」。每块卡片另有一条开关命令
panel:<面板 id>,不在工具栏上,用commandLayout排上去即可。 - 布局一改,设计器就把整份布局写进设置的
layout.panels(DESIGNER_SETTING_KEYS.layout),派发settings-change,宿主存起来、下次随settings赋回即可;给了settingsStorageKey时设计器自己存。拖分隔条只在松手时写一次。 - 宿主用
defaultLayout给缺省布局:用户没存过布局时用它,「恢复默认布局」也回到它。用settingsPolicy管控:layout.panels锁定整份布局,layout.panels.<面板 id>写hidden就没有这块面板、写locked就固定这块面板。面板 id 见包入口导出的REPORT_DESIGNER_PANELS。 - 报表参数卡片没有参数又不能新增时不登记,菜单栏、工具栏与状态栏在设置里关掉时不登记;没登记的区不显示,存在设置里的布局不受影响,再登记时照布局放回。
export function wireLayout(designer: XhReportDesigner): void {
// 缺省收起问题列表;宿主用不到物料面板,也不许用户把它开出来
designer.defaultLayout = { version: 1, hidden: [REPORT_DESIGNER_PANELS.issues] }
designer.settingsPolicy = { [`${DESIGNER_SETTING_KEYS.layout}.${REPORT_DESIGNER_PANELS.palette}`]: 'hidden' }
}窗口窄时左右两栏收起,从「面板」菜单打开时以抽屉盖在画布上;浮出的面板在窄屏时按停靠位置收进抽屉。
工具栏缺省只显示图标;设置的「工具栏」页可以换成文字或图标加文字,藏起、调换或跨组挪动按钮,也能整条隐藏,见下文「设置」。按钮的名字与快捷键报给读屏,悬停或键盘聚焦到按钮时提示命令的文字与快捷键;会删掉内容的命令按危险色显示。工具栏放不下时,末尾的按钮收进「更多」。
- 尺寸徽标按格计:选中一张卡片时宽、高各一个数字框,加减钮与上下方向键一次改一格,Shift 加方向键一次十格,输入格数后按 Enter 或离开数字框才生效,宽不超出看板的右边,至少一格;压到的卡片让开。连着点加减钮或按方向键合为一步撤销,输入的单独一步。选中几张或只读时只显示尺寸。
- 把字段拖到卡片上,数值字段进度量,其余进维度(明细表与分组报表进列,交叉表进行);加减维度、度量后,图表与指标卡片按查询里现有的项补全必填的引用。
- 报表参数的固定候选项在属性面板里是一张选项表,逐行编辑值、文字与禁用,可以新增、删除与换序,重复的取值标在那一格上。值格跟着参数的类型:
string是文本框,number是数字框,date、datetime是日期框;boolean固定true、false两行,值格只读,只编辑文字与禁用。 - 删除被别处用着的字段、参数或数据集时,先弹确认框列出会一并删掉的地方,确认后合为一步撤销。
datasetEditing决定数据集能改到什么程度:none只读;fields(缺省)能改字段的标题、类型与计算字段公式,增删报表参数;full另能增删数据集与字段。readonly只能选择、浏览与预览,不能修改。attribute 按三态读:没写或写"false"可修改,空串、"true"与别的写法只读,移除后回到可修改。这是一处行为变化:此前readonly="false"也算只读,现在是可修改。mode写preview切到预览,用查看器按parameters显示报表;切换时派发mode-change。
页面结构
- 看板按阅读顺序(先行后列)列出卡片:有标题的写标题、名字下面写种类,没有标题的写「种类名 · id」,行首是卡片种类的图标(包入口导出的
REPORT_CARD_TYPE_ICONS)。卡片下面是它的列表(维度、度量、系列、列、分组、行),列表下面是项。主体不是看板时,顶层是那一个视图。数据集与参数在数据面板里,联动在字段栏里,这里不列。 - 点一行选中它并让卡片露出来,画布上选中了什么,这里跟着选中那几行;悬停或用键盘走到一行时,画布上标出它所在的卡片。卡片可以多选:Ctrl/⌘ 加点加选或取消一行,Shift 选一段;列表项一次只选一个,新点上一项就换成选它。新选上的卡片滚进画布的可见区,只取消几行时画布不动。Delete 删除选中的卡片或列表项,与画布上删除一样进撤销。
- 列表项能拖动或用 Alt + 方向键换序,交叉表的行与列之间能互换;卡片的位置由网格定,不能在树里挪。顶上有搜索框。只读时只能看。
导入数据集、字段与参数
- 入口:数据集卡片的面板头上有「导入数据集」与「导入字段」,报表参数卡片的面板头上有「导入参数」。导入数据集与字段要
datasetEditing为full,导入参数要fields起,不够或只读时没有这几颗钮;导入字段还要先在数据面板里选中一个数据集或它的字段,导进那个数据集。 - 对话框里粘贴一份 JSON 样例或 JSON Schema,也可以从 .json 文件读进来;解析失败时写出第几行第几列。下面的预览列出推断出的字段,可以去掉不要的、改显示名与类型,确认后才导入。
- 数据集与字段是扁平的,只取一行的值字段:样例的根是数组时取它的项;根是对象、里面有带子字段的列表时(如
{ "data": [...], "total": 2 }),取第一个这样的列表的项;否则对象本身就是一行。嵌套的对象与列表跳过。 - 参数取顶层:值字段各是一个参数,标量数组是多值参数,对象跳过;JSON Schema 里的
required、enum、default换成必填、固定候选项与缺省值。 - 同名的字段与参数保留现有的,导入只新增,不改不删。一次导入记一步撤销;导入数据集后选中新数据集。
- 用代码:
canImportData(kind)判断此刻能不能导入,openDataImport(kind)打开对话框,kind为dataset、fields或parameters。三条命令的 id 是import-dataset、import-fields与import-parameters,也在菜单栏的「数据」菜单里,可以用commandLayout排到别处。 - 对话框只有 JSON 样例与 JSON Schema 两页;扩展可以经
fieldSources再登记来源页,见扩展。
快捷键
画布在 Tab 顺序里只占一个位置:卡片里的图例、图形与表格行在设计时只用来看,不接收焦点,点在上面就是点那张卡片。焦点在画布上时:
| 按键 | 作用 |
|---|---|
| Ctrl/⌘ + Z | 撤销 |
| Ctrl/⌘ + Shift + Z、Ctrl + Y | 重做 |
| Ctrl/⌘ + C、X、V | 复制、剪切、粘贴,经系统剪贴板,能在两个设计器之间粘贴 |
| Ctrl/⌘ + D | 给选中的卡片创建副本 |
| Ctrl/⌘ + Shift + J | 打开「编辑 JSON」对话框(开发者模式开着时) |
| Delete、Backspace | 删除选中的对象 |
| Ctrl/⌘ + A | 全选看板上的卡片 |
| 方向键 | 把选中的卡片挪一格;加 Shift 改大小。同一张卡片连着按、相隔不超过 1 秒的合为一步撤销 |
| PageUp、PageDown、Home、End | 滚动画布;没有选中卡片(或选中了几张)时方向键也是滚动 |
| Esc | 取消选择 |
| F10 | 把焦点交给菜单栏,再按 F10 或在菜单栏上按 Esc 回到画布 |
| Alt + F10 | 把焦点交给选中工具条(排列为空时交给尺寸徽标),在里面按 Esc 回到画布 |
| +(或 =)、- | 放大、缩小一档 |
| 0 | 适合窗口 |
| H | 打开或关上拖动工具 |
| Ctrl/⌘ + 滚轮 | 以指针为中心缩放 |
| Ctrl/⌘ + S | 保存(命令 save),派发 save |
| Ctrl/⌘ + , | 打开设置对话框 |
撤销历史
- 每一步撤销都带一句说明,按这一步改了什么生成,如「删除卡片「销售额」」「把字段「region」改名为「area」」「修改度量「amount」的聚合」;连带删掉引用时写上删了几处。说明按此刻的语言取,换了语言跟着换。工具栏上撤销、重做的名字与提示写着这一步,如「撤销:删除卡片「销售额」」。
- 「历史记录」卡片从上到下列出打开时的起点与之后的各步,当前所在的那一步选中;撤销掉、还能重做的标「已撤销」,保存时所在的那一步标「已保存」。点一步就一次跳过去,读屏播报跳到了哪一步、走了几步。卡片缺省隐藏,从「面板」菜单打开,打开后在右栏属性下面;面板头上是「清空历史」,确认后只清空撤销与重做,报表不变。
- 撤销步数缺省最多 100 步,
historyLimit(attributehistory-limit)改上限,下一次装载报表时生效;超出上限时最早的几步丢掉,起点改写「更早的步骤已丢弃」。 - 用代码:
historyState读快照(各步的说明、状态、是不是保存时所在,以及当前位置),goToHistory(position)跳到一步或起点,clearHistory()清空;撤销历史每变一次派发history-change,detail 与historyState相同。只读时不能跳,也不能清空。 - 清空历史也是一条命令
clear-history,不在工具栏上,可以用commandLayout排上去。
编辑 JSON
设置「基础」页里开着开发者模式时,「编辑」菜单与空白处的右键菜单末尾是「编辑 JSON」(命令 edit-json,Ctrl/⌘ + Shift + J),打开一个对话框,写着整份报表定义的 JSON(缩进两格)。开发者模式关着(缺省)时这条命令不出现,快捷键也不响应。
- 边改边检查:写得不是合法 JSON 时说明第几行第几列出错,能跳过去;JSON 读得了就按当前的扩展与表达式引擎校验,问题列在下面,说明与所在对象的名字同「问题」卡片,点一条光标跳到那一处。
- 没有错误、内容又改过时「应用」才能按:整份换上改好的报表,清空选择,记一步撤销,说明是「编辑 JSON」,派发一次
schema-change。 - 对话框里还能格式化与复制。只读或预览态时只能看与复制,没有「应用」。
- 用代码:
openJsonEditor()打开对话框,不看开发者模式,还没有报表时返回false。宿主要让用户用编辑 JSON,在settings里预先写general.developer为true,或用settingsPolicy锁住它。整份替换是 core 的编辑操作replaceDocument,宿主也可以自己执行。
新手引导
第一次打开设计器、铺好画布后,自动开始一段新手引导(设计器放在隐藏的页签或容器里时,等它显示出来再开始),一步步高亮对应的面板、菜单或按钮:先是欢迎与菜单栏(各张菜单里有什么,F10 进出菜单栏),再是各款共用的几步(工具栏、物料、画布、属性、问题、状态栏),接着是报表自己的几步——数据面板、「数据」菜单(导入数据集、字段与参数,登记了导入器时还说最下面的导入模板)、字段、卡片、联动与预览(并说「视图」菜单里也能在设计与预览之间切换),最后一步是设置:高亮「文件」菜单,设置在那里,引导能从「帮助」菜单重新开始。遮罩只盖住设计器的外框,页面别处照常能用。看板上没有卡片时跳过卡片与联动两步,目标面板隐藏着的那一步也跳过。
窄屏时菜单栏收成一颗「菜单」钮,「数据」菜单与设置两步都落在这颗钮上;工具栏上收进「更多」的钮,那一步落在「更多」钮上。
- 走完、跳过或关上一次以后,设计器在设置里记下
help.tourDone(DESIGNER_TOUR_DONE_KEY),派发settings-change;宿主存起设置、下次赋回,就不再自动开始。 - 宿主不要自动开始时写
autoTour = false(attributeauto-tour="false")。「帮助」菜单与设置「帮助」页里的「开始引导」、命令start-tour与startTour()随时从头开始;预览态或没有报表时startTour()返回false。 - 同一页只开一个引导:页面上别的设计器的引导开着时不自动开始;「开始引导」、
start-tour与startTour()先收起那一个再开始。 - 扩展可以经
tourSteps加自己的步骤,排在报表的几步之后,见扩展。
缩放
工具栏的缩放一组是拖动工具、缩小、放大、适合窗口与实际大小,空白处的右键菜单里也有适合窗口,状态栏右边写着缩放倍数,点一下回到实际大小。缩放范围是 10%–400%,每档 1.2 倍,放大、缩小以画布中间为准,Ctrl/⌘ 加滚轮以指针为准。
看板总是铺满画布的宽,缩放改的是卡片与文字的大小:缩小时一屏看到更多行。适合窗口缩到整块看板的高度落进画布,不放大到实际尺寸以上;之后画布或看板的尺寸变了就重新适配,直到改了缩放或在画布上按下指针。拖动卡片、改大小、框选与从物料面板拖进卡片在任何缩放下都落在同样的格子上。
设计器打开时按实际尺寸(100%)显示,从看板左上角开始,不自动适合窗口;要整块看板落进画布时点「适合窗口」。拖动工具(快捷键 H)开着时,在画布上哪儿按住拖动都是平移:不选中、不挪卡片,只点一下也不改选择,双击不当作双击卡片;画布显示手形,拖动中握拳。拖动途中按 Esc 只取消这一次、滚回原处,不在拖动时按 Esc 退出拖动工具。缩放只是看的方式,不进报表定义,也不记到下次打开。getViewport() 读当前视口(屏幕坐标 = 画布坐标 × zoom + (x, y),画布坐标是实际尺寸下的像素),setViewport() 设置它;zoomIn()、zoomOut()、zoomToFit()、zoomToActual() 与四条命令相同;视口变了(缩放或滚动画布)派发 viewport-change。
命令与菜单
菜单栏、工具栏与右键菜单里的项都是命令,扩展可以登记新的命令(见扩展)。commandLayout 改写它们的排列:写了某个位置,那里就只显示列出的命令,内置的与扩展的命令 id 可以混排。
最上一条是菜单栏,下面是工具栏。菜单栏放通用的功能,文件、编辑、视图、帮助四张与四款设计器一致,报表另有一张「数据」:
- 「文件」:保存(Ctrl / ⌘ + S)、设置(Ctrl / ⌘ + ,,同
openSettings())。 - 「编辑」:撤销、重做,剪切、复制、粘贴、删除、创建副本,全选,编辑 JSON(开发者模式开着时)。
- 「视图」:设计、预览,放大、缩小、适合窗口、实际大小,拖动,「面板」与「外观」两个子菜单;外观里是工具栏、状态栏两个显示开关(改的是设置)。
- 「数据」:导入数据集、导入字段、导入参数;登记了导入器时末尾另一组是「导入模板」。这张菜单的 id 是包入口导出的
REPORT_DATA_MENU(data)。 - 「帮助」:开始引导、帮助(在新窗口打开本站的设计器一页,地址是包入口导出的
REPORT_DESIGNER_HELP_URL)、快捷键(打开设置的快捷键页),关于(打开设置的关于页)。
画布有焦点时按 F10 把焦点交给菜单栏,左右键在菜单之间走,下键、Enter 或 Space 展开;再按 F10,或在菜单栏上按 Esc,回到画布。窄屏时菜单栏收成行首一颗「菜单」钮,各张菜单是它的子菜单。菜单栏常开,没有关掉它的开关;设置与帮助只在菜单栏里。
宿主自己的操作(新建、预览、返回列表)写成一个扩展的命令,placement 写 menubar:file 就进「文件」菜单,写了 toolbar 也上工具栏;模板标题写成设计器的子元素 slot="menubar-center",显示在整条菜单栏的正中,放不下时截断加省略号,再窄就藏起;保存状态写成 slot="status-end",显示在状态栏右端、缩放比例的左边,状态栏在设置里关掉时跟着不显示;别的要常显的内容写 slot="menubar-end",显示在菜单栏右边。连上文档之前写的与之后加的都一样。这样设计器外面不必另起一条操作条。
designer.extensions = [{
id: 'acme.host',
commands: [
{ id: 'acme.new', label: '新建', shortcut: ['Mod', 'Alt', 'N'], placement: ['menubar:file'], run: () => location.assign('/reports/new') },
{ id: 'acme.back', label: '返回列表', placement: ['menubar:file', 'toolbar'], run: () => history.back() },
],
}]
const title = document.createElement('span')
title.slot = 'menubar-center'
title.textContent = '销售看板 · SALES-01'
const saved = document.createElement('span')
saved.slot = 'status-end'
saved.textContent = '已保存'
designer.append(title, saved)- 右键菜单的条目是图标加文字,行尾写快捷键。
- 工具栏末组是面板:点开一张菜单,列出能开关的卡片,勾选的显示,末尾是显示或隐藏所有面板、还原面板显隐与恢复默认布局,见上文「面板布局」。
- 卡片上的右键菜单依次是剪切、复制、粘贴、创建副本,删除另成一组;空白处的是粘贴、全选、适合窗口与编辑 JSON(开发者模式开着时),各成一组。
- 扩展登记了导入器时,工具栏最前面多一组「导入模板」(
import-template,包入口导出的REPORT_IMPORT_TEMPLATE_COMMAND),「数据」菜单的末尾也有它,见扩展;没有导入器时没有这条命令。 commandLayout的位置:工具栏是toolbar,菜单栏是menubar:file、menubar:edit、menubar:view、menubar:data、menubar:help,右键菜单是menu:card、menu:blank,选中工具条见下一条。某张菜单排空了就不出现。工具栏与菜单栏此刻的缺省排列由包入口导出的reportToolbarLayout、reportMenubarLayout给出,宿主可以在它们上面改。- 选中工具条按选中的是一张还是几张卡片取命令落点
selection:card或selection:multiple的排列,缺省都是创建副本、复制与删除(包入口导出的REPORT_SELECTION_LAYOUT)。宿主用commandLayout改,写空数组就不放工具条;扩展命令的placement写上落点,就追加成一组。只读与预览时没有选中工具条,拖动、改大小时收起。 - 撤销、重做、剪切、复制、粘贴、创建副本、删除、全选、缩放的四条、设计与预览这几条命令的文字,以及工具栏、菜单栏与文件、编辑、视图、帮助四张菜单的名字,是四款设计器共用的文案,在 designer-shell 语言包的
xh-designer-commands一桶里,换语言时随mergeXhConfig一起换;要改这些文字,在全局配置里覆盖这一桶。报表自己的文案(含「数据」菜单的名字)在xh-report-designer。
designer.commandLayout = {
'toolbar': [['undo', 'redo'], ['preview']],
'menu:card': [['copy', 'duplicate'], ['delete']],
}右键点卡片或空白处时派发 card-contextmenu 或 blank-contextmenu,取消这个事件就不弹内建菜单,宿主可以画自己的;双击卡片派发 card-dblclick。
设置
设置是用户偏好,每人每机一份,不随报表定义保存。openSettings(section?) 打开设置对话框,左边一个分区一页:
- 「基础」(
general):主题(跟随页面、跟随系统、亮色、暗色)、主题色(跟随页面、八种预设、自定义颜色)、状态栏、自动保存(general.autoSave,缺省关,见上文「打开与保存」)、开发者模式(general.developer,缺省关;关着时编辑 JSON 这类调试查看入口不出现),以及分页、渲染两条调试日志(general.debugPagination、general.debugRender,缺省关,开发者模式关着时只读、也不记)。 - 「语言」(
language):跟随页面(缺省)、跟随系统,或在十种语言里选一种,名字是各自的本名(简体中文、繁體中文、English、日本語……),键是language.locale。 - 「布局」(
layout):逐块卡片一行,改显示、位置、先后与收起,表下是「恢复默认布局」,与面板头的「更多」菜单一样不靠拖动也能改布局;改动照样写进layout.panels。settingsPolicy把layout.panels写hidden时没有这一页,写locked时只读。 - 「工具栏」(
toolbar):按钮显示方式(toolbar.display:图标、文字、图标和文字,缺省只显示图标)、显示工具栏(toolbar.visible,关掉后整条工具栏不显示,命令照样能经快捷键与右键菜单执行)与按钮表(toolbar.layout)。按钮表一组一块,勾掉的按钮不上工具栏,拖动把手换先后、跨组挪,组头的勾选框整组勾选或勾掉,表下「恢复默认」删掉这个键。工具栏先按commandLayout(没写时按缺省排列)排出分组,再套用户的定制;后装的扩展命令放到缺省排列里挨着的位置,已经没有的命令跳过。只管顶部工具栏,右键菜单与选中工具条不进定制。这几条只走设置,没有单独的属性。 - 扩展登记的分区,排在这几页后面。分区 id 与
general、language、layout、toolbar或别的扩展重名时,给extensions赋值就抛DesignerExtensionError。 - 「帮助」:几段操作说明,下面是「开始引导」与「帮助」两颗钮,点了先关上对话框再开始引导或打开本站的设计器一页。
- 「快捷键」:一张快捷键表,从命令表生成,分通用、编辑、本设计器与扩展几组。保存、设置这些外壳命令归在通用一组,通用一组末尾是各款共有的按键(含 F10 进出菜单栏),报表去掉了看板上没有的按住空格拖动画布,Shift 加方向键写作改卡片大小;本设计器一组末尾是不是命令的几个键(Ctrl/⌘ + C、X、V 复制、剪切与粘贴,Backspace 删除)。
- 「关于」:报表设计器的版本(包入口导出的
REPORT_VERSION)与许可证、界面库的版本,后面接扩展的about字段给的几行(如附加包的版本与授权)。这三页只显示,不存值。
看板的网格是报表布局的一部分(卡片总落在格子上),随报表定义保存,所以报表没有自己的「画布」一页。datasetEditing 管的是能改到什么程度,属于权限,仍是设计器的属性。
export function openSettings(designer: XhReportDesigner): void {
designer.openSettings()
}值是扁平的键值,键是「分区 id.条目 id」,如 general.theme,没写的键取缺省值。宿主给缺省、锁定条目或读值时用 @xihan-ui/designer-shell 导出的 DESIGNER_SETTING_KEYS,不手写键。settings 赋值即生效;用户在对话框里改了一条,设计器换成新的一份并派发 settings-change,宿主存起来、下次赋回。settingsPolicy 隐藏或锁定条目,键是条目键或分区 id:hidden 不显示,一个分区的条目全藏了就没有这一页;locked 只读,说明换成「由所在系统管理」。
export function wireSettings(designer: XhReportDesigner, saved: ReportSettings, store: (settings: ReportSettings) => void): void {
// 宿主自己的缺省压在下面,用户存过的值盖在上面
designer.settings = { [DESIGNER_SETTING_KEYS.toolbarDisplay]: 'both', ...saved }
designer.settingsPolicy = { [DESIGNER_SETTING_KEYS.theme]: 'hidden', [DESIGNER_SETTING_KEYS.brand]: 'hidden' }
designer.addEventListener('settings-change', (event) => {
store((event as CustomEvent<{ settings: ReportSettings }>).detail.settings)
})
}- 主题只作用于设计器:主题与主题色写在
<xh-report-designer>自己的data-theme、data-brand上,页面其余部分不变;设置对话框、菜单这些浮层跟着设计器变。主题色是内置的:跟随页面(缺省,不写data-brand),或 UI 的八种预设,或自定义颜色,宿主不用传预设。 - 语言只作用于设计器:设置里选了语言,
<xh-report-designer>把这一语言的 UI、designer-shell 与报表语言包作为自己的xhConfig交出去,设计器、里面的元素(含预览的查看器与图表)与它弹出的浮层都换成这种语言,页面别处不变。跟随系统按浏览器的语言偏好挑最接近的一种,挑不到用英文;语言包第一次用到时才加载。跟随页面时xhConfig是空对象,照页面的全局配置。 - 调试日志:开发者模式与渲染日志都开着时,设计器在浏览器控制台记一组组折叠的日志,前缀是
[xh-report-designer] render:每次重画写改动的属性与耗时,画布上每张卡片(或报表主体)的取数、计算与出图各记一组耗时,取数一组里有请求与行数。预览由查看器自己画,不记。报表没有分页,分页日志开着也没有内容。日志只到控制台,不派发事件、不交给宿主。 - 扩展读设置:命令、面板与扩展从上下文的
settings读值,各分区没写的条目已经取了缺省值。
独立使用、没有宿主替你存时,给 settingsStorageKey:连上文档时若没赋 settings 就从根所在窗口的 localStorage 读,用户改了设置就写回去;标了 secret 的文本条目(如密钥)不存。读写失败派发 settings-storage-error,设置留在内存里。不给就不读写存储。
export function standalone(designer: XhReportDesigner): void {
designer.settingsStorageKey = 'report-designer:settings'
designer.addEventListener('settings-storage-error', (event) => {
console.warn('设置没能读写本机存储,这次改的只在内存里', (event as CustomEvent<{ error: unknown }>).detail.error)
})
}用代码编辑
设计器的每一步编辑都是 core 的编辑操作,宿主也可以自己执行:execute(operations) 执行一组操作并记为一步撤销,返回新的报表。core 的 plan… 函数按报表算出要执行的操作:
const card = designer.addCard(cardTemplate('chart.pie', 'sales'))
const owner = { card: card.id }
const start = designer.schema!
const dimension = planAddFieldItem(start, owner, 'dimensions', 'region')
const withDimension = applyOperations(start, dimension).document
const measure = planAddFieldItem(withDimension, owner, 'measures', 'amount')
const withMeasure = applyOperations(withDimension, measure).document
designer.execute([...dimension, ...measure, ...planFillBindings(withMeasure, card.id)])删除字段、参数或数据集用对应的 plan… 函数,引用它们的地方一并删掉:
const removal = planRemoveField(designer.schema!, 'sales', 'channel')
designer.execute(removal.operations)另有 addCard、照点物料的做法新建卡片的 addPaletteItem、select 与 getSelection、selectAll、copy、cut、paste、duplicate、deleteSelection、undo、redo、validate,以及让卡片露出来的 revealCard、在画布上标出一张卡片的 highlightCard(id | null),和 canUndo、canRedo、canPaste、canDelete 这几个只读状态;登记了导入器时,importTemplate() 与 canImportTemplate 同命令「导入模板」。
