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))