From 35bb1ac050e1514b867b69756819a383690a3d7b Mon Sep 17 00:00:00 2001 From: sunguangming Date: Tue, 2 Jan 2024 09:21:52 +0800 Subject: [PATCH] * Refactor program kanban. --- module/product/ui/kanban.html.php | 15 +- module/program/control.php | 13 +- module/program/css/kanban.ui.css | 8 ++ module/program/js/kanban.ui.js | 37 +++++ module/program/model.php | 39 ----- module/program/test/model/getkanbangroup.php | 44 ------ module/program/test/program.class.php | 11 -- module/program/ui/kanban.html.php | 71 ++++++++++ module/program/zen.php | 141 +++++++++++++++++++ 9 files changed, 270 insertions(+), 109 deletions(-) create mode 100644 module/program/css/kanban.ui.css create mode 100644 module/program/js/kanban.ui.js delete mode 100755 module/program/test/model/getkanbangroup.php create mode 100644 module/program/ui/kanban.html.php diff --git a/module/product/ui/kanban.html.php b/module/product/ui/kanban.html.php index 3178365b1e..8af9909105 100644 --- a/module/product/ui/kanban.html.php +++ b/module/product/ui/kanban.html.php @@ -22,13 +22,14 @@ foreach($kanbanList as $current => $region) { foreach($region['items'] as $index => $group) { - $group['draggable'] = true; - $group['colWidth'] = 'auto'; - $group['getCol'] = jsRaw('window.getCol'); - $group['getItem'] = jsRaw('window.getItem'); - $group['itemRender'] = jsRaw('window.itemRender'); - $group['canDrop'] = jsRaw('window.canDrop'); - $group['onDrop'] = jsRaw('window.onDrop'); + $group['draggable'] = true; + $group['colWidth'] = 'auto'; + $group['maxLaneHeight'] = '300px'; + $group['getCol'] = jsRaw('window.getCol'); + $group['getItem'] = jsRaw('window.getItem'); + $group['itemRender'] = jsRaw('window.itemRender'); + $group['canDrop'] = jsRaw('window.canDrop'); + $group['onDrop'] = jsRaw('window.onDrop'); $kanbanList[$current]['items'][$index] = $group; } } diff --git a/module/program/control.php b/module/program/control.php index 7f8fa045a7..0acb12d2aa 100644 --- a/module/program/control.php +++ b/module/program/control.php @@ -70,18 +70,15 @@ class program extends control * 项目集看板。 * Program kanban list. * + * @param string $browseType * @access public * @return void */ - public function kanban() + public function kanban($browseType = 'my') { - $uri = $this->app->getURI(true); - $this->session->set('projectList', $uri, 'project'); - $this->session->set('productPlanList', $uri, 'product'); - $this->session->set('releaseList', $uri, 'product'); - - $this->view->title = $this->lang->program->kanban->common; - $this->view->kanbanGroup = array_filter($this->program->getKanbanGroup()); + $this->view->title = $this->lang->program->kanban->common; + $this->view->kanbanList = $this->programZen->getKanbanList($browseType); + $this->view->browseType = $browseType; $this->display(); } diff --git a/module/program/css/kanban.ui.css b/module/program/css/kanban.ui.css new file mode 100644 index 0000000000..a95f68f632 --- /dev/null +++ b/module/program/css/kanban.ui.css @@ -0,0 +1,8 @@ +.card {padding: 6px;} +.card-item-wait .card {border-left: 2px solid rgba(var(--color-primary-500-rgb),var(--tw-text-opacity));} +.card-item-doing .card {border-left: 2px solid rgba(var(--color-success-500-rgb),var(--tw-text-opacity));} +.card-item-closed .card {border-left: 2px solid rgba(var(--color-gray-500-rgb),var(--tw-text-opacity));} +.card-item-delay .card {border-left: 2px solid rgba(var(--color-danger-500-rgb),var(--tw-text-opacity));} + +.card-prefix {position: absolute; right: 10px} +.card-suffix {min-width: 46px;} diff --git a/module/program/js/kanban.ui.js b/module/program/js/kanban.ui.js new file mode 100644 index 0000000000..7c7bc9fa3b --- /dev/null +++ b/module/program/js/kanban.ui.js @@ -0,0 +1,37 @@ +window.getCol = function(col) +{ + if(col.cards) col.subtitle = {html: "" + col.cards + ""}; +} + +window.itemRender = function(info) +{ + info.item.className.push('card-item-' + (info.item.status == 'doing' && info.item.delay ? 'delay' : info.item.status)); +} + +window.getItem = function(info) +{ + if(info.item.delay) + { + info.item.suffix = delayed; + info.item.suffixClass = 'label danger rounded-xl' + (info.item.status == 'doing' ? ' mr-8' : ''); + } + if(info.item.status == 'doing') info.item.prefix = {component: 'ProgressCircle', props: {percent: info.item.progress, size: 24}}; + if(info.item.type == 'doingExecutions' && privs.canViewExecution) + { + info.item.titleUrl = $.createLink('execution', 'task', `id=${info.item.id}`); + } + else if(info.item.type == 'doingProjects' && privs.canViewProject) + { + info.item.titleUrl = $.createLink('project', 'index', `id=${info.item.id}`); + } + else if(info.item.type == 'unexpiredPlans' && privs.canViewPlan) + { + info.item.titleUrl = $.createLink('productplan', 'view', `id=${info.item.id}`); + } + else if(info.item.type == 'normalReleases' && privs.canViewRelease) + { + info.item.titleUrl = $.createLink('release', 'view', `id=${info.item.id}`); + if(info.item.marker == '1') info.item.suffix = {html: ''}; + } + info.item.titleAttrs = {'class': 'text-black clip', 'title' : info.item.title}; +} diff --git a/module/program/model.php b/module/program/model.php index 6b4fa9c3e0..e560fe5a3a 100644 --- a/module/program/model.php +++ b/module/program/model.php @@ -275,45 +275,6 @@ class programModel extends model ->fetchAll('id'); } - /** - * 获取看板数据。 - * Get kanban group data. - * - * @access public - * @return array - */ - public function getKanbanGroup(): array - { - $programs = $this->getTopPairs('noclosed'); - - /* Group data by product. */ - list($productGroup, $planGroup, $releaseGroup, $projectGroup, $doingExecutions) = $this->getKanbanStatisticData($programs); - $productGroup = $this->processProductsForKanban($productGroup, $planGroup, $releaseGroup, $projectGroup, $doingExecutions); - - /* Group data by program. */ - $kanbanGroup = array(); - $kanbanGroup['my'] = array(); - $kanbanGroup['others'] = array(); - $involvedPrograms = $this->getInvolvedPrograms($this->app->user->account); - foreach($programs as $programID => $programName) - { - $programGroup = new stdclass(); - $programGroup->name = $programName; - $programGroup->products = zget($productGroup, $programID, array()); - - if(in_array($programID, $involvedPrograms)) - { - $kanbanGroup['my'][] = $programGroup; - } - else - { - $kanbanGroup['others'][] = $programGroup; - } - } - - return $kanbanGroup; - } - /** * 获取看板统计的相关数据。 * Get Kanban statistics data. diff --git a/module/program/test/model/getkanbangroup.php b/module/program/test/model/getkanbangroup.php deleted file mode 100755 index 9a2bec75d0..0000000000 --- a/module/program/test/model/getkanbangroup.php +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env php -id->range('1-3'); -$program->name->range('1-3')->prefix('项目集'); -$program->type->range('program'); -$program->path->range('1-3')->prefix(',')->postfix(','); -$program->grade->range('1'); -$program->status->range('wait'); -$program->openedBy->range('admin,test1'); -$program->begin->range('20220112 000000:0')->type('timestamp')->format('YYYY-MM-DD'); -$program->end->range('20220212 000000:0')->type('timestamp')->format('YYYY-MM-DD'); -$program->gen(3); - -zdTable('stakeholder')->gen(0); -zdTable('product')->gen(0); -zdTable('task')->gen(0); -zdTable('projectproduct')->gen(0); -zdTable('productplan')->gen(0); -zdTable('release')->gen(0); -zdTable('team')->gen(0); -zdTable('user')->gen(5); -su('admin'); - -global $app; -$app->rawModule = 'program'; - -$programTester = new programTest(); -$kanbanGroup = $programTester->getKanbanGroupTest(); - -r(count($kanbanGroup['my'])) && p('') && e('2'); //查看当前用户负责的项目集看板数量 -r(count($kanbanGroup['others'])) && p('') && e('1'); //查看当前用户其他的项目集看板数量 -r($kanbanGroup['my']) && p('0:name') && e('项目集1'); //查看当前用户负责的项目集看板详情 -r($kanbanGroup['others']) && p('0:name') && e('项目集2'); //查看当前用户其他的项目集看板详情 diff --git a/module/program/test/program.class.php b/module/program/test/program.class.php index 096beec678..46872da3e7 100644 --- a/module/program/test/program.class.php +++ b/module/program/test/program.class.php @@ -270,17 +270,6 @@ class programTest return $this->program->getInvolvedPrograms($account); } - /** - * Get kanban group. - * - * @access public - * @return array - */ - public function getKanbanGroupTest() - { - return $this->program->getKanbanGroup(); - } - /** * Get team member pairs . * diff --git a/module/program/ui/kanban.html.php b/module/program/ui/kanban.html.php new file mode 100644 index 0000000000..531125b981 --- /dev/null +++ b/module/program/ui/kanban.html.php @@ -0,0 +1,71 @@ + + * @package project + * @link https://www.zentao.net + */ +namespace zin; + +$privs = array +( + 'canViewExecution' => common::hasPriv('execution', 'task'), + 'canViewProject' => common::hasPriv('project', 'index'), + 'canViewRelease' => common::hasPriv('release', 'view'), + 'canViewPlan' => common::hasPriv('productplan', 'view'), +); + +foreach($kanbanList as $current => $region) +{ + foreach($region['items'] as $index => $group) + { + $group['draggable'] = true; + $group['colWidth'] = 'auto'; + $group['maxLaneHeight'] = '300px'; + $group['getCol'] = jsRaw('window.getCol'); + $group['getItem'] = jsRaw('window.getItem'); + $group['itemRender'] = jsRaw('window.itemRender'); + $group['canDrop'] = jsRaw('window.canDrop'); + $group['onDrop'] = jsRaw('window.onDrop'); + $kanbanList[$current]['items'][$index] = $group; + } +} + +jsVar('privs', $privs); +jsVar('delayed', $lang->project->statusList['delay']); + +featureBar +( + li + ( + setClass('nav-item item'), + a + ( + $browseType == 'my' ? setClass('active') : null, + set::href(createLink('program', 'kanban', "browseType=my")), + $lang->program->kanban->typeList['my'] + ) + ), + li + ( + setClass('nav-item item'), + set::active($browseType == 'other'), + a + ( + $browseType == 'other' ? setClass('active') : null, + set::href(createLink('program', 'kanban', "browseType=other")), + $lang->program->kanban->typeList['others'] + ) + ), +); +zui::kanbanList +( + set::key('kanban'), + set::items($kanbanList), + set::height('calc(100vh - 120px)') +); + +render(); diff --git a/module/program/zen.php b/module/program/zen.php index 5808403f80..097673a379 100644 --- a/module/program/zen.php +++ b/module/program/zen.php @@ -162,6 +162,147 @@ class programZen extends program return $this->loadModel('user')->getListByAccounts($accounts, 'account'); } + /** + * 获取项目集看板。 + * Get kanban list for product. + * + * @param string $browseType + * @access protected + * @return array + */ + protected function getKanbanList(string $browseType = 'my'): array + { + $kanbanList = array(); + + $programPairs = array(0 => $this->lang->project->noProgram) + $this->program->getPairs(true, 'order_asc'); + $programList = $this->getProgramList4Kanban($browseType); + + foreach($programList as $programID => $productList) + { + $region = array(); + + $heading = new stdclass(); + $heading->title = zget($programPairs, $programID, $programID); + + $region['key'] = $programID; + $region['id'] = $programID; + $region['heading'] = $heading; + + $lanes = array(); + $items = array(); + $columnCards = array(); + foreach($productList as $laneKey => $laneData) + { + $lanes[] = array('name' => $laneKey, 'title' => $laneData->name); + $columns = array(); + + foreach(array('unexpiredPlans', 'waitingProjects', 'doing', 'doingProjects', 'doingExecutions', 'normalReleases') as $columnKey) + { + $colTitle = $columnKey == 'doing' ? $this->lang->program->statusList['doing'] : $this->lang->program->kanban->{$columnKey}; + $column = array('name' => $columnKey, 'title' => $colTitle); + if($columnKey == 'doingExecutions' || $columnKey == 'doingProjects') $column['parentName'] = 'doing'; + $columns[] = $column; + + $cardList = !empty($laneData->{$columnKey}) ? $laneData->{$columnKey} : array(); + foreach($cardList as $card) + { + $items[$laneKey][$columnKey][] = array('id' => $card->id, 'name' => $card->id, 'title' => isset($card->name) ? $card->name : $card->title, 'status' => isset($card->status) ? $card->status : '', 'type' => $columnKey, 'delay' => !empty($card->delay) ? $card->delay : 0, 'progress' => isset($card->progress) ? $card->progress : 0, 'marker' => isset($card->marker) ? $card->marker : 0); + + if(!isset($columnCards[$columnKey])) $columnCards[$columnKey] = 0; + $columnCards[$columnKey] ++; + + if($columnKey == 'doingProjects') + { + if(!empty($latestExecutions[$card->id])) + { + $execution = $latestExecutions[$card->id]; + $items[$laneKey]['doingExecutions'][] = array('id' => $execution->id, 'name' => $execution->id, 'title' => $execution->name, 'status' => $execution->status, 'type' => 'doingExecution', 'delay' => !empty($execution->delay) ? $execution->delay : 0, 'progress' => $execution->progress); + + if(!isset($columnCards['doingExecutions'])) $columnCards['doingExecutions'] = 0; + $columnCards['doingExecutions'] ++; + } + } + } + } + } + + foreach($columns as $key => $column) $columns[$key]['cards'] = !empty($columnCards[$column['name']]) ? $columnCards[$column['name']] : 0; + $groupData['key'] = $programID; + $groupData['data']['lanes'] = $lanes; + $groupData['data']['cols'] = $columns; + $groupData['data']['items'] = $items; + + $region['items'] = array($groupData); + $kanbanList[] = $region; + } + + return $kanbanList; + } + + /** + * 获取项目集看板的数据。 + * Get kanban list data for product. + * + * @param string $browseType + * @access protected + * @return array + */ + protected function getProgramList4Kanban($browseType = 'my'): array + { + $programs = $this->program->getTopPairs('noclosed'); + $involvedPrograms = $this->program->getInvolvedPrograms($this->app->user->account); + foreach($programs as $programID => $programName) + { + if($browseType == 'my') + { + if(!in_array($programID, $involvedPrograms)) unset($programs[$programID]); + } + else + { + if(in_array($programID, $involvedPrograms)) unset($programs[$programID]); + } + } + + list($productGroup, $planGroup, $releaseGroup, $projectGroup, $doingExecutions) = $this->program->getKanbanStatisticData($programs); + + $programList = array(); + $today = helper::today(); + foreach($productGroup as $programID => $productList) + { + foreach($productList as $product) + { + $product->unexpiredPlans = isset($planGroup[$product->id]) ? $planGroup[$product->id] : array(); + + if(isset($projectGroup[$product->id])) + { + foreach($projectGroup[$product->id] as $project) + { + if(helper::diffDate($today, $project->end) > 0) $project->delay = 1; + if($project->status == 'wait') + { + $product->waitingProjects[$project->id] = $project; + } + elseif($project->status == 'doing') + { + $product->doingProjects[$project->id] = $project; + if(isset($doingExecutions[$project->id])) + { + $doingExecution = $doingExecutions[$project->id]; + if(helper::diffDate($today, $doingExecution->end) > 0) $doingExecution->delay = 1; + $product->doingExecutions[$doingExecution->id] = $doingExecution; + } + } + } + } + + $product->normalReleases = isset($releaseGroup[$product->id]) ? $releaseGroup[$product->id] : array(); + $programList[$programID][$product->id] = $product; + } + } + + return $programList; + } + /** * 构造1.5级导航的数据。 * Build the data of 1.5 level navigation.