视图 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、computed | mobx 的状态管理,详情可看 mobx 文档和下方 demo |
| _ | lodash 对象,可以用 lodash 里面的任何方法: 比如 _.includes([1, 2, 3], 1); |
| Controller | 必须继承它 |
| utils | utils |
| showMessage | function 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
| 属性名 | 类型 | 描述 |
|---|---|---|
| title | string | 不必须,默认为‘警告’ |
| content | string | 必须,确认框内容 |
| okText | string | 确认按钮文字,不必须,默认为‘确认’ |
| cancelText | string | 取消按钮文字,不必须,默认为‘取消’ |
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 用信息提示框显示,需要点确定 |
| message | string | 信息提示内容 |
| type | Success | 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 也可以直接使用
方法
| 方法 | 参数 | 备注 | 说明 |
|---|---|---|---|
| getContainerByKey | getContainerByKey(containerKey), containerKey: dsl 中定义在数据容器上的 key | getContainerByKey 返回值 | 获取数据容器上相关数据和方法 |
| goBack | goBack() | - | 回到上个页面 |
| 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 |
| triggerBatchAction | triggerBatchAction(batchParams: IBatchParams, actionLabel?: string) | actionLabel 会被操作日志记录为动作 | 调用批量接口 |
| triggerBizFlowFunc | triggerBizFlowFunc(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 参数 |
| i18n | i18n 相关 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 Aexport default class extends Controller { constructor() { super(); this.listener = PubSub.subscribe("moduleKey/yourTopic", (data) => { // do something }); } pageUnLoad = () => { this.listener.remove(); };}
// Page Bexport default class extends Controller { onSave = (data) => { PubSub.publish("moduleKey/yourTopic", data); };}// 自定义数据容器、控件import { PubSub } from "@terminus/nusi-engine";