# fappComponent - 表单应用组件扩展
表单应用内置了大量常用的组件,如文本输入、日期、密码输入组件等,能满足大部分的数据填报需求。在一些场景中也存在一些个性化的数据填报需求是内置组件无法满足的,此时就可以通过扩展一个新的表单输入组件来实现。
表单扩展组件可以做到和产品内置的组件一样的复用,由扩展开发者开发好组件扩展后,使用者可以像使用内置组件一样使用扩展组件。
本文讲述如何开发一个表单应用组件扩展。
# 扩展文件结构
- package.json定义扩展的配置信息
- main.ts定义扩展组件
- api
- properties
# package.json
示例如下:
{
"name": "succ-fappComponent-resourceSelector",
"displayName": "资源选择组件",
"categories": [
"fapp"
],
"main": "main",
"thumbnail": "thumbnail.png",
"contributes": {
"formComponent": [
{
"id": "resselector",
"category": "input",
"themeCategory": "input",
"group": "input",
"depends": "main",
"builderClassName": "FAppResSelectorComponentBuilder",
"implClass": "FAppResSelectorComponent",
"storeEnabled": true,
"embedded": true,
"supportExcel": false,
"icon": "resselector.svg",
"defaultDbfieldLength": 128,
"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": true,
"expand": false,
"propertyType": "group",
"inlineItem": {
"propertyName": "title",
"propertyType": "richTextEdit",
"saveTheme": true
},
"items": [
{
"propertyName": "desc",
"propertyType": "textArea",
"captionPosition": "top",
"layoutTheme": "formtextarea"
}
]
},
{
"propertyName": "contentSetting",
"propertyType": "group",
"expand": false,
"items": [
{
"propertyName": "value",
"propertyType": "expEdit",
"fieldPanelImpl": {
"depends": "commons/tree",
"implClass": "Tree",
"iconVisible": true,
"dragable": true,
"dragMoveable": false
}
},
"defaultValue",
{
"propertyName": "calcCondition",
"propertyType": "expEdit",
"captionPosition": "top",
"fieldPanelImpl": {
"depends": "commons/tree",
"implClass": "Tree",
"iconVisible": true,
"dragable": true,
"dragMoveable": false
}
},
{
"propertyName": "resourceDisplayMode",
"propertyType": "combobox",
"multipleSelect": false,
"items": [
"id",
"path",
"pathDesc"
]
},
{
"propertyName": "calcConditionEnabled",
"propertyType": "checkbox"
},
{
"propertyName": "calcCondition",
"propertyType": "expEdit",
"captionPosition": "top",
"showCaption": false,
"visibleCondition": "!!calcConditionEnabled",
"fieldPanelImpl": {
"depends": "commons/tree",
"implClass": "Tree",
"iconVisible": true,
"dragable": true,
"dragMoveable": false
}
},
{
"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": "pathField",
"propertyType": "combobox",
"itemIconVisible": true,
"captionIconVisible": false,
"arbitraryInputEnabled": true,
"type": "tree",
"visibleCondition": "!!storeEnabled"
},
{
"propertyName": "dbfieldConditionEnabled",
"propertyType": "checkbox",
"visibleCondition": "!!storeEnabled"
},
{
"propertyName": "dbfieldCondition",
"propertyType": "expEdit",
"fieldPanelImpl": {
"depends": "commons/tree",
"implClass": "Tree",
"iconVisible": true,
"itemDescVisible": true,
"dragable": true,
"dragMoveable": false
},
"visibleCondition": "!!dbfieldConditionEnabled"
},
{
"propertyName": "rememberLastValue",
"propertyType": "checkbox",
"visibleCondition": "!!storeEnabled"
},
{
"propertyName": "rootPath",
"propertyType": "edit",
"placeholder": "/TestCase"
},
{
"propertyName": "resourceType",
"propertyType": "combobox",
"multipleSelect": true,
"selectAllEnabled": true,
"defaultValue": ["tbl"],
"items": [
"tbl",
"fold",
"rpt",
"dash",
"fapp",
"app",
"tpg",
"spg",
"mpg",
"gpa",
"project",
"ts",
"action",
"action.ts",
"json",
"less",
"html",
"doc",
"docx",
"d.docx",
"xlsx",
"xls",
"pdf",
"pptx"
]
}
]
},
{
"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": "placeholder",
"propertyType": "edit"
},
{
"propertyName": "visibleConditionEnabled",
"propertyType": "checkbox"
},
{
"propertyName": "visibleCondition",
"propertyType": "expEdit",
"visibleCondition": "!!visibleConditionEnabled",
"fieldPanelImpl": {
"depends": "commons/tree",
"implClass": "Tree",
"iconVisible": true,
"dragable": true,
"dragMoveable": false
},
"showCaption": false
},
{
"propertyName": "editConditionEnabled",
"propertyType": "checkbox"
},
{
"propertyName": "editCondition",
"propertyType": "expEdit",
"fieldPanelImpl": {
"depends": "commons/tree",
"implClass": "Tree",
"iconVisible": true,
"dragable": true,
"dragMoveable": false
},
"showCaption": false,
"visibleCondition": "!!editConditionEnabled"
}
]
}
]
},
"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": "edit",
"visibleCondition": "!!showTitle && titlePosition == 'left'",
"max": 400,
"min": 10,
"defaultValue": "120px",
"saveTheme": true
}
]
},
{
"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"
}
]
}
]
}
}
}
]
},
"i18n": {
"zh_CN": {
"fapp.component.resselector.caption": "资源选择",
"fapp.component.resselector.defaultTitle": "资源选择",
"ppteditor.pathField": "资源路径字段",
"ppteditor.rootPath": "资源根目录",
"ppteditor.resourceType": "资源选择类型",
"ppteditor.resourceType.tbl": "模型",
"ppteditor.resourceType.fold": "文件夹",
"ppteditor.resourceDisplayMode": "显示内容",
"ppteditor.resourceDisplayMode.id": "资源ID",
"ppteditor.resourceDisplayMode.path": "资源路径",
"ppteditor.resourceDisplayMode.pathDesc": "资源路径描述",
"fapp.component.resselector.link.caption": "选择",
"ppteditor.resourceType.rpt": "报表",
"ppteditor.resourceType.dash": "仪表板",
"ppteditor.resourceType.fapp": "表单应用",
"ppteditor.resourceType.frm": "表单",
"ppteditor.resourceType.app": "应用",
"ppteditor.resourceType.tpg": "模板页面",
"ppteditor.resourceType.spg": "超级页面",
"ppteditor.resourceType.mpg": "移动页面",
"ppteditor.resourceType.gpa": "图分析",
"ppteditor.resourceType.project": "项目",
"ppteditor.resourceType.ts": "前端脚本",
"ppteditor.resourceType.action": "后端脚本",
"ppteditor.resourceType.action.ts": "后端ts脚本",
"ppteditor.resourceType.json": "json",
"ppteditor.resourceType.less": "less",
"ppteditor.resourceType.html": "html",
"ppteditor.resourceType.doc": "doc",
"ppteditor.resourceType.docx": "docx",
"ppteditor.resourceType.d.docx": "d.docx",
"ppteditor.resourceType.xlsx": "xlsx",
"ppteditor.resourceType.xls": "xls",
"ppteditor.resourceType.pdf": "pdf",
"ppteditor.resourceType.pptx": "pptx"
},
"en": {
"fapp.component.resselector.caption": "资源选择",
"fapp.component.resselector.defaultTitle": "资源选择",
"ppteditor.pathField": "资源路径字段",
"ppteditor.rootPath": "资源根目录",
"ppteditor.resourceType": "资源选择类型",
"ppteditor.resourceType.tbl": "模型",
"ppteditor.resourceType.fold": "文件夹",
"ppteditor.resourceDisplayMode": "显示内容",
"ppteditor.resourceDisplayMode.id": "资源ID",
"ppteditor.resourceDisplayMode.path": "资源路径",
"ppteditor.resourceDisplayMode.pathDesc": "资源路径描述",
"fapp.component.resselector.link.caption": "选择",
"ppteditor.resourceType.rpt": "报表",
"ppteditor.resourceType.dash": "仪表板",
"ppteditor.resourceType.fapp": "表单应用",
"ppteditor.resourceType.app": "应用",
"ppteditor.resourceType.tpg": "模板页面",
"ppteditor.resourceType.spg": "超级页面",
"ppteditor.resourceType.mpg": "移动页面",
"ppteditor.resourceType.gpa": "图分析",
"ppteditor.resourceType.project": "项目",
"ppteditor.resourceType.ts": "前端脚本",
"ppteditor.resourceType.action": "后端脚本",
"ppteditor.resourceType.action.ts": "后端ts脚本",
"ppteditor.resourceType.json": "json",
"ppteditor.resourceType.less": "less",
"ppteditor.resourceType.html": "html",
"ppteditor.resourceType.doc": "doc",
"ppteditor.resourceType.docx": "docx",
"ppteditor.resourceType.d.docx": "d.docx",
"ppteditor.resourceType.xlsx": "xlsx",
"ppteditor.resourceType.xls": "xls",
"ppteditor.resourceType.pdf": "pdf",
"ppteditor.resourceType.pptx": "pptx"
}
}
}
# 详细说明
属性 | 类型 | 必需 | 描述 |
---|---|---|---|
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
示例如下:
import {
Edit,
EditArgs,
Link
} from 'commons/basic';
import 'css!./main.css';
import {
FAppComponentArgs,
FAppComponentInput
} from 'fapp/fappbasic';
import {
FAppComponentBuilder,
FAppComponentPptDataProvider,
FAppExpContext,
FAppPartComponentBuilder
} from 'fapp/fappbuilder';
import {
FAppComponentData,
FAppComponentsDatas,
FAppTableDataInfo
} from 'fapp/form/fappdatamgr';
import {
getMetaRepository
} from 'metadata/metadata';
import {
assign,
Component,
message,
SZEvent
} from 'sys/sys';
/**
* 资源选择控件的数据对象
*/
export class FAppResSelectorComponentBuilder extends FAppComponentBuilder {
public components?: FAppPartComponentBuilder[];
public metaInfo: FAppResSelectorComponentInfo;
/** 资源选择控件的属性栏DP */
private resPptDataProvider: FAppComponentPptDataProvider;
/** @override */
protected initSubComponents(): void {
this.components = [];
let parent = this.parent || this;
let pathBuilder = new FAppPartComponentBuilder({ parent: parent, form: this.form, partType: 'pathField' });
let subcomps = [pathBuilder];
subcomps.forEach(comp => {
comp.isVirtual = true;
let metaInfo = this.metaInfo;
let dbfield = metaInfo.pathField;
let dbtable = metaInfo.dbtable;
let caption = metaInfo.title && metaInfo.title.text;
let json: JSONObject = { text: caption + '资源路径' };
dbfield && (json.dbfield = dbfield);
dbtable && (json.dbtable = dbtable);
comp.loadJSON(json);
if (parent.floatArea) {
comp.floatArea = parent.floatArea;
}
});
this.components.pushAll(subcomps);
}
/** @override */
public loadJSON(compInfo: FAppComponentInfo): void {
super.loadJSON(compInfo);
this.initSubComponents();
}
/**
* @override
* 资源选择类型如果多选或者选择全部时,此时是个数组
*/
public isConflictProperty(name: string): boolean | void {
if (name == 'resourceType') {
return false;
}
}
/**
* @override
*/
public getPptEditorDataProvider(): IComponentPptDataProvider {
let resDp = this.resPptDataProvider;
if (!resDp) {
resDp = this.resPptDataProvider = new ResSelectorPptDataProvider();
resDp.setComponentBuilder(this);
}
return resDp;
}
/** @override */
public compile(context: FAppExpContext): void {
if (this.components) {
if (this.parent && this.parent.floatArea) {
this.floatArea == null && (this.floatArea = this.parent.floatArea);
}
}
this.compileState = null;
this.components.forEach(comp => {
comp.compileState = null;
comp.field = null;
comp.floatArea = this.floatArea;
});
super.compile(context);
let pathComp = this.components[0];
if (this.field && pathComp.field) {
pathComp.field.setProperty('length', 512);
}
}
/** @override */
public generateField(reGenerate: boolean = false): void {
super.generateField(reGenerate);
if (this.storeEnabled || this.parent) { // FIXME 为什么取或?
let pathField = this.metaInfo.pathField;
if (pathField == null && this.app.getModelManager().isEnableSyncDbTable(this.field?.model)) { // 引用外部模型时,不自动生成路径字段
let dbfield = this.parent ? this.parent.getDbfield() : this.getDbfield();
let pathComp = this.components[0];
pathComp.setStoreEnabled(true, false);
pathComp.setProperty(FAppComponentPropertyName.Dbfield, `${dbfield}_PATH`, false);
this.setProperty('pathField', `${dbfield}_PATH`, false);
}
}
}
/** @override */
public compileAdd(context: FAppExpContext): void {
this.compile(context);
// ISSUE: BI-34604 删除后再undo添加回来,此时也需要编译影响的控件,避免引用该控件的错误提示依然存在
this.compileImpactComponent(context);
}
/** @override */
public compileRemove(context: FAppExpContext): void {
super.compileRemove(context);
this.components && this.components.forEach(comp => {
comp.compileRemove(context);
});
}
/** @override */
public getSubVar(varName: string): IExpVar {
let subVar = super.getSubVar(varName);
if (subVar) {
return subVar;
}
let subComp = this.components && this.components.find(comp => {
return varName && (varName.equalsIgnoreCase(comp.getTitleValue()) || varName.equalsIgnoreCase(comp.getId()));
});
return subComp && subComp.getSelfVar();
}
/**
* 设置资源路径字段的值,它的处理和dbfield一样
* @param value
* @param recordUndo
*/
public setPathField(value: any, recordUndo: boolean) {
let pathValue: string;
if (Array.isArray(value)) {
pathValue = value[0];
}
else if (value instanceof Object) {
pathValue = value[FAppComponentPropertyName.Dbfield];
}
let metaInfo = this.metaInfo;
let ov = metaInfo.pathField;
if (pathValue == ov) {
return;
}
let forms = this.app;
forms.beginUpdate();
try {
pathValue == null ? (delete metaInfo.pathField) : (metaInfo.pathField = pathValue);
recordUndo && forms.storeUndoItem({
op: UndoOperationType.Modify,
c: this,
p: 'pathField',
ov: ov
});
} finally {
forms.endUpdate();
}
}
}
/**
* 字段选择控件元数据
*/
interface FAppResSelectorComponentInfo extends FAppInputComponentInfo {
/** 资源路径字段 */
pathField?: string;
/** 资源选择控件根路径 */
rootPath?: string;
/** 资源选择控件可选择资源类型 */
resourceType?: string;
/** 资源选择控件显示内容格式 */
resourceDisplayMode?: string;
}
/**
* 资源选择控件显示模式
*/
const FAppResSelectorDisplayMode = {
/** 显示资源ID */
ResId: 'id',
/** 显示资源路径 */
ResPath: 'path',
/** 显示资源路径描述 */
ResPathTxt: 'pathDesc'
}
/**
* 资源选择控件的UI对象
*/
export class FAppResSelectorComponent extends FAppComponentInput {
protected innerComponent: Edit;
private link: Link;
public builder: FAppResSelectorComponentBuilder;
protected _createInnerComponent(args: FAppComponentArgs): Promise<Component> {
let compArgs = this.getComponentArgs();
let comp = this.innerComponent = new Edit(compArgs);
this.link = new Link({
domParent: this.domBody,
caption: message('fapp.component.resselector.link.caption'),
onclick: this.showDialog.bind(this)
});
return Promise.resolve(comp);
}
/** @override */
protected _reInitInnerComponent(args: FAppComponentArgs): void {
let innerComponent = this.innerComponent as Edit;
if (!innerComponent) {
return;
}
let compArgs = this.getComponentArgs();
innerComponent._reInit(compArgs);
}
/** @override */
public setValue(value: MetaFileInfo | string): Promise<void> {
return this.getDisplayText(value).then(text => {
super.setValue(text);
this.setEditEnable();
});
}
/** @override */
public loadData(data?: FAppComponentData): Promise<void> {
this.setEditEnable();
let compData: FAppComponentData = this.data = data || this.getComponentData();
let promise = compData ? this.doRefresh(compData) : null;
return Promise.resolve(promise).then(() => this.setValue(compData ? compData.value : null).then(() => this.requestRender()));
}
/** @override */
public doRefresh(compData: FAppComponentData, property?: string): Promise<void> {
return Promise.resolve(super.doRefresh(compData, property)).then(() => this.setEditEnable());
}
/** @override */
protected doChange(event: SZEvent, component?: Component, item?: any): void {
let value = item && item[0];
event.value = value;
this.setValue(value);
super.doChange(event, component, item);
}
/** @override */
public doValueChange(event: { value: MetaFileInfo } & SZEvent, item?: any) {
if (event) {
let value: MetaFileInfo = event.value;
if (value == null) {
return;
}
let dataInfo: FAppTableDataInfo;
let formData = this.dataManager.form(this.builder.form.getId().toUpperCase());
let ownerData: FAppComponentsDatas;
let pCompId: string;
if (this.isInnerComp) {
dataInfo = item && item.getProperty(FAppComponentPropertyName.CellDataInfo);
dataInfo && (ownerData = dataInfo.floatCell == null ? formData : formData.getFloatArea(dataInfo.floatCell).getRowBySN(dataInfo.rowsn));
dataInfo && (pCompId = dataInfo.sourceName);
}
else {
pCompId = this.builder.getId().toUpperCase();
}
ownerData == null && (ownerData = formData);
let formsdata = ownerData.getFormsData();
let batch = formsdata.batch;
if (!batch) {
formsdata.beginUpdate();
}
/**
* 20200923 liujingj BI-36391
* 资源选择控件字段名存资源ID,副字段存资源路径
*/
ownerData.setValue(pCompId, value && value.id);
ownerData.setValue(`${pCompId}.PATHFIELD`, value && value.path);
if (!batch) {
formsdata.endUpdate();
}
}
}
/** @override */
public refreshData(data: FAppComponentData, property?: string): Promise<void> {
return super.refreshData(data, property).then(() => {
this.setEditEnable();
});
}
/** @override */
public setReadOnly(v: boolean): void {
v = !!v;
if (this.readOnly !== v) {
this.readOnly = v;
let domBase = this.domBase;
if (domBase) {
if (v) {
domBase.setAttribute('readonly', 'true');
domBase.classList.add('fapp-readonly');
if (this.wantTab && document.activeElement === domBase) {
super.setDomParentFocus(domBase);
}
this.wantTab && domBase.removeAttribute('tabindex');
} else {
domBase.removeAttribute('readonly');
domBase.classList.remove('fapp-readonly');
this.wantTab && domBase.setAttribute('tabindex', '0');
}
}
}
this.waitInit().then(() => {
let innerComponent = this.innerComponent;
// 由于父类在实现控制控件的启用禁用(以及只读)时,同时将innerComponent的启用状态也修改了。但是本控件中的innerComponent是不可编辑的。
this.setEditEnable();
this.link && this.link.setEnabled(!v && this.enabled);
//控件在只读状态下不能显示palceholder
let comp = <any>innerComponent;
if (comp && comp.setPlaceholder) {
v ? comp.setPlaceholder('') : (this.enabled && comp.setPlaceholder(this.placeholder));//若禁用,则同样不显示占位符
}
});
}
/** @override */
public setEnabled(v: boolean): void {
super.setEnabled(v);
this.link && this.link.setEnabled(v);
this.setEditEnable();
}
/** @override */
public dispose(): void {
this.link && this.link.dispose();
super.dispose();
}
/**
* 显示拾取位置对话框
*/
private showDialog(): void {
import('metadata/metamgr').then(m => {
let projectName = this.builder.app.getProjectName();
let path: string;
let rootPath: string = this.builder.getProperty('rootPath');
let type: string | string[] = this.builder.getProperty('resourceType');
let resourceType: Array<string> = Array.isArray(type) ? type : [type];
(rootPath == null) && (rootPath = `/${projectName}`);
resourceType == null && (resourceType = [MetaFileType.FOLD]);
/**
* 20200812 liujingj BI-35764
* feat:资源选择控件可选任意类型资源,这里不做限制
*/
/**
* 20200821 liujingj 多次打开资源选择框时自动选中已选择的资源
*/
this.getSelectedResourcePath().then(locateAtPath => {
if (locateAtPath && !locateAtPath.startsWith(rootPath)) {
//如果当前的值不在根路径下面,那么不能将locatePath传给资源选择对话框,否则它会提示一个错误。
locateAtPath = null;
}
m.showResourceDialog({
locateAtPath: locateAtPath,
rootPath: rootPath,
returnTypes: resourceType,
onok: this.doChange.bind(this)
});
})
});
}
/**
* 获取当前选中资源的路径
*/
private getSelectedResourcePath(): Promise<string> {
let formData = this.dataManager.form(this.builder.form.getId().toUpperCase());
let compId = this.builder.getId();
if (!compId) {
return Promise.resolve(null);
}
let compId_ = this.builder.getId().toUpperCase();
let path = formData.getValue(`${compId_}.PATHFIELD`);
if (path) {
return Promise.resolve(path);
}
let resid = formData.getValue(compId_);
if (resid) {
let repo = getMetaRepository();
return repo.getFile(resid, false, false, false).then(file => {
if (file == null) {
return null;
}
return file.path;
});
}
return Promise.resolve(null);
}
/**
* 资源输入框禁用。
* * 由于父类在实现控制控件的启用禁用(以及只读)时,同时将innerComponent的启用状态也修改了。
* 但是本控件中的innerComponent是不可编辑的。
*/
private setEditEnable() {
this.innerComponent && this.innerComponent.setReadOnly(true); // 用setEnabled(false)后无法高亮,无法选中文字等等,改用setReadOnly
}
/**
* 生成控件显示内容
*/
private getDisplayText(value: MetaFileInfo | string): Promise<string> {
if (value == null) {
return Promise.resolve(null);
}
let repo = getMetaRepository();
let metaInfo = this.builder.metaInfo;
let displayFormatType = metaInfo.resourceDisplayMode || FAppResSelectorDisplayMode.ResPath;
// 获取元数据描述
let getResourceDesc = (resPath: string): Promise<string> => {
return repo.getFile(resPath, false, false, false).then(file => {
if (file == null) {
return null;
}
return file.desc || file.name;
});
}
// 根据元数据路径生成路径描述
let getResPathText = (resPath: string): Promise<string> => {
if (resPath == null) {
return Promise.resolve(null);
}
if (!resPath.startsWith('/')) {
return Promise.resolve(resPath);
}
let allPaths: Array<string> = [];
let tempResPath = resPath;
allPaths.push(tempResPath);
let lastIndex = tempResPath.lastIndexOf('/');
while (lastIndex > 0) {
tempResPath = tempResPath.substring(0, tempResPath.lastIndexOf('/'));
allPaths.push(tempResPath);
lastIndex = tempResPath.lastIndexOf('/');
}
if (allPaths.length == 0) {
return Promise.resolve(resPath);
}
let pIndex = allPaths.length - 1;
let result: Array<string> = [];
let addText = (): Promise<void> => {
if (pIndex < 0) {
return Promise.resolve();
}
return getResourceDesc(allPaths[pIndex]).then(desc => {
if (desc == null) {
// 未找到元数据,就返回原路径
pIndex = -999;
return;
}
result.push(desc);
pIndex--;
return addText();
});
}
return addText().then(() => {
if (pIndex == -999 || result.length == 0) {
return resPath;
}
return '/' + result.join('/');
});
}
if (typeof value === 'string') {
return repo.getFile(value, false, false, false).then(file => {
if (file == null) {
return value;
}
if (displayFormatType == FAppResSelectorDisplayMode.ResId) {
return file.id;
}
else if (displayFormatType == FAppResSelectorDisplayMode.ResPath) {
return file.path;
}
else {
return getResPathText(file.path);
}
});
}
else {
if (value.id == null) {
return Promise.resolve(null);
}
if (displayFormatType == FAppResSelectorDisplayMode.ResId) {
return Promise.resolve(value.id);
}
else if (displayFormatType == FAppResSelectorDisplayMode.ResPath) {
return Promise.resolve(value.path);
}
else {
return getResPathText(value.path);
}
}
}
private getComponentArgs(): EditArgs {
let compArgs: EditArgs = {
domParent: this.domBody,
onchange: this.doChange.bind(this),
oninput: this.doInput.bind(this),
onblur: this.doBlur.bind(this),
enabled: false,
value: this.value
};
if (this.viewMode !== FAppViewMode.Edit) {
assign(compArgs, this.getDataLimitInfo());
}
return compArgs;
}
}
/**
* 资源选择控件的属性栏DP
*/
class ResSelectorPptDataProvider extends FAppComponentPptDataProvider {
/** @implements */
public getPropertyConstructorArgs(catalog: string, propertyName: string, propertyType: string, partIndex?: number): PptItemConstructorArgs {
let errorInfo = this.component.getPptItemErrorInfo(propertyName);
let focus = !!errorInfo && this.form.needFocusError();
let focusName: string = errorInfo && this.form.getFocusErrorProperty();
if (propertyName == 'pathField') {
let componentBuilder = this.component;
let dbtable = componentBuilder.getDbtable();
let model = this.app.getModel(dbtable);
if (model && !this.app.getModelManager().isEnableSyncDbTable(model)) {
return {
dataProvider: model.getDwBussinessObject(),
arbitraryInputEnabled: false,
focus: (focusName == propertyName) || focus,
errorInfo: errorInfo
};
}
else {
return {
dataProvider: this.emptyDp,
arbitraryInputEnabled: true
};
}
} else {
return super.getPropertyConstructorArgs(catalog, propertyName, propertyType, partIndex);
}
}
}
# 详细说明
一个组件包括数据对象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 {
UrlInfo,
Component,
AjaxArgs,
CommandItemInfo,
IParameterized,
rc,
rc_task,
uuid,
EventListener,
downloadFile,
browser,
assign
} from "sys/sys";
import {
MetaFileViewerArgs,
IMetaFileViewer,
IDesigner,
getCurrentUser
} from "./metadata";
import {
ListArgs,
List,
GridArgs,
Grid,
TreeGridArgs
} from "commons/tree";
import {
FAppFormsDataMgr,
FAppComponentData,
FAppFloatAreaDataRow,
FAppFormData
} from "fapp/form/fappdatamgr";
import {
FAppFormBuilder,
FAppComponentBuilder
} from "fapp/fappbuilder";
import {
IDwTableEditorPanel
} from "dw/dwtable";
import {
getDwTableDataManager
} from "dw/dwapi";
import {
IExpEvalDataProvider
} from "commons/exp/expeval";
import {
TemplatePagePartArgs,
ITemplatePagePartRenderer
} from "app/templatepage";
import {
AnaComponent,
AnaComponentArgs,
IAnaObjectRenderer
} from "ana/anabrowser";
import {
WorkbenchArgs
} from "dsn/dsnframe";
import {
TableBuilder
} from "commons/table";
import {
IFAppBrowseDataMgr
} from "fapp/browser/fappbrowser-api";
/**
* 系统约定的个性化脚本的结构,不管是全局脚本、项目脚本还是应用内脚本都是这个结构(或其子类)。
*
* 暂不支持对象内的脚本(如某仪表板内部直接写的js内容)
*
* 更多信息见:<http://docs.succbi.com//develop/references/embedding-scripts/>
*/
export interface ICustomJS {
/**
* 明确的指定某个具体的文件类型的脚本,星号代表匹配所有类型,优先级高于直接在ts文件中export的函数
*/
CustomJS?: {
/**
* 可以在此处通过路径、资源ID、文件名、类型指定脚本:
* 优先级顺序为:id>路径>名称>类型>*。
* id/路径/名称 匹配到的脚本之间不会合并,取优先级最高的,与类型、*匹配到的脚本按优先级进行合并。
*
* 1. path或resID, 完整的路径或资源id。
* 2. name,文件名(无路径,带扩展名)
* 3. type,类型
* 4. *,默认
*/
[file_OR_type_OR_resid: string]: IMetaFileCustomJS,
app?: IAppCustomJS,
dash?: IMetaFileCustomJS,
rpt?: IMetaFileCustomJS,
fapp?: IFAppCustomJS,
fold?: IMetaMgrCustomJS,
tbl?: IDwCustomJS,
}
/**运行时记录下脚本路径,用于定位脚本异常。脚本中不需要指定。 */
scriptPath?: string;
}
/**
* 报表、仪表板、图分析等分析对象的脚本事件接口
*/
export interface IMetaFileCustomJS extends ICustomJS {
/**
* 当准备要打开一个资源时调用,此时资源内容可能还未加载,相关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>;
/**
* 当发生一次ajax数据请求时调用,实现者可以使用此函数个性化从服务器获取的数据。
*
* 1. 计算仪表板时,查询一个统计图的数据时会发起ajax
* 2. 打开表单时下载表单数据会发起ajax
*
* @param page 文件的查看器
* @param args 发起ajax请求的参数
* @returns 返回`Promise`表示此函数是异步的,系统需要等待它`resolve`后再继续,如果Promise返回了
* 一个对象,那么会使用这个新的对象当作数据替代原来从服务器获取的数据。
*/
onDidLoadData?(page: IVPage, args: AjaxArgs): void | Promise<any>;
/**
* 页面内发生刷新行为前调用。
*
* 1. 用户选择仪表板参数后,触发了仪表板的刷新
* 2. 用户点击了仪表板的某个图,触发了另外一些控件的刷新
* 3. 用户在表单数据浏览界面上选择了某些过滤条件,触发了明细列表的刷新
*
* @param event 发生交互行为的事件对象,可从该事件对象中获取交互行为的上下文信息
* @returns 返回`Promise`表示此函数是异步的,系统需要等待它`resolve`后再继续。
*/
onRefresh?(event: InterActionEvent): void | Promise<void>;
/**
* 页面内发生刷新行为后调用。
*
* 1. 用户选择仪表板参数后,触发了仪表板的刷新
* 2. 用户点击了仪表板的某个图,触发了另外一些控件的刷新
* 3. 用户在表单数据浏览界面上选择了某些过滤条件,触发了明细列表的刷新
*
* @param event 发生交互行为的事件对象,可从该事件对象中获取交互行为的上下文信息
* @returns 返回`Promise`表示此函数是异步的,系统需要等待它`resolve`后再继续。
*/
onDidRefresh?(event: InterActionEvent): void | Promise<void>;
/**
* 控件发生渲染前调用。
*
* 1. echarts构造好option后,setOption()前调用,可以通过此事件修改option
*
* @param event 发生交互行为的事件对象,可从该事件对象中获取交互行为的上下文信息
*/
onRender?(event: InterActionEvent): void | Promise<void>;
/**
* 展示提示框前调用。
*
* @param event 发生交互行为的事件对象,可从该事件对象中获取交互行为的上下文信息
*/
onTooltip?(event: InterActionEvent): void | Promise<void>;
/**
* 当初始化一个资源的“设计器”前调用。有些特殊的场合,希望能个性化定制产品的设计器界面,比如在仪表板设计器界面上加一个“发布”按钮,可以使用此接口。
*
* @param designer 设计器对象
* @param args 初始化设计器的参数
* @returns 返回`Promise`表示此函数是异步的,系统需要等待它`resolve`后再继续。
*/
onInitDesigner?(designer: IDesigner, args: WorkbenchArgs): void | Promise<void>;
/**
* 当要发起一个数据查询前调用。通过此函数开发者可以修改查询参数,可以拦截查询返回自己想要返回的结果。
*
* @param query 查询参数,开发者可以修改此对象,让系统发起查询时使用修改后的查询参数信息。
* @returns 返回`Promise`表示此函数是异步的,系统需要等待它`resolve`后再继续,如果此函数返回了一个
* 合法的`QueryDataResultInfo`对象,那么系统将不再发起默认的查询。
*/
onQueryData?(page: IVPage, query: QueryInfo): Promise<QueryDataResultInfo>;
/**
* 当一个数据查询完成后调用。通过此函数开发者可以修改查询结果,对查询结果进行二次处理(包括再加工、排序、过滤等)后再返回出去,例如:
*
* 1. 实现一个自定义排序,可以在此处读取query中的排序信息,内存排序后修改queryResult的结果顺序。
* 2. 为一些字段增加额外的文字字段。
*
* @param query 查询参数
* @param resid
* @param queryResult 查询结果,开发者可以修改此对象,让系统使用修改后的查询结果。
* @returns 返回`Promise`表示此函数是异步的,系统需要等待它`resolve`后再继续。
*/
onDidQueryData?(page: IVPage, query: QueryInfo, queryResult: QueryDataResultInfo): void | Promise<void>;
/**
* 在导出仪表板/报表/SuperPage之前调用,实现此接口需要自行生成导出文件。
*
* @param page
* @param args
*/
onExport?(page: IVPage, args: ExportAnaObjectArgs): Promise<ExportAnaObjectResult>;
/**
* 在生成导出文件之前调用。
*
* 1. 导出报表时,开发者可以通过修改 表格渲染对象`TableBuilder`实现自定义表格样式、数据
* @param page
* @param exportInfo 用于生成结果文件的一些中间产物,比如导出报表就是各个表格对象
*/
onGenerateExportFile?(page: IVPage, exportInfo: Array<TableBuilder> | any): Promise<void>;
/**
* 导出完成之后调用,可以替换或修改生成的blob文件。
* @param page
* @param result
*/
onDidExport?(page: IVPage, result: ExportAnaObjectResult): Promise<ExportAnaObjectResult>;
/**
* 提供给页面的个性化交互函数。
*
* 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>;
};
}
/**
* 数据模块的脚本事件接口
*/
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 const enum FAppCommandRendererId {
/** 流程应用顶部标签栏 */
Header = 'fappHeader',
/** 数据列表上的工具栏 */
DataListToolbar = 'fappDataListToolbar',
/** 数据列表上的工具栏中的"更多"菜单 */
DataListToolbarMoreMenu = 'fappDataListToolbarMoreMenu',
/** 表单应用表单内的右键菜单 */
FormContextMenu = 'fappFormContextMenu',
/** 以弹出对话框方式显示表单时的对话框,此时提供的命令将显示为对话框的按钮 */
FormDialog = 'fappFormDialog',
/** 表单上的工具栏 */
FormToolbar = 'fappFormToolbar',
/** 表单工具栏上的更多按钮 */
FormToolbarMoreMenu = 'fappFormToolbarMoreMenu',
/** 表单应用过滤树工具栏 */
FilterTreeToolbar = 'fappFilterTreeToolbar',
/** 表单应用过滤树右键菜单 */
FilterTreeContextMenu = 'fappFilterTreeContextMenu',
/**过滤树的添加按钮下拉菜单 */
FilterTreeAddMenu = 'fappFilterTreeAddMenu',
/**过滤树的更多按钮下拉菜单 */
FilterTreeMoreMenu = 'fappFilterTreeMoreMenu',
/** 表单应用数据列表的右键菜单 */
DataListContextMenu = 'fappDataListContextMenu',
/**填报单位树上的工具栏 */
OrgToolbar = 'fappOrgToolbar',
}
/**
* 表单应用的脚本事件接口
*/
export interface IFAppCustomJS extends IMetaFileCustomJS {
/**
* 提交数据前的事件。
*
* 当用户点击“提交”按钮,或者暂存直接入库的草稿数据时,如果数据校验无误可以提交,那么会先执行此事件,实现者可以在此事件中进行一些个性化的业务逻辑判断来决定是否允许用户继续进行数据提交。
*
* @param event 发生交互行为的事件对象,可从该事件对象中获取交互行为的上下文信息
* @returns 可返回:
* 1. `true` 执行正常的提交数据行为;
* 2. `false` 阻止此次数据提交操作;
* 3. `Promise<boolean>` 执行个性化的异步逻辑判断(比如弹出对话框确认),并根据异步判断返回值决定是否进行数据提交。实现者也可以选择在适当的时机调用`app.submitData()`执行默认的数据上报行为。
*/
onSubmitData?(event: FAppInterActionEvent): boolean | Promise<boolean>;
/**
* 成功上报数据后的事件。
*
* 实现者可以在此事件中进行一些个性化的业务逻辑,如发起流程、刷新缓存、调用其他脚本等。
*
* @param event 发生交互行为的事件对象,可从该事件对象中获取交互行为的上下文信息
* @returns 可返回:
* 1. `true` 执行默认的信息提示和跳转;
* 2. `false` 阻止默认的信息提示和跳转;
* 3. `Promise<boolean>` 执行个性化的异步逻辑判断(比如弹出对话框确认),并根据异步判断返回值决定是否进行默认的信息提示和跳转。
*/
onDidSubmitData?(event: FAppInterActionEvent): boolean | Promise<boolean>;
/**
* 删除数据前的事件。
*
* 当用户点击“删除”按钮时,且经过了的权限验证,那么会先执行此事件,实现者可以在此事件中进行一些个性化的业务逻辑判断来决定是否允许用户继续进行数据删除。
*
* @param event 发生交互行为的事件对象,可从该事件对象中获取交互行为的上下文信息
* @returns 可返回:
* 1. `true` 执行正常的删除数据行为,后面会弹出系统默认的提示对话框;
* 2. `false` 阻止此次数据提交操作,后面不会弹出系统默认的提示对话框;
* 3. `Promise<boolean>` 执行个性化的异步逻辑判断(比如弹出个性化的确认对话框),并根据异步判断返回值决定是否进行数据删除。实现者也可以选择在适当的时机调用`app.deleteData()`执行默认的数据删除行为。
*/
onDeleteData?(event: FAppInterActionEvent): boolean | Promise<boolean>;
/**
* 删除数据后的事件。
*
* 实现者可以在此事件中进行一些个性化的业务逻辑处理,如发起流程、刷新缓存、调用其他脚本等。
*
* @param event 发生交互行为的事件对象,可从该事件对象中获取交互行为的上下文信息
*/
onDidDeleteData?(event: FAppInterActionEvent): void;
/**
* 用户新建一个申请、一个维项、一条明细数据前的事件。
*
* 当用户点击“新增”按钮时,会先执行此事件,实现者可以在此事件中进行一些个性化的业务逻辑判断来决定是否允许新增数据,假如允许,则会打开表单。
*
* @param event 发生交互行为的事件对象,可从该事件对象中获取交互行为的上下文信息
* @returns 可返回:
* 1. `true` 执行正常的新增数据行为;
* 2. `false` 阻止此次新增数据操作;
* 3. `Promise<boolean>` 执行个性化的异步逻辑判断(比如弹出对话框确认),并根据异步判断返回值决定是否进行数据提交。实现者也可以选择在适当的时机调用`app.newData()`执行默认的数据上报行为。
*/
onNewData?(event: FAppInterActionEvent): boolean | Promise<boolean>;
/**
* 用户新建一个申请、一个维项、一条明细数据后,并已打开表单时的事件。
*
* 实现者可在此事件中进行一些个性化的业务逻辑,如进行计算、修改数据等。
*
* @param event 发生交互行为的事件对象,可从该事件对象中获取交互行为的上下文信息
*/
onDidNewData?(event: FAppInterActionEvent): void;
/**
* 第一次打开某张表单,准备创建表单前的事件。对于每张表单,该扩展函数只会调用一次,即切换到其他单位、数据期时,不会再次调用。
*
* 实现者通过提供自己的实现,可满足个性化展示、填报等需求,如使用树来代替表格显示浮动数据等。
*
* @param args 初始化参数
* @returns
* 1. `null` 使用系统默认的表单
* 2. 返回`Promise`表示此函数是异步的,系统需要等待它`resolve`后再继续,如果Promise返回了新的`IFAppForm`,那么将使用脚本返回的`IFAppForm`。
*/
onInitForm?(args: FAppFormArgs): void | Promise<IFAppForm>;
/**
* 创建一张表单后的事件。
* @param form 创建的表单
*
* 实现者可在此事件中进行一些个性化的业务逻辑,如进行计算、修改数据等。
*/
onDidInitForm?(form: IFAppForm): void;
/**
* 创建一个表单控件UI对象前的事件。
* @param args 构造参数
* @returns
* 1. `null` 不进行个性化,使用系统默认的实现
* 2. 返回新的控件UI对象,替换系统默认的实现
*/
onInitComponent?(args: FAppComponentArgs): void | IFAppComponent;
/**
* 显示一张表单前的事件,在触发该事件时,表单可能尚未创建。
*
* 实现者可在此事件中进行一些个性化的业务逻辑,如进行计算、修改数据等。
*
* @returns
* 1. `null`或`true` 不进行任何操作
* 2. `false`,阻止显示表单
*/
onShowForm?(args: {
/** 表单所属的表单应用对象 */
app: IFApp,
/** 将要显示的表单名 */
formName: string;
}): void | boolean;
/**
* 显示一张表单后的事件。
*
* 实现者可在此事件中进行一些个性化的业务逻辑,如进行计算、修改数据等。
*/
onDidShowForm?(args: {
/** 表单应用对象 */
app: IFApp,
/** 原来显示的表单名 */
lastFormName?: string;
/** 显示的表单名 */
formName: string;
}): void;
/**
* 构造报送单位树对象前的事件,此时树还没有构造出来。
*
* 以下情况有单位树或单位列表:
* 1. 设置了报送单位的周期填报应用;
* 2. 维护多级维维表的信息管理应用,此时单位树就是展示该维表的树;
* 3. 没有设置左侧过滤维表的信息管理应用,此时单位树就是展示该维表的树
*
* 实现者可在此事件中修改树或列表的初始化参数,如添加个性化样式、个性化列,监听点击事件等。
*
* @param args.app 表单应用对象
* @param args.initArgs 默认的初始化参数
* @param implClass 树或列表的实现类,可能为List、Grid、Tree、TreeGrid类
* @returns
* 1. `null` 使用系统默认的表单
* 2. 返回`Promise`表示此函数是异步的,系统需要等待它`resolve`后再执行构造操作,如果Promise返回了一个新的`ListArgs`,则使用该配置构造报送单位树。
*/
onInitOrgTree?(args: {
app: IFApp,
initArgs: ListArgs,
implClass: typeof List
}): void | Promise<ListArgs>;
/**
* 构造报送单位树完成后的事件。
*
* @param app 表单应用对象
* @param tree 树或列表对象,可能为List、Grid、Tree、TreeGrid对象
*/
onDidInitOrgTree?(args: {
app: IFApp,
list: List
}): void;
/**
* 即将渲染报送单位树时的事件,这个时候即将但未执行真正的渲染。
*
* 实现者可针对实际情况,通过修改数据,以满足个性化的渲染需求。
*
* @param args.app 表单应用
* @param args.data 数据列表的数据
* @param args.list 树或列表对象
* @returns
* 1. `null` 使用系统默认的刷新逻辑
* 2. 返回`Promise`表示此函数是异步的,系统需要等待它`resolve`后再执行刷新操作,如果Promise返回了一个新的结果,则使用该结果渲染报送单位树。
*/
onRenderOrgTree?(args: {
app: IFApp,
data: Array<JSONObject>,
list: List
}): void | Promise<Array<JSONObject>>;
/**
* 在构造数据列表前的事件,此时数据列表尚未创建出来。
*
* 实现者可在此事件中修改数据列表的初始化参数,如添加个性化样式、个性化列,监听点击事件等。
*
* @param args.app 表单应用对象
* @param args.initArgs 数据列表的初始化参数
* @param args.tabId 数据列表对应的流程应用标签页
*/
onInitDataList?(args: {
app: IFApp,
initArgs: TreeGridArgs | GridArgs,
tabId?: string
}): void | Promise<TreeGridArgs | GridArgs>;
/**
* 表单应用内部数据列表构造完成后的事件。
*
* @param args.app 表单应用对象
* @param args.list 数据列表对象
* @param args.tabId 数据列表对应的流程应用标签页
*/
onDidInitDataList?(args: {
app: IFApp,
list: List,
tabId?: string
}): void;
/**
* 即将渲染数据列表条目时的事件,这个时候即将但未执行真正的渲染。
*
* 实现者可针对实际情况,通过修改数据,以满足个性化的渲染需求。
*
* @param args.app 表单应用
* @param args.data 数据列表的数据
* @param args.list 列表对象
* @returns
* 1. `null` 使用系统默认的刷新逻辑
* 2. 返回`Promise`表示此函数是异步的,系统需要等待它`resolve`后再执行刷新操作,如果Promise返回了一个新的结果,则使用该结果渲染数据列表。
*/
onRenderDataList?(args: {
app: IFApp,
data: Array<JSONObject>,
list: Grid
}): void | Promise<Array<JSONObject>>;
/**
* 获取用于刷新表单应用中工具栏或菜单的条目的事件。
*
* 实现者可在此事件中实现一些个性化的工具栏或菜单的刷新逻辑,如添加自定义按钮,调整按钮顺序等。
*
* @param rendererId 工具栏或菜单ID
* @param commands 系统默认的命令信息
* @returns 可返回:
* 1. `null`或`undefined` 不进行个性化,使用默认规则刷新工具栏;
* 2. `Array<string | CommandItemInfo>` 根据该结果刷新工具栏;
* 3. `Promise<Array<string | CommandItemInfo>>` 根据异步逻辑返回的结果刷新工具栏。
*/
onFetchCommands?(args: {
app: IFApp,
rendererId: FAppCommandRendererId,
commands: Array<string | CommandItemInfo>
}): Array<string | CommandItemInfo> | Promise<Array<string | CommandItemInfo>>;
/**
* 即将执行一个命令或操作前的事件。
*
* 1. 实现者可在这里执行扩展的命令
* 2. 实现者可通过返回结果阻止系统执行内部的命令,如在提交前先弹出确认对话框等
*
* @param event 发生交互行为的事件对象,可从该事件对象中获取交互行为的上下文信息
* @param command 命令id
* @param args 命令参数
* @returns
* 1. `true` 执行默认的命令;
* 2. `false` 阻止执行默认的命令;
* 3. `Promise<boolean>` 执行个性化的异步逻辑判断(比如弹出对话框确认),并根据异步判断返回值决定是否进行内部的命令。
*/
onDoCommand?(event: FAppInterActionEvent, command: string, args?: JSONObject): boolean | Promise<boolean>;
}
/**
* 分析应用的脚本事件接口
*/
export interface IAnaCustomJS extends IMetaFileCustomJS {
/**
* 创建一个分析控件UI对象前的事件。
* @param args 构造参数
* @returns
* 1. `null` 不进行个性化,使用系统默认的实现
* 2. 返回新的构造参数,替换系统默认的参数
* 3. 返回新的控件UI对象,替换系统默认的实现
*/
onInitComponent?(args: AnaComponentArgs): void | AnaComponentArgs | AnaComponent;
}
/**
* 元数据管理界面的脚本事件接口
*/
export interface IMetaMgrCustomJS extends ICustomJS {
/**
* 当在浏览器窗口中打开一个新的元数据管理界面时调用,调用此函数时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>;
}
/**
* 数据集额的数据变化事件。当调用了数据集的数据修改或者刷新函数后,触发数据变化事件。
*
* 增量更新时,存在currentIndex,addRow,deleteRow,modifyRow属性;全量更新时则不存在。
*/
export interface IDatasetEvent {
/**数据集 */
dataset: IDataset;
/**刷新所有数据,包括currentDataRowIndex。 */
refreshAll?: boolean;
/**当前行。如果存在该属性,则是当前行发生变化。 */
currentIndex?: number;
/**新增的行。 */
addRows?: JSONObject[];
/**删除的行。 */
deleteRows?: JSONObject[];
/**修改的行。 */
modifyRows?: { row: JSONObject, fieldNames: string[] }[];
/**仅查询总行数。 */
totalRowCount?: number;
}
/**
* IDatasetRefreshArgs.loadMore的格式。按范围加载数据和按条件加载数据只能存在一个。通常在移动列表等有加载更多的操作时使用,加载更多不能和翻页同时使用。
*/
export interface DatasetLoadMoreArgs {
/**
* 追加数据的起始位置
*/
offset?: number;
/**
* 追加数据的数量
*/
limit?: number;
/**
* 追加数据的过滤条件。如列表的加载更多,每次加载一个行政区划的数据,那么初始化时传递的filter就是第一个行政区划,后续每次点击都传递一个新的行政区划。
* 如果传递了FilterInfo[],那么表示一次加载多个区块数据。
* TODO 还未实现
*/
filter?: FilterInfo | FilterInfo[];
}
/**
* 刷新dataset数据参数。
* 不缓存多页数据。
*/
export interface IDatasetRefreshArgs {
/**是否强制刷新,为true时无论数据集依赖的条件、参数和分页设置是否变化,都会触发查询。 */
force?: boolean;
/**要查询的字段,如果没有传入,则使用上次查询的字段,如果上次查询字段为空,则不查询。 */
fields?: string[];
/**是否要查询所有字段,默认为false */
queryAllFields?: boolean;
/**每行行数,如果没有传入,则使用上次的设置 */
pageSize?: number;
/**分页数,如果没有传入,则使用上次的设置 */
pageNum?: number;
/**是否刷新总行数。 */
refreshTotalRowCount?: boolean;
/**是否通知数据变化事件。由控件触发的refresh不应该触发事件变化,否则就循环了。 */
notifyChange?: boolean;
/**追加数据。详见DatasetLoadMoreInfo的注释。 */
loadMore?: DatasetLoadMoreArgs;
}
/**
* 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[];
}
/**
* 数据集接口。一个数据集描述一个仪表板、superpage、报表等对象的数据模型的数据访问和修改接口。
*/
export interface IDataset {
/**
* 返回数据集的ID,和对应的数据模型的ID一致。
*/
getId(): string;
/**
* 设置当前行。更多当前行的描述见`getCurrentDataRow()`函数。
*
* @param index 行号,从0开始。
*/
setCurrentDataRowIndex(index: number): Promise<void>;
/**
* 返回当前行号。更多当前行的描述见`getCurrentDataRow()`函数。
*
* @returns 返回当前行号,从0开始。
*/
getCurrentDataRowIndex(): number;
/**
* 返回当前行数据。默认当前行是第0行。
*
* 数据集对象有一个“当前行”的概念,普通的输入控件(指输入框、下拉框这类只能显示一个值的控件)绑定一个多行数据集的字段时,
* 将自动显示“当前行”的数据。通过`setCurrentDataRowIndex()`函数可以设置当前行是哪行,当前行发生变化时,相关的界面
* 元素也会随之刷新。
*
* @returns 返回当前行数据,是一个key是字段名,value是字段值的json。
*/
getCurrentDataRow(): JSONObject;
/**
* 返回当前分页的所在页。
*/
getCurrentPageIndex(): number;
/**
* 返回当前数据集中的所有行。
*
* @returns 当前数据集没有查询时返回`null`,当前数据集查询过但没有满足条件的数据时返回空数组。
*/
getRows(): JSONObject[];
/**
* 返回“当前行”(见`getCurrentDataRow()`函数说明)指定字段的值。
*
* @param field 字段名
* @returns 如果没有数据行,则返回null。
*/
getFieldValue(field: string): any;
/**
* 根据单个主键返回数据行。如果有多个主键,那么其他主键一定指定了过滤条件,剩余的单个主键查询出来的结果是不重复的。
* @param key
*/
getDataRowByKey(key: any): any[];
/**
* 根据主键返回行,仅能返回已经查询过的分页数据。
* @param keys
*/
getDataRowByKeys(keys: JSONObject): any[];
/**
* 设置排序,要排序生效需要调用refresh。
*/
setSort(sorts: QuerySortInfo[]): void;
/**
* 返回排序设置。
*/
getSort(): QuerySortInfo[];
/**
* 添加过滤条件,不会修改数据集定义时设置的过滤条件,要过滤条件生效需要调用refresh。
* 对象内自带的过滤条件总是作用,不用调用此接口设置。
* 新增的过滤条件不能引用对象内参数,因为不确定参数是否被计算过。
* @param filters
*/
setCustomFilter(filters: FilterInfo[]): void;
/**
* 返回过滤设置。
*/
getCustomFilter(): FilterInfo[];
/**
* 返回dataset数据装载完毕后的promise。
*/
ready(): Promise<void>;
/**
* 查询当前行数据。如果要查询的字段在当前已缓存数据中都存在,则直接返回;否则发起缺失的字段查询并回填到当前行中。
* @param fields
*/
fetchCurrentRow(fields: string[]): Promise<any[]>;
/**
* 发起自定义的查询,查询的字段必须在当前数据集对应的模型中。返回的数据不缓存。
* @param args
*/
fetchCustomData(args: IDatasetCustomFetchDataArgs): Promise<QueryDataResultInfo>;
/**
* 重新请求数据集数据。
* @param args 查询参数,如果没有传入,默认为{force: true}。
*/
refresh(args?: IDatasetRefreshArgs): Promise<void>;
/**
* 重新请求总行数。
* @param args
*/
refreshTotalRowCount(args?: IDatasetRefreshTotalRowCountArgs): Promise<void>;
/**
* 重新请求分组行数。
* @param args
*/
refreshGroupByTotalRowCount(args?: IDatasetRefreshGroupByTotalRowCountArgs): Promise<void>;
/**
* 设置分页大小。
* @param size
*/
setPageSize(size: number): void;
/**
* 返回每一页的行数。
*/
getPageSize(): number;
/**
* 返回总行数。
*/
getTotalRowCount(): number;
/**
* 返回分组后的行数。
* @param groupByFields
*/
getGroupByTotalRowCount(groupByFields: string[]): any[][];
/**
* 返回总页数。
*/
getTotalPageCount(): number;
/**
* 是否存在下一行数据,根据当前行计算而来。
*/
hasNext(): boolean;
/**
* 是否存在上一行数据,根据当前行计算而来。
*/
hasPrevious(): boolean;
/**
* 保存当前行修改并移动当前行到下一行。
*/
next(): Promise<void>;
/**
* 保存当前修改并移动当前行到上一行。
*/
previous(): Promise<void>;
/**
* 是否存在下一页。
*/
hasNextPage(): boolean;
/**
* 是否存在上一页。
*/
hasPreviousPage(): boolean;
/**
* 下一页。
*/
nextPage(): Promise<void>;
/**
* 上一页。
*/
previousPage(): Promise<void>;
/**
* 装载指定页数据。
* @param index
*/
loadPage(index: number): Promise<void>;
/**
* 追加数据。
* @param args
*/
loadMore(args: DatasetLoadMoreArgs): Promise<void>;
/**
* 插入行。
* @param row
*/
insert(row: JSONObject | JSONObject[]): Promise<void>;
/**
* 删除指定主键的行或指定字段对应的行。
* 删除数据不一定要依赖主键,也可能是其它业务过滤条件字段,如要删除当前行有ABC 3个字段,要删除的数据集假设只有AB两个字段,那么删除这2个字段对应的数据
* @param keys 指定的主键或字段及其对应的数据信息
* @param notifyChange 是否需要立即刷新数据。默认为true,若是提交数据则传false,可以在提交之后通过dw数据变化通知刷新。
*/
delete(keys: JSONObject | JSONObject[], notifyChange?: boolean): Promise<void>;
/**
* 修改指定主键额的行。
* @param keys
* @param values
*/
modify(keys: JSONObject, values: JSONObject): Promise<void>;
/**
* 更新当前行的指定字段的值。
* @param field
* @param value
*/
setFieldValue(field: string, value: any): Promise<void>;
/**
* 更新当前行的多个字段的值,如果传入了keys,则通过keys找到指定行。
* @param values
* @param keys
* @param notifyChange 是否需要立即刷新数据。默认为true,若是提交数据则传false,可以在提交之后通过dw数据变化通知刷新。
*/
setFieldValues(values: JSONObject, keys?: JSONObject, notifyChange?: boolean): Promise<void>;
/**
* 根据当前数据集的修改生成提交数据包,如果没有任何修改,则返回null。可以一次性获取到所有数据集的数据包,然后一起提交。
*/
makeDataPackage(): CommonTableSubmitDataPackage;
/**
* 批量将一批数据保存到数据集中。如提交表单时,将整个表单中所有输入控件的数据提交给数据集。此时并不会提交到数据模型。
* @param data JSONObject表示提交到一个单行数据集。数组表示提交全量覆盖一个多行数据集,修改的行需要带上完整的keys,用于查找要覆盖的行;新增的行无需keys。
* @param notifyChange dataset刷新,通知影响的控件刷新数据,默认为true
* @param overwrite 覆盖数据集所有数据 默认为false
* 在superPage中收集浮动控件草稿数据包时会指定allData=true, 提交数据时根据allData=true 指定overwrite
*/
saveDraft(data: DatasetDataPackageRowInfo[], notifyChange?: boolean, overwrite?: boolean): void;
/**
* 批量将一批数据复制粘贴到目标数据集中。
* @param data
* @param notifyChange 是否需要立即刷新数据。默认为true,若是提交数据则传false,可以在提交之后通过dw数据变化通知刷新。
*/
mergeInto(data: DatasetDataPackageRowInfo[], notifyChange?: boolean): void;
/**
* 重置数据集的修改。
*/
revert(): Promise<void>;
/**
* 清除修改状态。当数据提交后,需要将当前数据集的修改状态清理掉,如行的新增、删除,字段的修改等。
*/
clearModifyStatus(): void;
}
/**
* 报表、仪表板、superpage等可视化分析对象支持的事件
*/
export const enum IVPageEventName {
/**
* 当报表、仪表板、superpage等内部的某个输入控件的值发生变化时调用,通常这种变化是由用户的操作
* 引起的,而不是脚本调用`setValue()`函数调用引起的。
*/
oninputchange = "oninputchange",
/**
* 当页面出现错误时(如报表计算错误、表单提交数据错误、spg提交数据错误……)调用,脚本可以控制错误的显示
* 方式、处理方式以及是否显示默认的错误提示框。
*/
onerror = "onerror",
/**
* 数据提交(包括提交、删除、导入、复制、插入等所有提交操作)成功后调用。
*/
onsubmit = "onsubmit",
/**
* 页面显示时调用,执行此函数时组件只是准备要显示但还未显示(display还是none)。
*/
onshow = "onshow",
/**
* 页面隐藏前调用,可用于在页面隐藏前销毁一些资源,比如脚本自己开启的timer.
*
* 执行此函数时组件只是准备要隐藏但还未隐藏(display还不是none
*/
onhide = "onhide",
}
/**
* 报表、仪表板、superpage等可视化分析对象的一个脚本API抽象接口。
*/
export interface IVPage extends IParameterized {
/**
* 监听某个事件的变化。
*
* @param event 事件名,小些。
* @param listener 回调函数。
*/
on(event: IVPageEventName, listener: EventListener<IVPage, any>): void;
/**
* 删除某个监听
*
* @param event 事件名,小些。
* @param listener 要删除的回调函数,比如和`on`函数调用时传递的是同一个对象。
*/
off(event: IVPageEventName, listener: EventListener<IVPage, any>): void;
/**
* 返回文件信息。
*/
getFileInfo(): MetaFileInfo;
/**
* 返回外部传入的URL参数。
*/
getUrlParams(): JSONObject;
/**
* 清空数据并刷新整个页面。
*/
refresh?(): Promise<void>;
/**
* 刷新指定数据集,如未指定数据集参数,则刷新所有数据集。
* @param modelIds
*/
refreshDatasets?(modelIds?: string[]): Promise<void>;
/**
* 返回指定ID的控件,无法获取到浮动数据对应的控件,如一个图形在浮动面板中。
* @param id
*/
getComponent(id: string): IVComponent;
/**
* 找到页面下所有可提交的数据,并提交到数据集,不会提交到数据模型,需要调用dataset.submitData()提交数据。
*/
collectDraft(): Promise<DatasetDataPackageInfo>;
/**
* 如果当前是superpage,则返回提交的数据包。
*/
getSubmitDataArgs?(): API.spg.SpgSubmitDataArgs;
/**
* 根据数据集的名称返回数据集。
*/
getDataset?(name: string): IDataset;
/**
* 返回所有数据集。
*/
getDatasets(): IDataset[];
}
/**
* 报表、仪表板、superpage等可视化分析对象内的一个“控件”对象,此对象抽象了“控件”的脚本API接口。如果控件是一个浮动控件,则代表一个
* 浮动区域,通过`getFloatInstances()`获取到浮动主控件的浮动实例。
*
* TODO extends IValuable
*/
export interface IVComponent {
/**
* 返回控件ID。
*/
getId(): string;
/**
* 返回控件类型,如table,bar。
*/
getType(): string;
/**
* 返回是否是输入控件。
*/
isInputComponent(): boolean;
/**
* 返回是否是容器控件。
*/
isContainerComponent(): boolean;
/**
* 获得报表、仪表板、superpage对象。
*/
getPage(): IVPage;
/**
* 获取组件的UI对象。
*
* “调用组件方法”交互,需要获取到指定组件的UI对象,去执行特定的方法。
*/
getUIComponent(): AnaComponent;
/**
* 获得父控件,根控件是画布,画布的父控件是null。
*/
getParent(): IVComponent;
/**
* 如果当前控件是容器控件,返回下级控件,否则返回null。
*/
getChildren(): IVComponent[];
/**
* 在当前控件所在的命名控件中获得指定控件,如果当前控件是一个浮动面板内部的控件,那么获得的是当前浮动面板内部的控件,如果没有再向上查找,找不到返回`null`。
* @param id
*/
getComponent(id: string): IVComponent;
/**
* 返回是否是浮动控件,如浮动面板、报表浮动区域主单元格。浮动控件是自身会重复的控件,列表内部也有一个FloatQuery,但它自身不会重复,所以不是浮动控件。
*/
isFloatComponent(): boolean;
/**
* 如果当前控件有FloatQuery,则返回所有浮动的下级控件,否则返回null。如浮动面板的下级控件。
*/
getFloatChildren(): { [id: string]: IVComponent }[];
/**
* 如果当前控件是一个浮动控件,那么返回所有浮动出来的实例,否则返回null。如浮动面板返回的是浮动面板浮动出来的实例,是一个浮动面板的IVComponent数组。
*/
getFloatInstances(): IVComponent[];
/**
* 返回当前控件所在的浮动行数据。如浮动面板中的输入控件返回的是所在浮动面板的浮动行数据。
*/
getParentDataRow(): JSONObject;
/**
* 如果当前控件是多行控件,如列表,则返回所有行数的数据;单行数据控件返回null。
*/
getDataRows(): JSONObject[];
/**
* 如果当前控件是一个可以选中的控件,那么获取用户选中的数据。
*/
getSelectedDataRows(): JSONObject[];
/**
* 如果当前控件是一个可以勾选的控件,那么获取用户勾选的数据。
*/
getCheckedDataRows(): JSONObject[];
/**
* 如果当前控件是一个可以选中的控件,那么设置需要选中的数据。
* @param selectedDataRows
*/
setSelectedDataRows(selectedDataRows: number[]): void;
/**
* 如果当前控件是一个可以勾选的控件,那么设置需要勾选的数据。
* @param checkedDataRows
*/
setCheckedDataRows(checkedDataRows: number[]): void;
/**
* 收集控件下所有可提交的数据,需要调用dataset.submitData()提交数据。
* @param notifyChange
*/
collectDraft(notifyChange?: boolean): DatasetDataPackageInfo;
/**
* 设置value。所有控件都可以设置value。
* @param value
* @param fire 是否触发相关事件和交互动作,默认true
*/
setValue(value: any, fire?: boolean): void;
/**
* 返回控件的value。输入控件返回输入控件的值,富文本返回富文本的json格式内容,img返回src,列表返回列表的数据对象。
*/
getValue(): any;
/**
* 返回控件的txt,txt是value对应的描述文字,如日期的值是20200101,txt是2020年1月1日。
*/
getTxt(): string;
/**
* 设置属性值。
*
* “设置组件属性”交互可以让用户通过交互设置组件的特定属性,比如设置图片组件的src属性,此函数是组件对外公布的一个设置自己特定属性的接口。
*
* @param name 属性名,组件能被交互设置的属性,应该通过函数`getAccessablePropertyNames`(见builder.ts)报告出去。
* @param value
* @param fire
*/
setProperty(name: string, value: any, fire?: boolean): void;
/**
* 返回指定名称的属性值。
* @param name
*/
getProperty(name: string): any;
/**
* 执行控件提供的方法,触发行为。如点击列表的某行数据,点击按钮等。
*
* “调用组件方法”交互可以让用户通过交互执行特定组件的特定方法,比如调用视频组件的快进方法,此函数是组件对外公布的一个调用自己特定方法的接口。
*
* @param method 方法名,组件能被交互调用的方法应该通过函数`getAccessableMethodInfos`(见builder.ts)报告出去。
* @param params
*/
invokeMethod(method: string, params?: JSONObject): Promise<void>;
/**
* 设置控件的placeholder。
* @param placeholder
*/
setPlaceholder(placeholder: string): void;
/**
* 返回控件的placeholder。
*/
getPlaceholder(): string;
/**
* 设置控件disabled的状态。
* @param disabled
*/
setDisabled(disabled: boolean): void;
/**
* 返回控件的disabled状态。
*/
isDisabled(): boolean;
/**
* 设置控件的checked状态,只有可勾选的控件有效,如勾选框。
* @param checked
*/
setChecked(checked: boolean): void;
/**
* 返回控件的checked状态。
*/
isChecked(): boolean;
/**
* 设置控件的visible状态。
* @param visible
*/
setVisible(visible: boolean): void;
/**
* 返回控件的visible状态。
*/
isVisible(): boolean;
}
/**
* 记录输入控件属性计算后的属性值,通过监听器的on事件传递给外部。
*/
export interface InputDataChangeInfo {
/**
* 修改的对象id。
*/
cid?: string;
/**
* 修改的对象属性名。
*/
p?: string;
/**
* 修改的对象属性值。
*/
v?: any;
/**
* 修改的对象属性原值。
*/
ov?: any;
}
/**
* 草稿数据包中,数据行的类型。表明对当前行数据的操作,辅助生成最终的提交数据包。
*/
export const enum DatasetPackageRowType {
Add = 'a',
Delete = 'd',
Modify = 'm'
}
/**
* 数据集的行数据。
*/
export interface DatasetDataPackageRowInfo {
/**数据行。 */
data?: { [field: string]: any };
/**数据行类型 */
type?: DatasetPackageRowType;
/**嵌入的superpage数据。 */
embedsuperpages?: { [compId: string]: { superPage: IVPage, packageInfo?: DatasetDataPackageInfo } };
/**装载数据时的主键。 */
oldKeys?: { [field: string]: any };
/**
* 自增长主键。
* 使用场景:BI-38611 上报数据时记录虚拟主键,并非真是的数据库自增长序号,只是为了唯一标记一行数据,防止提交表单失败后再次上报会提交2条自增长数据
*/
oldIncreaseKey?: string;
}
export const EMPTY_KYES_STRING = '*';
/**
* superpage用于保存草稿的数据结构。
*/
export interface DatasetDataPackageInfo {
/**superpage中不同dataset数据。 */
packages?: {
/** key 是JSON.stringify(originPrimaryKeys) */
[datasetId: string]: {
/**
* 是否是全量数据,默认为false
* 全量数据要覆盖dataset原有多行数据。一般只有浮动控件才会上报多行全量数据
* 只是增量数据时,合并数据包数据与dataset 数据,即不删除主键不匹配的数据行
*/
allData?: boolean;
rows: { [key: string]: DatasetDataPackageRowInfo }
};
};
/**提交数据的输入组件。 */
inputs?: IVComponent[]
}
/**
* 脚本交互行为事件。
*/
export interface InterActionEvent {
/**
* 当前页面对象。
*/
page: IVPage;
/**
* 当前页面的UI对象。
*/
renderer?: IAnaObjectRenderer;
/**
* 用户所点击的控件。
*/
component?: IVComponent;
/**
* 触发事件的UI对象,使用的地方按需强制转换
*/
uicomponent?: Component;
/**
* 对应的dataset数据行。
*/
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接口
*/
export interface IFApp {
/**
* 表单浏览界面的数据对象。
*/
dataMgr?: IFAppBrowseDataMgr;
/**
* 表单填报界面的数据对象
*/
formsDataMgr?: FAppFormsDataMgr;
/**
* 获取当前显示的表单
*/
getCurrentForm(): IFAppForm;
/**
* 显示某张表单
* @param name 表单名称,忽略大小写
*/
showForm(name: string): Promise<IFAppForm>;
/**
* 执行控件提供的命令,触发行为。如点击列表的某行数据,点击按钮等。
* @param command 命令名
* @param data 参数
*/
doCommand(command: string, data?: JSONObject): Promise<void>;
/**
* 提交数据。
* @param args 提交参数 TODO 待完善
* @returns 未提交则返回false,例如脚本中阻止了提交。
*/
submitData(args?: JSONObject): Promise<void | boolean>;
}
/**
* 初始化一张表单对象的参数
*/
export interface FAppFormArgs {
/** 表单所属的表单应用对象 */
app: IFApp;
/** 表单的默认实现类 */
defaultImplClass: Constructable<IFAppForm>;
/** 表单名 */
formName: string;
/** 表单数据对象 */
builder: FAppFormBuilder;
/** 当前展现设备类型 */
deviceType: DeviceType;
/** 当前查看模式 */
viewMode: FAppViewMode;
/** 控件的父dom,如果构造时未传递,那么可在构造后调用{@link #setDomParent()}设置。*/
domParent?: HTMLElement;
}
/**
* 表单应用内的一张“表单”对象,此对象抽象了“表单”的脚本API接口。
* 外部实现自己的表单对象时,需实现此接口。
*/
export interface IFAppForm extends Component {
/**
* 表单所属的表单应用对象
*/
app: IFApp;
/**
* 获取表单名称
*/
getName(): string;
/**
* 获取表单中指定id的控件,支持以`父控件ID.子控件ID`这样的方式获取子控件
* @param id
*/
getComponent(id: string): IFAppComponent;
/**
* 表单内部会有一些异步的初始化逻辑,在外部调用表单对象的相关接口时,会通过此方法等待表单初始化完成
*
* @returns 初始化的Promise对象
*/
waitInit(): Promise<void>;
/**
* 全量刷新。此方法是内部自动调用的。
* 会在第一次打开表单或切换单位、数据期等执行。表单对象会复用,建议实现者尽量复用内部的对象和DOM。
*
* @param formData 表单的数据 TODO 传接口
* @returns 刷新的Promise对象
*/
doLoadData(formData?: FAppFormData): Promise<void>;
/**
* 增量刷新。
*
* 当用户在界面中进行了修改操作,可能触发一系列的影响修改,会引起一些控件的数据、状态发生修改。调用者可根据本方法的传入参数对UI进行刷新。
*
* @param args 修改的信息
* @returns 增量刷新的Promise对象
*/
doRefreshData(args: {
/**
* 修改的信息,是一个数组
*
* 对于每一个元素,
* 1. comp 表示修改的是控件,此时p属性表示需要刷新的属性,没有p属性表示刷新的属性是value
* 2. row 表示修改是浮动行
* 3. op 有row时可能有此属性,`+`代表是新增的行,`-`代表是删除的行,无此属性代表是修改的行
*/
data?: Array<{ comp?: FAppComponentData, row?: FAppFloatAreaDataRow, op?: '+' | '-', }>
}): Promise<void>;
}
/**
* 表单控件UI对象的构造参数
*/
export interface FAppComponentArgs {
/** 控件类型 */
type: string;
/** 控件的数据对象,用于获取属性 */
builder: FAppComponentBuilder;
/** 查看的设备类型 */
deviceType: DeviceType;
/** 控件编译信息,预览和填报界面需要传此参数 */
compiledInfo?: FAppComponentCompiledInfo;
/** 填报数据的管理对象 */
dataManager?: FAppFormsDataMgr;
}
/**
* 表单应用内的一个“控件”的UI对象,此对象抽象了“控件”的脚本API接口。
*
* TODO 待完善,尽量和分析控件使用相同的接口
*/
export interface IFAppComponent extends Component {
/**
* 等待控件初始化结束。控件内部元素的初始化过程可能是异步的,需要通过本方法让在刷新控件时,先等待控件初始化完毕。
* @returns
* 1. `null` 此控件的初始化过程不是异步的,可以直接进行交互操作
* 2. 控件初始化的Promise对象
*/
waitInit?(): void | Promise<void>;
/**
* 等待控件渲染结束。在修改控件数据和状态时,会触发控件的渲染,渲染过程可能是异步的,通过本方法告诉外部控件渲染已完成。
* @returns
* 1. `null` 此控件的渲染过程不是异步的
* 2. 渲染的Promise对象
*/
waitRender?(): void | Promise<void>;
/**
* 装载数据
* @param data 控件的数据。大部分情况下,不会传入此参数,需要控件自己去获取获取。只有列表控件、子表单控件等复杂控件,需要自行处理内部子控件的装载,需要将数据传递到子控件中。
* @returns
* 1. `null` 装载了数据,且渲染完毕
* 2. 渲染的Promise对象
*/
loadData(data?: FAppComponentData): void | Promise<void>;
/**
* 控件数据或属性(如控件值或显示条件)发生了变化,此时可以通过本方法实现控件的局部刷新。
* @param data 控件数据对象
* @param property 修改的属性,不传时代表修改的是value
* @returns
* 1. `null` 装载了数据,且渲染完毕
* 2. 渲染的Promise对象
*/
refreshData(data: FAppComponentData, property?: string): void | Promise<void>;
/**
* 获得报表、仪表板、superpage对象
*/
getForm(): IFAppForm;
/**
* 获得父控件,根控件是画布,画布的父控件是`null`
*/
getParent(): IFAppComponent;
/**
* 获得控件绑定的数据
*/
getData(): any;
/**
* 获得当前行或当前浮动面板的数据。
*/
getRowData(): FAppFloatAreaDataRow;
/**
* 如果当前控件是一个可以勾选的控件,那么获取用户勾选的数据
*/
getCheckedData(): any;
}
/**
* 表单应用脚本交互行为发生时调用脚本函数所传递的参数。
*/
export interface FAppInterActionEvent extends InterActionEvent {
/** 表单所属的表单应用对象 */
app: IFApp;
/**
* 是否是保存草稿操作
*
* 实际场景:暂存直接入库的草稿数据,也会执行`onSubmitData`事件,需要将该参数传给该事件,便于实现者对这种情况进行个性化。
*/
isDraft?: boolean;
}
/**
* 这个命名空间定义各个模块的一些二次开发或跨模块引用时经常用到的一些api函数。
*
* 这些api函数通常各自独立,用完即走。
*/
export namespace API {
export namespace ana {
/**
* 批量导出分析对象为 pdf | excel | csv。
* 总是会下载一个压缩文件。
* @param args
*/
export function exportContent(args: BatchExportAnaObjectArgs, download = true): Promise<RcExportResultInfo> {
let taskId = uuid();
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 {
/**
* 提交一个数据包
*
* @param dataPackage
*/
export function submitData(dataPackage: FAppDataPackageInfo): Promise<void> {
return null;
}
/**
* 脚本启动一个流程,
*/
export function startFlow(args: {
/**流程应用的id,或路径*/
resId: string,
/** 流程实例id,调用者可以提前将数据写好写到表单的模型中,自己`INSTANCE_ID`字段添上一个uuid,当然也可以不
* 提前吧数据填好,而是通过data参数传递给此函数,此函数写表单数据
*/
instanceId?: string,
/**
* 是否自动完成第一个人工任务节点,例如在下发任务的时候通过脚本启动的流程可能不需要自动完成第一个人工任务节点,
* 如果通过脚本启动流程后第一个人工任务节点明确是自己的,那么此时可能就需要自动结束第一个人工任务节点,默认为false
*/
autoComplete?: boolean,
/**启动时附带传递的表单数据,简单的名值对形式,用于写入简单的表单数据给表单*/
data?: JSONObject,
}): Promise<void> {
return null;
}
/**
* 批量启动启动流程
*/
export function startFlows(args: {
/**流程应用的id,或路径*/
resId: string,
/** 流程实例id */
instanceIds: Array<string>,
}): Promise<void> {
return null;
}
/**
* 执行流程的审批、退回、否决等动作
*/
export function processFlow(args: {
/**流程应用的id,或路径*/
resId: string,
/** 流程实例id */
instanceId: string,
/** 提交数据时同时需要执行的操作,如审批等 */
operate: FlowOperate;
/** 流程节点ID */
nodeId?: string;
/** 目标节点ID,在退回时可以指定退回到的节点 */
targetNodeId?: string;
/** 审批意见 */
comment?: string,
/**启动时附带传递的表单数据,简单的名值对形式,用于写入简单的表单数据给表单*/
data?: JSONObject,
}): Promise<void> {
return null;
}
}
/**
* 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;
}
}
/**
* spg模块的api
*/
export namespace spg {
const UPDATE_TYPES = ["inserted", "updated", "deleted"];
const UPDATE_TYPE_MAP = {
"inserted": DataChangeType.insertdata,
"updated": DataChangeType.updatedata,
"deleted": DataChangeType.deletedata
}
/**spg上数据提交、更新、导入等操作的数据包格式 */
export interface SpgSubmitDataArgs {
/**
* 由于多个spg可能一起提交数据,所以这里支持一个数组,每个元素是一个spg,如果内部有嵌套spg,那么也
* 是一个独立的数组元素(也就是说所有嵌套的子页面都会被拉平,和父页面平级放到这个数组中提交)。
*/
pages: Array<{
/**
* spg页面的资源id,后端会获取对应的模型更新约束
*/
resId?: string,
/**
* 参数和可能被引用到的变量的值,这些值用于帮助后端确定数据更新的范围
*/
params?: JSONObject,
/**
* 交互的事务ID。触发执行后端交互时,只需要传入事务ID,后端通过事务ID找到对应的交互列表。详见AnaActionTransaction。
*/
transactionId?: string;
/**
* 验证码的内容,可以是用户输入的短信验证码、图片验证码或者甚至是滑块验证码的信息。
*/
verificationCode?: string;
/**
* 增加、修改、删除数据。
* datasetId是spg内部引入的模型或数据集内部id(不是模型的资源id)
*
* 多个page对同一个模型增加数据时,后台会判断主键是否相同,并将相同主键的数据合并成一条。
* 如果使用了默认值表达式在后台生成主键值,且其他主键值也无法用于区分数据(比如可能都是同一个数据级次),那么后台会按照数据顺序进行匹配合并。
*/
packages?: { [datasetId: string]: SpgSubmitDataPackage },
/**
* 导入文件数据到指定的表。
*/
importFile?: {
/** 提前上传好的文件的文件id*/
fileId: string,
/** 文件名 */
fileName?: string,
/** 目标数据集id,是用户在“相关资源”中提前定义好的数据集的id */
targetDatasetId?: string,
/**覆盖模式 */
overwriteMode?: "all" | "append" | "merge",
/** 导入同名字段的数据 */
importSameFields?: boolean,
/** 关联数据集 */
joinDatasetId?: string;
/** 关联数据集字段 */
joinDatasetField?: string;
/** 关联目标数据集字段 */
joinFileField?: string;
/** 需要额外写入到目标表的字段值*/
fieldValues?: Array<{
/** 目标表字段名,可以是逻辑名也可以是物理名 */
name: string;
/**
* const: 表示是常量,可能是number,也可能是字符串
* fileField:文件列名,支持模糊匹配
* joinDatasetField:表示是关联表的一个字段
*/
valueType: "const" | "fileField" | "joinDatasetField",
value?: string;
}>
},
/**
* 将一个查询的数据复制到另一个表
*/
copyDataTo?: {
/** 引用源数据集id,是用户在“相关资源”中提前定义好的数据集的id */
sourceDatasetId?: string,
/** 目标数据集id,是用户在“相关资源”中提前定义好的数据集的id */
targetDatasetId?: string,
/**覆盖模式,需要注意的是,merge模式是需要模型存在主键的,否则无法匹配数据 */
overwriteMode?: "all" | "append" | "merge",
/** 复制同名字段的数据 */
importSameFields?: boolean,
/** 需要额外写入到目标表的字段值*/
fieldValues?: Array<{
/** 目标表字段名,可以是逻辑名也可以是物理名 */
name: string;
/**
* const: 表示是常量,可能是number,也可能是字符串
* field: 表示是一个源表的字段
*/
valueType: "const" | "field",
/** 表示是一个表达式,可以引用源数据集的字段 */
value?: string | number;
}>,
sourceFilters?: FilterInfo[],
/** 当 overwriteMode 为 merge 时,可以设定存在数据后需要更新的字段,可以是字段名数组或是一个逗号分隔的字符串,若为空,则默认更新所有字段 */
matchThenUpdateFields?: string | string[]
},
/**
* 提交数据的时候可以顺便启动流程,在一个事务里
*/
flowAction?: {
/**流程应用的id,或路径*/
resId: string,
/** 提交数据时同时需要执行的操作,如开始流程(submit)、审批(approve)、否决(reject)等 */
operate: FlowOperate;
/** 审批意见 */
comment?: string;
/** 手动选择的处理节点,暂时只支持一个节点。 */
targetNode?: string;
/** 手动选择的处理人,多个用户ID用逗号分割。 */
targetUsers?: string;
},
/**
* 提交数据的时候顺便发送消息,比如一些个性化的业务流程应用(用spg实现的流程,不是用了流程引擎),需要在执行操作的时候发送一些消息。
* 消息会等事务提交成功后发送。
*/
messageAction?: {
/** 发送给哪些用户*/
targetUsers: MessageTargetUsers,
/** 消息类型 */
type: SMessageType,
/**消息的标题 */
subject: string,
/**消息正文 */
content?: string,
/**业务分类 */
labels?: string[],
/**定位信息,符合url规范 */
link?: string,
},
/**
* 提交数据的时候顺便发送队列消息,比如新建了用户,需要通知其他系统。消息会等待数据库事务提交成功后发送。
*/
mqAction?: {
/**发送队列消息的约束ID,后端通过这个ID找到对应的消息服务配置 */
constraintId: string;
/*
// 后端需要的数据结构
// 定义在系统服务中的mq服务id,通过这个id可以找到对应的消息服务
serverId: string;
// 需要发送的消息
messages: Array<{
// topic,mq需要这个东西
topic: string;
// tag,mq需要这个东西
tag: string;
// 消息体,如果是json需要stringify的结果,获取后需要进行参数溶解
body: string;
}>;
*/
},
/**
* 执行sql,可以批量执行分号分隔的多个sql,sql中可以写宏。
*/
sql?: {
/**执行sql的约束ID,后端通过这个ID找到对应的sql */
constraintId: string;
/*
// 后端需要的数据结构
// 数据源
datasource: string;
// schema
schema: string;
// 执行的sql
sql: string | string[];
*/
},
/** 分布式锁 */
lock?: {
/** 约束ID,后端通过这个ID找到对应的配置,前端传ID以外的属性不起作用 */
constraintId: string;
/** redis锁或数据库行锁*/
type?: "redis" | "db" | "default";
/** 数据库行锁相关设置 */
db?: {
/** 用作数据库行锁的模型路径 */
modelPath?: string;
/** 主键字段 */
primaryKeyField?: string;
/** 主键值,前端定义的如果是表达式,应当在后端计算好 */
primaryKeyValue?: string | number;
/** 描述字段,锁表时更新的字段 */
descField?: string;
/** 描述值,锁表时更新的字段值,表达式应当在后端计算好 */
descValue?: string | number;
/** 最后更新时间字段,每次更新设置为数据库当前时间 */
lastUpateTimeField?: string;
},
/** redis分布式锁相关设置 */
redis?: {
/** 表达式在后端计算 */
key?: string;
/** 表达式在后端计算 */
value?: string | number;
/** 超时时间,超时后自动解除锁,为0表示不解除 */
timeout?: number;
},
/** 系统表作为行锁 */
default?: {
/** 主键值,前端定义的如果是表达式,应当在后端计算好 */
primaryKeyValue?: string | number;
/** 描述值,锁表时更新的字段值,表达式应当在后端计算好 */
descValue?: string | number;
}
}
}>,
}
/**定义消息发送的目标用户的配置信息 */
export interface MessageTargetUsers {
/**
* type表示如何选择目标用户:
* 1. dataset 数据集返回的用户
* 2. users 明确指定要发送的用户
* 3. orgsAndGroup 发送给指定机构内的特定用户组的用户
* 4. all:发送给所有用户
*/
type: "dataset" | "users" | "orgsAndGroup" | "all",
/** 从这个数据集中选择用户发送消息 */
userDataset?: string,
/**表示数据集内的字段名 */
userIdField?: string,
/**用户目录,默认内部用户("sys"),可以指定"external"表示外部用户 */
userDirectiory?: string,
/**明确指定要发送消息的用户 */
users?: Array<string>,
/**要发送消息的用户所在的机构(对应部门表中的数据) */
orgIds?: Array<string>,
/**发送给机构内的哪个用户组的用户的消息,如果为空表示要发送给机构内所有用户 */
userGroup?: string,
/** 是否需要包含对应机构下的下级机构所包含用户 */
includeChildOrg?: boolean;
}
export interface SpgSubmitDataResult {
/**spg页面的id */
resId: string;
/**返回给前端的全局参数计算结果。需要设置到前端全局参数中并触发影响计算(在交互列表执行完毕后)。 */
params?: JSONObject;
/**
* spg页面那些数据集被影响了
*/
datasets: Array<ActionSubmitDataSetResult>;
}
/**
* submit过程中的sql日志
*/
export interface SpgSubmitLogsInfo {
sql: string,
/** 标识这个sql是哪个操作的 */
action: "updateData" | "importFile" | "copyDataTo",
/** TODO sql的参数值,这个内容可能比较多,先不支持吧 */
params?: Array<any>,
/** 执行时间 */
totalTime?: number
/**模型绝对路径*/
path?: string,
}
/**
* 记录spg提交过程中的一些信息
*/
export interface SpgSubmitInfo extends SubmitResultImpactInfo {
/**
* submit的数据结果
*/
spgSubmitDataResult: Array<SpgSubmitDataResult>,
/**
* submit过程中的sql日志
*/
spgSubmitLogsInfo: Array<SpgSubmitLogsInfo>,
/**
* 其他属性
*/
[property: string]: any
}
/**
* 提交数据包、导入文件数据、删除数据、复制数据都是这一个函数,可以一起执行多个操作,在一个事务执行。
*/
export function submitData(args: SpgSubmitDataArgs): Promise<SpgSubmitInfo> {
return rc({
url: "/api/dw/custom/submitData",
data: args
}).then((results: SpgSubmitInfo) => {
let events: DwDataChangeEvent[] = [];
let eventMap: { [path: string]: DwDataChangeEvent } = {};
results.spgSubmitDataResult.forEach(element => {
element.datasets.forEach(dataset => {
let path = dataset.path;
let event = eventMap[path];
if (!event) {
// dataset是后端合并好的结果,同一个模型所有dataset的信息都是一样的,直接用第一个就好
event = eventMap[path] = assign({}, dataset, { path: path, type: [], senders: [], filters: [], fields: [] });
events.push(event);
}
(<Array<DataChangeType>>event.type).distinctPushAll(dataset.type);
event.filters.pushAll(dataset.filters || []);
event.fields.distinctPushAll(dataset.fields || []);
event.senders.push({ resId: element.resId, modelId: dataset.datasetId });
dataset.selfIncrease && (event.increaseOrDecrease = true);
});
});
// 将更新了取值公式的模型加入事件通知
results.impactTables?.forEach((impact) => {
let path = impact.path;
let event = eventMap[path];
let type = DataChangeType.updatedata;
if (!event) {
event = eventMap[path] = { path: path, type: type, senders: [] };
events.push(event);
}
else if (event.type !== type) {
event.type = DataChangeType.refreshall;
}
});
getDwTableDataManager().updateCache(events, true)
// 这里只把必要的两个属性返回,其他按需增加吧
if (results.needUpdateUser) {
getCurrentUser().updateUser({ type: DataChangeType.updatedata }, true);
}
return {
spgSubmitDataResult: results.spgSubmitDataResult,
spgSubmitLogsInfo: results.spgSubmitLogsInfo
};
});
}
}
/**
* 支付 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";
}
}
}
/**
* API.spg.SpgSubmitDataArgs的别名
*/
export type SpgSubmitDataArgs = API.spg.SpgSubmitDataArgs;
# properties
# 属性栏配置
表单扩展组件的属性栏配置定义在fappComponent
下面的properties
中,用于配置组件的属性名以及属性设置方式。
属性栏配置分为三个部分:
data
,描述组件的数据。需要参与计算的属性应该放到分组下设置。style
,描述组件的样式。将组件存为一种组件风格时,会将该分组下的属性保存。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":"文件夹"
}
}