进入后端项目工程跟目录
概要
[基础]当原有控件或容器无法满足当前业务模块或者功能,自定义控件可以提供字段级别的控件自定义,自定义容器可以处理容器级别的自定义。Trantor会开放对应的 api 来支持自定义控件与容器的开发。 [模块]Trantor 自定义控件每一个工程可以划分为一个前端模块,或者附带于后端模块上,模块与模块之间互不影响。
一、准备工作(环境搭建与t-tools)
1、 切换 npm 源
npm config set registry https://registry.npm.terminus.io2、查看所有版本
npm view @terminus/t-tools versions3、全局安装 t-tools
npm i @terminus/t-tools -gOR
yarn add @terminus/t-tools -g4、初始化项目
# 进入后端项目工程跟目录t-tools init
cd frontend会出现 this 是否初始化示例。如果想初始化一个 容器,和控件。请选择 Y,否则选择 N。
? 是否初始化示例? › (Y/n)
注意:默认会再当前执行命令下创建 frontend 目录,默认打包到 frontend/dist 目录下,后续可以对此目录进行修改。
默认初始化配置
以下是默认初始化的配置:
项目名称 -> frontend
项目目录 -> frontend
打包路径 -> ./dist
开发者名 -> anonymous
自定义初始化配置 如果需要自定义以上默认配置使用
t-tools init -c# ORt-tools init --custom
cd frontend5、新建控件
注意:必须在前端项目目录(frontend)下执行以下命令
t-tools create如果是容器需要选择或填写的内容: 组件类型, 容器名称, 模型名称, 说明
如果是控件需要选择或天下的内容: 组件类型, 组件名称, 名称, 说明
6、打包组件
注意:必须在前端项目目录(frontend)下执行以下命令
t-tools build
打包命令参数
-s, --server 启动服务 (default: false) # 启动服务,可用于调试-m, --source-map 是否启用 source map (default: false) # 是否有 soure map,用户调试比较方便-w, --watch 是否启用监听 (default: false) # 是否实时监听文件变化进行打包-d, --development 模式 (default: false) # 是否是dev 模式,dev模式不对文件进行压缩, 不会实际生成文件, 文件会生成到内存中, 方便线上调试。
t-tools dev
相当于把所有 build 参数都开启, dev 是留给线上调试用的,请参照线上调试开发。不会真正生成文件,但是可以使用 URL 访问文件,如果需要生成文件,请参照 build 命令。
容器或控件列表
当开发者使用t-tools dev 或者使用 t-tools build -s会打开浏览器出现以下列表 方便开发者核对容器或者控件信息。

6、删除容器或控件
注意:必须在前端项目目录(frontend)下执行以下命令
t-tools remove
会显示容器组件列表,可多选删除
7、setting.js
const { dynamicExternals } = require("@terminus/t-tools-externals-lazy-pkgs");
module.exports = { // 打包存放目录 dist: "./dist",
// 开发配置 dev: { port: 8011, browser: true, },
// 是否是 trantor 项目 isTrantor: true,
// 依赖 externals: [ // 内部依赖 "@terminus/nusi", "@terminus/nusi-engine", "react", "classnames", "lodash", "mobx", "moment", "react-dom", "superagent", "superagent-use", ],
dynamicExternals,
// 本地扩展查找路径 false 不查找任何 node_module (禁止引入任自定义何第三方组件), 此配置如果设置为 true 则允许查找本地 node_modules 目录下文件进行打包 // 但是不要轻易使用 node_modules=true,这样会导致整个宝越来越大,除非真的需要第三方依赖。 node_modules: false,
// 打包工具 webpack | rollup tool: "webpack",};二、自定义控件目录结构
.├── manifest.json # 自动生成组件描述文件,可手动修改描述等问题,结构禁止手动修改。├── package.json # 依赖文件├── redeme.md # README 文件├── setting.js # 配置文件,有相关端口,打包目录配置├── src # 源码文件,所有需要编译的文件都应该放在此文件目录下面│ ├── assets # 资源文件 图片等资源。│ │ └── logo.svg│ ├── components # 组件文件夹│ │ ├── Menus│ │ │ ├── index.less│ │ │ └── index.tsx│ │ └── Tabs│ │ ├── input.tsx│ │ └── show.tsx│ ├── style # 样式文件,less 禁止使用 通用样式,只允许使用函数,变量。原因:会导致整体打包变大│ │ └── index.less│ └── typings.d.ts # 全局类型文件└── tsconfig.json # TS 配置文件,如果需要更多ts功能支持请配置此文件三、自定义控件写法约束
1、依赖限制
- 🔞 不要引用
externals以外的依赖@terminus/nusi@terminus/nusi-enginereactclassnameslodashmobxmomentreact-dom。 - 🔞 不要再组件内部引用
externals子文件,只允许整体引用,子文件引用会导致这个文件重复打包。 - 🔞 除了类型声明可以引用子文件夹以外,变量声明禁止引用子文件。
2、公共 less 文件限制
- 🔞 不要再功能的 less 文件内使用生成样式的语法表述,尽量只使用
VariablesMixins。
3、样式问题使用 OR 无法生效问题
- 由于自定义组件起名可能会冲突,强制了使用 css module 请参照 css module 或者使用 classnames/bind
四、自定义控件与容器的实践
自定义控件
功能内容:
当Trantor现有的控件满足不了现有的业务场景,我们可以使用自定义控件去满足当前场景。
控件分为只读控件和写入控件,一般去自定义控件的时候,我们都会去提供这两种类型的控件(看实际业务场景),如果你的业务只需要展示性控件,可以只提供只读控件,不需要写自定义写入组件, 反之 …
-
首先提供的自定义控件只读控件一定包含props的value值,写入控件类型一定包含props的onChange事件
value:当前控件的基础值,如果是可写控件可做默认值填充,只读控件就当当前值填充
onChange: onChange事件监听当前value值并向下抛出当前控件最新的值
value和onChange传值表现可直接理解为React父子组件之间传值的表现
-
props:
// 字段配置信息import { IField } from '@terminus/nusi-engine'// 显示状态(show)组件的Propsinterface IShowProps<T=any> {field: IFieldvalue: T // 基础value值, 只读控件的话会直接显示context: any}// 编辑状态(input)组件的Propsinterface IInputProps<T=any> extends IShowProps{id: stringsize: 'large' | 'small' | 'default'onChange(value: T): void_registerCallback: (validateCheck: (errs: any, values: any) => {}, itemProps: { help: string }) => {}}注
- IField 基础field类型,其中会包含一些对Field的基础描述字段,比如type, tag, isNullable, show等,详情请参考
nusi-engine/src/types/field.ts - props.context 为全量数据,在 Table 或者 TableForm 中为单行数据。
- props.context 为了处理form联动,或者字段判断渲染。
- context._setValues({[FieldName: string]: value}) 方法,可以设置表单的其他字段。
- context 是一个 readonly 不可以直接修改值
- context._setValues() 只能再 form 里面使用
- IField 基础field类型,其中会包含一些对Field的基础描述字段,比如type, tag, isNullable, show等,详情请参考
自定义容器
如果当前的Trantor容器满足当前的业务需求,我们可以自定义容器来满足当前的业务需求
- 精简的Props
interface IContainerProps { id?: string model: string // 模型 title?: string // 容器标题 fields?: IFields // fields 配置 actions?: IActions // 容器自定义操作}-
容器类型
- 布局容器 自定义布局容器无需继承,按照需求实现自定义布局
- 数据容器
-
自定义数据容器必须为class组件类型,因为要继承nusi-engine提供的继承基础容器提供的能力,比如基础的请求模型,刷新容器,初始化容器能力
-
数据容器类型
容器使用的mobx进行的状态管理,数据容器必须继承以下一个基类
- ItemContainer 继承自DataContainer,DataType为’Sinlge’ 使用场景:单条详情、单个编辑,比如Trantor当前提供的Detail,Form,Record数据容器
- ListContainer 继承自DataContainer,DataType为’List’ 使用场景:多条/多个, 比如Trantor当前提供的Table, TableForm数据容器
- TreeContainer 继承自 BaseContainer, DataType为’List’ 使用场景: 自关联模型展示, 比如Trantor当前提供的CascadeList数据容器
-
数据容器相关接口
- BaseContainer
interface BaseContainer {// 如果数据容器需要包裹高阶组件,可以实现这个方法static exportComponent?: (component: React.Component) => React.Componentdata: IDictionary | IDictionary[] // 获取模型数据config: ParseConfigs // 获取当前数据容器 DSL 配置fields: IField[] // 获取当前数据容器字段配置actions: IAction[] // 获取当前数据容器 Action 配置lookupContext?: { // 当 lookup 的数据容器和主数据容器一起提交时需要配置data: any || ()=> any, // 当前数据容器需要提交的数据,默认为 this.datavalidate?: async ()=> boolean // 随其他数据容器一起提交时的校验方法}getActionContext: (data: any, action: Action) => { // 获取执行 Action 的上下文,可覆盖context: IDictionary | IDictionary[], // 覆盖后可修改 Action 执行的上下文}}- DataContainer
interface DataContainer extends BaseContainer {extraFields: IField[] // 需要额外获取除配置之外的字段fetchDataEnd: (result: {singleResult: boolean // 是否单结果data: IDictionary | IDictionary[] // 单结果为 map ,多结果为 list<map>count?: number // 本次查询总条数,用于分页groupCounts?: { ALL: number, [key: string]: number }, // 入参传了 group 时才有,按字段值区分的记录数}) => void // 覆盖后可以对数据进行个性化处理}- ListContainer 多条数据数据容器父类
interface ListContainer extends DataContainer {}- ItemContainer 单条数据数据容器父类
interface ItemContainer extends DataContainer {}- TreeContainer 自关联数据容器父类
interface TreeContainer extends BaseContainer {getNodes: () => ITreeNode[] // 获取所有的 tree 数据reverseTree: (parentId: string) // 根据 parentId 反向构建树fetchNodes: (parentId: string, fetchCondition?: string) // parentId 和 fetchCondition 获取节点数据}
五、前端模块部署 Deploy
注意:t-tools 再 0.1.4 版本后支持,t-tools 分为两个版本,一个是自有版本,再 Trantor 0.13.0 后 将转向 t-tools 1.13.0 版本
Trantor < 0.13.0 && t-tools 大于 0.1.4 小于 0.1.6
// deploy.js 配置module.exports = { // 配置 options: { NAME: 'Trantor前端资源模块', // 模块名称 META_STORE_URL: '', // 例如: https://terminuss.io/t-project/trantor-deployment/meta-store 需要根据项目来写 MODULE_KEY: 'frontend', // 这个可以自定义前端资源key,第一次定完后,不在可以变更 VERSION: '0.0.1-SNAPSHOT', // 每次发版需要更换版本,需要走模块发布流程 },
// 发布前操作 Hook async before(){
},
// 发布后操作 Hook async after(){
}}Trantor >= 0.13.0 && t-tools 大于 1.13.0
// deploy.js 配置module.exports = { // 配置 options: { NAME: 'Trantor前端资源模块', DEPLOY_URL: '', // 例如: https://test-gateway.app.terminus.io/t-project/trantor-deployment/meta-store/${tenantKey}/${projectKey} MODULE_KEY: 'custom_frontend', // 模块名称: 自定义名称 VERSION: '0.0.2-SNAPSHOT', // 模块版本: 自定义版本 PRODUCT: { KEY: 'test', // 与 projectKey 相同 VERSION: '3' // 目前定制项目的版本是固定的, 都是 0.0.0-SNAPSHOT }, },
// 发布前操作 Hook async before(){
},
// 发布后操作 Hook async after(){
}}六、自定义 webpack配置(1.5.x以上版本支持)
使用 setting.webpackConfig 配置,值为:false | object | function
// ObjectwebpackConfig = { // 这里是 webpack 完整的配置使用 webpack-merge 与以生成的webpack配置进行合并}
// FunctionwebpackConfig = function(webpackConfig, webpackMerge, options){ // webpackConfig t-tools 生成的webpack配置 // webpackMerge webpack-merge 模块包的功能本体 // options 是setting.js 配置内容
// return 最终实际使用的webpack配置 return webpackConfig}七、本地调试
启动
cd [自定义组件项目目录]npm run dev在线调试
- DSL 中引入需要调试的数据容器和控件
- 在需要调试的页面 url 后面加上 _debug 参数, 例如: http://xxx.com?aaa=bbb&_debug
例子-在线调试
<View title="用户"> <Definition> <Containers> <!-- 引入需要调试的数据容器 --> <Container key="MyContainer" url="//127.0.0.1/MyContainer.js" /> </Containers> <Widgets> <!-- 引入需要调试的控件 --> <Widget key="MyWidget" url="//127.0.0.0.1/MyWidget.js" /> </Widgets> </Definition> <!-- 使用调试容器 --> <MyContainer /></View>控件使用方式
// 与字段同级<RenderType> <custom_InputNumberHasUnit unit="productionUnit"/></RenderType><Field renderType='custom_InputNumberHasUnit' />从 Data Store 获取数据
antor 支持从 DS 中获取数据,condition 表达式见:DataStore - 从 DS 动态获取
示例-从 Data Store 获取数据
interface IParams { targetModel: string, // 获取哪个模型的数据 singleResult?: boolean, // 是否是单条数据, 默认为false context?: { // condition 执行上下文 modelKey: string, record: IDictionary | IDictionary[], } condition?: string // 条件表达式 fields?: string[], // 输出字段, 默认为 id、mainField paging?: { // 分页信息, 默认为 { currentPage:1, pageSize: 1000 } currentPage: number pageSize: number, },}
// 例子import { fetchDSData } from '@terminus/nusi-engine'
const data = await fetchDSData({ targetModel: model, fields: ['sex', 'username'],})调用 Server Action
示例
import { triggerServerAction } from '@terminus/nusi-engine'
const result = await triggerServerAction(actionKey, data)
// data 参数结构为export interface IActionContext { modelKey?: string // 入参模型 record?: IDictionary | IDictionary[] // 模型数据 env?: IDictionary // 额外数据 process?: string // 结果处理 Action}获取附件的 OSS 访问地址
import { getOssAccessUrl } from '@terminus/nusi-engine';const url = await getOssAccessUrl('//ali-oss.com/xxx.jpg');其他
- 默认生成的是模板性质的代码,作为初始化后的初期约束
- 当不能满足调试需求时可以手动进行修改
- 按照各个文件的注释信息进行操作
- 文档参考 编写自定义数据容器与控件(新)