ZanBuild 工程日记

2017-11-13 14:51

之前三期算是练手, 整个项目的整体流程都已经很清楚了, 那么从第四期开始, 就要在代码质量和机构层面来搞一些事情了.

先来回顾一下第三期的编码过程.

可以说看了那几篇关于 JS 的文章有点蛋疼. 十月中旬的那两周把第三期的代码折腾来折腾去, 最后还是发现了很多问题. 作罢, 现在已经全部改回到默认的 React 写法了. 总有那么点不甘心, 迫于时间, 不然还想重新研究一下到底哪里除了问题 (个人感觉上来说, 还是只从 React.Component.prototype 复制属性过来, 而没有任何地方去执行父类初始化带来了那些无法更新等的一系列问题). 第三期结束之后, 大致有如下几点问题:

  • 感觉最不好的地方就是代码结构不好, 没有很好地复用

  • 组件封装有问题, 后面想复用组件很困难

  • 由于没有复用, 重复代码是很多的

  • 这一点可能是看文章的副作用, 现在很抵触 extends 继承... 总不想写 Base 然后去继承一下 (比如 Build Create 等几个模块中的代码很多都是可以复用的, 当时由于抵触没有去写, 现在要想办法找到一个在 React class 中能用组合的方式)

今天分析完了页面 UI 和新一起的整个逻辑. 感觉又是一轮重写. 这次准备 80% 时间设计, 20% 时间编码. 下面就从服务端数据开始, 从头设计一下前端这一层能做的复用.


接口名:

// 创建清单
/manifest/create

请求参数:

{

  "type": "1",

  "alias": "test",

  "component": 4,

  "branch": "dev",

  "version": "1.1.2"

}

样例返回:

{

  "result": 0,

  "dependencies": [

    {

      "id": 1,

      "name": "wsc_shop",

      "version": "1.2.1",

      "bundleId": "com.xxx.mobile:wsc_shop"

    },

    {

      "id": 2,

      "name": "wsc_login",

      "version": "1.2.0",

      "bundleId": "com.xxx.mobile:wsc_login"

    }

  ],

  "build": 0,

  "buildStatus": 0,

  "create": "2017-11-13T07:49:54.000Z",

  "id": 584,

  "component": 18,

  "type": "1",

  "alias": "zanwsc",

  "parent": "07051d043d333bf0ed1f53cc0d1f3edbb8e9a417",

  "branch": "dev",

  "version": "1.1.1",

  "hash": "f5fd0b003575f011ae015cfd3a4b71867ed9b11d"

}

接口名:

//  保存清单

/manifest/:hash

请求参数:

{

  "components": [

    {

      "id": 1,

      "name": "wsc_shop",

      "version": "1.2.1"

    },

    {

      "id": 2,

      "name": "wsc_login",

      "version": "1.2.0"

    }

  ],

  "hash": "f5fd0b003575f011ae015cfd3a4b71867ed9b11d"

}

样例返回:

{"result":0}

接口名:

// 详情

manifest/:hash

请求参数:

样例返回:

{

  "result": 0,

  "id": 580,

  "component": 18,

  "type": 1,

  "parent": "07051d043d333bf0ed1f53cc0d1f3edbb8e9a417",

  "hash": "98776bbbd5d3c9646aa4d2cf18dd5f60fae46fd7",

  "alias": "ZanBuild Docker Runner Test",

  "dependencies": [

    {

      "id": 1,

      "name": "wsc_shop",

      "version": "1.2.1",

      "bundleId": "com.xxx.mobile:wsc_shop"

    },

    {

      "id": 2,

      "name": "wsc_login",

      "version": "1.2.0",

      "bundleId": "com.xxx.mobile:wsc_login"

    }

  ],

  "build": 367,

  "branch": "master",

  "version": "3.24.3",

  "buildStatus": 1,

  "create": "2017-11-13T02:43:15.000Z"

}

接口名:

// 构建

/manifest/:hash//build

请求参数:

{}

样例返回:

{"result":0,"build":26}

接口名:

// 草稿清单

/manifest/drafts?component=:componentId

请求参数:

样例返回:

{

  "result": 0,

  "manifest": [

    {

      "id": 582,

      "component": 1,

      "type": 1,

      "parent": "afeea00e2cacefdbbb04b05910b5c06d8976a886",

      "hash": "96e733684be8d5c8baf46efc77516920eacb2a22",

      "alias": "wsc_shop",

      "build": 0,

      "branch": "dev",

      "version": "1.1.2",

      "buildStatus": 0,

      "create": "2017-11-13T06:57:37.000Z"

    },

    {

      "id": 581,

      "component": 1,

      "type": 1,

      "parent": "afeea00e2cacefdbbb04b05910b5c06d8976a886",

      "hash": "ef09ff23cc0d68577328e878752d6f395241aa00",

      "alias": "test",

      "build": 0,

      "branch": "dev",

      "version": "1.1.1",

      "buildStatus": 0,

      "create": "2017-11-13T06:14:12.000Z"

    }
  ]
}

接口名:

// 历史清单

/manifest/history?type=0&component=1&buildStatus=1

请求参数:

样例返回:

{

  "result": 0,

  "manifest": [

    {

      "id": 461,

      "component": 1,

      "type": 0,

      "parent": "409083f04d98605fb9e20e2c12d4f53d8587733b",

      "hash": "fb433e0f2f0f5855c70fbacb55fe594040d90c97",

      "alias": "wsc_shop 打包测试",

      "build": 253,

      "branch": "master",

      "version": "1.2.0-beta",

      "buildStatus": 1,

      "create": "2017-11-07T07:46:37.000Z"

    },

    {

      "id": 375,

      "component": 1,

      "type": 0,

      "parent": "409083f04d98605fb9e20e2c12d4f53d8587733b",

      "hash": "93b0e6415cc0ee4861ae70336f5068923e2dda2d",

      "alias": "aa",

      "build": 213,

      "branch": "master",

      "version": "1.1.1",

      "buildStatus": 1,

      "create": "2017-11-02T09:55:35.000Z"

    }

  ]
}

接口名:

/component/list

请求参数:

样例返回:

{

  "result": 0,

  "list": [

    {

      "id": 1,

      "name": "wsc_shop",

      "repo": "http://xxx.com/wsc_android/wsc_shop",

      "bundleId": "com.xxx.mobile:wsc_shop",

      "buildUrl": "http://jenkins-xxx.com/job/wsc_shop/build"

    }

    {

      "id": 18,

      "name": "ZanBuild-WSC",

      "repo": "http://gitlab.xxx.com/wsc_android/wsc",

      "bundleId": "com.xxx.kdt",

      "buildUrl": "http://jenkins-xxx.com/job/ZanBuild-WSC/build"

    }

  ]

}

接口名:

// 更新组件

/component/:id

请求参数:

{

  "id": 7,

  "name": "wsc_fans",

  "bundleId": "com.xxx.mobile:wsc_fans",

  "repo": "http://xxx.com/wsc_android/wsc_fans",

  "buildUrl": "http://jenkins-xxx.com/job/wsc_fans/build",

  "status": "saved",  // 可不传

  "dbId": 7  // 可不传

}

样例返回:

{"result":0}

接口名:

// 添加新的组件

/component/

请求参数:

{

  "name": "test_component",

  "bundleId": "com.test",

  "repo": "http://gitlab.test",

  "buildUrl": "http://jenkins.test",

  "status": "modified"

}

样例返回:

{"result":0}

这次做第四期, 想要表格的功能尽量简单, 做展示用, 最多有点击跳转的逻辑. 尽量把组件和清单的编辑功能从表格里面分离开.

每个表格所需要的数据的格式是一致的, 如下 (需要特殊处理的除外, 基本数据结构如下):

    let id = Object.assign({}, {

            value: index + 1

        });

        let hash = Object.assign({}, {

            value: item.hash

        });

        let pHash = Object.assign({}, {

            value: item.parent === '0' ? '无' : item.parent

        });

        let type = Object.assign({}, {

            value: typeStr

        });

        let create = Object.assign({}, {

            value: common_shared.timeFormat(item.create)

        });

        let status = Object.assign({}, {

            value: statusStr

        });

        let dataRow = Object.assign({}, {

            key: index,

            id: id,

            hash: hash,

            pHash: pHash,

            create: create,

            type: type,

            status: status

        });

然后在表格中, 使用下面的代码生成 dataSource:

  const dataSource = data.map((item) => {

            const obj = {}

            Object.keys(item).forEach((key) => {

                obj[key] = key === 'key' ? item[key] : item[key].value

            })

            return obj

        })

在第三期中, 这里的数据转化的工作是比较头疼的, 服务端返回的数据都要经过重组, 表格才能使用. 这个问题在四期中还是不可避免的.

先看一下要使用到表格的页面:

  • 首页

  • 清单查询页面, 要加一栏操作, 操作的功能为 "复制" 功能

  • 创建清单页面

  • 添加组件页面

  • 构建区构建清单页面

    这里点击组件名或者构建之后, 隐藏掉这个草稿清单的表格, 显示构建组件(这个构建组件的ui展示和创建清单时一样, 但是可以构建), 下面是构建组件需要的表格数据

  • 构建记录页面

  • 待定的测试区和发布区

  • 新的详情页面, 依赖表格所需的数据

整理如下:

表格分为两种类型, 一种是 "清单" 类型, 一种是 "组件" 类型.

清单类型表格条目信息统一成:

组件类型表格条目信息统一成:

所有清单类型的表格, 操作里面默认放置复制, 开启不开启用参数控制; 默认放置编辑按钮, 开启不开启用参数控制; 点击复制, 跳转到构建区/清单构建tab下;

页面现在已经分析清楚了, 从 sketch 中能很清楚查看四期的页面和逻辑. 现在开始先用新的工程结构搭建静态页面, 页面和路由搭建完成之后, 再开始分析数据, 接入 redux, 联调所有接口.

工程日志

  1. 重新封装 App 显示 Home 和 CI;
  2. 重新封装 Header;
  3. 显示 Header;
  4. 重新封装 Footer;
  5. 显示 Footer;
  6. 创建 Home mock页面, 对应的是所有构建成功的清单以及一个开始新的集成按钮;
  7. 创建 Home css, 设置 minheigh;
  8. 封装一个 PureButton, 并显示在 Home 组件中;
  9. 传入点击回调;
  10. 封装清单类型的表格, 表格列见前文;
  11. 创建 CI mock页面;
  12. NavLink 封装; 接受传入的地址;
  13. 在 CINaviagtion 中创建子导航, 并添加样式, 默认都显示;
  14. 点击二级导航才展示三级导航;
  15. 在清单类型列表中添加操作一栏;
  16. 确定清单类型的表格数据, 都不能编辑, 是超链接的, render 成 <a> 标签, 支持跳转; 清单类型表格数据还是按照之前的格式封装;
  17. 增加组件类型表格; 显示在组件配置页面; mock数据;
  18. 创建详情页面; 详情页面没有三级路由, 二级路由挂在 integrating 下;
  19. 点击组件表格中查看详情, 跳转到详情页面;
  20. 组件详情页面由多种不同路由请求组成; 根据每种请求显示不同内容; 先分析需要哪些 ui 组件;
  21. 完成配置详情页面路由, 跳转到详情页面的路由必须带 hash requesttype editable;
  22. 分析组件详情页面所需的 ui 组件;
  23. 创建组件详情页面 ui;
  24. 组件详情页面, 现有组件刚进入的时候, 要先点击 编辑组件 按钮, 才能进行编辑; 点击之后, 按钮变成 保存组件, 点击保存, 请求保存接口; 点击删除, 删除现有组件;
  25. 创建清单详情页面 ui;
  26. 调整清单详情页面 ui;
  27. 创建清单搜索页面ui;
  28. 完成清单搜索页面, 组件下拉列表项数据组装和渲染;
  29. 检查清单搜索页面三个 select 是否工作正常, 返回数据是否正确;
  30. 完成清单搜索查询功能;
  31. 完成清单搜索重置功能;
  32. 完成组件详情页面 Form 表单配置;
  33. reducer 定义在各自文件中;
  34. 组件详情页面, 编辑现有组件默认为 disabled 状态;
  35. 调试组件详情页面编辑和保存按钮, 点击编辑, 可以编辑组件信息;
  36. 调试组件详情页面编辑和保存按钮, 点击保存, 可以获取到所有最新的表单信息;
  37. 调试组件详情页面编辑和保存按钮, 点击保存, 可以保存组件;
  38. 如果没有变动, 不要请求服务端;
  39. 完成清单创建头部信息部分 (输入内容并保存);
  40. 实现一个 FormItem 中间包含一个 Select, 并获取 Select 的变化;
  41. 创建清单页面获取组件列表并保存;
  42. 创建清单页面, 请求创建清单接口;
  43. 构建版本加 regx 验证;
  44. 创建空的创建清单列表, 展示一下;
  45. 创建清单页面, 根据选择的组件 id 查询对应的分支列表; 首选要获取到用户选择的组件名称;
  46. 增加构建分支 selectloading 状态; 选择了组件之后要 loading 等待获取到相应的组件分支列表;
  47. 定义请求组件分支的 actionreducer;
  48. 拿到创建按钮点击之后的所有参数, 核对是否正确;
  49. 请求创建接口;
  50. 创建之后, 隐藏创建 header, 显示信息 header; 创建信息 header;
  51. 展示 infoHeader 信息;
  52. 创建清单的时候, 根据清单类型, 校验版本号;
  53. 定义创建清单依赖的 Table;
  54. 有数据时才展示表格, 在表格和 header 之间添加分割线;
  55. 将原先创建清单中的按钮迁移过来;
  56. 创建完清单之后展示按钮区域和表格;
  57. 组装依赖表格数据;
  58. 展示依赖表格;
  59. 获取依赖项选中后数据;
  60. 传入 depTableData 之前, 从 createRawData 中拿出 component, 从 depTableData 中删除;
  61. 首先, 传入一个参数, 这个参数是一个布尔, 如果当前依赖列表中已经存在的组件, checked 变为 true, 默认勾选;
  62. 完成删除一个依赖, 取消勾选的时候, 要从 tableData 中删除指定的 component;
  63. 完成增加一个依赖, 点击勾选的时候, 按照传过来的 id, 从 depRawData 中拿出指定 component, 添加到 tableData 末尾;
  64. 把依赖表格显示在页面上, 点击添加依赖, 显示依赖表格, 添加依赖按钮变成收起依赖;
  65. 完成全选逻辑;
  66. 所有表格加上 id 一列;
  67. 保存清单;
  68. 保存操作数据校验, 不能有空信息;
  69. 编辑清单版本号; 先可以点击编辑, 唤出保存和取消按钮;
  70. 完成单个编辑的逻辑;
  71. 完成删除依赖的逻辑;
  72. 修复依赖的默认勾选问题, 是因为数据格式变化了;
  73. 完成添加依赖表格中查看详情按钮逻辑;
  74. 完成父亲单跳转逻辑;
  75. 完成清单草稿;
  76. 父清单显示成链接;
  77. 完成草稿 ui;
  78. 完成构建页面, 完成构建接口调用;
  79. 构建的时候构建按钮要 loading, 构建成功之后要 disable 掉构建按钮, 并跳转到构建记录页面;
  80. 修改导航样式;
  81. 直接点进构建页面的时候显示空界面, 提示去草稿选择构建清单; 完成三个卡片的跳转逻辑;
  82. 清单详情界面; 在草稿中选择清单然后跳转到详情界面;
  83. 当没有清单可以构建的时候, 在 MfstBuild 里面点击创建清单要清空创建的状态, 重新创建
  84. 草稿页面查看详情, 构建, 复制;
  85. 主页面三个功能按钮;
  86. 组件删除;
  87. 点击 build 按钮之后, 要 reset 构建页面;
  88. 构建中的清单, 再次构建的话的错误处理;
  89. 完成所有错误处理;
  90. reset create 页面;
  91. datafactorymiddleware 放到线上环境;
  92. redirect 302;
  93. copy 接口调试完成;
  94. 消息提示;
  95. trace 页面 build/:id 接口调试;
  96. 一级导航前加上 icon;
  97. 修复创建页面组件下拉搜索问题;
  98. 加入创建人;
  99. gitlabpage 地址做成 <a>;
  100. 没有依赖的时候隐藏保存依赖和批量编辑版本号按钮;
  101. create 界面的依赖组件列表用 componentDataCache;
  102. 构建详情界面头部 bottommargin 问题;
  103. 修改清单详情的时候的版本号限制问题;
  104. 去掉保存清单依赖之后的 modal 可以跳转到构建页面的逻辑;
  105. modal 改成 notification;
  106. 构建详情 loading ui 浮在 日志上;
  107. 增加创建新清单按钮;
  108. 没有依赖的时候隐藏保存依赖和批量编辑版本号按钮;
  109. 清理代码;
  110. parant 详情报 404;
  111. proptypes 补全;
  112. 没有日志显示加载中;

302 重定向测试
调整一下需要突出引导的按钮的样式
补注释和文档
url简化
runner 状态
buildResult 写到常量
yaml editor

记录

  1. antdForm 其内部会有状态缓存; 在配合 redux 使用的时候, 如果不通过像 container 一样绑定 store, 然后通过 mapPropsToFields 方法将通过 connect 中传递过来的 state 解析成所需的 props, 将无法直接从 container 获取到 props. 详情见: Antd 表单与 Redux
  2. defaultValue 不刷新界面的情况, 是 reduxantd 组件冲突了; 用 <Form>initialValue 解决;