umi代码生成插件的探究

简单探索

首先,在官网上看到了官方写的一个生成器
官网介绍

再去源码里扒一扒 找到关键所在
源码

简而言之,就是利用插件的api注册了一个生成model的指令,生成器指向目录里的model.js

代码如下

import { join } from 'path';
import assert from 'assert';

export default api => {
  const { paths, config } = api;
  const absTemplatePath = join(__dirname, '../template/generators');

  return class Generator extends api.Generator {
    writing() {
       ...     
      const name = this.args[0].toString();
      const models='model'

      ...
     // 将模板目录下里的model代码 拷贝到项目的model目录下 并命名为指令输入的文件名
      this.fs.copyTpl(
        join(absTemplatePath, 'model.js'),
        join(paths.absSrcPath, models, `${name}.js`),
        {
          name,
        },
      );
    }
  };
};

这是一个简单的生成规则


../template/generators/model.js
export default {
  state: '<%= name %>',
  subscriptions: {
    setup({ dispatch, history }) {
    },
  },
  reducers: {
    update(state) {
      return `${state}_<%= name %>`;
    },
  },
  effects: {
    *fetch({ type, payload }, { put, call, select }) {
    },
  },
}

用过umi,dva的,应该知道,这是一个常规的dvamodel

里面的<%= name %>是什么呢 这个放到后面说

深入原理

一些疑问

  • umi g的指令如何注册

  • umi api提供的generator是基于第三方的生成器还是umi自带

  • generator如何注册到umi中去

  • fs 又是用的是什么插件还是umi自带 如何运作的

弄懂这些 我们才能知道这个generator是否能脱离umi独立运行

用户输入umi g

我们沿着用户输入指令发生了什么 依次来说

umi是umi的cli g的命令肯定是在某个地方注册了

开始上代码

代码入口:umi-build-dev/src/plugin/commnds

 function registerCommand(command, description) {
    const details = `
Examples:

  ${chalk.gray('# generate page users')}
  umi generate page users

  ${chalk.gray('# g is the alias for generate')}
  umi g page index

  ${chalk.gray('# generate page with less file')}
  umi g page index --less
  `.trim();
    api.registerCommand(
      command,
      {
        description,
        usage: `umi ${command} type name [options]`,
        details,
      },
      generate,
    );
  }

  registerCommand('g', 'generate code snippets quickly (alias for generate)');
  registerCommand('generate', 'generate code snippets quickly');

api.registerCommand是umi插件提供的注册命令方法,这里不做具体介绍,详情见umi-build-dev下的Service

值得关注的是generate方法

 const {
    service: { generators },
    log,
  } = api;
 function generate(args = {}) {
    try {
      const name = args._[0];
      assert(name, `run ${chalk.cyan.underline('umi help generate')} to checkout the usage`);
      assert(generators[name], `Generator ${chalk.cyan.underline(name)} not found`);
      const { Generator, resolved } = generators[name];
      const generator = new Generator(args._.slice(1), {
        ...args,
        env: {
          cwd: api.cwd,
        },
        resolved: resolved || __dirname,
      });
      return generator
        .run()
        .then(() => {
          log.success('');
        })
        .catch(e => {
          log.error(e);
        });
    } catch (e) {
      log.error(`Generate failed, ${e.message}`);
      console.log(e);
    }
  }
  

这里的逻辑 根据用户在g后面输入的name去找这个Genenrator类 找到了就实例化 运行run方法,走一个Promise 可以看出generator取service

我们接着来看Service里的genrator是啥

// Service.js
export default class Service {
constructor({ cwd }) {
 
  ....
  this.generators = {};
  ....
}

只是里面的一个属性 Service里找遍 没发现对generators的操作,但是在PlugnAPI里找到了

// PluginAPI.js
import BasicGenerator from './BasicGenerator';
export default class PluginAPI {
  constructor(id, service) {
 .....................
    this.Generator = BasicGenerator;
  }
  registerGenerator(name, opts) {
    const { generators } = this.service;
    assert(typeof name === 'string', `name should be supplied with a string, but got ${name}`);
    assert(opts && opts.Generator, `opts.Generator should be supplied`);
    assert(!(name in generators), `Generator ${name} exists, please select another one.`);
    generators[name] = opts;
  }
..............

这段代码解释了两个问题

1.我们调用的api.registerGenerator是什么

2.api.Generator是什么

很明显,我们调的api.registerGenerator是将name和Option存入service里的genrator对象上 以name为key Option为value

顺便一提 PluginAPI是对Service属性和方法的代理,通过他写入,我觉得是为了良好封装,保证Service的私有性

第二个问题 我们继续看BasicGenerator

 // BasicGenerator.js
import Generator from 'yeoman-generator';
const { existsSync } = require('fs');
const { join } = require('path');

class BasicGenerator extends Generator {
  constructor(args, opts) {
    super(args, opts);
    this.isTypeScript = existsSync(join(opts.env.cwd, 'tsconfig.json'));
  }
}

export default BasicGenerator;

api.Generator的真面目原来是yeoman-generator

yeoman-generator是什么,网上冲浪了一下

什么是 yeoman yeoman 是一个可以帮助我们生成任何类型 app 的脚手架工具,这一功能依赖于 yeoman 的各种 generators。每一个 generator 都是 yeoman 封装好的一个个 npm package,我们可以引用已经发布的 generators ,也可以自定义一个 generator。

我们可以简单看一下之前执行的run方法

// yeoman-generator
run(cb) {
    const promise = new Promise((resolve, reject) => {
      const self = this;
      this._running = true;
      this.emit('run');

      const methods = Object.getOwnPropertyNames(Object.getPrototypeOf(this));
      const validMethods = methods.filter(methodIsValid);
      assert(
        validMethods.length,
        'This Generator is empty. Add at least one method for it to run.'
      );

      this.env.runLoop.once('end', () => {
        this.emit('end');
        resolve();
      });

      // Ensure a prototype method is a candidate run by default
      function methodIsValid(name) {
        return name.charAt(0) !== '_' && name !== 'constructor';
      }

      function addMethod(method, methodName, queueName) {
        queueName = queueName || 'default';
        debug(`Queueing ${methodName} in ${queueName}`);
        self.env.runLoop.add(queueName, completed => {
          debug(`Running ${methodName}`);
          self.emit(`method:${methodName}`);

          runAsync(function() {
            self.async = () => this.async();
            return method.apply(self, self.args);
          })()
            .then(completed)
            .catch(err => {
              debug(`An error occured while running ${methodName}`, err);

              // Ensure we emit the error event outside the promise context so it won't be
              // swallowed when there's no listeners.
              setImmediate(() => {
                self.emit('error', err);
                reject(err);
              });
            });
        });
      }

      function addInQueue(name) {
        const item = Object.getPrototypeOf(self)[name];
        const queueName = self.env.runLoop.queueNames.indexOf(name) === -1 ? null : name;

        // Name points to a function; run it!
        if (typeof item === 'function') {
          return addMethod(item, name, queueName);
        }

        // Not a queue hash; stop
        if (!queueName) {
          return;
        }

        // Run each queue items
        _.each(item, (method, methodName) => {
          if (!_.isFunction(method) || !methodIsValid(methodName)) {
            return;
          }

          addMethod(method, methodName, queueName);
        });
      }

      validMethods.forEach(addInQueue);
    ......
    });

    // Maintain backward compatibility with the callback function
    if (_.isFunction(cb)) {
      promise.then(cb, cb);
    }

    return promise;
  }

简单的说,这里把Genertator里的方法先进行有效性过滤,加入一个队列 然后一个个去执行 返回一个promise供外部调用

但是我们却没有看到this.fs的身影

这时候看看构造函数,有惊喜

// yeoman-generator
const FileEditor = require('mem-fs-editor');
class Generator extends EventEmitter {
constructor(args, options) {
  super();
  ..........
  this.fs = FileEditor.create(this.env.sharedFs);
}

我们再追根溯源到mem-fs-editor

// mem-fs-editor
'use strict';

function EditionInterface(store) {
  this.store = store;
}

.....
EditionInterface.prototype.copyTpl = require('./actions/copy-tpl.js');
....

exports.create = function (store) {
  return new EditionInterface(store);
};

一堆方法,copyTpl赫然在其中

我们用到的copyTpl方法

'use strict';

var extend = require('deep-extend');
var ejs = require('ejs');
var isBinaryFileSync = require('isbinaryfile').isBinaryFileSync;

function render(contents, filename, context, tplSettings) {
  let result;

  const contentsBuffer = Buffer.from(contents, 'binary');
  if (isBinaryFileSync(contentsBuffer, contentsBuffer.length)) {
    result = contentsBuffer;
  } else {
    result = ejs.render(
      contents.toString(),
      context,
      // Setting filename by default allow including partials.
      extend({filename: filename}, tplSettings)
    );
  }

  return result;
}

module.exports = function (from, to, context, tplSettings, options) {
  context = context || {};
  tplSettings = tplSettings || {};

  this.copy(
    from,
    to,
    extend(options || {}, {
      process: function (contents, filename) {
        return render(contents, filename, context, tplSettings);
      }
    }),
    context,
    tplSettings
  );
};

给ejs模板传参就在这啦

最后顺便一提,umi插件中经常用到的api其实就是在service中用proxy属性代理了一下pluginAPI生成的 在初始化插件件方法 initPlugin

this是service对象
  const api = new Proxy(new PluginAPI(id, this), {
        get: (target, prop) => {
          if (this.pluginMethods[prop]) {
            return this.pluginMethods[prop];
          }
          if (
            [
              // methods
             ...
            ].includes(prop)
          ) {
            if (typeof this[prop] === 'function') {
              return this[prop].bind(this);
            } else {
              return this[prop];
            }
          } else {
            return target[prop];
          }
        },
      });
  • PluginAPI其实就是Service的代理 Service将自身接口提供给PluginAPI 由它来进行对Service属性方法进行写入,保证了Service的内部私有性

  • 使用Proxy进行对象取值拦截,在其中进行取值优先级划分

归纳了一个关系图

xmind

改造

注册自己的指令

myRule

这里我将存放规则的路径指到项目目录下,之前umi-plugin-dva的处理是打在npm包里,我这样做 就是为了可以随着项目的进行,频繁改动规则和模板

自定义一个生成规则

import { join } from 'path';
const fs=require('fs');
export default api => {
  const  {paths} = api;
  const configPath=join(__dirname,'./template/generatorConfig','index.js');
  const absTemplatePath = join(__dirname, '../template/generators');
  const genConfig=require(configPath);
  return class Generator extends api.Generator {
    // 判断第二个参数
    isType(type){
      const _type =this.args[1]&& tdhis.args[1].toString();
      return  _type===type
    }
    // 生成DTO
    generateBean(){
      const name = this.args[0].toString();
      if(!this.isType('bean')){
        return
      }
      this.fs.copyTpl(join(absTemplatePath, 'bean.ts'), join(paths.absSrcPath, `beans/${name}`, `${name}DTO.ts`), {
        name,
        columns:genConfig[name]['columns']
      });
    }
    // 生成列表
    generateList(){
      const name = this.args[0].toString();
      if(!this.isType('list')){
        return
      }
      if(!fs.existsSync(configPath)) {
        api.log.error('新建列表模板缺少generatorConfig.js')
        return
      }

      this.fs.copyTpl(join(absTemplatePath, 'list.tsx'),join(paths.absSrcPath, `pages/${name}/list`, `index.tsx`), {
        name,
        queryFormItems:genConfig[name]['queryFormItems'],
        columns: JSON.stringify(genConfig[name]['columns'])
      });
    }
    generateForm(){
      if(!this.isType('form')){
        return
      }
      const name = this.args[0].toString();
      this.fs.copyTpl(join(absTemplatePath, 'save.tsx'),join(paths.absSrcPath, `pages/${name}/form`, `index.tsx`), {
        name,
        formItems:genConfig[name]['formItems']
      });
    }
    generateDetail(name){

    }
    // 生成请求类
    generateRequest(){
      if(!this.isType('request')){
        return
      }
      const name = this.args[0].toString();
      this.fs.copyTpl(join(absTemplatePath, 'request.ts'), join(paths.absSrcPath, `requests/${name}`, `${name}Request.ts`), {
        name
      });
      this.fs.copyTpl(join(absTemplatePath, 'response.ts'), join(paths.absSrcPath, `requests/${name}`, `${name}Response.ts`), {
        name
      });
    }
    // model和样式是默认生成的
    generateCommonModule(){
      // 如果已经存在model.ts index.less 默认不再次生成 防止覆盖

      const name = this.args[0].toString();
      this.fs.copyTpl(join(absTemplatePath, 'model.ts'), join(paths.absSrcPath, `pages/${name}`, `model.ts`), {
        name
      });
      this.fs.copyTpl(join(absTemplatePath, 'index.less'), join(paths.absSrcPath, `pages/${name}`, `index.less`), {
        name
      });
    }
  };
};

自定义配置


const config = {
  Project: {
    formItems: `[[  
      {name:'项目名称',id:'projectName',},    
      {name:'项目类型',id:'type',Comp:<Select></Select>},
      {name:'项目属性',id:'property',Comp:<Select></Select>},
      {name:'是否公司项目',id:'property',Comp:<Select></Select>},
      {name:'项目所在地',id:'place',Comp:<Select></Select>},
      {name:'预期规模保费(单位:元)',id:'fee'},
      {name:'预期经纪费收入(单位:元)',id:'jingjifee'},
      {name:'预期费用支出(单位:元)',id:'input'},
      {name:'预期收益(单位:元)',id:'finance'},
      {name:'预计承保日期',id:'chengbDate',Comp:<DatePicker />},
      {name:'项目实施周期',id:'term',Comp:<DatePicker />},
      {name:'所属机构名称',id:'depName',Comp:<Select></Select>},
      {name:'是否直接立项审核通过',id:'isPass',Comp:<Select></Select>},
      {name:'项目立项日期',id:'lixDate',Comp:<DatePicker />},
      {name:'牵头部门',id:'dep',Comp:<Select></Select>},
      {name:'委托授权到期提醒',id:'weit',Comp:<Select></Select>},
      {name:'保单到期提醒',id:'exp',Comp:<Select></Select>},
      {name:'项目险种说明',id:'con',Comp:<Select></Select>},
      {name:'项目描述',id:'desc',Comp:<Input.TextArea/>}
      
    ]]`,
    columns: [
      {
        title: '项目编码',
        dataIndex: 'code',
        key: 'code',
      }, {
        title: '项目名称',
        dataIndex: 'projectName',
        key: 'projectName',
      }, {
        title: '项目类型',
        dataIndex: 'type',
        key: 'type',

      }, {
        title: '所属机构编码',
        dataIndex: 'ownerOrgCode',
        key: 'ownerOrgCode',

      }, {
        title: '所属机构名称',
        dataIndex: 'ownerOrgName',
        key: 'ownerOrgName',
      },
      {
        title: '项目所在地',
        dataIndex: 'place',
        key: 'place',
      },
      {
        title: '项目属性',
        dataIndex: 'property',
        key: 'property',

      },
      {
        title: '项目流程',
        dataIndex: 'process',
        key: 'process',

      }, {
        title: '项目状态',
        dataIndex: 'status',
        key: 'status',

      },
    ],
    queryFormItems: `[
      {name:'项目编码',id:'code',},
      {name:'项目名称',id:'projectName',},
      {name:'客户ID',id:'customerId',},
      {name:'客户名称',id:'customerName'},
      {name:'项目类型',id:'type',Comp:<Select></Select>},
      {name:'项目属性',id:'property',Comp:<Select></Select>},
      {name:'项目状态',id:'status',Comp:<Select></Select>},
      {name:'项目所在地',id:'place',Comp:<Select></Select>},
      {name:'所属机构编码',id:'ownerOrgCode',Comp:<Select></Select>},
      {name:'所属机构名称',id:'ownerOrgName'},
      {name:'项目流程',id:'process',Comp:<Select></Select>}
    ]`,
  },


自定义模板-实体


// bean
export default class <%= name %>DTO {

  <% columns.forEach(function(bean){ %>
    // <%- bean.title %>
    public <%- bean.key %> :string
    <% }); %>

}

生成效果

// ProjectDTO
export default class ProjectDTO {

 
   // 项目编码
   public code :string
   
   // 项目名称
   public projectName :string
   
   // 项目类型
   public type :string
   
   // 所属机构编码
   public ownerOrgCode :string
   
   // 所属机构名称
   public ownerOrgName :string
   
   // 项目所在地
   public place :string
   
   // 项目属性
   public property :string
   
   // 项目流程
   public process :string
   
   // 项目状态
   public status :string
   

}

自定义模板-请求

// request

import request from '@/utils/request'
import { requestWrapper } from '@/requests/common/BaseRequest'
import BaseResponse from '@/requests/common/BaseResponse'

export default class <%= name %>Request {


  public static async submit (params: <%= name %>Request): Promise<BaseResponse> {
    const req = request.request({
      url: '/submit',
      method: 'post',
      data: params,
    })
    return requestWrapper(BaseResponse, req)
  }


public static async remove (params: <%= name %>Request): Promise<BaseResponse> {
    const req = request.request({
      url: '/remove',
      method: 'post',
      data: params,
    })
    return requestWrapper(BaseResponse, req)
  }

public static async getDetail (params: <%= name %>Request): Promise<BaseResponse> {
    const req = request.request({
      url: '/getDetail',
      method: 'get',
      data: params,
    })
    return requestWrapper(BaseResponse, req)
  }

}

效果

// request
import request from '@/utils/request'
import { requestWrapper } from '@/requests/common/BaseRequest'
import BaseResponse from '@/requests/common/BaseResponse'

export default class ProjectRequest {


  public static async submit (params: ProjectRequest): Promise<BaseResponse> {
    const req = request.request({
      url: '/submit',
      method: 'post',
      data: params,
    })
    return requestWrapper(BaseResponse, req)
  }


public static async remove (params: ProjectRequest): Promise<BaseResponse> {
    const req = request.request({
      url: '/remove',
      method: 'post',
      data: params,
    })
    return requestWrapper(BaseResponse, req)
  }

public static async getDetail (params: ProjectRequest): Promise<BaseResponse> {
    const req = request.request({
      url: '/getDetail',
      method: 'get',
      data: params,
    })
    return requestWrapper(BaseResponse, req)
  }

}



自定义模板-请求结果


// response

import { Type } from 'class-transformer'
import BaseResponse from '@/requests/common/BaseResponse'
import <%= name %>DTO from '@/beans/<%= name %>/<%= name %>DTO'

class DetailResponse  extends BaseResponse  {
  @Type(() =><%= name %>DTO)
  public data?: <%= name %>DTO
}

export {DetailResponse}


// response
import { Type } from 'class-transformer'
import BaseResponse from '@/requests/common/BaseResponse'
import ProjectDTO from '@/beans/Project/ProjectDTO'

class DetailResponse  extends BaseResponse  {
  @Type(() =>ProjectDTO)
  public data?: ProjectDTO
}

export {DetailResponse}

自定义模板-model


// model
import router from 'umi/router'
import { message } from 'antd'
import BaseModel from '@/dva/baseModel'
import { EffectsMapObject, ReducersMapObjectWithEnhancer } from 'dva'
import BaseResponse from '@/requests/common/BaseResponse'
import {DetailResponse} from '@/requests/<%= name %>/<%= name %>Response'
import <%= name %>Request from '@/requests/<%= name %>/<%= name %>Request'
import BaseTypeRequest from '@/requests/common/BaseTypeRequest'
import BaseTypeResponse from '@/requests/common/BaseTypeResponse'

import { ReducersMapObject } from 'redux'
class <%= name %>Model extends BaseModel {

  namespace = '<%= name %>'
  state: object = {
    formData: { },
    ...this.state,
  }
  subscriptions = {
    setup ({ dispatch, history }: { dispatch: Function, history: { listen: Function } }) {
      history.listen((location: { query: object, pathname: string }) => {

          const query = location.query
          if (location.pathname === '/<%= name %>/searchForm') {
            dispatch({
              type: 'queryList',
              payload: { ...query, funcId: 2019090904 },
            });
          }
        },
      )
    },
  }
  effects: EffectsMapObject = {
    * onSubmit ({ payload }, { call, put }) {
      const data:BaseResponse = yield call(<%= name %>Request.submit, { ...payload })
    },
    // * getLevels ({ payload }, { call, put }) {
    //   const data: BaseTypeResponse = yield call(BaseTypeRequest.getLevels, payload)
    //   if (data.isSuccess()) {
    //     yield put({
    //       type: 'updateState',
    //       payload: {
    //         levels: data.data,
    //       },
    //     })
    //   }
    // },
  * onGetDetail ({ payload }, { call, put }) {
  const data: DetailResponse= yield call(<%= name %>Request.getDetail, { ...payload})
  },
  * onRemove ({ payload }, { call, put }) {
  const data:BaseResponse= yield call(<%= name %>Request.remove, { ...payload })
 },

    ...this.effects,
  }

}

export default new <%= name %>Model()


// model
import router from 'umi/router'
import { message } from 'antd'
import BaseModel from '@/dva/baseModel'
import { EffectsMapObject, ReducersMapObjectWithEnhancer } from 'dva'
import BaseResponse from '@/requests/common/BaseResponse'
import {DetailResponse} from '@/requests/Project/ProjectResponse'
import ProjectRequest from '@/requests/Project/ProjectRequest'

import { ReducersMapObject } from 'redux'
class ProjectModel extends BaseModel {

  namespace = 'Project'
  state: object = {
    formData: { },
    ...this.state,
  }
  subscriptions = {
    setup ({ dispatch, history }: { dispatch: Function, history: { listen: Function } }) {
      history.listen((location: { query: object, pathname: string }) => {

          const query = location.query
          if (location.pathname === '/Project/searchForm') {
            dispatch({
              type: 'queryList',
              payload: { ...query, funcId: 2019090904 },
            });
          }
        },
      )
    },
  }
  effects: EffectsMapObject = {
    * onSubmit ({ payload }, { call, put }) {
      const data:BaseResponse = yield call(ProjectRequest.submit, { ...payload, accountType: 1 })
    },

  * onGetDetail ({ payload }, { call, put }) {
  const data: DetailResponse= yield call(ProjectRequest.getDetail, { ...payload, accountType: 1 })
  },
  * onRemove ({ payload }, { call, put }) {
  const data:BaseResponse= yield call(ProjectRequest.remove, { ...payload, accountType: 1 })
 },

    ...this.effects,
  }

}

export default new ProjectModel()

自定义模板-表单

// save


/**
 * @Author ZhangYunpeng0126
 * @Description 水平布局表单
 * @Date 11:30 2019/12/9
 */
import React, { useEffect, useCallback } from 'react'
import { connect } from 'dva'
import SubmitForm from '@/components/XyzForm/SubmitForm'
import { FormItemInfo } from '@/components/XyzForm/BaseForm/BaseFormArea'
import { Form, Select, Input } from 'antd'
import { createFormField } from 'rc-form'
import Button from 'antd/lib/button'

const Option = Select.Option

interface IConnectProps {
  app: object,
  <%= name %>: object,
  loading: boolean
}

interface IProps {
  form: any,
  dispatch: Function,
  <%= name %>: { },
}

function <%= name %>Form (props: IProps) {

  const { form, dispatch,  <%= name %> } = props


  const onSubmit = (values: object) => {
    const { dispatch } = props
    dispatch({
      type: '<%= name %>/onSubmit',
      payload: values,
    })
  }
  const FormItems: FormItemInfo[][] =<%- formItems %>
  const subTitle: string[] = []
  const title: string = '标题一'
  return <SubmitForm  subTitle={subTitle} FormItemInfos={FormItems} form={form} onSubmit={onSubmit}/>
}

export default connect(({ app, <%= name %>, loading }: IConnectProps) => ({
  app,
  <%= name %>,
  loading,
}))(Form.create({
  mapPropsToFields (props: any) {
    // console.log('mapPropsToFields', props)
    return {
      name1: createFormField({ value: props.<%= name %>.formData.name1 }),
    }
  },
  onFieldsChange (props, fields) {
    console.log('onFieldsChange', fields)

  },
})(<%= name %>Form))




// form

/**
 * @Author ZhangYunpeng0126
 * @Description 水平布局表单
 * @Date 11:30 2019/12/9
 */
import React, { useEffect, useCallback } from 'react'
import { connect } from 'dva'
import SubmitForm from '@/components/XyzForm/SubmitForm'
import { FormItemInfo } from '@/components/XyzForm/BaseForm/BaseFormArea'
import { Form, Select, Input, DatePicker } from 'antd'
import { createFormField } from 'rc-form'
import Button from 'antd/lib/button'

const Option = Select.Option

interface IConnectProps {
  app: object,
  Project: object,
  loading: boolean
}

interface IProps {
  form: any,
  dispatch: Function,
  Project: {},
}

function ProjectForm (props: IProps) {

  const { form, dispatch, Project } = props

  const onSubmit = (values: object) => {
    const { dispatch } = props
    dispatch({
      type: 'Project/onSubmit',
      payload: values,
    })
  }
  const FormItems: FormItemInfo[][] = [[
    { name: '项目名称', id: 'projectName' },
    { name: '项目类型', id: 'type', Comp: <Select><Option value={'1'}>请选择</Option></Select> },
    { name: '项目属性', id: 'property', Comp: <Select><Option value={'1'}>请选择</Option></Select> },
    { name: '是否公司项目', id: 'property', Comp: <Select><Option value={'1'}>请选择</Option></Select> },
    { name: '项目所在地', id: 'place', Comp: <Select><Option value={'1'}>请选择</Option></Select> },
    { name: '预期规模保费(单位:元)', id: 'fee' },
    { name: '预期经纪费收入(单位:元)', id: 'jingjifee' },
    { name: '预期费用支出(单位:元)', id: 'input' },
    { name: '预期收益(单位:元)', id: 'finance' },
    { name: '预计承保日期', id: 'chengbDate', Comp: <DatePicker/> },
    { name: '项目实施周期', id: 'term', Comp: <DatePicker/> },
    { name: '所属机构名称', id: 'depName', Comp: <Select><Option value={'1'}>请选择</Option></Select> },
    { name: '是否直接立项审核通过', id: 'isPass', Comp: <Select><Option value={'1'}>请选择</Option></Select> },
    { name: '项目立项日期', id: 'lixDate', Comp: <DatePicker/> },
    { name: '牵头部门', id: 'dep', Comp: <Select><Option value={'1'}>请选择</Option></Select> },
    { name: '委托授权到期提醒', id: 'weit', Comp: <Select><Option value={'1'}>请选择</Option></Select> },
    { name: '保单到期提醒', id: 'exp', Comp: <Select><Option value={'1'}>请选择</Option></Select> },
    { name: '项目险种说明', id: 'con', Comp: <Select><Option value={'1'}>请选择</Option></Select> },
    { name: '项目描述', id: 'desc', Comp: <Input.TextArea/> },

  ]]
  const subTitle: string[] = []
  const title: string = '标题一'
  return <SubmitForm subTitle={subTitle} FormItemInfos={FormItems} form={form} onSubmit={onSubmit}/>
}

export default connect(({ app, Project, loading }: IConnectProps) => ({
  app,
  Project,
  loading,
}))(Form.create({
  mapPropsToFields (props: any) {
    // console.log('mapPropsToFields', props)
    return {
      name1: createFormField({ value: props.Project.formData.name1 }),
    }
  },
  onFieldsChange (props, fields) {
    console.log('onFieldsChange', fields)

  },
})(ProjectForm))

插件插入方式

可以有两种方式

  • 直接在umirc文件的plugin中,引用本地插件路径
  • 发布到npm仓库引入
    推荐以包引入,因为最新版本的umi插件中会对package.json进行检查,要包含这个插件名,才允许插入

npm打包方式

一开始踩了个坑,用了默认打包方式打成esm了 esm一般在现代浏览器中没问题,但是在node环境可能会有问题
这里应该打成cjs包 才能在node环境运行,用umi推荐的father打包工具,在faterrc里配置 cjs方式就行了

最后

总结一下

优点

  • 这个generator完全是可以脱离umi自己整一个cli 可以适用于所有有需要的项目

  • 灵活性较高,规则和模板配合,可以适用于一些约定式框架的通用代码的生成

  • 统一模板 就可以统一一些书写风格

  • 提高编码效率,让电脑搬砖

局限性

  • 依赖于框架,不同框架可能要写不同的规则

  • 中大型项目收益较高 小项目可能没啥收益

谢谢,再见