mirror of
https://gitee.com/zoujingli/ThinkAdmin.git
synced 2026-06-06 20:18:10 +08:00
队列执行弹窗增加状态点、进度文字、任务状态标记和更大的日志面板,并对日志内容进行 HTML 转义,避免任务输出影响页面结构。 优化进度条和日志区域样式,支持成功、执行中和失败状态的不同视觉反馈,同时保留自动滚动查看最新日志。 为队列表格时间与耗时格式化增加前端兜底函数,修正脚本中转义后的 变量输出,并更新测试确保生成脚本可直接执行。
181 lines
12 KiB
PHP
181 lines
12 KiB
PHP
// +----------------------------------------------------------------------
|
||
// | Static Plugin for ThinkAdmin
|
||
// +----------------------------------------------------------------------
|
||
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
|
||
// +----------------------------------------------------------------------
|
||
// | 官方网站: https://thinkadmin.top
|
||
// +----------------------------------------------------------------------
|
||
// | 开源协议 ( https://mit-license.org )
|
||
// | 免责声明 ( https://thinkadmin.top/disclaimer )
|
||
// +----------------------------------------------------------------------
|
||
// | gitee 代码仓库:https://gitee.com/zoujingli/think-plugs-static
|
||
// | github 代码仓库:https://github.com/zoujingli/think-plugs-static
|
||
// +----------------------------------------------------------------------
|
||
|
||
layui.define(function (exports) {
|
||
|
||
let template = [
|
||
'<div class="ta-queue-box" data-queue-state="0" data-queue-load="{{d.code}}">',
|
||
' <div class="ta-queue-status">',
|
||
' <div class="ta-queue-status-main">',
|
||
' <span class="ta-queue-status-dot"></span>',
|
||
' <div class="ta-queue-status-title layui-elip notselect nowrap" data-message-title><b class="color-desc">...</b></div>',
|
||
' </div>',
|
||
' <div class="ta-queue-status-code"># {{d.code}}</div>',
|
||
' </div>',
|
||
' <div class="ta-queue-progress-meta"><span>执行进度</span><b data-progress-text>0.00%</b></div>',
|
||
' <div class="layui-progress layui-progress-big ta-queue-progress"><div class="layui-progress-bar transition layui-bg-blue" lay-percent="0.00%"></div></div>',
|
||
' <div class="ta-queue-log">',
|
||
' <div class="ta-queue-log-head"><span>执行日志</span><small>实时刷新</small></div>',
|
||
' <code></code>',
|
||
' </div>',
|
||
'</div>'
|
||
].join('');
|
||
|
||
function injectStyle() {
|
||
if (document.getElementById('ta-queue-style')) return;
|
||
let style = document.createElement('style');
|
||
style.id = 'ta-queue-style';
|
||
style.innerHTML = [
|
||
'@keyframes taQueuePulse{0%{box-shadow:0 0 0 0 rgba(59,130,246,.28);}70%{box-shadow:0 0 0 8px rgba(59,130,246,0);}100%{box-shadow:0 0 0 0 rgba(59,130,246,0);}}',
|
||
'.ta-queue-layer{border-radius:12px;overflow:hidden;box-shadow:0 18px 45px rgba(15,23,42,.22);}',
|
||
'.ta-queue-layer .layui-layer-title{height:50px;line-height:50px;padding:0 50px 0 18px;color:#1f2937;font-size:15px;font-weight:700;border-bottom:1px solid var(--ta-border-color,#edf0f5);background:linear-gradient(180deg,#fff,#f8fbfc);}',
|
||
'.ta-queue-layer .layui-layer-setwin{top:17px;right:16px;}',
|
||
'.ta-queue-layer .layui-layer-content{overflow:hidden;background:var(--ta-body-bg,#f4f7fb);}',
|
||
'.ta-queue-box{height:100%;min-height:0;box-sizing:border-box;display:flex;flex-direction:column;padding:18px 20px 20px;background:var(--ta-body-bg,#f4f7fb);}',
|
||
'.ta-queue-status{min-height:50px;flex-shrink:0;display:flex;align-items:center;justify-content:space-between;gap:14px;box-sizing:border-box;padding:12px 14px;background:var(--ta-surface,#fff);border:1px solid var(--ta-border-color,#edf0f5);border-radius:12px;box-shadow:0 8px 22px rgba(15,35,60,.06);}',
|
||
'.ta-queue-status-main{display:flex;align-items:center;gap:10px;min-width:0;flex:1;}',
|
||
'.ta-queue-status-dot{width:8px;height:8px;display:inline-block;flex:0 0 8px;border-radius:50%;background:#94a3b8;}',
|
||
'.ta-queue-status-title{min-width:0;flex:1;color:#334155;font-size:13px;font-weight:600;text-align:left!important;}',
|
||
'.ta-queue-status-title b{font-weight:700;}',
|
||
'.ta-queue-status-code{flex-shrink:0;max-width:220px;padding:2px 8px;color:#8c98a8;font-size:12px;line-height:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;border-radius:999px;background:#f3f6fa;}',
|
||
'.ta-queue-box[data-queue-state="1"] .ta-queue-status-dot,.ta-queue-box[data-queue-state="2"] .ta-queue-status-dot{background:#3b82f6;animation:taQueuePulse 1.6s ease-out infinite;}',
|
||
'.ta-queue-box[data-queue-state="3"] .ta-queue-status{border-color:#b7ebc6;background:linear-gradient(135deg,#f6ffed,#fff);}',
|
||
'.ta-queue-box[data-queue-state="3"] .ta-queue-status-dot{background:#16a34a;box-shadow:0 0 0 5px rgba(22,163,74,.12);}',
|
||
'.ta-queue-box[data-queue-state="4"] .ta-queue-status{border-color:#ffd6d6;background:linear-gradient(135deg,#fff5f5,#fff);}',
|
||
'.ta-queue-box[data-queue-state="4"] .ta-queue-status-dot{background:#ff4d4f;box-shadow:0 0 0 5px rgba(255,77,79,.12);}',
|
||
'.ta-queue-box[data-queue-state="4"] .ta-queue-status-title,.ta-queue-box[data-queue-state="4"] .ta-queue-status-title b{color:#ff4d4f!important;}',
|
||
'.ta-queue-progress-meta{flex-shrink:0;display:flex;align-items:center;justify-content:space-between;margin:12px 2px 6px;color:#7b8794;font-size:12px;line-height:18px;}',
|
||
'.ta-queue-progress-meta b{color:#475569;font-size:12px;font-weight:600;}',
|
||
'.ta-queue-progress{flex-shrink:0;height:10px;border-radius:999px;background:#e7edf5;overflow:hidden;}',
|
||
'.ta-queue-progress .layui-progress-bar{height:10px;border-radius:999px;box-shadow:0 2px 6px rgba(30,64,175,.22);}',
|
||
'.ta-queue-log{flex:1 1 auto;min-height:230px;display:flex;flex-direction:column;margin-top:14px;border-radius:12px;background:#202a33;border:1px solid #19232c;overflow:hidden;box-shadow:inset 0 0 0 1px rgba(255,255,255,.02),0 10px 24px rgba(15,23,42,.12);}',
|
||
'.ta-queue-log-head{height:38px;flex:0 0 38px;display:flex;align-items:center;justify-content:space-between;padding:0 14px;background:#15202a;color:#e5edf5;font-size:13px;font-weight:600;}',
|
||
'.ta-queue-log-head small{font-size:12px;font-weight:400;color:#94a3b8;}',
|
||
'.ta-queue-log code{flex:1 1 auto;display:block;height:auto;min-height:0;margin:0;padding:12px 14px;border:0;box-sizing:border-box;background:#202a33;color:#e5edf5;font-family:Consolas,Monaco,Menlo,monospace;font-size:12px;line-height:22px;white-space:normal;overflow:auto;}',
|
||
'.ta-queue-log code p{min-height:22px;height:auto;line-height:22px;margin:0;color:#e5edf5;overflow:visible;text-overflow:clip;white-space:pre-wrap;word-break:break-word;}',
|
||
'.ta-queue-log code .color-desc{color:#94a3b8!important;}'
|
||
].join('');
|
||
document.head.appendChild(style);
|
||
}
|
||
|
||
function escapeHtml(value) {
|
||
return String(value).replace(/[&<>"']/g, function (char) {
|
||
return {'&': '&', '<': '<', '>': '>', '"': '"', "'": '''}[char];
|
||
});
|
||
}
|
||
|
||
function Queue(code, doScript, element) {
|
||
let queue = this;
|
||
injectStyle();
|
||
(this.doAjax = true) && (this.doReload = false) || layer.open({
|
||
type: 1, title: '任务执行进度', area: ['640px', '520px'], skin: 'ta-queue-layer', anim: 2, shadeClose: false, end: function () {
|
||
queue.doAjax = queue.doReload && doScript && $.layTable.reload(((element || {}).dataset || {}).tableId || true) && false;
|
||
}, content: laytpl(template).render({code: code}), success: function ($elem) {
|
||
new Progress($elem, code, queue, doScript);
|
||
}
|
||
});
|
||
}
|
||
|
||
function Progress($elem, code, queue, doScript) {
|
||
let that = this;
|
||
|
||
this.$box = $elem.find('[data-queue-load=' + code + ']');
|
||
if (queue.doAjax === false || this.$box.length < 1) return false;
|
||
|
||
this.$code = this.$box.find('code');
|
||
this.$title = this.$box.find('[data-message-title]');
|
||
this.$progressText = this.$box.find('[data-progress-text]');
|
||
this.$percent = this.$box.find('.layui-progress div');
|
||
|
||
// 设置数据缓存
|
||
this.SetCache = function (code, index, value) {
|
||
let ckey = code + '_' + index, ctype = 'admin-queue-script';
|
||
return value !== undefined ? layui.data(ctype, {key: ckey, value: value}) : layui.data(ctype)[ckey] || 0;
|
||
};
|
||
|
||
// 更新任务显示状态
|
||
this.SetState = function (status, message) {
|
||
that.$box.attr('data-queue-state', status);
|
||
if (message.indexOf('javascript:') === -1) if (status === 1) {
|
||
that.$title.html('<b class="color-text">' + message + '</b>').addClass('text-center');
|
||
that.$percent.addClass('layui-bg-blue').removeClass('layui-bg-green layui-bg-red');
|
||
} else if (status === 2) {
|
||
if (message.indexOf('>>>') > -1) {
|
||
that.$title.html('<b class="color-blue">' + message + '</b>').addClass('text-center');
|
||
} else {
|
||
that.$title.html('<b class="color-blue">正在处理:</b>' + message).removeClass('text-center');
|
||
}
|
||
that.$percent.addClass('layui-bg-blue').removeClass('layui-bg-green layui-bg-red');
|
||
} else if (status === 3) {
|
||
queue.doReload = true;
|
||
that.$title.html('<b class="color-green">' + message + '</b>').addClass('text-center');
|
||
that.$percent.addClass('layui-bg-green').removeClass('layui-bg-blue layui-bg-red');
|
||
} else if (status === 4) {
|
||
that.$title.html('<b class="color-red">' + message + '</b>').addClass('text-center');
|
||
that.$percent.addClass('layui-bg-red').removeClass('layui-bg-blue layui-bg-green');
|
||
}
|
||
};
|
||
|
||
// 读取任务进度信息
|
||
this.LoadProgress = function () {
|
||
if (queue.doAjax === false || that.$box.length < 1) return false;
|
||
$.form.load(tapiRoot + '/queue/progress', {code: code}, 'post', function (ret) {
|
||
if (parseInt(ret.code, 10) === 200) {
|
||
let data = ret && typeof ret.data === 'object' && ret.data ? ret.data : {};
|
||
let status = parseInt(data.status || '0');
|
||
let progress = parseFloat(data.progress || '0.00');
|
||
let message = typeof data.message === 'string' && data.message.length ? data.message : '>>> 等待任务状态更新 <<<';
|
||
let history = Array.isArray(data.history) ? data.history : [];
|
||
let lines = [];
|
||
for (let idx in history) {
|
||
let line = history[idx] || {}, text = String(line.message || ''), percent = '[ ' + (line.progress || '0.00') + '% ] ';
|
||
if (!text.length) {
|
||
continue;
|
||
}
|
||
if (text.indexOf('javascript:') === -1) {
|
||
lines.push(escapeHtml(text.indexOf('>>>') > -1 ? text : percent + text));
|
||
} else if (!that.SetCache(code, idx) && doScript !== false) {
|
||
that.SetCache(code, idx, 1)
|
||
$.form.goto(text);
|
||
}
|
||
}
|
||
if (!isFinite(progress)) {
|
||
progress = 0;
|
||
}
|
||
progress = Math.max(0, Math.min(100, progress));
|
||
let percentText = progress.toFixed(2) + '%';
|
||
that.$code.html(lines.length ? '<p>' + lines.join('</p><p>') + '</p>' : '<p class="color-desc">暂无执行日志</p>').animate({scrollTop: that.$code[0].scrollHeight + 'px'}, 200);
|
||
that.$progressText.text(percentText);
|
||
if (status > 0) {
|
||
that.SetState(status, message);
|
||
that.$percent.attr('lay-percent', percentText) && layui.element.render();
|
||
status === 3 || status === 4 || setTimeout(that.LoadProgress, Math.floor(Math.random() * 200));
|
||
} else {
|
||
that.$box.attr('data-queue-state', '0');
|
||
that.$title.html('<b class="color-desc">' + message + '</b>').addClass('text-center');
|
||
that.$percent.attr('lay-percent', percentText) && layui.element.render();
|
||
setTimeout(that.LoadProgress, Math.floor(Math.random() * 500) + 200);
|
||
}
|
||
return false;
|
||
}
|
||
}, false);
|
||
};
|
||
|
||
// 首页加载进度信息
|
||
this.LoadProgress();
|
||
}
|
||
|
||
exports('taQueue', Queue);
|
||
});
|