跳转到内容

视图 controller 语法


Controller 可以让前端自定义写一些代码逻辑,配合视图 xml 使用。Controller 使用 ts/es6 编写。

Controller 实例在视图渲染后会作为 DSL 表达式的上下文使用。例如 attr="#{getContainerByKey('table').data.id}",实质上是调用了 controller 实例中的 this.getContainerByKey('table').data.id

注意:在用 getContainerByKey 时确保数据容器上没有写 id 属性,id 会覆盖当前数据容器的 key。

可依赖的方法

// nusi-sdk:
import {
state, // 对应mobx的observable
action, // 对应mobx的action
computed, // 对应mobx的computed
_, // 对应 lodash
Controller, // Controller 基类
utils, // 工具方法
showMessage, // 显示自定义消息
PubSub, // 订阅发布
} from "nusi-sdk";
属性说明
state、action、computedmobx 的状态管理,详情可看 mobx 文档和下方 demo
_lodash 对象,可以用 lodash 里面的任何方法: 比如 _.includes([1, 2, 3], 1);
Controller必须继承它
utilsutils
showMessagefunction showMessage(message: IMessage): Promise;
PubSub发布订阅实例

utils

方法类型说明
openUrl(url: string, isBlank: boolean = true) => void isBlank 默认值 为 true浏览器中打开连接,url 可以是外部链接,可以是应用内链接, isBlank 指定是不是另开 tab 打开
getModuleApiUrl(moduleKey: string) => string根据模块 key 获取后端服务对应请求服务链接
confirm(context: string | IConfirmOptions) => void打开确认框,确认框内容是 context
openGlobalLoading() => void开启全局加载效果,开启后需要用 closeGlobalLoading 关闭
closeGlobalLoading() => void关闭全局加载效果,与 openGlobalLoading 配合使用

IConfirmOptions

属性名类型描述
titlestring不必须,默认为‘警告’
contentstring必须,确认框内容
okTextstring确认按钮文字,不必须,默认为‘确认’
cancelTextstring取消按钮文字,不必须,默认为‘取消’
import { utils } from "@terminus/nusi-sdk";
// 默认提示
utils.confirm("这是一个确认").then((result: boolean) => {
// 可以做一些调用接口的动作
if (result) {
// 这里是点击确定按钮后的回调
} else {
// 这里是点击取消按钮后的回调
}
});
// 自定义提示属性
utils
.confirm({
title: "确认标题",
content: "确认内容",
okText: "确定按钮的文案",
cancelText: "取消按钮的文案",
})
.then((result: boolean) => {
// 可以做一些调用接口的动作
if (result) {
// 这里是点击确定按钮后的回调
} else {
// 这里是点击取消按钮后的回调
}
});

IMessage

属性名类型描述
level”Weak” | “Strong”不必须,默认为 Weak, Weak 用 Toast 显示,会自动消失,Strong 用信息提示框显示,需要点确定
messagestring信息提示内容
typeSuccess | Info | Warning | Error 不必须,type 指定了 Strong level 时信息提示类型的。表现在信息提示内容前的图标不一样

PubSub

发布订阅实例

方法参数描述
subscribe(topic: string, listener: (data) => void, key?: string, followLast = false) => void订阅
publish(topic: string, data?: any, key?: string) => void发布

Controller 类实现了的方法和属性如下

controller 中的方法都注入到了 dsl 当做上下文,所以在 dsl 也可以直接使用

方法

方法参数备注说明
getContainerByKeygetContainerByKey(containerKey), containerKey: dsl 中定义在数据容器上的 keygetContainerByKey 返回值获取数据容器上相关数据和方法
goBackgoBack()-回到上个页面
openView(viewKey: string, params?: IOpenViewParams) => void;-跳转 视图
triggerLogicFlow(flowKey:string, data?: any[]: actionLabel?: string)actionLabel 会被操作日志记录为动作调用 logic flow
triggerLogicFunction(functionKey:string, data?: any[]: actionLabel?: string)actionLabel 会被操作日志记录为动作调用 logic function
triggerLogicController(functionKey:string, data?: any[]: actionLabel?: string)actionLabel 会被操作日志记录为动作调用 logic controller
triggerBatchActiontriggerBatchAction(batchParams: IBatchParams, actionLabel?: string)actionLabel 会被操作日志记录为动作调用批量接口
triggerBizFlowFunctriggerBizFlowFunc(bizFlowFuncKey: string, params: IBizFlowParams)-调用 审业务流接口
getDataSource(dataStore: ‘DataStore’, dataStoreParams: IDataStoreParams)-从 data-store/logic function/ logic flow 获取数据

getContainerByKey 返回参数

{
data: IDictionary[] | IDictionary; // 数据容器对应的数据
searchData: IDictionary // 在Table中的筛选数据
selectedData: IDictionary[] // 被选中的容器数据
clearData: () => void; // 清除数据容器数据
refresh: (id?: string | null, level?: number) => void; // 刷新数据容器数据, 树容器可指定刷新id(获得其子节点,指定null则刷新顶层),以及向上刷新的层数level
refreshParent: (id: string) => void; // 仅用于树容器,刷新指定id的父节点children列表
updateData?: (data: IDictionary | IDictionary[], index?: number) => void; // 更新数据容器数据,Table/TableFrom支持更新指定的某一行数据
updateSelectedData?: (data: IDictionary[]) => void
validateData?: ((option?: IValidateFuncOption) => void) // 推荐用法,IValidateFuncOption 参考下文
| ((fields?: string[], index?: number) => void) // 旧用法,建议使用推荐用法
instance?: ReactNode // 容器的实例(注意必须容器已经show且为Class类型才会有instance)
}
export type IValidateFuncOption = {
fields?: string[] // 适用于tableform,指定校验字段名称
rowIndex?: number // 适用于tableform,指定校验某一行
autoScroll?: boolean // 适用于form,校验后自动滚动到错误字段,默认为true
}

openView 方法入参

type openView = (
viewKey: string,
params: {
openViewType?: "Self" | "Dialog" | "Drawer" | "Columns";
openViewSize?: "s" | "m" | "l";
payloadCallback?: (ctx: IActionContext) => void;
record: IDictionary | IDictionary[];
pageState?: IDictionary;
env?: IDictionary;
slotKey?: string;
containerId?: string;
}
) => void;

IDataStoreParams 参数

interface IDataStoreParams extends IDataSourceBaseParams {
singleResult?: boolean; // 期望返回值是否为单个record,默认为false
search?: IDictionary;
searchValue?: IDictionary<ISearchValue>;
condition?: {
expression: string;
params?: any[];
};
queryParams?: ILoadDataQuery;
groupCountField?: string;
}

IBatchParams 参数

interface IBatchParams {
query?: IBatchActionParams;
flowKey?: string;
funcKey?: string;
record?: IDictionary;
idCondition?: IQueryValue;
}
interface IBatchActionParams extends IDataSourceBaseParams {
params?: IDictionary;
searchValue?: IDictionary<ISearchValue>;
queryParams?: ILoadDataQuery;
}
interface IQueryValue {
type: "One" | "Collection" | "Range";
value?: any; // type == One
values?: any[]; // type == Collection
rangeValue?: { startValue?: any; endValue?: any };
fullMatch?: boolean; // 是否精确匹配
}
interface ILoadDataQuery {
postFlow?: string;
postFunc?: string;
groupCountPostFLow?: string;
groupCountPostFunc?: string;
}

IBizFlowParams 参数

interface IBizFlowParams {
flowKey: string;
actKey: string;
label?: string;
data: IData;
moduleKey: string;
}

IOpenViewParams 参数

| 参数 | 类型 | 说明 | 默认值 | | --------------- | ------------------------------------------- | --------------------------------------------------------------- | ---------------------------------------- | --- | | record | IDictionary | IDictionary[] | 传递给下个视图,视图上用 pageRecord 去取 | {} | | env | any | 传递给下个视图,视图上用 env 去取 | {} | | openViewType | “Self” | “Dialog” | “Drawer” | “Columns” | 打开视图的类型 | “Self” | | openViewSize | “s” | “m” | “l” | number | 打开的视图大小 | “m” | | slotKey | string | 将视图在某个插槽内打开 | null | | payloadCallback | (context: any) => void | 配合 GoBackWithContext 使用,可以获取到在视图中勾选或提交的数据 | null | | onColumnClose | () => void | 关闭分栏视图的回调 | null | | onClose | (params?: IGoBackParams) => void | 关闭弹窗视图的回调 | null | | onMaskClose | () => void | 关闭抽屉视图的回调 | null | | isMaskClosable | boolean | 打开抽屉时是否有遮罩 | true |

getDataSource 方法入参

// getDataSource 接收两个参数,数据源类型 和 详细参数
// 由于使用了typescript的重载机制,需要在调用时携带调用者。
// 例如 this.getDataSource(),不支持单独使用 getDataSource()
type getDataSource = (
dataSourceType: "DataStore" | "LogicFunc" | "LogicFLow",
params: IDataStoreParams | ILogicDataParams
) => Promise<IDataSourceResult>;
// function、flow 接口参数
export interface ILogicDataParams extends IDataSourceBaseParams {
modelKey: string;
fields: string[]; // #
page?: {
no: number;
size: number;
};
order?: {
asc: boolean;
field: string;
};
fuzzyValue?: string; // 搜索框使用的模糊搜索字段
dataSourceKey: string;
params?: IDictionary; // 需要传递给后端的数据
}
// dataStore 接口参数
export interface IDataStoreParams extends IDataSourceBaseParams {
modelKey: string;
fields: string[]; // #
page?: {
no: number;
size: number;
};
order?: {
asc: boolean;
field: string;
};
fuzzyValue?: string; // 搜索框使用的模糊搜索字段
singleResult?: boolean; // 期望返回值是否为单个record,默认为false
search?: IDictionary; // 需要要检索的字段和值
searchValue?: IDictionary<ISearchValue>; // 和 search 二选一,用于查询关联模型字段及完全匹配搜索
condition?: {
expression: string; // condition字符串,例如: 'id = ? and status = ?'
params?: any[]; // condition的补充参数,数组,例如: [2, 'pending']
};
}
export interface ISearchValue {
value: any;
range?: boolean;
fullMatch?: boolean;
}

triggerLogicFlow / triggerLogicFunction / triggerLogicController 方法入参

type triggerLogicFlow = (logicFlowKey: string, data: any) => Promise<any>;
type triggerLogicFunction = (
logicFunctionKey: string,
data: any
) => Promise<any>;
type triggerLogicController = (
logicControllerKey: string,
data: any
) => Promise<any>;

属性

名称描述
pageRecord页面携带的 record 数据,若不携带 record 默认会返回空对象(016 版本开始不需要通过pageContext.record访问)
env页面携带的 env 参数
i18ni18n 相关 i18n.get(‘警告’).d(‘警告’) 带参数 i18n.get(‘fn_编辑模型’, { model: modelInfo.name })

Controller 生命周期

名称说明
pageDidLoad页面挂载时的回调,不要在此执行依赖 xml 中容器数据加载的行为,同 react componentDidMount
pageDidUpdate会在更新后会被立即调用。首次渲染不会执行此方法。同react componentDidUpdate
pageUnLoad会在页面卸载及销毁之前直接调用, 同react componentWillUnmount
onPageLeave页面在离开时的回调,若返回字符串,则会拦截离开操作并要求用户确认。返回的字符串为提醒文本,注意浏览器在关闭页面场景中提醒文本为默认,不可自定义。

示例

import { state, action, _, computed, Controller } from "nusi-sdk";
export default class extends Controller {
@state
data = [];
@computed get value() {
return _.filter(this.data, ({ show }) => show);
}
@action
onChange() {}
}

典型场景

调用 api 来获取&提交数据

import { Controller, showMessage } from "nusi-sdk";
export default class extends Controller {
someAction = () => {
this.triggerLogicController("{业务域}/{ControllerName}/{MethodName}", [])
.then((res) => {
// dosomething
})
.catch((res) => {
// 异常消息提示
showMessage({
type: "Error",
message: res.message,
});
});
this.triggerLogicFunction("funcKey", [])
.then((res) => {
// dosomething
})
.catch((res) => {
// 异常消息提示
showMessage({
type: "Error",
message: res.message,
});
});
this.triggerLogicFlow("flowKey", [])
.then((res) => {
// dosomething
})
.catch((res) => {
// 异常消息提示
showMessage({
type: "Error",
message: res.message,
});
});
};
}

获取其他数据容器数据

<View title="用户详情">
<Detail key="user" model="user2_User">
<Fields>
<Field name="id"/>
<Field name="name"/>
<Field name="locked" />
</Fields>
</Detail>
<Table model="user2_Address" dataCondition="userId = ?" dataParams="[#user.id]">
<Fields>
<Field name="isHome" show="#{getContainerByKey('user').data.locked}" />
<Field name="city" />
</Fields>
</Table>
</View>

表单联动

<View title="用户地址编辑">
<Form key="user" model="user2_User" onFieldValueChange="#{userFieldChange}">
<Fields>
<Field name="sex" />
<Field name="name" />
<Field name="age" />
<Field name="username" />
</Fields>
<Actions>
<Action layout="Header" action="user2_User_update" label="更新" />
</Actions>
</Form>
</View>
import { Controller } from "nusi-sdk";
export default class extends Controller {
// 当字段值发生变化时触发以下方法
userFieldChange = (fieldName: string, value: string) => {
if (fieldName === "name") {
// 判断当前哪个字段的值发生变化
const user = this.getContainerByKey("user"); // 根据 key 获取数据容器
user.updateData({
// 更新数据容器数据
username: value,
});
}
};
}

触发 Action

<View title="用户地址编辑">
<Form key="user" model="user2_User">
<Fields>
<Field name="sex" />
<Field name="name" />
<Field name="age" />
<Field name="username" />
</Fields>
<Actions>
<Action layout="Header" action="#{doAction}" label="更新" />
</Actions>
</Form>
</View>
import { Controller, utils } from "nusi-sdk";
export default class extends Controller {
doAction = ({ record }) => {
// 打开新的视图, 并传递 record 和 env
this.openView('viewKey', {
record,
env: { status: 'success' }
openViewType: 'Dialog'
})
};
}

TableForm 联动

<View title="用户详情编辑">
<Form model="user2_User" key="test">
<Fields>
<Field name="name"/>
<Field name="locked" />
<Field name="age" />
<Field name="code" />
<Field name="username" />
</Fields>
<Actions>
<Action type="Submit" label="新增" show="#{!this.data.id}" logicFunction="user2_User_create" after="GoBack" layout="Footer"/>
<Action type="Submit" label="修改" show="#{!!this.data.id}" logicFunction="user2_User_update" after="GoBack" layout="Footer"/>
</Actions>
</Form>
<TableForm key="transFee" model="custom_TransFee" onFieldValueChange="#{tableFormFieldChange}" lookupFrom="test.transFee">
<Fields>
<Field name="bus" />
<Field name="airplain" />
<Field name="total" readonly="true" />
</Fields>
</TableForm>
</View>
import { Controller } from "nusi-sdk";
export default class extends Controller {
// 当字段值发生变化时触发以下方法
calcTotalFee = (rowData: any = {}) => {
const { bus = 0, airplain = 0 } = rowData;
return bus + airplain;
};
tableFormFieldChange = (value: string, fieldName: string, index: number) => {
const user = this.getContainerByKey("transFee"); // 根据 key 获取数据容器
const data = [...user.data]; // 获取这个数据容器的data
const rowData = { ...data[index], total: this.calcTotalFee(data[index]) };
data.splice(index, 1, rowData);
user.updateData(data);
};
}

动态校验根据(不同情况执行不同的校验规则)

controller 里提供了 validateData(fields, index?)  函数用于校验指定字段,该函数接收 1-2 个参数: fields、index , fields  是个数组([‘field1’, ‘field2’, …])表示要去校验的相应字段,必填;只有在 tableForm  里 index  才是必须,指定当前行。

<TableForm key="qualificationLicense" onFieldValueChange="#{tableFormFieldChange}">
<Fields>
<Field label="到期日期" name="expirationTime">
<Validations>
<Validation required="#{!this.record.alwaysValid || this.record.alwaysValid == 'false'}" message="到期日期不能为空" />
</Validations>
</Field>
<Field name="alwaysValid" label="是否长期有效"/>
</Fields>
</TableForm>

expirationTime 的必填与否,取决于字段 alwaysValid 的值,所以当 alwaysValid 发生 change 的时候,要去重新校验 expirationTime

import { Controller } from "nusi-sdk";
export default class extends Controller {
tableFormFieldChange = (value: string, fieldName: string, index: number) => {
const license = this.getContainerByKey("qualificationLicense"); // 根据 key 获取数据容器
if (fieldName === "alwaysValid") {
setTimeout(() => {
license.validateData(["expirationTime"], index);
}, 0);
}
};
}

PubSub(页面通信)

如果遇到多个页面需要通信的场景可以通过 PubSub 来解决

import { Controller, PubSub } from "nusi-sdk";
// Page A
export default class extends Controller {
constructor() {
super();
this.listener = PubSub.subscribe("moduleKey/yourTopic", (data) => {
// do something
});
}
pageUnLoad = () => {
this.listener.remove();
};
}
// Page B
export default class extends Controller {
onSave = (data) => {
PubSub.publish("moduleKey/yourTopic", data);
};
}
// 自定义数据容器、控件
import { PubSub } from "@terminus/nusi-engine";