Merge branch 'master' into story

This commit is contained in:
sunguangming
2024-05-29 15:44:14 +08:00
204 changed files with 1282 additions and 593 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
"pkg": {
"xuanxuan": {
"gitVersion": "43de52ba50fdb43ac1bc89e623e29e3ecdd4cce1",
"version": "8.1"
"version": "8.1.1"
},
"zentaoext": {
"gitRepo": "easycorp/zentaoext"
+2 -1
View File
@@ -16,7 +16,7 @@ if(!class_exists('config')){class config{}}
if(!function_exists('getWebRoot')){function getWebRoot(){}}
/* 基本设置。Basic settings. */
$config->version = '20.1.0'; // ZenTaoPHP的版本。 The version of ZenTaoPHP. Don't change it.
$config->version = '20.1.0'; // ZenTaoPHP的版本。 The version of ZenTaoPHP. Don't change it.
$config->liteVersion = '1.2'; // 迅捷版版本。 The version of Lite.
$config->charset = 'UTF-8'; // ZenTaoPHP的编码。 The encoding of ZenTaoPHP.
$config->cookieLife = time() + 2592000; // Cookie的生存时间。The cookie life time.
@@ -25,6 +25,7 @@ $config->webRoot = ''; // URL根目录。 The root
$config->customSession = false; // 是否开启自定义session的存储路径。Whether custom the session save path.
$config->edition = 'open'; // 设置系统的edition,可选值:open|biz|max。Set edition, optional: open|biz|max.
$config->tabSession = false; // 是否开启浏览器新标签独立session.
$config->clientCache = false; // 是否开启客户端缓存。Whether enable client cache or not.
/* 框架路由相关设置。Routing settings. */
$config->requestType = 'PATH_INFO'; // 请求类型:PATH_INFO|PATHINFO2|GET。 The request type: PATH_INFO|PATH_INFO2|GET.
+1 -1
View File
@@ -358,7 +358,7 @@ $filter->execution->story->cookie['storyModuleParam'] = 'int';
$filter->execution->story->cookie['storyProductParam'] = 'int';
$filter->execution->story->cookie['storyBranchParam'] = 'int';
$filter->execution->story->cookie['executionStoryOrder'] = 'code';
$filter->execution->export->cookie['checkedItem'] = 'reg::checked';
$filter->execution->export->cookie['checkedItem'] = 'reg::paramValue';
$filter->execution->kanban->cookie['taskToOpen'] = 'int';
$filter->execution->all->cookie['showExecutionBatchEdit'] = 'int';
$filter->execution->tree->cookie['showStory'] = 'int';
@@ -7,5 +7,5 @@ window.waitDom('input[name=PM]', function(){ $('input[name=PM]').closest('div.fo
window.waitDom('input[name=PO]', function(){ $('input[name=PO]').closest('div.form-group').hide();})
window.waitDom('input[name=QD]', function(){ $('input[name=QD]').closest('div.form-group').hide();})
window.waitDom('input[name=RD]', function(){ $('input[name=RD]').closest('div.form-group').hide();})
window.waitDom('.form-group.linkProduct', function(){ $('.form-group.linkProduct').closest('div.form-row').hide();})
window.waitDom('[data-name=productsBox]', function(){ $('[data-name=productsBox]').hide();})
</script>
@@ -101,7 +101,7 @@ $fnBuildSingleCard = function($kanban) use ($executionActions, $lang, $kanbanvie
div(setClass('kanban-desc'), html($kanban->desc)),
row
(
setClass('kanban-footer'),
setClass('kanban-footer bottom-2'),
set::style(array('position' => 'relative')),
cell
(
@@ -21,11 +21,12 @@ foreach($fields as $fieldKey => $fieldConfig)
}
/* Generate fields for the batch create form. */
$fnGenerateFields = function() use ($lang, $fields, $config)
$fnGenerateFields = function() use ($lang, $fields, $stories, $config)
{
/* Generate fields with the appropriate properties. */
$items = array();
$items[] = array('name' => 'id', 'label' => $lang->idAB, 'control' => 'index', 'width' => '32px');
if($stories) $items[] = array('name' => 'uploadImage', 'label' => '', 'control' => 'hidden', 'hidden' => true);
$cols = array_merge($items, array_map(function($name, $field)
{
@@ -51,6 +52,7 @@ formBatchPanel
(
set::id('dataform'),
set::ajax(array('beforeSubmit' => jsRaw('clickSubmit'))),
$stories ? set::data($stories) : null,
set::title($storyID ? $storyTitle . $lang->hyphen . $this->lang->story->subdivide : $this->lang->story->batchCreate),
set::uploadParams('module=story&params=' . helper::safe64Encode("productID=$productID&branch=$branch&moduleID=$moduleID&storyID=$storyID&executionID=$executionID&plan=&type=$type")),
set::pasteField('title'),
@@ -68,7 +68,7 @@ function toggleXuanClient()
/* Handle chat notice change */
function handleXuanNoticeChange(notice)
{
$('#chatNoticeBadge').toggleClass('show', !!notice.count).text(notice.count);
$('#chat-btn .notice-badge').toggleClass('show', !!notice.count).text(notice.count);
}
/* Handle client route change */
+41
View File
@@ -70,4 +70,45 @@ class fixer extends baseFixer
return $value;
}
/**
* 对工作流配置的日期格式字段赋予NULL默认值。
* Assign a default value of NULL to the date format field of the workflow configuration.
*
* @param string $fieldName
* @access public
* @return mixed
*/
public function get(string $fields = ''): mixed
{
global $config, $app;
if($config->edition != 'open' && empty($app->installing))
{
$moduleName = $moduleName ? $moduleName : $app->getModuleName();
$methodName = $methodName ? $moduleName : $app->getMethodName();
$flow = $app->control->loadModel('workflow')->getByModule($moduleName);
if(!$flow) return parent::get($fields);
$action = $app->control->loadModel('workflowaction')->getByModuleAndAction($flow->module, $methodName);
if(!$action || $action->extensionType != 'extend') return parent::get($fields);
$fieldList = $app->control->workflowaction->getFields($flow->module, $action->action);
$layouts = $app->control->loadModel('workflowlayout')->getFields($moduleName, $methodName);
if($layouts)
{
foreach($fieldList as $key => $field)
{
if($field->buildin || !$field->show || !isset($layouts[$field->field])) continue;
if($field->control == 'date' || $field->control == 'datetime')
{
if(empty($this->data->{$field->field})) $this->data->{$field->field} = NULL;
}
}
}
}
return parent::get($fields);
}
}
-1
View File
@@ -193,7 +193,6 @@ class gitlabRepo
if(count($branchList) < $params['per_page']) break;
}
if(empty($branches) and empty($default)) $branches['master'] = 'master';
asort($branches);
$branches = $default + $branches;
+3
View File
@@ -188,6 +188,9 @@ class gogsRepo
*/
public function createBranch($branchName = '', $ref = 'master')
{
$branches = $this->branch();
if(isset($branches[$branchName])) return array('result' => 'fail', 'message' => 'Branch is exists');
chdir($this->root);
execCmd(escapeCmd("{$this->client} stash"));
$res = execCmd(escapeCmd("{$this->client} checkout -b {$branchName} origin/{$ref}"), 'array');
+2
View File
@@ -207,8 +207,10 @@ function thinkOptions(): thinkOptions{return createWg('thinkOptions', func_get_a
function thinkTableInput(): thinkTableInput {return createWg('thinkTableInput', func_get_args());}
function thinkInput(): thinkInput {return createWg('thinkInput', func_get_args());}
function thinkStepMenu(): thinkStepMenu {return createWg('thinkStepMenu', func_get_args());}
function thinkResult(): thinkResult {return createWg('thinkResult', func_get_args());}
function thinkCover(): thinkCover {return createWg('thinkCover', func_get_args());}
function affected(): affected {return createWg('affected', func_get_args());}
function thinkPffa(): thinkPffa {return createWg('thinkPffa', func_get_args());}
if(is_dir(__DIR__ . DS . 'wg' . DS . 'boardeditor'))
{
+1 -1
View File
@@ -44,7 +44,7 @@ class backBtn extends btn
$backs = array(
'task' => 'execution-task,my-work,my-contribute,execution-tree,execution-grouptask,project-execution,product-track,repo-view,story-change,execution-kanban,execution-taskkanban,my-index,feedback-adminview,projectstory-track',
'story' => 'product-browse,projectstory-story,execution-story,my-work,my-contribute,productplan-view,build-view,projectbuild-view,product-track,repo-view,testcase-zerocase,my-index,feedback-adminview,projectstory-track',
'story' => 'product-browse,projectstory-story,execution-story,my-work,my-contribute,productplan-view,build-view,projectbuild-view,product-track,repo-view,testcase-zerocase,my-index,feedback-adminview,projectstory-track,task-view',
'bug' => 'bug-browse,project-bug,my-work,my-contribute,execution-bug,bug-view,qa-index,execution-task,product-track,execution-task,task-view,repo-view,story-change,repo-review,feedback-adminview,my-index,projectstory-track',
'testcase' => 'testcase-browse,project-testcase,my-work,my-contribute,execution-testcase,testtask-cases,testsuite-view,product-browse,testcase-view,qa-index,caselib-browse,product-track,story-change,my-index,projectstory-track',
'testsuite' => 'testsuite-browse,testsuite-view,testtask-cases,my-index',
+5 -1
View File
@@ -28,6 +28,9 @@ class chatBtn extends wg
#xuan-chat-view .unconfigured {top: 50px;}
#reload-ai-chat.disabled {cursor: wait; color: #999!important;}
#chat-btn .notice-badge {position: absolute; bottom: 2px; right: 1px; line-height: 14px; height: 14px; min-width: 14px; text-align: center; display: inline-block; font-size: 12px; border-radius: 7px; opacity: 0; transform: scale(0); transition: .2s; transition-property: transform, opacity; padding: 0 2px;}
#chat-btn .notice-badge.show {opacity: 1; transform: scale(1);}
CSS;
}
@@ -154,7 +157,8 @@ class chatBtn extends wg
setClass('ghost px-1'),
set('square', false),
set::icon(img(set('src', 'static/svg/ai-chat.svg'))),
set('onclick', 'window.toggleChatContainer()')
set('onclick', 'window.toggleChatContainer()'),
span(setClass(array('badge', 'bg-danger', 'text-white', 'notice-badge')))
)
);
}
+4 -4
View File
@@ -24,13 +24,13 @@ class demandBasicInfo extends wg
$productList = '';
$mailtoList = '';
foreach(explode(',', $demand->product) as $product) $productList .= zget($products, $product) . ',';
foreach(explode(',', $demand->mailto) as $user) $mailtoList .= zget($users, $user) . ' ';
foreach(explode(',', $demand->product) as $product) $productList .= zget($products, $product) . ', ';
foreach(explode(',', $demand->mailto) as $user) $mailtoList .= zget($users, $user) . ', ';
$items = array();
$items[$lang->demand->pool] = zget($demandpools, $demand->pool, '');
$items[$lang->demand->status] = zget($lang->demand->statusList, $demand->status);
$items[$lang->demand->product] = trim($productList, ',') ? trim($productList, ',') : $lang->demand->undetermined;
$items[$lang->demand->product] = trim($productList, ', ') ? trim($productList, ', ') : $lang->demand->undetermined;
$items[$lang->demand->pri] = array('control' => 'pri', 'text' => $lang->demand->priList, 'pri' => $demand->pri);
$items[$lang->demand->category] = zget($lang->demand->categoryList, $demand->category);
$items[$lang->demand->source] = zget($lang->demand->sourceList, $demand->source);
@@ -40,7 +40,7 @@ class demandBasicInfo extends wg
$items[$lang->demand->feedbackedBy] = $demand->feedbackedBy;
$items[$lang->demand->email] = $demand->email;
$items[$lang->demand->keywords] = $demand->keywords;
$items[$lang->demand->mailto] = $mailtoList;
$items[$lang->demand->mailto] = trim($mailtoList, ', ');
return $items;
}
+1 -1
View File
@@ -100,7 +100,7 @@ class detailCard extends wg
return new entityTitle
(
setClass('panel-title'),
setClass('panel-title flex-1 w-0'),
set::id($objectID),
set::title($title),
set::color($color),
+2 -2
View File
@@ -6,9 +6,9 @@ window.setMultipleCell = function(value, pairs, delimiter)
pairs = JSON.parse(pairs);
const result = [];
const data = $.isArray(value) ? value : value.split(',');
$.each(data, function(index, value)
$.each(data, function(_, value)
{
if(value) result.push(pairs[value]);
if(value && pairs[value]) result.push(pairs[value]);
});
return result.join(delimiter);
};
+25 -16
View File
@@ -6,20 +6,28 @@ require_once dirname(__DIR__) . DS . 'textarea' . DS . 'v1.php';
class editor extends wg
{
protected static $version = '0.12.0'; // Keep in sync with ZenEditor release.
protected static array $defineProps = array(
'uploadUrl?: string', // 图片上传链接
'placeholder?: string=""', // 占位文本
'fullscreenable?: bool=true', // 是否可全屏
'resizable?: bool=true', // 是否可拖拽调整大小
'exposeEditor?: bool=true', // 是否将编辑器实例挂载到 `window.$zenEditors`
'size?: string="sm"', // 尺寸,可选值 'sm', 'lg', 'full'
'hideMenubar?: bool=false', // 是否隐藏菜单栏
'bubbleMenu?: bool=false', // 是否启用浮动菜单
'menubarMode?: string="compact"', // 菜单栏模式,可选值 'compact', 'full'
'locale?: string', // 语言,可选值 'zh', 'en',默认跟随浏览器,也可以是自定义的语言项 JSON,详见 ZenEditor 文档
'value?: string', // 初始内容
'templateType?: string', // 模板类型
'uid?: string' // 图片上传 uid
'uploadUrl?: string', // 图片上传链接
'placeholder?: string=""', // 占位文本
'fullscreenable?: bool=true', // 是否可全屏
'resizable?: bool=true', // 是否可拖拽调整大小
'exposeEditor?: bool=true', // 是否将编辑器实例挂载到 `window.$zenEditors`
'size?: string="sm"', // 尺寸,可选值 'sm', 'lg', 'full'
'hideMenubar?: bool=false', // 是否隐藏菜单栏
'hideUI?: bool=false', // 是否隐藏整个编辑器 UI
'readonly?: bool=false', // 是否只读
'bubbleMenu?: bool=false', // 是否启用浮动菜单
'slashMenu?: bool=false', // 是否启用 `/` 菜单
'menubarMode?: string="compact"', // 菜单栏模式,可选值 'basic', 'compact', 'full'
'locale?: string', // 语言,可选值 'zh', 'en',默认跟随浏览器,也可以是自定义的语言项 JSON,详见 ZenEditor 文档
'markdown?: bool=false', // 是否启用 Markdown 模式,若启用,为了兼容性将会隐藏一些功能
'neglectDefaultTextStyle?: bool=false', // 是否不赋予默认的文本样式
'preferHardBreak?: bool=false', // 是否优先使用硬回车而不是新段落
'value?: string', // 初始内容
'templateType?: string', // 模板类型
'uid?: string' // 图片上传 uid
);
protected static string $css = <<<CSS
@@ -45,8 +53,9 @@ class editor extends wg
public static function getPageJS(): ?string
{
$content = file_get_contents(__DIR__ . DS . 'js' . DS . 'v1.js');
$content .= '$.getLib(\'zen-editor/zen-editor.esm.js\', {type: "module"}, () => {document.body.dataset.loadedEditor = true;});';
$version = self::$version;
$content = file_get_contents(__DIR__ . DS . 'js' . DS . 'v1.js');
$content .= "$.getLib('zen-editor/zen-editor.esm.js?v=$version', {type: 'module'}, () => {document.body.dataset.loadedEditor = true;});";
return $content;
}
@@ -92,7 +101,7 @@ class editor extends wg
$this->prop('size') === 'full' ? setStyle('height', '100%') : setClass('h-auto')
);
$props = $this->props->pick(array('uploadUrl', 'placeholder', 'fullscreenable', 'resizable', 'exposeEditor', 'size', 'hideMenubar', 'bubbleMenu', 'menubarMode', 'collaborative', 'hocuspocus', 'docName', 'username', 'userColor'));
$props = $this->props->pick(array('uploadUrl', 'placeholder', 'fullscreenable', 'resizable', 'exposeEditor', 'size', 'hideMenubar', 'hideUI', 'readonly', 'bubbleMenu', 'slashMenu', 'menubarMode', 'locale', 'markdown', 'neglectDefaultTextStyle', 'preferHardBreak'));
foreach($props as $key => $value)
{
if($key == 'placeholder' && empty($value)) $value = $lang->noticePasteImg;
+10
View File
@@ -135,6 +135,16 @@ class fileSelector extends wg
}
$checkFiles = $checkFiles->do('return file');
$this->setProp('onAdd', $checkFiles);
$removeFile = jsCallback('file')
->do(<<<'JS'
if(file.url != undefined && file.file == undefined)
{
const $form = $(event.target).closest('form');
if($form.length > 0) $('<input />').attr('type', 'hidden').attr('name', 'deleteFiles[' + file.id + ']').attr('value', file.id).appendTo($form);
}
JS)->do('return file;');
$this->setProp('onRemove', $removeFile);
}
/**
+1
View File
@@ -276,6 +276,7 @@ class header extends wg
if($config->edition == 'open') unset($lang->createIcons['effort']);
if($config->systemMode == 'light') unset($lang->createIcons['program']);
if(empty($config->board)) unset($lang->createIcons['board']);
/* Check whether the creation permission is available, and print create buttons. */
$items = array();
+4 -3
View File
@@ -25,7 +25,8 @@ class moduleMenu extends wg
'tree?: array',
'checkOnClick?: bool|string',
'appendSettingItems?: array',
'onCheck?: function'
'onCheck?: function',
'toggleSidebar?: bool=true',
);
protected static array $defineBlocks = array
@@ -310,7 +311,7 @@ class moduleMenu extends wg
),
$this->buildActions(),
$this->block('footer'),
row
$this->prop('toggleSidebar') ? row
(
setClass('justify-end p-1'),
btn
@@ -321,7 +322,7 @@ class moduleMenu extends wg
set::hint($app->lang->collapse),
on::click()->do('$this.closest(".sidebar").sidebar("toggle");')
)
),
) : null,
$isInSidebar && !empty($header) ? on::init()->do('$("#mainContainer").addClass("has-module-menu-header")') : null
),
);
+66
View File
@@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace zin;
require_once dirname(__DIR__) . DS . 'datalist' . DS . 'v1.php';
class researchtaskBasicInfo extends wg
{
protected static array $defineProps = array
(
'task' => '?object', // 当前任务。
'product' => '?object', // 当前产品。
'execution' => '?object', // 当前执行。
'users' => '?array', // 用户信息。
'users' => '?array', // 用户列表。
'statusText' => '?string', // 状态信息。
);
protected function getItems(): array
{
global $lang, $config;
$task = $this->prop('task', data('task'));
if(!$task) return array();
$execution = $this->prop('execution', data('execution'));
$users = $this->prop('users', data('users'));
$statusText = $this->prop('statusText', $task->status);
$items = array();
if($execution->multiple)
{
$items[$lang->researchtask->execution] = array('control' => 'text', 'text' => $execution->name, 'title' => $execution->name);
}
$items[$lang->researchtask->assignedTo] = $task->assignedTo ? zget($users, $task->assignedTo) . $lang->at . formatTime($task->assignedDate) : '';
$items[$lang->researchtask->status] = array
(
'control' => 'status',
'class' => 'task-status',
'status' => $task->status,
'text' => $statusText
);
$items[$lang->researchtask->progress] = "$task->progress %";
$items[$lang->researchtask->pri] = array
(
'control' => 'pri',
'pri' => $task->pri,
'text' => $lang->researchtask->priList
);
return $items;
}
protected function build()
{
return new datalist
(
set::className('task-basic-info'),
set::items($this->getItems())
);
}
}
+16
View File
@@ -1,3 +1,10 @@
/**
* 切换选中的交互。
* Toggle the display of checked interaction.
*
* @access public
* @return void
*/
window.toggleChecked = function()
{
if($(this).data('type') == 'radio')
@@ -16,6 +23,8 @@ window.toggleChecked = function()
$(this).toggleClass('is-checked', $(this).find('input').is(':checked'));
}
/* 如果当前项是其他的话,选中时展示输入框,类型为单选时,选择其他项隐藏输入框,多选时取消选中隐藏输入框。*/
/* If the current item is Other, display the input box when selected. If the type is Single Choice, select Other to hide the input box. If Multiple Choice, uncheck the hide input box. */
if($(this).hasClass('has-input'))
{
$(this).find('textarea').toggleClass('hidden', !$(this).find('input').prop('checked'));
@@ -27,6 +36,13 @@ window.toggleChecked = function()
}
}
/**
* 输入其他内容。
* Input other content.
*
* @access public
* @return void
*/
window.inputOther = function()
{
this.style.height = 'auto';
-4
View File
@@ -1,4 +0,0 @@
window.changeIsRequired = function(e)
{
$('.selectable-rows').toggleClass('hidden', e.target.value == 0);
}
+2 -7
View File
@@ -16,11 +16,6 @@ class thinkCheckbox extends thinkRadio
'maxCount?: string',
);
public static function getPageJS(): string
{
return file_get_contents(__DIR__ . DS . 'js' . DS . 'v1.js');
}
protected function buildFormItem(): array
{
global $lang, $app;
@@ -45,7 +40,7 @@ class thinkCheckbox extends thinkRadio
setClass($className),
input
(
set::placeholder($lang->thinkstep->inputContent),
set::placeholder($lang->thinkstep->placeholder->inputContent),
set::type('number'),
set::min(1),
set::name('options[minCount]'),
@@ -58,7 +53,7 @@ class thinkCheckbox extends thinkRadio
setClass($className),
input
(
set::placeholder($lang->thinkstep->inputContent),
set::placeholder($lang->thinkstep->placeholder->inputContent),
set::type('number'),
set::min(1),
set::name('options[maxCount]'),
+1 -1
View File
@@ -28,7 +28,7 @@ class thinkInput extends thinkQuestion
set::name('result'),
set::required($required),
set::value($value),
set::placeholder($lang->thinkstep->pleaseInput)
set::placeholder($lang->thinkstep->placeholder->pleaseInput)
);
return $detailWg;
}
+3 -3
View File
@@ -67,12 +67,12 @@ class thinkOptions extends wg
(
setID($id),
setClass('think-options w-full'),
div(set::className('think-options-body')),
div(setClass('think-options-body')),
zui::thinkOptions
(
set::_to("#$id"),
set::deleteStepTip($deleteStepTip),
set::enterPlaceholder($lang->thinkstep->pleaseInput),
set::enterPlaceholder($lang->thinkstep->placeholder->pleaseInput),
set($this->props->pick(array('name', 'data')))
),
$showOther ? div
@@ -92,7 +92,7 @@ class thinkOptions extends wg
(
setClass('h-full w-full flex items-center text-gray-400 pl-2.5'),
setStyle('background', 'rgba(244, 245, 247, .7)'),
$lang->thinkstep->pleaseInput,
$lang->thinkstep->placeholder->pleaseInput,
),
),
div
+32
View File
@@ -0,0 +1,32 @@
.w-3\/7 {width: 42.857142%}
.w-2\/7 {width: 28.571429%}
.text-blue {color: #2294FB;}
.bg-blue {background-color: rgba(34, 148, 251, var(--tw-bg-opacity));}
.triangle {
width: 0;
height: 0;
}
.triangle-up {
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-bottom: 16px solid rgba(var(--color-important-500-rgb));;
}
.triangle-down {
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 16px solid #2294FB;
}
.triangle-left {
border-top: 10px solid transparent;
border-bottom: 10px solid transparent;
border-right: 16px solid rgba(var(--color-special-500-rgb));;
}
.triangle-right {
border-top: 10px solid transparent;
border-bottom: 10px solid transparent;
border-left: 16px solid rgba(var(--color-success-500-rgb));
}
+157
View File
@@ -0,0 +1,157 @@
<?php
declare(strict_types=1);
namespace zin;
/**
* 思引波特五力模型部件类。
* thinmory porter's five forces model widget class.
*/
class thinkPffa extends wg
{
protected static array $defineProps = array(
'item: object'
);
public static function getPageCSS(): ?string
{
return file_get_contents(__DIR__ . DS . 'css' . DS . 'v1.css');
}
protected function build()
{
global $lang;
return div
(
setClass('flex items-center'),
div
(
setClass('w-2/7'),
span(setClass('text-gray-400 text-sm'),$lang->thinkwizard->block . $lang->thinkwizard->blockList[2]),
div
(
setClass('flex items-center mt-1'),
div
(
setClass('bg-white w-full px-2 py-2.5 border border-gray-200 h-28 overflow-auto'),
span(setClass('text-success text-sm'),$lang->thinkwizard->unAssociated),
div
(
setClass('flex flex-wrap'),
div(setClass('w-8 h-8 bg-success bg-opacity-20 mt-1 mr-2')),
div(setClass('w-8 h-8 bg-success bg-opacity-20 mt-1 mr-2'))
),
div(setClass('text-center text-sm leading-tight text-gray-300 mt-1'),$lang->thinkwizard->pffaGroundText[2])
),
div
(
setClass('triangle triangle-right')
)
)
),
div
(
setClass('w-3/7'),
div
(
span(setClass('text-gray-400 text-sm'),$lang->thinkwizard->block . $lang->thinkwizard->blockList[1]),
div
(
setClass('flex justify-center flex-wrap mt-1'),
div
(
setClass('bg-white w-full px-2 py-2.5 border border-gray-200 h-28 overflow-auto'),
span(setClass('text-blue text-sm'),$lang->thinkwizard->unAssociated),
div
(
setClass('flex flex-wrap'),
div(setClass('w-8 h-8 bg-blue bg-opacity-20 mt-1 mr-2')),
div(setClass('w-8 h-8 bg-blue bg-opacity-20 mt-1 mr-2')),
div(setClass('w-8 h-8 bg-blue bg-opacity-20 mt-1 mr-2')),
div(setClass('w-8 h-8 bg-blue bg-opacity-20 mt-1 mr-2'))
),
div(setClass('text-center text-sm leading-tight text-gray-300 mt-1'),$lang->thinkwizard->pffaGroundText[1])
),
div
(
setClass('triangle triangle-down')
)
)
),
div
(
span(setClass('text-gray-400 text-sm'),$lang->thinkwizard->block . $lang->thinkwizard->blockList[5]),
div
(
setClass('flex justify-center flex-wrap mt-1'),
div
(
setClass('bg-white w-full px-2 py-2.5 border border-gray-200 h-28 overflow-auto'),
span(setClass('text-warning text-sm'),$lang->thinkwizard->unAssociated),
div
(
setClass('flex flex-wrap'),
div(setClass('w-8 h-8 bg-warning bg-opacity-20 mt-1 mr-2')),
div(setClass('w-8 h-8 bg-warning bg-opacity-20 mt-1 mr-2')),
div(setClass('w-8 h-8 bg-warning bg-opacity-20 mt-1 mr-2')),
div(setClass('w-8 h-8 bg-warning bg-opacity-20 mt-1 mr-2')),
),
div(setClass('text-center text-sm leading-tight text-gray-300 mt-1'),$lang->thinkwizard->pffaGroundText[5])
)
)
),
div
(
setClass('relative pt-3.5'),
span(setClass('absolute text-gray-400 text-sm'),$lang->thinkwizard->block . $lang->thinkwizard->blockList[4]),
div
(
setClass('flex justify-center flex-wrap mt-1'),
div
(
setClass('triangle triangle-up')
),
div
(
setClass('bg-white w-full px-2 py-2.5 border border-gray-200 h-28 overflow-auto'),
span(setClass('text-important text-sm'),$lang->thinkwizard->unAssociated),
div
(
setClass('flex flex-wrap'),
div(setClass('w-8 h-8 bg-important bg-opacity-20 mt-1 mr-2')),
div(setClass('w-8 h-8 bg-important bg-opacity-20 mt-1 mr-2')),
div(setClass('w-8 h-8 bg-important bg-opacity-20 mt-1 mr-2')),
div(setClass('w-8 h-8 bg-important bg-opacity-20 mt-1 mr-2')),
),
div(setClass('text-center text-sm leading-tight text-gray-300 mt-1'),$lang->thinkwizard->pffaGroundText[4])
)
)
)
),
div
(
setClass('w-2/7'),
span(setClass('text-gray-400 text-sm ml-4'),$lang->thinkwizard->block . $lang->thinkwizard->blockList[3]),
div
(
setClass('flex items-center mt-1'),
div
(
setClass('triangle triangle-left')
),
div
(
setClass('bg-white w-full px-2 py-2.5 border border-gray-200 h-28 overflow-auto'),
span(setClass('text-special text-sm'),$lang->thinkwizard->unAssociated),
div
(
setClass('flex flex-wrap'),
div(setClass('w-8 h-8 bg-special bg-opacity-20 mt-1 mr-2')),
div(setClass('w-8 h-8 bg-special bg-opacity-20 mt-1 mr-2'))
),
div(setClass('text-center text-sm leading-tight text-gray-300 mt-1'),$lang->thinkwizard->pffaGroundText[3])
)
)
)
);
}
}
+1 -1
View File
@@ -79,7 +79,7 @@ class thinkRadio extends thinkQuestion
set::inline(true),
set::value($required),
set::items($requiredItems),
$questionType == 'checkbox' ? bind::change('changeIsRequired(event)') : null
$questionType == 'checkbox' ? on::change()->toggleClass('.selectable-rows', 'hidden', 'target.value == 0') : null
)
),
$this->children()
+29
View File
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace zin;
class thinkResult extends wg
{
protected static array $defineProps = array(
'wizard: object', // 模型数据
'mode?: string="view"', // 模型展示模式 preview|view
);
protected function buildModel(): wg|node
{
// TODO
return div('模型区域');
}
protected function build(): wg|node
{
$wizard = $this->prop('wizard');
return div
(
setClass('think-result-content col items-center px-7 py-6'),
div(setClass('w-full h-10 text-center text-md py-2.5 ellipsis overflow-hidden whitespace-nowrap'), $wizard->introduction),
div(setClass('my-4'), setStyle('min-height', '200px'), $this->buildModel()),
div(setClass('w-full text-center text-md py-2.5'), html(htmlspecialchars_decode($wizard->suggestion)))
);
}
}
+9 -8
View File
@@ -9,12 +9,11 @@ class thinkStep extends wg
'action?: string="detail"',
'addType?: string',
'isRun?: bool=false', // 是否是分析活动
'defaultFields: array', // 默认的配置项
);
protected function buildBody(): wg|array
{
list($item, $action, $addType, $isRun, $defaultFields) = $this->prop(array('item', 'action', 'addType', 'isRun', 'defaultFields'));
list($item, $action, $addType, $isRun) = $this->prop(array('item', 'action', 'addType', 'isRun'));
$step = $addType ? null : $item;
$questionType = $addType ? $addType : ($item->options->questionType ?? '');
@@ -23,7 +22,7 @@ class thinkStep extends wg
if($questionType === 'input') return thinkInput(set::step($step), set::questionType('input'), set::mode($action), set::isRun($isRun));
if($questionType === 'radio') return thinkRadio(set::step($step), set::questionType('radio'), set::mode($action), set::isRun($isRun));
if($questionType === 'checkbox') return thinkCheckbox(set::step($step), set::questionType('checkbox'), set::mode($action), set::isRun($isRun));
if($questionType === 'tableInput') return thinkTableInput(set::step($step), set::questionType('tableInput'), set::mode($action), set::isRun($isRun), set::defaultFields($defaultFields));
if($questionType === 'tableInput') return thinkTableInput(set::step($step), set::questionType('tableInput'), set::mode($action), set::isRun($isRun));
return array();
}
@@ -39,6 +38,8 @@ class thinkStep extends wg
$typeLang = $action . 'Step';
$type = $addType ? $addType : ($basicType == 'question' ? $item->options->questionType : $basicType);
$title = $action == 'detail' ? ($lang->thinkstep->$basicType . $lang->thinkstep->info) : sprintf($lang->thinkstep->formTitle[$type], $lang->thinkstep->$typeLang);
$canEdit = common::hasPriv('thinkstep', 'edit');
$canDelete = common::hasPriv('thinkstep', 'delete');
return div
(
@@ -55,17 +56,17 @@ class thinkStep extends wg
setStyle(array('min-width' => '48px')),
btnGroup
(
btn
$canEdit ? btn
(
setClass('btn ghost text-gray w-5 h-5'),
set::icon('edit'),
set::url(createLink('thinkstep', 'edit', "stepID={$item->id}")),
),
!$item->existNotNode ? btn
) : null,
$canDelete ? (!$item->existNotNode ? btn
(
setClass('btn ghost text-gray w-5 h-5 ml-1 ajax-submit'),
set::icon('trash'),
setData('url', createLink('thinkstep', 'ajaxDelete', "stepID={$item->id}")),
setData('url', createLink('thinkstep', 'delete', "stepID={$item->id}")),
setData('confirm', $lang->thinkstep->deleteTips[$basicType])
) : btn
(
@@ -76,7 +77,7 @@ class thinkStep extends wg
'data-title' => $lang->thinkstep->cannotDeleteNode,
'data-placement' => 'bottom-start',
))
)
)) : null
)
)
),
+5 -5
View File
@@ -51,8 +51,8 @@ class thinkStepBase extends wg
}
if($options->required && $questionType == 'tableInput')
{
if($options->isSupportAdd) $tips = sprintf($lang->thinkrun->tableInputTitle->notSupportAdd, count(get_object_vars($options->fields)), $options->requiredRows);
if(!$options->isSupportAdd) $tips = sprintf($lang->thinkrun->tableInputTitle->supportAdd, $options->requiredRows);
if($options->supportAdd) $tips = sprintf($lang->thinkrun->tableInputTitle->notSupportAdd, count(get_object_vars($options->fields)), $options->requiredRows);
if(!$options->supportAdd) $tips = sprintf($lang->thinkrun->tableInputTitle->supportAdd, $options->requiredRows);
}
}
@@ -120,7 +120,7 @@ class thinkStepBase extends wg
setClass('is-required'),
set::value($title ?? ''),
set::name('title'),
set::placeholder($lang->thinkstep->inputContent)
set::placeholder($lang->thinkstep->placeholder->inputContent)
)
),
formGroup
@@ -131,7 +131,7 @@ class thinkStepBase extends wg
(
setClass('desc'),
set::name('desc'),
set::placeholder($lang->thinkstep->pleaseInput),
set::placeholder($lang->thinkstep->placeholder->pleaseInput),
html($desc ?? ''),
set::rows(3)
)
@@ -143,7 +143,7 @@ class thinkStepBase extends wg
{
global $lang;
$actions = array(array(
'text' => $lang->thinkmodel->save,
'text' => $lang->save,
'btnType' => 'submit',
'class' => 'primary h-7 ring-0 submit-btn px-4',
'style' => array('min-width' => '0')
+47 -4
View File
@@ -1,3 +1,5 @@
/* 进入页面,树结构为展开状态,添加节点及完成后,树结构为收起状态。*/
/* Entering the page, the step tree is in an expanded state. After adding nodes and completing them, the step tree is in a collapsed state. */
setTimeout(function(){if(from && !localStorage.getItem('thinkTreeFrom')) localStorage.setItem('thinkTreeFrom', from);}, 15);
setTimeout(function()
{
@@ -12,7 +14,17 @@ setTimeout(function()
}, 60);
var thinkTreeInfo = {id: 0, wizard: 0};
window.addStep = function(id, addType)
/**
* 添加节点。
* Add a node.
*
* @param int id
* @param string addType
* @access public
* @return void
*/
window.addNode = function(id, addType)
{
const $element = $(`div[data-id='${id}']`).not('div[data-type=annex]');
const {wizard} = $element.data();
@@ -29,6 +41,8 @@ window.addStep = function(id, addType)
inputTpl += `<input id="stepName" class="form-control" data-id="${id}" data-parent="${parentID}" data-add-type="${addType}" data-wizard="${wizard}">`;
inputTpl += '</div></li>';
/* 当父节点为展开状态时,收起子节点。 */
/* When the parent node is in an expanded state, collapse the child node. */
if($element.parent().hasClass('is-nested-show')) $element.find('.tree-toggle').trigger('click');
setTimeout(function()
@@ -41,14 +55,25 @@ window.addStep = function(id, addType)
setTimeout(function()
{
$('#stepName').trigger('focus');
document.getElementById("stepName").addEventListener('blur', saveStep);
document.getElementById("stepName").addEventListener('keydown', function(e)
$("#stepName").on('blur', saveStep);
$("#stepName").on('keydown', function(e)
{
if(e.keyCode == 13) saveStep.call(this);
});
}, 1);
}
/**
* 添加问题。
* Add a question.
*
* @param int stepID 操作列所在的步骤id
* @param int parentID 父节点id
* @param string type 分析步骤类型 transition|question
* @param string questionType 问题类型 radio|checkbox|input|tableInput
* @access public
* @return void
*/
window.addQuestion = function(stepID, parentID, type, questionType = '')
{
$('.listitem').removeClass('selected');
@@ -60,8 +85,10 @@ window.addQuestion = function(stepID, parentID, type, questionType = '')
const level = parent == parentID ? $element.data('level') : $element.data('level') + 1;
const style = `style="margin-left: calc(${parent == parentID ? 12 : 3} * (-4px)); padding-left: 12px;"`;
const className = parent == parentID ? '' : 'mt-2';
const $title = `<li class="tree-item tree-item-uncreated ${className}"><div class="listitem item-inner tree-item-inner selected text-primary font-bold" ${style}>${untitledGroup[type]}</div></li>`;
const $title = `<li class="tree-item tree-item-uncreated ${className}"><div class="listitem item-inner tree-item-inner selected text-primary font-bold" ${style}>${untitledLangs[type]}</div></li>`;
/* 当父节点为收起状态时,展开子节点。 */
/* When the parent node is in a collapsed state, expand the child nodes. */
if(!$(`div[data-id='${parentID}']`).parent().hasClass('is-nested-show')) $(`div[data-id='${parentID}']`).find('.tree-toggle').trigger('click');
setTimeout(function()
@@ -69,11 +96,20 @@ window.addQuestion = function(stepID, parentID, type, questionType = '')
if($element.next('.tree').length == 0) $element.after(`<menu class="tree" level="${level}" data-type=${type} data-level="${level}"></menu>`);
$(`div[data-id='${stepID}']`).next('.tree').append($title);
loadPartial($.createLink('thinkstep', 'create', `wizardID=${wizard}&parent=${parentID}&type=${type}&questionType=${questionType}`), '.think-step');
/* 去掉节点收起状态。*/
/* Remove the node's collapsed state. */
localStorage.removeItem('thinkTreeInfo');
localStorage.removeItem('thinkTreeFrom');
}, 1);
}
/**
* 保存分析步骤。
* Save a step.
*
* @access public
* @return void
*/
window.saveStep = function()
{
const name = $(this).val();
@@ -102,6 +138,13 @@ window.saveStep = function()
});
}
/**
* 切换所有问题步骤的展示。
* Toggle the display of all question steps.
*
* @access public
* @return void
*/
var nodeElement = [];
window.toggleQuestionShow = function()
{
+45 -30
View File
@@ -44,12 +44,13 @@ class thinkStepMenu extends wg
{
if(!is_object($setting)) continue;
$canView = common::hasPriv('thinkstep', 'view');
$unClickable = $toggleNonNodeShow && $setting->id != $activeKey && $setting->type != 'node' && json_decode($setting->answer) == null;
$item = array(
'key' => $setting->id,
'text' => $setting->title,
'hint' => $unClickable ? $this->lang->thinkrun->error->unanswered :$setting->title,
'url' => $unClickable ? '' : $setting->url,
'url' => $unClickable || !$canView ? '' : $setting->url,
'data-id' => $setting->id,
'data-type' => $setting->type,
'data-parent' => $setting->parent,
@@ -79,14 +80,19 @@ class thinkStepMenu extends wg
$this->modules = $this->prop('modules');
$app->loadLang('thinkstep');
$untitledGroup = array('transition' => $lang->thinkstep->untitled . $lang->thinkstep->transition, 'question' => $lang->thinkstep->untitled . $lang->thinkstep->question);
jsVar('untitledGroup', $untitledGroup);
$untitledLangs = array('transition' => $lang->thinkstep->untitled . $lang->thinkstep->transition, 'question' => $lang->thinkstep->untitled . $lang->thinkstep->question);
jsVar('untitledLangs', $untitledLangs);
$this->setProp('items', $this->buildMenuTree(array(), 0));
}
private function getActions($item): array
{
$canCreate = common::hasPriv('thinkstep', 'create');
$canEdit = common::hasPriv('thinkstep', 'edit');
$canDelete = common::hasPriv('thinkstep', 'delete');
if(!$canCreate && !$canEdit && !$canDelete) return array();
$actions = array();
$moreBtn = $this->getOperateItems($item);
$actions[] = array(
@@ -116,35 +122,51 @@ class thinkStepMenu extends wg
}
}
$menus = array();
if($item->type == 'node') $menus[] = array(
'key' => 'addNode',
'icon' => 'add-chapter',
'text' => $this->lang->thinkstep->actions['sameNode'],
'onClick' => jsRaw("() => addStep({$item->id}, 'same')")
);
if($item->grade != 3 && $item->type == 'node' && $canAddChild) $menus[] = array(
'key' => 'addNode',
'icon' => 'add-sub-chapter',
'text' => $this->lang->thinkstep->actions['childNode'],
'onClick' => jsRaw("() => addStep({$item->id}, 'child')")
);
$canCreate = common::hasPriv('thinkstep', 'create');
$canEdit = common::hasPriv('thinkstep', 'edit');
$canDelete = common::hasPriv('thinkstep', 'delete');
$parentID = $item->type != 'node' ? $item->parent : $item->id;
$confirmTips = $this->lang->thinkstep->deleteTips[$item->type];
$menus = array();
$transitionAction = array();
if($canCreate)
{
if($item->type == 'node') $menus[] = array(
'key' => 'addNode',
'icon' => 'add-chapter',
'text' => $this->lang->thinkstep->actions['sameNode'],
'onClick' => jsRaw("() => addNode({$item->id}, 'same')")
);
if($item->grade != 3 && $item->type == 'node' && $canAddChild) $menus[] = array(
'key' => 'addNode',
'icon' => 'add-sub-chapter',
'text' => $this->lang->thinkstep->actions['childNode'],
'onClick' => jsRaw("() => addNode({$item->id}, 'child')")
);
$transitionAction[] = array('type' => 'divider');
$transitionAction[] = array(
'key' => 'transition',
'icon' => 'transition',
'text' => $this->lang->thinkstep->actions['transition'],
'onClick' => jsRaw("() => addQuestion({$item->id}, {$parentID}, 'transition')"),
);
}
$menus = array_merge($menus, array(
array(
$canEdit ? array(
'key' => 'editNode',
'icon' => 'edit',
'text' => $this->lang->thinkstep->actions['edit'],
'url' => createLink('thinkstep', 'edit', "stepID={$item->id}")
),
!$item->existNotNode ? array(
) : null,
$canDelete ? (!$item->existNotNode ? array(
'key' => 'deleteNode',
'icon' => 'trash',
'text' => $this->lang->thinkstep->actions['delete'],
'innerClass' => 'ajax-submit',
'data-url' => createLink('thinkstep', 'ajaxDelete', "stepID={$item->id}"),
'data-url' => createLink('thinkstep', 'delete', "stepID={$item->id}"),
'data-confirm' => $confirmTips,
) : array(
'key' => 'deleteNode',
@@ -154,17 +176,10 @@ class thinkStepMenu extends wg
'data-toggle' => 'tooltip',
'data-title' => $this->lang->thinkstep->cannotDeleteNode,
'data-placement' => 'right',
),
array('type' => 'divider'),
array(
'key' => 'transition',
'icon' => 'transition',
'text' => $this->lang->thinkstep->actions['transition'],
'onClick' => jsRaw("() => addQuestion({$item->id}, {$parentID}, 'transition')"),
),
));
)) : null
), $transitionAction);
if(($showQuestionOfNode && $item->type == 'node') || $item->hasSameQuestion || $item->type == 'question') $menus = array_merge($menus, array(
if($canCreate && (($showQuestionOfNode && $item->type == 'node') || $item->hasSameQuestion || $item->type == 'question')) $menus = array_merge($menus, array(
array('type' => 'divider'),
array(
'key' => 'radio',
+1 -1
View File
@@ -1,2 +1,2 @@
.form-horz .form-group {align-items: center;}
textarea.w-4\/5 {width: calc(80% - 52px);}
textarea.result-width {width: calc(80% - 52px);}
+30 -15
View File
@@ -1,18 +1,19 @@
window.changeSupportAdd = function(e)
{
$('.can-add-rows').toggleClass('hidden', e.target.value == 0);
}
window.changeIsRequired = function(e)
{
$('.required-rows').toggleClass('hidden', e.target.value == 0);
}
/**
* 处理输入框值变化。
* Handling input box value changes.
*
* @param Event e Event object
* @returns void
*/
window.changeInput = function(e)
{
const value = $(this).val();
const intValue = value < 1 ? 1 : parseInt(value, 10);
$(this).val(intValue);
}
/**
* 设置行号。
* Set line number.
*
* @access public
@@ -20,22 +21,29 @@ window.changeInput = function(e)
*/
function setLineIndex()
{
let index = 1;
let index = 0;
$('.rows-group').each(function()
{
const resultIndex = fieldsCount + index - 1;
$(this).find('[id^="customFields"]').attr('name', 'customFields[' + index + ']');
const resultIndex = fieldsCount + index;
$(this).find('[id^="customFields"]').attr('name', 'customFields[' + resultIndex + ']');
$(this).find('[id^="result"]').attr('name', 'result[' + resultIndex + ']');
$('.add-rows').toggleClass('disabled', index >= canAddRows);
$('.add-rows').toggleClass('disabled', index + 1 >= canAddRows);
index ++;
});
}
/**
* 添加行。
* Add rows.
*
* @param event $e
* @returns void
*/
window.addRow = function(e)
{
if($('.rows-group').length >= canAddRows) return;
var parentFormGroup = $(e.target || e).closest('.form-group');
var $newRow = $('.rows-template').clone();
const parentFormGroup = $(e.target || e).closest('.form-group');
const $newRow = $('.rows-template').clone();
$newRow.find('.btn-add').attr('onclick', 'addRow(this)');
$newRow.removeClass('rows-template').removeClass('hidden').addClass('rows-group');
$newRow.find('textarea').val('');
@@ -44,11 +52,18 @@ window.addRow = function(e)
setLineIndex();
}
/**
* 删除行。
* Delete rows.
*
* @access public
* @return void
*/
$(document).on('click', '.btn-delete', function() {
zui.Modal.confirm({message: deleteTip}).then((res) => {
if(res)
{
var $row = $(this).closest('.form-group');
const $row = $(this).closest('.form-group');
$row.remove();
if(canAddRows == 1) $('.add-rows').removeClass('disabled');
setLineIndex();
+31 -35
View File
@@ -12,7 +12,7 @@ class thinkTableInput extends thinkQuestion
protected static array $defineProps = array(
'requiredRows?: number=1', // 必填行数
'fields?: array', // 行标题
'isSupportAdd?: bool', // 是否支持用户添加行
'supportAdd?: bool', // 是否支持用户添加行
'canAddRows: number=1', // 可添加行数
);
@@ -31,25 +31,23 @@ class thinkTableInput extends thinkQuestion
{
global $lang;
$detailWg = parent::buildDetail();
list($step, $fields, $isSupportAdd, $canAddRows) = $this->prop(array('step', 'fields', 'isSupportAdd', 'canAddRows'));
list($step, $fields, $supportAdd, $canAddRows) = $this->prop(array('step', 'fields', 'supportAdd', 'canAddRows'));
if($step)
{
if(!empty($step->options->fields)) $step->options->fields = is_string($step->options->fields) ? explode(', ', $step->options->fields) : array_values((array)$step->options->fields);
if(!empty($step->options->customFields)) $step->options->customFields = is_string($step->options->customFields) ? explode(', ', $step->options->customFields) : array_values((array)$step->options->customFields);
$customFields = $step->options->customFields ?? array();
$fields = $step->options->fields ?? array();
$isSupportAdd = $step->options->isSupportAdd;
$supportAdd = $step->options->supportAdd;
$canAddRows = $step->options->canAddRows;
$answer = $step->answer;
$result = isset($answer->result) && !empty($answer->result) ? $answer->result : array();
$result = is_array($result) ? $result : get_object_vars($result);
$result = isset($answer->result) && !empty($answer->result) ? $answer->result : array();
$customFields = !empty($answer->customFields) ? get_object_vars($answer->customFields) : array();
}
jsVar('canAddRows', (int)$canAddRows);
jsVar('fieldsCount', count($fields));
jsVar('deleteTip', $lang->thinkrun->deleteTip);
jsVar('deleteTip', $lang->thinkrun->tips->delete);
$tableInputItems = array();
$disabledAdd = !empty($customFields) && (int)$canAddRows <= count($customFields);
foreach($fields as $index => $item)
{
$tableInputItems[] = formGroup
@@ -65,7 +63,7 @@ class thinkTableInput extends thinkQuestion
(
set::rows('2'),
set::name('result[' . $index . ']'),
setClass('mt-2 w-4/5'),
setClass('mt-2 result-width'),
set::value(!empty($result) && isset($result[$index]) ? $result[$index] : ''),
set::placeholder($lang->thinkrun->pleaseInput)
),
@@ -73,22 +71,20 @@ class thinkTableInput extends thinkQuestion
(
setClass('flex'),
setStyle(array('min-width' => '40px')),
($index == count($fields) - 1 && $isSupportAdd) ? icon
($index == count($fields) - 1 && $supportAdd) ? icon
(
'plus',
setClass('mr-1 btn-add ml-2 text-sm text-primary add-rows'),
on::click('addRow'),
set::title(sprintf($lang->thinkrun->addTips, $canAddRows)),
setClass('mr-1 btn-add ml-2 text-sm text-primary add-rows', $disabledAdd ? 'disabled' : ''),
on::click('addRow(event)'),
set::title(sprintf($lang->thinkrun->tips->add, $canAddRows)),
) : null,
)
);
}
if(!empty($customFields))
{
foreach($customFields as $index => $item)
{
$resultIndex = count($fields) + $index;
$tableInputItems[] = formGroup
(
setClass('flex rows-group flex-nowrap items-center'),
@@ -97,17 +93,17 @@ class thinkTableInput extends thinkQuestion
set::rows('2'),
setClass('mt-2 w-1/5'),
setID('customFields'),
set::name('customFields[' . $index + 1 . ']'),
set::name('customFields[' . $index . ']'),
set::value($item),
set::placeholder($lang->thinkrun->placeholder->rowTitle)
),
textarea
(
set::rows('2'),
setClass('mt-2 w-4/5 ml-2'),
setClass('mt-2 ml-2 result-width'),
setID('result'),
set::name('result[' . $resultIndex .']'),
set::value($result[$resultIndex] ?? ''),
set::name('result[' . $index .']'),
set::value($result[$index] ?? ''),
set::placeholder($lang->thinkrun->placeholder->rowContent)
),
div
@@ -117,9 +113,9 @@ class thinkTableInput extends thinkQuestion
icon
(
'plus',
setClass('mr-1 btn-add ml-2 text-sm text-primary add-rows'),
on::click('addRow'),
set::title(sprintf($lang->thinkrun->addTips, $canAddRows)),
setClass('mr-1 btn-add ml-2 text-sm text-primary add-rows', $disabledAdd ? 'disabled' : ''),
on::click('addRow(event)'),
set::title(sprintf($lang->thinkrun->tips->add, $canAddRows)),
),
icon
(
@@ -148,7 +144,7 @@ class thinkTableInput extends thinkQuestion
(
set::rows('2'),
setID('result'),
setClass('mt-2 w-4/5 ml-2'),
setClass('mt-2 result-width ml-2'),
set::value(''),
set::placeholder($lang->thinkrun->placeholder->rowContent)
),
@@ -160,8 +156,8 @@ class thinkTableInput extends thinkQuestion
(
'plus',
setClass('mr-1 btn-add ml-2 text-sm text-primary add-rows'),
on::click('addRow'),
set::title(sprintf($lang->thinkrun->addTips, $canAddRows)),
on::click('addRow(event)'),
set::title(sprintf($lang->thinkrun->tips->add, $canAddRows)),
),
icon
(
@@ -180,14 +176,14 @@ class thinkTableInput extends thinkQuestion
$app->loadLang('thinkstep');
$formItems = parent::buildFormItem();
list($step, $required, $requiredRows, $isSupportAdd, $canAddRows, $fields) = $this->prop(array('step','required', 'requiredRows', 'isSupportAdd', 'canAddRows', 'fields'));
list($step, $required, $requiredRows, $supportAdd, $canAddRows, $fields) = $this->prop(array('step','required', 'requiredRows', 'supportAdd', 'canAddRows', 'fields'));
if($step)
{
$required = $step->options->required;
if(!empty($step->options->fields)) $step->options->fields = is_string($step->options->fields) ? explode(', ', $step->options->fields) : array_values((array)$step->options->fields);
$fields = $step->options->fields ?? array();
$requiredRows = $step->options->requiredRows;
$isSupportAdd = $step->options->isSupportAdd;
$supportAdd = $step->options->supportAdd;
$canAddRows = $step->options->canAddRows;
}
@@ -207,7 +203,7 @@ class thinkTableInput extends thinkQuestion
set::inline(true),
set::items($lang->thinkstep->requiredList),
set::value($required),
bind::change('changeIsRequired(event)')
on::change()->toggleClass('.required-rows', 'hidden', 'target.value == 0')
)
),
formGroup
@@ -220,7 +216,7 @@ class thinkTableInput extends thinkQuestion
set::type('number'),
set::name('options[requiredRows]'),
set::value($requiredRows),
set::placeholder($lang->thinkstep->inputContent),
set::placeholder($lang->thinkstep->placeholder->inputContent),
set::min(1),
on::input('changeInput')
)
@@ -235,16 +231,16 @@ class thinkTableInput extends thinkQuestion
set::label($lang->thinkstep->label->isSupportAdd),
radioList
(
set::name('options[isSupportAdd]'),
set::name('options[supportAdd]'),
set::inline(true),
set::items($lang->thinkstep->requiredList),
set::value($isSupportAdd ? $isSupportAdd : 0),
bind::change('changeSupportAdd(event)')
set::value($supportAdd ? $supportAdd : 0),
on::change()->toggleClass('.can-add-rows', 'hidden', 'target.value == 0')
)
),
formGroup
(
setClass('w-1/2 can-add-rows', $isSupportAdd ? '' : 'hidden'),
setClass('w-1/2 can-add-rows', $supportAdd ? '' : 'hidden'),
set::label($lang->thinkstep->label->canAddRows),
set::labelClass('required'),
input
@@ -252,7 +248,7 @@ class thinkTableInput extends thinkQuestion
set::type('number'),
set::name('options[canAddRows]'),
set::value($canAddRows),
set::placeholder($lang->thinkstep->inputContent),
set::placeholder($lang->thinkstep->placeholder->inputContent),
set::min(1),
on::input('changeInput')
)
-8
View File
@@ -1,9 +1 @@
#accountCreateForm .form-row {max-width: 500px;}
#mainNavbar>.container {padding-top: 8px}
#mainNavbar .nav {justify-content: left; position: relative; left: 0px; top: -4px;}
@media (min-width: 1400px){#mainNavbar .nav {left: -12px;}}
#mainNavbar {background: none;}
#mainNavbar .nav-item>a {padding-right: 0.5rem}
#mainNavbar .nav-item>.active {color: unset; font-weight: 700;}
#mainNavbar .nav-item>a.active:after {position: absolute; content: ''; border-bottom: 2px solid #2e7fff; inset: 0 6px 0 15px;}
+2 -2
View File
@@ -137,9 +137,9 @@ class action extends control
/* 当对象类型为program、project、execution、product时,需要检查是否有重复的对象。 */
/* When the object type is program, project, execution, product, you need to check if there are duplicate objects. */
if(in_array($oldAction->objectType, array('program', 'project', 'execution', 'product')))
if(in_array($oldAction->objectType, array('program', 'project', 'execution', 'product', 'build')))
{
$table = $oldAction->objectType == 'product' ? TABLE_PRODUCT : TABLE_PROJECT;
$table = $this->config->objectTables[$oldAction->objectType];
list($repeatObject, $object) = $this->action->getRepeatObject($oldAction, $table);
if($repeatObject)
+6 -1
View File
@@ -1748,7 +1748,7 @@ class actionModel extends model
->andWhere('deleted')->eq('0')
->fetch();
}
else
elseif(in_array($action->objectType, array('program', 'project', 'execution')))
{
$sprintProject = isset($object->project) ? $object->project : 0;
$repeatObject = $this->dao->select('*')->from(TABLE_PROJECT)
@@ -1764,6 +1764,11 @@ class actionModel extends model
->andWhere('deleted')->eq('0')
->fetch();
}
else
{
$objectNameFields = $this->config->action->objectNameFields[$action->objectType];
$repeatObject = $this->dao->select('*')->from($table)->where('id')->ne($action->objectID)->andWhere('deleted')->eq('0')->andWhere($objectNameFields)->eq($object->name)->fetch();
}
return array($repeatObject, $object);
}
+2 -2
View File
@@ -18,8 +18,8 @@ $config->api->form->editStruct['desc'] = array('type' => 'string', 'require
$config->api->form->createLib = array();
$config->api->form->createLib['libType'] = array('type' => 'string', 'required' => true, 'default' => 'nolink');
$config->api->form->createLib['product'] = array('type' => 'int', 'required' => true, 'default' => 0);
$config->api->form->createLib['project'] = array('type' => 'int', 'required' => true, 'default' => 0);
$config->api->form->createLib['product'] = array('type' => 'int', 'required' => false, 'default' => 0);
$config->api->form->createLib['project'] = array('type' => 'int', 'required' => false, 'default' => 0);
$config->api->form->createLib['execution'] = array('type' => 'int', 'required' => false, 'default' => 0);
$config->api->form->createLib['name'] = array('type' => 'string', 'required' => true, 'default' => '');
$config->api->form->createLib['baseUrl'] = array('type' => 'string', 'required' => false, 'default' => '');
+4 -1
View File
@@ -367,7 +367,10 @@ class api extends control
if(!empty($_POST))
{
/* 组装formData。 */
$formData = form::data($this->config->api->form->createLib)->add('addedBy', $this->app->user->account)->add('addedDate', helper::now())->get();
$fields = $this->config->api->form->createLib;
if($this->post->libType == 'project') $fields['project']['required'] = true;
if($this->post->libType == 'product') $fields['product']['required'] = true;
$formData = form::data($fields)->add('addedBy', $this->app->user->account)->add('addedDate', helper::now())->get();
$formData->product = $formData->libType == 'product' && !empty($formData->product) ? $formData->product : 0;
$formData->project = $formData->libType == 'project' && !empty($formData->project) ? $formData->project : 0;
$formData->execution = $formData->libType == 'project' && !empty($formData->execution) ? $formData->execution : 0;
+1 -1
View File
@@ -3302,7 +3302,7 @@ $config->bi->builtin->metrics[] = array
'unit' => 'hour',
'dateType' => 'week',
'desc' => '按瀑布项目统计的每周的任务的计划完成工时指的是在瀑布项目管理方法中,按计划需要完成的任务的总预计工时。这个度量项用于评估每周的任务的预期工作量,可用作与实际花费工时和已完成任务的预计工时进行比较。',
'definition' => "1.任务截至日期小于等于本周结束日期,累加预计工时。/n/r2.任务预计开始日期小于或等于本周结束日期,预计截至日期大于本周结束日期,累加预计工时=(任务的预计工时÷任务工期天数)x 任务预计开始到本周结束日期的天数。/n/r条件:过滤父任务,过滤已删除的任务,过滤已取消的任务,过滤已删除的执行的任务,过滤已删除的项目;任务未填写预计开始日期时默认取任务所属阶段的计划开始日期;任务未填写预计截至日期,预计截至日期默认取任务所属阶段的计划完成日期,时间只计算后台维护的工作日。"
'definition' => "1.任务截至日期小于等于本周结束日期,累加预计工时。\n2.任务预计开始日期小于或等于本周结束日期,预计截至日期大于本周结束日期,累加预计工时=(任务的预计工时÷任务工期天数)x 任务预计开始到本周结束日期的天数。\n条件:过滤父任务,过滤已删除的任务,过滤已取消的任务,过滤已删除的执行的任务,过滤已删除的项目;任务未填写预计开始日期时默认取任务所属阶段的计划开始日期;任务未填写预计截至日期,预计截至日期默认取任务所属阶段的计划完成日期,时间只计算后台维护的工作日。"
);
$config->bi->builtin->metrics[] = array
+3 -3
View File
@@ -1628,7 +1628,7 @@ from (
0 as userlogin,
0 as consumed,
sum(case when t1.action = 'opened' then 1 else 0 end) as storyopen,
sum(case when t1.action = 'closed' and t2.status = 'closed' then 1 else 0 end) as storyclose,
count(distinct case when t1.action = 'closed' and t2.status = 'closed' then t2.id else 0 end) - 1 as storyclose,
0 as taskopen,
0 as taskfinish,
0 as bugopen,
@@ -1646,7 +1646,7 @@ from (
0 as storyopen,
0 as storyclose,
sum(case when t1.action = 'opened' then 1 else 0 end) as taskopen,
sum(case when t1.action = 'finished' and t2.status in ('done', 'closed') then 1 else 0 end) as taskfinish,
count(distinct case when t1.action = 'finished' and t2.status in ('done', 'closed') then t2.id else 0 end) - 1 as taskfinish,
0 as bugopen,
0 as bugresolve,
0 as actions
@@ -1664,7 +1664,7 @@ from (
0 as taskopen,
0 as taskfinish,
sum(case when t1.action = 'opened' then 1 else 0 end) as bugopen,
sum(case when t1.action = 'resolved' and t2.status in ('resolved', 'closed') then 1 else 0 end) as bugresolve,
count(distinct case when t1.action = 'resolved' and t2.status in ('resolved', 'closed') then t2.id else 0 end) - 1 as bugresolve,
0 as actions
from zt_action t1
left join zt_bug t2 on t1.objectID = t2.id
+3 -3
View File
@@ -256,9 +256,9 @@ $lang->block->modules['waterfallproject'] = new stdclass();
$lang->block->modules['waterfallproject']->availableBlocks['waterfallgantt'] = "{$lang->projectCommon} Plan";
$lang->block->modules['waterfallproject']->availableBlocks['projectdynamic'] = 'Dynamics';
$lang->block->modules['agileplus'] = $lang->block->modules['scrumproject'];
$lang->block->modules['waterfallplus'] = $lang->block->modules['waterfallproject'];
$lang->block->modules['ipdproject'] = $lang->block->modules['waterfallproject'];
$lang->block->modules['agileplusproject'] = $lang->block->modules['scrumproject'];
$lang->block->modules['waterfallplusproject'] = $lang->block->modules['waterfallproject'];
$lang->block->modules['ipdproject'] = $lang->block->modules['waterfallproject'];
$lang->block->modules['product'] = new stdclass();
$lang->block->modules['product']->availableBlocks['overview'] = "{$lang->productCommon} Overview";
+5 -5
View File
@@ -256,9 +256,9 @@ $lang->block->modules['waterfallproject'] = new stdclass();
$lang->block->modules['waterfallproject']->availableBlocks['waterfallgantt'] = "{$lang->projectCommon}计划";
$lang->block->modules['waterfallproject']->availableBlocks['projectdynamic'] = '最新动态';
$lang->block->modules['agileplus'] = $lang->block->modules['scrumproject'];
$lang->block->modules['waterfallplus'] = $lang->block->modules['waterfallproject'];
$lang->block->modules['ipdproject'] = $lang->block->modules['waterfallproject'];
$lang->block->modules['agileplusproject'] = $lang->block->modules['scrumproject'];
$lang->block->modules['waterfallplusproject'] = $lang->block->modules['waterfallproject'];
$lang->block->modules['ipdproject'] = $lang->block->modules['waterfallproject'];
$lang->block->modules['product'] = new stdclass();
$lang->block->modules['product']->availableBlocks['overview'] = "{$lang->productCommon}总览";
@@ -578,7 +578,7 @@ $lang->block->projectstatistic->task = '任务';
$lang->block->projectstatistic->bug = 'Bug';
$lang->block->projectstatistic->storyPoints = '总规模';
$lang->block->projectstatistic->done = '已完成';
$lang->block->projectstatistic->undone = '剩余';
$lang->block->projectstatistic->undone = '未关闭';
$lang->block->projectstatistic->costs = '已投入';
$lang->block->projectstatistic->consumed = '消耗工时';
$lang->block->projectstatistic->remainder = '预计剩余';
@@ -637,7 +637,7 @@ $lang->block->executionstatistic->totalEstimate = '预计工时';
$lang->block->executionstatistic->totalConsumed = '消耗工时';
$lang->block->executionstatistic->totalLeft = '剩余工时';
$lang->block->executionstatistic->burn = $lang->execution->common . '燃尽图';
$lang->block->executionstatistic->cfd = $lang->execution->common . '任务累计流图';
$lang->block->executionstatistic->cfd = $lang->execution->common . '任务累积流图';
$lang->block->executionstatistic->story = '需求';
$lang->block->executionstatistic->doneStory = '已完成';
$lang->block->executionstatistic->totalStory = '需求总数';
+1 -1
View File
@@ -3349,7 +3349,7 @@ class blockZen extends block
if($taskProgressGroup) $taskProgressGroup = array_column($taskProgressGroup, null, 'project');
}
return array('riskCountGroup' => $riskCountGroup, 'issueCountGroup' => $issueCountGroup, 'investedGroup' => $investedGroup, 'consumeTaskGroup' => $consumeTaskGroup, 'leftTaskGroup' => $leftTaskGroup, 'countStoryGroup' => $countStoryGroup, 'finishedStoryGroup' => $finishedStoryGroup, 'unclosedStoryGroup' => $unclosedStoryGroup, 'countTaskGroup' => $countTaskGroup, 'waitTaskGroup' => $waitTaskGroup, 'doingTaskGroup' => $doingTaskGroup, 'countBugGroup' => $countBugGroup, 'closedBugGroup' => $closedBugGroup, 'activatedBugGroup' => $activatedBugGroup, 'taskProgressGroup' => $taskProgressGroup, 'SVGroup' => $SVGroup, 'PVGroup' => $PVGroup, 'EVGroup' => $EVGroup, 'CVGroup' => $CVGroup, 'ACGroup' => $ACGroup);
return array('riskCountGroup' => $riskCountGroup, 'issueCountGroup' => $issueCountGroup, 'investedGroup' => $investedGroup, 'consumeTaskGroup' => $consumeTaskGroup, 'leftTaskGroup' => $leftTaskGroup, 'countStoryGroup' => $countStoryGroup, 'finishedStoryGroup' => $finishedStoryGroup, 'unclosedStoryGroup' => $unclosedStoryGroup, 'countTaskGroup' => $countTaskGroup, 'waitTaskGroup' => $waitTaskGroup, 'doingTaskGroup' => $doingTaskGroup, 'countBugGroup' => $countBugGroup, 'closedBugGroup' => $closedBugGroup, 'activatedBugGroup' => $activatedBugGroup) + ($getWaterfall ? array('taskProgressGroup' => $taskProgressGroup, 'SVGroup' => $SVGroup, 'PVGroup' => $PVGroup, 'EVGroup' => $EVGroup, 'CVGroup' => $CVGroup, 'ACGroup' => $ACGroup) : array());
}
/**
+2 -2
View File
@@ -468,7 +468,7 @@ class bug extends control
$this->view->users = $users;
$this->view->assignedTo = isset($users[$oldBug->openedBy]) ? $oldBug->openedBy : $this->bug->getModuleOwner($oldBug->module, $oldBug->product);
$this->view->executions = $this->loadModel('product')->getExecutionPairsByProduct($oldBug->product, $oldBug->branch ? "0,{$oldBug->branch}" : '0', (int)$oldBug->project, 'stagefilter');
$this->view->builds = $this->bugZen->addReleaseLabelForBuilds($oldBug->product, $builds);
$this->view->builds = $this->build->addReleaseLabelForBuilds($oldBug->product, $builds);
$this->view->actions = $this->loadModel('action')->getList('bug', $bugID);
$this->display();
}
@@ -1496,7 +1496,7 @@ class bug extends control
$product = $this->loadModel('product')->getById($productID);
$bug = $this->bug->getById($bugID);
$branch = $product->type == 'branch' ? ($bug->branch > 0 ? $bug->branch . ',0' : '0') : '';
$productBugs = $this->bug->getProductBugPairs($productID, $branch, $search, $limit, 'all');
$productBugs = $this->bug->getProductBugPairs($productID, $branch, $search, $limit, 'single');
unset($productBugs[$bugID]);
if($type == 'json') return print(helper::jsonEncode($productBugs));
+9 -1
View File
@@ -6,6 +6,8 @@ global $lang, $app;
$isShadowProduct = data('product.shadow');
$noMultipleProject = data('project.multiple') === '0';
$isOriginalProduct = (int)data('bug.productID') === (int)data('productID');
$resultFiles = data('resultFiles');
$resultFiles = array_values($resultFiles);
$fields = defineFieldList('bug.create', 'bug');
@@ -27,7 +29,7 @@ $fields->field('execution')
if(common::hasPriv('build', 'create'))
{
$fields->field('openedBuild')
->itemBegin()->control(array('control' => 'btn', 'data-toggle' => 'modal', 'id' => 'createBuild'))
->itemBegin()->control(array('control' => 'btn', 'data-toggle' => 'modal', 'id' => 'createBuild', 'data-size' => 'lg'))
->text($lang->build->create)->hint($lang->build->create)
->url(createLink('build', 'create', 'executionID=' . data('executionID') . '&productID=' . data('bug.productID') . '&projectID=' . data('projectID')))
->className(count(data('builds')) > 1 || !data('executionID') ? 'hidden' : '')
@@ -48,6 +50,10 @@ $fields->field('steps')
->width('full')
->control(array('control' => 'editor', 'templateType' => 'bug'));
$fields->field('files')
->width('full')
->control('fileSelector', array('defaultFiles' => $resultFiles));
$fields->field('story')->foldable();
$fields->field('task')->foldable();
@@ -68,4 +74,6 @@ $fields->field('module')->className($isShadowProduct ? 'w-1/2' : 'w-1/4')->class
$fields->field('openedBuild')->className($isShadowProduct ? 'w-1/2' : 'w-1/4')->className('full:w-1/2');
$fields->field('resultFiles')->control('hidden')->value(data('resultFiles'));
if($isShadowProduct && !$isOriginalProduct) $fields->moveAfter('module', 'product');
+28 -16
View File
@@ -35,12 +35,12 @@ class bugZen extends bug
{
if($bug->execution && !$this->loadModel('execution')->checkPriv($bug->execution))
{
if(isInModal())
if(isInModal() || !$this->server->http_referer)
{
echo js::alert($this->lang->bug->notice->executionAccessDenied);
$loginLink = $this->createLink('user', 'login');
if(strpos($this->server->http_referer, $loginLink) !== false) return print(js::locate($this->createLink('bug', 'index', '')));
if($this->server->http_referer && strpos($this->server->http_referer, $loginLink) !== false) return print(js::locate($this->createLink('bug', 'index', '')));
if($this->app->tab == 'my') return print(js::reload('parent'));
@@ -50,8 +50,8 @@ class bugZen extends bug
{
$locate = array('load' => true);
$loginLink = $this->createLink('user', 'login');
if(strpos($this->server->http_referer, $loginLink) !== false) $locate = $this->createLink('bug', 'index');
return $this->send(array('result' => 'fail', 'message' => $this->lang->bug->notice->executionAccessDenied, 'load' => array('alert' => $this->lang->bug->notice->executionAccessDenied, 'locate' => $locate)));
if($this->server->http_referer && strpos($this->server->http_referer, $loginLink) !== false) $locate = $this->createLink('bug', 'index');
return $this->send(array('result' => 'fail', 'load' => array('alert' => $this->lang->bug->notice->executionAccessDenied, 'locate' => $locate)));
}
}
@@ -1072,6 +1072,17 @@ class bugZen extends bug
$bug = $this->gettasksForCreate($bug);
if(in_array($this->config->edition, array('max', 'ipd'))) $this->view->injectionList = $this->view->identifyList = $this->loadModel('review')->getPairs($bug->projectID, $bug->productID, true);
$resultFiles = array();
if(!empty($resultID) && !empty($stepIdList))
{
$resultFiles = $this->loadModel('file')->getByObject('stepResult', (int)$resultID, str_replace('_', ',', $stepIdList));
foreach($resultFiles as $resultFile)
{
$resultFile->name = $resultFile->title;
$resultFile->url = $this->createLink('file', 'download', "fileID={$resultFile->id}");
}
}
$this->view->title = isset($this->products[$bug->productID]) ? $this->products[$bug->productID] . $this->lang->hyphen . $this->lang->bug->create : $this->lang->bug->create;
$this->view->productMembers = $this->getProductMembersForCreate($bug);
$this->view->gobackLink = $from == 'global' ? $this->createLink('bug', 'browse', "productID=$bug->productID") : '';
@@ -1094,7 +1105,7 @@ class bugZen extends bug
$this->view->allBuilds = !empty($bug->allBuilds) ? $bug->allBuilds : '';
$this->view->allUsers = !empty($bug->allUsers) ? $bug->allUsers : '';
$this->view->releasedBuilds = $this->loadModel('release')->getReleasedBuilds((int)$bug->productID, (string)$bug->branch);
$this->view->resultFiles = !empty($resultID) && !empty($stepIdList) ? $this->loadModel('file')->getByObject('stepResult', (int)$resultID, str_replace('_', ',', $stepIdList)) : array();
$this->view->resultFiles = $resultFiles;
$this->view->contactList = $this->loadModel('user')->getContactLists();
$this->view->branchID = $bug->branch != 'all' ? $bug->branch : '0';
}
@@ -1822,25 +1833,26 @@ class bugZen extends bug
*/
private function updateFileAfterCreate(int $bugID): bool
{
if(isset($this->post->resultFiles))
$this->loadModel('file');
if(!empty($_POST['resultFiles']))
{
$resultFiles = $this->post->resultFiles;
if(isset($this->post->deleteFiles))
if($resultFiles) $resultFiles = json_decode($resultFiles, true);
if(!empty($_POST['deleteFiles']))
{
foreach($this->post->deleteFiles as $deletedCaseFileID) $resultFiles = trim(str_replace(",$deletedCaseFileID,", ',', ",$resultFiles,"), ',');
foreach($this->post->deleteFiles as $deletedCaseFileID) unset($resultFiles[$deletedCaseFileID]);
}
$files = $this->dao->select('*')->from(TABLE_FILE)->where('id')->in($resultFiles)->fetchAll('id');
foreach($files as $file)
foreach($resultFiles as $file)
{
unset($file->id);
$file->objectType = 'bug';
$file->objectID = $bugID;
$this->dao->insert(TABLE_FILE)->data($file)->exec();
unset($file['id']);
$file['objectType'] = 'bug';
$file['objectID'] = $bugID;
$this->file->saveFile($file, 'url,deleted,realPath,webPath,name,url,extra');
}
}
$uid = $this->post->uid ? $this->post->uid : '';
$this->loadModel('file')->updateObjectID($uid, $bugID, 'bug');
$this->file->updateObjectID($uid, $bugID, 'bug');
$this->file->saveUpload('bug', $bugID);
return !dao::isError();
@@ -2190,7 +2202,7 @@ class bugZen extends bug
helper::setcookie('bugModule', '0', 0);
/* Remove upload image file and session. */
if(!empty($_POST['uploadImage']) and !empty($_SESSION['bugImagesFile']))
if(!empty($_POST['uploadImage']) && !empty($_SESSION['bugImagesFile']))
{
$classFile = $this->app->loadClass('zfile');
$file = current($_SESSION['bugImagesFile']);
+5 -7
View File
@@ -13,12 +13,11 @@ if($config->testcase->needReview || !empty($config->testcase->forceReview))
$config->caselib->testcase->actionList['review']['data-toggle'] = 'modal';
}
$config->caselib->testcase->actionList['edit']['icon'] = 'edit';
$config->caselib->testcase->actionList['edit']['hint'] = $lang->testcase->edit;
$config->caselib->testcase->actionList['edit']['text'] = $lang->testcase->edit;
$config->caselib->testcase->actionList['edit']['url'] = array('module' => 'testcase', 'method' => 'edit', 'params' => 'caseID={id}');
$config->caselib->testcase->actionList['edit']['order'] = 5;
$config->caselib->testcase->actionList['edit']['data-toggle'] = 'modal';
$config->caselib->testcase->actionList['edit']['icon'] = 'edit';
$config->caselib->testcase->actionList['edit']['hint'] = $lang->testcase->edit;
$config->caselib->testcase->actionList['edit']['text'] = $lang->testcase->edit;
$config->caselib->testcase->actionList['edit']['url'] = array('module' => 'testcase', 'method' => 'edit', 'params' => 'caseID={id}');
$config->caselib->testcase->actionList['edit']['order'] = 5;
$config->caselib->testcase->actionList['delete']['icon'] = 'trash';
$config->caselib->testcase->actionList['delete']['hint'] = $lang->testcase->delete;
@@ -42,7 +41,6 @@ $config->caselib->dtable->fieldList['id']['name'] = 'id';
$config->caselib->dtable->fieldList['title']['link'] = array('module' => 'testcase', 'method' => 'view', 'params' => "caseID={id}");
$config->caselib->dtable->fieldList['title']['nestedToggle'] = false;
$config->caselib->dtable->fieldList['title']['data-toggle'] = 'modal';
$config->caselib->dtable->fieldList['type']['show'] = true;
$config->caselib->dtable->fieldList['status']['show'] = true;
+1
View File
@@ -55,6 +55,7 @@ class chart extends control
$this->view->title = $this->lang->chart->preview;
$this->view->groups = $this->loadModel('tree')->getGroupPairs($dimensionID, 0, 1);
$this->view->treeMenu = $this->chart->getTreeMenu($groupID);
$this->view->recTotal = count($this->getMenuItems($this->view->treeMenu));
$this->view->charts = $charts;
$this->view->dimensionID = $dimensionID;
$this->view->groupID = $groupID;
+7 -1
View File
@@ -19,7 +19,13 @@ jsVar('maxPreviewTips', sprintf($lang->chart->chartMaxChecked, $config->chart->c
$items = array();
foreach($groups as $id => $name)
{
$items[] = array('text' => $name, 'url' => inlink('preview', "dimension={$dimensionID}&group={$id}"), 'active' => $id == $groupID);
$items[] = array
(
'text' => $name,
'url' => inlink('preview', "dimension={$dimensionID}&group={$id}"),
'badge' => $id == $groupID ? array('text' => $recTotal, 'class' => 'size-sm canvas ring-0 rounded-md') : null,
'active' => $id == $groupID
);
}
featureBar(set::items($items));
+20
View File
@@ -47,4 +47,24 @@ class chartZen extends chart
return $chart;
}
/**
* 获取菜单项。
* Get menu items.
*
* @param array $menus
* @access protected
* @return array
*/
protected function getMenuItems(array $menus): array
{
$items = array();
foreach($menus as $menu)
{
if($menu->parent == 0) continue;
$items[] = $menu;
}
return $items;
}
}
+4 -5
View File
@@ -254,6 +254,7 @@ class commonModel extends model
foreach($this->config->CSPs as $CSP) helper::header('Content-Security-Policy', "$CSP;");
}
if(!empty($this->config->xFrameOptions)) helper::header('X-Frame-Options', $this->config->xFrameOptions);
if($this->loadModel('setting')->getItem('owner=system&module=sso&key=turnon'))
{
if(isset($_SERVER["HTTPS"]) and $_SERVER["HTTPS"] == 'on')
@@ -262,10 +263,6 @@ class commonModel extends model
helper::header('Set-Cookie', "$session; SameSite=None; Secure=true", false);
}
}
else
{
if(!empty($this->config->xFrameOptions)) helper::header('X-Frame-Options', $this->config->xFrameOptions);
}
}
/**
@@ -1222,8 +1219,9 @@ class commonModel extends model
*/
$module = $this->app->getModuleName();
$whitelist = is_string($whitelist) ? $whitelist : '|index|tutorial|install|upgrade|sso|cron|misc|user-login|user-deny|user-logout|user-reset|user-forgetpassword|user-resetpassword|my-changepassword|my-preference|file-read|file-download|file-preview|file-uploadimages|file-ajaxwopifiles|report-annualdata|misc-captcha|execution-printkanban|traincourse-ajaxuploadlargefile|traincourse-playvideo|screen-view|zanode-create|screen-ajaxgetchart|ai-chat|';
$skiplist = '|cron-index|';
if(strpos($whitelist, "|{$module}|") !== false || strpos($whitelist, "|{$module}-{$method}|") !== false) return true;
if((strpos($whitelist, "|{$module}|") !== false && strpos($skiplist, "|{$module}-{$method}|") === false) || strpos($whitelist, "|{$module}-{$method}|") !== false) return true;
/**
* 如果以上条件都不满足,则视为当前页面必须在 iframe 中打开,使用 302 跳转实现。
@@ -2882,6 +2880,7 @@ class commonModel extends model
if($config->edition == 'open') unset($lang->createIcons['effort']);
if($config->systemMode == 'light') unset($lang->createIcons['program']);
if(empty($config->board)) unset($lang->createIcons['board']);
/* Check whether the creation permission is available, and print create buttons. */
foreach($lang->createIcons as $objectType => $objectIcon)
+2
View File
@@ -192,6 +192,8 @@ class customModel extends model
$menu = static::buildMenuItems($allMenu, $customMenuMap, $module, $order);
ksort($menu, SORT_NUMERIC);
if(!isset($lang->{$tab})) return array();
/* Set divider in main && module menu. */
if(!isset($lang->{$tab}->menuOrder))
{
+2
View File
@@ -26,3 +26,5 @@
#outlineToggle {display: block; position: absolute; top: 6.5rem; right: 3.5rem; z-index: 100;}
#docPanel .panel-heading > .panel-actions.hasEditor {min-width: 320px;}
#docPanel .detail-content {max-width: var(--zt-panel-form-max-width); overflow: hidden;}
.editor-container, .editor-container > .form-control {box-shadow: none!important;}
+1
View File
@@ -83,6 +83,7 @@ $doc->type == 'text' ? form
set::name('content'),
set::size('full'),
set::resizable(false),
$doc->contentType == 'markdown' ? set::markdown(true) : null,
html($doc->content)
),
formHidden('status', $doc->status),
+10 -2
View File
@@ -170,7 +170,8 @@ $contentDom = div
set::url(createLink('doc', 'edit', "docID=$doc->id")),
$doc->type != 'text' ? setData('toggle', 'modal') : null,
setClass('btn ghost'),
icon('edit')
icon('edit'),
setData('app', $app->tab)
) : null,
common::hasPriv('doc', 'delete') && !$doc->deleted ? btn
(
@@ -237,7 +238,14 @@ $contentDom = div
div
(
setClass('detail-content article'),
html($doc->content)
$doc->contentType == 'markdown' ? editor
(
set::size('full'),
set::markdown(true),
set::readonly(true),
set::hideUI(true),
html($doc->content)
) : html($doc->content)
),
div
(
+2 -1
View File
@@ -1993,6 +1993,7 @@ class execution extends control
$this->loadModel('common')->saveQueryCondition($this->dao->get(), 'story', false);
$stories = array_filter($stories, function($story) {return in_array($story->stage, $this->config->execution->storyKanbanCols);});
foreach($stories as $story) unset($story->type);
/* Get execution's product. */
$productID = 0;
@@ -2937,7 +2938,7 @@ class execution extends control
if($this->post->exportType == 'selected')
{
$checkedItem = $this->cookie->checkedItem;
if(strpos(",$checkedItem,", ",{$execution->id},") === false) unset($executionStats[$i]);
if(strpos(",$checkedItem,", ",pid{$execution->id},") === false) unset($executionStats[$i]);
}
}
+1
View File
@@ -7,3 +7,4 @@
.maxh-72{max-height:18rem;}
.actions-menu {bottom: 12px;}
#mainContent {margin-bottom: 60px;}
-7
View File
@@ -16,13 +16,6 @@ $(document).off('click','.batch-btn').on('click', '.batch-btn', function()
{
postAndLoadPage(url, form);
}
}).off('click', '#actionBar .export').on('click', '#actionBar .export', function()
{
const dtable = zui.DTable.query($('#table-execution-all'));
const checkedList = dtable.$.getChecks();
if(!checkedList.length) return;
$.cookie.set('checkedItem', checkedList, {expires:config.cookieLife, path:config.webRoot});
});
const today = zui.formatDate(new Date(), 'yyyy-MM-dd');
+1 -1
View File
@@ -1,6 +1,6 @@
window.randTipInfo = function(rowDatas)
{
let tipHtml = `<p>${rowDatas[0].name} ${workHour} /h</p>`;
let tipHtml = burnBy == 'storyPoint' ? `<p>${rowDatas[0].name} ${storyPoint} /sp</p>` : `<p>${rowDatas[0].name} ${workHour} /h</p>`;
rowDatas.forEach(rowData =>
{
if(rowData.data == 'null') return;
+13 -10
View File
@@ -101,28 +101,31 @@ window.checkProducts = function()
window.buildColCardActions = function(col)
{
let actions = [];
let $group = $(`[z-key=group${col.group}]`);
let laneID = 0;
if($group.length) laneID = $group.find('.kanban-lane.is-first').attr('z-lane');
if(col.type == 'backlog')
{
if(priv.canCreateStory)
{
action = {text: storyLang.create, url: $.createLink('story', 'create', 'productID=' + productID + '&branch=0&moduleID=0&storyID=0&objectID=' + executionID + '&bugID=0&planID=0&todoID=0&extra=regionID=' + col.region + ',laneID=' + 0 + ',columnID=' + col.id), 'data-toggle': 'modal', 'data-size': 'lg'};
action = {text: storyLang.create, url: $.createLink('story', 'create', 'productID=' + productID + '&branch=0&moduleID=0&storyID=0&objectID=' + executionID + '&bugID=0&planID=0&todoID=0&extra=regionID=' + col.region + ',laneID=' + laneID + ',columnID=' + col.id), 'data-toggle': 'modal', 'data-size': 'lg'};
actions.push(action);
}
if(priv.canBatchCreateStory) actions.push({text: storyLang.batchCreate, url: productCount > 1 ? '#batchCreateStory' : $.createLink('story', 'batchCreate', 'productID=' + productID + '&branch=0&moduleID=0&storyID=0&objectID=' + executionID + '&planID=0&storyType=story&extra=regionID=' + col.region + ',laneID=' + 0 + ',columnID=' + col.id), 'data-toggle': 'modal', 'data-size' : 'lg', 'innerClass': 'story-batch-btn'});
if(priv.canLinkStory) actions.push({text: executionLang.linkStory, url: $.createLink('execution', 'linkStory', 'executionID=' + executionID + '&browseType=&param=0&orderBy=id_desc&recPerPage=50&pageID=1&extra=laneID=0,columnID=' + col.id), 'data-toggle': 'modal', 'data-size' : 'lg'});
if(priv.canBatchCreateStory) actions.push({text: storyLang.batchCreate, url: productCount > 1 ? '#batchCreateStory' : $.createLink('story', 'batchCreate', 'productID=' + productID + '&branch=0&moduleID=0&storyID=0&objectID=' + executionID + '&planID=0&storyType=story&extra=regionID=' + col.region + ',laneID=' + laneID + ',columnID=' + col.id), 'data-toggle': 'modal', 'data-size' : 'lg'});
if(priv.canLinkStory) actions.push({text: executionLang.linkStory, url: $.createLink('execution', 'linkStory', 'executionID=' + executionID + `&browseType=&param=0&orderBy=id_desc&recPerPage=50&pageID=1&extra=laneID=${laneID},columnID=` + col.id), 'data-toggle': 'modal', 'data-size' : 'lg'});
if(priv.canLinkStoryByPlan) actions.push({text: executionLang.linkStoryByPlan, url: '#linkStoryByPlan', 'data-toggle': 'modal', 'data-size': 'sm'});
}
else if(col.type == 'unconfirmed')
{
if(priv.canCreateBug) actions.push({text: bugLang.create, url: $.createLink('bug', 'create', 'productID=' + productID + '&moduleID=0&extra=regionID=' + col.region + ',groupID=' + col.group + ',laneID=' + 0 + ',columnID=' + col.id + ',executionID=' + executionID), 'data-toggle': 'modal', 'data-size' : 'lg'});
if(priv.canBatchCreateBug) actions.push({text: bugLang.batchCreate, url: productCount > 1 ? '#batchCreateBug' : $.createLink('bug', 'batchcreate', 'productID=' + productID + '&branch=all&executionID=' + executionID + '&module=0&extra=regionID=' + col.region + ',laneID=' + 0 + ',columnID=' + col.id), 'data-toggle': 'modal', 'data-size': 'lg'});
if(priv.canCreateBug) actions.push({text: bugLang.create, url: $.createLink('bug', 'create', 'productID=' + productID + '&moduleID=0&extra=regionID=' + col.region + ',groupID=' + col.group + ',laneID=' + laneID + ',columnID=' + col.id + ',executionID=' + executionID), 'data-toggle': 'modal', 'data-size' : 'lg'});
if(priv.canBatchCreateBug) actions.push({text: bugLang.batchCreate, url: productCount > 1 ? '#batchCreateBug' : $.createLink('bug', 'batchcreate', 'productID=' + productID + '&branch=all&executionID=' + executionID + '&module=0&extra=regionID=' + col.region + ',laneID=' + laneID + ',columnID=' + col.id), 'data-toggle': 'modal', 'data-size': 'lg'});
}
else if(col.type == 'wait')
{
if(priv.canCreateTask) actions.push({text: taskLang.create, url: $.createLink('task', 'create', 'executionID=' + executionID + '&storyID=0&moduleID=0&taskID=0&todoID=0&extra=regionID=' + col.region + ',laneID=' + 0 + ',columnID=' + col.id), 'data-toggle': 'modal', 'data-size' : 'lg'});
if(priv.canBatchCreateTask) actions.push({text: taskLang.batchCreate, url: $.createLink('task', 'batchcreate', 'executionID=' + executionID + '&storyID=0&moduleID=0&taskID=0&iframe=0&extra=regionID=' + col.region + ',laneID=' + 0 + ',columnID=' + col.id), 'data-toggle': 'modal', 'data-size' : 'lg'});
if(priv.canImportBug && vision == 'rnd') actions.push({text: executionLang.importBug, url: $.createLink('execution', 'importBug', 'executionID=' + executionID + '&storyID=0&moduleID=0&taskID=0&todoID=0&extra=laneID=' + 0 + ',columnID=' + col.id), 'data-toggle': 'modal', 'data-size' : 'lg'});
if(priv.canCreateTask) actions.push({text: taskLang.create, url: $.createLink('task', 'create', 'executionID=' + executionID + '&storyID=0&moduleID=0&taskID=0&todoID=0&extra=regionID=' + col.region + ',laneID=' + laneID + ',columnID=' + col.id), 'data-toggle': 'modal', 'data-size' : 'lg'});
if(priv.canBatchCreateTask) actions.push({text: taskLang.batchCreate, url: $.createLink('task', 'batchcreate', 'executionID=' + executionID + '&storyID=0&moduleID=0&taskID=0&iframe=0&extra=regionID=' + col.region + ',laneID=' + laneID + ',columnID=' + col.id), 'data-toggle': 'modal', 'data-size' : 'lg'});
if(priv.canImportBug && vision == 'rnd') actions.push({text: executionLang.importBug, url: $.createLink('execution', 'importBug', 'executionID=' + executionID + '&storyID=0&moduleID=0&taskID=0&todoID=0&extra=laneID=' + laneID + ',columnID=' + col.id), 'data-toggle': 'modal', 'data-size' : 'lg'});
}
return actions;
@@ -278,7 +281,7 @@ window.buildBugActions = function(item)
if(priv.canCopyBug) actions.push({text: bugLang.copy, icon: 'copy', url: $.createLink('bug', 'create', 'productID=' + productID + '&branch=&extras=bugID=' + item.id + ',regionID=' + item.lane + ',laneID=' + item.lane + ',columnID=' + item.column + ',executionID=' + executionID), 'data-toggle': 'modal', 'data-size': 'lg'});
if(priv.canToStoryBug && (item.status != 'closed')) actions.push({text: bugLang.toStory, icon: 'lightbulb', url: $.createLink('story', 'create', 'product=' + productID + '&branch=' + '0' + '&module=' + '0' + '&story=' + '0' + '&execution=' + '0' + '&bugID=' + item.id), 'data-toggle': 'modal', 'data-size': 'lg'});
if(priv.canActivateBug && (item.status == 'fixed' || item.status == 'testing' || item.status == 'tested' || item.status == 'closed')) actions.push({text: bugLang.activate, icon: 'magic', url: $.createLink('bug', 'activate', 'bugID=' + item.id), 'data-toggle': 'modal', 'data-size': 'lg'});
if(priv.canDeleteBug) actions.push({text: bugLang.delete, icon: 'trash', url: $.createLink('bug', 'delete', 'bugID=' + item.id), 'data-confirm': bugLang.confirmDelete, 'innerClass': 'ajax-submit'});
if(priv.canDeleteBug) actions.push({text: bugLang.delete, icon: 'trash', url: $.createLink('bug', 'delete', 'bugID=' + item.id + '&from=taskkanban'), 'data-confirm': bugLang.confirmDelete, 'innerClass': 'ajax-submit'});
return actions;
}
@@ -294,7 +297,7 @@ window.buildTaskActions = function(item)
if(priv.canActivateTask && (item.status == 'developed' || item.status == 'canceled' || item.status == 'closed')) actions.push({text: executionLang.activate, icon: 'magic', url: $.createLink('task', 'activate', 'taskID=' + item.id), 'data-toggle': 'modal', 'data-size': 'lg'});
if(priv.canCreateTask) actions.push({text: taskLang.copy, icon: 'copy', url: $.createLink('task', 'create', 'executionID=' + executionID + '&storyID=' + '0' + '&moduleID=' + '0' + '&taskID=' + item.id), 'data-toggle': 'modal', 'data-size': 'lg'});
if(priv.canCancelTask && (item.status == 'wait' || item.status == 'developing' || item.status == 'pause')) actions.push({text: taskLang.cancel, icon: 'cancel', url: $.createLink('task', 'cancel', 'taskID=' + item.id), 'data-toggle': 'modal', 'data-size': 'lg'});
if(priv.canDeleteTask) actions.push({text: taskLang.delete, icon: 'trash', url: $.createLink('task', 'delete', 'taskID=' + item.id), 'data-confirm': taskLang.confirmDelete, 'innerClass': 'ajax-submit'});
if(priv.canDeleteTask) actions.push({text: taskLang.delete, icon: 'trash', url: $.createLink('task', 'delete', 'executionID=0&taskID=' + item.id + '&from=taskkanban'), 'data-confirm': taskLang.confirmDelete, 'innerClass': 'ajax-submit'});
return actions;
}
+4
View File
@@ -64,6 +64,7 @@ $(document).off('click','.batch-btn').on('click', '.batch-btn', function()
if(confirm(confirmStoryToTaskTip))
{
console.log('ddd');
$('#storyIdList').val(checkedIdList);
}
else
@@ -77,10 +78,13 @@ $(document).off('click','.batch-btn').on('click', '.batch-btn', function()
$('#storyIdList').val(unlinkTaskIdList);
}
}
console.log('ccc');
zui.Modal.hide('#taskModal');
console.log('eee');
const formData = new FormData($("#toTaskForm")[0]);
console.log(formData);
postAndLoadPage($('#toTaskForm').attr('action'), formData);
return false;
-1
View File
@@ -238,7 +238,6 @@ window.getItem = function(info)
info.item.content = {html: content};
if(info.item.color && info.item.color != '#fff') info.item.className = 'color-' + info.item.color.replace('#', '');
info.item.titleAttrs = {'class': 'card-title clip'};
}
window.getItemActions = function(item)
+5 -4
View File
@@ -2406,10 +2406,11 @@ class executionModel extends model
public function updateProducts(int $executionID, object|array $postData): bool
{
$this->loadModel('user');
$products = array_filter(array_merge(zget($postData, 'products', array()), zget($postData, 'otherProducts', array())));
$branches = zget($postData, 'branch', array(0));
$plans = zget($postData, 'plans', array());
$oldProducts = $this->dao->select('*')->from(TABLE_PROJECTPRODUCT)->where('project')->eq($executionID)->fetchGroup('product', 'branch');
$otherProducts = zget($postData, 'otherProducts', array());
$products = !empty($otherProducts) ? array_filter(array_merge(zget($postData, 'products', array()), $otherProducts)) : zget($postData, 'products', array());
$branches = zget($postData, 'branch', array(0));
$plans = zget($postData, 'plans', array());
$oldProducts = $this->dao->select('*')->from(TABLE_PROJECTPRODUCT)->where('project')->eq($executionID)->fetchGroup('product', 'branch');
if(empty($_POST['otherProducts'])) $this->dao->delete()->from(TABLE_PROJECTPRODUCT)->where('project')->eq($executionID)->exec();
$members = array_keys($this->getTeamMembers($executionID));
if(empty($products))
+1 -1
View File
@@ -116,7 +116,7 @@ formBatchPanel
set::control('picker'),
set::ditto(true),
set::defaultDitto('off'),
set::items(array()),
set::items($rdUsers),
set::hidden(strpos("{$showFields}", 'RD') === false),
set::width('180px')
),
+2
View File
@@ -11,6 +11,8 @@ declare(strict_types=1);
namespace zin;
jsVar('workHour', $lang->execution->workHour);
jsVar('storyPoint', $lang->execution->storyPoint);
jsVar('burnBy', $burnBy);
jsVar('+type', str_replace(',', '%2C', $type));
jsVar('executionID', $executionID);
jsVar('watermark', $lang->execution->watermark);
+1
View File
@@ -200,6 +200,7 @@ $fields->field('acl')
->control(array(
'type' => 'aclBox',
'aclItems' => $lang->execution->aclList,
'aclValue' => data('execution.acl'),
'whitelistLabel' => $lang->whitelist,
'userItems' => data('users')
));
+6 -5
View File
@@ -24,11 +24,12 @@ div
(
setClass('w-1/5'),
moduleMenu(set(array(
'modules' => $deptTree,
'activeKey' => $deptID,
'closeLink' => $this->createLink('group', 'manageMember', "groupID={$group->id}"),
'showDisplay' => false,
'app' => $app->tab
'modules' => $deptTree,
'activeKey' => $deptID,
'closeLink' => $this->createLink('group', 'manageMember', "groupID={$group->id}"),
'showDisplay' => false,
'app' => $app->tab,
'toggleSidebar' => false
)))
),
cell
-7
View File
@@ -1,12 +1,5 @@
#status {flex-direction: row;}
#hostCreateForm .form-row:last-child {width: 66%;}
#mainNavbar>.container {padding-top: 8px}
#mainNavbar .nav {justify-content: left; position: relative; left: 0px; top: -4px;}
@media (min-width: 1400px){#mainNavbar .nav {left: -12px;}}
#mainNavbar {background: none;}
#mainNavbar .nav-item>a {padding-right: 0.5rem}
#mainNavbar .nav-item>.active {color: unset; font-weight: 700;}
#mainNavbar .nav-item>a.active:after {position: absolute; content: ''; border-bottom: 2px solid #2e7fff; inset: 0 6px 0 15px;}
.form-horz .form-label{width: 8rem;}
.form-horz .form-group{padding-left: 8rem;}
-1
View File
@@ -96,7 +96,6 @@ $lang->job->paramValueList['$zentao_repopath'] = 'Current version library path';
$lang->job->engineList = array();
$lang->job->engineList[''] = '';
$lang->job->engineList['gitfox'] = 'GitFox';
$lang->job->engineList['gitlab'] = 'GitLab';
$lang->job->engineList['jenkins'] = 'Jenkins';
-1
View File
@@ -96,7 +96,6 @@ $lang->job->paramValueList['$zentao_repopath'] = 'Current version library path';
$lang->job->engineList = array();
$lang->job->engineList[''] = '';
$lang->job->engineList['gitfox'] = 'GitFox';
$lang->job->engineList['gitlab'] = 'GitLab';
$lang->job->engineList['jenkins'] = 'Jenkins';
-1
View File
@@ -96,7 +96,6 @@ $lang->job->paramValueList['$zentao_repopath'] = 'Current version library path';
$lang->job->engineList = array();
$lang->job->engineList[''] = '';
$lang->job->engineList['gitfox'] = 'GitFox';
$lang->job->engineList['gitlab'] = 'GitLab';
$lang->job->engineList['jenkins'] = 'Jenkins';
-1
View File
@@ -96,7 +96,6 @@ $lang->job->paramValueList['$zentao_repopath'] = '当前版本库路径';
$lang->job->engineList = array();
$lang->job->engineList[''] = '';
$lang->job->engineList['gitfox'] = 'GitFox';
$lang->job->engineList['gitlab'] = 'GitLab';
$lang->job->engineList['jenkins'] = 'Jenkins';
+4 -1
View File
@@ -537,7 +537,10 @@ class kanban extends control
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'closeModal' => true, 'load' => true));
}
$this->view->lanes = $this->kanban->getLanePairsByRegion($regionID, $from == 'kanban' ? 'all' : 'story');
$laneType = $from == 'kanban' ? 'all' : 'story';
if($this->config->vision == 'lite') $laneType = 'task';
$this->view->lanes = $this->kanban->getLanePairsByRegion($regionID, $laneType);
$this->view->from = $from;
$this->view->regionID = $regionID;
+1 -1
View File
@@ -93,7 +93,7 @@ foreach($cards as $card)
'class' => 'card-title'
)
),
$card->name
!empty($card->title) ? $card->title : $card->name
)
),
div
@@ -34,7 +34,8 @@ class count_of_developed_story_in_execution extends baseCalc
if(!isset($this->result[$execution])) $this->result[$execution] = 0;
if(!in_array($stage, array('developed', 'testing', 'tested', 'verified', 'released')) && $closedReason == 'done') $this->result[$execution] += 1;
if(in_array($stage, array('developed', 'testing', 'tested', 'verified', 'released'))) $this->result[$execution] += 1;
if($closedReason) $this->result[$execution] += 1;
}
public function getResult($options = array())
@@ -22,7 +22,7 @@ class count_of_assigned_task_in_user extends baseCalc
{
public $dataset = 'getTasks';
public $fieldList = array('t1.id', "if(t1.mode='multi' and t4.`status`!='done', t4.account, t1.assignedTo) as assignedTo", 't1.status', 't1.mode', 't4.account', 't3.status as projectStatus', 't2.status as executionStatus', 't4.status as teamStatus');
public $fieldList = array('t1.id', "case when t1.mode != '' and t4.status != 'done' then t4.account when t1.mode != '' and t4.status = 'done' then '' else t1.assignedTo end as assignedTo", 't1.status', 't1.mode', 't4.account', 't3.status as projectStatus', 't2.status as executionStatus', 't4.status as teamStatus');
public $result = array();
+3 -8
View File
@@ -674,7 +674,7 @@ class dataset
$stmt = $this->dao->select($fieldList)->from(TABLE_TASK)->alias('t1')
->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.execution=t2.id')
->leftJoin(TABLE_PROJECT)->alias('t3')->on('t2.project=t3.id')
->leftJoin(TABLE_TASKTEAM)->alias('t4')->on('t1.id=t4.task')
->leftJoin(TABLE_TASKTEAM)->alias('t4')->on("t1.id=t4.task and t1.mode != ''")
->where('t1.deleted')->eq(0)
->andWhere('t2.deleted')->eq(0)
->andWhere('t3.deleted')->eq(0);
@@ -692,14 +692,9 @@ class dataset
*/
public function getLines($fieldList)
{
$stmt = $this->dao->select($fieldList)->from(TABLE_MODULE)->alias('t1')
->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.root=t2.id')
return $this->dao->select($fieldList)->from(TABLE_MODULE)->alias('t1')
->where('t1.deleted')->eq(0)
->andWhere('t2.deleted')->eq(0)
->andWhere('t1.type')->eq('line')
->andWhere('t2.type')->eq('program');
return $this->defaultWhere($stmt, 't2');
->andWhere('t1.type')->eq('line');
}
/**
+1
View File
@@ -756,6 +756,7 @@ class metricModel extends model
if(!$metric) return false;
$calculator = $this->getCalculator($metric->scope, $metric->purpose, $metric->code);
if(!$calculator) return array();
/* 因为是单个度量项的计算,所以需要优先查看是否支持可用的性能优化,如果有的话,使用性能优化方式计算。*/
/* Because this is a single metric calculation, it is important to first look to see if any performance optimizations are supported and, if so, use them. */
@@ -8,7 +8,7 @@ timeout=0
cid=1
- 测试分组数。 @6
- 测试项目2。第0条的value属性 @1
- 测试项目2。第0条的value属性 @11
*/
include dirname(__FILE__, 7) . '/test/lib/init.php';
@@ -24,4 +24,4 @@ $calc = $metric->calcMetric(__FILE__);
r(count($calc->getResult())) && p('') && e('6'); // 测试分组数。
r($calc->getResult(array('project' => '4'))) && p('0:value') && e('1'); // 测试项目2。
r($calc->getResult(array('project' => '4'))) && p('0:value') && e('11'); // 测试项目2。
@@ -19,4 +19,4 @@ zendata('module')->loadYaml('line', true, 4)->gen(200);
$metric = new metricTest();
$calc = $metric->calcMetric(__FILE__);
r($calc->getResult()) && p('0:value') && e('80'); // 测试产品线数
r($calc->getResult()) && p('0:value') && e('100'); // 测试产品线数
+2 -2
View File
@@ -23,7 +23,7 @@ cid=1
- 第0条的code属性 @count_of_program
- 第1条的code属性 @count_of_doing_program
- 执行metric模块的getListByFilter方法,参数是$filters4, 'wait' @0
- 执行metric模块的getListByFilter方法,参数是$filters4 @212
- 执行metric模块的getListByFilter方法,参数是$filters4 @224
*/
include dirname(__FILE__, 5) . '/test/lib/init.php';
@@ -44,4 +44,4 @@ r($metric->getListByFilter($filters3)) && p('0:code;1:code') && e('count_of_exec
r(count($metric->getListByFilter($filters3))) && p('') && e(25);
r($metric->getListByFilter($filters4)) && p('0:code;1:code') && e('count_of_program,count_of_doing_program');
r($metric->getListByFilter($filters4, 'wait')) && p('') && e('0');
r(count($metric->getListByFilter($filters4))) && p('') && e(212);
r(count($metric->getListByFilter($filters4))) && p('') && e(224);
@@ -40,7 +40,7 @@ cid=1
- 第0条的purpose属性 @scale
- 第1条的object属性 @case
- 第1条的purpose属性 @scale
- 第2条的object属性 @story
- 第2条的object属性 @feedback
- 第2条的purpose属性 @scale
*/
@@ -55,4 +55,4 @@ r($metric->getModuleTreeList('system')) && p('0:object,purpose;1:object,purpo
r($metric->getModuleTreeList('project')) && p('0:object,purpose;1:object,purpose;2:object,purpose') && e('bug,scale,effort,hour,execution,scale'); // 测试范围为project的模块树
r($metric->getModuleTreeList('product')) && p('0:object,purpose;1:object,purpose;2:object,purpose') && e('bug,rate,bug,scale,case,scale'); // 测试范围为product的模块树
r($metric->getModuleTreeList('execution')) && p('0:object,purpose;1:object,purpose;2:object,purpose') && e('story,rate,story,scale,task,hour'); // 测试范围为execution的模块树
r($metric->getModuleTreeList('user')) && p('0:object,purpose;1:object,purpose;2:object,purpose') && e('bug,scale,case,scale,story,scale'); // 测试范围为user的模块树
r($metric->getModuleTreeList('user')) && p('0:object,purpose;1:object,purpose;2:object,purpose') && e('bug,scale,case,scale,feedback,scale'); // 测试范围为user的模块树
@@ -15,7 +15,7 @@ cid=1
- 第0条的id属性 @143
- 第0条的purpose属性 @scale
- 第0条的code属性 @count_of_productplan_in_product
- 获取系统度量项的个数 @139
- 获取系统度量项的个数 @143
- 获取系统度量项目的个数,限制limit @2
*/
@@ -26,5 +26,5 @@ $metric = new metricTest();
r($metric->fetchMetricsByScope('system')) && p('0:id,purpose,code') && e('1,scale,count_of_program'); // 获取系统范围下的第一个度量项
r($metric->fetchMetricsByScope('product')) && p('0:id,purpose,code') && e('143,scale,count_of_productplan_in_product'); // 获取产品范围下的第一个度量项
r(count($metric->fetchMetricsByScope('system'))) && p() && e(139); // 获取系统度量项的个数
r(count($metric->fetchMetricsByScope('system', 2))) && p() && e(2); // 获取系统度量项目的个数,限制limit
r(count($metric->fetchMetricsByScope('system'))) && p() && e(143); // 获取系统度量项的个数
r(count($metric->fetchMetricsByScope('system', 2))) && p() && e(2); // 获取系统度量项目的个数,限制limit
+7 -7
View File
@@ -7,9 +7,9 @@ title=fetchModules
timeout=0
cid=1
- 获取范围为系统的分组个数 @26
- 获取范围为产品的分组个数 @9
- 获取范围为项目的分组个数 @10
- 获取范围为系统的分组个数 @27
- 获取范围为产品的分组个数 @10
- 获取范围为项目的分组个数 @12
- 获取范围为执行的分组个数 @5
- 获取范围为用户的分组个数 @4
@@ -19,8 +19,8 @@ include dirname(__FILE__, 2) . '/lib/calc.unittest.class.php';
$metric = new metricTest();
r(count($metric->fetchModules('system'))) && p() && e(26); // 获取范围为系统的分组个数
r(count($metric->fetchModules('product'))) && p() && e(9); // 获取范围为产品的分组个数
r(count($metric->fetchModules('project'))) && p() && e(10); // 获取范围为项目的分组个数
r(count($metric->fetchModules('system'))) && p() && e(27); // 获取范围为系统的分组个数
r(count($metric->fetchModules('product'))) && p() && e(10); // 获取范围为产品的分组个数
r(count($metric->fetchModules('project'))) && p() && e(12); // 获取范围为项目的分组个数
r(count($metric->fetchModules('execution'))) && p() && e(5); // 获取范围为执行的分组个数
r(count($metric->fetchModules('user'))) && p() && e(4); // 获取范围为用户的分组个数
r(count($metric->fetchModules('user'))) && p() && e(5); // 获取范围为用户的分组个数
@@ -37,7 +37,7 @@ $fnGenerateFilterBlock = function($code, $filterItem) use($lang)
$code == 'object' ? icon
(
'chevron-double-down',
setClass('pull-right collapse-icon'),
setClass('pull-right collapse-icon ghost'),
on::click('collapseFilters')
) : null,
set::headingActions(array($removeAction))
+1 -1
View File
@@ -341,7 +341,7 @@ class metricZen extends metric
}
else
{
if($date == 'now') $date = helper::today();
if($date == 'now') $date = helper::now();
$scopeList = $this->metric->getPairsByScopeAndDate($scope, $date);
+1
View File
@@ -64,6 +64,7 @@ $lang->misc->checkTable = "Prüfe Datentabellen";
$lang->misc->needRepair = "Repariere Datentabellen";
$lang->misc->repairTable = "Datenbank íst beschädigt. Bitte brüfen und reparieren!";
$lang->misc->repairFail = "Reparatur fehlgeschlagen. Bitte wechseln Sie in das Verzeichnis der Datenbank, und versuchen Sie folgenden Befehl <code>myisamchk -r -f %s.MYI</code> zum reparieren der DB.";
$lang->misc->withoutCmd = 'Reparatur fehlgeschlagen.';
$lang->misc->connectFail = "Failed to connect to the database. Error: %s,<br/> Please check the MySQL error log and troubleshoot.";
$lang->misc->tableName = "Tabellenname";
$lang->misc->tableStatus = "Status";
+1
View File
@@ -64,6 +64,7 @@ $lang->misc->checkTable = "Check Data Table";
$lang->misc->needRepair = "Repair Table";
$lang->misc->repairTable = "Database table might be damaged due to power outage. Please check and repair!";
$lang->misc->repairFail = "Failed to repair. Please go to the data directory of your database, and try to execute <code>myisamchk -r -f %s.MYI</code> repair.";
$lang->misc->withoutCmd = 'Failed to repair.';
$lang->misc->connectFail = "Failed to connect to the database. Error: %s,<br/> Please check the MySQL error log and troubleshoot.";
$lang->misc->tableName = "Table Name";
$lang->misc->tableStatus = "Status";
+1
View File
@@ -64,6 +64,7 @@ $lang->misc->checkTable = "Vérifiez les Données de la Table";
$lang->misc->needRepair = "Réparer la Table";
$lang->misc->repairTable = "La Base de Données est peut-être endommagée. Elle a besoin d'être vérifiée et éventuellement réparée !";
$lang->misc->repairFail = "La réparation a échoué. Allez dans le répertoire de données de votre table et essayez d'exécuter <code>myisamchk -r -f %s.MYI</code> pour la réparer. Mais on ne promet rien :-(";
$lang->misc->withoutCmd = 'La réparation a échoué.';
$lang->misc->connectFail = "Echec de connexion à la Base de données. Erreur: %s,<br/>Vérifiez la log des erreurs MySQL et des dysfonctionnements.";
$lang->misc->tableName = "Nom de la Table";
$lang->misc->tableStatus = "Statut";
+1
View File
@@ -64,6 +64,7 @@ $lang->misc->checkTable = "检查修复数据表";
$lang->misc->needRepair = "修复表";
$lang->misc->repairTable = "数据库表可能因为断电原因损坏,需要检查修复!!";
$lang->misc->repairFail = "修复失败,请到该数据库的数据目录下,尝试执行<code>myisamchk -r -f %s.MYI</code>进行修复。";
$lang->misc->withoutCmd = '修复失败。';
$lang->misc->connectFail = "连接数据库失败,错误:%s,<br/> 请检查mysql错误日志,排查错误。";
$lang->misc->tableName = "表名";
$lang->misc->tableStatus = "状态";
+3 -1
View File
@@ -69,7 +69,9 @@ $config->mr->taskDtable->fieldList['name']['flex'] = '';
$config->mr->taskDtable->fieldList['name']['type'] = 'nestedTitle';
$config->mr->taskDtable->fieldList['name']['nestedToggle'] = true;
$config->mr->taskDtable->fieldList['name']['sortType'] = true;
$config->mr->taskDtable->fieldList['name']['link'] = array('module' => 'task', 'method' => 'view', 'params' => 'taskID={id}', 'target' => '_blank');
$config->mr->taskDtable->fieldList['name']['data-toggle'] = 'modal';
$config->mr->taskDtable->fieldList['name']['data-size'] = 'lg';
$config->mr->taskDtable->fieldList['name']['link'] = array('module' => 'task', 'method' => 'view', 'params' => 'taskID={id}');
$config->mr->taskDtable->fieldList['name']['required'] = true;
$config->mr->taskDtable->fieldList['pri']['title'] = $lang->priAB;
+6 -4
View File
@@ -38,9 +38,10 @@ $actionMenu['list']['unlink']['className'] = 'ajax-submit';
$storyCols = $config->release->dtable->story->fieldList;
$storyCols['actions'] = $actionMenu;
$storyCols['id']['checkbox'] = false;
$storyCols['title']['data-toggle'] = '';
$storyCols['title']['data-toggle'] = 'modal';
$storyCols['title']['data-size'] = 'lg';
$storyCols['title']['nestedToggle'] = false;
$storyCols['title']['link'] = array('module' => 'story', 'method' => 'view', 'params' => 'storyID={id}', 'target' => '_blank');
$storyCols['title']['link'] = array('module' => 'story', 'method' => 'view', 'params' => 'storyID={id}');
$storyCols['actions']['list']['unlink']['data-confirm'] = $lang->productplan->confirmUnlinkStory;
$storyCols['actions']['list']['unlink']['url'] = $this->createLink('mr', 'unlink', "MRID=$MR->id&productID=$product->id&type=story&linkID={id}&confirm=yes");
@@ -54,8 +55,9 @@ $bugCols['actions']['list']['unlink']['data-confirm'] = $lang->productplan->conf
$bugCols['actions']['list']['unlink']['url'] = $this->createLink('mr', 'unlink', "MRID=$MR->id&productID=$product->id&type=bug&linkID={id}&confirm=yes");
$bugCols['id']['checkbox'] = false;
$bugCols['title']['data-toggle'] = '';
$bugCols['title']['link'] = array('module' => 'bug', 'method' => 'view', 'params' => 'bugID={id}', 'target' => '_blank');
$bugCols['title']['data-toggle'] = 'modal';
$bugCols['title']['data-size'] = 'lg';
$bugCols['title']['link'] = array('module' => 'bug', 'method' => 'view', 'params' => 'bugID={id}');
$bugs = initTableData($bugs, $bugCols);
$taskCols = $config->mr->taskDtable->fieldList;
+3 -2
View File
@@ -50,8 +50,9 @@ div
$cols = array();
foreach($config->release->dtable->defaultFields['linkBug']['bug'] as $field) $cols[$field] = zget($config->bug->dtable->fieldList, $field, array());
$data = array_values($allBugs);
$cols['title']['data-toggle'] = '';
$cols['title']['link'] = array('module' => 'bug', 'method' => 'view', 'params' => 'bugID={id}', 'target' => '_blank');
$cols['title']['data-toggle'] = 'modal';
$cols['title']['data-size'] = 'lg';
$cols['title']['link'] = array('module' => 'bug', 'method' => 'view', 'params' => 'bugID={id}');
dtable
(
set::userMap($users),
+3 -2
View File
@@ -49,9 +49,10 @@ div
$cols = array();
foreach($config->release->dtable->defaultFields['linkStory'] as $field) $cols[$field] = zget($config->release->dtable->story->fieldList, $field, array());
$cols = array_map(function($col){$col['show'] = true; return $col;}, $cols);
$cols['title']['data-toggle'] = '';
$cols['title']['data-toggle'] = 'modal';
$cols['title']['data-size'] = 'lg';
$cols['title']['nestedToggle'] = false;
$cols['title']['link'] = array('module' => 'story', 'method' => 'view', 'params' => 'storyID={id}', 'target' => '_blank');
$cols['title']['link'] = array('module' => 'story', 'method' => 'view', 'params' => 'storyID={id}');
$data = array_values($allStories);
dtable
+1 -1
View File
@@ -987,7 +987,7 @@ $config->my->audit->dtable->fieldList['title']['name'] = 'title';
$config->my->audit->dtable->fieldList['title']['title'] = $lang->my->auditField->title;
$config->my->audit->dtable->fieldList['title']['type'] = 'title';
$config->my->audit->dtable->fieldList['title']['fixed'] = 'left';
$config->my->audit->dtable->fieldList['title']['link'] = array('module' => '{module}', 'method' => 'view', 'params' => 'id={id}&from=my');
$config->my->audit->dtable->fieldList['title']['link'] = array('module' => '{module}', 'method' => 'view', 'params' => 'id={id}');
$config->my->audit->dtable->fieldList['title']['data-toggle'] = 'modal';
$config->my->audit->dtable->fieldList['title']['data-size'] = 'lg';
$config->my->audit->dtable->fieldList['title']['sortType'] = true;
+1 -1
View File
@@ -66,7 +66,7 @@ window.renderCell = function(result, info)
{
html += "<span class='label gray-pale rounded-xl'>" + multipleAB + "</span>";
}
if(task.parent > 0)
if(task.isChild)
{
html += "<span class='label gray-pale rounded-xl'>" + childrenAB + "</span>";
}

Some files were not shown because too many files have changed in this diff Show More