Merge branch 'sprint/168' into 'sprint/168_dingna_task_43401'

# Conflicts:
#   module/execution/control.php
#   module/execution/view/kanban.html.php
#   module/kanban/lang/zh-cn.php
#   module/kanban/model.php
This commit is contained in:
李玉春
2021-10-29 05:50:12 +00:00
37 changed files with 870 additions and 677 deletions
+48 -48
View File
@@ -14,53 +14,53 @@ $lang->api->common = 'API';
$lang->api->getModel = 'Super Model API';
$lang->api->sql = 'SQL Query API';
$lang->api->index = 'Api Doc Home';
$lang->api->editLib = 'Edit Api Doc';
$lang->api->releases = 'Releases';
$lang->api->index = 'Home';
$lang->api->editLib = 'Edit';
$lang->api->releases = 'Release';
$lang->api->deleteRelease = 'Delete Release';
$lang->api->deleteLib = 'Delete Api Doc';
$lang->api->deleteLib = 'Delete API Doc';
$lang->api->createRelease = 'Publish';
$lang->api->createLib = 'Create Api Library';
$lang->api->createApi = 'Create Api';
$lang->api->createLib = 'Create API Library';
$lang->api->createApi = 'Create API Document';
$lang->api->createAB = 'Create';
$lang->api->edit = 'Edit';
$lang->api->delete = 'Delete';
$lang->api->position = 'Positions';
$lang->api->position = 'Position';
$lang->api->startLine = "%s,%s";
$lang->api->desc = 'Description';
$lang->api->debug = 'Debug';
$lang->api->submit = 'Submit';
$lang->api->url = 'Url';
$lang->api->url = 'URL';
$lang->api->result = 'Result';
$lang->api->status = 'Status';
$lang->api->data = 'Content';
$lang->api->noParam = 'Get debugging does not require input parameters,';
$lang->api->noModule = 'There is no directory under the interface library. Please maintain the directory first';
$lang->api->noModule = 'No directory in the API library. Please add the directory first';
$lang->api->post = 'Please refer to the page form for post debugging';
$lang->api->noUniqueName = 'Api library name already exists。';
$lang->api->noUniqueVersion = 'Version already exists。';
$lang->api->noUniqueName = 'The API library name exists.';
$lang->api->noUniqueVersion = 'The version exists.';
$lang->api->version = 'Version';
$lang->api->createStruct = 'Create Data Structure';
$lang->api->editStruct = 'Edit Data Structure';
$lang->api->deleteStruct = 'Delete Data Structure';
$lang->api->create = 'Ceate Doc';
$lang->api->title = 'Interface Name';
$lang->api->pageTitle = 'Api Library';
$lang->api->create = 'Create API';
$lang->api->title = 'Name';
$lang->api->pageTitle = 'API Library';
$lang->api->module = 'Directory';
$lang->api->apiDoc = 'Interface';
$lang->api->apiDoc = 'API';
$lang->api->manageType = 'Manage Directory';
$lang->api->managePublish = 'Manage Version';
$lang->api->doing = 'Doing';
$lang->api->done = 'Done';
$lang->api->basicInfo = 'Essential Information';
$lang->api->apiDesc = 'Interface Description';
$lang->api->confirmDelete = "Are you sure to delete this interface?";
$lang->api->confirmDeleteLib = "Are you sure to delete this interface library?";
$lang->api->basicInfo = 'Basic Information';
$lang->api->apiDesc = 'Description';
$lang->api->confirmDelete = "Do you want to delete this API?";
$lang->api->confirmDeleteLib = "Do you want to delete this interface library?";
$lang->api->filterStruct = "use struct";
$lang->api->defaultVersion = "Current Version";
/* Common access control lang. */
$lang->api->whiteList = 'White list';
$lang->api->whiteList = 'Whitelist';
$lang->api->aclList['open'] = 'Open';
$lang->api->aclList['private'] = 'Private';
$lang->api->aclList['custom'] = 'Custom';
@@ -68,9 +68,9 @@ $lang->api->group = 'Group';
$lang->api->user = 'User';
$lang->api->noticeAcl = array(
'open' => 'Users who can access the api library which the library belongs can access it.',
'custom' => 'Users in the whiltelist can access it.',
'private' => 'Only the one who created it can access it.',
'open' => 'Users who can access the API library can access it.',
'custom' => 'Users on the whiltelist can access it.',
'private' => 'Only the one who creates it can access it.',
);
/* fields of struct */
@@ -81,7 +81,7 @@ $lang->struct->field = 'Field';
$lang->struct->paramsType = 'Type';
$lang->struct->required = 'Require';
$lang->struct->desc = 'Description';
$lang->struct->descPlaceholder = 'Parameter description';
$lang->struct->descPlaceholder = 'Parameter Description';
$lang->struct->action = 'Action';
$lang->struct->addSubField = 'Add Subfield';
@@ -94,36 +94,36 @@ $lang->struct->typeOptions = array(
/* fields of form */
$lang->api->struct = 'Data Structure';
$lang->api->structName = 'Structure Name';
$lang->api->structName = 'Name';
$lang->api->structType = 'Type';
$lang->api->structAttr = 'Attribute';
$lang->api->structAddedBy = 'Creator';
$lang->api->structAddedDate = 'Created Time';
$lang->api->name = 'Interface Library Name';
$lang->api->baseUrl = 'Base Url';
$lang->api->baseUrlDesc = 'Site or path. for example, api.zentao.com or /v1';
$lang->api->structAddedBy = 'CreatedBy';
$lang->api->structAddedDate = 'Created';
$lang->api->name = 'API Library Name';
$lang->api->baseUrl = 'Base URL';
$lang->api->baseUrlDesc = 'Site or path, e.g., api.zentao.com or /v1.';
$lang->api->desc = 'Description';
$lang->api->control = 'Access Control';
$lang->api->noLib = 'There is no interface library at present。';
$lang->api->noApi = 'There is no interface for the time being。';
$lang->api->noStruct = 'There is no structure for the time being。';
$lang->api->lib = 'Interface Library';
$lang->api->apiList = 'Interface List';
$lang->api->formTitle = 'Interface Name';
$lang->api->noLib = 'No API library yet.';
$lang->api->noApi = 'No API yet.';
$lang->api->noStruct = 'No API yet.';
$lang->api->lib = 'API Library';
$lang->api->apiList = 'API List';
$lang->api->formTitle = 'API Name';
$lang->api->path = 'Request Path';
$lang->api->protocol = 'Request Protocol';
$lang->api->method = 'Request Method';
$lang->api->requestType = 'Request Type';
$lang->api->status = 'Development Status';
$lang->api->owner = 'Person In Charge';
$lang->api->paramsExample = 'Response Example';
$lang->api->protocol = 'Protocol';
$lang->api->method = 'Method';
$lang->api->requestType = 'Type';
$lang->api->status = 'Status';
$lang->api->owner = 'Owner';
$lang->api->paramsExample = 'Request Example';
$lang->api->header = 'Request Header';
$lang->api->query = 'Request Parameters';
$lang->api->query = 'Parameter';
$lang->api->params = 'Request Body';
$lang->api->response = 'Response';
$lang->api->responseExample = 'Response Example';
$lang->api->res = new stdClass();
$lang->api->res->name = '名称';
$lang->api->res->name = 'Name';
$lang->api->res->desc = 'Description';
$lang->api->res->type = 'Type';
$lang->api->req = new stdClass();
@@ -195,14 +195,14 @@ $lang->api->allParamsTypeOptions = array_merge($lang->api->paramsTypeOptions, $l
$lang->api->requiredOptions = array(0 => 'No', 1 => 'Yes');
$lang->doclib = new stdclass();
$lang->doclib->name = 'Interface Library Name';
$lang->doclib->name = 'API Library Name';
$lang->apistruct = new stdClass();
$lang->apistruct->name = 'Structure Name';
$lang->apistruct->name = 'Name';
$lang->api_lib_release = new stdClass();
$lang->api_lib_release->version = 'Version';
$lang->api->error = new stdclass();
$lang->api->error->onlySelect = 'SQL interface only allow SELECT query.';
$lang->api->error->disabled = 'For security reasons, this feature is disabled. You can go to the config directory and modify the configuration item %s to open this function.';
$lang->api->error->onlySelect = 'SQL API only allows SELECT query.';
$lang->api->error->disabled = 'For security reasons, this feature is disabled. Go to the config directory and modify the configuration item %s to enable it.';
+1
View File
@@ -9,6 +9,7 @@ $lang->project = new stdclass();
$lang->scrum = new stdclass();
$lang->waterfall = new stdclass();
$lang->execution = new stdclass();
$lang->kanban = new stdclass();
$lang->story = new stdclass();
$lang->release = new stdclass();
$lang->branch = new stdclass();
+35 -34
View File
@@ -47,6 +47,7 @@ $lang->designedByAIUX = "<a href='https://api.zentao.net/goto.php?item=aiux' cla
$lang->reset = 'Reset';
$lang->cancel = 'Cancel';
$lang->refresh = 'Refresh';
$lang->create = 'Create';
$lang->edit = 'Edit';
$lang->delete = 'Delete';
$lang->close = 'Close';
@@ -177,38 +178,38 @@ $lang->score->shortCommon = 'Score';
$lang->testreport->shortCommon = 'Report';
$lang->qa->shortCommon = 'QA';
$lang->dashboard = 'Dashboard';
$lang->contribute = 'Contribute';
$lang->dynamic = 'Dynamic';
$lang->contact = 'Contacts';
$lang->whitelist = 'Whitelist';
$lang->roadmap = 'Roadmap';
$lang->track = 'Track';
$lang->settings = 'Settings';
$lang->overview = 'Overview';
$lang->module = 'Module';
$lang->priv = 'Privilege';
$lang->design = 'Design';
$lang->other = 'Other';
$lang->estimation = 'Estimation';
$lang->issue = 'Issue';
$lang->risk = 'Risk';
$lang->measure = 'Report';
$lang->treeView = 'Tree View';
$lang->groupView = 'Group View';
$lang->kanban = 'Kanban';
$lang->burn = 'Burndown';
$lang->view = 'View';
$lang->intro = 'Introduction';
$lang->indexPage = 'Index';
$lang->model = 'Model';
$lang->redev = 'Develop';
$lang->browser = 'Browser';
$lang->db = 'Database';
$lang->editor = 'Editor';
$lang->timezone = 'Timezone';
$lang->security = 'Security';
$lang->calendar = 'Calendar';
$lang->dashboard = 'Dashboard';
$lang->contribute = 'Contribute';
$lang->dynamic = 'Dynamic';
$lang->contact = 'Contacts';
$lang->whitelist = 'Whitelist';
$lang->roadmap = 'Roadmap';
$lang->track = 'Track';
$lang->settings = 'Settings';
$lang->overview = 'Overview';
$lang->module = 'Module';
$lang->priv = 'Privilege';
$lang->design = 'Design';
$lang->other = 'Other';
$lang->estimation = 'Estimation';
$lang->issue = 'Issue';
$lang->risk = 'Risk';
$lang->measure = 'Report';
$lang->treeView = 'Tree View';
$lang->groupView = 'Group View';
$lang->executionKanban = 'Kanban';
$lang->burn = 'Burndown';
$lang->view = 'View';
$lang->intro = 'Introduction';
$lang->indexPage = 'Index';
$lang->model = 'Model';
$lang->redev = 'Develop';
$lang->browser = 'Browser';
$lang->db = 'Database';
$lang->editor = 'Editor';
$lang->timezone = 'Timezone';
$lang->security = 'Security';
$lang->calendar = 'Calendar';
$lang->my->work = 'Work';
@@ -227,8 +228,8 @@ $lang->doc->api = 'API';
$lang->doc->execution = $lang->execution->common;
$lang->doc->custom = 'Custom';
$lang->doc->wiki = 'WIKI';
$lang->doc->apiDoc = 'Doc';
$lang->doc->apiStruct = 'Struct';
$lang->doc->apiDoc = 'API Docuemnt';
$lang->doc->apiStruct = 'Data Structure';
$lang->product->list = $lang->productCommon . ' List';
$lang->product->kanban = $lang->productCommon . ' Kanban';
+2 -1
View File
@@ -246,7 +246,7 @@ if($config->systemMode == 'new') $lang->execution->homeMenu->executionkanban = a
$lang->execution->menu = new stdclass();
$lang->execution->menu->task = array('link' => "{$lang->task->common}|execution|task|executionID=%s", 'subModule' => 'task,tree', 'alias' => 'importtask,importbug');
$lang->execution->menu->kanban = array('link' => "$lang->kanban|execution|kanban|executionID=%s");
$lang->execution->menu->kanban = array('link' => "$lang->executionKanban|execution|kanban|executionID=%s");
$lang->execution->menu->burn = array('link' => "$lang->burn|execution|burn|executionID=%s");
$lang->execution->menu->view = array('link' => "$lang->view|execution|grouptask|executionID=%s", 'alias' => 'grouptask,tree,taskeffort,gantt,calendar,relation,maintainrelation');
$lang->execution->menu->story = array('link' => "$lang->SRCommon|execution|story|executionID=%s", 'subModule' => 'story', 'alias' => 'batchcreate,linkstory,storykanban');
@@ -521,6 +521,7 @@ $lang->navGroup->build = 'project';
$lang->navGroup->measrecord = 'project';
$lang->navGroup->milestone = 'project';
$lang->navGroup->kanban = 'execution';
$lang->navGroup->execution = 'execution';
$lang->navGroup->task = 'execution';
$lang->navGroup->build = 'execution';
+33 -32
View File
@@ -47,6 +47,7 @@ $lang->designedByAIUX = "<a href='https://api.zentao.net/goto.php?item=aiux' cla
$lang->reset = '重填';
$lang->cancel = '取消';
$lang->refresh = '刷新';
$lang->create = '新建';
$lang->edit = '编辑';
$lang->delete = '删除';
$lang->close = '关闭';
@@ -177,38 +178,38 @@ $lang->score->shortCommon = '积分';
$lang->testreport->shortCommon = '报告';
$lang->qa->shortCommon = 'QA';
$lang->dashboard = '仪表盘';
$lang->contribute = '贡献';
$lang->dynamic = '动态';
$lang->contact = '联系人';
$lang->whitelist = '白名单';
$lang->roadmap = '路线图';
$lang->track = '矩阵';
$lang->settings = '设置';
$lang->overview = '概况';
$lang->module = '模块';
$lang->priv = '权限';
$lang->design = '设计';
$lang->other = '其他';
$lang->estimation = '估算';
$lang->issue = '问题';
$lang->risk = '风险';
$lang->measure = '度量';
$lang->treeView = '树状图';
$lang->groupView = '分组视图';
$lang->kanban = '看板';
$lang->burn = '燃尽图';
$lang->view = '视图';
$lang->intro = '介绍';
$lang->indexPage = '首页';
$lang->model = '模型';
$lang->redev = '二次开发';
$lang->browser = '浏览器';
$lang->db = '数据库';
$lang->editor = '编辑器';
$lang->timezone = '时区';
$lang->security = '安全';
$lang->calendar = '日程';
$lang->dashboard = '仪表盘';
$lang->contribute = '贡献';
$lang->dynamic = '动态';
$lang->contact = '联系人';
$lang->whitelist = '白名单';
$lang->roadmap = '路线图';
$lang->track = '矩阵';
$lang->settings = '设置';
$lang->overview = '概况';
$lang->module = '模块';
$lang->priv = '权限';
$lang->design = '设计';
$lang->other = '其他';
$lang->estimation = '估算';
$lang->issue = '问题';
$lang->risk = '风险';
$lang->measure = '度量';
$lang->treeView = '树状图';
$lang->groupView = '分组视图';
$lang->executionKanban = '看板';
$lang->burn = '燃尽图';
$lang->view = '视图';
$lang->intro = '介绍';
$lang->indexPage = '首页';
$lang->model = '模型';
$lang->redev = '二次开发';
$lang->browser = '浏览器';
$lang->db = '数据库';
$lang->editor = '编辑器';
$lang->timezone = '时区';
$lang->security = '安全';
$lang->calendar = '日程';
$lang->my->work = '待处理';
+31 -31
View File
@@ -177,37 +177,37 @@ $lang->score->shortCommon = '積分';
$lang->testreport->shortCommon = '報告';
$lang->qa->shortCommon = 'QA';
$lang->dashboard = '儀表盤';
$lang->contribute = '貢獻';
$lang->dynamic = '動態';
$lang->contact = '聯繫人';
$lang->whitelist = '白名單';
$lang->roadmap = '路線圖';
$lang->track = '矩陣';
$lang->settings = '設置';
$lang->overview = '概況';
$lang->module = '模組';
$lang->priv = '權限';
$lang->design = '設計';
$lang->other = '其他';
$lang->estimation = '估算';
$lang->issue = '問題';
$lang->risk = '風險';
$lang->measure = '度量';
$lang->treeView = '樹狀圖';
$lang->groupView = '分組視圖';
$lang->kanban = '看板';
$lang->burn = '燃盡圖';
$lang->view = '視圖';
$lang->intro = '介紹';
$lang->indexPage = '首頁';
$lang->model = '模型';
$lang->redev = '二次開發';
$lang->browser = '瀏覽器';
$lang->db = '資料庫';
$lang->editor = '編輯器';
$lang->timezone = '時區';
$lang->security = '安全';
$lang->dashboard = '儀表盤';
$lang->contribute = '貢獻';
$lang->dynamic = '動態';
$lang->contact = '聯繫人';
$lang->whitelist = '白名單';
$lang->roadmap = '路線圖';
$lang->track = '矩陣';
$lang->settings = '設置';
$lang->overview = '概況';
$lang->module = '模組';
$lang->priv = '權限';
$lang->design = '設計';
$lang->other = '其他';
$lang->estimation = '估算';
$lang->issue = '問題';
$lang->risk = '風險';
$lang->measure = '度量';
$lang->treeView = '樹狀圖';
$lang->groupView = '分組視圖';
$lang->executionKanban = '看板';
$lang->burn = '燃盡圖';
$lang->view = '視圖';
$lang->intro = '介紹';
$lang->indexPage = '首頁';
$lang->model = '模型';
$lang->redev = '二次開發';
$lang->browser = '瀏覽器';
$lang->db = '資料庫';
$lang->editor = '編輯器';
$lang->timezone = '時區';
$lang->security = '安全';
$lang->calendar = '日程';
$lang->my->work = '待處理';
+1 -1
View File
@@ -43,7 +43,7 @@
#kanbanList .kanban-header-col[data-type="doingProject"] + .kanban-header-col[data-type="doingExecution"] {padding: 38px 10px 0;}
#kanbanList .kanban-header-col[data-type="doingProject"]:after {content: attr(data-span-text); display: block; position: absolute; z-index: 10; left: 0; right: -100%; right: calc(-100% - 2px); top: 0; line-height: 36px; text-align: center; font-weight: bold; border-bottom: 2px solid #fff; background-color: #ededed;}
#kanbanList .kanban-col[data-type="unclosedProduct"] .kanban-lane-items {height: 100%; display: flex; flex-direction: column; justify-content: center;}
#kanbanList .kanban-col[data-type="unclosedProduct"] .kanban-item {background-color: transparent; border: none; padding: 0; text-align: center;}
#kanbanList .kanban-col[data-type="unclosedProduct"] .kanban-item {background-color: transparent; border: none; padding: 0; text-align: center; box-shadow: none!important;}
#kanbanList .kanban-col[data-type="unclosedProduct"] .kanban-item:hover {box-shadow: none;}
#kanbanList .kanban-col[data-type="unclosedProduct"] .kanban-item > .title {white-space: normal;}
#kanbanList .kanban-col[data-type="normalRelease"] .kanban-item > .title {display: flex; flex-direction: row; flex-wrap: nowrap; align-items: center; height: 38px;}
+3 -3
View File
@@ -8,11 +8,11 @@ $lang->datatable->reset = 'Reset';
$lang->datatable->custom = 'Customize Columns';
$lang->datatable->customTip = 'Check columns to display and drag columns to order.';
$lang->datatable->switchToTable = 'Switch to Common Table';
$lang->datatable->switchToDatatable = 'Switch to Advanced Table';
$lang->datatable->switchToTable = 'Common Table';
$lang->datatable->switchToDatatable = 'Advanced Table';
$lang->datatable->required = 'Required';
$lang->datatable->confirmReset = 'Do you want to restore the default settings?';
$lang->datatable->setGlobal = 'Set as Global';
$lang->datatable->setGlobal = 'Set Global';
$lang->datatable->resetGlobal = 'Global Reset';
$lang->datatable->branch = 'Branch';
+8 -8
View File
@@ -29,12 +29,12 @@ $lang->doc->files = 'Files';
$lang->doc->addedBy = 'Author';
$lang->doc->addedDate = 'Added';
$lang->doc->editedBy = 'UpdatedBy';
$lang->doc->editedDate = 'UpdatedDate';
$lang->doc->editedDate = 'Updated';
$lang->doc->version = 'Version';
$lang->doc->basicInfo = 'Basic Info';
$lang->doc->basicInfo = 'Basic Information';
$lang->doc->deleted = 'Deleted';
$lang->doc->fileObject = 'Dependent Item';
$lang->doc->whiteList = 'White List';
$lang->doc->whiteList = 'Whitelist';
$lang->doc->contentType = 'Format';
$lang->doc->separator = "<i class='icon-angle-right'></i>";
$lang->doc->fileTitle = 'File Name';
@@ -51,7 +51,7 @@ $lang->doc->item = ' Items';
$lang->doc->num = 'Documents';
$lang->doc->searchResult = 'Search Result';
$lang->doc->mailto = 'Mailto';
$lang->doc->noModule = 'No document in this lib, please create it';
$lang->doc->noModule = 'No document in this library. Create one.';
$lang->doc->noChapter = 'No chapters or articles in this book. Please add chapters and articles.';
$lang->doc->views = 'Views';
$lang->doc->draft = 'Draft';
@@ -73,7 +73,7 @@ $lang->doc->todayEdited = 'Updated Today';
$lang->doc->pastEdited = 'Total Updated';
$lang->doc->myDoc = 'My Documents';
$lang->doc->myCollection = 'My Favorites';
$lang->doc->tableContents = 'Catalog';
$lang->doc->tableContents = 'Directory';
/* Methods list */
$lang->doc->index = 'Document Home';
@@ -91,12 +91,12 @@ $lang->doc->manageType = 'Manage Category';
$lang->doc->editType = 'Edit';
$lang->doc->deleteType = 'Delete';
$lang->doc->addType = 'Add';
$lang->doc->childType = 'Catalog';
$lang->doc->catalogName = 'Catalog Name';
$lang->doc->childType = 'Directory';
$lang->doc->catalogName = 'Name';
$lang->doc->collect = 'Add Favorite';
$lang->doc->cancelCollection = 'Remove Favorite';
$lang->doc->deleteFile = 'Delete File';
$lang->doc->menuTitle = 'Menu';
$lang->doc->menuTitle = 'Direcotory';
$lang->doc->collectAction = 'Add Favorite';
+4 -4
View File
@@ -1825,7 +1825,7 @@ class execution extends control
* Kanban.
*
* @param int $executionID
* @param string $type
* @param string $browseType story|bug|task|all
* @param string $orderBy
* @param string $groupBy
* @access public
@@ -1841,11 +1841,11 @@ class execution extends control
/* Compatibility IE8. */
if(strpos($this->server->http_user_agent, 'MSIE 8.0') !== false) header("X-UA-Compatible: IE=EmulateIE7");
$kanbanGroup = $this->loadModel('kanban')->getExecutionKanban($executionID, $type, $groupBy);
$kanbanGroup = $this->loadModel('kanban')->getExecutionKanban($executionID, $browseType);
if(empty($kanbanGroup))
{
$this->kanban->createLanes($executionID, $type, $groupBy);
$kanbanGroup = $this->kanban->getExecutionKanban($executionID);
$this->kanban->createLanes($executionID);
$kanbanGroup = $this->kanban->getExecutionKanban($executionID, $browseType);
}
$this->execution->setMenu($executionID);
+17 -1
View File
@@ -1,14 +1,30 @@
#main > .container {max-width: 1960px!important; padding: 0 10px}
.kanban + .kanban {margin-top: 15px}
.kanban-item {position: relative;}
.kanban-item > .title { display: block;white-space: nowrap; overflow: hidden; text-overflow: ellipsis}
.kanban-item > .infos {position: relative; margin-top: 5px}
.kanban-item > .infos > .info + .info {margin-left: 10px}
.kanban-item > .infos > .info + .info {margin-left: 8px}
.kanban-item > .infos > .info-id,
.kanban-item > .infos > .info-deadline,
.kanban-item > .infos > .info-estimate {font-size: 12px; position: relative; top: 2px}
.kanban-item > .infos > .label-pri {min-width: 16px; line-height: 14px; height: 16px; padding: 0}
.kanban-item > .infos > .label-severity {transform: scale(.75)}
.kanban-item > .infos > .avatar {position: absolute; right: 0; top: 0}
.kanban-item > .actions {position: absolute; top: 4px; right: 4px; opacity: 0;}
.kanban-item:hover > .actions {opacity: 1;}
.kanban-item > .actions > a {display: block; float: left; width: 20px; height: 20px; line-height: 20px; text-align: center; border-radius: 4px; opacity: .7;}
.kanban-item > .actions > a:hover {background-color: rgba(0,0,0,.075); opacity: 1;}
.kanban-header-col > .actions {padding-top: 22px}
.kanban-header-parent-col .kanban-header-col > .actions {padding-top: 6px; padding-right: 2px;}
.kanban-header-col > .actions > a {display: block; float: left; width: 20px; height: 20px; line-height: 20px; text-align: center; border-radius: 4px; opacity: .7;}
.kanban-header-col > .actions > a:hover {background-color: rgba(0,0,0,.075); opacity: 1;}
.kanban-lane-name > .actions {position: absolute; top: 0; left: 0; right: 0; opacity: 0;}
.kanban-lane-name:hover > .actions {opacity: 1;}
.kanban-lane-name > .actions > a {display: block; width: 20px; height: 20px; line-height: 20px; text-align: center; opacity: .7; color: #fff;}
.kanban-lane-name > .actions > a:hover {background-color: rgba(0,0,0,.2); opacity: 1;}
.kanban-affixed .kanban-header-col > .title > .text,
.kanban-affixed .kanban-header-col > .title > .count {color: #fff!important;}
+312 -368
View File
@@ -1,371 +1,45 @@
/**
* Get story demo kanban data
* 获取“软件需求”看板演示数据
* @returns {Object} Kanban data
* @todo 该方法作为演示,应该在最终版本中移除
*/
function getStoryKanbanDemoData()
function changeView(view)
{
/* Define kanban columns 定义看板列 */
var columns =
[
/* 看板列定义数据结构:
id: 列 ID(确保在页面上唯一),
type: 类型,
name: 显示的名称,
color: 列名称颜色,
parentType: 所属的父级列类型
asParent: 是否作为父级列
cardType: 看板卡片类型,例如:'story'(需求)
limit: 最大数目,该列上允许展示的卡片最大数目,如果为 0 表示无限制 */
{id: 'story-blacklog', type: 'blacklog', name: 'Blacklog', color: '', limit: 0},
{id: 'story-ready', type: 'ready', name: '准备好', color: '', limit: 4},
{id: 'story-dev', type: 'dev', name: '开发', color: '', limit: 4, asParent: true},
{id: 'story-dev-doing', type: 'dev-doing', name: '进行中', color: '#126eed', limit: 2, parentType: 'dev'},
{id: 'story-dev-done', type: 'dev-done', name: '完成', color: '#2fab8e', limit: 2, parentType: 'dev'},
{id: 'story-test', type: 'test', name: '测试', color: '', limit: 4, asParent: true},
{id: 'story-test-doing', type: 'test-doing', name: '进行中', color: '#126eed', limit: 2, parentType: 'test'},
{id: 'story-test-done', type: 'test-done', name: '完成', color: '#2fab8e', limit: 2, parentType: 'test'},
{id: 'story-accepted', type: 'accepted', name: '已验收', color: '', limit: 0},
{id: 'story-published', type: 'published', name: '已发布', color: '', limit: 0},
];
/* Define cards in blacklog column 定义 Blacklog 列上的卡片 */
var blacklogColCards =
[
/* 需求数据结构:
id: ID,通常为需求 ID,
title: 名称,
order: 显示顺序,如果指定为 0,则按 ID 倒序排序,
pri: 优先级,
estimate: 预估时间,
assignedTo: 指派给,
deadline: 截止日期 */
{id: 10010, title: '第一条 Blacklog', order: 1, pri: 1, estimate: 2, assignedTo: 'admin', deadline: '2021-10-30'},
{id: 10011, title: '文档模块的实现', order: 2, pri: 2, estimate: 0, assignedTo: 'sunhao'},
{id: 10012, title: '实现软件需求看板视图中不同分组条件的看板展示方式', order: 3, pri: 3, estimate: 10, assignedTo: 'admin'},
{id: 10013, title: 'Blacklog 3', order: 4, pri: 0, estimate: 0, assignedTo: 'sunhao'},
{id: 10014, title: '实现泳道的更多操作菜单', order: 5, pri: 0, estimate: 0, assignedTo: 'sunhao'},
];
/* Define cards in ready column 定义 准备好 列上的卡片 */
var readyColCards =
[
/* 需求数据结构参见 blacklogColCards 定义 */
{id: 10020, title: '在看板列中实现创建任务功能', order: 10, pri: 1, estimate: 2, assignedTo: 'admin', deadline: '2021-10-30'},
{id: 10021, title: '实现泳道的上下移动', order: 11, pri: 2, estimate: 0, assignedTo: 'sunhao'},
{id: 10024, title: '实现泳道的更多操作菜单', order: 15, pri: 0, estimate: 0, assignedTo: 'sunhao'},
];
var devDoingColCards = /* Define cards in dev/doing column 定义 开发/进行中 列上的卡片 */
[
/* 需求数据结构参见 blacklogColCards 定义 */
{id: 10030, title: '实现看板的更多操作菜单', order: 10, pri: 3, estimate: 0, assignedTo: 'admin', deadline: '2021-10-30'},
{id: 10031, title: '实现卡片拖动效果功能', order: 11, pri: 2, estimate: 2, assignedTo: 'sunhao'},
];
var devDoneColCards = /* Define cards in dev/done column 定义 开发/完成 列上的卡片 */
[
/* 需求数据结构参见 blacklogColCards 定义 */
{id: 10040, title: '实现看板列卡片排序功能', order: 10, pri: 1, estimate: 4, assignedTo: 'admin', deadline: '2021-10-30'},
];
var testDoingColCards = /* Define cards in test/doing column 定义 测试/进行中 列上的卡片 */
[
/* 需求数据结构参见 blacklogColCards 定义 */
{id: 10050, title: '实现看板列的更多操作菜单', order: 10, pri: 3, estimate: 0, assignedTo: 'admin', deadline: '2021-10-30'},
{id: 10051, title: '实现Bug卡片的更多操作功能', order: 11, pri: 2, estimate: 2, assignedTo: 'sunhao'},
];
var testDoneColCards = /* Define cards in test/done column 定义 测试/完成 列上的卡片 */
[
/* 需求数据结构参见 blacklogColCards 定义 */
{id: 10060, title: '实现任务卡片的更多操作功能', order: 10, pri: 1, estimate: 4, assignedTo: 'admin', deadline: '2021-10-30'},
];
var acceptedColCards =
[
/* 需求数据结构参见 blacklogColCards 定义 */
{id: 10070, title: '实现执行看板的新建按钮组功能', order: 10, pri: 3, estimate: 12, assignedTo: 'admin', deadline: '2021-10-30'},
{id: 10071, title: '在看板列中实现提交Bug功能', order: 11, pri: 2, estimate: 2, assignedTo: 'sunhao'},
{id: 10072, title: '在看板列中实现需求的添加和关联功能', order: 10, pri: 3, estimate: 10, assignedTo: 'admin', deadline: '2021-10-30'},
{id: 10073, title: '实现任务看板视图中不同分组条件的看板展示方式', order: 11, pri: 2, estimate: 2, assignedTo: 'sunhao'},
];
var publishedColCards =
[
/* 需求数据结构参见 blacklogColCards 定义 */
{id: 10080, title: '实现任务看板视图中泳道分组下拉菜单功能', order: 10, pri: 3, estimate: 2, assignedTo: 'admin'},
];
/* Define kanban cards in lane 定义看板泳道内每一列的卡片,属性名为看板列类型,属性值为卡片列表 */
var cards =
{
/* 看板列类型: 卡片列表 */
blacklog: blacklogColCards,
ready: readyColCards,
'dev-doing': devDoingColCards,
'dev-done': devDoneColCards,
'test-doing': testDoingColCards,
'test-done': testDoneColCards,
accepted: acceptedColCards,
published: publishedColCards,
};
/* Define kanban lanes 定义看板泳道 */
var lanes =
[
/* 泳道数据结构:
id: 泳道 ID,确保页面上唯一,可以为数字或字符串,
name: 名称,
cards: 定义每列上的卡片
color: 泳道名称背景色
defaultCardType: 看板泳道上的卡片默认类型,例如:'story'(需求) */
{id: 'story', name: '软件需求', cards: cards, color: '#3dc6fc', defaultCardType: 'story'},
]
/* Return kanban data 返回看板数据 */
/* 看板数据结构:
id: ID,确保页面上唯一,可以为数字或字符串,
columns: 定义看板上的所有列,
lanes: 定义看板上的所有泳道
defaultCardType: 看板上的卡片默认类型,例如:'story'(需求) */
return kanbanGroup.story;
return {id: 'story', columns: columns, lanes: lanes, defaultCardType: 'story'};
var link = createLink('execution', 'kanban', "executionID=" + executionID + '&type=' + view);
location.href = link;
}
/**
* Get bug demo kanban data
* 获取“Bug”看板演示数据
* @returns {Object} Kanban data
* @todo 该方法作为演示,应该在最终版本中移除
*/
function getBugKanbanDemoData()
{
return kanbanGroup.bug;
/* Define kanban columns 定义看板列 */
var columns =
[
/* 看板列定义数据结构:
id: 列 ID(确保在页面上唯一),
type: 类型,
name: 显示的名称,
color: 列名称颜色,
parentType: 所属的父级列类型
asParent: 是否作为父级列
cardType: 看板卡片类型,例如:'story'(需求)
limit: 最大数目,该列上允许展示的卡片最大数目,如果为 0 表示无限制 */
{id: 'bug-wait', type: 'wait', name: '待确认', color: '', limit: 0},
{id: 'bug-confirmed', type: 'confirmed', name: '已确认', color: '', limit: 4},
{id: 'bug-resolving', type: 'resolving', name: '解决中', color: '', limit: 4, asParent: true},
{id: 'bug-resolving-doing', type: 'resolving-doing',name: '进行中', color: '#126eed', limit: 2, parentType: 'resolving'},
{id: 'bug-resolving-done', type: 'resolving-done', name: '完成', color: '#2fab8e', limit: 2, parentType: 'resolving'},
{id: 'bug-test', type: 'test', name: '测试', color: '', limit: 4, asParent: true},
{id: 'bug-test-doing', type: 'test-doing', name: '测试中', color: '#126eed', limit: 2, parentType: 'test'},
{id: 'bug-test-done', type: 'test-done', name: '测试完毕', color: '#2fab8e', limit: 2, parentType: 'test'},
{id: 'bug-closed', type: 'closed', name: '已关闭', color: '', limit: 0},
];
/* Define cards in wait column 定义 待确认 列上的卡片 */
var waitColCards =
[
/* bug 数据结构:
id: ID,通常为 Bug ID,
title: 名称,
order: 显示顺序,如果指定为 0,则按 ID 倒序排序,
pri: 优先级,
severity: 紧急程度,
assignedTo: 指派给 */
{id: 10010, title: '在看板列中实现创建任务功能', order: 10, pri: 1, severity: 2, assignedTo: 'admin'},
{id: 10011, title: '实现泳道的上下移动', order: 11, pri: 2, severity: 1, assignedTo: 'sunhao'},
{id: 10014, title: '实现泳道的更多操作菜单', order: 15, pri: 0, severity: 1, assignedTo: 'sunhao'},
];
/* Define cards in confirmed column 定义 已确认 列上的卡片 */
var confirmedColCards =
[
/* Bug 数据结构参见 waitColCards 定义 */
{id: 10020, title: '在看板列中实现创建任务功能', order: 10, pri: 1, severity: 2, assignedTo: 'admin'},
{id: 10021, title: '实现泳道的上下移动', order: 11, pri: 2, severity: 1, assignedTo: 'sunhao'},
{id: 10024, title: '实现泳道的更多操作菜单', order: 15, pri: 0, severity: 1, assignedTo: 'sunhao'},
];
var resolvingDoingColCards = /* Define cards in resolving/doing column 定义 开发/进行中 列上的卡片 */
[
/* Bug 数据结构参见 waitColCards 定义 */
{id: 10030, title: '实现看板的更多操作菜单', order: 10, pri: 3, severity: 1, assignedTo: 'admin'},
{id: 10031, title: '实现卡片拖动效果功能', order: 11, pri: 2, severity: 2, assignedTo: 'sunhao'},
];
var resolvingDoneColCards = /* Define cards in resolving/done column 定义 开发/完成 列上的卡片 */
[
/* Bug 数据结构参见 waitColCards 定义 */
{id: 10040, title: '实现看板列卡片排序功能', order: 10, pri: 1, severity: 4, assignedTo: 'admin'},
];
var testDoingColCards = /* Define cards in test/doing column 定义 测试/进行中 列上的卡片 */
[
/* Bug 数据结构参见 waitColCards 定义 */
{id: 10050, title: '实现看板列的更多操作菜单', order: 10, pri: 3, severity: 3, assignedTo: 'admin'},
{id: 10051, title: '实现Bug卡片的更多操作功能', order: 11, pri: 2, severity: 2, assignedTo: 'sunhao'},
];
var testDoneColCards = /* Define cards in test/done column 定义 测试/完成 列上的卡片 */
[
/* Bug 数据结构参见 waitColCards 定义 */
{id: 10060, title: '实现任务卡片的更多操作功能', order: 10, pri: 1, severity: 4, assignedTo: 'admin'},
];
var closedColCards =
[
/* Bug 数据结构参见 waitColCards 定义 */
{id: 10070, title: '实现执行看板的新建按钮组功能', order: 10, pri: 3, severity: 3, assignedTo: 'admin'},
{id: 10071, title: '在看板列中实现提交Bug功能', order: 11, pri: 2, severity: 2, assignedTo: 'sunhao'},
{id: 10072, title: '在看板列中实现需求的添加和关联功能', order: 10, pri: 3, severity: 3, assignedTo: 'admin'},
{id: 10073, title: '实现任务看板视图中不同分组条件的看板展示方式', order: 11, pri: 2, severity: 2, assignedTo: 'sunhao'},
];
/* Define kanban cards in lane 定义看板泳道内每一列的卡片,属性名为看板列类型,属性值为卡片列表 */
var cards =
{
/* 看板列类型: 卡片列表 */
wait: waitColCards,
confirmed: confirmedColCards,
'resolving-doing': resolvingDoingColCards,
'resolving-done': resolvingDoneColCards,
'test-doing': testDoingColCards,
'test-done': testDoneColCards,
closed: closedColCards,
};
/* Define kanban lanes 定义看板泳道 */
var lanes =
[
/* 泳道数据结构:
id: 泳道 ID,确保页面上唯一,可以为数字或字符串,
name: 名称,
cards: 定义每列上的卡片
color: 泳道名称背景色
defaultCardType: 看板泳道上的卡片默认类型,例如:'bug'(Bug) */
{id: 'bug', name: 'Bug', cards: cards, color: '#9c30b0', defaultCardType: 'bug'},
]
/* Return kanban data 返回看板数据 */
/* 看板数据结构:
id: ID,确保页面上唯一,可以为数字或字符串,
columns: 定义看板上的所有列,
lanes: 定义看板上的所有泳道
defaultCardType: 看板上的卡片默认类型,例如:'bug'(Bug) */
return {id: 'bug', columns: columns, lanes: lanes, defaultCardType: 'bug'};
}
/**
* Get task demo kanban data
* 获取“任务”看板演示数据
* @returns {Object} Kanban data
* @todo 该方法作为演示,应该在最终版本中移除
*/
function getTaskKanbanDemoData()
{
return kanbanGroup.task;
/* Define kanban columns 定义看板列 */
var columns =
[
/* 看板列定义数据结构:
id: 列 ID(确保在页面上唯一),
type: 类型,
name: 显示的名称,
color: 列名称颜色,
parentType: 所属的父级列类型
asParent: 是否作为父级列
cardType: 看板卡片类型,例如:'task'(任务)
limit: 最大数目,该列上允许展示的卡片最大数目,如果为 0 表示无限制 */
{id: 'task-wait', type: 'wait', name: '未开始', color: '', limit: 0},
{id: 'task-dev', type: 'dev', name: '开发', color: '', limit: 4, asParent: true},
{id: 'task-dev-doing', type: 'dev-doing', name: '研发中', color: '#126eed', limit: 2, parentType: 'dev'},
{id: 'task-dev-done', type: 'dev-done', name: '研发完成', color: '#2fab8e', limit: 2, parentType: 'dev'},
{id: 'task-pause', type: 'pause', name: '已暂停', color: '', limit: 0},
{id: 'task-cancel', type: 'cancel', name: '已取消', color: '', limit: 0},
{id: 'task-closed', type: 'closed', name: '已关闭', color: '', limit: 0},
];
/* Define cards in wait column 定义 未开始 列上的卡片 */
var waitColCards =
[
/* 任务数据结构:
id: ID,通常为任务 ID,
name: 名称,
order: 显示顺序,如果指定为 0,则按 ID 倒序排序,
pri: 优先级,
estimate: 预估时间,
assignedTo: 指派给,
deadline: 截止日期 */
{id: 10020, name: '在看板列中实现创建任务功能', order: 10, pri: 1, estimate: 2, assignedTo: 'admin', deadline: '2021-10-30'},
{id: 10021, name: '实现泳道的上下移动', order: 11, pri: 2, estimate: 0, assignedTo: 'sunhao', deadline: '2021-10-22'},
{id: 10024, name: '实现泳道的更多操作菜单', order: 15, pri: 0, estimate: 0, assignedTo: 'sunhao'},
];
var devDoingColCards = /* Define cards in dev/doing column 定义 开发/进行中 列上的卡片 */
[
/* 任务数据结构参见 waitColCards 定义 */
{id: 10030, name: '实现看板的更多操作菜单', order: 10, pri: 3, estimate: 0, assignedTo: 'admin', deadline: '2021-10-30'},
{id: 10031, name: '实现卡片拖动效果功能', order: 11, pri: 2, estimate: 2, assignedTo: 'sunhao'},
];
var devDoneColCards = /* Define cards in dev/done column 定义 开发/完成 列上的卡片 */
[
/* 任务数据结构参见 waitColCards 定义 */
{id: 10040, name: '实现看板列卡片排序功能', order: 10, pri: 1, estimate: 4, assignedTo: 'admin', deadline: '2021-10-30'},
];
var pauseColCards = /* Define cards in pause column 定义 已暂停 列上的卡片 */
[
/* 任务数据结构参见 waitColCards 定义 */
{id: 10050, name: '实现看板列的更多操作菜单', order: 10, pri: 3, estimate: 0, assignedTo: 'admin', deadline: '2021-10-30'},
{id: 10051, name: '实现Bug卡片的更多操作功能', order: 11, pri: 2, estimate: 2, assignedTo: 'sunhao'},
];
var cancelColCards = /* Define cards in cancel column 定义 已取消 列上的卡片 */
[
/* 任务数据结构参见 waitColCards 定义 */
{id: 10060, name: '实现任务卡片的更多操作功能', order: 10, pri: 1, estimate: 4, assignedTo: 'admin', deadline: '2021-10-30'},
];
var closedColCards =
[
/* 任务数据结构参见 waitColCards 定义 */
{id: 10070, name: '实现执行看板的新建按钮组功能', order: 10, pri: 3, estimate: 12, assignedTo: 'admin', deadline: '2021-10-30'},
{id: 10071, name: '在看板列中实现提交Bug功能', order: 11, pri: 2, estimate: 2, assignedTo: 'sunhao'},
{id: 10072, name: '在看板列中实现任务的添加和关联功能', order: 10, pri: 3, estimate: 10, assignedTo: 'admin', deadline: '2021-10-30'},
{id: 10073, name: '实现任务看板视图中不同分组条件的看板展示方式', order: 11, pri: 2, estimate: 2, assignedTo: 'sunhao'},
];
/* Define kanban cards in lane 定义看板泳道内每一列的卡片,属性名为看板列类型,属性值为卡片列表 */
var cards =
{
/* 看板列类型: 卡片列表 */
wait: waitColCards,
'dev-doing': devDoingColCards,
'dev-done': devDoneColCards,
pause: pauseColCards,
cancel: cancelColCards,
closed: closedColCards,
};
/* Define kanban lanes 定义看板泳道 */
var lanes =
[
/* 泳道数据结构:
id: 泳道 ID,确保页面上唯一,可以为数字或字符串,
name: 名称,
cards: 定义每列上的卡片
color: 泳道名称背景色
defaultCardType: 看板泳道上的卡片默认类型,例如:'task'(任务) */
{id: 'task', name: '任务', cards: cards, color: '#126eed', defaultCardType: 'task'},
]
/* Return kanban data 返回看板数据 */
/* 看板数据结构:
id: ID,确保页面上唯一,可以为数字或字符串,
columns: 定义看板上的所有列,
lanes: 定义看板上的所有泳道
defaultCardType: 看板上的卡片默认类型,例如:'task'(任务) */
return {id: 'task', columns: columns, lanes: lanes, defaultCardType: 'task'};
}
/**
* Render user avatar
* @param {String|{account: string, avatar: string}} user User account or user object
* @returns {string}
*/
function renderUserAvatar(user)
function renderUserAvatar(user, objectType, objectID)
{
if(typeof user === 'string') user = {account: user};
if(!user.avatar && window.userList && window.userList[user.account]) user = window.userList[user.account];
return $('<div class="avatar has-text avatar-sm avatar-circle" />').avatar({user: user});
if(objectType == 'task')
{
var link = createLink('task', 'assignto', 'executionID=' + executionID + '&id=' + objectID, '', true);
}
else
{
var link = createLink(objectType, 'assignto', 'id=' + objectID, '', true);
}
return $('<a class="avatar has-text avatar-sm avatar-circle iframe" href="' + link + '"/>').avatar({user: user});
}
/**
* Render deadline
* @param {String|Date} deadline Deadline
* @returns {JQuery}
*/
function renderDeadline(deadline)
{
var date = $.zui.createDate(deadline);
var now = new Date();
now.setHours(0);
now.setMinutes(0);
now.setSeconds(0);
now.setMilliseconds(0);
var isEarlyThanToday = date.getTime() < now.getTime();
return $('<span class="info info-deadline"/>').text(deadline).addClass(isEarlyThanToday ? 'text-red' : 'text-muted');
}
/**
@@ -397,14 +71,25 @@ function renderStoryItem(item, $item, col)
'<span class="info info-pri label-pri label-pri-' + item.pri + '" title="' + item.pri + '">' + item.pri + '</span>',
item.estimate ? '<span class="info info-estimate text-muted">' + item.estimate + 'h</span>' : '',
].join(''));
if(item.assignedTo) $infos.append(renderUserAvatar(item.assignedTo));
if(item.assignedTo) $infos.append(renderUserAvatar(item.assignedTo, 'story', item.id));
var $actions = $item.find('.actions');
if(!$actions.length)
{
$actions = $([
'<div class="actions">',
'<a data-contextmenu="story" data-col="' + col.type + '">',
'<i class="icon icon-ellipsis-v"></i>',
'</a>',
'</div>'
].join('')).appendTo($item);
}
$item.attr('data-type', 'story').addClass('kanban-item-story');
return $item;
}
/**
* Render bug item 提供方法渲染看板中的 Bug 卡片
* @param {Object} item Bug item object
@@ -434,7 +119,20 @@ function renderBugItem(item, $item, col)
'<span class="info info-severity label-severity" data-severity="' + item.severity + '" title="' + item.severity + '"></span>',
'<span class="info info-pri label-pri label-pri-' + item.pri + '" title="' + item.pri + '">' + item.pri + '</span>',
].join(''));
if(item.assignedTo) $infos.append(renderUserAvatar(item.assignedTo));
if(item.deadline) $infos.append(renderDeadline(item.deadline));
if(item.assignedTo) $infos.append(renderUserAvatar(item.assignedTo, 'bug', item.id));
var $actions = $item.find('.actions');
if(!$actions.length)
{
$actions = $([
'<div class="actions">',
'<a data-contextmenu="bug" data-col="' + col.type + '">',
'<i class="icon icon-ellipsis-v"></i>',
'</a>',
'</div>'
].join('')).appendTo($item);
}
$item.attr('data-type', 'bug').addClass('kanban-item-bug');
@@ -470,7 +168,20 @@ function renderTaskItem(item, $item, col)
'<span class="info i nfo-pri label-pri label-pri-' + item.pri + '" title="' + item.pri + '">' + item.pri + '</span>',
item.estimate ? '<span class="info info-estimate text-muted">' + item.estimate + 'h</span>' : '',
].join(''));
if(item.assignedTo) $infos.append(renderUserAvatar(item.assignedTo));
if(item.deadline) $infos.append(renderDeadline(item.deadline));
if(item.assignedTo) $infos.append(renderUserAvatar(item.assignedTo, 'task', item.id));
var $actions = $item.find('.actions');
if(!$actions.length)
{
$actions = $([
'<div class="actions">',
'<a data-contextmenu="task" data-col="' + col.type + '">',
'<i class="icon icon-ellipsis-v"></i>',
'</a>',
'</div>'
].join('')).appendTo($item);
}
$item.attr('data-type', 'task').addClass('kanban-item-task');
@@ -478,7 +189,6 @@ function renderTaskItem(item, $item, col)
}
/* Add column renderer/ 添加特定列类型或列条目类型渲染方法 */
/* Add column renderer/ 添加特定列类型或列卡片类型渲染方法 */
addColumnRenderer('story', renderStoryItem);
addColumnRenderer('bug', renderBugItem);
addColumnRenderer('task', renderTaskItem);
@@ -492,10 +202,62 @@ addColumnRenderer('task', renderTaskItem);
*/
function renderColumnCount($count, count, col)
{
var text = count + '/' + (!col.limit ? '<i class="icon icon-infinite"></i>' : '');
var text = count + '/' + (col.limit < 0 ? '<i class="icon icon-infinite"></i>' : col.limit);
$count.html(text + '<i class="icon icon-arrow-up"></i>');
}
/**
* Render header column 渲染看板列头部
* @param {JQuery} $col Header column element
* @param {Object} col Header column object
* @param {JQuery} $header Header element
* @param {Object} kanban Kanban object
*/
function renderHeaderCol($col, col, $header, kanban)
{
if(col.asParent) $col = $col.children('.kanban-header-col');
if(!$col.children('.actions').length)
{
var $actions = $('<div class="actions" />');
if(col.type === 'backlog' || col.type === 'wait' || col.type == 'unconfirmed')
{
$actions.append([
'<a data-contextmenu="columnCreate" data-type="' + col.type + '" data-kanban="' + kanban.id + '" data-parent="' + (col.parentType || '') + '" class="text-primary">',
'<i class="icon icon-expand-alt"></i>',
'</a>'
].join(''));
}
$actions.append([
'<a data-contextmenu="column" data-type="' + col.type + '" data-kanban="' + kanban.id + '" data-parent="' + (col.parentType || '') + '">',
'<i class="icon icon-ellipsis-v"></i>',
'</a>'
].join(''));
$actions.appendTo($col);
}
}
/**
* Render lane name 渲染看板泳道名称
* @param {JQuery} $name Name element
* @param {Object} lane Lane object
* @param {JQuery} $kanban $kanban element
* @param {Object} columns Kanban columns
* @param {Object} kanban Kanban object
*/
function renderLaneName($name, lane, $kanban, columns, kanban)
{
if(!$name.children('.actions').length)
{
$([
'<div class="actions">',
'<a data-contextmenu="lane" data-lane="' + lane.id + '" data-kanban="' + kanban.id + '">',
'<i class="icon icon-ellipsis-v"></i>',
'</a>',
'</div>'
].join('')).appendTo($name);
}
}
/**
* Updata kanban data
* 更新看板上的数据
@@ -657,6 +419,148 @@ function handleFinishDrop(event)
$('#kanbans').find('.can-drop-here').removeClass('can-drop-here');
}
/**
* Handle sort cards in column 处理对列卡片进行排序
*/
function handleSortColCards()
{
/* TODO: handle sort cards from column contextmenu */
}
/**
* Create column menu 创建列操作菜单
* @returns {Object[]}
*/
function createColumnMenu(options)
{
var $col = options.$trigger.closest('.kanban-col');
var col = $col.data('col');
var kanbanID = options.kanban;
var items = [];
if(priv.hasEditName) items.push({label: executionLang.editName, url: $.createLink('kanban', 'setColumn', 'col=' + col.columnID + '&executionID=' + executionID), className: 'iframe'})
if(priv.hasSetWIP) items.push({label: executionLang.setWIP, url: $.createLink('kanban', 'setWIP', 'col=' + col.columnID + '&executionID=' + executionID), className: 'iframe'})
items.push({label: executionLang.sortColumn, items: ['按ID倒序', '按ID顺序'], className: 'iframe', onClick: handleSortColCards})
return items;
}
/**
* Create column create button menu 创建列添加按钮操作菜单
* @returns {Object[]}
*/
function createColumnCreateMenu(options)
{
var $col = options.$trigger.closest('.kanban-col');
var col = $col.data('col');
var items = [];
if(col.laneType == 'story')
{
if(priv.canCreateStory) items.push({label: storyLang.create, url: $.createLink('story', 'create', 'productID=' + productID)});
if(priv.canBatchCreateStory) items.push({label: storyLang.batchCreate, url: $.createLink('story', 'batchcreate', 'productID=' + productID)});
if(priv.canLinkStory) items.push({label: executionLang.linkStory, url: $.createLink('execution', 'linkStory', 'executionID=' + executionID)});
if(priv.canLinkStoryByPlane) items.push({label: executionLang.linkStoryByPlan, url: '#linkStoryByPlan', 'attrs' : {'data-toggle': 'modal'}});
}
else if(col.laneType == 'bug')
{
if(priv.canCreateBug) items.push({label: bugLang.create, url: $.createLink('bug', 'create', 'productID=0&moduleID=0&extra=executionID=' + executionID)});
if(priv.canBatchCreateBug) items.push({label: bugLang.batchCreate, url: $.createLink('bug', 'batchcreate', 'productID=' + productID + '&moduleID=0&executionID=' + executionID)});
}
else
{
if(priv.canCreateTask) items.push({label: taskLang.create, url: $.createLink('task', 'create', 'executionID=' + executionID)});
if(priv.canBatchCreateTask) items.push({label: taskLang.batchCreate, url: $.createLink('task', 'batchcreate', 'executionID=' + executionID)});
}
return items;
}
/**
* Create lane menu 创建泳道操作菜单
* @returns {Object[]}
*/
function createLaneMenu(options)
{
var $lane = options.$trigger.closest('.kanban-lane');
var $kanban = $lane.closest('.kanban');
var lane = $lane.data('lane');
var kanbanID = options.kanban;
var items =
[
{label: '泳道设置', icon: 'edit', url: $.createLink('kanban', 'laneedit', 'lane=' + lane.id + '&kanban=' + kanbanID), className: 'iframe'},
{label: '泳道上移', icon: 'arrow-up', url: $.createLink('kanban', 'lanemove', 'direction=up&lane=' + lane.id + '&kanban=' + kanbanID), className: 'iframe', disabled: !$kanban.prev('.kanban').length},
{label: '泳道下移', icon: 'arrow-down', url: $.createLink('kanban', 'lanemove', 'direction=down&lane=' + lane.id + '&kanban=' + kanbanID), className: 'iframe', disabled: !$kanban.next('.kanban').length},
];
var bounds = options.$trigger[0].getBoundingClientRect();
items.$options = {x: bounds.right, y: bounds.top};
return items;
}
/**
* Create story menu 创建需求卡片操作菜单
* @returns {Object[]}
*/
function createStoryMenu(options)
{
var $card = options.$trigger.closest('.kanban-item');
var story = $card.data('item');
var items =
[
{label: '编辑需求', icon: 'edit', url: $.createLink('story', 'edit', 'storyID=' + story.id), className: 'iframe'},
{label: '变更需求', icon: 'change', url: $.createLink('story', 'change', 'storyID=' + story.id), className: 'iframe'},
{label: '移除需求', icon: 'unlink', url: $.createLink('story', 'unlink', 'storyID=' + story.id), className: 'iframe'},
{label: '分解任务', icon: 'plus', url: $.createLink('task', 'create', 'storyID=' + story.id), className: 'iframe'},
{label: '批量分解', icon: 'pluses', url: $.createLink('task', 'batchCreate', 'storyID=' + story.id), className: 'iframe'},
];
return items;
}
/**
* Create bug menu 创建 Bug 卡片操作菜单
* @returns {Object[]}
*/
function createBugMenu(options)
{
var $card = options.$trigger.closest('.kanban-item');
var bug = $card.data('item');
var items =
[
{label: '编辑Bug', icon: 'edit', url: $.createLink('bug', 'edit', 'bugID=' + bug.id), className: 'iframe'},
{label: '确认Bug', icon: 'ok', url: $.createLink('bug', 'confirm', 'bugID=' + bug.id), className: 'iframe'},
{label: '复制Bug', icon: 'copy', url: $.createLink('bug', 'copy', 'bugID=' + bug.id), className: 'iframe'},
{label: '转软件需求', icon: 'lightbulb', url: $.createLink('story', 'create', 'bugID=' + bug.id), className: 'iframe'},
];
return items;
}
/**
* Create task menu 创建任务卡片操作菜单
* @returns {Object[]}
*/
function createTaskMenu(options)
{
var $card = options.$trigger.closest('.kanban-item');
var task = $card.data('item');
var items =
[
{label: '编辑任务', icon: 'edit', url: $.createLink('task', 'edit', 'taskID=' + task.id), className: 'iframe'},
{label: '拆分子任务', icon: 'plus', url: $.createLink('task', 'create', 'taskID=' + task.id), className: 'iframe'},
{label: '复制任务', icon: 'copy', url: $.createLink('task', 'copy', 'taskID=' + task.id), className: 'iframe'},
{label: '取消任务', icon: 'cancel', url: $.createLink('task', 'cancel', 'taskID=' + task.id), className: 'iframe'},
];
return items;
}
/* Define menu creators */
window.menuCreators =
{
column: createColumnMenu,
columnCreate: createColumnCreateMenu,
lane: createLaneMenu,
story: createStoryMenu,
bug: createBugMenu,
task: createTaskMenu,
};
/* Overload kanban default options */
$.extend($.fn.kanban.Constructor.DEFAULTS,
{
@@ -683,26 +587,66 @@ $(function()
showCount: true,
showZeroCount: true,
fluidBoardWidth: true,
countRender: renderColumnCount,
droppable:
{
target: findDropColumns,
finish: handleFinishDrop,
mouseButton: 'left'
}
},
onRenderHeaderCol: renderHeaderCol,
onRenderLaneName: renderLaneName,
onRenderCount: renderColumnCount
};
/* Hide group menu. */
hideGroupMenu();
/* Create story kanban 创建需求看板 */
createKanban('story', getStoryKanbanDemoData(), commonOptions);
if(browseType == 'all' || browseType == 'story') createKanban('story', kanbanGroup.story, commonOptions);
/* Create bug kanban 创建 Bug 看板 */
createKanban('bug', getBugKanbanDemoData(), commonOptions);
if(browseType == 'all' || browseType == 'bug') createKanban('bug', kanbanGroup.bug, commonOptions);
/* Create task kanban 创建 任务 看板 */
createKanban('task', getTaskKanbanDemoData(), commonOptions);
if(browseType == 'all' || browseType == 'task') createKanban('task', kanbanGroup.task, commonOptions);
/* Init iframe modals */
$(document).on('click', '.iframe', function(event)
{
$(this).modalTrigger({show: true, width: '500px'});
event.preventDefault();
});
/* Init contextmenu */
$('#kanbans').on('click', '[data-contextmenu]', function(event)
{
var $trigger = $(this);
var menuType = $trigger.data('contextmenu');
var menuCreator = window.menuCreators[menuType];
if(!menuCreator) return;
var options = $.extend({event, $trigger: $trigger}, $trigger.data());
var items = menuCreator(options);
if(!items || !items.length) return;
$.zui.ContextMenu.show(items, items.$options || {event: event});
});
/* Hide contextmenu when page scroll */
$(window).on('scroll', function()
{
$.zui.ContextMenu.hide();
});
$('#toStoryButton').on('click', function()
{
var planID = $('#plan').val();
if(planID)
{
parent.location.href = createLink('execution', 'importPlanStories', 'executionID=' + executionID + '&planID=' + planID);
}
})
});
/**
+5
View File
@@ -376,6 +376,11 @@ $lang->execution->kanbanHideCols = 'Geschlossene und abgebrochene Spalten in K
$lang->execution->kanbanShowOption = 'Aufklappen';
$lang->execution->kanbanColsColor = 'Spaltenfarben';
$lang->execution->kanbanViewList['all'] = 'All';
$lang->execution->kanbanViewList['story'] = "{$lang->SRCommon}";
$lang->execution->kanbanViewList['bug'] = 'Bug';
$lang->execution->kanbanViewList['task'] = 'Task';
$lang->kanbanSetting = new stdclass();
$lang->kanbanSetting->noticeReset = 'Möchten Sie die Einstellungen des Kanbans zurücksetzen?';
$lang->kanbanSetting->optionList['0'] = 'Verstecken';
+8
View File
@@ -101,6 +101,9 @@ $lang->execution->copyNoExecution = 'There are no ' . $lang->executionCommon . '
$lang->execution->noTeam = 'No team members at the moment';
$lang->execution->or = ' or ';
$lang->execution->selectProject = 'Please select project';
$lang->execution->editName = 'Edit Name';
$lang->execution->setWIP = 'WIP Settings';
$lang->execution->sortColumn = 'Kanban Card Sorting';
/* Fields of zt_team. */
$lang->execution->root = 'Root';
@@ -385,6 +388,11 @@ $lang->execution->kanbanHideCols = 'Closed & Cancelled Columns';
$lang->execution->kanbanShowOption = 'Unfold';
$lang->execution->kanbanColsColor = 'Customize Column Color';
$lang->execution->kanbanViewList['all'] = 'All';
$lang->execution->kanbanViewList['story'] = "{$lang->SRCommon}";
$lang->execution->kanbanViewList['bug'] = 'Bug';
$lang->execution->kanbanViewList['task'] = 'Task';
$lang->kanbanSetting = new stdclass();
$lang->kanbanSetting->noticeReset = 'Do you want to reset Kanban?';
$lang->kanbanSetting->optionList['0'] = 'Hide';
+5
View File
@@ -376,6 +376,11 @@ $lang->execution->kanbanHideCols = 'Colonnes masquées';
$lang->execution->kanbanShowOption = 'Déplier';
$lang->execution->kanbanColsColor = 'Personnalisation Couleurs';
$lang->execution->kanbanViewList['all'] = 'All';
$lang->execution->kanbanViewList['story'] = "{$lang->SRCommon}";
$lang->execution->kanbanViewList['bug'] = 'Bug';
$lang->execution->kanbanViewList['task'] = 'Task';
$lang->kanbanSetting = new stdclass();
$lang->kanbanSetting->noticeReset = 'Voulez-vous réinitialiser le tableau Kanban ?';
$lang->kanbanSetting->optionList['0'] = 'Masquer';
+5
View File
@@ -377,6 +377,11 @@ $lang->execution->kanbanHideCols = 'Cột Đã đóng & đã hủy';
$lang->execution->kanbanShowOption = 'Mở ra';
$lang->execution->kanbanColsColor = 'Tùy biến màu cột';
$lang->execution->kanbanViewList['all'] = 'All';
$lang->execution->kanbanViewList['story'] = "{$lang->SRCommon}";
$lang->execution->kanbanViewList['bug'] = 'Bug';
$lang->execution->kanbanViewList['task'] = 'Task';
$lang->kanbanSetting = new stdclass();
$lang->kanbanSetting->noticeReset = 'Bạn có muốn thiết lập lại Kanban?';
$lang->kanbanSetting->optionList['0'] = 'Ẩn';
+8
View File
@@ -101,6 +101,9 @@ $lang->execution->copyNoExecution = '没有可用的' . $lang->executionCommon .
$lang->execution->noTeam = '暂时没有团队成员';
$lang->execution->or = '或';
$lang->execution->selectProject = '请选择项目';
$lang->execution->editName = '编辑名称';
$lang->execution->setWIP = '在制品数量设置(WIP)';
$lang->execution->sortColumn = '看板列卡片排序';
/* Fields of zt_team. */
$lang->execution->root = '源ID';
@@ -386,6 +389,11 @@ $lang->execution->kanbanHideCols = '看板隐藏已关闭、已取消列';
$lang->execution->kanbanShowOption = '显示折叠信息';
$lang->execution->kanbanColsColor = '看板列自定义颜色';
$lang->execution->kanbanViewList['all'] = '综合看板';
$lang->execution->kanbanViewList['story'] = "{$lang->SRCommon}看板";
$lang->execution->kanbanViewList['bug'] = 'Bug看板';
$lang->execution->kanbanViewList['task'] = '任务看板';
$lang->kanbanSetting = new stdclass();
$lang->kanbanSetting->noticeReset = '是否恢复看板默认设置?';
$lang->kanbanSetting->optionList['0'] = '隐藏';
+28 -1
View File
@@ -81,6 +81,8 @@
</ul>
</div>
<?php endif;?>
<?php else:?>
<?php $canCreateTask = $canBatchCreateTask = $canCreateBug = $canBatchCreateBug = $canCreateStory = $canBatchCreateStory = $canLinkStory = $canLinkStoryByPlane = false;?>
<?php endif;?>
</div>
</div>
@@ -104,12 +106,37 @@
<div class="modal-body">
<div class='input-group'>
<?php echo html::select('plan', $allPlans, '', "class='form-control chosen' id='plan'");?>
<span class='input-group-btn'><?php echo html::commonButton($lang->execution->linkStory, "id='toTaskButton'", 'btn btn-primary');?></span>
<span class='input-group-btn'><?php echo html::commonButton($lang->execution->linkStory, "id='toStoryButton'", 'btn btn-primary');?></span>
</div>
</div>
</div>
</div>
</div>
<?php js::set('executionID', $executionID);?>
<?php js::set('productID', $productID);?>
<?php js::set('kanbanGroup', $kanbanGroup);?>
<?php js::set('browseType', $browseType);?>
<?php
js::set('priv',
array(
'hasEditName' => common::hasPriv('kanban', 'setColumn'),
'hasSetWIP' => common::hasPriv('kanban', 'setWIP'),
'canCreateTask' => $canCreateTask,
'canBatchCreateTask' => $canBatchCreateTask,
'canCreateBug' => $canCreateBug,
'canBatchCreateBug' => $canBatchCreateBug,
'canCreateStory' => $canCreateStory,
'canBatchCreateStory' => $canBatchCreateStory,
'canLinkStory' => $canLinkStory,
'canLinkStoryByPlane' => $canLinkStoryByPlane,
)
);
?>
<?php js::set('executionLang', $lang->execution);?>
<?php js::set('storyLang', $lang->story);?>
<?php js::set('taskLang', $lang->task);?>
<?php js::set('bugLang', $lang->bug);?>
<?php js::set('editName', $lang->execution->editName);?>
<?php js::set('setWIP', $lang->execution->setWIP);?>
<?php js::set('sortColumn', $lang->execution->sortColumn);?>
<?php include '../../common/view/footer.html.php';?>
+11
View File
@@ -25,6 +25,7 @@ $lang->moduleOrder[40] = 'release';
$lang->moduleOrder[45] = 'project';
$lang->moduleOrder[50] = 'projectstory';
$lang->moduleOrder[55] = 'execution';
$lang->moduleOrder[56] = 'kanban';
$lang->moduleOrder[60] = 'task';
$lang->moduleOrder[65] = 'build';
@@ -547,6 +548,16 @@ $lang->release->methodOrder[60] = 'unlinkBug';
$lang->release->methodOrder[65] = 'batchUnlinkBug';
$lang->release->methodOrder[70] = 'changeStatus';
/* Kanban */
$lang->resource->kanban = new stdclass();
$lang->resource->kanban->setLane = 'setLane';
$lang->resource->kanban->setColumn = 'setColumn';
$lang->resource->kanban->setWIP = 'setWIP';
$lang->kanban->methodOrder[5] = 'setLane';
$lang->kanban->methodOrder[10] = 'setColumn';
$lang->kanban->methodOrder[15] = 'setWIP';
/* Execution. */
$lang->resource->execution = new stdclass();
$lang->resource->execution->view = 'view';
+54 -7
View File
@@ -80,17 +80,21 @@ class kanban extends control
* @access public
* @return void
*/
public function setLaneColumn($columnID)
public function setColumn($columnID)
{
$column = $this->kanban->getColumnById($columnID);
if($_POST)
{
$params = fixer::input('post')->get();
$this->kanban->updateLaneColumn($columnID, $params);
/* Check lane column name is unique. */
$exist = $this->kanban->getColumnByName($this->post->name, $column->lane);
if($exist and $exist->id != $columnID)
{
return $this->sendError($this->lang->kanban->noColumnUniqueName);
}
$changes = $this->kanban->updateLaneColumn($columnID, $column);
if(dao::isError()) return $this->sendError(dao::getError());
$changes = common::createChanges($column, $params);
if($changes)
{
$actionID = $this->loadModel('action')->create('kanbancolumn', $columnID, 'Edited');
@@ -101,8 +105,51 @@ class kanban extends control
}
$this->view->column = $column;
$this->view->title = $column->name . $this->lang->colon . $this->lang->kanban->setLaneColumn;
$this->view->title = $column->name . $this->lang->colon . $this->lang->kanban->setColumn;
$this->display();
}
/**
* AJAX: Update the cards sorting of the lane column.
*
* @param string $laneType story|bug|task
* @param int $columnID
* @param string $orderBy id_desc|id_asc|pri_desc|pri_asc|lastEditedDate_desc|lastEditedDate_asc|deadline_desc|deadline_asc|assignedTo_asc
* @access public
* @return void
*/
public function ajaxCardsSort($laneType, $columnID, $orderBy = 'id_desc')
{
$oldCards = array();
$column = $this->dao->select('parent,cards')->from(TABLE_KANBANCOLUMN)->where('id')->eq($columnID)->fetch();
/* Get the cards of the kanban column. */
if($column->parent == -1)
{
$childColumns = $this->dao->select('id,cards')->from(TABLE_KANBANCOLUMN)->where('parent')->eq($columnID)->fetchAll();
foreach($childColumns as $childColumn)
{
$oldCards[$childColumn->id] = $childColumn->cards;
}
}
else
{
$oldCards[$columnID] = $column->cards;
}
/* Update Kanban column card order. */
$table = $this->config->objectTables[$laneType];
foreach($oldCards as $colID => $cards)
{
if(empty($cards)) continue;
$objects = $this->dao->select('id')->from($table)
->where('id')->in($cards)
->orderBy($orderBy)
->fetchPairs('id');
$objectIdList = ',' . implode(',', $objects) . ',';
$this->dao->update(TABLE_KANBANCOLUMN)->set('cards')->eq($objectIdList)->where('id')->eq($colID)->exec();
}
echo true;
}
}
@@ -2,3 +2,4 @@ td>ul {padding-left: 0;}
li {display: block; float: left; padding: 5px; width: 28px; height: 28px;}
li>a { position: relative; display: block; width: 100%; height: 100%; padding: 0; margin: 0; font-family: ZentaoIcon; font-size: 14px; font-style: normal; font-weight: 400; font-variant: normal; line-height: 1; text-align: center; text-transform: none; border: 1px solid transparent; border-radius: 50%; speak: none; -webkit-font-smoothing: antialiased; }
li>a.active:before {font-size: 14px; content: "\e5ca"}
+18 -10
View File
@@ -1,16 +1,24 @@
<?php
$lang->kanban = new stdClass();
$lang->kanban->WIP = 'WIP';
$lang->kanban->setWIP = 'WIP Settings';
$lang->kanban->WIPStatus = 'WIP Status';
$lang->kanban->WIPStage = 'WIP Stage';
$lang->kanban->WIPType = 'WIP Type';
$lang->kanban->WIPCount = 'WIP Count';
$lang->kanban->noLimit = 'No Limit ∞';
$lang->kanban->setLane = 'Lane Settings';
$lang->kanban->laneName = 'Lane Name';
$lang->kanban->laneColor = 'Lane Color';
$lang->kanban->WIP = 'WIP';
$lang->kanban->setWIP = 'WIP Settings';
$lang->kanban->WIPStatus = 'WIP Status';
$lang->kanban->WIPStage = 'WIP Stage';
$lang->kanban->WIPType = 'WIP Type';
$lang->kanban->WIPCount = 'WIP Count';
$lang->kanban->noLimit = 'No Limit ∞';
$lang->kanban->setLane = 'Lane Settings';
$lang->kanban->laneName = 'Lane Name';
$lang->kanban->laneColor = 'Lane Color';
$lang->kanban->setLaneColumn = 'Column Settings';
$lang->kanban->columnName = 'Column Name';
$lang->kanban->columnColor = 'Column Color';
$lang->kanban->noColumnUniqueName = 'The Kanban column name already exists.';
$lang->kanban->error = new stdclass();
$lang->kanban->error->parentLimitNote = 'The WIPs in the parent column cannot be < the sum of the WIPs in the child column.';
$lang->kanban->error->childLimitNote = 'The sum of products in the child column cannot be > the number of products in the parent column.';
$this->lang->kanban->laneTypeList = array();
$this->lang->kanban->laneTypeList['story'] = $lang->SRCommon;
+190 -110
View File
@@ -32,86 +32,94 @@ class kanbanModel extends model
->fetchAll('id');
if(empty($lanes)) return array();
foreach($lanes as $lane) $this->updateCards($lane);
$columns = $this->dao->select('*')->from(TABLE_KANBANCOLUMN)
->where('deleted')->eq(0)
->andWhere('lane')->in(array_keys($lanes))
->fetchGroup('lane', 'id');
/* Get parent column type pairs. */
$parentTypes = $this->dao->select('id, type')->from(TABLE_KANBANCOLUMN)
->where('deleted')->eq(0)
->andWhere('lane')->in(array_keys($lanes))
->andWhere('parent')->eq(-1)
->fetchPairs('id', 'type');
if($objectType == 'story' or $objectType == 'all') $stories = $this->loadModel('story')->getExecutionStories($executionID);
if($objectType == 'bug' or $objectType == 'all') $bugs = $this->loadModel('bug')->getExecutionBugs($executionID);
if($objectType == 'task' or $objectType == 'all') $tasks = $this->loadModel('execution')->getKanbanTasks($executionID, "id");
/* Get group objects. */
if($browseType == 'all' or $browseType == 'story') $objectGroup['story'] = $this->loadModel('story')->getExecutionStories($executionID);
if($browseType == 'all' or $browseType == 'bug') $objectGroup['bug'] = $this->loadModel('bug')->getExecutionBugs($executionID);
if($browseType == 'all' or $browseType == 'task') $objectGroup['task'] = $this->loadModel('execution')->getKanbanTasks($executionID, "id");
/* Init vars. */
$kanban = array();
/* Build kanban group data. */
$kanbanGroup = array();
foreach($lanes as $laneID => $lane)
{
$laneData = array();
$columnData = array();
$cards = array();
$laneType = $lane->type;
$laneData['id'] = $lane->type;
$laneData['id'] = $laneType;
$laneData['name'] = $lane->name;
$laneData['color'] = $lane->color;
$laneData['defaultCardType'] = $lane->type;
$laneData['order'] = $lane->order;
$laneData['defaultCardType'] = $laneType;
foreach($columns[$laneID] as $colID => $col)
foreach($columns[$laneID] as $columnID => $column)
{
$columnData[$col->id]['id'] = $lane->type . '-' . $col->type;
$columnData[$col->id]['type'] = $col->type;
$columnData[$col->id]['name'] = $col->name;
$columnData[$col->id]['color'] = $col->color;
$columnData[$col->id]['limit'] = $col->limit;
$columnData[$col->id]['asParent'] = $col->parent == -1 ? true : false;
$columnData[$column->id]['id'] = $laneType . '-' . $column->type;
$columnData[$column->id]['columnID'] = $columnID;
$columnData[$column->id]['type'] = $column->type;
$columnData[$column->id]['name'] = $column->name;
$columnData[$column->id]['color'] = $column->color;
$columnData[$column->id]['limit'] = $column->limit;
$columnData[$column->id]['laneType'] = $laneType;
$columnData[$column->id]['asParent'] = $column->parent == -1 ? true : false;
if($col->parent > 0)
if($column->parent > 0)
{
$columnData[$col->id]['parentType'] = zget($parentTypes, $col->parent, '');
$columnData[$column->id]['parentType'] = zget($parentTypes, $column->parent, '');
}
$cardIdList = array_filter(explode(',', $col->cards));
$cardOrder = 1;
$item = array();
$cardIdList = array_filter(explode(',', $column->cards));
foreach($cardIdList as $cardID)
{
if($lane->type == 'story') $object = zget($stories, $cardID, array());
if($lane->type == 'task') $object = zget($tasks, $cardID, array());
if($lane->type == 'bug') $object = zget($bugs, $cardID, array());
$cardData = array();
$objects = zget($objectGroup, $laneType, array());
$object = zget($objects, $cardID, array());
$item['id'] = $object->id;
$item['order'] = $cardOrder;
$item['pri'] = $object->pri ? $object->pri : '';
$item['estimate'] = $lane->type == 'bug' ? '' : $object->estimate;
$item['assignedTo'] = $object->assignedTo;
$item['deadline'] = $lane->type == 'task' ? $object->deadline : '';
$cardData['id'] = $object->id;
$cardData['order'] = $cardOrder;
$cardData['pri'] = $object->pri ? $object->pri : '';
$cardData['estimate'] = $laneType == 'bug' ? '' : $object->estimate;
$cardData['assignedTo'] = $object->assignedTo;
$cardData['deadline'] = $laneType == 'task' ? $object->deadline : '';
$cardData['severity'] = $laneType == 'bug' ? $object->severity : '';
if($lane->type == 'task')
if($laneType == 'task')
{
$item['name'] = $object->name;
$cardData['name'] = $object->name;
}
else
{
$item['title'] = $object->title;
$cardData['title'] = $object->title;
}
$laneData['cards'][$col->type][] = $item;
$laneData['cards'][$column->type][] = $cardData;
$cardOrder ++;
}
if(!isset($laneData['cards'][$column->type])) $laneData['cards'][$column->type] = array();
}
$kanban[$lane->type]['id'] = $lane->type;
$kanban[$lane->type]['columns'] = array_values($columnData);
$kanban[$lane->type]['lanes'][] = $laneData;
$kanban[$lane->type]['defaultCardType'] = $lane->type;
$kanbanGroup[$laneType]['id'] = $laneType;
$kanbanGroup[$laneType]['columns'] = array_values($columnData);
$kanbanGroup[$laneType]['lanes'][] = $laneData;
$kanbanGroup[$laneType]['defaultCardType'] = $laneType;
}
return $kanban;
return $kanbanGroup;
}
/**
@@ -330,93 +338,97 @@ class kanbanModel extends model
/**
* Update column cards.
*
* @param int $executionID
* @param string $objectType story|bug|task
* @param object $columns
* @param object $lane
* @access public
* @return void
*/
public function updateCards($executionID, $objectType, $columns)
public function updateCards($lane)
{
$objects = array();
if($objectType == 'story') $objects = $this->loadModel('story')->getExecutionStories($executionID);
if($objectType == 'bug') $objects = $this->loadModel('bug')->getExecutionBugs($executionID);
if($objectType == 'task') $objects = $this->loadModel('execution')->getKanbanTasks($executionID);
if($objectType == 'story')
$laneType = $lane->type;
$executionID = $lane->execution;
$cardPairs = $this->dao->select('*')->from(TABLE_KANBANCOLUMN)
->where('deleted')->eq(0)
->andWhere('lane')->eq($lane->id)
->fetchPairs('type' ,'cards');
if($laneType == 'story')
{
$data = new stdClass();
foreach($columns as $colID => $col)
$stories = $this->loadModel('story')->getExecutionStories($executionID);
foreach($stories as $storyID => $story)
{
foreach($objects as $storyID => $story)
foreach($this->config->kanban->storyColumnStageList as $colType => $stage)
{
if($col->type == 'backlog' and $story->status == 'active' and $story->stage == 'projected') $data->cards .= $storyID . ',';
if($colType == 'closed' and $story->status == 'closed' and $story->stage == 'closed') $data->cards .= $storyID . ',';
if($story->stage == $colType and strpos(',backlog,closed,', $colType) === false and $story->status == 'active') $data->cards .= $storyID . ',';
}
if(!empty($data->cards)) $data->cards = ',' . $data->cards;
}
$this->dao->insert(TABLE_KANBANCOLUMN)->data($data)->exec();
if($colType == 'develop') $devColumnID = $this->dao->lastInsertId();
if($colType == 'test') $testColumnID = $this->dao->lastInsertId();
}
elseif($type == 'bug')
{
foreach($this->lang->kanban->bugColumn as $colType => $name)
{
$data = new stdClass();
$data->lane = $laneID;
$data->name = $name;
$data->type = $colType;
$data->cards = '';
if(strpos(',fixing,fixed,', $colType) !== false) $data->parent = $resolvingColumnID;
if(strpos(',testing,tested,', $colType) !== false) $data->parent = $testColumnID;
if(strpos(',resolving,fixing,test,testing,tested,', $colType) === false)
{
foreach($objects as $bugID => $bug)
if(strpos(',ready,develop,test,', $colType) !== false) continue;
if($colType == 'backlog' and $story->stage == $stage and strpos($cardPairs['ready'], ",$storyID,") === false and strpos($cardPairs['backlog'], ",$storyID,") === false)
{
if($colType == 'unconfirmed' and $bug->status == 'active' and $bug->confirmed == 0) $data->cards .= $bugID . ',';
if($colType == 'confirmed' and $bug->status == 'active' and $bug->confirmed == 1) $data->cards .= $bugID . ',';
if($colType == 'fixed' and $bug->status == 'resolved') $data->cards .= $bugID . ',';
if($colType == 'closed' and $bug->status == 'closed') $data->cards .= $bugID . ',';
$cardPairs['backlog'] .= empty($cardPairs['backlog']) ? ',' . $storyID . ',' : $storyID . ',';
}
elseif($story->stage == $stage and strpos($cardPairs[$colType], ",$storyID,") === false)
{
$cardPairs[$colType] .= empty($cardPairs[$colType]) ? ',' . $storyID . ',' : $storyID . ',';
}
elseif($story->stage != $stage and strpos($cardPairs[$colType], ",$storyID,") !== false)
{
$cardPairs[$colType] = str_replace(",$storyID,", ',', $cardPairs[$colType]);
}
$this->dao->insert(TABLE_KANBANCOLUMN)->data($data)->exec();
if($colType == 'resolving') $resolvingColumnID = $this->dao->lastInsertId();
if($colType == 'test') $testColumnID = $this->dao->lastInsertId();
}
}
}
elseif($type == 'task')
elseif($laneType == 'bug')
{
foreach($this->lang->kanban->taskColumn as $colType => $name)
$bugs = $this->loadModel('bug')->getExecutionBugs($executionID);
foreach($bugs as $bugID => $bug)
{
$data = new stdClass();
$data->lane = $laneID;
$data->name = $name;
$data->type = $colType;
$data->cards = '';
if(strpos(',developing,developed,', $colType) !== false) $data->parent = $devColumnID;
if(strpos(',develop,', $colType) === false)
foreach($this->config->kanban->bugColumnStatusList as $colType => $status)
{
foreach($objects as $taskID => $task)
if(strpos(',resolving,fixing,test,testing,tested,', $colType) !== false) continue;
if($colType == 'unconfirmed' and $bug->status == $status and $bug->confirmed == 0 and strpos($cardPairs['unconfirmed'], ",$bugID,") === false and strpos($cardPairs['fixing'], ",$bugID,") === false)
{
if($colType == 'developing' and $task->status == 'doing') $data->cards .= $taskID . ',';
if($colType == 'developed' and $task->status == 'done') $data->cards .= $taskID . ',';
if(strpos(',wait,pause,canceled,closed,', $colType) !== false and $task->status == $colType) $data->cards .= $taskID . ',';
$cardPairs['unconfirmed'] .= empty($cardPairs['unconfirmed']) ? ',' . $bugID . ',' : $bugID . ',';
}
elseif($colType == 'confirmed' and $bug->status == $status and $bug->confirmed == 1 and strpos($cardPairs['confirmed'], ",$bugID,") === false and strpos($cardPairs['fixing'], ",$bugID,") === false)
{
$cardPairs['confirmed'] .= empty($cardPairs['confirmed']) ? ',' . $bugID . ',' : $bugID . ',';
}
elseif($colType == 'fixed' and $bug->status == $status and strpos($cardPairs['fixed'], ",$bugID,") === false and strpos($cardPairs['testing'], ",$bugID,") === false and strpos($cardPairs['tested'], ",$bugID,") === false)
{
$cardPairs['confirmed'] .= empty($cardPairs['confirmed']) ? ',' . $bugID . ',' : $bugID . ',';
}
elseif($bug->status == $status and strpos($cardPairs[$colType], ",$bugID,") === false)
{
$cardPairs[$colType] .= empty($cardPairs[$colType]) ? ',' . $bugID . ',' : $bugID . ',';
}
elseif($bug->status != $status and strpos($cardPairs[$colType], ",$bugID,") !== false)
{
$cardPairs[$colType] = str_replace(",$bugID,", ',', $cardPairs[$colType]);
}
$this->dao->insert(TABLE_KANBANCOLUMN)->data($data)->exec();
if($colType == 'develop') $devColumnID = $this->dao->lastInsertId();
}
}
}
elseif($laneType == 'task')
{
$tasks = $this->loadModel('execution')->getKanbanTasks($executionID);
foreach($tasks as $taskID => $task)
{
foreach($this->config->kanban->taskColumnStatusList as $colType => $status)
{
if($colType == 'develop') continue;
if($task->status == $status and strpos($cardPairs[$colType], ",$taskID,") === false)
{
$cardPairs[$colType] .= empty($cardPairs[$colType]) ? ',' . $taskID . ',' : $taskID . ',';
}
elseif($task->status != $status and strpos($cardPairs[$colType], ",$taskID,") !== false)
{
$cardPairs[$colType] = str_replace(",$taskID,", ',', $cardPairs[$colType]);
}
}
}
}
foreach($cardPairs as $colType => $cards)
{
$this->dao->update(TABLE_KANBANCOLUMN)->set('cards')->eq($cards)->where('lane')->eq($lane->id)->andWhere('type')->eq($colType)->exec();
}
}
/**
@@ -439,6 +451,24 @@ class kanbanModel extends model
return $column;
}
/**
* Get Column by column name.
*
* @param string $name
* @param int $laneID
* @access public
* @return object
*/
public function getColumnByName($name, $laneID)
{
return $this->dao->select('*')
->from(TABLE_KANBANCOLUMN)
->where('name')->eq($name)
->andWhere('lane')->eq($laneID)
->fetch();
}
/**
* Get lane by id.
*
@@ -460,15 +490,57 @@ class kanbanModel extends model
*/
public function setWIP($columnID)
{
$column = $this->getColumnById($columnID);
$data = fixer::input('post')
$oldColumn = $this->getColumnById($columnID);
$column = fixer::input('post')
->cleanInt('limit')
->remove('WIPCount,noLimit')
->get();
$this->dao->update(TABLE_KANBANCOLUMN)->data($data)
/* Check column limit. */
$sumChildLimit = 0;
if($oldColumn->parent == -1 and $column->limit != -1)
{
$childColumns = $this->dao->select('id,`limit`')->from(TABLE_KANBANCOLUMN)->where('parent')->eq($columnID)->fetchAll();
foreach($childColumns as $childColumn)
{
if($childColumn->limit == -1)
{
dao::$errors['limit'] = $this->lang->kanban->error->parentLimitNote;
return false;
}
$sumChildLimit += $childColumn->limit;
}
if($sumChildLimit > $column->limit)
{
dao::$errors['limit'] = $this->lang->kanban->error->parentLimitNote;
return false;
}
}
elseif($oldColumn->parent > 0)
{
$parentColumn = $this->getColumnByID($oldColumn->parent);
if($parentColumn->limit != -1)
{
$siblingLimit = $this->dao->select('`limit`')->from(TABLE_KANBANCOLUMN)
->where('`parent`')->eq($oldColumn->parent)
->andWhere('id')->ne($columnID)
->fetch('limit');
$sumChildLimit = $siblingLimit + $column->limit;
if($column->limit == -1 or $siblingLimit == -1 or $sumChildLimit > $parentColumn->limit)
{
dao::$errors['limit'] = $this->lang->kanban->error->childLimitNote;
return false;
}
}
}
$this->dao->update(TABLE_KANBANCOLUMN)->data($column)
->autoCheck()
->checkIF($data->limit != -1, 'limit', 'gt', 0)
->checkIF($column->limit != -1, 'limit', 'gt', 0)
->batchcheck($this->config->kanban->setwip->requiredFields, 'notempty')
->where('id')->eq($columnID)
->exec();
@@ -499,16 +571,24 @@ class kanbanModel extends model
/**
* Update lane column.
*
* @param int $columnID
* @param int $columnID
* @param object $column
* @access public
* @return array
*/
public function updateLaneColumn($columnID, $data)
public function updateLaneColumn($columnID, $column)
{
$data = fixer::input('post')->get();
$this->dao->update(TABLE_KANBANCOLUMN)->data($data)
->autoCheck()
->batchcheck($this->config->kanban->setlaneColumn->requiredFields, 'notempty')
->where('id')->eq($columnID)
->exec();
if(dao::isError()) return;
$changes = common::createChanges($column, $data);
return $changes;
}
}
@@ -1,12 +1,12 @@
<?php
/**
* The set lane file of task module of ZenTaoPMS.
* The set column file of task module of ZenTaoPMS.
*
* @copyright Copyright 2009-2021 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
* @license ZPL (http://zpl.pub/page/zplv12.html)
* @author Yuchun Li <liyuchun@easycorp.ltd>
* @author Thanatos<xiawenlong@easycorp.ltd>
* @package kanban
* @version $Id: setlane.html.php 935 2021-10-26 16:24:24Z liyuchun@easycorp.ltd $
* @version $Id: setcolumn.html.php 935 2021-10-26 16:24:24Z xiawenlong@easycorp.ltd $
* @link https://www.zentao.net
*/
?>
+1 -1
View File
@@ -76,7 +76,7 @@ $lang->program->stakeholderTypeList['inside'] = 'Inside';
$lang->program->stakeholderTypeList['outside'] = 'Outside';
$lang->program->noProgram = 'No program.';
$lang->program->showClosed = 'Closed programs.';
$lang->program->showClosed = 'Closed';
$lang->program->tips = 'If a parent item set is selected, products under that parent item set can be associated. If no item set is selected, a product with the same name as the item is created by default and associated with that item.';
$lang->program->confirmBatchUnlink = "Do you want to batch unlink these stakeholders?";
$lang->program->beginLetterParent = "Parent begin date: %s, begin date should be > = parent begin date.";
+18 -2
View File
@@ -132,11 +132,27 @@ class searchModel extends model
$condition = '';
if($operator == "include")
{
$condition = ' LIKE ' . $this->dbh->quote("%$value%");
if($this->post->$fieldName == 'module')
{
$allModules = $this->loadModel('tree')->getAllChildId($value);
if($allModules) $condition = helper::dbIN($allModules);
}
else
{
$condition = ' LIKE ' . $this->dbh->quote("%$value%");
}
}
elseif($operator == "notinclude")
{
$condition = ' NOT LIKE ' . $this->dbh->quote("%$value%");
if($this->post->$fieldName == 'module')
{
$allModules = $this->loadModel('tree')->getAllChildId($value);
if($allModules) $condition = " NOT " . helper::dbIN($allModules);
}
else
{
$condition = ' NOT LIKE ' . $this->dbh->quote("%$value%");
}
}
elseif($operator == 'belong')
{
+9 -2
View File
@@ -136,8 +136,15 @@ class story extends control
if($objectID != 0)
{
if($objectID != $this->session->project) $this->loadModel('action')->create('story', $storyID, 'linked2execution', '', $objectID);
if($this->config->systemMode == 'new') $this->loadModel('action')->create('story', $storyID, 'linked2project', '', $this->session->project);
$object = $this->dao->findById((int)$objectID)->from(TABLE_PROJECT)->fetch();
if($object->type != 'project')
{
$this->loadModel('action')->create('story', $storyID, 'linked2execution', '', $objectID);
}
else
{
$this->loadModel('action')->create('story', $storyID, 'linked2project', '', $objectID);
}
}
if($todoID > 0)
+1 -1
View File
@@ -184,7 +184,7 @@ class upgrade extends control
$title = $this->lang->upgrade->toPMS15Guide;
if(isset($this->config->maxVersion))
{
$this->lang->upgrade->to15Desc = str_replace('15', $this->lang->maxName, $this->lang->upgrade->to15Desc);
$this->lang->upgrade->to15Desc = str_replace('15.0', $this->lang->maxName, $this->lang->upgrade->to15Desc);
$title = $this->lang->upgrade->toMAXGuide;
}
elseif(isset($this->config->bizVersion))
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 200 KiB

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.
Binary file not shown.