# fappComponent - 表单应用组件扩展

表单应用内置了大量常用的组件,如文本输入、日期、密码输入组件等,能满足大部分的数据填报需求。在一些场景中也存在一些个性化的数据填报需求是内置组件无法满足的,此时就可以通过扩展一个新的表单输入组件来实现。

表单扩展组件可以做到和产品内置的组件一样的复用,由扩展开发者开发好组件扩展后,使用者可以像使用内置组件一样使用扩展组件。

本文讲述如何开发一个表单应用组件扩展。

# 扩展文件结构

  1. package.json定义扩展的配置信息
  2. main.ts定义扩展组件
  3. api
  4. properties

# package.json

示例如下:

# 详细说明

属性 类型 必需 描述
id string 组件类型id。
caption string 组件标题,通过过国际化fapp.component.caption.xxxx.caption获得显示文字。
category string 组件的分类。
group string 分组,layout, input, other 通过国际化fapp.component.group.xxxx.caption 获得显示文字。
themeCategory string 组件的主题分类。
icon string class或图片的url,icon-开头表示class,其他情况表示图片的url;当不传icon时,系统默认根据id传递图标。
depends string 依赖的模块。
definitionClassName string 组件定义的类名,来自公共依赖中。
implClass string 实现类的名称或类本身。
storeEnabled boolean 组件默认是否存储数据。
properties JSON 属性栏配置。见]属性栏配置
# id

类型id,所有组件定义的类型id不能重复,比如edit表示文本输入组件,number表示数字输入,他们的category都是input。 如:"id": "resselector",

# caption

组件标题,通过过国际化fapp.component.caption.xxxx.caption获得显示文字。默认不配置,可以国际化信息中配置。

# category

组件的分类。 如:"category": "text"

# group

分组,layout, input, other 通过国际化fapp.component.group.xxxx.caption 获得显示文字。 如:"group": "input"

# themeCategory

组件的主题分类。

# icon

class或图片的url,icon-开头表示class,其他情况表示图片的url;当不传icon时,系统默认根据id传递图标。 如:"icon": "resselector.svg"

# depends

依赖的模块。 如:"depends": "main"

# definitionClassName

组件定义的类名,来自公共依赖中。 如:"definitionClassName": "FAppResSelectorComponentBuilder"

# implClass

实现类的名称或类本身。 如:"implClass": "FAppResSelectorComponent"

# storeEnabled

是否存储数据。 如:"storeEnabled": true

# properties

组件属性栏的配置。 如:

"properties": {
	"formData": {
		"propertyName": "formData",
		"propertyType": "container",
		"items": [
			{
				"propertyName": "type",
				"propertyType": "combobox",
				"layoutTheme": "formcombobox",
				"itemIconVisible": true,
				"captionIconVisible": true,
				"captionTextField": "caption",
				"multipleSelect": false,
				"checkboxVisible": true
			},
			{
				"propertyName": "titleSetting",
				"expandVisible": false,
				"propertyType": "group",
				"items": [
					{
						"propertyName": "title",
						"propertyType": "richTextEdit",
						"saveTheme": true
					},
					{
						"propertyName": "desc",
						"propertyType": "textArea",
						"layoutTheme": "formtextarea"
					},
					{
						"propertyName": "visibleEnabled",
						"propertyType": "checkbox"
					},
					{
						"propertyName": "visibleCondition",
						"propertyType": "expEdit",
						"visibleCondition": "!!visibleEnabled",
						"fieldPanelImpl": {
							"depends": "commons/tree",
							"implClass": "Tree",
							"iconVisible": true,
							"dragable": true,
							"dragMoveable": false
						},
						"showCaption": false
					},
					{
						"propertyName": "editEnabled",
						"propertyType": "checkbox"
					},
					{
						"propertyName": "editCondition",
						"propertyType": "expEdit",
						"fieldPanelImpl": {
							"depends": "commons/tree",
							"implClass": "Tree",
							"iconVisible": true,
							"dragable": true,
							"dragMoveable": false
						},
						"showCaption": false,
						"visibleCondition": "!!editEnabled"
					},
					{
						"propertyName": "placeholder",
						"propertyType": "edit"
					}
				]
			},
			{
				"propertyName": "contentSetting",
				"propertyType": "group",
				"expand": false,
				"inlineItem": {
					"propertyName": "value",
					"propertyType": "expEdit",
					"fieldPanelImpl": {
						"depends": "commons/tree",
						"implClass": "Tree",
						"iconVisible": true,
						"dragable": true,
						"dragMoveable": false
					}
				},
				"items": [
					"defaultValue",
					{
						"propertyName": "calcCondition",
						"propertyType": "expEdit",
						"captionPosition": "top",
						"fieldPanelImpl": {
							"depends": "commons/tree",
							"implClass": "Tree",
							"iconVisible": true,
							"dragable": true,
							"dragMoveable": false
						}
					}
				]
			},
			{
				"propertyName": "checkSetting",
				"propertyType": "group",
				"items": [
					{
						"propertyName": "notNull",
						"propertyType": "checkbox"
					},
					{
						"propertyName": "validEnabled",
						"propertyType": "checkbox"
					},
					{
						"propertyName": "validExp",
						"propertyType": "expEdit",
						"fieldPanelImpl": {
							"depends": "commons/tree",
							"implClass": "Tree",
							"iconVisible": true,
							"dragable": true,
							"dragMoveable": false
						},
						"visibleCondition": "!!validEnabled"
					},
					{
						"propertyName": "validMessage",
						"propertyType": "expEdit",
						"contentType": "macro",
						"fieldPanelImpl": {
							"depends": "commons/tree",
							"implClass": "Tree",
							"iconVisible": true,
							"dragable": true,
							"dragMoveable": false
						},
						"visibleCondition": "!!validEnabled"
					},
					{
						"propertyName": "setValidExp",
						"propertyType": "link",
						"showCaption": "false",
						"enabled": true,
						"visibleCondition": "!!validEnabled"
					}
				]
			},
			{
				"propertyName": "advancedSetting",
				"propertyType": "group",
				"expand": false,
				"items": [
					{
						"propertyName": "storeEnabled",
						"propertyType": "checkbox"
					},
					{
						"propertyName": "dbtable",
						"propertyType": "combobox",
						"itemIconVisible": true,
						"captionIconVisible": false,
						"visibleCondition": "!!storeEnabled"
					},
					{
						"propertyName": "dbfield",
						"propertyType": "combobox",
						"itemIconVisible": true,
						"captionIconVisible": false,
						"arbitraryInputEnabled": true,
						"type": "tree",
						"visibleCondition": "!!storeEnabled"
					},
					{
						"propertyName": "dbfieldConditionEnabled",
						"propertyType": "checkbox",
						"visibleCondition": "!!storeEnabled"
					},
					{
						"propertyName": "dbfieldCondition",
						"propertyType": "edit",
						"visibleCondition": "!!dbfieldConditionEnabled"
					},
					{
						"propertyName": "rememberLastValue",
						"propertyType": "checkbox",
						"visibleCondition": "!!storeEnabled"
					},
					{
						"propertyName": "rootPath",
						"propertyType": "edit"
					},
					{
						"propertyName": "resourceType",
						"propertyType": "combobox",
						"multipleSelect": true,
						"items": [
							"all",
							"tbl",
							"fold"
						]
					}
				]
			}
		]
	},
	"style": {
		"propertyName": "style",
		"propertyType": "container",
		"items": [
			"compStyle",
			{
				"propertyName": "inputTitleSetting",
				"propertyType": "group",
				"expand": true,
				"items": [
					{
						"propertyName": "showTitle",
						"propertyType": "checkbox",
						"defaultValue": true,
						"saveTheme": true
					},
					{
						"propertyName": "titlePosition",
						"propertyType": "combobox",
						"items": [
							"left",
							"top"
						],
						"keyPrefix": "ppteditor.title.position",
						"visibleCondition": "!!showTitle",
						"saveTheme": true
					},
					{
						"propertyName": "titleWidth",
						"propertyType": "spinner",
						"visibleCondition": "!!showTitle",
						"suffix": "%",
						"max": 50,
						"min": 10,
						"defaultValue": 30,
						"saveTheme": true
					}
				]
			},
			{
				"propertyName": "fontSetting",
				"propertyType": "group",
				"expand": false,
				"items": [
					{
						"propertyName": "font",
						"propertyType": "fontEditor",
						"saveTheme": true,
						"expand": false
					}
				]
			},
			{
				"propertyName": "fill",
				"propertyType": "fill",
				"saveTheme": true
			},
			{
				"propertyName": "border",
				"propertyType": "border",
				"saveTheme": true
			},
			{
				"propertyName": "width",
				"propertyType": "slider",
				"saveTheme": true,
				"visibleCondition": "widthVisible == true",
				"max": 360,
				"min": 70
			},
			{
				"propertyName": "layoutSetting",
				"propertyType": "group",
				"expand": false,
				"visibleCondition": "layoutVisible == true",
				"inlineItem": {
					"propertyName": "layout",
					"propertyType": "combobox",
					"saveTheme": true,
					"items": [
						"default",
						"oneQuarter",
						"oneThird",
						"half",
						"twoThirds",
						"threeQuarters"
					],
					"defaultValue": "default",
					"cleanIconVisible": true
				},
				"items": [
					{
						"propertyName": "inputBoxSize",
						"propertyType": "selectPanel",
						"captionPosition": "top",
						"items": [
							"small",
							"middle",
							"large"
						],
						"defaultValue": "middle",
						"saveTheme": true
					}
				]
			},
			{
				"propertyName": "padding",
				"propertyType": "padding",
				"saveTheme": true
			},
			{
				"propertyName": "margin",
				"propertyType": "padding",
				"saveTheme": true
			},
			{
				"propertyName": "conditionSetting",
				"propertyType": "group",
				"items": [
					{
						"propertyName": "conditionStyle",
						"propertyType": "valueDecoratedButton"
					}
				]
			}
		]
	}
}

# main.ts

示例如下:

# 详细说明

一个组件包括数据对象Builder和UI对象Component。二者缺一不可。

# 数据对象Builder
# 数据对象Constructor
constructor(args: FAppComponentBuilderArgs)
# FAppComponentBuilderArgs
属性名 类型 描述
form FAppFormBuilder 组件所属的表单。
# 组件编译Compile
/**
 * 分析组件中填报需要的属性。并且分析出模型字段信息。有可能一个组件需要对应多个字段。
 */
public compile(context: FAppExpContext): void;
# UI对象Component
# UI对象Constructor
constructor(args: FAppComponentArgs)

当组件初始化时会通过构造函数初始化组件。

# FAppComponentArgs
属性名 类型 描述
type FAppComponentType 组件类型。扩展组件可自定义类型
builder FAppComponentBuilder 组件的数据对象。
compiledInfo FAppComponentCompiledInfo 组件编译信息,预览和填报界面需要传此参数。
viewMode FAppViewMode 显示模式。包括编辑模式、预览模式和填报模式。
deviceType DeviceType 设备类型。
dataManager FAppFormsDataMgr 填写数据的管理对象。
# 加载数据
/**
 * 加载组件数据
 */
public loadData(data?: FAppComponentData): Promise<void>;
/**
 * 根据数据刷新状态和样式
 */
public doRefresh(compData: FAppComponentData, property?: string): void;
/**
 * 值变化的事件,一般此时需要同步修改数据层数据。
 */
protected doChange(event: SZEvent, component?: Component, item?: any): void;
/**
 * 增量刷新组件数据或属性
 */
public refreshData(data: FAppComponentData, property?: string): Promise<void>;
/**
 * 增量刷新组件内的浮动行。只有表格、列表、子表单等填报多行数据的组件才需要实现此方法。
 * @param rows 发生修改的浮动行
 */
public refreshRows?(rows: Array<{
	/** 浮动行数据 */
	row: FAppFloatAreaDataRow,
	/** 操作类型:`+`代表是新增的行,`-`代表是删除的行,无此属性代表是修改的行 */
	op: '+' | '-'
}>): Promise<void>

所有表单扩展组件都需要实现以上函数。

# dispose

销毁组件,如清理组件DOM上绑定的事件。

# 示例参考

示例 描述
resourceSelector (opens new window) 资源选择组件。

# api

表单应用扩展开发接口中包括,IFAppComponent(组件扩展开发接口)。

场景一:如果想开发一个个性化输入组件,那么你需要实现IFAppComponent开发个性化组件。并实现该接口中一些方法。

/// <amd-module name="metadata/metadata-script-api"/>

/**
 * 此文件定义系统的前端开发接口相关的接口和类型,需要注意:
 * 
 * 1. 此文件只定义`interface`、`enum`等类似,并不编写业务逻辑代码实现,所以对本文件的依赖不会导致运行时的更多js的依赖
 * 2. 
 * 3. <https://docs.succbi.com/develop/references/embedding-scripts/>
 * 4. <https://docs.succbi.com/develop/references/embedding-scripts/hooks/custom.js.html>
 */

import type {
	ITemplatePagePartRenderer,
	TemplatePagePartArgs
} from "app/app-templatepage";
import type {
	BOPageBuilder
} from "bo/bo-builder";
import type {
	IExpEvalDataProvider
} from "commons/exp/expeval";
import type {
	ExportFileConfigs,
	ExportFileHooks
} from "commons/export";
import type {
	TableBuilder
} from "commons/table/table";
import type {
	IDwTableEditorPanel
} from "dw/dwtable";
import {
	Component,
	UrlInfo,
	browser,
	downloadFile,
	rc_task,
	rcuuid
} from "sys/sys";
import type {
	BOComponentArgs,
	IBOComponent,
	IBOPage
} from "../bo/bo-browser";
import type {
	IBODataManager,
	IBONodeData
} from "../bo/bo-datamgr";
import type {
	IMetaFileViewer,
	MetaFileViewerArgs
} from "./metadata";

import FilterClauseInfo = succ.types.FilterClause;
/**
 * 该文件定义系统前端钩子函数和脚本接口。
 * 
 * 系统前端脚本分为如下几大类:
 * 
 * 1. 页面脚本。页面包括仪表板、报表、superpage、表单。脚本定义在页面内部,会产生一个隐藏的元数据文
 *    件和页面文件一起删除、重命名、移动等。
 * 2. 应用脚本。应用脚本用于定义应用中多个文件公用的页面脚本。脚本定义在应用的`app.ts`中。
 * 3. 项目脚本。用于定义项目中多个文件公用的页面脚本。脚本定义在系统项目的`public/project.ts`的
 *    `ProjectScript`类中。
 * 4. 系统脚本。用于介入元数据管理界面的构造。脚本定义在系统项目的`public/project.ts`的
 *    `ProjectScript`类中。
 * 
 * *页面脚本*
 * 
 * 几个BO模块的页面采用统一的方式调用钩子函数。钩子函数的定义分为几个不同的层级,采用冒泡的机制调
 * 用,顺序依次是:
 * 
 * 1. 页面内脚本
 * 2. 应用脚本
 * 3. 项目脚本
 * 
 * 例如,当调用`onDidRefreshPage`时,按页面->应用->项目的顺序依次调用,如果某个钩子函数实现返回
 * `Promise`会等待完成后再进行后续调用。举个例子,应用中的`onDidRefreshPage`在每个页面的右上角显示一
 * 个帮助图标,点击显示帮助文档。该钩子函数不影响页面内的特定钩子函数实现。
 * 
 */

/**
 * 页面导出脚本钩子函数的参数。
 */
export interface PageExportScriptEvent extends succ.bo.script.PageScriptEvent {

	/**
	 * 业务对象的导出配置,包括导出形式、导出哪些sheet等。
	 * 
	 * 可以修改这个属性,影响后面的导出过程。
	 */
	exportArgs: BOPageExportArgs;

	/**
	 * 底层导出的控制配置。
	 * 
	 * 可以进行没有提供UI的精细化的设置,如:
	 * 1. 生成的pdf的版本;
	 * 2. 注册新的pdf字体
	 * 
	 * 默认为`null,通过给该属性赋值的方式设置导出的底层控制参数。
	 */
	exportConfigs?: ExportFileConfigs;

	/**
	 * 导出的钩子函数。
	 * 
	 * 通过钩子函数,开发者可以介入构造导出元素、生成导出文件的各个过程,如
	 * 1. 通过 {@link ExportFileHooks.onAddPage} 忽略某页;
	 * 2. 通过 {@link ExportFileHooks.onAddNode} 忽略某元素;
	 * 3. 通过 {@link ExportFileHooks.xlsx.afterParseFileInfo} 修改即将导出的excel单元格样式。
	 * 
	 * 默认为`null,通过给该属性赋值的方式按需添加导出的底层钩子函数。
	 */
	exportHooks?: ExportFileHooks;
}

/**
 * 执行脚本交互中指定的函数定义。
 */
export interface CustomActionFunctions {
	[actionName: string]: (event: InterActionEvent) => boolean | void | Promise<boolean | void>;
}

/**
 * 前端执行的自定义表达式函数。
 */
export interface CustomExpFunctions {
	[functionName: string]: (context: IExpEvalDataProvider, ...args: any) => string | Promise<string>;
}

/**
 * 前端脚本数据集。
 */
export interface CustomDatasets {
	[dataSetName: string]: IScriptDatasetProvider;
}

/**
 * 文件的脚本接口。
 * 
 * 所有BO的脚本接口都从该接口继承,包括:
 * 
 * 1. 数据管理脚本。{@link DwScript}
 * 2. 应用脚本。{@link ITemplatePageScript}
 * 3. 模板页面脚本。{@link IMobilePageScript}
 * 4. BO页面脚本。{@link IPageScript}
 * 
 * TODO: 门户和移动应用也继承了bo对象,但脚本接口没有继承,bo脚本接口应该有一个通用的更抽象的接口定义。
 */
export interface IFileScript {
}

/**
 * 页面脚本的提供者。
 * 
 * 应用和项目脚本都可以提供页面脚本,优先级低于页面本身定义的脚本。
 */
export interface IPageScriptProvider {

	/**
	 * 根据文件信息返回页面对应的脚本。
	 * 
	 * 例如所有的spg都公用的脚本。
	 * 
	 * @param file 文件信息。对于新建的未保存的文件,文件信息中只有部分属性,如
	 * `type`、`projectName`、`parentDir`。
	 * @returns
	 */
	getPageScript?(file: Partial<MetaFileInfo>): IPageScript;
}

/**
 * 自定义脚本函数的提供者。
 * 
 * 页面、应用、项目脚本都实现该接口,从不同级别提供自定义函数。通用函数按不同范围可以定义在应用或项
 * 目下。
 */
export interface ICustomScriptProvider {

	/**
	 * 返回执行脚本交互的函数。
	 */
	getCustomActionFunctions?(): CustomActionFunctions;

	/**
	 * 返回前端自定义表达式函数。
	 */
	getCustomExpFunctions?(): CustomExpFunctions;

	/**
	 * 返回脚本数据集。
	 */
	getCustomDatasets?(): CustomDatasets;
}

/**
 * 项目脚本。
 * 
 * 项目脚本定义在项目的public目录下,以`project.ts`命名。
 * 
 * 项目脚本导出命名为`ProjectScript`的class,向项目内所有页面(包括应用内页面)提供页面脚本和脚本函
 * 数。
 */
export interface IProjectScript extends ICustomScriptProvider, IPageScriptProvider {

}

/**
 * 应用脚本。
 * 
 * 应用脚本定义在应用的`代码`目录下,以`app.ts`命名。应用脚本提供:
 * 
 * 应用脚本导出命名为`AppScript`的class,向应用内所有页面提供页面脚本和脚本函数。
 * 
 * 应用脚本中定义的钩子函数和脚本函数优先级高于项目脚本。
 */
export interface IAppScript extends ICustomScriptProvider, IPageScriptProvider {

}

/**
 * 数据模块的脚本接口。
 * @TODO 待设计
 */
export interface DwScript extends IFileScript {
	/**
	 * 当准备在数据表编辑界面中的下部创建一个子面板时调用
	 * @param panel 面板对象
	 */
	onInitPanel?(panel: IDwTableEditorPanel): void;
	/**
	 * 在数据表编辑界面中的下部的创建一个子面板后调用
	 * @param panel 
	 */
	onDidInitPanel?(panel: IDwTableEditorPanel): void;

	/**
	 * 对子面板进行操作,导致界面进行刷新前进行操作
	 * @param panel 
	 */
	onRefreshPanel?(panel: IDwTableEditorPanel): void | Promise<void>;

	/**
	 * 对子面板进行操作导致界面刷新后进行操作
	 * @param panel 
	 */
	onDidRefreshPanel?(panel: IDwTableEditorPanel): void | Promise<void>;
}

/**
 * 模板页面的脚本接口。
 */
export interface ITemplatePageScript extends IFileScript, ICustomScriptProvider {
	/**
	 * 当在浏览器窗口中打开一个新的门户框架时调用,调用此函数时门户框架还未完成初始化。
	 * 
	 * @param path 文件路径
	 * @param args url参数
	 * @returns 返回`Promise`表示此函数是异步的,系统需要等待它`resolve`后再继续。
	 */
	onInitFrame?(path?: string, args?: UrlInfo): void | Promise<void>;

	/**
	 * 当在浏览器窗口中打开一个新的门户框架时调用,调用此函数时门户框架已完成初始化,但页面相关元数据内容可能还未完全显示完毕。
	 * 
	 * @param frame 门户框架
	 * @returns 返回`Promise`表示此函数是异步的,系统需要等待它`resolve`后再继续。
	 */
	onDidInitFrame?(frame: Component): void | Promise<void>;

	/**
	 * 初始化门户框架内部的一个组件时调用,比如:
	 * 1. 初始化左侧资源树
	 * 2. 初始化顶部的标签页
	 * 
	 * 此事件调用时组件还未创建,开发者可以通过实现此函数重新设置构造参数,或者重载门户默认的组件实现。
	 * 
	 * @param frame 门户框架
	 * @param rootRes 组件要渲染的“根”资源信息
	 * @param initArgs 
	 * @param implClass 
	 */
	onInitFrameComponent?(frame: Component, initArgs: TemplatePagePartArgs, implClass: Constructable<ITemplatePagePartRenderer>, rootRes?: ResourceRefInfo): void | ITemplatePagePartRenderer | Promise<void | ITemplatePagePartRenderer>;

	/**
	 * 初始化门户框架内部的一个组件后调用,比如:
	 * 1. 初始化左侧资源树
	 * 2. 初始化顶部的标签页
	 * 
	 * 此事件调用时组件已创建,开发者可以通过实现此函数重新设置组件的一些事件或属性。
	 * 
	 * @param frame 门户框架
	 * @param rootRes 组件要渲染的“根”资源信息
	 * @param comp 组件对象
	 */
	onDidInitFrameComponent?(frame: Component, comp: ITemplatePagePartRenderer, rootRes?: ResourceRefInfo): void | Promise<void>;

	/**
	 * 当准备构建一个新的“文件查看器”对象时调用。
	 * 
	 * 1. 此函数只会在门户应用内被调用,当用户在门户应用内点击一个资源门户应用要构造一个新的`IMetaFileViewer`来显示它,此时会调用此函数。
	 * 2. 实现者可以修改参数`viewerInitArgs`中的信息,以实现给新构造的`IMetaFileViewer`对象传递个性化信息
	 * 3. 实现者甚至可以自己构造自己的`IMetaFileViewer`返回
	 * 
	 * @param viewerInitArgs 构造`IMetaFileViewer`时传递的参数,实现者可以修改
	 * @returns 返回`Promise`表示此函数是异步的,系统需要等待它`resolve`后再继续,如果Promise返回了
	 * 	新的`IMetaFileViewer`,那么将使用脚本返回的`IMetaFileViewer`实现。
	 */
	onInitFileViewer?(viewerInitArgs: MetaFileViewerArgs): void | Promise<IMetaFileViewer>;

	/**
	 * 当构造了一个新的“文件查看器”对象后时调用。
	 * 
	 * 1. 此函数只会在门户应用内被调用,当用户在门户应用内点击一个资源门户应用要构造一个新的`IMetaFileViewer`来显示它,在构造完毕后会调用此函数。
	 * 2. 调用此函数时`IMetaFileViewer`已经存在,但是元数据还未渲染完毕。
	 * 
	 * @param viewer 文件的查看器
	 * @returns 返回`Promise`表示此函数是异步的,系统需要等待它`resolve`后再继续。
	 */
	onDidInitFileViewer?(viewer: IMetaFileViewer): void | Promise<void>;

	/**
	 * 有页面要准备显示时调用。
	 * 
	 * 1. 门户内的某个子资源页面显示前调用
	 * 2. 发生下钻时,下钻出的新页面显示前调用
	 * @param component 将要显示的页面控件
	 */
	onShow?(component: Component): void;
	/**
	 * 有页面要准备隐藏时调用。
	 * 
	 * 1. 门户内用户点击某个子资源页面时会隐藏当前显示的页面。
	 * 2. 发生下钻时,下钻出的新页面显示前调用会覆盖当前显示的页面
	 * @param component 将要隐藏的页面控件
	 */
	onHide?(component: Component): void;

	/**
	 * 有页面已显示时调用。
	 * 
	 * 1. 门户内的某个子资源页面显示后调用
	 * 2. 发生下钻时,下钻出的新页面显示后调用
	 * @param component 已显示的页面控件
	 */
	onDidShow?(component: Component): void;
	/**
	 * 有页面已隐藏时调用。
	 * 
	 * 1. 门户内用户点击某个子资源页面时会隐藏当前显示的页面。
	 * 2. 发生下钻时,下钻出的新页面显示前调用会覆盖当前显示的页面
	 * @param component 已隐藏的页面控件
	 */
	onDidHide?(component: Component): void;

	/**
	 * 页面被销毁时触发的事件。
	 * 
	 * 1. 门户内,用户关闭了某页面
	 * 
	 * @param viewer 文件的查看器
	 */
	onClose?(viewer: IMetaFileViewer): void;
}

/**
 * 移动应用的脚本接口。
 */
export interface IMobilePageScript extends IFileScript, ICustomScriptProvider {

}

/**
 * 页面前端脚本。
 * 
 * 页面前端脚本可以定义钩子函数、自定义函数和脚本数据集。
 * 
 * 页面脚本在使用时会按优先级合并应用和项目下的通用钩子函数。
 */
export interface IPageScript extends IFileScript, ICustomScriptProvider, succ.bo.script.IPageHandleEventScript {

	/**
	 * 页面显示全局加载动画前触发。
	 * 
	 * 实现该钩子函数可以替换页面默认的加载动画,和{@link onHideLoading}一起实现。
	 * 
	 * @param event 
	 * @returns 返回当前正在等待的promise。
	 */
	onShowLoading?(event: succ.bo.script.PageScriptEvent): Promise<void>;

	/**
	 * 页面隐藏全局加载动画前触发。
	 * 
	 * 实现该钩子函数可以替换页面默认的加载动画,和{@link onShowLoading}一起实现。
	 * 
	 * @param event 
	 */
	onHideLoading?(event: succ.bo.script.PageScriptEvent): void;

	/**
	 * 刷新页面前触发。
	 * 
	 * 调用该函数时页面已经创建并完成必要初始化但还未开始渲染。此时页面的根DOM已经创建,组件还未创
	 * 建。
	 * 
	 * @param args 
	 * @param args.page 页面对象。 
	 * @returns Fulfilled - 继续页面渲染。Rejected - 阻止页面渲染。
	 */
	onRefreshPage?(args: succ.bo.script.PageScriptEvent): void | Promise<void>;

	/**
	 * 刷新页面后触发。
	 * 
	 * 调用该函数时页面已经渲染完毕,但渲染的Promise尚未结束,页面渲染的加载动画还未结束。
	 * 
	 * @param args 
	 * @param args.page 页面对象。 
	 * @returns Fulfilled - 完成页面渲染。Rejected - 显示错误信息并完成页面渲染。
	 */
	onDidRefreshPage?(args: succ.bo.script.PageScriptEvent): void | Promise<void>;

	/**
	 * 创建一个组件UI对象前触发。
	 * 
	 * 通过该钩子函数可以修改创建组件的行为,如替换组件的`class`。
	 * 
	 * @param args 构造参数
	 * @param args.componentArgs 创建组件的构造参数。
	 * @returns
	 * 1. `null` 不进行个性化,使用系统默认的实现
	 * 2. `null` 直接修改传入参数,修改构造参数,还是用系统默认的UI组件
	 * 3. 返回新的控件UI对象,替换系统默认的实现
	 */
	onCreateComponent?(args: succ.bo.script.PageScriptEvent): void | IBOComponent;

	/**
	 * 组件销毁前触发。
	 * 
	 * 调用该函数时组件准备被销毁。
	 * 
	 * @param args 
	 * @param args.component 组件对象。 
	 */
	onDisposeComponent?(args: succ.bo.script.PageScriptEvent): void;

	/**
	 * 组件刷新前触发,初次渲染和增量渲染时触发。
	 * 
	 * 调用该函数时,组件的渲染信息尚未被组件消费。此时可以修改渲染信息、组件属性以控制每一次渲染。
	 * 
	 * @param args 
	 * @param args.component 组件对象。 
	 * @returns Fulfilled - 继续组件刷新。Rejected - 阻止组件刷新。
	 */
	onRefreshComponent?(args: succ.bo.script.PageScriptEvent): void | Promise<void>;

	/**
	 * 组件刷新后触发,初次渲染和增量渲染时触发。
	 * 
	 * 调用该函数时组件已经渲染完成。如果是容器组件,则容器组件内的所有下级组件渲染完成。当组件中断
	 * 渲染或渲染出现异常时,不会调用该函数。
	 * 
	 * @param args 
	 * @param args.component 组件对象。 
	 * @returns Fulfilled -完成组件刷新。Rejected - 完成组件刷新并显示错误信息。
	 */
	onDidRefreshComponent?(args: succ.bo.script.PageScriptEvent): void | Promise<void>;

	/**
	 * 组件渲染前DOM前触发。
	 * 
	 * 调用该函数时组件已经准备好数据状态,尚未开始渲染。此处可以修改组件数据状态,从而调整组件的渲
	 * 染结果。
	 * 
	 * @param args 
	 * @param args.component 组件对象。 
	 * @param args.uiView 组件渲染数据状态。
	 * @returns Fulfilled - 完成组件刷新。Rejected - 完成组件刷新并显示错误信息。
	 * @deprecated onRenderingStart
	 */
	onRenderComponent?(args: succ.bo.script.PageScriptEvent): void | Promise<void>;

	/**
	 * 组件开始实际渲染之前调用。
	 * 
	 * 组件可以在实际渲染之前先准备好数据,将准备好的数据传入钩子函数以供二次开发修改,再将经过
	 * 钩子函数处理的数据进行实际渲染。
	 * 
	 * 例如使用`echarts`实现的柱形图,在{@link IComponentRendererExt.render}中构造好`echarts`的
	 * option后调用该钩子函数,二次开发脚本中修改option后再交由组件扩展渲染。
	 * 
	 * @param args 
	 * @param args.component 组件对象。 
	 * @param args.uiView 组件渲染数据状态。
	 * @returns 
	 */
	onRenderingStart?(args: succ.bo.script.PageScriptEvent): void;

	/**
	 * 组件成功渲染后调用。
	 * 
	 * @param args 
	 * @param args.component 组件对象。 
	 * @returns 
	 */
	onRenderingFinish?(args: succ.bo.script.PageScriptEvent): void;

	/**
	 * 组件渲染失败后调用。
	 * 
	 * @param args 
	 * @param args.component 组件对象。 
	 * @param args.error 组件渲染失败的错误信息。
	 * @returns 
	 */
	onRenderingFail?(args: succ.bo.script.PageScriptEvent): void;


	/**
	 * 页面导出前触发。
	 * 
	 * 调用本函数前,用户已经设置好导出选项(如导出类型、导出范围),准备开始导出。
	 * 此处可以阻止导出,也可以修改导出选项,也可以设置更多导出的钩子函数,介入导出的各个环节,修改导出元素等。
	 * 
	 * @param args 
	 * @param args.page 页面对象。
	 * @param args.exportArgs 导出的基础属性,如导出类型、导出的范围等。不为`null`,可以在这个钩子函数中修改这些属性。
	 * @param args.exportConfig 导出的底层属性,可以通过给该属性赋值的方式设置导出的底层控制参数(如生成的pdf版本)。
	 * @param args.exportEvents 导出的底层钩子函数,通过给该属性赋值的方式按需添加导出的底层钩子函数。
	 * @returns Fulfilled - 继续页面渲染。Rejected - 阻止页面渲染。
	 */
	onExportPage?(args: PageExportScriptEvent): void | Promise<void>;
}

/**
 * 系统数据项目下的脚本接口(project.ts),在普通项目脚本的基础上加了元数据管理界面的事件接口,
 * 用于个性化元数据管理界面。
 * 系统数据项目下提供的脚本接口可作用于所有项目,但优先级最低,可被普通项目和应用下的脚本覆盖。
 */
export interface ISysProjectScript extends IProjectScript {
	/**
	 * 当在浏览器窗口中打开一个新的元数据管理界面时调用,调用此函数时UI框架还未完成初始化。
	 * 
	 * @param path 文件路径
	 * @param args url参数
	 * @returns 返回`Promise`表示此函数是异步的,系统需要等待它`resolve`后再继续。
	 */
	onInitMetaMgr?(path: string, args: UrlInfo): void | Promise<void>;

	/**
	 * 当在浏览器窗口中打开一个元数据管理界面时调用,调用此函数时UI框架已完成初始化,但页面相关元数据内容可能还未完全显示完毕。
	 * 
	 * @param metamgr 元数据管理界面
	 * @returns 返回`Promise`表示此函数是异步的,系统需要等待它`resolve`后再继续。
	 */
	onDidInitMetaMgr?(metamgr: Component): void | Promise<void>;
}

/**
 * 刷新dataset数据参数。
 * 不缓存多页数据。
 */
export interface IDatasetRefreshArgs {
	/**是否强制刷新,为true时无论数据集依赖的条件、参数和分页设置是否变化,都会触发查询。 */
	force?: boolean;
	/**要查询的字段,如果没有传入,则使用上次查询的字段,如果上次查询字段为空,则不查询。 */
	fields?: string[];
	/**是否要查询所有字段,默认为false */
	queryAllFields?: boolean;
	/**每行行数,如果没有传入,则使用上次的设置 */
	pageSize?: number;
	/**分页数,如果没有传入,则使用上次的设置 */
	pageNum?: number;
	/**是否刷新总行数。 */
	refreshTotalRowCount?: boolean;
	/**是否通知数据变化事件。由控件触发的refresh不应该触发事件变化,否则就循环了。 */
	notifyChange?: boolean;
}

/**
 * fetchCustomData的参数。
 */
export interface IDatasetCustomFetchDataArgs extends IDatasetRefreshArgs {
	/**过滤条件 */
	filter?: FilterClauseInfo[];
	/**是否要下载维键的文字 */
	needCodeDesc?: boolean;
}

/**
 * 刷新dataset的总行数参数。
 */
export interface IDatasetRefreshTotalRowCountArgs {
	/**是否强制刷新,为true时无论数据集依赖的条件、参数和分页设置是否变化,都会触发查询。 */
	force?: boolean;
	/**是否通知数据变化事件。由控件触发的refresh不应该触发事件变化,否则就循环了。 */
	notifyChange?: boolean;
}

/**
 * 刷新dataset的按字段分组后的行数参数。
 */
export interface IDatasetRefreshGroupByTotalRowCountArgs extends IDatasetRefreshTotalRowCountArgs {
	/**分组字段。 */
	groupByFields?: string[];
}

/**
 * 脚本交互行为事件。
 * @deprecated
 */
export interface InterActionEvent {
	/**
	 * 当前页面对象。
	 * @deprecated 有renderer可以获取到data
	 */
	dataManager: IBODataManager;

	/**
	 * 当前页面的UI对象。
	 */
	page?: IBOPage;

	/**
	 * 用户所点击的控件。
	 * @deprecated 感觉又uicomponent就好了
	 */
	component?: IBONodeData;

	/**
	 * 触发事件的UI对象,使用的地方按需强制转换
	 * @TODO 可以重命名为component
	 */
	uicomponent?: IBOComponent;

	/**
	 * 对应的dataset数据行。
	 * @deprecated 应该通过component去获取数据
	 */
	dataRow?: JSONObject;

	/**
	 * 控件的数据对象,不同控件包含的属性不同,通过修改这些属性可以实现修改控件渲染结果的目的。
	 */
	data?: InterActionEventData;

	/**
	 * 原始的浏览器事件对象。
	 */
	event?: Event;

	/**
	 * 传递的参数,可用于脚本交互传递参数。
	 */
	params?: JSONObject;
}

/**
 * InterActionEvent的data属性的类型。通过修改data中的属性可以达到修改后续渲染结果的目的。
 * 
 * @example
 * event.data.echartOption.series[0].type='bar' 可以使渲染的echarts的第0个系列显示为柱形图。
 * 
 */
export interface InterActionEventData {

	/**echarts图形控件的option,如柱形图、折线图、地图中使用到echarts的图层等 */
	echartOption?: JSONObject;

	/**计算出来的控件value,重新设置该属性可以修改控件输出的值,如文本、下拉框等 */
	value?: any;

	/**计算出来的控件txt,重新设置该属性可以修改控件输出的标题,如下拉框等 */
	txt?: any;

	/**对于数据集类型的控件,比如列表、tree等,dataset表示它的数据,通常是一个数组,每个元素是一个json,key是字段名(不带模型前缀) */
	dataset?: Array<any>;

	/**表格控件的TableBuilder实例,如列表,分组表等 */
	tableBuilder?: TableBuilder;

}

/**
 * 这个命名空间定义各个模块的一些二次开发或跨模块引用时经常用到的一些api函数。
 * 
 * 这些api函数通常各自独立,用完即走。
 */
export namespace API {
	export namespace ana {

		/**
		 * 批量导出分析对象为 pdf | excel | csv。
		 * 总是会下载一个压缩文件。
		 * @param args
		*/
		export function exportContent(args: BatchExportBOObjectArgs, download = true): Promise<RcExportResultInfo> {
			let taskId = rcuuid();
			return rc_task({
				url: '/api/ana/services/export',
				uuid: taskId,
				data: args
			}).then((result: RcExportResultInfo) => {
				download && downloadFile(`/downloadservice/${result.downloadId}`);
				return result;
			});
		}
	}

	/**
	 * CI模块提供的一些api。
	 */
	export namespace ci {
	}

	/**
	 * dw模块的api
	 */
	export namespace dw {

		/**
		 * 删除或清空数据
		 */
		export function deleteData(args: {
			/**相关的资源id或路径,后端将会根据用户对这个资源的权限来进行数据约束判断*/
			referredResId: string,
			/**相关的资源修改时间,后端将会根据用户对这个修改时间版本的资源的权限来进行数据约束判断*/
			referredResModifyTime?: number,
			/**要删除数据的数据集的id*/
			datasetId?: string,
			/**是否是清空所有数据,注意只是清空权限范围内的数据,根据数据集的约束来清空 */
			deleteAll?: boolean,
			/**删除的数据的id */
			ids?: Array<string>,
			/**后端的数据集约束有些需要用到参数,此时需要吧可能用到的参数的值传递给后端*/
			params?: JSONObject,
		}): Promise<{
			/**成功删除了多少行数据 */
			rowCount: number,
		}> {
			return null;
		}

		/**
		 * 导入文件数据到指定的表。
		 */
		export function importFileData(args: {
			/**相关的资源id或路径,后端将会根据用户对这个资源的权限来进行数据约束判断*/
			referredResId: string,
			/**相关的资源修改时间,后端将会根据用户对这个修改时间版本的资源的权限来进行数据约束判断*/
			referredResModifyTime?: number,
			/** 提前上传好的文件的文件id*/
			fileId: string,
			/** 文件名 */
			fileName?: string,
			/** 目标数据集id,是用户在“相关资源”中提前定义好的数据集的id */
			targetDatasetId?: string,
			/** 导入同名字段的数据 */
			importSameFields?: boolean,
			/** 关联数据集 */
			joinDatasetId?: string;
			/** 关联数据集字段 */
			joinDatasetField?: string;
			/** 关联目标数据集字段 */
			joinFileField?: string;
			/** 需要额外写入到目标表的字段值*/
			fieldValues?: [{
				/** 目标表字段名,可以是逻辑名也可以是物理名 */
				name: string;
				/** 
				 * const: 表示是常量,可能是number,也可能是字符串
				 * fileField:文件列名,支持模糊匹配
				 * joinDatasetField:表示是关联表的一个字段
				 */
				valueType: "const" | "fileField" | "joinDatasetField",
				value?: string | number;
			}]
			/**后端的数据集约束有些需要用到参数,此时需要吧可能用到的参数的值传递给后端*/
			params?: JSONObject,
		}): Promise<{
			/**覆盖了多少行数据 */
			overwriteCount: number,
			/**成功导入了多少行数据 */
			rowCount: number
			/**与关联表正确匹配了多少行数据 */
			matchedCount: number,
		}> {
			return null;
		}

		/**
		 * 将一个查询的数据复制到另一个表
		 */
		export function copyDataTo(args: {
			/**相关的资源id或路径,后端将会根据用户对这个资源的权限来进行数据约束判断*/
			referredResId: string,
			/**相关的资源修改时间,后端将会根据用户对这个修改时间版本的资源的权限来进行数据约束判断*/
			referredResModifyTime?: number,
			/** 目标数据集id,是用户在“相关资源”中提前定义好的数据集的id */
			sourceDatasetId?: string,
			/** 目标数据集id,是用户在“相关资源”中提前定义好的数据集的id */
			targetDatasetId?: string,
			/**覆盖模式 */
			overwriteMode?: "all" | "append",
			/** 复制同名字段的数据 */
			importSameFields?: boolean,
			/** 需要额外写入到目标表的字段值*/
			fieldValues?: [{
				/** 目标表字段名,可以是逻辑名也可以是物理名 */
				name: string;
				/** 
				 * const: 表示是常量,可能是number,也可能是字符串
				 * field: 表示是一个源表的字段
				 */
				valueType: "const" | "field",
				/** 表示是一个表达式,可以引用源数据集的字段 */
				value?: string;
			}],
			/**后端的数据集约束有些需要用到参数,此时需要吧可能用到的参数的值传递给后端*/
			params?: JSONObject,
		}): Promise<{
			/**覆盖了多少行数据 */
			overwriteCount: number,
			/**成功复制了多少行数据 */
			rowCount: number
		}> {
			return null;
		}
	}

	/**
	 * 支付 api
	 */
	export namespace Pay {
		/**
		 * 唤起支付
		 * @param orderId 
		 * @param scenario 
		 * @returns 
		 */
		export function callPay(orderId: string, scenarioId: string): Promise<CallPayResult> {
			if (browser.mobile) {
				return import("commons/mobile/napi").then(n => {
					return n.napi.callPay(orderId, scenarioId);
				});
			}
			return Promise.resolve(null);
		}

		export interface CallPayResult {
			/**
				 * 订单编号
				 */
			id: string;

			/**
			 * 支付状态
			 * 
			 * "paid": 支付成功
			 * "cancel": 支付失败,退出支付
			 */
			payState: "paid" | "cancel";
		}
	}
}

# properties

# 属性栏配置

表单扩展组件的属性栏配置定义在fappComponent下面的properties中,用于配置组件的属性名以及属性设置方式。

属性栏配置分为三个部分:

  1. data,描述组件的数据。需要参与计算的属性应该放到分组下设置。
  2. style,描述组件的样式。将组件存为一种组件风格时,会将该分组下的属性保存。
  3. action,描述组件支持的交互行为,不配置时会提供全部的交互行为配置。系统默认提供了一系列的交互行为,这里设置组件支持哪些交互行为。
# 基本结构

# 国际化配置

国际化配置定义在contributes下面的i18n中。 如:

"i18n": {
	"zh_CN": {
		"fapp.component.resselector.caption": "资源选择",
		"fapp.component.resselector.defaultTitle": "资源选择",
		"ppteditor.rootPath": "资源根目录",
		"ppteditor.resourceType": "资源选择类型",
		"ppteditor.resourceType.all":"全部",
		"ppteditor.resourceType.tbl":"模型",
		"ppteditor.resourceType.fold":"文件夹"
	},
	"en": {
		"fapp.component.resselector.caption": "资源选择",
		"fapp.component.resselector.defaultTitle": "资源选择",
		"ppteditor.rootPath": "资源根目录",
		"ppteditor.resourceType": "资源选择类型",
		"ppteditor.resourceType.all":"全部",
		"ppteditor.resourceType.tbl":"模型",
		"ppteditor.resourceType.fold":"文件夹"
	}
}
是否有帮助?
0条评论
评论