This commit is contained in:
sunguangming
2023-04-26 18:55:13 +08:00
7 changed files with 203 additions and 52 deletions
+2 -2
View File
@@ -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
{
+27 -7
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env php
<?php
include dirname(__FILE__, 5) . "/test/lib/init.php";
include dirname(dirname(dirname(__FILE__))) . '/lib/init.php';
su('admin');
/**
@@ -9,16 +9,36 @@ title=测试 projectModel->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的项目
+33
View File
@@ -0,0 +1,33 @@
#!/usr/bin/env php
<?php
include dirname(__FILE__, 5) . "/test/lib/init.php";
function initData()
{
$task = zdTable('task');
$task->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的情况
+32
View File
@@ -0,0 +1,32 @@
#!/usr/bin/env php
<?php
include dirname(__FILE__, 5) . "/test/lib/init.php";
function initData()
{
$task = zdTable('task');
$task->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的情况
-24
View File
@@ -1,24 +0,0 @@
#!/usr/bin/env php
<?php
include dirname(dirname(dirname(__FILE__))) . '/lib/init.php';
su('admin');
/**
title=测试 projectModel->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的项目
+105 -13
View File
@@ -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($(`<div class="panel danger shadow-xl mx-auto my-4 rounded-lg" style="max-width: 1000px"><div class="panel-heading"><div class="panel-title font-bold text-lg">Fatal error: ${options.url}</div></div></div>`).append($('<div class="panel-body mono"></div>').append(data)));
}
function initZinbar()
{
if(!DEBUG) return;
let $bar = $('#zinbar');
if($bar.length) return;
$bar = $(
[
'<div id="zinbar" class="shadow-xl h-5 bg-inverse text-canvas fixed row items-center text-base" style="right:0;bottom:0;z-index: 99999">',
'<div id="zinErrors" class="mono row items-center"></div>',
'<div id="pagePerf" class="row items-center"></div>',
'<div id="partPerf" class="row items-center"></div>',
'<div id="zinErrorList" class="mono absolute shadow-xl ring rounded fade-from-right" style="bottom:100%;right:0;margin-bottom:1px"></div>',
'</div>'
].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(`<div class="opacity-50 pl-2">${options.id === 'page' ? 'PAGE' : 'PARTIAL'}</div>`).append($('<div class="px-2 zin-perf-load">loading...</div>')).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(`<i class="icon icon-arrow-down"></i>${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($('<div class="px-2 zin-perf-render">rendering...</div>')).attr('title', `Renderring ${options.id}`);
}
else if(stage === 'renderEnd')
{
const renderTime = options.renderEnd - options.renderBegin;
$perf.find('.zin-perf-render').addClass('font-bold').html(`<i class="icon icon-${renderTime > 200 ? 'frown' : (renderTime > 50 ? 'meh' : 'smile')}"></i> ${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');
$(`<div class="zin-error-item ${errType}-pale text-fore px-2 py-1 ring ring-darker" style=""><div class="zin-error-msg font-bold"><strong class="text-${errType}" style="text-transform: uppercase;">${phpErrType}</strong> ${error.message}</div><div class="zin-error-info ellipsis text-sm opacity-60"><strong>vim +${error.line}</strong> ${error.file}</div></div>`).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(`<span class="error font-bold px-2">ERRO ${errors}</span>`);
if(warnings) $count.append(`<span class="warning font-bold px-2">WARN ${warnings}</span>`);
if(infos) $count.append(`<span class="info font-bold px-2">INFO ${infos}</span>`);
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;
+4 -6
View File
@@ -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)
{