# 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 {
	ITemplatePagePartRenderer,
	TemplatePagePartArgs
} from "app/app-templatepage";
import {
	IExpEvalDataProvider
} from "commons/exp/expeval";
import {
	TableBuilder
} from "commons/table/table";
import {
	WorkbenchConf
} from "dsn/dsnframe";
import {
	IDwTableEditorPanel
} from "dw/dwtable";
import {
	FAppOrgItemInfo,
	IFAppBrowseDataMgr
} from "fapp/browser/fapp-browser-api";
import {
	FAppSheetData,
	FAppSheetsDataMgr
} from "fapp/form/fapp-datamgr";
import {
	browser,
	Component,
	downloadFile,
	rc_task,
	UrlInfo,
	rcuuid
} from "sys/sys";
import {
	BOComponentArgs,
	IBOComponent,
	IBOPage,
	IBOSheetComponent
} from "../bo/bo-browser";
import type {
	IBONodeData,
	IBODataManager
} from "../bo/bo-datamgr";
import {
	IDesigner,
	IMetaFileViewer,
	MetaFileViewerArgs
} from "./metadata";

/**
 * 系统约定的个性化脚本的结构,不管是全局脚本、项目脚本还是应用内脚本都是这个结构(或其子类)。
 * 
 * 暂不支持对象内的脚本(如某仪表板内部直接写的js内容)
 * 
 * 更多信息见:<http://docs.succbi.com//develop/references/embedding-scripts/>
 */
export interface ICustomJS extends IMetaFileCustomJS, IMetaMgrCustomJS {
	/**
	 * 明确的指定某个具体的文件类型的脚本,星号代表匹配所有类型,优先级高于直接在ts文件中export的函数
	 */
	CustomJS?: {
		/**
		 * 可以在此处通过路径、资源ID、文件名、类型指定脚本:
		 * 优先级顺序为:id>路径>名称>类型>*。
		 * id/路径/名称 匹配到的脚本之间不会合并,取优先级最高的,与类型、*匹配到的脚本按优先级进行合并。
		 * 
		 * 1. path或resID, 完整的路径或资源id。
		 * 2. name,文件名(无路径,带扩展名)
		 * 3. type,类型
		 * 4. *,默认
		 */
		[file_OR_type_OR_resid: string]: IMetaFileCustomJS | IBOCustomJS | IDwCustomJS | IMetaMgrCustomJS,
		app?: IAppCustomJS,
		dash?: IBOCustomJS,
		rpt?: IBOCustomJS,
		spg?: IBOCustomJS,
		fapp?: IBOCustomJS,
		fold?: IMetaMgrCustomJS,
		tbl?: IDwCustomJS,
	}

	/**运行时记录下脚本路径,用于定位脚本异常。脚本中不需要指定。 */
	scriptPath?: string;
}

/**
 * 报表、仪表板、图分析等分析对象的脚本事件接口
 */
export interface IMetaFileCustomJS {

	/**
	 * 当准备要打开一个资源时调用,此时资源内容可能还未加载,相关UI元素还未初始化。
	 * 
	 * 1. 从浏览器地址栏上打开一个页面时会调用
	 * 2. 在门户应用内部第一次点开一个资源时也会调用
	 * 
	 * @param path 文件路径
	 * @param args url参数
	 * @returns 返回`Promise`表示此函数是异步的,系统需要等待它`resolve`后再继续。
	 */
	onOpenFile?(path: string, args: UrlInfo): void | Promise<void>;

	/**
	 * 当元数据文件信息、内容、或其他的一些元数据信息加载完毕后调用。
	 * 
	 * @param viewer 文件的查看器
	 * @param file 文件元数据信息
	 * @returns 返回`Promise`表示此函数是异步的,系统需要等待它`resolve`后再继续。
	 */
	onDidLoadFile?(viewer: IMetaFileViewer, file: MetaFileInfo): void | Promise<void>;

	/**
	 * 当初始化一个资源的“设计器”前调用。有些特殊的场合,希望能个性化定制产品的设计器界面,比如在仪表板设计器界面上加一个“发布”按钮,可以使用此接口。
	 * 
	 * @param designer 设计器对象
	 * @param conf 初始化设计器的UI配置
	 * @returns 返回`Promise`表示此函数是异步的,系统需要等待它`resolve`后再继续。
	 */
	onInitDesigner?(designer: IDesigner, conf: WorkbenchConf): void | Promise<void>;

	/**
	 * 提供给页面的个性化交互函数。
	 * 
	 * 1. 在设计器上设计者可以选择交互动作是脚本,此时可以从这里提供的脚本函数中选择一个函数
	 * 2. 用户不能在设计器上直接输入脚本,只能选择`custom.js`文件中提供的脚本交互函数
	 */
	CustomActions?: {
		[actionName: string]: (event: InterActionEvent) => boolean | void | Promise<boolean | void>;
	};

	/**
	 * 提供给表达式中执行的个性化脚本函数。
	 * 1. 用户定义的个性化脚本函数,在表达式中调用`SCRIPT_STR`函数即可执行。
	 * 2. context: 表达式计算上下文
	 * 3. args: 脚本函数执行参数,参数类型只支持基本类型`string|number|boolean|Date`或者基本类型的数组形式`Array<string|number|boolean|Date>`
	 */
	CustomExpFunctions?: {
		[functionName: string]: (context: IExpEvalDataProvider, ...args: any) => string | Promise<string>;
	};

	/**
	 * 提供给页面的个性化脚本数据集。
	 *
	 * 1. 在设计器上设计者可以添加脚本数据集,此时需要从此脚本中选择一个定义好的脚本数据集,作为数据
	 *    和字段的来源;
	 * 2. 用户不能在设计器上直接输入脚本,只能选择`custom.js`文件中提供的脚本数据集;
	 */
	CustomDataSets?: {
		[dataSetName: string]: IScriptDatasetProvider;
	}

}
/**
 * 数据模块的脚本事件接口
 */
export interface IDwCustomJS extends IMetaFileCustomJS {
	/**
	 * 当准备在数据表编辑界面中的下部创建一个子面板时调用
	 * @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 IAppCustomJS extends IMetaFileCustomJS {
	/**
	 * 当在浏览器窗口中打开一个新的门户框架时调用,调用此函数时门户框架还未完成初始化。
	 * 
	 * @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 IBOCustomJS extends IMetaFileCustomJS {

	/**
	 * BO对象由于计算、渲染或交互触发了显示等待提示框的事件。当实现了该函数,BO对象默认的等待提示框不会显示。
	 * 
	 * 例如:当交互触发时,希望显示“正在提交数据”。
	 * 
	 * let renderer = event.renderer;
	 * let actionManager = renderer.getActionManager();
	 * if(actionManager.isRunning()) {
	 *  showCustomWaiting("正在提交数据");
	 * } else {
	 * 	showWaiting(renderer.waitRender());
	 * }
	 * 
	 * @param event 
	 * @returns 返回当前正在等待的promise。
	 */
	onShowWaiting?(event?: InterActionEvent): Promise<void>;

	/**
	 * 创建一个组件UI对象前的事件。
	 * @param args 构造参数
	 * @returns
	 * 1. `null` 不进行个性化,使用系统默认的实现
	 * 2. `null` 直接修改传入参数,修改构造参数,还是用系统默认的UI组件
	 * 3. 返回新的控件UI对象,替换系统默认的实现
	 */
	onInitComponent?(args: BOComponentArgs): void | IBOComponent;

	/**
	 * 组件发生刷新行为前调用。
	 * 
	 * 1. 用户选择仪表板参数后,触发了仪表板组件的刷新
	 * 2. 用户点击了仪表板的某个图,触发了另外一些组件的刷新
	 * 3. 用户在表单数据浏览界面上选择了某些过滤条件,触发了明细列表的刷新
	 * 
	 * @param event 发生交互行为的事件对象,可从该事件对象中获取交互行为的上下文信息
	 * @returns 返回`Promise`表示此函数是异步的,系统需要等待它`resolve`后再继续。
	 * @TODO 改为onRefreshComponent(IBOComponent)
	 */
	onRefresh?(event?: InterActionEvent): void | Promise<void>;

	/**
	 * 页面内发生刷新行为后调用。
	 * 
	 * 1. 用户选择仪表板参数后,触发了仪表板组件的刷新 
	 * 2. 用户点击了仪表板的某个图,触发了另外一些组件的刷新
	 * 3. 用户在表单数据浏览界面上选择了某些过滤条件,触发了明细列表的刷新
	 * 
	 * @param event 发生交互行为的事件对象,可从该事件对象中获取交互行为的上下文信息
	 * @returns 返回`Promise`表示此函数是异步的,系统需要等待它`resolve`后再继续。
	 * @TODO 改为onDidRefreshComponent(IBOComponent)
	 */
	onDidRefresh?(event?: InterActionEvent): void | Promise<void>;

	/**
	 * 组件发生渲染前调用。
	 * 
	 * 1. echarts构造好option后,setOption()前调用,可以通过此事件修改option
	 * 
	 * @param event 发生交互行为的事件对象,可从该事件对象中获取交互行为的上下文信息
	 * @TODO 改为onRenderComponent(IBOComponent, args); args中包含渲染前的数据,传递给脚本,脚本修改它后利用它来影响渲染
	 */
	onRender?(event: InterActionEvent): void | Promise<void>;

	/**
	 * 组件渲染完毕后调用。可以在渲染完毕后修改组件的UI。
	 * 
	 * @param component 当前组件的UI对象。
	 */
	onDidRenderComponent?(component?: IBOComponent): void | Promise<void>;

	/**
	 * 当要发起一个数据查询前调用。通过此函数开发者可以修改查询参数,可以拦截查询返回自己想要返回的结果。
	 * 
	 * @param query 查询参数,开发者可以修改此对象,让系统发起查询时使用修改后的查询参数信息。
	 * @returns 返回`Promise`表示此函数是异步的,系统需要等待它`resolve`后再继续,如果此函数返回了一个
	 * 	合法的`QueryDataResultInfo`对象,那么系统将不再发起默认的查询。
	 * @deprecated 查询的参数现在都是在约束中,这个钩子意义不大了
	 */
	onQueryData?(page: IBODataManager, query: QueryInfo): Promise<QueryDataResultInfo>;

	/**
	 * 在导出仪表板/报表/SuperPage之前调用,实现此接口需要自行生成导出文件。
	 * @deprecated 如果要替换整个导出,为什么不直接加一个自定义脚本函数?
	 * @param page 
	 * @param args
	 */
	onExport?(page: IBODataManager, args: BOObjectExportArgs): Promise<ExportBOObjectResult>;

	/**
	 * 在生成导出文件之前调用。
	 * 
	 * 1. 导出报表时,开发者可以通过修改 表格渲染对象`TableBuilder`实现自定义表格样式、数据
	 * @param page 
	 * @param exportInfo 用于生成结果文件的一些中间产物,比如导出报表就是各个表格对象
	 * @TODO 改为onExport,第二个参数也不对,应该是兼容所有模块的。而且这里导出的格式是什么也不知道
	 */
	onGenerateExportFile?(page: IBODataManager, exportInfo: Array<TableBuilder> | any): Promise<void>;

	/**
	 * 导出完成之后调用,可以替换或修改生成的blob文件。
	 * @param page 
	 * @param result 
	 * @TODO 场景是什么,为什么是anaobjectresult,表单怎么办
	 */
	onDidExport?(page: IBODataManager, result: ExportBOObjectResult): Promise<ExportBOObjectResult>;

}

/**
 * 元数据管理界面的脚本事件接口
 */
export interface IMetaMgrCustomJS {
	/**
	 * 当在浏览器窗口中打开一个新的元数据管理界面时调用,调用此函数时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?: FilterInfo[];
	/**是否要下载维键的文字 */
	needCodeDesc?: boolean;
}

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

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

/**
 * 脚本交互行为事件。
 */
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条评论
评论