• 需求背景:

    • 禅道开源版没有报表自定义功能,但是接测试负责人的需求,需要在项目维度下对各个项目的需求实现率进行统计掌握,方便构建模型以供后续工作进行参考

      扩展效果

      image.png

      涉及修改的文件&思路

      字库

    • 老规矩 按照禅道二开的拓展规则进行拓展

    • module/report/ext/lang/zh-cn/report_extend.php
    • report_extend.php 为扩展字库文件,名称任意。 ```php <?php

// FIXME 增加需求实现率字段 $lang->report->projectStoryRate = $lang->projectCommon . ‘需求实现率汇总表’;

// FIXME 依据项目状态进行统计 需求实现情况。 细分为 进行中,已关闭 $lang->report->closedProject = ‘已关闭’ . $lang->projectCommon; $lang->report->processProject = ‘进行中’ . $lang->projectCommon;

// FIXME 增加报表种类超链接 $lang->reportList->project->lists[13] = $lang->projectCommon . ‘需求实现率汇总表|report|projectstoryrate’;

// FIXME 需求实现率字段 $lang->report->storyRate = ‘需求实现率’;

  1. <a name="nCRYv"></a>
  2. ## controler
  3. - 同样,按照规则,扩展目录为 module/report/ext/control/projectstoryrate.php
  4. - 此时名称不可任意命名 control 类名对应着 所渲染的视图名称,即,control 类名 是与 所渲染的前端页面名称是一致的,见下图
  5. - ![image.png](https://cdn.nlark.com/yuque/0/2021/png/1608527/1615947392738-0bcf9754-77ef-4fd5-98e4-a66635bfb61a.png#align=left&display=inline&height=717&margin=%5Bobject%20Object%5D&name=image.png&originHeight=717&originWidth=1120&size=554821&status=done&style=none&width=1120)
  6. ```php
  7. <?php
  8. /**
  9. * Create a bug.
  10. *
  11. * @param int $productID
  12. * @param string $branch
  13. * @param string $extras others params, forexample, projectID=10,moduleID=10
  14. * @access public
  15. * @return void
  16. */
  17. include '../../control.php'; // 将 report模块下的 原生的control类引入
  18. class myReport extends report // 采用继承拓展的方式来对control下的方法进行拓展
  19. {
  20. /* 进行自定义方法补充 */
  21. /**
  22. * Product information report.
  23. *
  24. * @access public
  25. * @return void
  26. */
  27. public function projectStoryRate($status = '') // 名称任意,命名规则参照原control类采用驼峰命名法
  28. {
  29. // 字库读取
  30. $this->app->loadLang('project'); // 读取 project模块下的字库
  31. $this->app->loadLang('story'); // 读取 story 模块下的字库
  32. $this->view->title = $this->lang->report->projectStoryRate; // 字库取值渲染到页面 - title
  33. $this->view->position[] = $this->lang->report->projectStoryRate; // 控制左侧边栏选中效果。
  34. // 产品查询结果渲染到前端页面并用变量 products 进行保存,可直接在view文件中进行引用 查询ok
  35. $this->view->projects = $this->report->getStoryRate($status); //$this->module名->方法名 这种调用方式是直接调用模块下的某个方法
  36. $this->view->users = $this->loadModel('user')->getPairs('noletter|noclosed'); // getPairs 取得用户的key/value 数组
  37. $this->view->submenu = 'project'; // 传给view 文件中的submenu字段,组成侧边导航栏
  38. $this->view->status = $status; // 查询条件,由前端从传进来
  39. $this->display(); // 渲染到对应视图文件 即 projectstoryrate.html.php
  40. }
  41. }

model & sql编写

  • 接前面control中调用方法,接着对model层进行扩展,同样按照规则进行拓展,
  • 路径 module/report/ext/model/getStoryRate.php ```php <?php public function getStoryRate($status) { // 编写sql查询到所有的project,用于前端页面展示传值 // conditions 为project_status // 思路 ,按照 project_status 进行筛选展示结果。 /*

    • 思路梳理
    • 查到所有的项目信息 ok
    • 查到所有项目的需求信息 ok
    • 项目信息与需求信息进行合并成为一个数组,返回给前端。not yet */

    $projects = $this->dao->select(‘‘)->from(TABLE_PROJECT) ->where(‘deleted’)->eq(0) ->beginIF($status)->andWhere(‘status’)->eq($status)->fi() ->fetchAll(‘id’); // 查询到项目关联的需求信息 主要是个数 / select from zt_story as story left join zt_projectstory zp on story.id = zp.story left join zt_project as project on project.id = zp.project where story.deleted = ‘0’ and project.name != ‘’ / // db中查询到结果 // 首先要在db中测试确认sql没有问题,之后转写成禅道php可以识别的方式,如下,下面贴出禅道官方的介绍 // https://devel.easycorp.cn/book/extension/intro-45.html $storys = $this->dao->select(‘story.id,story.status,project.name,project.id as project’)->from(TABLE_STORY)->alias(‘story’)

    1. ->leftJoin(TABLE_PROJECTSTORY)->alias('zp')->on('story.id = zp.story')
    2. ->leftJoin(TABLE_PROJECT)->alias('project')->on('project.id = zp.project')
    3. ->where('story.deleted')->eq(0)
    4. ->andWhere('project.name')->ne('')

    // ->andWhere(‘project.id’)->in($this->dao->select(‘id’)->from(TABLE_PROJECT))->fi()

    1. ->fetchAll();

    foreach($storys as $story) { // 遍历拿到的story结果集,需要与每个项目进行关联, // 简单来说,是吧所有找到的需求,找到他的爸爸,然后组成一个数组,传给调用出渲染到前端视图进行处理

    1. // 形如
    2. // projects
    3. - project (当前项目没有需求)
    4. - project
    5. - storys
    6. - project
    7. - storys

    // 单个需求信息与项目信息进行对应赋值 // 需求 与 项目如何对应? foreach($projects as $project ){

    1. if ($story->name == $project->name ){
    2. // 判断是否为同一项目,是,则添加到当前项目数组的story中
    3. // 此处需要多次进行debug以确定结果是否为
    4. // 利用项目id 与前面story 查询语句得到的结果中的项目id进行对应,组合数组
    5. $projects[$story->project]->storys[$story->id] = $story;
    6. }

    } }

    unset($projects[‘’]); // return $products; return $projects; // 传回调用处,也就是control层

}

  1. <a name="9wIld"></a>
  2. ## view 视图编写 ,缺失的js方法补充
  3. ```php
  4. <?php include '../../../common/view/header.html.php';?>
  5. <?php if(common::checkNotCN()):?>
  6. <style>#conditions .col-xs { width: 126px; }</style>
  7. <?php endif;?>
  8. <div id='mainContent' class='main-row'>
  9. <div class='side-col col-lg'>
  10. <?php include 'blockreportlist.html.php';?>
  11. </div>
  12. <div class='main-col'>
  13. <?php if(empty($projects)):?>
  14. <div class="cell">
  15. <div class="table-empty-tip">
  16. <p><span class="text-muted"><?php echo $lang->error->noData;?></span></p>
  17. </div>
  18. </div>
  19. <?php else:?>
  20. <div class='cell'>
  21. <div class='panel'>
  22. <div class="panel-heading">
  23. <div class="panel-title">
  24. <div class="table-row" id='conditions'>
  25. <div class="col-2"><?php echo $title;?></div>
  26. <div class="col-xs text-right text-gray text-middle"><?php echo $lang->report->conditions?></div>
  27. <div>
  28. <div class='col-xs input-group'>
  29. <span class='input-group-addon'><?php echo $lang->project->status;?></span>
  30. <?php echo html::select('status', $lang->project->statusList, $status , "class='form-control chosen ' onchange='changeParams(this)'");?>
  31. </div>
  32. </div>
  33. </div>
  34. </div>
  35. <nav class="panel-actions btn-toolbar"></nav>
  36. </div>
  37. <div data-ride='table'>
  38. <table class='table table-condensed table-striped table-bordered table-fixed no-margin' id='productList'>
  39. <thead>
  40. <tr>
  41. <!-- 项目名称-->
  42. <th class='text-center w-200px'><?php echo $lang->project->name;?></th>
  43. <!-- 团队-->
  44. <th class='text-center w-200px'><?php echo $lang->project->teamname;?></th>
  45. <!-- 责任人-->
  46. <th class='text-center w-70px'><?php echo $lang->project->owner;?></th>
  47. <!-- 项目所处阶段-->
  48. <th class="text-center w-70px" ><?php echo $lang->project->projectStage;?></th>
  49. <!-- 项目开始时间-->
  50. <th class="text-center w-100px"><?php echo $lang->projectCommon . $lang->project->begin;?></th>
  51. <!-- 项目结束时间-->
  52. <th class="text-center w-100px"><?php echo $lang->projectCommon . $lang->project->end;?></th>
  53. <!-- 需求状态为 草稿-->
  54. <th class="text-center w-70px"><?php echo $lang->story->statusList['draft'];?></th>
  55. <!-- 需求状态为 激活-->
  56. <th class="text-center w-70px"><?php echo $lang->story->statusList['active'];?></th>
  57. <!-- 需求状态为 已变更-->
  58. <th class="text-center w-70px"><?php echo $lang->story->statusList['changed'];?></th>
  59. <!-- 需求状态为 已关闭-->
  60. <th class="text-center w-70px"><?php echo $lang->story->statusList['closed'];?></th>
  61. <!-- 需求 总计个数-->
  62. <th class="text-center w-70px"><?php echo $lang->report->total;?></th>
  63. <!-- 需求 实现率 计算公式为 已关闭个数/总计个数-->
  64. <th class="text-center w-200px"><?php echo $lang->report->storyRate;?></th>
  65. </tr>
  66. </thead>
  67. <tbody>
  68. <?php $color = false;?>
  69. <!-- 初始化值的地方存在问题,要考虑重复赋值-->
  70. <?php foreach($projects as $project):?>
  71. <?php
  72. $closedCount = 0;
  73. $activeCount = 0;
  74. $changedCount = 0;
  75. $draftCount = 0;
  76. ?>
  77. <tr class="text-center">
  78. <!-- 项目名 同时创建a标签供点击跳转-->
  79. <td class='text-center text-blue' title="<?php echo $project->name;?>" ><?php echo "<p>" . html::a($this->createLink('project', 'view', "project=$project->id"), $project->name) . "</p>";?></td>
  80. <!-- 所属团队-->
  81. <td class="<?php echo $class;?>"><?php echo $project->team;?></td>
  82. <!-- 负责人-->
  83. <td class="<?php echo $class;?>"><?php echo $project->PO;?></td>
  84. <!-- 项目所属阶段-->
  85. <td class="<?php echo $class;?>"><?php echo $lang->project->projectStageList[$project->projectStage];?></td>
  86. <!-- 项目开始日期-->
  87. <td class="<?php echo $class;?>"><?php echo $project->begin == '2030-01-01' ? $lang->productplan->future : $project->begin;?></td>
  88. <!-- 项目截止日期-->
  89. <td class="<?php echo $class;?>"><?php echo $project->end == '2030-01-01' ? $lang->productplan->future : $project->end;?></td>
  90. <!-- 如果 产品计划不为空-->
  91. <!-- 原报表为产品简报,需求与计划挂钩,-->
  92. <?php foreach($project->storys as $story):?>
  93. <!-- 需求个数统计 -->
  94. <!-- switch 根据状态统计,count ++ 然后赋值 -->
  95. <?php
  96. switch ($story->status) {
  97. case "closed":
  98. $closedCount++;
  99. break;
  100. case "active":
  101. $activeCount++;
  102. break;
  103. case "changed":
  104. $changedCount++;
  105. break;
  106. case "draft":
  107. $draftCount++;
  108. break;
  109. }
  110. ?>
  111. <?php endforeach;?>
  112. <!-- 需求为草稿的个数 -->
  113. <td class="<?php echo $class;?>"><?php echo $draftCount;?></td>
  114. <!-- 需求状态为激活的个数-->
  115. <td class="<?php echo $class;?>"><?php echo $activeCount;?></td>
  116. <!-- 需求状态为已变更的个数-->
  117. <td class="<?php echo $class;?>"><?php echo $changedCount;?></td>
  118. <!-- 需求状态为已关闭的个数-->
  119. <td class="<?php echo $class;?>"><?php echo $closedCount;?></td>
  120. <!-- 需求 总个数统计-->
  121. <td class="<?php echo $class;?>"><?php echo $draftCount + $activeCount + $changedCount + $closedCount;?></td>
  122. <!-- 需要对 小数尾部进行处理-->
  123. <td class="<?php echo $class;?>">
  124. <?php
  125. $total = $draftCount + $changedCount + $closedCount + $activeCount;
  126. // 合计不为0 则进入计算,为0直接返回输出0
  127. if ($total!=0) {
  128. $rate = ($closedCount / $total) * 10000;
  129. $rate = floor($rate) / 10000 * 100 . "%";
  130. echo $rate;
  131. }else{
  132. echo '0%';
  133. }
  134. ;?>
  135. </td>
  136. </tr>
  137. <?php endforeach;?>
  138. </tbody>
  139. </table>
  140. </div>
  141. </div>
  142. </div>
  143. <?php endif;?>
  144. </div>
  145. </div>
  146. <?php include '../../../common/view/footer.html.php';?>
  • view 视图在编写的过程中想要添加一个检索条件,下贴出涉及检索部分的前端代码 以及需要的js代码
  • 对应的前端页面组件为下图
  • image.png

    1. <div class="panel-title">
    2. <div class="table-row" id='conditions'>
    3. <div class="col-2"><?php echo $title;?></div>
    4. <div class="col-xs text-right text-gray text-middle"><?php echo $lang->report->conditions?></div>
    5. <div>
    6. <div class='col-xs input-group'>
    7. <span class='input-group-addon'><?php echo $lang->project->status;?></span>
    8. <?php echo html::select('status', $lang->project->statusList, $status , "class='form-control chosen ' onchange='changeParams(this)'");?>
    9. </div>
    10. </div>
    11. </div>
    12. </div>
  • 字段方面不再进行赘述,可以参考其他视图文件中对字库的引用,以及前几篇文章中的记录。

  • 可以注意到 select 标签 中 有一个 js方法 onchange=’changeParams(this)’ ,调用了一个js方法,扩展路径如下
    • module/report/ext/js/projectstoryrate/projectstoryrate.js
  • 内容 ```javascript function changeParams(obj) {

    var status = $(‘#conditions’).find(‘#status’).val();

  1. var link = createLink('report', 'projectStoryRate','status=' + status);
  2. location.href=link;

}

```

  • 可以看到上面的代码十分的简单,及时 取得 id 为 conditons 的区块,在区块中查询 id 为 status 的变量,并且取到值
  • 重点是下一步的方法 ,createLink
  • image.png
  • 点开源码可以看到 ,该方法为禅道自己封装的一个js方法,其主要功用是创建一个链接 诸如禅道页面上的各种跳转,其实底层大多是这个方法调用来生成的。其调用方式也比较直观 。
  • 参数解释,
    • report 为 model名,
    • projectStoryRate 还记得这个名字么,是本篇扩展的 control类中方法名,
    • status 则是需要传的参数。而status 则是前端页面进行下拉选框进行选择时发生更改的参数值。
  • 调用链条: 前端用户选择状态-> onchange=’changeParams(this)’ 方法 -> createLink -> location.href
  • 综上,完成根据状态进行不同情况的查询。

总结:本篇需要一定的二开经验基础,对文章有疑问可留言进行交流。