Merge branch 'main' into feature/doctemplate
This commit is contained in:
@@ -118,6 +118,7 @@ zentaoxx:
|
||||
sed -i "s/text NOT NULL DEFAULT ''/text NULL/" zentaoxx/db_bak/*.sql
|
||||
sed -i "s/text NOT NULL/text NULL/" zentaoxx/db_bak/*.sql
|
||||
sed -i "s/ENGINE=MyISAM/ENGINE=InnoDB/" zentaoxx/db_bak/*.sql
|
||||
sed -i "s/utf8/utf8mb4/" zentaoxx/db_bak/*.sql
|
||||
sed -i "/`xxb_user` ADD `clientStatus`/d; /`xxb_user` ADD `clientLang`/d; /`xxb_file` CHANGE `pathname`/d" zentaoxx/db_bak/xuanxuan.sql
|
||||
cp zentaoxx/db_bak/upgradexuanxuan*.sql zentaoxx/db_bak/xuanxuan.sql zentaoxx/db/
|
||||
rm -rf zentaoxx/db_bak/
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
**/
|
||||
class bugActiveEntry extends entry
|
||||
{
|
||||
/**
|
||||
/**
|
||||
* POST method.
|
||||
*
|
||||
* @param int $bugID
|
||||
@@ -19,11 +19,12 @@ class bugActiveEntry extends entry
|
||||
* @return string
|
||||
*/
|
||||
public function post($bugID)
|
||||
{
|
||||
{
|
||||
$control = $this->loadController('bug', 'activate');
|
||||
|
||||
$fields = 'assignedTo,uid,openedBuild,comment';
|
||||
$this->batchSetPost($fields);
|
||||
|
||||
$control = $this->loadController('bug', 'activate');
|
||||
$control->activate($bugID);
|
||||
|
||||
$data = $this->getData();
|
||||
@@ -33,6 +34,6 @@ class bugActiveEntry extends entry
|
||||
$bug = $this->loadModel('bug')->getByID($bugID);
|
||||
|
||||
return $this->send(200, $this->format($bug, 'openedBy:user,openedDate:time,assignedTo:user,assignedDate:time,reviewedBy:user,reviewedDate:time,lastEditedBy:user,lastEditedDate:time,closedBy:user,closedDate:time,deleted:bool,mailto:userList'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
**/
|
||||
class bugAssignEntry extends entry
|
||||
{
|
||||
/**
|
||||
/**
|
||||
* POST method.
|
||||
*
|
||||
* @param int $bugID
|
||||
@@ -19,11 +19,12 @@ class bugAssignEntry extends entry
|
||||
* @return string
|
||||
*/
|
||||
public function post($bugID)
|
||||
{
|
||||
{
|
||||
$control = $this->loadController('bug', 'assignTo');
|
||||
|
||||
$fields = 'assignedTo,mailto,comment';
|
||||
$this->batchSetPost($fields);
|
||||
|
||||
$control = $this->loadController('bug', 'assignTo');
|
||||
$control->assignTo($bugID);
|
||||
|
||||
$data = $this->getData();
|
||||
@@ -33,6 +34,6 @@ class bugAssignEntry extends entry
|
||||
$bug = $this->loadModel('bug')->getByID($bugID);
|
||||
|
||||
return $this->send(200, $this->format($bug, 'openedBy:user,openedDate:time,assignedTo:user,assignedDate:time,reviewedBy:user,reviewedDate:time,lastEditedBy:user,lastEditedDate:time,closedBy:user,closedDate:time,deleted:bool,mailto:userList'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
**/
|
||||
class bugCloseEntry extends entry
|
||||
{
|
||||
/**
|
||||
/**
|
||||
* POST method.
|
||||
*
|
||||
* @param int $bugID
|
||||
@@ -19,11 +19,12 @@ class bugCloseEntry extends entry
|
||||
* @return string
|
||||
*/
|
||||
public function post($bugID)
|
||||
{
|
||||
{
|
||||
$control = $this->loadController('bug', 'close');
|
||||
|
||||
$fields = 'comment';
|
||||
$this->batchSetPost($fields);
|
||||
|
||||
$control = $this->loadController('bug', 'close');
|
||||
$control->close($bugID);
|
||||
|
||||
$data = $this->getData();
|
||||
@@ -32,6 +33,6 @@ class bugCloseEntry extends entry
|
||||
$bug = $this->loadModel('bug')->getByID($bugID);
|
||||
|
||||
return $this->send(200, $this->format($bug, 'openedBy:user,openedDate:time,assignedTo:user,assignedDate:time,reviewedBy:user,reviewedDate:time,lastEditedBy:user,lastEditedDate:time,closedBy:user,closedDate:time,deleted:bool,mailto:userList'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,11 +20,12 @@ class bugConfirmEntry extends entry
|
||||
**/
|
||||
public function post($bugID)
|
||||
{
|
||||
$control = $this->loadController('bug', 'confirm');
|
||||
|
||||
$fields = 'assignedTo,mailto,comment,pri,type,status,deadline';
|
||||
$bug = $this->loadModel('bug')->getByID($bugID);
|
||||
$this->batchSetPost($fields, $bug);
|
||||
|
||||
$control = $this->loadController('bug', 'confirm');
|
||||
$control->confirm($bugID);
|
||||
|
||||
$data = $this->getData();
|
||||
|
||||
@@ -45,9 +45,10 @@ class bugRecordEstimateEntry extends entry
|
||||
{
|
||||
if($this->config->edition == 'open') return $this->send400('ZenTaoPMS does not have bug effort function.');
|
||||
|
||||
$control = $this->loadController('effort', 'createForObject');
|
||||
|
||||
$fields = 'id,dates,consumed,objectType,objectID,work';
|
||||
$this->batchSetPost($fields);
|
||||
$control = $this->loadController('effort', 'createForObject');
|
||||
$control->createForObject('bug', $bugID);
|
||||
|
||||
$data = $this->getData();
|
||||
|
||||
@@ -20,10 +20,11 @@ class bugResolveEntry extends entry
|
||||
*/
|
||||
public function post($bugID)
|
||||
{
|
||||
$control = $this->loadController('bug', 'resolve');
|
||||
|
||||
$fields = 'resolution,resolvedBuild,resolvedDate,duplicateBug,assignedTo,uid,comment';
|
||||
$this->batchSetPost($fields);
|
||||
|
||||
$control = $this->loadController('bug', 'resolve');
|
||||
$control->resolve($bugID);
|
||||
|
||||
$data = $this->getData();
|
||||
|
||||
@@ -80,6 +80,8 @@ class executionsEntry extends entry
|
||||
{
|
||||
$useCode = $this->checkCodeUsed();
|
||||
|
||||
$control = $this->loadController('execution', 'create');
|
||||
|
||||
$fields = 'project,name,begin,end,lifetime,desc,days,percent,parent';
|
||||
if($useCode) $fields .= ',code';
|
||||
|
||||
@@ -96,8 +98,6 @@ class executionsEntry extends entry
|
||||
$this->setPost('products', $this->request('products', array()));
|
||||
$this->setPost('plans', $this->request('plans', array()));
|
||||
|
||||
$control = $this->loadController('execution', 'create');
|
||||
|
||||
$requireFields = 'name,begin,end';
|
||||
if($useCode) $requireFields .= ',code';
|
||||
$this->requireFields($requireFields);
|
||||
|
||||
@@ -55,12 +55,13 @@ class feedbacksEntry extends entry
|
||||
*/
|
||||
public function post()
|
||||
{
|
||||
$control = $this->loadController('feedback', 'create');
|
||||
|
||||
$fields = 'module,product,type,title,public,desc,status,feedbackBy,notify,uid,pri';
|
||||
$this->batchSetPost($fields);
|
||||
|
||||
$this->setPost('notifyEmail', $this->request('notifyEmail', ''));
|
||||
|
||||
$control = $this->loadController('feedback', 'create');
|
||||
$this->requireFields('title,product');
|
||||
$control->create();
|
||||
|
||||
|
||||
@@ -30,6 +30,9 @@ class gitlabWebhookEntry extends baseEntry
|
||||
$this->app->user = new stdclass();
|
||||
$this->app->user->account = '';
|
||||
$this->app->user->admin = false;
|
||||
$this->app->user->rights['rights'] = array();
|
||||
$this->app->user->rights['acls'] = array();
|
||||
|
||||
$repo = $this->loadModel('repo')->getByID($repoID);
|
||||
if(empty($repo)) return;
|
||||
|
||||
|
||||
@@ -19,10 +19,11 @@ class mrEntry extends baseEntry
|
||||
*/
|
||||
public function post()
|
||||
{
|
||||
$this->loadController('mr', 'create');
|
||||
|
||||
$fields = 'repoID,jobID,sourceBranch,targetBranch,diffs,mergeStatus';
|
||||
$this->batchSetPost($fields);
|
||||
|
||||
$this->loadController('mr', 'create');
|
||||
$MRID = $this->loadModel('mr')->apiCreate();
|
||||
if(dao::isError()) $this->sendError(400, dao::getError());
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ class pipelinesEntry extends entry
|
||||
*/
|
||||
public function get()
|
||||
{
|
||||
|
||||
$pipeline = $this->param('pipeline', '');
|
||||
$repoUrl = $this->param('repoUrl', '');
|
||||
if(empty($pipeline) and empty($repoUrl)) return $this->sendError(400, 'The parameter is incorrect!');
|
||||
|
||||
@@ -20,10 +20,11 @@ class productplanLinkBugsEntry extends entry
|
||||
*/
|
||||
public function post($planID)
|
||||
{
|
||||
$control = $this->loadController('productplan', 'linkBug');
|
||||
|
||||
$fields = 'bugs';
|
||||
$this->batchSetPost($fields);
|
||||
|
||||
$control = $this->loadController('productplan', 'linkBug');
|
||||
$control->linkBug($planID);
|
||||
|
||||
$data = $this->getData();
|
||||
|
||||
@@ -20,10 +20,11 @@ class productplanLinkStoriesEntry extends entry
|
||||
*/
|
||||
public function post($planID)
|
||||
{
|
||||
$control = $this->loadController('productplan', 'linkStory');
|
||||
|
||||
$fields = 'stories';
|
||||
$this->batchSetPost($fields);
|
||||
|
||||
$control = $this->loadController('productplan', 'linkStory');
|
||||
$control->linkStory($planID);
|
||||
|
||||
$data = $this->getData();
|
||||
|
||||
@@ -70,13 +70,14 @@ class productplansEntry extends entry
|
||||
if(!$productID) $productID = $this->param('product', 0);
|
||||
if(!$productID) return $this->sendError(400, 'No product id.');
|
||||
|
||||
$control = $this->loadController('productplan', 'create');
|
||||
|
||||
$fields = 'branch,begin,end,title,desc';
|
||||
$this->batchSetPost($fields);
|
||||
$this->setPost('product', $productID);
|
||||
$this->setPost('parent', $this->request('parent', 0));
|
||||
$this->setPost('branch', $this->request('branch', 0));
|
||||
|
||||
$control = $this->loadController('productplan', 'create');
|
||||
$control->create($productID, $this->param('branch', 0), $this->param('parent', 0));
|
||||
|
||||
$data = $this->getData();
|
||||
|
||||
@@ -63,6 +63,8 @@ class productProjectsEntry extends entry
|
||||
*/
|
||||
public function post()
|
||||
{
|
||||
$control = $this->loadController('project', 'create');
|
||||
|
||||
$fields = 'name,begin,end,products';
|
||||
$this->batchSetPost($fields);
|
||||
|
||||
@@ -73,7 +75,6 @@ class productProjectsEntry extends entry
|
||||
$this->setPost('PM', $this->request('PM', ''));
|
||||
$this->setPost('model', $this->request('model', 'scrum'));
|
||||
|
||||
$control = $this->loadController('project', 'create');
|
||||
$this->requireFields('name,code,begin,end,products');
|
||||
|
||||
$control->create($this->request('model', 'scrum'));
|
||||
|
||||
@@ -72,12 +72,13 @@ class programsEntry extends entry
|
||||
*/
|
||||
public function post()
|
||||
{
|
||||
$control = $this->loadController('program', 'create');
|
||||
|
||||
$fields = 'name,PM,budget,budgetUnit,desc,begin,end';
|
||||
$this->batchSetPost($fields);
|
||||
$this->setPost('acl', $this->request('acl', 'open'));
|
||||
$this->setPost('whitelist', $this->request('whitelist', array()));
|
||||
|
||||
$control = $this->loadController('program', 'create');
|
||||
$this->requireFields('name,begin,end');
|
||||
|
||||
$control->create($this->request('parent', 0));
|
||||
|
||||
@@ -67,6 +67,8 @@ class stakeholdersEntry extends entry
|
||||
*/
|
||||
public function post()
|
||||
{
|
||||
$control = $this->loadController('project', 'create');
|
||||
|
||||
$fields = 'name,begin,end,products';
|
||||
$this->batchSetPost($fields);
|
||||
|
||||
@@ -77,7 +79,6 @@ class stakeholdersEntry extends entry
|
||||
$this->setPost('PM', $this->request('PM', ''));
|
||||
$this->setPost('model', $this->request('model', 'scrum'));
|
||||
|
||||
$control = $this->loadController('project', 'create');
|
||||
$this->requireFields('name,code,begin,end,products');
|
||||
|
||||
$control->create($this->request('model', 'scrum'));
|
||||
|
||||
@@ -69,6 +69,8 @@ class storiesEntry extends entry
|
||||
if(!$productID and isset($this->requestBody->product)) $productID = $this->requestBody->product;
|
||||
if(!$productID) return $this->sendError(400, 'Need product id.');
|
||||
|
||||
$control = $this->loadController('story', 'create');
|
||||
|
||||
$fields = 'title,spec,verify,module,reviewer,type,parent,moduleOptionMenu,source,sourceNote,category,pri,estimate,mailto,keywords,notifyemail,uid,status';
|
||||
$this->batchSetPost($fields);
|
||||
|
||||
@@ -83,7 +85,6 @@ class storiesEntry extends entry
|
||||
$this->setPost('type', $this->param('type', 'story'));
|
||||
$this->setPost('status', $this->param('status', 'draft'));
|
||||
|
||||
$control = $this->loadController('story', 'create');
|
||||
$this->requireFields('title,spec,pri,category');
|
||||
|
||||
$control->create($productID, $this->param('branch', 0), $this->param('moduleID', 0), $this->param('storyID', 0), $this->param('objectID', 0), $this->param('bugID', 0), 0, 0, '', $this->param('type', 'story'));
|
||||
|
||||
@@ -21,11 +21,11 @@ class storyActiveEntry extends entry
|
||||
*/
|
||||
public function post($storyID)
|
||||
{
|
||||
$control = $this->loadController('story', 'activate');
|
||||
|
||||
$fields = 'assignedTo,status,comment';
|
||||
$this->batchSetPost($fields);
|
||||
|
||||
$control = $this->loadController('story', 'activate');
|
||||
|
||||
$control->activate($storyID);
|
||||
|
||||
$data = $this->getData();
|
||||
|
||||
@@ -20,10 +20,11 @@ class storyAssignToEntry extends entry
|
||||
*/
|
||||
public function post($storyID)
|
||||
{
|
||||
$control = $this->loadController('story', 'assignTo');
|
||||
|
||||
$fields = 'assignedTo,comment';
|
||||
$this->batchSetPost($fields);
|
||||
|
||||
$control = $this->loadController('story', 'assignTo');
|
||||
$control->assignTo($storyID);
|
||||
|
||||
$data = $this->getData();
|
||||
|
||||
@@ -20,10 +20,11 @@ class storyCloseEntry extends entry
|
||||
*/
|
||||
public function post($storyID)
|
||||
{
|
||||
$control = $this->loadController('story', 'close');
|
||||
|
||||
$fields = 'closedReason,duplicateStory,comment';
|
||||
$this->batchSetPost($fields);
|
||||
|
||||
$control = $this->loadController('story', 'close');
|
||||
$control->close($storyID);
|
||||
|
||||
$data = $this->getData();
|
||||
|
||||
@@ -45,9 +45,10 @@ class storyRecordEstimateEntry extends entry
|
||||
{
|
||||
if($this->config->edition == 'open') return $this->send400('ZenTaoPMS does not have story effort function.');
|
||||
|
||||
$control = $this->loadController('effort', 'createForObject');
|
||||
$fields = 'id,dates,consumed,objectType,objectID,work';
|
||||
$this->batchSetPost($fields);
|
||||
$control = $this->loadController('effort', 'createForObject');
|
||||
|
||||
$control->createForObject('story', $storyID);
|
||||
|
||||
$data = $this->getData();
|
||||
|
||||
@@ -21,11 +21,11 @@ class storyReviewEntry extends entry
|
||||
*/
|
||||
public function post($storyID)
|
||||
{
|
||||
$control = $this->loadController('story', 'review');
|
||||
|
||||
$fields = 'reviewedDate,result,closedReason,pri,estimate,comment';
|
||||
$this->batchSetPost($fields);
|
||||
|
||||
$control = $this->loadController('story', 'review');
|
||||
|
||||
$control->review($storyID);
|
||||
|
||||
$data = $this->getData();
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
**/
|
||||
class taskActiveEntry extends entry
|
||||
{
|
||||
/**
|
||||
/**
|
||||
* POST method.
|
||||
*
|
||||
* @param int $taskID
|
||||
@@ -19,11 +19,12 @@ class taskActiveEntry extends entry
|
||||
* @return string
|
||||
*/
|
||||
public function post($taskID)
|
||||
{
|
||||
{
|
||||
$control = $this->loadController('task', 'activate');
|
||||
|
||||
$fields = 'assignedTo,left,comment';
|
||||
$this->batchSetPost($fields);
|
||||
|
||||
$control = $this->loadController('task', 'activate');
|
||||
$control->activate($taskID);
|
||||
|
||||
$data = $this->getData();
|
||||
@@ -33,6 +34,6 @@ class taskActiveEntry extends entry
|
||||
$task = $this->loadModel('task')->getByID($taskID);
|
||||
|
||||
return $this->send(200, $this->format($task, 'openedBy:user,openedDate:time,assignedTo:user,assignedDate:time,reviewedBy:user,reviewedDate:time,lastEditedBy:user,lastEditedDate:time,closedBy:user,closedDate:time,deleted:bool,mailto:userList'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ class taskBatchCreateEntry extends entry
|
||||
if(!$executionID) $executionID = $this->param('execution', 0);
|
||||
if(!$executionID) return $this->send400('Need execution id.');
|
||||
|
||||
$control = $this->loadController('task', 'batchCreate');
|
||||
|
||||
$storyID = $this->param('story', 0);
|
||||
$moduleID = $this->param('module', 0);
|
||||
$taskID = $this->param('task', 0);
|
||||
@@ -72,7 +74,6 @@ class taskBatchCreateEntry extends entry
|
||||
$this->setPost('pri', $pri);
|
||||
$this->setPost('story', $stories);
|
||||
|
||||
$control = $this->loadController('task', 'batchCreate');
|
||||
$control->batchCreate($executionID, $storyID, $moduleID, $taskID);
|
||||
|
||||
$data = $this->getData();
|
||||
|
||||
@@ -50,9 +50,10 @@ class taskRecordEstimateEntry extends entry
|
||||
*/
|
||||
public function post($taskID)
|
||||
{
|
||||
$control = $this->loadController('task', 'recordWorkhour');
|
||||
|
||||
$fields = 'date,consumed,left,work';
|
||||
$this->batchSetPost($fields);
|
||||
$control = $this->loadController('task', 'recordWorkhour');
|
||||
$control->recordWorkhour($taskID);
|
||||
|
||||
$data = $this->getData();
|
||||
|
||||
@@ -101,6 +101,8 @@ class tasksEntry extends entry
|
||||
*/
|
||||
public function post($executionID)
|
||||
{
|
||||
$control = $this->loadController('task', 'create');
|
||||
|
||||
$fields = 'name,type,assignedTo,estimate,story,execution,project,module,pri,desc,estStarted,deadline,mailto,team,teamEstimate,multiple,uid';
|
||||
$this->batchSetPost($fields);
|
||||
|
||||
@@ -116,7 +118,6 @@ class tasksEntry extends entry
|
||||
$this->setPost('teamSource', array_fill(0, count($this->request('team')), ''));
|
||||
}
|
||||
|
||||
$control = $this->loadController('task', 'create');
|
||||
$this->requireFields('name,assignedTo,type,estStarted,deadline');
|
||||
|
||||
$control->create($executionID, $this->request('storyID', 0), $this->request('moduleID', 0), $this->request('copyTaskID', 0), $this->request('copyTodoID', 0));
|
||||
|
||||
@@ -74,6 +74,8 @@ class testcasesEntry extends entry
|
||||
if(!$productID and isset($this->requestBody->product)) $productID = $this->requestBody->product;
|
||||
if(!$productID) return $this->sendError(400, 'Need product id.');
|
||||
|
||||
$control = $this->loadController('testcase', 'create');
|
||||
|
||||
$fields = 'module,type,stage,story,title,precondition,pri';
|
||||
$this->batchSetPost($fields);
|
||||
$this->setPost('product', $productID);
|
||||
@@ -84,20 +86,23 @@ class testcasesEntry extends entry
|
||||
$steps = array();
|
||||
$expects = array();
|
||||
$stepType = array();
|
||||
$index = 1;
|
||||
$useName = count(array_column($this->requestBody->steps, 'name')) == count($this->requestBody->steps);
|
||||
foreach($this->requestBody->steps as $step)
|
||||
{
|
||||
$type = isset($step->type) ? $step->type : 'step';
|
||||
$name = $useName ? $step->name : $index;
|
||||
$type = isset($step->type) ? $step->type : 'step';
|
||||
$index ++;
|
||||
|
||||
$steps[] = $step->desc;
|
||||
$expects[] = $type == 'group' ? '' : $step->expect;
|
||||
$stepType[] = $type;
|
||||
$steps[$name] = $step->desc;
|
||||
$expects[$name] = $type == 'group' ? '' : $step->expect;
|
||||
$stepType[$name] = $type;
|
||||
}
|
||||
$this->setPost('steps', $steps);
|
||||
$this->setPost('expects', $expects);
|
||||
$this->setPost('stepType', $stepType);
|
||||
}
|
||||
|
||||
$control = $this->loadController('testcase', 'create');
|
||||
$this->requireFields('title,type,pri,steps');
|
||||
|
||||
$control->create(0);
|
||||
|
||||
@@ -57,13 +57,14 @@ class testsuitesEntry extends entry
|
||||
if(!$productID and isset($this->requestBody->product)) $productID = $this->requestBody->product;
|
||||
if(!$productID) return $this->sendError(400, 'Need product id.');
|
||||
|
||||
$control = $this->loadController('testsuite', 'create');
|
||||
|
||||
$fields = 'name,type';
|
||||
$this->batchSetPost($fields);
|
||||
$this->setPost('product', $productID);
|
||||
$this->setPost('desc', $this->request('desc', ''));
|
||||
$this->setPost('type', $this->request('type', 'private'));
|
||||
|
||||
$control = $this->loadController('testsuite', 'create');
|
||||
$this->requireFields('name');
|
||||
|
||||
$control->create($productID);
|
||||
|
||||
@@ -54,10 +54,11 @@ class ticketsEntry extends entry
|
||||
*/
|
||||
public function post()
|
||||
{
|
||||
$control = $this->loadController('ticket', 'create');
|
||||
|
||||
$fields = 'module,product,type,openedBuild,assignedTo,deadline,title,desc,status,notify,uid,pri';
|
||||
$this->batchSetPost($fields);
|
||||
|
||||
$control = $this->loadController('ticket', 'create');
|
||||
$this->requireFields('title,product,module');
|
||||
$control->create();
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@ class todosEntry extends entry
|
||||
*/
|
||||
public function post()
|
||||
{
|
||||
$control = $this->loadController('todo', 'create');
|
||||
|
||||
$fields = 'name,desc,begin,end,private';
|
||||
$this->batchSetPost($fields);
|
||||
|
||||
@@ -53,7 +55,6 @@ class todosEntry extends entry
|
||||
$this->setPost('end', str_replace(':', '', $this->request('end')));
|
||||
$this->setPost('pri', $this->request('pri', '3'));
|
||||
|
||||
$control = $this->loadController('todo', 'create');
|
||||
$this->requireFields('name');
|
||||
|
||||
$control->create();
|
||||
|
||||
@@ -55,6 +55,8 @@ class usersEntry extends entry
|
||||
*/
|
||||
public function post()
|
||||
{
|
||||
$control = $this->loadController('user', 'create');
|
||||
|
||||
$fields = 'type,dept,account,password,visions,realname,join,role,email,commiter,gender,group,passwordStrength';
|
||||
$this->batchSetPost($fields);
|
||||
|
||||
@@ -74,7 +76,6 @@ class usersEntry extends entry
|
||||
$this->setPost('passwordLength', strlen($_POST['password1']));
|
||||
unset($_POST['password']);
|
||||
|
||||
$control = $this->loadController('user', 'create');
|
||||
$this->requireFields('account,gender,password1,realname');
|
||||
|
||||
$control->create();
|
||||
|
||||
+3
-1
@@ -16,7 +16,7 @@ if(!class_exists('config')){class config{}}
|
||||
if(!function_exists('getWebRoot')){function getWebRoot(){}}
|
||||
|
||||
/* 基本设置。Basic settings. */
|
||||
$config->version = '21.5'; // ZenTaoPHP的版本。 The version of ZenTaoPHP. Don't change it.
|
||||
$config->version = '21.6'; // 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.
|
||||
@@ -270,3 +270,5 @@ else
|
||||
unset($config->maxVersion);
|
||||
unset($config->ipdVersion);
|
||||
}
|
||||
|
||||
if(strtolower($config->db->encoding) == 'utf8') $config->db->encoding = 'utf8mb4';
|
||||
|
||||
@@ -582,6 +582,7 @@ define('TABLE_RISK', '`' . $config->db->prefix . 'risk`');
|
||||
define('TABLE_RISKISSUE', '`' . $config->db->prefix . 'riskissue`');
|
||||
define('TABLE_SOLUTIONS', '`' . $config->db->prefix . 'solutions`');
|
||||
define('TABLE_SQLVIEW', '`' . $config->db->prefix . 'sqlview`');
|
||||
define('TABLE_SQLBUILDER', '`' . $config->db->prefix . 'sqlbuilder`');
|
||||
define('TABLE_TICKET', '`' . $config->db->prefix . 'ticket`');
|
||||
define('TABLE_TICKETRELATION', '`' . $config->db->prefix . 'ticketrelation`');
|
||||
define('TABLE_TICKETSOURCE', '`' . $config->db->prefix . 'ticketsource`');
|
||||
@@ -638,6 +639,9 @@ if(!defined('TABLE_MARKETREPORT')) define('TABLE_MARKETREPORT', '`' . $confi
|
||||
if(!defined('TABLE_MARKETRESEARCH')) define('TABLE_MARKETRESEARCH', '`' . $config->db->prefix . 'project`');
|
||||
if(!defined('TABLE_SYSTEM')) define('TABLE_SYSTEM', '`' . $config->db->prefix . 'system`');
|
||||
|
||||
define('JIRA_TMPRELATION', '`jiratmprelation`');
|
||||
define('CONFLUENCE_TMPRELATION', '`confluencetmprelation`');
|
||||
|
||||
$config->objectTables['dept'] = TABLE_DEPT;
|
||||
$config->objectTables['product'] = TABLE_PRODUCT;
|
||||
$config->objectTables['productplan'] = TABLE_PRODUCTPLAN;
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
ALTER TABLE `zt_workflowgroup` MODIFY `name` varchar(255) NOT NULL DEFAULT '';
|
||||
ALTER TABLE `zt_workflowgroup` ADD COLUMN `exclusive` enum('0','1') NOT NULL DEFAULT '0' AFTER `main`;
|
||||
ALTER TABLE `zt_doclib` ADD COLUMN `archived` enum('0','1') NOT NULL DEFAULT '0';
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -3,3 +3,9 @@ ALTER TABLE `zt_feedback` ADD COLUMN `prevAssignedTo` varchar(255) NOT NULL DEFA
|
||||
|
||||
ALTER TABLE `zt_doc` ADD COLUMN `readGroups` varchar(255) NOT NULL DEFAULT '' AFTER `users`;
|
||||
ALTER TABLE `zt_doc` ADD COLUMN `readUsers` text NULL AFTER `readGroups`;
|
||||
|
||||
ALTER TABLE `zt_workflowgroup` MODIFY `name` varchar(255) NOT NULL DEFAULT '';
|
||||
ALTER TABLE `zt_workflowgroup` ADD COLUMN `exclusive` enum('0','1') NOT NULL DEFAULT '0' AFTER `main`;
|
||||
ALTER TABLE `zt_doclib` ADD COLUMN `archived` enum('0','1') NOT NULL DEFAULT '0';
|
||||
|
||||
UPDATE `zt_cron` SET `command` = 'moduleName=compile&methodName=ajaxSyncCompile' WHERE `command` = 'moduleName=compile&methodName=syncCompile';
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
ALTER TABLE `zt_compile` CHANGE `status` `status` varchar(100) NOT NULL DEFAULT '';
|
||||
ALTER TABLE `zt_measqueue` CHANGE `status` `status` varchar(100) NOT NULL DEFAULT '';
|
||||
|
||||
REPLACE INTO `zt_workflowaction` (`module`, `action`, `method`, `name`, `type`, `batchMode`, `extensionType`, `open`, `position`, `layout`, `show`, `order`, `buildin`, `role`, `virtual`, `conditions`, `verifications`, `hooks`, `linkages`, `js`, `css`, `toList`, `blocks`, `desc`, `status`, `vision`, `createdBy`, `createdDate`, `editedBy`, `editedDate`) VALUES
|
||||
('feedback', 'batchcreate', 'batchcreate', '批量创建', 'batch', 'different', 'none', 'normal', 'browse', 'normal', 'direct', 0, 1, 'buildin', 0, NULL, NULL, NULL, NULL, NULL, NULL, '', NULL, NULL, 'enable', 'rnd', 'admin', '2025-03-24 14:50:45', '', NULL),
|
||||
('feedback', 'exporttemplate', 'exporttemplate', '导出模板', 'single', 'different', 'none', 'modal', 'browse', 'normal', 'direct', 0, 1, 'buildin', 0, NULL, NULL, NULL, NULL, NULL, NULL, '', NULL, '', 'enable', 'rnd', 'admin', '2025-03-24 14:50:45', '', NULL),
|
||||
('feedback', 'import', 'import', '导入', 'single', 'different', 'none', 'modal', 'browse', 'normal', 'direct', 0, 1, 'buildin', 0, NULL, NULL, NULL, NULL, NULL, NULL, '', NULL, NULL, 'enable', 'rnd', 'admin', '2025-03-24 14:50:45', '', NULL),
|
||||
('feedback', 'showimport', 'showimport', '显示导入内容', 'single', 'different', 'none', 'modal', 'browse', 'normal', 'direct', 0, 1, 'buildin', 0, NULL, NULL, NULL, NULL, NULL, NULL, '', NULL, NULL, 'enable', 'rnd', 'admin', '2025-03-24 14:50:45', '', NULL);
|
||||
|
||||
DELETE FROM `zt_workflowdatasource` WHERE `code` IN ('color', 'litecolor');
|
||||
INSERT INTO `zt_workflowdatasource` (`type`, `name`, `code`, `buildin`, `vision`, `createdBy`, `createdDate`, `datasource`, `view`, `keyField`, `valueField`) VALUES
|
||||
('option', '颜色', 'color', '1', 'rnd', 'admin', '1970-01-01 00:00:01', '{"#ef4444":"#ef4444","#f97316":"#f97316","#eab308":"#eab308","#84cc16":"#84cc16","#22c55e":"#22c55e","#14b8a6":"#14b8a6","#0ea5e9":"#0ea5e9","#6366f1":"#6366f1","#a855f7":"#a855f7","#d946ef":"#d946ef","#ec4899":"#ec4899"}', '', '', ''),
|
||||
('option', '颜色', 'litecolor', '1', 'lite', 'admin', '1970-01-01 00:00:01', '{"#ef4444":"#ef4444","#f97316":"#f97316","#eab308":"#eab308","#84cc16":"#84cc16","#22c55e":"#22c55e","#14b8a6":"#14b8a6","#0ea5e9":"#0ea5e9","#6366f1":"#6366f1","#a855f7":"#a855f7","#d946ef":"#d946ef","#ec4899":"#ec4899"}', '', '', '');
|
||||
|
||||
UPDATE `zt_workflowfield` SET `control` = 'select', `options` = (SELECT id FROM `zt_workflowdatasource` WHERE `code` = 'color' limit 1) WHERE `module` = 'testcase' AND `field` = 'color';
|
||||
UPDATE `zt_workflowfield` SET `control` = 'select', `options` = (SELECT id FROM `zt_workflowdatasource` WHERE `code` = 'bugs' limit 1) WHERE `module` = 'testcase' AND `field` = 'fromBug';
|
||||
UPDATE `zt_workflowfield` SET `control` = 'select', `options` = (SELECT id FROM `zt_workflowdatasource` WHERE `code` = 'color' limit 1) WHERE `module` = 'story' AND `field` = 'color';
|
||||
UPDATE `zt_workflowfield` SET `control` = 'select', `options` = (SELECT id FROM `zt_workflowdatasource` WHERE `code` = 'bugs' limit 1) WHERE `module` = 'story' AND `field` = 'fromBug';
|
||||
UPDATE `zt_workflowfield` SET `control` = 'select', `options` = (SELECT id FROM `zt_workflowdatasource` WHERE `code` = 'feedbacks' limit 1) WHERE `module` = 'story' AND `field` = 'feedback';
|
||||
UPDATE `zt_workflowfield` SET `control` = 'select', `options` = (SELECT id FROM `zt_workflowdatasource` WHERE `code` = 'stories' limit 1) WHERE `module` = 'story' AND `field` = 'duplicateStory';
|
||||
UPDATE `zt_workflowfield` SET `control` = 'select', `options` = (SELECT id FROM `zt_workflowdatasource` WHERE `code` = 'color' limit 1) WHERE `module` = 'requirement' AND `field` = 'color';
|
||||
UPDATE `zt_workflowfield` SET `control` = 'select', `options` = (SELECT id FROM `zt_workflowdatasource` WHERE `code` = 'feedbacks' limit 1) WHERE `module` = 'requirement' AND `field` = 'feedback';
|
||||
UPDATE `zt_workflowfield` SET `control` = 'select', `options` = (SELECT id FROM `zt_workflowdatasource` WHERE `code` = 'stories' limit 1) WHERE `module` = 'requirement' AND `field` = 'duplicateStory';
|
||||
UPDATE `zt_workflowfield` SET `control` = 'select', `options` = (SELECT id FROM `zt_workflowdatasource` WHERE `code` = 'color' limit 1) WHERE `module` = 'epic' AND `field` = 'color';
|
||||
UPDATE `zt_workflowfield` SET `control` = 'select', `options` = (SELECT id FROM `zt_workflowdatasource` WHERE `code` = 'feedbacks' limit 1) WHERE `module` = 'epic' AND `field` = 'feedback';
|
||||
UPDATE `zt_workflowfield` SET `control` = 'select', `options` = (SELECT id FROM `zt_workflowdatasource` WHERE `code` = 'stories' limit 1) WHERE `module` = 'epic' AND `field` = 'duplicateStory';
|
||||
UPDATE `zt_workflowfield` SET `control` = 'select', `options` = (SELECT id FROM `zt_workflowdatasource` WHERE `code` = 'color' limit 1) WHERE `module` = 'bug' AND `field` = 'color';
|
||||
UPDATE `zt_workflowfield` SET `control` = 'select', `options` = (SELECT id FROM `zt_workflowdatasource` WHERE `code` = 'color' limit 1) WHERE `module` = 'task' AND `field` = 'color';
|
||||
UPDATE `zt_workflowfield` SET `control` = 'select', `options` = (SELECT id FROM `zt_workflowdatasource` WHERE `code` = 'bugs' limit 1) WHERE `module` = 'task' AND `field` = 'fromBug';
|
||||
UPDATE `zt_workflowfield` SET `name` = REPLACE(`name`, '版本', '代码') WHERE `module` = 'task' AND `field` = 'repo';
|
||||
DELETE FROM `zt_workflowfield` WHERE `module` = 'testcase' AND `field` in ('order', 'frequency');
|
||||
DELETE FROM `zt_workflowfield` WHERE `module` = 'product' AND `field` in ('order', 'createdVersion');
|
||||
DELETE FROM `zt_workflowfield` WHERE `module` = 'story' AND `field` in ('childStories', 'linkStories');
|
||||
DELETE FROM `zt_workflowfield` WHERE `module` = 'requirement' AND `field` in ('childStories', 'linkStories', 'toBug', 'fromBug');
|
||||
DELETE FROM `zt_workflowfield` WHERE `module` = 'epic' AND `field` in ('childStories', 'linkStories', 'toBug', 'fromBug');
|
||||
DELETE FROM `zt_workflowfield` WHERE `module` = 'bug' AND `field` = 'storyVersion';
|
||||
DELETE FROM `zt_workflowfield` WHERE `module` = 'task' AND `field` in ('storyVersion', 'designVersion', 'v1', 'v2', 'vision');
|
||||
DELETE FROM `zt_workflowfield` WHERE `module` = 'project' AND `field` = 'project';
|
||||
DELETE FROM `zt_workflowfield` WHERE `module` in ('project', 'execution') AND `field` in ('budgetUnit', 'output', 'path', 'grade', 'version', 'parentVersion', 'openedVersion', 'order', 'vision', 'team');
|
||||
|
||||
UPDATE `zt_workflowfield` SET `control` = 'multi-select' WHERE `module` = 'testcase' AND `field` = 'stage';
|
||||
UPDATE `zt_workflowfield` SET `control` = 'multi-select' WHERE `module` = 'bug' AND `field` = 'os';
|
||||
UPDATE `zt_workflowfield` SET `control` = 'multi-select' WHERE `module` = 'bug' AND `field` = 'browser';
|
||||
UPDATE `zt_workflowfield` SET `control` = 'select' WHERE `module` = 'project' AND `field` = 'model';
|
||||
UPDATE `zt_workflowfield` SET `control` = 'select' WHERE `module` = 'project' AND `field` = 'type';
|
||||
UPDATE `zt_workflowfield` SET `control` = 'select', `options` = "{\"extend\":\"\\u7ee7\\u627f\",\"reset\":\"\\u91cd\\u65b0\\u5b9a\\u4e49\"}" WHERE `module` = 'project' AND `field` = 'auth';
|
||||
|
||||
UPDATE `zt_workflowaction` SET `layout` = 'side' WHERE `module` = 'ticket' AND `action` = 'edit';
|
||||
|
||||
DELETE FROM `zt_block` WHERE `dashboard` = 'my' AND `module` = 'product' AND `code` IN ('overview', 'statistic') AND `vision` = 'lite';
|
||||
DELETE FROM `zt_block` WHERE `dashboard` = 'my' AND `module` = 'qa' AND `code` = 'statistic' AND `vision` = 'lite';
|
||||
|
||||
ALTER TABLE `zt_charter` ADD COLUMN `branch` text NULL AFTER `product`;
|
||||
+275
-272
File diff suppressed because it is too large
Load Diff
+166
@@ -1,3 +1,169 @@
|
||||
2025-04-03 21.6
|
||||
完成的需求
|
||||
修复的Bug
|
||||
|
||||
2025-03-21 21.6.beta
|
||||
完成的需求
|
||||
开源版:
|
||||
72457 jira数据导入可以导入bug的影响版本字段
|
||||
72350 jira导入支持版本下的子研发需求和bug
|
||||
72284 jira导入方式的提示语优化
|
||||
72151 jira导入页面,对象映射默认匹配选项
|
||||
72150 jira对象字段导入逻辑调整
|
||||
72106 jira导入增加进度提示
|
||||
72103 去掉文档创建点击发布按钮出现的弹窗
|
||||
72102 去掉文档创建点击存为草稿按钮出现的弹窗
|
||||
71965 jira导入字段映射过滤已选字段
|
||||
71964 jira导入字段映射选项修改
|
||||
71870 jira数据导入页面增加提示语
|
||||
71765 支持将已发布的文档存为草稿
|
||||
71749 文档编辑页面点击发布按钮的逻辑调整
|
||||
71748 文档编辑页面点击存为草稿按钮的逻辑调整
|
||||
71747 打印创建文档的弹窗
|
||||
71746 创建文档时先出弹窗设置所属位置和权限
|
||||
71736 支持在章节下添加子章节或添加同级章节
|
||||
71674 修改团队空间中文档设置弹窗、移动弹窗内的字段
|
||||
71673 修改我的空间中文档设置弹窗、移动弹窗内的字段
|
||||
71672 修改项目空间中文档设置弹窗、移动弹窗内的字段
|
||||
71671 修改产品空间中文档设置弹窗、移动弹窗内的字段
|
||||
71651 发布中的应用版本号取消格式判断
|
||||
71639 jira云版本回收站的项目不导入禅道
|
||||
71528 支持对删除章节进行权限控制
|
||||
71527 支持对编辑章节进行权限控制
|
||||
71526 支持对添加章节进行权限控制
|
||||
71476 在文档模块中打印删除子文档的弹窗
|
||||
71474 修改编辑目录弹窗中的上级目录字段
|
||||
71396 修改文档模块中删除目录的弹窗提示语和逻辑
|
||||
71384 支持删除章节
|
||||
71383 在文档列表页打印编辑章节的弹窗
|
||||
71271 在文档列表页打印添加子章节的弹窗
|
||||
71260 支持编辑章节
|
||||
71258 支持在文档下添加子章节
|
||||
71163 jira导入后执行自动匹配关系
|
||||
71135 在文档列表页打印删除章节的二次确认弹窗
|
||||
70729 jira导入后层级关系自动匹配关系
|
||||
70728 jira导入后评论自动匹配关系
|
||||
70727 jira导入后工时自动匹配关系
|
||||
70725 jira导入后项目自动匹配关系
|
||||
70682 导入页面默认匹配字段
|
||||
70572 jira导入后数据能够正常操作
|
||||
70571 再次进入导入页面回填上次信息
|
||||
70570 jira导入后用户信息自动匹配关系
|
||||
70568 jira导入支持对象解决方案映射
|
||||
70567 jira导入支持对象动作映射
|
||||
70566 jira导入支持对象状态映射
|
||||
70565 jira导入支持对象字段映射
|
||||
70564 jira导入支持对象映射
|
||||
70563 jira导入页面交互优化
|
||||
企业版:
|
||||
72529 Confluence导入去掉空间映射步骤的页面
|
||||
72316 新增Confluence 文档列表获取接口
|
||||
72310 新增Confluence 子页面列表获取接口
|
||||
72309 新增获取 Confluence 用户信息接口
|
||||
72308 新增Confluence 空间列表获取接口
|
||||
72307 新增Jira 事务列表获取接口
|
||||
72306 新增Confluence 对象获取接口
|
||||
72305 新增Jira 事务获取接口
|
||||
72304 新增Confluence 附件下载接口
|
||||
72276 支持导入Confluence编辑器中插入的“信息面板”和“备注面板”
|
||||
72275 支持导入Confluence编辑器中插入的“日期”
|
||||
72268 支持导入Confluence编辑器中插入的“决定”
|
||||
72251 jira导入后内置字段的处理
|
||||
72200 支持导入Confluence编辑器中插入的“附件”
|
||||
72185 工作流支持发布到项目的更多菜单中
|
||||
72175 修改Confluence导入文件夹映射关系
|
||||
72149 jira对象动作导入逻辑调整
|
||||
72133 升级后实现禅道原来文档的权限迁移为协作文档的权限
|
||||
72131 支持导入Confluence页面中的“评论”内容
|
||||
72123 实现协作文档的权限继承后台文档的权限
|
||||
72111 支持导入Confluence编辑器中插入的“贡献者”
|
||||
72032 实现禅道章节增加权限控制
|
||||
72027 调整创建文档弹窗和设置弹窗的文档协作权限
|
||||
72008 支持导入Confluence编辑器中插入的“用户信息”
|
||||
72007 支持导入Confluence编辑器中插入的“展开”
|
||||
72006 支持导入Confluence编辑器中插入的“摘录”
|
||||
71968 jira导入后的项目流程模板映射逻辑
|
||||
71879 支持导入Confluence编辑器中插入的“操作项”
|
||||
71877 支持导入Confluence编辑器中插入的“分隔条”
|
||||
71876 Confluence导入页面自定义布局后的映射规则
|
||||
71875 实现Confluence数据格式转Block suite格式的适配机制
|
||||
71873 支持Confluence导入页面中上传的附件
|
||||
71871 Confluence中文件夹的映射关系调整
|
||||
71863 支持将Confluence中已归档的空间导入到禅道产品空间中
|
||||
71862 支持将Confluence中已归档的空间导入到禅道项目空间中
|
||||
71861 支持将Confluence中已归档的空间导入到禅道团队空间中
|
||||
71859 在Confluence导入空间映射页面,增加已归档的空间的显示
|
||||
71842 支持导入Confluence编辑器中插入“任务报告”
|
||||
71840 支持导入Confluence编辑器中插入的“PDF”文件
|
||||
71839 支持导入Confluence编辑器中插入的“Powerpoint”文件
|
||||
71838 支持导入Confluence编辑器中插入的“Excel”文件
|
||||
71837 支持导入Confluence编辑器中插入的“子页面”
|
||||
71833 支持导入Confluence编辑器中插入的“无序列表”
|
||||
71832 支持导入Confluence编辑器中插入的“Jira事务”
|
||||
71831 支持导入Confluence编辑器中插入的“卡片”
|
||||
71830 Confluence页面中无法导入的内容,在导入后的文档中增加标识
|
||||
71829 支持导入Confluence编辑器中插入的“锚点链接”
|
||||
71827 在Confluence空间映射表单页打印标识字段
|
||||
71743 Confluence导入支持白板的映射
|
||||
71690 支持导入Confluence编辑器中插入的“表情符号”
|
||||
71644 后台数据导入页新增导入Confluence入口
|
||||
71643 支持导入Confluence用户
|
||||
71451 反馈中存在未关闭的转化对象单个关闭反馈给出提示信息
|
||||
71450 关闭处理中的反馈时给未关闭转换对象的指派人发送消息通知
|
||||
71439 支持Confluence导入博文状态的映射
|
||||
71436 支持Confluence导入页面状态的映射
|
||||
71330 支持Confluence导入填写授权信息
|
||||
71312 地盘的日志列表支持高级表格功能
|
||||
71305 支持导入Confluence编辑器中插入的“Confluence页面”
|
||||
71294 Confluence导入后支持文档的版本切换
|
||||
71285 支持导入Confluence编辑器中插入的“Draw.io Diagrams”数据
|
||||
71279 Confluence导入支持子文件夹映射
|
||||
71245 Confluence导入后博文的权限匹配
|
||||
71244 支持导入Confluence编辑器中插入的“音视频”文件
|
||||
71243 支持导入Confluence编辑器中插入的“Word”文件
|
||||
71242 支持导入confluence编辑器中插入的“图片”文件
|
||||
71241 保留Confluence页面导入后的段落格式
|
||||
71240 支持导入Confluence编辑器中插入的“引用”
|
||||
71239 支持导入Confluence编辑器中插入的“代码片段”
|
||||
71236 保留Confluence页面导入后的文本样式
|
||||
71235 保留Confluence页面导入后的标题结构
|
||||
71234 支持导入Confluence编辑器中插入的“链接”
|
||||
71192 支持导入Confluence编辑器中插入的“有序列表”
|
||||
71181 支持将Confluence中已归档的页面导入到禅道
|
||||
71164 jira从文件导增加API接口对接
|
||||
71157 Confluence导入后智能链接的权限匹配
|
||||
71156 Confluence导入后数据库的权限匹配
|
||||
71155 Confluence导入后页面的权限匹配
|
||||
71154 Confluence导入后文件夹的权限匹配
|
||||
71153 Confluence导入后空间的权限匹配
|
||||
71143 Confluence导入支持博文的映射
|
||||
71141 Confluence导入支持智能链接页面映射
|
||||
71140 Confluence导入支持数据库页面表格视图布局的映射
|
||||
71101 反馈列表中批量关闭反馈时过滤掉存在未关闭的转化对象的反馈
|
||||
71068 已处理状态的反馈支持激活功能
|
||||
70726 jira导入后工作流自动匹配关系
|
||||
70722 支持将Confluence中已归档的空间导入到禅道我的空间中
|
||||
70717 Confluence导入支持空间映射的页面展示
|
||||
70675 支持导入Confluence编辑器中插入的“表格”
|
||||
70670 Confluence导入支持页面映射
|
||||
70668 Confluence导入支持文件夹映射
|
||||
70667 Confluence导入支持空间映射
|
||||
70569 jira导入全局关联关系映射(企业版)
|
||||
60842 已关闭反馈激活时根据实际情况更新状态
|
||||
60841 已关闭的反馈激活转化的对象后状态更新为处理中
|
||||
60840 已处理的反馈激活转化的对象后状态更新为处理中
|
||||
60837 反馈的历史记录中提供详细的系统更新状态的信息
|
||||
修复的Bug
|
||||
开源版:
|
||||
60409 导入jira数据后内置字段中没有解决时间/解决方案和环境信息
|
||||
60473 confluence数据导入后用户无法修改密码
|
||||
60646 jira故事的story point导入后内容没有带入
|
||||
60668 文档添加子章节后,文档一直显示加载中
|
||||
企业版:
|
||||
59343 自定义列勾选富文本类型字段后有报错
|
||||
旗舰版:
|
||||
60648 评审时回退到开始节点并选择按审批流程顺序审批后发起人未收到邮件
|
||||
|
||||
2025-03-06 21.5
|
||||
完成的需求
|
||||
开源版:
|
||||
|
||||
@@ -195,6 +195,7 @@ unset($lang->createIcons['product']);
|
||||
unset($lang->createIcons['program']);
|
||||
|
||||
if($config->edition != 'open') unset($lang->workflow->menu->flowgroup);
|
||||
if($config->edition != 'open') unset($lang->workflow->menu->ruler);
|
||||
|
||||
$lang->createObjects['story'] = 'Story';
|
||||
$lang->createObjects['task'] = 'Task';
|
||||
|
||||
@@ -195,6 +195,7 @@ unset($lang->createIcons['product']);
|
||||
unset($lang->createIcons['program']);
|
||||
|
||||
if($config->edition != 'open') unset($lang->workflow->menu->flowgroup);
|
||||
if($config->edition != 'open') unset($lang->workflow->menu->ruler);
|
||||
|
||||
$lang->createObjects['story'] = 'Story';
|
||||
$lang->createObjects['task'] = 'Task';
|
||||
|
||||
@@ -195,6 +195,7 @@ unset($lang->createIcons['product']);
|
||||
unset($lang->createIcons['program']);
|
||||
|
||||
if($config->edition != 'open') unset($lang->workflow->menu->flowgroup);
|
||||
if($config->edition != 'open') unset($lang->workflow->menu->ruler);
|
||||
|
||||
$lang->createObjects['story'] = 'Story';
|
||||
$lang->createObjects['task'] = 'Task';
|
||||
|
||||
@@ -195,6 +195,7 @@ unset($lang->createIcons['product']);
|
||||
unset($lang->createIcons['program']);
|
||||
|
||||
if($config->edition != 'open') unset($lang->workflow->menu->flowgroup);
|
||||
if($config->edition != 'open') unset($lang->workflow->menu->ruler);
|
||||
|
||||
$lang->createObjects['story'] = '目标';
|
||||
$lang->createObjects['task'] = '任务';
|
||||
|
||||
@@ -45,6 +45,7 @@ else
|
||||
);
|
||||
}
|
||||
|
||||
$lang->task->statusList['changed'] = $lang->task->storyChange;
|
||||
featureBar
|
||||
(
|
||||
li
|
||||
|
||||
@@ -49,7 +49,7 @@ if(common::canModify('execution', $execution))
|
||||
|
||||
$cols = $this->loadModel('datatable')->getSetting('execution');
|
||||
$tableData = initTableData($tasks, $cols, $this->task);
|
||||
$lang->task->statusList['changed'] = $lang->my->storyChanged;
|
||||
$lang->task->statusList['changed'] = $lang->task->storyChange;
|
||||
foreach($tableData as $task)
|
||||
{
|
||||
$task->rawStory = $task->story;
|
||||
|
||||
@@ -595,7 +595,6 @@ $lang->resource->doc->sortCatalog = 'sortCatalog';
|
||||
$lang->resource->doc->sortDoc = 'sortDoc';
|
||||
$lang->resource->doc->deleteCatalog = 'deleteCatalog';
|
||||
// $lang->resource->doc->displaySetting = 'displaySetting';
|
||||
$lang->resource->doc->exportFiles = 'exportFiles';
|
||||
|
||||
$lang->doc->methodOrder[5] = 'index';
|
||||
$lang->doc->methodOrder[10] = 'mySpace';
|
||||
|
||||
@@ -919,7 +919,7 @@ class baseControl
|
||||
ob_start();
|
||||
call_user_func_array(array($module, $methodName), $fetchParams);
|
||||
$output = ob_get_contents();
|
||||
ob_end_clean();
|
||||
if(ob_get_level() > 0) ob_end_clean();
|
||||
|
||||
unset($module);
|
||||
|
||||
|
||||
@@ -843,12 +843,6 @@ class baseRouter
|
||||
*/
|
||||
public function setDebug()
|
||||
{
|
||||
/* 如果没有启用 debug 模式,但是用户已登录且在请求头中有 X-ZIN-DEBUG,则启用 debug 模式。If debug is not enabled, but the user is logged in and X-ZIN-DEBUG is in the request header, enable debug mode. */
|
||||
if(empty($this->config->debug) && !empty($_SERVER['HTTP_X_ZIN_DEBUG']) && !empty($_SESSION['user']->account) && $_SESSION['user']->account != 'guest')
|
||||
{
|
||||
$this->config->debug = 1;
|
||||
}
|
||||
|
||||
if(!empty($this->config->debug)) error_reporting(E_ALL & ~ E_STRICT);
|
||||
}
|
||||
|
||||
|
||||
@@ -400,6 +400,22 @@ class helper extends baseHelper
|
||||
$checkFunc = 'check' . $operator;
|
||||
return validater::$checkFunc($value1, $value2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 替换 Emoji 为指定字符串。
|
||||
* Replace Emoji to a specified string.
|
||||
*
|
||||
* @param string $subject
|
||||
* @param string $replace
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public static function replaceEmoji(string $subject, string $replace = '[Emoji]'): string
|
||||
{
|
||||
/* 匹配大部分常见 Emoji 范围(包括符号、旗帜、交通工具等)。 */
|
||||
$pattern = '/[\x{1F300}-\x{1F5FF}\x{1F600}-\x{1F64F}\x{1F680}-\x{1F6FF}\x{2600}-\x{26FF}\x{2700}-\x{27BF}\x{1F900}-\x{1F9FF}]/u';
|
||||
return preg_replace($pattern, $replace, $subject);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -215,6 +215,8 @@ class dao extends baseDAO
|
||||
|
||||
$module = $this->app->getModuleName();
|
||||
$method = $this->app->getMethodName();
|
||||
if($module == 'story' && $this->app->rawModule == 'requirement') $module = 'requirement';
|
||||
if($module == 'story' && $this->app->rawModule == 'epic') $module = 'epic';
|
||||
|
||||
$flowAction = $this->dbh->query("SELECT * FROM " . TABLE_WORKFLOWACTION . " WHERE `module` = '{$module}' AND `action` = '{$method}' AND `buildin` = '1' AND `extensionType` = 'extend' AND `vision` = '{$this->config->vision}'")->fetch(PDO::FETCH_OBJ);
|
||||
if(!$flowAction) return $this;
|
||||
|
||||
+11
-5
@@ -300,11 +300,19 @@ class dbh
|
||||
switch($this->config->driver)
|
||||
{
|
||||
case 'oceanbase':
|
||||
$sql = "CREATE DATABASE `{$this->config->name}`";
|
||||
return $this->rawQuery($sql);
|
||||
case 'mysql':
|
||||
$sql = "CREATE DATABASE `{$this->config->name}`";
|
||||
if($version > 4.1) $sql .= " DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci";
|
||||
if(version_compare($version, '5.6', '>='))
|
||||
{
|
||||
$sql .= " DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci";
|
||||
}
|
||||
elseif(version_compare($version, '4.1', '>='))
|
||||
{
|
||||
$sql .= " DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci";
|
||||
}
|
||||
return $this->rawQuery($sql);
|
||||
|
||||
case 'dm':
|
||||
/*
|
||||
$tableSpace = strtoupper($this->config->name);
|
||||
@@ -318,11 +326,9 @@ class dbh
|
||||
$this->rawQuery($createTableSpace);
|
||||
$this->rawQuery($createUser);
|
||||
}
|
||||
*/
|
||||
|
||||
*/
|
||||
$createSchema = "CREATE SCHEMA {$this->config->name} AUTHORIZATION {$this->config->user}";
|
||||
return $this->rawQuery($createSchema);
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -119,6 +119,10 @@ class form extends fixer
|
||||
if($moduleName == 'projectrelease') $moduleName = 'release';
|
||||
if($moduleName == 'projectbuild') $moduleName = 'build';
|
||||
|
||||
/* 用户需求和业务需求用自己的工作流。*/
|
||||
if($moduleName == 'story' && $app->rawModule == 'requirement') $moduleName = 'requirement';
|
||||
if($moduleName == 'story' && $app->rawModule == 'epic') $moduleName = 'epic';
|
||||
|
||||
$flow = $app->control->loadModel('workflow')->getByModule($moduleName);
|
||||
if(!$flow) return $configObject;
|
||||
|
||||
@@ -214,6 +218,9 @@ class form extends fixer
|
||||
if($app->rawMethod == 'requirement' && $module == 'story') $module = 'requirement';
|
||||
if($app->rawMethod == 'epic' && $module == 'story') $module = 'epic';
|
||||
|
||||
if($app->rawModule == 'requirement' && $module == 'story') $module = 'requirement';
|
||||
if($app->rawModule == 'epic' && $module == 'story') $module = 'epic';
|
||||
|
||||
if($method == 'batchcreate') $method = 'create';
|
||||
if($method == 'batchedit') $method = 'edit';
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ $PHPMAILER_LANG['from_failed'] = '发送地址错误:';
|
||||
$PHPMAILER_LANG['instantiate'] = '未知函数调用。';
|
||||
//$PHPMAILER_LANG['invalid_email'] = 'Not sending, email address is invalid: ';
|
||||
$PHPMAILER_LANG['mailer_not_supported'] = '发信客户端不被支持。';
|
||||
$PHPMAILER_LANG['provide_address'] = '收件人没有设置邮箱,请到后台-人员-用户编辑中添加上邮箱地址。';
|
||||
$PHPMAILER_LANG['provide_address'] = '收件人没有设置邮箱,请到后台-人员管理-用户-编辑中添加上邮箱地址。';
|
||||
$PHPMAILER_LANG['recipients_failed'] = 'SMTP 错误:收件人地址错误,或检查SMTP服务设置,是否允许发送该邮件地址:';
|
||||
//$PHPMAILER_LANG['signing'] = 'Signing Error: ';
|
||||
//$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() failed.';
|
||||
|
||||
@@ -17,7 +17,7 @@ $PHPMAILER_LANG['from_failed'] = '發送地址錯誤:';
|
||||
$PHPMAILER_LANG['instantiate'] = '未知函數調用。';
|
||||
//$PHPMAILER_LANG['invalid_email'] = 'Not sending, email address is invalid: ';
|
||||
$PHPMAILER_LANG['mailer_not_supported'] = '發信客戶端不被支持。';
|
||||
$PHPMAILER_LANG['provide_address'] = '收件人沒有設置郵箱,請到後台-人員-用戶編輯中添加上郵箱地址。';
|
||||
$PHPMAILER_LANG['provide_address'] = '收件人沒有設置郵箱,請到後台-人員管理-用戶-編輯中添加上郵箱地址。';
|
||||
$PHPMAILER_LANG['recipients_failed'] = 'SMTP 錯誤:收件人地址錯誤,或檢查SMTP服務設置,是否允許發送該郵件地址:';
|
||||
//$PHPMAILER_LANG['signing'] = 'Signing Error: ';
|
||||
//$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() failed.';
|
||||
|
||||
@@ -164,7 +164,7 @@ class giteaRepo
|
||||
for($page = $pageID; true; $page ++)
|
||||
{
|
||||
$params['page'] = $page;
|
||||
$branchList = json_decode(commonModel::http($url . '?' . http_build_query($params)));
|
||||
$branchList = json_decode(commonModel::http($url . '&' . http_build_query($params)));
|
||||
if(empty($branchList) || !is_array($branchList)) break;
|
||||
|
||||
foreach($branchList as $branch)
|
||||
|
||||
@@ -164,7 +164,7 @@ class gogsRepo
|
||||
for($page = $pageID; true; $page ++)
|
||||
{
|
||||
$params['page'] = $page;
|
||||
$branchList = json_decode(commonModel::http($url . '?' . http_build_query($params)));
|
||||
$branchList = json_decode(commonModel::http($url . '&' . http_build_query($params)));
|
||||
if(empty($branchList) || !is_array($branchList)) break;
|
||||
|
||||
foreach($branchList as $branch)
|
||||
|
||||
@@ -173,8 +173,9 @@ class command
|
||||
return $list;
|
||||
}
|
||||
|
||||
public static function first(array $nodes, string|array|object $selectors)
|
||||
public static function first(array $nodes, string|array|object|null $selectors = null)
|
||||
{
|
||||
if($selectors === null) return reset($nodes);
|
||||
foreach($nodes as $node)
|
||||
{
|
||||
$result = $node->findFirst($selectors);
|
||||
@@ -183,8 +184,9 @@ class command
|
||||
return array();
|
||||
}
|
||||
|
||||
public static function last(array $nodes, string|array|object $selectors)
|
||||
public static function last(array $nodes, string|array|object|null $selectors = null)
|
||||
{
|
||||
if($selectors === null) return end($nodes);
|
||||
foreach($nodes as $node)
|
||||
{
|
||||
$result = $node->findLast($selectors);
|
||||
|
||||
@@ -176,13 +176,13 @@ class node implements \JsonSerializable
|
||||
public function findFirst(string|array|object $selectors): ?node
|
||||
{
|
||||
$results = $this->find($selectors, true);
|
||||
return empty($results) ? null : $results[0];
|
||||
return empty($results) ? null : reset($results);
|
||||
}
|
||||
|
||||
public function findLast(string|array|object $selectors): ?node
|
||||
{
|
||||
$results = $this->find($selectors, true, true);
|
||||
return empty($results) ? null : $results[0];
|
||||
return empty($results) ? null : end($results);
|
||||
}
|
||||
|
||||
public function prop(array|string $name, mixed $defaultValue = null): mixed
|
||||
|
||||
@@ -46,7 +46,7 @@ class backBtn extends btn
|
||||
'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,execution-calendar,my-effort,company-effort,company-calendar,product-dynamic,project-dynamic,execution-dynamic,project-view,execution-view,task-view',
|
||||
'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,my-effort,company-effort,company-calendar,demandpool-track,product-dynamic,project-dynamic,execution-dynamic,project-view,execution-view,ticket-browse,ticket-view,roadmap-view,charter-view,demand-view,review-assess,review-audit',
|
||||
'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,my-effort,company-effort,company-calendar,product-dynamic,project-dynamic,execution-dynamic,project-view,execution-view,ticket-browse,ticket-view',
|
||||
'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,my-effort,company-effort,company-calendar,product-dynamic,project-dynamic,execution-dynamic,project-view,execution-view,testreport-view',
|
||||
'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,my-effort,company-effort,company-calendar,product-dynamic,project-dynamic,execution-dynamic,project-view,execution-view,testreport-view,bug-browse,project-bug,bug-view',
|
||||
'testsuite' => 'testsuite-browse,testsuite-view,testtask-cases,my-index,product-dynamic,project-dynamic,execution-dynamic,project-view,execution-view',
|
||||
'testtask' => 'testtask-browse,testtask-cases,qa-index,testcase-browse,execution-build,my-index,my-effort,company-effort,company-calendar,product-dynamic,project-dynamic,execution-dynamic,project-view,execution-view',
|
||||
'testreport' => 'testreport-browse,project-testreport,execution-testreport,execution-testtask',
|
||||
|
||||
@@ -144,7 +144,7 @@ class bugBasicInfo extends wg
|
||||
|
||||
$items[$lang->bug->deadline] = array
|
||||
(
|
||||
'control' => 'span',
|
||||
'control' => 'html',
|
||||
'content' => html(formatTime($bug->deadline) . (isset($bug->delay) ? sprintf($lang->bug->notice->delayWarning, $bug->delay) : ''))
|
||||
);
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ class datalist extends wg
|
||||
div
|
||||
(
|
||||
setClass('datalist-item-label', $labelClass),
|
||||
set::title($label),
|
||||
$label
|
||||
),
|
||||
div
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
.doc-app-header > .nav {--nav-active-bg: var(--color-primary-100); --nav-active-color: var(--color-fore); --nav-item-height: 24px; --nav-divider-margin: 8px;}
|
||||
.doc-space-card-libs {min-height: 118px;}
|
||||
.doc-app-header > .doc-app-nav > .row{min-width: 350px;}
|
||||
|
||||
.icon-api {text-align: center; --api-color-rgb: var(--color-success-500-rgb); color: rgba(var(--api-color-rgb), 1); margin: 0 3px;}
|
||||
.icon-api::before {content: ' '; display: inline-block; width: 8px; height: 8px; border-radius: 2px; background-color: currentColor; min-width: 0;}
|
||||
|
||||
+167
-62
@@ -333,16 +333,15 @@ function handleSwitchView(mode, location, info)
|
||||
}
|
||||
}
|
||||
|
||||
function showDocBasicModal(docID, isDraft, withTitle = false)
|
||||
function showDocBasicModal(parentID, docID, isDraft, modalType = 'doc')
|
||||
{
|
||||
const docApp = getDocApp();
|
||||
const spaceType = docApp.spaceType;
|
||||
const spaceID = docApp.spaceID;
|
||||
const libID = docApp.libID;
|
||||
const moduleID = docApp.moduleID;
|
||||
const url = $.createLink('doc', 'setDocBasic', `objectType=${spaceType}&objectID=${spaceID}&libID=${libID}&moduleID=${moduleID}&docID=${docID || 0}&isDraft=${isDraft ? 'yes' : 'no'}&withTitle=${withTitle ? 'yes' : 'no'}`);
|
||||
const url = $.createLink('doc', 'setDocBasic', `objectType=${spaceType}&objectID=${spaceID}&libID=${libID}&moduleID=${moduleID}&parentID=${parentID || 0}&docID=${docID || 0}&isDraft=${isDraft ? 'yes' : 'no'}&modalType=${modalType}`);
|
||||
|
||||
console.log('showDocBasicModal', {docBasicModal: {...docBasicModal}, docID});
|
||||
if(docBasicModal.doc === docID && docBasicModal.current)
|
||||
{
|
||||
docBasicModal.current.show();
|
||||
@@ -364,7 +363,7 @@ function showDocBasicModal(docID, isDraft, withTitle = false)
|
||||
}
|
||||
},
|
||||
onHidden: () => {
|
||||
if(!docBasicModal.keepOnHide) docBasicModal.current.destroy();
|
||||
if(!docBasicModal.keepOnHide && docBasicModal.current) docBasicModal.current.destroy();
|
||||
},
|
||||
$onDestroy: () => {
|
||||
if(docBasicModal.current) docBasicModal = {};
|
||||
@@ -447,9 +446,10 @@ function submitNewDoc(doc, spaceID, libID, moduleID, formData, afterCreate)
|
||||
content : removeEmoji(doc.html),
|
||||
status : doc.status || 'normal',
|
||||
contentType: doc.contentType,
|
||||
type : 'text',
|
||||
type : doc.type || 'text',
|
||||
lib : libID,
|
||||
module : moduleID,
|
||||
parent : doc.parent,
|
||||
title : doc.title,
|
||||
keywords : doc.keywords,
|
||||
acl : 'private',
|
||||
@@ -487,9 +487,12 @@ function submitNewDoc(doc, spaceID, libID, moduleID, formData, afterCreate)
|
||||
newDoc.space = `${spaceType}.${docData[spaceType]}`;
|
||||
}
|
||||
resolve(newDoc);
|
||||
docApp.load(null, null, null, {noLoading: true, picks: 'doc'}).then(() => {
|
||||
afterCreate?.(newDoc);
|
||||
});
|
||||
if(afterCreate)
|
||||
{
|
||||
docApp.load(null, null, null, {noLoading: true, picks: 'doc'}).then(() => {
|
||||
afterCreate(newDoc);
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
@@ -506,13 +509,51 @@ function submitNewDoc(doc, spaceID, libID, moduleID, formData, afterCreate)
|
||||
});
|
||||
}
|
||||
|
||||
function submitEditDoc(doc, formData)
|
||||
{
|
||||
const url = $.createLink('doc', 'edit', `docID=${doc.id}`);
|
||||
const docApp = getDocApp();
|
||||
const docData = {status: 'normal', type: 'chapter'};
|
||||
|
||||
if(formData) mergeDocFormData(docData, formData);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
$.post(url, docData, (res) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
const data = JSON.parse(res);
|
||||
if(!checkResponse(data)) return;
|
||||
if(typeof data !== 'object' || data.result === 'fail')
|
||||
{
|
||||
let message = data.message || data.error || getLang('errorOccurred');
|
||||
if(typeof message === 'object') message = Object.values(message).map(x => Array.isArray(x) ? x.join('\n') : x).join('\n');
|
||||
throw new Error(message);
|
||||
}
|
||||
resolve(doc);
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
resolve(false);
|
||||
showSaveFailedAlert(error);
|
||||
if(!error.message) return;
|
||||
}
|
||||
}).fail(error => {
|
||||
resolve(false);
|
||||
showSaveFailedAlert(error);
|
||||
}).complete(() => {
|
||||
docApp.load(null, null, null, {noLoading: true, picks: 'doc'});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理创建文档的操作请求,向服务器发送请求并返回创建的文档对象。
|
||||
* Handle the create doc operation request, send a request to the server and return the created doc object.
|
||||
*/
|
||||
function handleCreateDoc(doc, spaceID, libID, moduleID)
|
||||
{
|
||||
return showDocBasicModal(0, doc.status === 'draft').then((formData) => {
|
||||
return showDocBasicModal(0, 0, doc.status === 'draft').then((formData) => {
|
||||
return submitNewDoc(doc, spaceID, libID, moduleID, formData);
|
||||
});
|
||||
}
|
||||
@@ -540,6 +581,7 @@ function handleSaveDoc(doc)
|
||||
type : 'text',
|
||||
lib : libID,
|
||||
module : moduleID,
|
||||
parent : doc.parent,
|
||||
title : doc.title,
|
||||
keywords : doc.keywords,
|
||||
acl : doc.acl,
|
||||
@@ -593,6 +635,22 @@ function handleSaveDoc(doc)
|
||||
}).complete(() => {
|
||||
docApp.isSavingDoc = false;
|
||||
$(docApp.element).find('[zui-command^="saveDoc"],[zui-command^="saveNewDoc"]').removeAttr('disabled');
|
||||
const {id, acl, users, addedBy} = doc;
|
||||
if(acl == 'private' && !users.includes(currentUser) && addedBy !== currentUser)
|
||||
{
|
||||
if(docApp.hasEditingDoc)
|
||||
{
|
||||
docApp.cancelEditDoc().then(() => {
|
||||
docApp.delete('doc', id);
|
||||
docApp.load(null, null, null, {noLoading: true, picks: 'doc'});
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
docApp.delete('doc', id);
|
||||
docApp.load(null, null, null, {noLoading: true, picks: 'doc'});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -703,6 +761,10 @@ function importFileToEditor(type, options)
|
||||
});
|
||||
}
|
||||
|
||||
function isOpenVersion(version) {
|
||||
return /^\d/.test(version || $('#zuiCSS').attr('href').split('?v=').pop());
|
||||
}
|
||||
|
||||
/**
|
||||
* 定义文档各个试图和 UI 元素的上的操作方法。
|
||||
* Define the operation methods on the views and UI elements of the doc.
|
||||
@@ -768,15 +830,15 @@ const actionsMap =
|
||||
const moreItems =
|
||||
[
|
||||
hasPriv('addChapter') ? {icon: 'plus', text: lang.addSubChapter, command: `addChapter/${doc.id}`} : null,
|
||||
hasPriv('addChapter') ? {icon: 'plus', text: lang.addSameChapter, command: `addChapter/${doc.parent}`} : null,
|
||||
hasPriv('create') ? {icon: 'plus', text: lang.addSubDoc, command: `addDoc/${doc.id}`} : null,
|
||||
hasPriv('addChapter') && doc.parent != 0 ? {icon: 'plus', text: lang.addSameChapter, command: `addChapter/${doc.parent}`} : null,
|
||||
hasPriv('create') ? {icon: 'plus', text: lang.addSubDoc, command: `startCreateDoc/${doc.id}`} : null,
|
||||
!isChapter && canMoveDoc(doc) ? {icon: 'folder-move', text: lang.moveDoc, command: `moveDoc/${doc.id}`} : null,
|
||||
!isChapter && hasPriv('delete') ? {icon: 'trash', text: lang.delete, command: `deleteDoc/${doc.id}`} : null,
|
||||
isChapter && hasPriv('editChapter') ? {icon: 'pencil', text: lang.editChapter, command: `editChapter/${doc.id}`} : null,
|
||||
isChapter && hasPriv('deleteChapter') ? {icon: 'trash', text: lang.deleteChapter, command: `deleteChapter/${doc.id}`} : null,
|
||||
isChapter && hasPriv('deleteChapter') ? {icon: 'trash', text: lang.deleteChapter, command: `deleteDoc/${doc.id}`} : null,
|
||||
];
|
||||
return [
|
||||
hasPriv('sortDoc') ? {icon: 'move cursor-move', className: 'sort-handler', hint: lang.sortDoc, size: 'xs'} : null,
|
||||
(!isChapter && hasPriv('sortDoc')) || (isChapter && hasPriv('sortChapter')) ? {icon: 'move cursor-move', className: 'sort-handler', hint: lang.sortDoc, size: 'xs'} : null,
|
||||
moreItems.length ? {icon: 'ellipsis-v', caret: false, placement: 'bottom-end', size: 'xs', items: moreItems} : null,
|
||||
];
|
||||
}
|
||||
@@ -784,7 +846,6 @@ const actionsMap =
|
||||
const moreItems = [];
|
||||
if(canModify && canMoveDoc(doc)) moreItems.push({icon: 'folder-move', text: lang.moveDoc, command: `moveDoc/${doc.id}`});
|
||||
if(canModify && hasPriv('delete')) moreItems.push({icon: 'trash', text: lang.delete, command: `deleteDoc/${doc.id}`});
|
||||
if(canEditDoc && ['html', 'attachment', 'markdown'].includes(doc.contentType)) moreItems.push({icon: 'edit', text: lang.convertToNewDoc, command: 'startConvertDoc'});
|
||||
if(!isApi && canModify && hasPriv('effort')) moreItems.push({icon: 'time', text: lang.effort, command: `effortDoc/${doc.id}`});
|
||||
if(!isApi && !isTemplate)
|
||||
{
|
||||
@@ -807,7 +868,7 @@ const actionsMap =
|
||||
}
|
||||
}
|
||||
const docApp = this;
|
||||
const isOpen = /^\d/.test(config.version || $('#zuiCSS').attr('href').split('?v=').pop());
|
||||
const isOpen = isOpenVersion(config.version);
|
||||
if(!isOpen && docApp && docApp.mode === 'view' && doc.status != 'draft' && !doc.api && !isTemplate)
|
||||
{
|
||||
moreItems.push({icon: 'link', text: getLang('relateObject'), className: 'relateObject-btn', command: 'toggleViewSideTab/relateObject'})
|
||||
@@ -926,6 +987,7 @@ const actionsMap =
|
||||
const lang = getLang();
|
||||
const canModify = canModifySpace();
|
||||
const libID = docApp.libID;
|
||||
const docID = docApp.docID;
|
||||
|
||||
if(isApiLib(docApp.lib))
|
||||
{
|
||||
@@ -962,12 +1024,12 @@ const actionsMap =
|
||||
/* 文档列表没有文档时的按钮。Actions for empty doc list. */
|
||||
if(info.ui === 'doc-list-empty')
|
||||
{
|
||||
return canCreateDoc ? [{icon: 'plus', text: lang.createDoc, btnType: 'primary', command: 'startCreateDoc'}] : null;
|
||||
return canCreateDoc ? [{icon: 'plus', text: lang.createDoc, btnType: 'primary', command: `startCreateDoc/${docID}`}] : null;
|
||||
}
|
||||
|
||||
const items = canModify ?
|
||||
[
|
||||
canCreateDoc ? {icon: 'plus', text: lang.createDoc, command: 'startCreateDoc'} : null,
|
||||
canCreateDoc ? {icon: 'plus', text: lang.createDoc, command: `startCreateDoc/${docID}`} : null,
|
||||
canCreateDoc ? {type: 'divider'} : null,
|
||||
canCreateOffice ? {icon: 'file-word', text: lang.createList.word, command: 'startCreateOffice/word'} : null,
|
||||
canCreateOffice ? {icon: 'file-powerpoint', text: lang.createList.ppt, command: 'startCreateOffice/ppt'} : null,
|
||||
@@ -1004,7 +1066,7 @@ const actionsMap =
|
||||
}
|
||||
|
||||
const moreItems = [];
|
||||
if((doc.contentType === 'doc' || doc.contentType === 'markdown') && !isApiDoc(doc))
|
||||
if(isOpenVersion() && (doc.contentType === 'doc' || doc.contentType === 'markdown') && !isApiDoc(doc))
|
||||
{
|
||||
if(config.debug > 5) moreItems.push({text: lang.importConfluence, command: 'importFile/confluence'});
|
||||
moreItems.push(
|
||||
@@ -1105,17 +1167,26 @@ function getActions(type, info)
|
||||
const commands =
|
||||
{
|
||||
/* 创建文档。 Create doc. */
|
||||
startCreateDoc: function()
|
||||
startCreateDoc: function(_, args)
|
||||
{
|
||||
const docApp = getDocApp();
|
||||
if(!docApp.libList.length) return zui.Modal.alert(getLang('createLibFirst'));
|
||||
|
||||
const {spaceType} = docApp;
|
||||
if(spaceType === 'api') return docApp.startCreateDoc();
|
||||
|
||||
const {spaceID, libID, moduleID} = docApp;
|
||||
return showDocBasicModal(0, true, true).then((formData) => {
|
||||
const doc = {contentType: 'doc', status: 'draft'};
|
||||
return submitNewDoc(doc, spaceID, libID, moduleID, formData, (newDoc) => {
|
||||
docApp.startEditDoc(newDoc.id);
|
||||
return showDocBasicModal(args?.[0] ?? 0, 0, true, 'doc').then((formData) => {
|
||||
zui.Editor.loadModule().then(() => {
|
||||
const emptyDoc = zui.Editor.createEmptyDoc();
|
||||
const snapshot = zui.Editor.toSnapshot(emptyDoc);
|
||||
const doc = {contentType: 'doc', status: 'draft', content: JSON.stringify(snapshot)};
|
||||
return submitNewDoc(doc, spaceID, libID, moduleID, formData, (newDoc) => {
|
||||
docApp.selectDoc(newDoc.id);
|
||||
docApp.startEditDoc(newDoc.id);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
},
|
||||
startEditDoc: function(_, [doc], options)
|
||||
@@ -1271,12 +1342,13 @@ const commands =
|
||||
},
|
||||
deleteModule: function(_, args)
|
||||
{
|
||||
const docApp = getDocApp();
|
||||
const moduleID = args[0] || docApp.moduleID;
|
||||
const docApp = getDocApp();
|
||||
const moduleID = args[0] || docApp.moduleID;
|
||||
const moduleInfo = docApp.treeMap.modules.get(+moduleID);
|
||||
|
||||
$.ajaxSubmit(
|
||||
{
|
||||
confirm: getLang('confirmDeleteModule'),
|
||||
confirm: getLang(moduleInfo.modules.length > 0 || moduleInfo.docs.length > 0 ? 'confirmDeleteModuleWithSub' : 'confirmDeleteModule'),
|
||||
url: $.createLink('doc', 'deleteCatalog', `moduleID=${moduleID}`),
|
||||
load: false,
|
||||
onSuccess: function(res)
|
||||
@@ -1323,48 +1395,27 @@ const commands =
|
||||
},
|
||||
addChapter: function(_, args)
|
||||
{
|
||||
zui.Modal.prompt({message: getLang('chapterName')}).then(newChapterName =>
|
||||
{
|
||||
if(!newChapterName) return;
|
||||
|
||||
const docApp = getDocApp();
|
||||
const {spaceType, spaceID, libID, moduleID} = docApp;
|
||||
const url = $.createLink('doc', 'create', `objectType=${spaceType}&objectID=${Math.max(spaceID, 0)}&libID=${libID}&moduleID=${moduleID}`);
|
||||
const docData =
|
||||
{
|
||||
status : 'normal',
|
||||
type : 'chapter',
|
||||
lib : libID,
|
||||
module : moduleID,
|
||||
title : newChapterName,
|
||||
acl : 'open',
|
||||
space : spaceType,
|
||||
parent : args[0] || 0,
|
||||
};
|
||||
|
||||
$.post(url, docData, (res) =>
|
||||
{
|
||||
try
|
||||
const docApp = getDocApp();
|
||||
const {spaceID, libID, moduleID} = docApp;
|
||||
return showDocBasicModal(args[0], 0, false, 'chapter').then((formData) => {
|
||||
const doc = {type: 'chapter', status: 'normal', parent: args[0]};
|
||||
return submitNewDoc(doc, spaceID, libID, moduleID, formData).then(() => {
|
||||
docApp.load(null, null, null, {noLoading: false, picks: 'doc'});
|
||||
if(docBasicModal.current)
|
||||
{
|
||||
const data = JSON.parse(res);
|
||||
if(!checkResponse(data)) return;
|
||||
if(typeof data !== 'object' || data.result === 'fail') throw new Error(getErrorMessage(data));
|
||||
docBasicModal.current.destroy();
|
||||
docBasicModal = {};
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
showSaveFailedAlert(error);
|
||||
}
|
||||
}).fail(error => {
|
||||
showSaveFailedAlert(error);
|
||||
}).complete(() => {
|
||||
docApp.load(null, null, null, {noLoading: true, picks: 'doc'});
|
||||
});
|
||||
});
|
||||
},
|
||||
editChapter: function(_, args)
|
||||
{
|
||||
const docApp = getDocApp();
|
||||
const {spaceID, libID, moduleID} = docApp;
|
||||
return showDocBasicModal(args[0], false, true)
|
||||
return showDocBasicModal(0, args[0], false, 'chapter').then((formData) => {
|
||||
return submitEditDoc({id: args[0]}, formData);
|
||||
});
|
||||
},
|
||||
moveDoc: function(_, args)
|
||||
{
|
||||
@@ -1409,9 +1460,13 @@ const commands =
|
||||
const docApp = getDocApp();
|
||||
const docID = args[0] || docApp.docID;
|
||||
const isApi = isApiDoc(docID);
|
||||
const docInfo = docApp.treeMap.docs.get(docID)
|
||||
const doc = docInfo.data;
|
||||
|
||||
const isChapter = doc?.type === 'chapter';
|
||||
$.ajaxSubmit(
|
||||
{
|
||||
confirm: getLang('confirmDelete'),
|
||||
confirm: getLang(isChapter ? (docInfo.docs.length > 0 ? 'confirmDeleteChapterWithSub' : 'confirmDeleteChapter') : (docInfo.docs.length > 0 ? 'confirmDeleteWithSub' : 'confirmDelete')),
|
||||
url: $.createLink(isApi ? 'api' : 'doc', 'delete', `${isApi ? 'apiID' : 'docID'}=${isApi ? getApiID(docID) : docID}`),
|
||||
load: false,
|
||||
onSuccess: function()
|
||||
@@ -1471,7 +1526,7 @@ const commands =
|
||||
const docID = args[0] || doc.id;
|
||||
const docType = args[1] || doc.contentType;
|
||||
const saveEdited = args[2] || 0;
|
||||
showDocBasicModal(docID).then(formData => {
|
||||
showDocBasicModal(0, docID).then(formData => {
|
||||
savingDocData[docID] = formData;
|
||||
if(saveEdited == 1)
|
||||
{
|
||||
@@ -2108,4 +2163,54 @@ window.getFileActions = function(file)
|
||||
|
||||
fileActions.push({icon: 'download', title: downloadLang, url: downloadLink.replace('{id}', file.id).replace('\\', ''), className: 'text-primary', target: '_blank'});
|
||||
return fileActions;
|
||||
};
|
||||
|
||||
window.useHocuspocus = function(app)
|
||||
{
|
||||
if(vision !== 'rnd') return false;
|
||||
return enableHocuspocus && app.spaceType !== 'mine';
|
||||
};
|
||||
|
||||
let lastDisconnectMessager = null;
|
||||
|
||||
window.onHocuspocusConnect = function(app)
|
||||
{
|
||||
if(app.spaceType === 'mine') return;
|
||||
|
||||
lastDisconnectMessager && lastDisconnectMessager.hide();
|
||||
lastDisconnectMessager = null;
|
||||
zui.Messager.show({
|
||||
content: hocuspocusConnect,
|
||||
type: 'success',
|
||||
time: 3000,
|
||||
});
|
||||
};
|
||||
|
||||
window.onHocuspocusDisconnect = function(app)
|
||||
{
|
||||
if(app.spaceType === 'mine') return;
|
||||
|
||||
lastDisconnectMessager && lastDisconnectMessager.hide();
|
||||
lastDisconnectMessager = zui.Messager.show({
|
||||
content: hocuspocusDisconnect,
|
||||
type: 'danger',
|
||||
time: 0,
|
||||
});
|
||||
};
|
||||
|
||||
window.onAuthenticationFailed = function(app)
|
||||
{
|
||||
if(app.spaceType === 'mine') return;
|
||||
|
||||
zui.Messager.show({
|
||||
content: needReadable,
|
||||
type: 'danger',
|
||||
time: 3000,
|
||||
});
|
||||
};
|
||||
|
||||
window.onDocAppModeChange = function()
|
||||
{
|
||||
lastDisconnectMessager && lastDisconnectMessager.hide();
|
||||
lastDisconnectMessager = null;
|
||||
}
|
||||
|
||||
@@ -129,6 +129,12 @@ class docApp extends wg
|
||||
{
|
||||
global $app, $lang, $config;
|
||||
|
||||
jsVar('hocuspocusConnect', $lang->doc->hocuspocusConnect);
|
||||
jsVar('hocuspocusDisconnect', $lang->doc->hocuspocusDisconnect);
|
||||
jsVar('enableHocuspocus', $config->docHocuspocus->enable);
|
||||
jsVar('needReadable', $lang->doc->needReadable);
|
||||
jsVar('vision', $config->vision);
|
||||
|
||||
/**
|
||||
* 定义文档应用接口链接。
|
||||
* Define the fetcher links for doc app.
|
||||
@@ -138,7 +144,7 @@ class docApp extends wg
|
||||
$filesFetcher = createLink('doc', 'ajaxGetFiles', 'type={objectType}&objectID={objectID}');
|
||||
$libSummariesFetcher = createLink('doc', 'ajaxGetLibSummaries', 'spaceType={spaceType}&spaceList={spaceList}');
|
||||
$uploadUrl = createLink('file', 'ajaxUpload', 'uid={uid}&objectType={objectType}&objectID={objectID}&extra={extra}&field={field}&api={api}&gid={gid}');
|
||||
$downloadUrl = createLink('file', 'ajaxQuery', 'fileID={gid}');
|
||||
$downloadUrl = createLink('file', 'ajaxQuery', 'fileID={gid}&objectType={objectType}&objectID={objectID}&title={title}&extra={extra}');
|
||||
|
||||
/**
|
||||
* 定义文档界面上的文件下载链接。
|
||||
@@ -275,9 +281,13 @@ class docApp extends wg
|
||||
set::_style(array('height' => 'calc(100vh - 72px)')),
|
||||
set::_id('docApp'),
|
||||
set::token(session_id()),
|
||||
set::awarenessUser(array('id' => $app->user->id, 'name' => $app->user->realname)),
|
||||
set::useHocuspocus($config->docHocuspocus->enable),
|
||||
set::awarenessUser(array('id' => $app->user->id, 'account' => $app->user->account, 'name' => $app->user->realname, 'avatar' => $app->user->avatar)),
|
||||
set::useHocuspocus(jsRaw('window.useHocuspocus')),
|
||||
set::hocuspocusUrls(array($config->docHocuspocus->http, $config->docHocuspocus->websocket)),
|
||||
set::onHocuspocusConnect(jsRaw('window.onHocuspocusConnect')),
|
||||
set::onHocuspocusDisconnect(jsRaw('window.onHocuspocusDisconnect')),
|
||||
set::onAuthenticationFailed(jsRaw('window.onAuthenticationFailed')),
|
||||
set::onModeChange(jsRaw('window.onDocAppModeChange')),
|
||||
set::spaceType($spaceType),
|
||||
set::spaceID(data('spaceID')),
|
||||
set::libID(data('libID')),
|
||||
|
||||
@@ -214,7 +214,10 @@ class dtable extends wg
|
||||
foreach($modifier as $subModifier)
|
||||
{
|
||||
if(!is_callable($subModifier)) continue;
|
||||
if($subModifier == 'strip_tags' && empty(zget($item, $key, ''))) continue;
|
||||
|
||||
if($subModifier instanceof \Closure) $subModifier = $subModifier->bindTo($item);
|
||||
|
||||
if(is_object($item)) $item->$key = $subModifier($item->$key);
|
||||
else $item[$key] = $subModifier($item[$key]);
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ class fileSelector extends wg
|
||||
'fileIcons?: string|array= "paper-clip"', // 文件图标。
|
||||
'uploadBtn?: string|array', // 上传按钮。
|
||||
'renameBtn?: bool|string|array|callback=true', // 重命名按钮。
|
||||
'removeBtn?: bool|string|array|callback=true', // 删除按钮。
|
||||
'removeBtn?: bool|string|array|callback', // 删除按钮。
|
||||
'removeConfirm?: string|array', // 删除确认提示。
|
||||
'maxFileSize?: int|string', // 限制文件大小。
|
||||
'maxFileCount?: int=0', // 限制文件数目,如果设置为非大于 `0` 的数则不限制。
|
||||
@@ -53,6 +53,7 @@ class fileSelector extends wg
|
||||
|
||||
protected function created()
|
||||
{
|
||||
if(!$this->hasProp('removeBtn')) $this->setProp('removeBtn', common::hasPriv('file', 'delete'));
|
||||
if(!$this->hasProp('totalFileSize'))
|
||||
{
|
||||
$maxFileSize = ini_get('post_max_size');
|
||||
@@ -115,7 +116,7 @@ class fileSelector extends wg
|
||||
->const('acceptFileTypes', $acceptFileTypes)
|
||||
->do(<<<'JS'
|
||||
const typeIndex = file.name.lastIndexOf(".");
|
||||
const fileType = file.name.slice(typeIndex + 1);
|
||||
const fileType = "," + file.name.slice(typeIndex + 1) + ",";
|
||||
if(acceptFileTypes)
|
||||
{
|
||||
if(acceptFileTypes.indexOf(fileType) == -1)
|
||||
|
||||
@@ -259,6 +259,7 @@ class formPanel extends panel
|
||||
set::value($field->value),
|
||||
set::placeholder($field->placeholder)
|
||||
);
|
||||
if((bool)$field->readonly) $extraMain[] = formHidden($field->field, $field->value);
|
||||
}
|
||||
return $extraMain;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.history-panel-action .avatar {width: 21px !important; height: 21px !important;}
|
||||
.history-panel-body .listitem {padding: 0.4rem;}
|
||||
.history-panel-action > .item-inner {padding-left: 1.5rem;}
|
||||
@@ -25,6 +25,11 @@ class history extends wg
|
||||
'commentBtn?: string|array' // 是否允许添加备注。
|
||||
);
|
||||
|
||||
public static function getPageCSS(): ?string
|
||||
{
|
||||
return file_get_contents(__DIR__ . DS . 'css' . DS . 'v1.css');
|
||||
}
|
||||
|
||||
public static function getPageJS(): ?string
|
||||
{
|
||||
return file_get_contents(__DIR__ . DS . 'js' . DS . 'v1.js');
|
||||
|
||||
@@ -147,7 +147,7 @@ class moduleMenu extends wg
|
||||
$lang = $app->loadLang('datatable')->datatable;
|
||||
|
||||
$items = array();
|
||||
if($settingLink)
|
||||
if($settingLink && common::hasPriv('tree', 'browse'))
|
||||
{
|
||||
$tab = $this->prop('settingApp', $app->tab);
|
||||
$settingText = $this->prop('settingText', $lang->moduleSetting);
|
||||
|
||||
@@ -40,7 +40,7 @@ class modulePicker extends wg
|
||||
setID('moduleBox'),
|
||||
$this->prop('label', null),
|
||||
picker(set($this->props->pick(array('id', 'name', 'value', 'required', 'items')))),
|
||||
$items || empty($this->prop('manageLink'))? null : span
|
||||
$items || empty($this->prop('manageLink')) || !common::hasPriv('tree', 'browse') ? null : span
|
||||
(
|
||||
setClass('input-group-btn'),
|
||||
a
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
.monaco-editor .wave-decoration {
|
||||
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 4'%3E%3Cpath fill='none' stroke='%23FF0000' d='M0 3.5c5 0 5-3 10-3s5 3 10 3 5-3 10-3 5 3 10 3'/%3E%3C/svg%3E") repeat-x 0 100%;
|
||||
background-size: 8px 4px;
|
||||
background-color: white;
|
||||
animation: waveMove 1s infinite linear;
|
||||
}
|
||||
|
||||
@keyframes waveMove {
|
||||
from { background-position: 0 100%; }
|
||||
to { background-position: -20px 100%; }
|
||||
}
|
||||
|
||||
.monaco-editor .yellow-decoration {
|
||||
background-color: rgba(255, 255, 0, 0.2); /* 半透明黄色 */
|
||||
}
|
||||
+89
-11
@@ -1,7 +1,10 @@
|
||||
setTimeout(function()
|
||||
{
|
||||
createMonaco(id, action, options, diffContent, onMouseDown, onMouseMove, vsPath, clientLang);
|
||||
resize(id);
|
||||
$.getLib(config.webRoot + 'js/monaco-editor/min/vs/loader.js', {root: false}, function()
|
||||
{
|
||||
createMonaco(id, action, options, diffContent, onMouseDown, onMouseMove, vsPath, clientLang);
|
||||
resize(id);
|
||||
});
|
||||
}, 200);
|
||||
|
||||
function createMonaco(id, action, options, diffContent, onMouseDown, onMouseMove, vsPath, clientLang)
|
||||
@@ -16,18 +19,33 @@ function createMonaco(id, action, options, diffContent, onMouseDown, onMouseMove
|
||||
}
|
||||
});
|
||||
|
||||
let decorations = [];
|
||||
let programmaticSelectionRange = null;
|
||||
|
||||
require(['vs/editor/editor.main'], function ()
|
||||
{
|
||||
if(!lineMap && action == 'diff' && diffContent && diffContent.line) lineMap = diffContent.line.new;
|
||||
if(lineMap) options.lineNumbers = (line) => {return lineMap[line - 1];};
|
||||
|
||||
$.extend(options, {
|
||||
// 关键配置项
|
||||
automaticLayout: true, // 自动调整布局
|
||||
scrollBeyondLastLine: false, // 禁用底部滚动区域
|
||||
minimap: {enabled: false}, // 可选:禁用缩略图以节省空间
|
||||
|
||||
// 编辑器尺寸配置
|
||||
fontSize: 14,
|
||||
lineHeight: 24,
|
||||
lineNumbersMinChars: 2,
|
||||
|
||||
// 内边距设置
|
||||
padding: {top: 8, bottom: 8}
|
||||
});
|
||||
|
||||
if(action == 'diff')
|
||||
{
|
||||
options.renderSideBySide = $.cookie.get('renderSideBySide') == 'true';
|
||||
|
||||
options.lineNumbers = function(number)
|
||||
{
|
||||
const newlc = diffContent.line.new;
|
||||
return newlc[number - 1];
|
||||
};
|
||||
|
||||
modifiedEditor = monaco.editor.createDiffEditor(document.getElementById(id), options);
|
||||
window.modifiedEditor = modifiedEditor;
|
||||
|
||||
@@ -60,9 +78,69 @@ function createMonaco(id, action, options, diffContent, onMouseDown, onMouseMove
|
||||
if(selectedLines)
|
||||
{
|
||||
const lines = selectedLines.split(',');
|
||||
const startLine = parseInt(lines[0]);
|
||||
const endLine = parseInt(lines[1] || startLine + 1);
|
||||
editor.setSelection(new monaco.Range(startLine, 1, endLine + 1, 1))
|
||||
let startLine = parseInt(lines[0]);
|
||||
let endLine = parseInt(lines[1] || startLine + 1);
|
||||
let startCol = 1;
|
||||
let endCol = 0;
|
||||
if(lines.length == 4)
|
||||
{
|
||||
startCol = parseInt(lines[2]);
|
||||
endCol = parseInt(lines[3]);
|
||||
}
|
||||
if(endCol == 0) endLine += 1;
|
||||
if(lineMap)
|
||||
{
|
||||
lineMap.forEach((line, index) =>
|
||||
{
|
||||
if(line == startLine) startLine = index + 1;
|
||||
if(line == endLine) endLine = index + 1;
|
||||
});
|
||||
}
|
||||
|
||||
const range = new monaco.Range(startLine, startCol, endLine, endCol);
|
||||
programmaticSelectionRange = range;
|
||||
editor.setSelection(range);
|
||||
updateDecorations();
|
||||
}
|
||||
|
||||
function updateDecorations()
|
||||
{
|
||||
editor.deltaDecorations(decorations, []);
|
||||
decorations = [];
|
||||
|
||||
if(programmaticSelectionRange)
|
||||
{
|
||||
decorations.push({
|
||||
range: programmaticSelectionRange,
|
||||
options: {
|
||||
className: selectedClass,
|
||||
isWholeLine: false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
decorations = editor.deltaDecorations([], decorations);
|
||||
}
|
||||
|
||||
editor.onDidChangeCursorSelection(function()
|
||||
{
|
||||
// 只有当没有程序化选择时才更新装饰器
|
||||
if(!programmaticSelectionRange) return;
|
||||
|
||||
// 如果用户手动改变了选择,清除程序化选择的样式
|
||||
if(e.selections.some(sel => !rangesEqual(sel, programmaticSelectionRange)))
|
||||
{
|
||||
programmaticSelectionRange = null;
|
||||
updateDecorations();
|
||||
}
|
||||
});
|
||||
|
||||
function rangesEqual(r1, r2)
|
||||
{
|
||||
return r1.startLineNumber === r2.startLineNumber &&
|
||||
r1.startColumn === r2.startColumn &&
|
||||
r1.endLineNumber === r2.endLineNumber &&
|
||||
r1.endColumn === r2.endColumn;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+21
-10
@@ -5,13 +5,15 @@ namespace zin;
|
||||
class monaco extends wg
|
||||
{
|
||||
protected static array $defineProps = array(
|
||||
'id:string',
|
||||
'action?:string',
|
||||
'options?:array',
|
||||
'diffContent?:array',
|
||||
'selectedLines?:string',
|
||||
'onMouseDown?:string',
|
||||
'onMouseMove?:string'
|
||||
'id: string',
|
||||
'action?: string',
|
||||
'options?: array',
|
||||
'diffContent?: array',
|
||||
'selectedLines?: string', // startLine,endLine 或者 startLine,endLine,startCol,endCol
|
||||
'selectedClass?: string', // yellow-decoration: 黄色背景,wave-decoration: 红色波浪线
|
||||
'lineMap?: array', // 用于设置行号的显示规则,如 array('编辑器行号' => '实际行号')
|
||||
'onMouseDown?: string',
|
||||
'onMouseMove?: string'
|
||||
);
|
||||
|
||||
protected static array $defaultProps = array(
|
||||
@@ -21,11 +23,17 @@ class monaco extends wg
|
||||
'onMouseDown' => '',
|
||||
'onMouseMove' => ''
|
||||
);
|
||||
|
||||
public static function getPageJS(): ?string
|
||||
{
|
||||
return file_get_contents(__DIR__ . DS . 'js' . DS . 'v1.js');
|
||||
}
|
||||
|
||||
public static function getPageCSS(): ?string
|
||||
{
|
||||
return file_get_contents(__DIR__ . DS . 'css' . DS . 'v1.css');
|
||||
}
|
||||
|
||||
protected function build()
|
||||
{
|
||||
global $app;
|
||||
@@ -36,8 +44,10 @@ class monaco extends wg
|
||||
$options = $this->prop('options');
|
||||
$diffContent = $this->prop('diffContent');
|
||||
$selectedLines = $this->prop('selectedLines');
|
||||
$onMouseDown = $this->prop('onMouseDown');
|
||||
$onMouseMove = $this->prop('onMouseMove');
|
||||
$selectedClass = $this->prop('selectedClass');
|
||||
$lineMap = $this->prop('lineMap');
|
||||
$onMouseDown = $this->prop('onMouseDown');
|
||||
$onMouseMove = $this->prop('onMouseMove');
|
||||
|
||||
if(!$options) $options = new stdclass();
|
||||
return div
|
||||
@@ -51,7 +61,8 @@ class monaco extends wg
|
||||
jsVar('vsPath', $vsPath),
|
||||
jsVar('clientLang', $clientLang),
|
||||
jsVar('selectedLines', $selectedLines),
|
||||
h::import($app->getWebRoot() . 'js/monaco-editor/min/vs/loader.js'),
|
||||
jsVar('selectedClass', $selectedClass),
|
||||
jsVar('+lineMap', empty($lineMap) ? null : $lineMap),
|
||||
setID($id)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -187,6 +187,7 @@ class navbar extends wg
|
||||
'text' => $menuItem->text,
|
||||
'type' => 'text',
|
||||
'tagName' => 'span',
|
||||
'icon' => isset($menuItem->icon) ? $menuItem->icon : '',
|
||||
);
|
||||
continue;
|
||||
|
||||
@@ -326,6 +327,7 @@ class navbar extends wg
|
||||
{
|
||||
$items[] = array(
|
||||
'class' => $class,
|
||||
'icon' => isset($menuItem->icon) ? $menuItem->icon : '',
|
||||
'text' => $label,
|
||||
'url' => commonModel::createMenuLink($menuItem, $tab),
|
||||
'active' => $isActive,
|
||||
@@ -338,7 +340,7 @@ class navbar extends wg
|
||||
}
|
||||
else
|
||||
{
|
||||
$items[] = array('class' => $class, 'text' => $menuItem->text, 'active' => $isActive);
|
||||
$items[] = array('class' => $class, 'icon' => isset($menuItem->icon) ? $menuItem->icon : '', 'text' => $menuItem->text, 'active' => $isActive);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ window.addNewLine = function(e)
|
||||
const objectClass = objectType == 'roadmap' ? '.linkRoadmap' : '.linkPlan';
|
||||
newLine.find('.linkProduct').html(`<div id=product${index} class='form-group-wrapper picker-box' data-on='change' data-call='refreshPicker' data-params='event'></div>`);
|
||||
newLine.find(objectClass).html(`<div id=${objectType}${index} class='form-group-wrapper picker-box'></div>`);
|
||||
newLine.find('.branchBox').html(`<div id=branch${index} class='form-group-wrapper picker-box' data-call="branchChange(event)" data-on="change"></div>`, false);
|
||||
|
||||
$(obj).closest('.productBox').after(newLine);
|
||||
|
||||
@@ -50,7 +51,7 @@ window.addNewLine = function(e)
|
||||
options.disabled = false;
|
||||
options.placeholder = '';
|
||||
new zui.Picker(`#product${index}`, options);
|
||||
|
||||
new zui.Picker(`#branch${index}`, {name: `branch[${index}]`, multiple: true, items: []});
|
||||
new zui.Picker(`#${objectType}${index}`, {name: `${objectType}[${index}]`, items: [], multiple: true});
|
||||
|
||||
refreshPicker();
|
||||
@@ -77,22 +78,27 @@ window.removeLine = function(e)
|
||||
*/
|
||||
window.loadRoadmap = function(product)
|
||||
{
|
||||
let productID = product.val();
|
||||
let branchID = product.closest('.productBox').find('.linkBranch .pick-value').val();
|
||||
let link = $.createLink('demand', 'ajaxGetRoadmaps', 'productID=' + productID + '&branch=all¶m=nolaunching');
|
||||
const $items = [{text: '', value: '', key: ''}];
|
||||
const $roadmap = product.closest('.productBox').find('.roadmapBox .pick-value');
|
||||
const objectType = product.closest('.productsBox').data('objecttype');
|
||||
if(objectType != 'roadmap') return;
|
||||
|
||||
let productID = product.val();
|
||||
let branchID = product.closest('.productBox').find('.linkBranch .pick-value').val();
|
||||
if(typeof branchID == 'undefined' || branchID === '') branchID = 'all';
|
||||
let link = $.createLink('demand', 'ajaxGetRoadmaps', 'productID=' + productID + '&branch=' + branchID + '¶m=nolaunching');
|
||||
const $items = [{text: '', value: '', key: ''}];
|
||||
const $roadmap = product.closest('.productBox').find('.roadmapBox .pick-value');
|
||||
const oldRoadmap = product.closest('.productBox').find('[name^=roadmap]').val();
|
||||
|
||||
$roadmap.closest('.picker-box').removeClass('hidden');
|
||||
$roadmap.closest('.linkRoadmap').find('[name^=roadmapName]').addClass('hidden');
|
||||
$roadmap.closest('.linkRoadmap').find('[name^=roadmapName]').attr('disabled', 'disabled');
|
||||
$roadmap.zui('picker').render({disabled: false});
|
||||
$roadmap.zui('picker').$.setValue('');
|
||||
|
||||
$.getJSON(link, function(data)
|
||||
{
|
||||
$roadmap.zui('picker').render({items: data});
|
||||
if(productID == '' || productID === undefined) $roadmap.zui('picker').render({items: $items});
|
||||
$roadmap.zui('picker').$.setValue(oldRoadmap);
|
||||
|
||||
let optionCount = $roadmap.zui('picker').options.items.length;
|
||||
|
||||
@@ -110,52 +116,50 @@ window.loadRoadmap = function(product)
|
||||
*/
|
||||
window.loadPlan = function(product)
|
||||
{
|
||||
const objectType = product.closest('.productsBox').data('objecttype');
|
||||
if(objectType != 'plan') return;
|
||||
|
||||
let productID = product.val();
|
||||
let link = $.createLink('charter', 'ajaxGetPlans', 'productID=' + productID);
|
||||
let branchID = product.closest('.productBox').find('.linkBranch .pick-value').val();
|
||||
if(typeof branchID == 'undefined' || branchID === '') branchID = 'all';
|
||||
let link = $.createLink('charter', 'ajaxGetPlans', 'productID=' + productID + '&branchID=' + branchID);
|
||||
const $items = [{text: '', value: '', key: ''}];
|
||||
const $plan = product.closest('.productBox').find('.planBox .pick-value');
|
||||
const oldPlan = product.closest('.productBox').find('[name^=plan]').val();
|
||||
|
||||
$plan.closest('.picker-box').removeClass('hidden');
|
||||
$plan.zui('picker').render({disabled: false});
|
||||
$plan.zui('picker').$.setValue('');
|
||||
|
||||
$.getJSON(link, function(data)
|
||||
{
|
||||
$plan.zui('picker').render({items: data});
|
||||
if(productID == '' || productID === undefined) $plan.zui('picker').render({items: $items});
|
||||
$plan.zui('picker').$.setValue(oldPlan);
|
||||
});
|
||||
}
|
||||
|
||||
window.refreshPicker = function(event)
|
||||
{
|
||||
let $products = $('.productsBox').find('.linkProduct .pick-value');
|
||||
let selectedProducts = [];
|
||||
$products.each(function()
|
||||
{
|
||||
if($(this).val() != '') selectedProducts.push($(this).val());
|
||||
});
|
||||
|
||||
$products.each(function()
|
||||
{
|
||||
let $product = $(this);
|
||||
let productID = $product.val();
|
||||
let $productPicker = $product.zui('picker');
|
||||
let productItems = $productPicker.options.items;
|
||||
|
||||
$.each(productItems, function(i, item)
|
||||
{
|
||||
if(item.value == '') return;
|
||||
productItems[i].disabled = selectedProducts.includes(item.value) && item.value != productID;
|
||||
})
|
||||
|
||||
$productPicker.render({items: productItems});
|
||||
});
|
||||
|
||||
if(typeof event !== "undefined")
|
||||
{
|
||||
const objectType = $(event.target).closest('.productsBox').data('objecttype');
|
||||
if(objectType == 'roadmap') loadRoadmap($(event.target));
|
||||
if(objectType == 'plan') loadPlan($(event.target));
|
||||
const $product = $(event.target);
|
||||
const productID = $product.val();
|
||||
if(multiBranchProducts[productID])
|
||||
{
|
||||
loadBranches($product);
|
||||
}
|
||||
else
|
||||
{
|
||||
updateItems();
|
||||
|
||||
const objectType = $product.closest('.productsBox').data('objecttype');
|
||||
if(objectType == 'roadmap') loadRoadmap($product);
|
||||
if(objectType == 'plan') loadPlan($product);
|
||||
|
||||
let $formRow = $product.closest('.productBox');
|
||||
$formRow.find('.form-group').eq(0).addClass('w-1/2').removeClass('w-1/4');
|
||||
$formRow.find('.form-group').eq(1).addClass('hidden');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,3 +172,93 @@ window.loadRoadmapStories = function(event)
|
||||
|
||||
openUrl(link, {load: 'modal', size: 'lg', type: 'iframe'});
|
||||
}
|
||||
|
||||
window.loadBranches = function(product)
|
||||
{
|
||||
let currentProduct = product.val();
|
||||
let $formRow = product.closest('.productBox');
|
||||
let charterID = $('[name="charter"]').length ? $('[name="charter"]').val() : 0;
|
||||
const objectType = $('.productsBox').data('linkType');
|
||||
|
||||
if(typeof projectID == 'undefined') projectID = 0;
|
||||
$.getJSON($.createLink('branch', 'ajaxGetBranches', "productID=" + currentProduct + "&oldBranch=¶m=active&projectID=" + projectID + "&withMainBranch=true&isTwins=no&fieldID=0&multiple=&charterID=" + charterID), function(data)
|
||||
{
|
||||
if(data.length > 0)
|
||||
{
|
||||
$formRow.find('.form-group').eq(0).addClass('w-1/4').removeClass('w-1/2');
|
||||
$formRow.find('.form-group').eq(1).removeClass('hidden');
|
||||
const $branchPicker = $formRow.find('[name^=branch]').zui('picker');
|
||||
$branchPicker.render({items: data, multiple: true});
|
||||
$branchPicker.$.setValue('0').then(() =>
|
||||
{
|
||||
if(objectType == 'roadmap')
|
||||
{
|
||||
loadRoadmap(product);
|
||||
}
|
||||
else
|
||||
{
|
||||
loadPlan(product);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
if(objectType == 'roadmap')
|
||||
{
|
||||
loadRoadmap(product);
|
||||
}
|
||||
else
|
||||
{
|
||||
loadPlan(product);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
updateItems();
|
||||
}
|
||||
|
||||
window.branchChange = function(event)
|
||||
{
|
||||
const objectType = $(event.target).closest('.productsBox').data('objecttype');
|
||||
const $product = $(event.target).closest('.productBox').find('.linkProduct .pick-value');
|
||||
if(objectType == 'roadmap')
|
||||
{
|
||||
loadRoadmap($product);
|
||||
}
|
||||
else
|
||||
{
|
||||
loadPlan($product);
|
||||
}
|
||||
|
||||
updateItems();
|
||||
}
|
||||
|
||||
window.updateItems = function()
|
||||
{
|
||||
let chosenProducts = [];
|
||||
$(".productsBox [name^='product']").each(function()
|
||||
{
|
||||
const productID = $(this).val();
|
||||
if(productID > 0 && chosenProducts.indexOf(productID) == -1) chosenProducts.push(productID);
|
||||
});
|
||||
|
||||
let allItems = zui.Picker.query("[name^='product']").options.items;
|
||||
let productItems = [];
|
||||
for(i = 0; i < allItems.length; i++)
|
||||
{
|
||||
allItems[i].disabled = false;
|
||||
if(chosenProducts.includes(allItems[i].value)) allItems[i].disabled = true;
|
||||
productItems[allItems[i].value] = Object.assign({},allItems[i]);
|
||||
productItems[allItems[i].value].i = i;
|
||||
}
|
||||
|
||||
$(".productsBox [name^='product']").each(function()
|
||||
{
|
||||
const $product = $(this).zui('picker');
|
||||
const productID = $(this).val();
|
||||
let currentProductItems = JSON.parse(JSON.stringify(allItems));
|
||||
if(productID != 0) currentProductItems[productItems[productID].i].disabled = false;
|
||||
|
||||
$product.render({items: currentProductItems});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ class productCharterBox extends wg
|
||||
(
|
||||
setClass('productsBox'),
|
||||
setData(array('objecttype' => $this->prop('objectType'))),
|
||||
jsVar('multiBranchProducts', data('multiBranchProducts')),
|
||||
$productsBox
|
||||
);
|
||||
}
|
||||
@@ -89,6 +90,23 @@ class productCharterBox extends wg
|
||||
)
|
||||
),
|
||||
formGroup
|
||||
(
|
||||
set::width('1/4'),
|
||||
setClass('hidden linkBranch ml-px'),
|
||||
set::label(''),
|
||||
inputGroup
|
||||
(
|
||||
setClass('branchBox'),
|
||||
picker
|
||||
(
|
||||
set::name('branch[0][]'),
|
||||
set::items(array()),
|
||||
set::multiple(true),
|
||||
setData(array('on' => 'change', 'call' => 'branchChange', 'params' => 'event'))
|
||||
)
|
||||
)
|
||||
),
|
||||
formGroup
|
||||
(
|
||||
set::width('1/2'),
|
||||
set::label($lang->charter->$objectType),
|
||||
@@ -185,6 +203,22 @@ class productCharterBox extends wg
|
||||
)
|
||||
),
|
||||
formGroup
|
||||
(
|
||||
set::width('1/4'),
|
||||
setClass('hidden linkBranch ml-px'),
|
||||
set::label(''),
|
||||
inputGroup
|
||||
(
|
||||
setClass('branchBox'),
|
||||
picker
|
||||
(
|
||||
set::name("branch[$index]"),
|
||||
set::items(array()),
|
||||
setData(array('on' => 'change', 'call' => 'branchChange', 'params' => 'event'))
|
||||
)
|
||||
)
|
||||
),
|
||||
formGroup
|
||||
(
|
||||
set::width('1/2'),
|
||||
set::label($lang->charter->$objectType),
|
||||
|
||||
@@ -77,6 +77,8 @@ function isFieldRequired(?string $name, ?string $requiredFields = null): bool
|
||||
global $config, $app;
|
||||
$moduleName = $app->moduleName;
|
||||
$methodName = $app->methodName;
|
||||
if($moduleName == 'story' && $app->rawModule == 'requirement') $moduleName = 'requirement';
|
||||
if($moduleName == 'story' && $app->rawModule == 'epic') $moduleName = 'epic';
|
||||
if(!isset($config->$moduleName->$methodName->requiredFields)) return false;
|
||||
|
||||
$requiredFields = $config->$moduleName->$methodName->requiredFields;
|
||||
|
||||
@@ -88,7 +88,7 @@ $config->action->majorList['doc'] = array('releaseddoc');
|
||||
|
||||
$config->action->needGetProjectType = 'build,task,bug,case,testcase,caselib,testtask,testsuite,testreport,doc,issue,release,risk,design,opportunity,trainplan,gapanalysis,researchplan,researchreport,';
|
||||
$config->action->needGetRelateField = ',branch,story,epic,requirement,productplan,release,task,build,bug,testcase,case,testtask,testreport,doc,doclib,issue,risk,opportunity,trainplan,gapanalysis,team,whitelist,researchplan,researchreport,meeting,kanbanlane,kanbancolumn,module,review,';
|
||||
$config->action->noLinkModules = ',doclib,module,webhook,gitlab,instance,gitea,gogs,sonarqube,pipeline,jenkins,kanban,kanbanspace,kanbancolumn,kanbanlane,kanbanregion,kanbancard,execution,project,traincategory,apistruct,program,product,user,entry,repo,pivot,scene,boardspace,auditplan,auditresult,productline,';
|
||||
$config->action->noLinkModules = ',doclib,module,webhook,gitlab,instance,gitea,gogs,sonarqube,pipeline,jenkins,kanban,kanbanspace,kanbancolumn,kanbanlane,kanbanregion,kanbancard,execution,project,traincategory,apistruct,program,product,user,entry,repo,pivot,scene,boardspace,auditplan,auditresult,productline,chapter,';
|
||||
$config->action->ignoreObjectType4Dynamic = 'kanbanregion,kanbanlane,kanbancolumn';
|
||||
$config->action->ignoreActions4Dynamic = 'disconnectxuanxuan,reconnectxuanxuan,loginxuanxuan,logoutxuanxuan,editmr,removemr,syncdoingbyticket,syncdoingbystory,syncdoingbyuserstory,syncdoingbyepic,syncdoingbytask,syncdoingbybug,syncdoingbytodo,syncdoingbydemand';
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ $lang->action->old = 'Old';
|
||||
$lang->action->new = 'New';
|
||||
$lang->action->to = 'To';
|
||||
$lang->action->superReviewer = 'Super Reviewer';
|
||||
$lang->action->byRule = '(Triggered by Rule %s)';
|
||||
|
||||
$lang->action->trash = 'Aufräumen';
|
||||
$lang->action->undelete = 'Wiederherstellen';
|
||||
|
||||
@@ -35,6 +35,7 @@ $lang->action->old = 'Old';
|
||||
$lang->action->new = 'New';
|
||||
$lang->action->to = 'To';
|
||||
$lang->action->superReviewer = 'Super Reviewer';
|
||||
$lang->action->byRule = '(Triggered by Rule %s)';
|
||||
|
||||
$lang->action->trash = 'Recycle';
|
||||
$lang->action->undelete = 'Restore';
|
||||
|
||||
@@ -35,6 +35,7 @@ $lang->action->old = 'Old';
|
||||
$lang->action->new = 'New';
|
||||
$lang->action->to = 'To';
|
||||
$lang->action->superReviewer = 'Super Reviewer';
|
||||
$lang->action->byRule = '(Triggered by Rule %s)';
|
||||
|
||||
$lang->action->trash = 'Corbeille';
|
||||
$lang->action->undelete = 'Restaurer';
|
||||
|
||||
@@ -35,6 +35,7 @@ $lang->action->old = '旧值为';
|
||||
$lang->action->new = '新值为';
|
||||
$lang->action->to = '到';
|
||||
$lang->action->superReviewer = '超级评审人';
|
||||
$lang->action->byRule = '(由规则引擎%s触发)';
|
||||
|
||||
$lang->action->trash = '回收站';
|
||||
$lang->action->undelete = '还原';
|
||||
|
||||
+21
-3
@@ -197,6 +197,7 @@ class actionModel extends model
|
||||
$this->loadModel('workflow');
|
||||
foreach($actions as $actionID => $action)
|
||||
{
|
||||
$extra = $action->extra;
|
||||
$actionName = strtolower($action->action);
|
||||
if($this->config->edition != 'open' && !isset($flowList[$action->objectType])) $flowList[$action->objectType] = $this->workflow->getByModule($action->objectType);
|
||||
|
||||
@@ -426,7 +427,7 @@ class actionModel extends model
|
||||
->andWhere('vision')->eq($this->config->vision)
|
||||
->orderBy($orderBy)
|
||||
->page($pager)
|
||||
->fetchAll();
|
||||
->fetchAll('', false);
|
||||
if(empty($trashes)) return array();
|
||||
|
||||
/* 按对象类型对已删除的对象进行分组,并获取名称字段。 */
|
||||
@@ -455,6 +456,13 @@ class actionModel extends model
|
||||
$objectNames['jenkins'] = $this->dao->select("id, {$field} AS name")->from($table)->where('id')->in($objectIdList)->andWhere('type')->eq('jenkins')->fetchPairs();
|
||||
$objectNames['gitlab'] = $this->dao->select("id, {$field} AS name")->from($table)->where('id')->in($objectIdList)->andWhere('type')->eq('gitlab')->fetchPairs();
|
||||
}
|
||||
elseif($objectType == 'pivot')
|
||||
{
|
||||
$objectNames[$objectType] = $this->dao->select("t1.id, t2.{$field} AS name")->from($table)->alias('t1')
|
||||
->leftJoin(TABLE_PIVOTSPEC)->alias('t2')->on('t1.id = t2.pivot and t1.version = t2.version')
|
||||
->where('t1.id')->in($objectIdList)
|
||||
->fetchPairs();
|
||||
}
|
||||
else
|
||||
{
|
||||
$objectNames[$objectType] = $this->dao->select("id, {$field} AS name")->from($table)->where('id')->in($objectIdList)->fetchPairs();
|
||||
@@ -577,7 +585,7 @@ class actionModel extends model
|
||||
*/
|
||||
public function getHistory(array|int $actionID): array
|
||||
{
|
||||
return $this->dao->select()->from(TABLE_HISTORY)->where('action')->in($actionID)->fetchGroup('action');
|
||||
return $this->dao->select('*')->from(TABLE_HISTORY)->where('action')->in($actionID)->fetchGroup('action');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -831,6 +839,11 @@ class actionModel extends model
|
||||
list($extra) = explode('|', $extra);
|
||||
if(!empty($desc['extra'][$extra])) $actionDesc = str_replace('$extra', $desc['extra'][$extra], $desc['main']);
|
||||
}
|
||||
if(($action->objectType == 'story' || $action->objectType == 'demand') && $action->action == 'fromboard')
|
||||
{
|
||||
$actionExtra = html::a(helper::createLink('board', 'view', "canvasID={$action->extra}"), $action->extra);
|
||||
$actionDesc = str_replace('$extra', $actionExtra, $desc['main']);
|
||||
}
|
||||
|
||||
if($action->objectType == 'module' && strpos(',created,moved,', $action->action) !== false)
|
||||
{
|
||||
@@ -849,11 +862,16 @@ class actionModel extends model
|
||||
|
||||
if($action->objectType == 'board' && in_array($action->action, array('importstory', 'importdemand', 'importrequirement', 'importepic', 'convertdemand', 'convertepic', 'convertrequirement', 'convertstory')))
|
||||
{
|
||||
if($action->action == 'importstory' || $action->action == 'importrequirement' || $action->action == 'convertstory' || $action->action == 'convertrequirement')
|
||||
if($action->action == 'importstory' || $action->action == 'importrequirement' || $action->action == 'convertstory')
|
||||
{
|
||||
$story = $this->loadModel('story')->getById((int)$action->extra);
|
||||
$link = helper::createLink('story', 'view', "storyID={$action->extra}");
|
||||
}
|
||||
if($action->action == 'convertrequirement')
|
||||
{
|
||||
$story = $this->loadModel('story')->getById((int)$action->extra);
|
||||
$link = helper::createLink('requirement', 'view', "storyID={$action->extra}");
|
||||
}
|
||||
if($action->action == 'importdemand' || $action->action == 'convertdemand')
|
||||
{
|
||||
$story = $this->loadModel('demand')->getByID((int)$action->extra);
|
||||
|
||||
@@ -88,7 +88,7 @@ class actionZen extends action
|
||||
if($trash->objectType == 'pivot')
|
||||
{
|
||||
$pivotNames = json_decode($trash->objectName, true);
|
||||
if(empty($pivotNames)) return;
|
||||
$pivotNames = empty($pivotNames) ? array() : $pivotNames;
|
||||
$trash->objectName = zget($pivotNames, $this->app->getClientLang(), '') ? : reset(array_filter($pivotNames));
|
||||
}
|
||||
else
|
||||
@@ -116,6 +116,7 @@ class actionZen extends action
|
||||
if($trash->objectType == 'doc')
|
||||
{
|
||||
$params = "docID={$trash->objectID}";
|
||||
if($trash->comment == 'chapter') $module = 'chapter';
|
||||
}
|
||||
if(in_array($module, array('traincourse','traincontents')))
|
||||
{
|
||||
|
||||
@@ -9,7 +9,7 @@ $config->admin->menuGroup['system'] = array('custom|mode', 'backup', 'cron',
|
||||
$config->admin->menuGroup['company'] = array('dept', 'company', 'user', 'group', 'tutorial');
|
||||
$config->admin->menuGroup['switch'] = array('admin|setmodule');
|
||||
$config->admin->menuGroup['model'] = array('auditcl', 'stage', 'design', 'cmcl', 'reviewcl', 'custom|required', 'custom|set', 'custom|flow', 'custom|code', 'custom|percent','custom|estimate', 'custom|hours', 'subject', 'process', 'activity', 'zoutput', 'classify', 'holiday', 'reviewsetting', 'custom|project');
|
||||
$config->admin->menuGroup['feature'] = array('custom|set', 'custom|product', 'custom|execution', 'custom|required', 'custom|kanban', 'approvalflow', 'measurement', 'meetingroom', 'custom|browsestoryconcept', 'custom|kanban', 'sqlbuilder', 'report', 'custom|limittaskdate', 'measurement');
|
||||
$config->admin->menuGroup['feature'] = array('custom|set', 'custom|product', 'custom|execution', 'custom|required', 'custom|kanban', 'measurement', 'meetingroom', 'custom|browsestoryconcept', 'custom|kanban', 'sqlbuilder', 'report', 'custom|limittaskdate', 'measurement');
|
||||
$config->admin->menuGroup['template'] = array('custom|set', 'baseline');
|
||||
$config->admin->menuGroup['message'] = array('mail', 'webhook', 'sms', 'message');
|
||||
$config->admin->menuGroup['dev'] = array('dev', 'entry', 'editor');
|
||||
@@ -61,7 +61,7 @@ $config->admin->plugins[194]->abstract = '每日生成公司级禅道应用巡
|
||||
$config->admin->plugins[194]->viewLink = 'https://www.zentao.net/extension-viewExt-194.html';
|
||||
|
||||
$config->admin->apiRoot = 'https://www.zentao.net';
|
||||
$config->admin->cdnRoot = 'https://cdn.easycorp.cn/web';
|
||||
$config->admin->cdnRoot = 'https://static.zentao.net/web';
|
||||
$config->admin->classURL = 'https://www.zentao.net/zentao.html';
|
||||
$config->admin->dynamicURL = 'https://www.zentao.net/dynamic.html';
|
||||
$config->admin->downloadURL = 'https://www.zentao.net/download.html';
|
||||
|
||||
@@ -57,7 +57,7 @@ $(() =>
|
||||
$('select[name="type"]').on('change', handleModelTypeChange);
|
||||
$('select[name="vendor"]').on('change', handleVendorChange);
|
||||
$('select[name="proxyType"]').on('change', handleProxyTypeChange);
|
||||
$('button[type="submit"]]').on('click', e =>
|
||||
$('button[type="submit"]').on('click', e =>
|
||||
{
|
||||
$('button[type="submit"]').attr('disabled', 'disabled');
|
||||
$('#test-conn-btn').attr('disabled', 'disabled');
|
||||
|
||||
@@ -59,7 +59,7 @@ $(() =>
|
||||
$('select[name="type"]').on('change', handleModelTypeChange);
|
||||
$('select[name="vendor"]').on('change', handleVendorChange);
|
||||
$('select[name="proxyType"]').on('change', handleProxyTypeChange);
|
||||
$('button[type="submit"]]').on('click', e =>
|
||||
$('button[type="submit"]').on('click', e =>
|
||||
{
|
||||
$('button[type="submit"]').attr('disabled', 'disabled');
|
||||
$('#test-conn-btn').attr('disabled', 'disabled');
|
||||
|
||||
@@ -282,6 +282,7 @@ class api extends control
|
||||
$api->icon = "api is-$api->method";
|
||||
$api->api = true;
|
||||
$api->title = "$api->method $api->path " . htmlspecialchars_decode($api->title);
|
||||
$api->editable = common::hasPriv('api', 'edit');
|
||||
}
|
||||
$data['docs'] = array_values($apis);
|
||||
}
|
||||
@@ -341,6 +342,7 @@ class api extends control
|
||||
$api->icon = "api is-$api->method";
|
||||
$api->title = "$api->method $api->path $api->title";
|
||||
$api->api = true;
|
||||
$api->editable = common::hasPriv('api', 'edit');
|
||||
}
|
||||
$this->send($api);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,2 @@
|
||||
.table-empty-tip {padding: 80px 10px; text-align: center; background: #fff;}
|
||||
.table-empty-tip .btn-group .btn {background-color: rgba(var(--color-primary-50-rgb),var(--tw-bg-opacity)); color: var(--nav-active-color); --tw-ring-color: rgba(var(--color-primary-50-rgb),var(--tw-bg-opacity));}
|
||||
|
||||
.picker-menu .menu-item.is-nested > .item-inner .item-content .item-title {flex-grow: 0!important; opacity: .6;}
|
||||
.picker-menu .menu-item.is-nested > .item-inner .item-content .item-subtitle {--skin-pale-rgb: var(--color-gray-500-rgb); height: 1rem; min-width: 1rem; border-radius: var(--radius-full); --skin-color: rgba(var(--skin-pale-rgb), .1); --skin-text: rgba(var(--skin-pale-rgb), 1); --state-color: rgba(var(--skin-pale-rgb), .1); --skin-bg: var(--skin-color); --skin-ring: var(--skin-color); --skin-border: var(--skin-color); --skin-hover-text: var(--skin-text); --skin-hover-bg: var(--skin-bg); --tw-ring-color: var(--skin-ring); --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); --color-link-hover: var(--skin-text); background-color: var(--skin-bg); border-color: var(--skin-border); box-shadow: var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000); color: var(--skin-text); display: inline-flex; font-size: .75rem; align-items: center; justify-content: center; line-height: 1rem; padding-left: .25rem; padding-right: .25rem;}
|
||||
.picker-menu .menu-item.is-nested > .item-inner {background-color: inherit!important; color: inherit!important; cursor: default;}
|
||||
.picker-menu .menu-item.is-nested > .item-inner::before {display: none;}
|
||||
|
||||
@@ -4760,3 +4760,45 @@ $config->bi->builtin->metrics[] = array
|
||||
'desc' => '按系统统计的主机总数是指在禅道中的全部主机总数。',
|
||||
'definition' => "所有主机的个数求和"
|
||||
);
|
||||
|
||||
$config->bi->builtin->metrics[] = array
|
||||
(
|
||||
'name' => '按月统计的人均研发效能',
|
||||
'alias' => '人均研发效能',
|
||||
'code' => 'avg_of_dev_efficiency',
|
||||
'purpose' => 'rate',
|
||||
'scope' => 'system',
|
||||
'object' => 'user',
|
||||
'unit' => 'percentage',
|
||||
'dateType' => 'month',
|
||||
'desc' => '指团队成员在单位时间内完成的平均需求规模。它用于评估团队的生产力、资源利用效率以及工作负荷的合理性。',
|
||||
'definition' => "按月统计的人均研发效能 = 当月发布的研发需求的规模总数 / 当月禅道系统中的总人数\n当月发布的研发需求,是统计当月阶段状态为已发布、已关闭关闭原因为已完成的研发需求,过滤已删除的研发需求,次月过滤已统计过的研发需求。"
|
||||
);
|
||||
|
||||
$config->bi->builtin->metrics[] = array
|
||||
(
|
||||
'name' => '按月统计的已发布研发需求平均交付周期',
|
||||
'alias' => '已发布研发需求平均交付周期',
|
||||
'code' => 'avg_of_release_story_delivery_time',
|
||||
'purpose' => 'time',
|
||||
'scope' => 'system',
|
||||
'object' => 'story',
|
||||
'unit' => 'day',
|
||||
'dateType' => 'month',
|
||||
'desc' => '指从需求被提出(创建)到最终交付给客户或上线所需的平均时间,反映了团队或组织在需求实现过程中的效率。它可以用于评估需求的响应速度和交付速度。',
|
||||
'definition' => "按月统计的已发布研发需求平均交付周期 = sum ( 当月发布的研发需求的发布时间 - 当月发布的研发需求的创建时间 ) / 当月发布的研发需求总数\n当月发布的研发需求,是统计当月阶段状态为已发布、已关闭关闭原因为已完成的研发需求,过滤已删除的研发需求,次月过滤已统计过的研发需求。"
|
||||
);
|
||||
|
||||
$config->bi->builtin->metrics[] = array
|
||||
(
|
||||
'name' => '按月统计的已发布研发需求平均缺陷密度',
|
||||
'alias' => '已发布研发需求平均缺陷密度',
|
||||
'code' => 'avg_of_release_story_defect_density',
|
||||
'purpose' => 'qc',
|
||||
'scope' => 'system',
|
||||
'object' => 'story',
|
||||
'unit' => 'count',
|
||||
'dateType' => 'month',
|
||||
'desc' => '指在测试阶段发现的缺陷数量与需求规模的比值。它反映了代码或系统的质量状况,用于评估开发过程的质量以及测试的有效性。',
|
||||
'definition' => "按月统计的已发布研发需求平均缺陷密度 = sum ( 当月发布的研发需求关联的Bug总数 ) / 当月发布的研发需求的规模总数\n当月发布的研发需求,是统计当月阶段状态为已发布、已关闭关闭原因为已完成的研发需求,过滤已删除的研发需求,次月过滤已统计过的研发需求。\n当月发布的研发需求关联的Bug总数,是统计当月发布的每个研发需求关联Bug,过滤已删除的bug。"
|
||||
);
|
||||
|
||||
@@ -197,12 +197,12 @@ $lang->block->default['full']['my'][] = array('title' => "Recent {$lang->project
|
||||
$lang->block->default['full']['my'][] = array('title' => "Undone {$lang->projectCommon} list", 'module' => 'project', 'code' => 'project', 'width' => '2', 'params' => array('type' => 'undone', 'count' => '20', 'orderBy' => 'id_desc'));
|
||||
$lang->block->default['full']['my'][] = array('title' => "{$lang->projectCommon} Statistic", 'module' => 'project', 'code' => 'statistic', 'width' => '2', 'params' => array('type' => 'undone', 'count' => '20'));
|
||||
$lang->block->default['full']['my'][] = array('title' => "Undone {$lang->execution->common} Statistic", 'module' => 'execution', 'code' => 'statistic', 'width' => '2', 'params' => array('type' => 'undone', 'count' => '20'));
|
||||
$lang->block->default['full']['my'][] = array('title' => "Unclosed {$lang->productCommon} Statistic", 'module' => 'product', 'code' => 'statistic', 'width' => '2', 'params' => array('type' => 'noclosed', 'count' => '20'));
|
||||
$lang->block->default['full']['my'][] = array('title' => "Unclosed {$lang->productCommon} test statistics", 'module' => 'qa', 'code' => 'statistic', 'width' => '2', 'params' => array('type' => 'noclosed', 'count' => '20'));
|
||||
if($config->vision != 'lite') $lang->block->default['full']['my'][] = array('title' => "Unclosed {$lang->productCommon} Statistic", 'module' => 'product', 'code' => 'statistic', 'width' => '2', 'params' => array('type' => 'noclosed', 'count' => '20'));
|
||||
if($config->vision != 'lite') $lang->block->default['full']['my'][] = array('title' => "Unclosed {$lang->productCommon} test statistics", 'module' => 'qa', 'code' => 'statistic', 'width' => '2', 'params' => array('type' => 'noclosed', 'count' => '20'));
|
||||
$lang->block->default['full']['my'][] = array('title' => "Zentao Dynamic", 'module' => 'zentaodynamic', 'code' => 'zentaodynamic', 'width' => '1');
|
||||
$lang->block->default['full']['my'][] = array('title' => 'Dynamic', 'module' => 'dynamic', 'code' => 'dynamic', 'width' => '1');
|
||||
$lang->block->default['full']['my'][] = array('title' => "Team", 'module' => 'teamachievement', 'code' => 'teamachievement', 'width' => '1');
|
||||
$lang->block->default['full']['my'][] = array('title' => "{$lang->productCommon} Overview", 'module' => 'product', 'code' => 'overview', 'width' => '1');
|
||||
if($config->vision != 'lite') $lang->block->default['full']['my'][] = array('title' => "{$lang->productCommon} Overview", 'module' => 'product', 'code' => 'overview', 'width' => '1');
|
||||
$lang->block->default['full']['my'][] = array('title' => "{$lang->projectCommon} Overview", 'module' => 'project', 'code' => 'overview', 'width' => '1');
|
||||
$lang->block->default['full']['my'][] = array('title' => "{$lang->execution->common} Overview", 'module' => 'execution', 'code' => 'overview', 'width' => '1');
|
||||
|
||||
|
||||
@@ -197,12 +197,12 @@ $lang->block->default['full']['my'][] = array('title' => "Recent {$lang->project
|
||||
$lang->block->default['full']['my'][] = array('title' => "Undone {$lang->projectCommon} list", 'module' => 'project', 'code' => 'project', 'width' => '2', 'params' => array('type' => 'undone', 'count' => '20', 'orderBy' => 'id_desc'));
|
||||
$lang->block->default['full']['my'][] = array('title' => "{$lang->projectCommon} Statistic", 'module' => 'project', 'code' => 'statistic', 'width' => '2', 'params' => array('type' => 'undone', 'count' => '20'));
|
||||
$lang->block->default['full']['my'][] = array('title' => "Undone {$lang->execution->common} Statistic", 'module' => 'execution', 'code' => 'statistic', 'width' => '2', 'params' => array('type' => 'undone', 'count' => '20'));
|
||||
$lang->block->default['full']['my'][] = array('title' => "Unclosed {$lang->productCommon} Statistic", 'module' => 'product', 'code' => 'statistic', 'width' => '2', 'params' => array('type' => 'noclosed', 'count' => '20'));
|
||||
$lang->block->default['full']['my'][] = array('title' => "Unclosed {$lang->productCommon} test statistics", 'module' => 'qa', 'code' => 'statistic', 'width' => '2', 'params' => array('type' => 'noclosed', 'count' => '20'));
|
||||
if($config->vision != 'lite') $lang->block->default['full']['my'][] = array('title' => "Unclosed {$lang->productCommon} Statistic", 'module' => 'product', 'code' => 'statistic', 'width' => '2', 'params' => array('type' => 'noclosed', 'count' => '20'));
|
||||
if($config->vision != 'lite') $lang->block->default['full']['my'][] = array('title' => "Unclosed {$lang->productCommon} test statistics", 'module' => 'qa', 'code' => 'statistic', 'width' => '2', 'params' => array('type' => 'noclosed', 'count' => '20'));
|
||||
$lang->block->default['full']['my'][] = array('title' => "Zentao Dynamic", 'module' => 'zentaodynamic', 'code' => 'zentaodynamic', 'width' => '1');
|
||||
$lang->block->default['full']['my'][] = array('title' => 'Dynamic', 'module' => 'dynamic', 'code' => 'dynamic', 'width' => '1');
|
||||
$lang->block->default['full']['my'][] = array('title' => "Team", 'module' => 'teamachievement', 'code' => 'teamachievement', 'width' => '1');
|
||||
$lang->block->default['full']['my'][] = array('title' => "{$lang->productCommon} Overview", 'module' => 'product', 'code' => 'overview', 'width' => '1');
|
||||
if($config->vision != 'lite') $lang->block->default['full']['my'][] = array('title' => "{$lang->productCommon} Overview", 'module' => 'product', 'code' => 'overview', 'width' => '1');
|
||||
$lang->block->default['full']['my'][] = array('title' => "{$lang->projectCommon} Overview", 'module' => 'project', 'code' => 'overview', 'width' => '1');
|
||||
$lang->block->default['full']['my'][] = array('title' => "{$lang->execution->common} Overview", 'module' => 'execution', 'code' => 'overview', 'width' => '1');
|
||||
|
||||
|
||||
@@ -197,12 +197,12 @@ $lang->block->default['full']['my'][] = array('title' => "Recent {$lang->project
|
||||
$lang->block->default['full']['my'][] = array('title' => "Undone {$lang->projectCommon} list", 'module' => 'project', 'code' => 'project', 'width' => '2', 'params' => array('type' => 'undone', 'count' => '20', 'orderBy' => 'id_desc'));
|
||||
$lang->block->default['full']['my'][] = array('title' => "{$lang->projectCommon} Statistic", 'module' => 'project', 'code' => 'statistic', 'width' => '2', 'params' => array('type' => 'undone', 'count' => '20'));
|
||||
$lang->block->default['full']['my'][] = array('title' => "Undone {$lang->execution->common} Statistic", 'module' => 'execution', 'code' => 'statistic', 'width' => '2', 'params' => array('type' => 'undone', 'count' => '20'));
|
||||
$lang->block->default['full']['my'][] = array('title' => "Unclosed {$lang->productCommon} Statistic", 'module' => 'product', 'code' => 'statistic', 'width' => '2', 'params' => array('type' => 'noclosed', 'count' => '20'));
|
||||
$lang->block->default['full']['my'][] = array('title' => "Unclosed {$lang->productCommon} test statistics", 'module' => 'qa', 'code' => 'statistic', 'width' => '2', 'params' => array('type' => 'noclosed', 'count' => '20'));
|
||||
if($config->vision != 'lite') $lang->block->default['full']['my'][] = array('title' => "Unclosed {$lang->productCommon} Statistic", 'module' => 'product', 'code' => 'statistic', 'width' => '2', 'params' => array('type' => 'noclosed', 'count' => '20'));
|
||||
if($config->vision != 'lite') $lang->block->default['full']['my'][] = array('title' => "Unclosed {$lang->productCommon} test statistics", 'module' => 'qa', 'code' => 'statistic', 'width' => '2', 'params' => array('type' => 'noclosed', 'count' => '20'));
|
||||
$lang->block->default['full']['my'][] = array('title' => "Zentao Dynamic", 'module' => 'zentaodynamic', 'code' => 'zentaodynamic', 'width' => '1');
|
||||
$lang->block->default['full']['my'][] = array('title' => 'Dynamic', 'module' => 'dynamic', 'code' => 'dynamic', 'width' => '1');
|
||||
$lang->block->default['full']['my'][] = array('title' => "Team", 'module' => 'teamachievement', 'code' => 'teamachievement', 'width' => '1');
|
||||
$lang->block->default['full']['my'][] = array('title' => "{$lang->productCommon} Overview", 'module' => 'product', 'code' => 'overview', 'width' => '1');
|
||||
if($config->vision != 'lite') $lang->block->default['full']['my'][] = array('title' => "{$lang->productCommon} Overview", 'module' => 'product', 'code' => 'overview', 'width' => '1');
|
||||
$lang->block->default['full']['my'][] = array('title' => "{$lang->projectCommon} Overview", 'module' => 'project', 'code' => 'overview', 'width' => '1');
|
||||
$lang->block->default['full']['my'][] = array('title' => "{$lang->execution->common} Overview", 'module' => 'execution', 'code' => 'overview', 'width' => '1');
|
||||
|
||||
|
||||
@@ -197,12 +197,12 @@ $lang->block->default['full']['my'][] = array('title' => "我近期参与的{$la
|
||||
$lang->block->default['full']['my'][] = array('title' => "未完成的{$lang->projectCommon}列表", 'module' => 'project', 'code' => 'project', 'width' => '2', 'params' => array('type' => 'undone', 'count' => '20', 'orderBy' => 'id_desc'));
|
||||
$lang->block->default['full']['my'][] = array('title' => "未完成的{$lang->execution->common}统计", 'module' => 'execution', 'code' => 'statistic', 'width' => '2', 'params' => array('type' => 'undone', 'count' => '20'));
|
||||
$lang->block->default['full']['my'][] = array('title' => "未完成的{$lang->projectCommon}统计", 'module' => 'project', 'code' => 'statistic', 'width' => '2', 'params' => array('type' => 'undone', 'count' => '20'));
|
||||
$lang->block->default['full']['my'][] = array('title' => "未关闭的{$lang->productCommon}统计", 'module' => 'product', 'code' => 'statistic', 'width' => '2', 'params' => array('type' => 'noclosed', 'count' => '20'));
|
||||
$lang->block->default['full']['my'][] = array('title' => "未关闭{$lang->productCommon}的测试统计", 'module' => 'qa', 'code' => 'statistic', 'width' => '2', 'params' => array('type' => 'noclosed', 'count' => '20'));
|
||||
if($config->vision != 'lite') $lang->block->default['full']['my'][] = array('title' => "未关闭的{$lang->productCommon}统计", 'module' => 'product', 'code' => 'statistic', 'width' => '2', 'params' => array('type' => 'noclosed', 'count' => '20'));
|
||||
if($config->vision != 'lite') $lang->block->default['full']['my'][] = array('title' => "未关闭{$lang->productCommon}的测试统计", 'module' => 'qa', 'code' => 'statistic', 'width' => '2', 'params' => array('type' => 'noclosed', 'count' => '20'));
|
||||
$lang->block->default['full']['my'][] = array('title' => "禅道动态", 'module' => 'zentaodynamic', 'code' => 'zentaodynamic', 'width' => '1');
|
||||
$lang->block->default['full']['my'][] = array('title' => "最新动态", 'module' => 'dynamic', 'code' => 'dynamic', 'width' => '1');
|
||||
$lang->block->default['full']['my'][] = array('title' => "团队成就", 'module' => 'teamachievement', 'code' => 'teamachievement', 'width' => '1');
|
||||
$lang->block->default['full']['my'][] = array('title' => "{$lang->productCommon}总览", 'module' => 'product', 'code' => 'overview', 'width' => '1');
|
||||
if($config->vision != 'lite') $lang->block->default['full']['my'][] = array('title' => "{$lang->productCommon}总览", 'module' => 'product', 'code' => 'overview', 'width' => '1');
|
||||
$lang->block->default['full']['my'][] = array('title' => "{$lang->projectCommon}总览", 'module' => 'project', 'code' => 'overview', 'width' => '1');
|
||||
$lang->block->default['full']['my'][] = array('title' => "{$lang->execution->common}总览", 'module' => 'execution', 'code' => 'overview', 'width' => '1');
|
||||
|
||||
|
||||
+10
-8
@@ -1893,7 +1893,7 @@ class blockZen extends block
|
||||
|
||||
/* 通过度量项获取迭代总量。 */
|
||||
$executionCount = 0;
|
||||
$executionCountGroup = $project ? $this->metric->getResultByCodeWithArray('count_of_execution_in_project', array('project' => $project)) : $this->metric->getResultByCodeWithArray('count_of_execution', array(), 'cron');
|
||||
$executionCountGroup = $project ? $this->metric->getResultByCodeWithArray('count_of_execution_in_project', array('project' => $project)) : $this->metric->getResultByCodeWithArray('count_of_execution', array(), 'cron', null, $this->config->vision);
|
||||
if(!empty($executionCountGroup))
|
||||
{
|
||||
$executionCountGroup = reset($executionCountGroup);
|
||||
@@ -1902,7 +1902,7 @@ class blockZen extends block
|
||||
|
||||
/* 通过度量项获取今年完成的迭代数量。 */
|
||||
$finishedExecution = 0;
|
||||
$finishedExecutionGroup = $project ? $this->metric->getResultByCodeWithArray('count_of_annual_finished_execution_in_project', array('project' => $project, 'year' => date('Y')), 'cron') : $this->metric->getResultByCodeWithArray('count_of_annual_finished_execution', array('year' => date('Y')), 'cron');
|
||||
$finishedExecutionGroup = $project ? $this->metric->getResultByCodeWithArray('count_of_annual_finished_execution_in_project', array('project' => $project, 'year' => date('Y')), 'cron') : $this->metric->getResultByCodeWithArray('count_of_annual_finished_execution', array('year' => date('Y')), 'cron', null, $this->config->vision);
|
||||
if(!empty($finishedExecutionGroup))
|
||||
{
|
||||
$finishedExecutionGroup = reset($finishedExecutionGroup);
|
||||
@@ -1911,7 +1911,7 @@ class blockZen extends block
|
||||
|
||||
/* 通过度量项获取未开始的迭代数量。 */
|
||||
$waitExecution = 0;
|
||||
$waitExecutionGroup = $project ? $this->metric->getResultByCodeWithArray('count_wait_execution_in_project', array('project' => $project), 'cron') : $this->metric->getResultByCodeWithArray('count_of_wait_execution', array(), 'cron');
|
||||
$waitExecutionGroup = $project ? $this->metric->getResultByCodeWithArray('count_wait_execution_in_project', array('project' => $project), 'cron') : $this->metric->getResultByCodeWithArray('count_of_wait_execution', array(), 'cron', null, $this->config->vision);
|
||||
if(!empty($waitExecutionGroup))
|
||||
{
|
||||
$waitExecutionGroup = reset($waitExecutionGroup);
|
||||
@@ -1920,7 +1920,7 @@ class blockZen extends block
|
||||
|
||||
/* 通过度量项获取进行中的迭代数量。 */
|
||||
$doingExecution = 0;
|
||||
$doingExecutionGroup = $project ? $this->metric->getResultByCodeWithArray('count_of_doing_execution_in_project', array('project' => $project), 'cron') : $this->metric->getResultByCodeWithArray('count_of_doing_execution', array(), 'cron');
|
||||
$doingExecutionGroup = $project ? $this->metric->getResultByCodeWithArray('count_of_doing_execution_in_project', array('project' => $project), 'cron') : $this->metric->getResultByCodeWithArray('count_of_doing_execution', array(), 'cron', null, $this->config->vision);
|
||||
if(!empty($doingExecutionGroup))
|
||||
{
|
||||
$doingExecutionGroup = reset($doingExecutionGroup);
|
||||
@@ -1929,7 +1929,7 @@ class blockZen extends block
|
||||
|
||||
/* 通过度量项获取已挂起的迭代数量。 */
|
||||
$suspendedExecution = 0;
|
||||
$suspendedExecutionGroup = $project ? $this->metric->getResultByCodeWithArray('count_of_suspended_execution_in_project', array('project' => $project), 'cron') : $this->metric->getResultByCodeWithArray('count_of_suspended_execution', array(), 'cron');
|
||||
$suspendedExecutionGroup = $project ? $this->metric->getResultByCodeWithArray('count_of_suspended_execution_in_project', array('project' => $project), 'cron') : $this->metric->getResultByCodeWithArray('count_of_suspended_execution', array(), 'cron', null, $this->config->vision);
|
||||
if(!empty($suspendedExecutionGroup))
|
||||
{
|
||||
$suspendedExecutionGroup = reset($suspendedExecutionGroup);
|
||||
@@ -1937,7 +1937,7 @@ class blockZen extends block
|
||||
}
|
||||
|
||||
/* 如果是地盘下的区块跳转到执行列表,如果是单项目下的区块跳转到项目的迭代列表。 */
|
||||
$url = common::hasPriv('execution', 'all') ? helper::createLink('execution', 'all', 'status=all') : null;
|
||||
$url = common::hasPriv('execution', 'all') && $this->config->vision != 'lite' ? helper::createLink('execution', 'all', 'status=all') : null;
|
||||
if($project) $url = common::hasPriv('project', 'execution') ? helper::createLink('project', 'execution', "status=all&projectID=$project") : null;
|
||||
|
||||
/* 组装区块左侧的数据。 */
|
||||
@@ -2086,7 +2086,7 @@ class blockZen extends block
|
||||
}
|
||||
|
||||
$this->view->reviews = $reviews;
|
||||
if($this->config->edition == 'max' or $this->config->edition == 'ipd')
|
||||
if($this->config->edition != 'open')
|
||||
{
|
||||
$this->app->loadLang('approval');
|
||||
$this->view->flows = $this->dao->select('module,name')->from(TABLE_WORKFLOW)->where('buildin')->eq(0)->fetchPairs('module', 'name');
|
||||
@@ -2210,7 +2210,7 @@ class blockZen extends block
|
||||
$this->view->{$objectList[$objectType]} = $objects;
|
||||
}
|
||||
if(isset($hasViewPriv['testcase'])) $this->view->testcases = $this->loadModel('testcase')->getByAssignedTo($this->app->user->account, 'skip|run', 'id_desc', $pager);
|
||||
if(isset($hasViewPriv['testtask'])) $this->view->testtasks = $this->loadModel('testtask')->getByUser($this->app->user->account, $pager, 'id_desc', 'assignedTo');
|
||||
if(isset($hasViewPriv['testtask'])) $this->view->testtasks = $this->loadModel('testtask')->getByUser($this->app->user->account, $pager, 'id_desc', 'wait');
|
||||
|
||||
if(isset($hasViewPriv['meeting']))
|
||||
{
|
||||
@@ -2474,6 +2474,7 @@ class blockZen extends block
|
||||
$stmt = $this->dao->select('id,product,lib,title,type,addedBy,addedDate,editedDate,status,acl,`groups`,users,deleted')->from(TABLE_DOC)->alias('t1')
|
||||
->where('deleted')->eq(0)
|
||||
->andWhere('product')->in($productIdList)
|
||||
->beginIF($this->config->doc->notArticleType)->andWhere('t1.type')->notIN($this->config->doc->notArticleType)->fi()
|
||||
->orderBy('product,status,editedDate_desc')
|
||||
->query();
|
||||
$docGroup = array();
|
||||
@@ -2549,6 +2550,7 @@ class blockZen extends block
|
||||
$stmt = $this->dao->select('t1.id,t1.lib,t1.title,t1.type,t1.addedBy,t1.addedDate,t1.editedDate,t1.status,t1.acl,t1.groups,t1.users,t1.deleted,if(t1.project = 0, t2.project, t1.project) as project')->from(TABLE_DOC)->alias('t1')
|
||||
->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t1.execution=t2.id')
|
||||
->where('t1.deleted')->eq(0)
|
||||
->beginIF($this->config->doc->notArticleType)->andWhere('t1.type')->notIN($this->config->doc->notArticleType)->fi()
|
||||
->andWhere('t2.deleted', true)->eq(0)
|
||||
->orWhere('t2.deleted is null')
|
||||
->markRight(1)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user