diff --git a/framework/base/router.class.php b/framework/base/router.class.php
index edf5628f5a..fda6aeb75c 100644
--- a/framework/base/router.class.php
+++ b/framework/base/router.class.php
@@ -2886,8 +2886,8 @@ class baseRouter
{
if(isset($this->config->zin) || isset($_SERVER['HTTP_X_ZIN_OPTIONS']))
{
- if(!isset($this->zinErrors)) $this->zinErrors = [];
- $this->zinErrors[] = ['file' => $file, 'line' => $line, 'message' => $message];
+ if(!isset($this->zinErrors)) $this->zinErrors = array();
+ $this->zinErrors[] = array('file' => $file, 'line' => $line, 'message' => $message, 'level' => $level);
}
else
{
diff --git a/module/project/test/model/activate.php b/module/project/test/model/activate.php
index 437fa349f5..366c2a505b 100755
--- a/module/project/test/model/activate.php
+++ b/module/project/test/model/activate.php
@@ -1,6 +1,6 @@
#!/usr/bin/env php
activate();
cid=1
pid=1
-激活id为66状态是closed的项目 >> status,closed,doing
-激活id为73状态是suspended的项目 >> status,suspended,doing
+激活id为20的项目 >> object
+激活id为20的项目 >> object
*/
global $tester;
$tester->loadModel('project');
-$changes1 = $tester->project->activate(66);
-$changes2 = $tester->project->activate(73);
+$project = new Project();
+
+$data1 = array(
+ 'id' => 2,
+ 'begin'=> '2023-04-26',
+ 'end'=> '10001-01-07',
+ 'readjustTime'=> 1,
+ 'readjustTask'=> 1,
+ 'status'=> 'doing',
+ 'comment'=> 'sdfsdf'
+);
+
+$data2 = array(
+ 'id' => 3,
+ 'begin'=> '2023-04-26',
+ 'end'=> '10001-01-07',
+ 'readjustTime'=> 1,
+ 'readjustTask'=> 1,
+ 'status'=> 'doing',
+ 'comment'=> 'sdfsdf'
+);
+
+r($project->activate($data1)) && p('1:field,old,new') && e('status,closed,doing'); // 激活id为2状态是closed的项目
+r($project->activate($data2)) && p('1:field,old,new') && e('status,suspended,doing'); // 激活id为3状态是suspended的项目
-r($changes1) && p('1:field,old,new') && e('status,closed,doing'); // 激活id为66状态是closed的项目
-r($changes2) && p('1:field,old,new') && e('status,suspended,doing'); // 激活id为73状态是suspended的项目
diff --git a/module/task/test/tao/computetaskprogress.php b/module/task/test/tao/computetaskprogress.php
new file mode 100755
index 0000000000..d5b6d9966a
--- /dev/null
+++ b/module/task/test/tao/computetaskprogress.php
@@ -0,0 +1,33 @@
+#!/usr/bin/env php
+id->range('1-5');
+ $task->name->prefix('任务')->range('1-5');
+ $task->consumed->range('0,10,15,20,25');
+ $task->left->range('0,0,1,5,10');
+
+ $task->gen(5);
+}
+
+/**
+
+title=测试computeTaskProgress
+cid=1
+
+*/
+initData();
+
+$tester->loadModel('task');
+
+$taskIDList = range(1,5);
+$taskList = $tester->task->getByList($taskIDList);
+
+r($tester->task->computeTaskProgress($taskList[1])) && p() && e('0'); //测试任务消耗工时为0,剩余工时为0的情况
+r($tester->task->computeTaskProgress($taskList[2])) && p() && e('100'); //测试任务消耗工时不为0,剩余工时为0的情况
+r($tester->task->computeTaskProgress($taskList[3])) && p() && e('94'); //测试任务消耗工时为15,剩余工时为1的情况
+r($tester->task->computeTaskProgress($taskList[4])) && p() && e('80'); //测试任务消耗工时为20,剩余工时为5的情况
+r($tester->task->computeTaskProgress($taskList[5])) && p() && e('71'); //测试任务消耗工时为25,剩余工时为10的情况
diff --git a/module/task/test/tao/computetasksprogress.php b/module/task/test/tao/computetasksprogress.php
new file mode 100755
index 0000000000..c06e714000
--- /dev/null
+++ b/module/task/test/tao/computetasksprogress.php
@@ -0,0 +1,32 @@
+#!/usr/bin/env php
+id->range('1-5');
+ $task->name->prefix('任务')->range('1-5');
+ $task->consumed->range('0,10,15,20,25');
+ $task->left->range('0,0,1,5,10');
+
+ $task->gen(5);
+}
+
+/**
+title=测试computeTasksProgress
+cid=2
+
+*/
+$tester->loadModel('task');
+
+initData();
+
+$taskIDList = range(1,5);
+$taskList = $tester->task->getByList($taskIDList);
+
+r($tester->task->computeTasksProgress($taskList)) && p('1:progress') && e('0'); //测试任务消耗工时为0,剩余工时为0的情况
+r($tester->task->computeTasksProgress($taskList)) && p('2:progress') && e('100'); //测试任务消耗工时不为0,剩余工时为0的情况
+r($tester->task->computeTasksProgress($taskList)) && p('3:progress') && e('94'); //测试任务消耗工时为15,剩余工时为1的情况
+r($tester->task->computeTasksProgress($taskList)) && p('4:progress') && e('80'); //测试任务消耗工时为20,剩余工时为5的情况
+r($tester->task->computeTasksProgress($taskList)) && p('5:progress') && e('71'); //测试任务消耗工时为25,剩余工时为10的情况
diff --git a/test/model/project/activate.php b/test/model/project/activate.php
deleted file mode 100755
index 455c4cef16..0000000000
--- a/test/model/project/activate.php
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/usr/bin/env php
-activate();
-cid=1
-pid=1
-
-激活id为66状态是closed的项目 >> status,closed,doing
-激活id为73状态是suspended的项目 >> status,suspended,doing
-
-*/
-
-global $tester;
-$tester->loadModel('project');
-
-$changes1 = $tester->project->activate(66);
-$changes2 = $tester->project->activate(73);
-
-r($changes1) && p('1:field,old,new') && e('status,closed,doing'); // 激活id为66状态是closed的项目
-r($changes2) && p('1:field,old,new') && e('status,suspended,doing'); // 激活id为73状态是suspended的项目
diff --git a/www/js/zui3/zin.js b/www/js/zui3/zin.js
index 625ef3aaeb..cf9d3640c1 100644
--- a/www/js/zui3/zin.js
+++ b/www/js/zui3/zin.js
@@ -1,8 +1,10 @@
-(function(){
+(function()
+{
let DEBUG = true;
const currentCode = window.name.substring(4);
const isInAppTab = parent.window !== window;
const fetchTasks = new Map();
+ const phpErrTypes = {'1': 'error', '2': 'warning', '4': 'parse', '8': 'notice', '16': 'core-error', '32': 'core-warning', '64': 'compile-error', '128': 'compile-warning', '256': 'user-error', '512': 'user-warning', '1024': 'user-notice', '2048': 'strict', '4096': 'recoverable-error', '8192': 'deprecated', '16384': 'user-deprecated', '32767': 'all'};
let currentAppUrl = '';
$.apps = $.extend(
@@ -23,7 +25,7 @@
reloadApp: function(code, url)
{
loadPage(url);
- }
+ },
}, parent.window.$.apps);
const renderMap =
@@ -39,12 +41,86 @@
activeFeature: (data) => activeNav(data, '#featureBar'),
activeMenu: activeNav,
table: updateTable,
- zinErrors: showZinErrors
+ fatal: showFatalError,
+ zinErrors: (data, info, options) => showErrors(data, options.id === 'page'),
};
- function showZinErrors(data)
+ function showFatalError(data, info, options)
{
- if(DEBUG && Array.isArray(data) && data.length) console.log('[ZIN] errors:', data);
+ $('body').empty().append($(`
Fatal error: ${options.url}
`).append($('').append(data)));
+ }
+
+ function initZinbar()
+ {
+ if(!DEBUG) return;
+ let $bar = $('#zinbar');
+ if($bar.length) return;
+
+ $bar = $(
+ [
+ '',
+ '
',
+ '
',
+ '
',
+ '
',
+ '
'
+ ].join('')).insertAfter('body');
+
+ $('#zinErrorList').on('click', '.zin-error-item', function()
+ {
+ const error = $(this).data('error');
+ navigator.clipboard.writeText(`vim +${error.line} ${error.file}`).then(() => {zui.Messager.show({content: 'Copied vim command to clipboard.', type: 'success'});});
+ });
+ }
+
+ function updatePerfInfo(options, stage, error)
+ {
+ console.log('updatePerfInfo', options, stage, error);
+ options[stage] = performance.now();
+ const $perf = options.id === 'page' ? $('#pagePerf') : $('#partPerf');
+ if(stage === 'requestBegin')
+ {
+ $perf.html(`${options.id === 'page' ? 'PAGE' : 'PARTIAL'}
`).append($('loading...
')).attr('title', `Loading from ${options.url}`);
+ }
+ else if(stage === 'requestEnd')
+ {
+ const loadTime = options.requestEnd - options.requestBegin;
+ $perf.find('.zin-perf-load').addClass('font-bold').html(`${loadTime.toFixed(2)}ms`).addClass(loadTime > 400 ? 'text-danger' : (loadTime > 100 ? 'text-warning' : 'text-success')).attr('title', `Load time for ${options.url}`);
+ if(error) showErrors([{message: error.message}]);
+ }
+ else if(stage === 'renderBegin')
+ {
+ $perf.append($('rendering...
')).attr('title', `Renderring ${options.id}`);
+ }
+ else if(stage === 'renderEnd')
+ {
+ const renderTime = options.renderEnd - options.renderBegin;
+ $perf.find('.zin-perf-render').addClass('font-bold').html(` ${renderTime.toFixed(2)}ms`).addClass(renderTime > 200 ? 'text-danger' : (renderTime > 50 ? 'text-warning' : 'text-success')).attr('title', `Render time for ${options.id}`);
+ }
+ }
+
+ function showErrors(data, clear)
+ {
+ if(!DEBUG) return;
+
+ const $count = $('#zinErrors').empty();
+ const $list = $('#zinErrorList');
+ if(clear) $list.empty();
+ data.forEach((error) =>
+ {
+ const phpErrType = phpErrTypes[error.level] || 'info';
+ const errType = phpErrType.includes('error') ? 'error' : (phpErrType.includes('warning') ? 'warning' : 'info');
+ $(`${phpErrType} ${error.message}
vim +${error.line} ${error.file}
`).data('error', error).appendTo($list);
+ });
+
+ const warnings = $list.find('.zin-error-item.warning-pale').length;
+ const errors = $list.find('.zin-error-item.error-pale').length;
+ const infos = $list.find('.zin-error-item.info-pale').length;
+ if(errors) $count.append(`ERRO ${errors}`);
+ if(warnings) $count.append(`WARN ${warnings}`);
+ if(infos) $count.append(`INFO ${infos}`);
+
+ if(clear && (warnings || errors || infos)) $list.addClass('in');
}
function updatePageWithHtml(data)
@@ -90,12 +166,12 @@
dtable.render(props);
}
- function renderPartial(info)
+ function renderPartial(info, options)
{
if(window.config.onRenderPage && window.config.onRenderPage(info)) return;
const render = renderMap[info.name];
- if(render) return render(info.data);
+ if(render) return render(info.data, info, options);
/* Common render */
const selector = parseSelector(info.selector);
@@ -132,10 +208,10 @@
else $target.replaceWith(info.data);
}
- function renderPage(list)
+ function renderPage(list, options)
{
if(DEBUG) console.log('[APP] ', 'render:', list);
- list.forEach(renderPartial);
+ list.forEach(item => renderPartial(item, options));
$.apps.updateApp(currentCode, currentAppUrl, document.title);
}
@@ -172,23 +248,37 @@
const target = options.target || '#main';
const selectors = Array.isArray(options.selectors) ? options.selectors : options.selectors.split(',');
const url = options.url;
+
+ if(DEBUG) console.log('[APP]', 'request', options);
+ if(DEBUG && !selectors.includes('zinErrors()')) selectors.push('zinErrors()');
+ const isDebugRequest = DEBUG && selectors.length !== 1 || selectors[0] !== 'zinErrors()';
return $.ajax(
{
url: url,
headers: {'X-ZIN-Options': JSON.stringify($.extend({selector: selectors, type: 'list'}, options.zinOptions)), 'X-ZIN-App': currentCode},
- beforeSend: () => toggleLoading(target, true),
+ beforeSend: () =>
+ {
+ updatePerfInfo(options, 'requestBegin');
+ if(!isDebugRequest) return;
+ toggleLoading();
+ },
success: (data) =>
{
- try{data = JSON.parse(data);}catch(e){data = [{name: 'html', data: data}];}
+ updatePerfInfo(options, 'requestEnd');
+ options.result = 'success';
+ try{data = JSON.parse(data);}catch(e){data = [{name: data.includes('Fatal error') ? 'fatal' : 'html', data: data}];}
if(options.updateUrl !== false) currentAppUrl = url;
data.forEach((item, idx) => item.selector = selectors[idx]);
- renderPage(data);
+ updatePerfInfo(options, 'renderBegin');
+ renderPage(data, options);
+ updatePerfInfo(options, 'renderEnd');
$(document).trigger('pagerender.app');
if(options.success) options.success(data);
if(onFinish) onFinish(null, data);
},
error: (xhr, type, error) =>
{
+ updatePerfInfo(options, 'requestEnd', error);
if(type === 'abort') return console.log('[ZIN] ', 'Abord fetch data from ' + url, {xhr, type, error});;
if(DEBUG) console.error('[ZIN] ', 'Fetch data failed from ' + url, {xhr, type, error});
zui.Messager.show('ZIN: Fetch data failed from ' + url);
@@ -262,7 +352,6 @@
if(!selector)
{
selector = ($('#main').length ? '#main>*,#pageCSS>*,#pageJS,#configJS>*,title>*,activeMenu()' : 'body>*,title>*');
- if(DEBUG) selector += ',zinErrors()';
}
fetchContent(url, selector, id);
}
@@ -426,7 +515,10 @@
$(() =>
{
+ initZinbar();
+
if(window.defaultAppUrl) loadPage(window.defaultAppUrl);
+ else if(DEBUG) loadCurrentPage('zinErrors()');
DEBUG = window.config.debug;
diff --git a/zin/core/dom.class.php b/zin/core/dom.class.php
index 4d13506cab..3949c5f162 100644
--- a/zin/core/dom.class.php
+++ b/zin/core/dom.class.php
@@ -123,6 +123,8 @@ class dom
*/
public function build()
{
+ if(empty($this->selectors) && !empty($this->dataCommands)) return [];
+
$list = [];
$children = $this->renderInner ? $this->wg->children() : $this->children;
@@ -149,9 +151,7 @@ class dom
public function renderJson()
{
- $list = $this->build();
- if(empty($list)) return '{}';
-
+ $list = $this->build();
$output = [];
foreach($list as $name => $item)
{
@@ -188,9 +188,7 @@ class dom
public function renderList()
{
- $list = $this->build();
- if(empty($list)) return '[]';
-
+ $list = $this->build();
$output = [];
foreach($list as $name => $item)
{