邹景立 11377b6761 style(queue): 完善队列进度展示
队列执行弹窗增加状态点、进度文字、任务状态标记和更大的日志面板,并对日志内容进行 HTML 转义,避免任务输出影响页面结构。

优化进度条和日志区域样式,支持成功、执行中和失败状态的不同视觉反馈,同时保留自动滚动查看最新日志。

为队列表格时间与耗时格式化增加前端兜底函数,修正脚本中转义后的  变量输出,并更新测试确保生成脚本可直接执行。
2026-05-21 00:17:01 +08:00

181 lines
12 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// +----------------------------------------------------------------------
// | 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 {'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'}[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);
});