$(function(){ // ============ 节点叠加层状态(模块级) ============ // currentNodeId: 当前选中的节点 ID;为空表示纯配置视图(配置基础层) // nodeMap: { node_id: {node_id, is_master, ip_address, last_heartbeat_at} } // onlineNodeCount: 在线节点总数(用于 all 模式全网预估提示) var currentNodeId = ''; var nodeMap = {}; var onlineNodeCount = 0; var tableId = init.tableRenderId; // ============ 归属可视化(D19 + run_type + 主节点判断)============ // 入参:run_type、status、当前选中节点是否主节点 // 返回:{text, class} 用于渲染徽章 function calcOwnership(runType, status, isMaster) { if (status == 0) { return { text: '已停用', cls: 'layui-badge layui-bg-gray' }; } switch (runType) { case 'all': // 所有节点都跑(含当前节点) return { text: '当前自动', cls: 'layui-badge layui-bg-green' }; case 'main': if (isMaster == 1) { return { text: '当前(主)自动', cls: 'layui-badge layui-bg-green' }; } // 非主节点:此任务不会在当前节点调度 return { text: '仅主节点', cls: 'layui-badge layui-bg-orange' }; case 'auto': // 多节点竞争执行(谁抢到谁跑) return { text: '竞争', cls: 'layui-badge layui-bg-blue' }; case 'manual': // 仅手动触发 return { text: '仅手动', cls: 'layui-badge layui-bg-orange' }; default: return { text: runType || '-', cls: 'layui-badge' }; } } // ============ 加载节点列表(hostList 接口)============ function loadNodeList(callback) { ua.request.get({ url: ua.url('system.timer_config/hostList'), prefix: true, }, function (res) { var list = (res && res.data) ? res.data : []; onlineNodeCount = list.length; nodeMap = {}; var $sel = $('#node_filter'); // 保留首项(全局配置) $sel.find('option').not(':first').remove(); $.each(list, function (i, item) { nodeMap[item.node_id] = item; var masterTag = item.is_master == 1 ? ' [主]' : ''; var ip = item.ip_address || ''; $sel.append(''); }); // 重新渲染 select if (window.layui && layui.form) { layui.form.render('select', 'timerNodeFilter'); } if (typeof callback === 'function') { callback(list); } }, function (res) { // hostList 失败:保持纯配置视图(不阻塞页面) $('#node_hint').attr('class', 'layui-badge').text('节点列表加载失败:' + (res && res.msg ? res.msg : '未知错误')).show(); }); } // ============ 更新节点 hint 提示 ============ function updateNodeHint() { var $hint = $('#node_hint'); if (!currentNodeId) { $hint.hide(); return; } var node = nodeMap[currentNodeId]; if (!node) { $hint.hide(); return; } var master = node.is_master == 1 ? '主节点' : '非主节点'; var ip = node.ip_address || ''; $hint.attr('class', 'layui-badge layui-bg-blue') .text('当前视角:' + node.node_id + ' / ' + master + (ip ? ' / ' + ip : '')) .show(); } // ============ 表格 cols(基础配置层 + 节点叠加列始终在,按节点上下文渲染)============ var cols = [[ { type: 'checkbox' }, { field: 'id', title: 'ID', width: 80 }, { field: 'task_name', title: '任务名称', minWidth: 180 }, { field: 'run_type', search: 'select', selectList: ua.getDataBrage('select_list_run_type'), title: '运行类型', width: 110, templet: function (d) { var map = ua.getDataBrage('select_list_run_type'); return map[d.run_type] || d.run_type; } }, { field: 'status', search: 'select', selectList: ua.getDataBrage('select_list_status'), title: '状态', width: 90, templet: ua.table.switch }, // 并发数:NULL=继承代码默认;正整数=覆盖;行内可改(edit:'text' → modify) { field: 'concurrency', title: '并发', width: 110, align: 'center', search: false, edit: 'text', templet: function (d) { if (d.concurrency === null || d.concurrency === undefined || d.concurrency === '') { return '默认'; } return d.concurrency; } }, // 手动触发目标节点(只读展示) { field: 'trigger_node_id', title: '触发节点', width: 140, search: false, templet: function (d) { if (!d.trigger_node_id) { return '-'; } return d.trigger_node_id; } }, // 归属列(节点叠加):无选中节点时显示"全局"占位,有节点时按 run_type+主节点算 { field: '_ownership', title: '归属', width: 130, align: 'center', search: false, templet: function (d) { if (!currentNodeId) { return '全局'; } var node = nodeMap[currentNodeId]; var isMaster = node ? node.is_master : 0; var info = calcOwnership(d.run_type, d.status, isMaster); return '' + info.text + ''; } }, { field: 'is_synced', search: 'select', selectList: ua.getDataBrage('select_list_is_synced'), title: '同步状态', width: 100, templet: function (d) { if (d.is_synced == 1) { return '已同步'; } return '未同步'; } }, { field: 'last_execute_node', title: '最后执行节点', width: 140, search: false }, { field: 'last_execute_time', title: '最后执行时间', width: 170, search: 'range', templet: function (d) { if (!d.last_execute_time || d.last_execute_time == 0) { return '暂无'; } var date = new Date(d.last_execute_time * 1000); var Y = date.getFullYear(); var m = ('0' + (date.getMonth() + 1)).slice(-2); var day = ('0' + date.getDate()).slice(-2); var H = ('0' + date.getHours()).slice(-2); var i = ('0' + date.getMinutes()).slice(-2); var s = ('0' + date.getSeconds()).slice(-2); return Y + '-' + m + '-' + day + ' ' + H + ':' + i + ':' + s; } }, { width: 250, title: '操作', templet: ua.table.tool, fixed: 'right', operat: [ [{ class: 'layui-btn layui-btn-primary layui-btn-xs', method: 'tab', field: 'id', text: '详情', title: '查看详情', auth: 'read', url: init.readUrl, icon: '' }], 'edit', [{ // 手动触发:定向选中节点(method:none,自处理 click + POST body 带 trigger_node_id) // D19:跨 run_type、穿透 status(调试工作流) class: 'layui-btn layui-btn-warm layui-btn-xs timer-trigger-btn', method: 'none', field: 'id', text: '触发', title: '手动触发到选中节点', auth: 'trigger', url: init.triggerUrl, icon: '', extend: '', // 节点叠加层启用:必须选中节点才显示触发按钮 _if: function () { return currentNodeId !== ''; } }] ] }, ]]; // ============ 初始化表格(配置基础层,永远渲染)============ ua.table.render({ init: init, cols: cols, }); ua.listen(); // ============ 行内编辑(concurrency)校验 + all 模式预估提示 ============ // ua.table 已通过 listenEdit 绑定 edit 事件并 POST 到 modifyUrl; // 这里挂钩 layui table edit 事件做前置校验 + all 模式预估提示 if (window.layui && layui.table) { layui.table.on('edit(' + tableId + '_LayFilter)', function (obj) { if (obj.field === 'concurrency') { var val = obj.value; // 空:行内编辑不允许置空(继承默认请走 edit 表单),modify 接口也会拒绝 if (val === '' || !/^[1-9]\d*$/.test(val)) { ua.msg.error('concurrency 必须是正整数(继承默认请走编辑表单)'); layui.table.reloadData(tableId); return false; } // all 模式预估提示:全网并发 = 在线节点数 × 此值 var rowData = obj.data; if (rowData.run_type === 'all' && onlineNodeCount > 0) { var total = onlineNodeCount * parseInt(val, 10); setTimeout(function () { layer.tips( 'all 模式:全网预估 = ' + onlineNodeCount + ' 节点 × ' + val + ' = ' + total + ' 并发', '.layui-table-edit-tips', { tips: [2, '#FF5722'], time: 6000 } ); }, 100); } } }); } // ============ 手动触发按钮(定向选中节点)============ // method:'none' 不绑定默认行为,自处理 click $(document).on('click', '.timer-trigger-btn', function (e) { e.preventDefault(); var taskId = $(this).attr('data-id'); if (!taskId) { ua.msg.error('未找到任务 ID'); return; } if (!currentNodeId) { // 兜底(_if 已阻止渲染,但用户切节点后表格未 reload 时仍可能进入) ua.msg.error('请先在顶部选择要触发的节点'); return; } var node = nodeMap[currentNodeId] || {}; var master = node.is_master == 1 ? '主节点' : '非主节点'; var ip = node.ip_address || ''; ua.msg.confirm( '确定将此任务触发到节点 [' + currentNodeId + ' / ' + master + (ip ? ' / ' + ip : '') + '] 执行?\n(跨 run_type、穿透 status)', function () { ua.request.post({ url: ua.url(init.triggerUrl + '?id=' + taskId), prefix: true, data: { trigger_node_id: currentNodeId }, }, function (res) { ua.msg.success(res.msg || '触发成功,等待定时器执行', function () { layui.table.reloadData(tableId); }); }); } ); }); // ============ 节点选择器联动 ============ if (window.layui && layui.form) { layui.form.on('select(node_filter)', function (data) { currentNodeId = data.value || ''; updateNodeHint(); // 通知后端 reload(后端可选记录 node 上下文;归属列由前端 JS 算,不依赖后端) layui.table.reloadData(tableId, { where: { node_id: currentNodeId } }); }); } // ============ 首次加载节点列表(异步,不阻塞表格渲染)============ loadNodeList(); });