diff --git a/static/app.js b/static/app.js index 3cf208e..c85d6be 100644 --- a/static/app.js +++ b/static/app.js @@ -102,10 +102,13 @@ function mdToHtml(md) { /* ---------- Markdown 区块(带 原文/Markdown 切换,默认 Markdown) ---------- */ const mdRaw = {}; // id -> {md: bool, text: string} -function mdBlock(id, text, cls = '') { +function mdToggleBtn(id) { + // 切换开关:放入区块标题行右侧(由调用方放到 .sec-title 内) + return ``; +} +function mdBody(id, text, cls = '') { mdRaw[id] = {md: true, text: text || ''}; - return `
-
${mdToHtml(text)}
`; + return `
${mdToHtml(text)}
`; } function mdToggle(id) { const rec = mdRaw[id]; @@ -1436,10 +1439,10 @@ async function openTaskDetail(tid) {
创建/更新
${fmtTime(t.created_at)} / ${fmtTime(t.updated_at)}
截止
${t.deadline || '—'}
-
任务指令
- ${mdBlock('td-desc-' + t.id, t.description || t.title, 'sm')} - ${t.output_text ? `
产出物 v${t.output_version}(${t.output_text.length} 字)
- ${mdBlock('td-out-' + t.id, t.output_text)}` : ''} +
任务指令${mdToggleBtn('td-desc-' + t.id)}
+ ${mdBody('td-desc-' + t.id, t.description || t.title, 'sm')} + ${t.output_text ? `
产出物 v${t.output_version}(${t.output_text.length} 字)${mdToggleBtn('td-out-' + t.id)}
+ ${mdBody('td-out-' + t.id, t.output_text)}` : ''} ${t.error ? `
错误信息
${esc(t.error)}
` : ''}
执行日志
@@ -1839,6 +1842,7 @@ async function pageReports() { /* ---------- 运行日志 ---------- */ let logsState = {page: 1, pageSize: 50, level: '', q: ''}; +window.logsState = logsState; // 供分页组件通过 window[stateVar] 读写 async function pageLogs() { const st = logsState; @@ -1861,9 +1865,10 @@ async function pageLogs() { ${st.q ? `` : ''} + ${pagerHtml(r.page, totalPages, r.total, 'logsState', 'pageLogs', 'top')}
${r.data.map(l => `
${fmtTime(l.created_at)} [任务#${l.task_id} ${esc(l.task_title || '')}] ${esc(l.message)}
`).join('') || '
暂无日志
'}
- ${pagerHtml(r.page, totalPages, r.total, 'logsState', 'pageLogs')}`; + ${pagerHtml(r.page, totalPages, r.total, 'logsState', 'pageLogs', 'bottom')}`; bindPagerGo('logsState', totalPages, 'pageLogs'); const qel = $('#logs-q'); if (qel) qel.addEventListener('keydown', e => { if (e.key === 'Enter') { logsState.q = qel.value.trim(); logsState.page = 1; pageLogs(); } }); @@ -1871,6 +1876,7 @@ async function pageLogs() { /* ---------- 告警中心 ---------- */ let alertsState = {page: 1, pageSize: 20, level: '', type: '', read: ''}; +window.alertsState = alertsState; // 供分页组件通过 window[stateVar] 读写 async function pageAlerts() { const st = alertsState; @@ -1901,13 +1907,14 @@ async function pageAlerts() {
+ ${pagerHtml(r.page, totalPages, r.total, 'alertsState', 'pageAlerts', 'top')}
${r.data.map(a => `
${ALERT_LEVEL[a.level] || 'ℹ️'} ${esc(a.title)} ${a.read ? '' : '未读'}
${esc(a.detail)}
${fmtTime(a.created_at)} · ${esc(a.type)}
`).join('') || '
暂无告警 🎉
'}
- ${pagerHtml(r.page, totalPages, r.total, 'alertsState', 'pageAlerts')}`; + ${pagerHtml(r.page, totalPages, r.total, 'alertsState', 'pageAlerts', 'bottom')}`; bindPagerGo('alertsState', totalPages, 'pageAlerts'); refreshAlertBadge(); } @@ -1920,9 +1927,14 @@ async function readAllAlerts() { toast('已全部标记为已读', 'ok'); pageAlerts(); } -/* ---------- 分页组件(快速定位页数) ---------- */ -function pagerHtml(page, totalPages, total, stateVar, refreshFn) { - if (totalPages <= 1) return total ? `
共 ${total} 条
` : ''; +/* ---------- 分页组件(快速定位页数,顶部/底部各一份 + 每页条数) ---------- */ +function pagerHtml(page, totalPages, total, stateVar, refreshFn, pos = 'bottom') { + const st = window[stateVar] || {}; + const sizes = [10, 20, 50, 100]; + const sizeSel = ``; + if (totalPages <= 1) return total ? `
共 ${total} 条${sizeSel}
` : ''; const pages = []; const add = p => { if (pages[pages.length - 1] !== p && p >= 1 && p <= totalPages) pages.push(p); }; add(1); @@ -1931,27 +1943,36 @@ function pagerHtml(page, totalPages, total, stateVar, refreshFn) { for (let p = lo; p <= hi; p++) add(p); if (hi < totalPages - 1) pages.push('…'); add(totalPages); - return `
+ return `
+ ${sizeSel} 共 ${total} 条 · ${page}/${totalPages} 页 ${pages.map(p => p === '…' ? '' : ``).join('')} - 跳至 + 跳至
`; } -function goPageInput(stateVar, totalPages, refreshFn) { - const v = parseInt($('#pager-go')?.value || ''); +function setPageSize(stateVar, size, refreshFn) { + window[stateVar].pageSize = parseInt(size) || 50; + window[stateVar].page = 1; + window[refreshFn](); +} + +function goPageInput(stateVar, totalPages, refreshFn, pos) { + const el = document.getElementById('pager-go-' + pos); + const v = parseInt(el?.value || ''); if (!v || v < 1 || v > totalPages) return toast(`页码需在 1-${totalPages} 之间`, 'err'); window[stateVar].page = v; window[refreshFn](); } function bindPagerGo(stateVar, totalPages, refreshFn) { - const el = $('#pager-go'); - if (!el) return; - el.addEventListener('keydown', e => { if (e.key === 'Enter') goPageInput(stateVar, totalPages, refreshFn); }); + ['top', 'bottom'].forEach(pos => { + const el = document.getElementById('pager-go-' + pos); + if (el) el.addEventListener('keydown', e => { if (e.key === 'Enter') goPageInput(stateVar, totalPages, refreshFn, pos); }); + }); } /* ---------- 开放 API ---------- */ diff --git a/static/style.css b/static/style.css index d3a55e3..bb4de88 100644 --- a/static/style.css +++ b/static/style.css @@ -139,6 +139,9 @@ tr:hover td{background:var(--panel2)} .filter-bar .fbtn.on.warn-on{background:var(--warn)} .filter-bar .fbtn.on.danger-on{background:var(--danger)} .pager{display:flex;gap:4px;align-items:center;flex-wrap:wrap;margin-top:14px;justify-content:center} +.pager.top{margin-top:0;margin-bottom:14px} +.psize-sel{background:var(--panel2);border:1px solid var(--border);color:var(--text);border-radius:7px;padding:4px 6px;font-size:12px;cursor:pointer;height:30px} +.psize-sel:hover{border-color:var(--accent)} .pager .pbtn{min-width:30px;height:30px;padding:0 8px;border:1px solid var(--border);background:var(--panel2);color:var(--text);border-radius:7px;font-size:12px;cursor:pointer} .pager .pbtn:hover{border-color:var(--accent);color:var(--accent)} .pager .pbtn.cur{background:var(--accent);border-color:var(--accent);color:#fff;font-weight:700} @@ -166,9 +169,10 @@ tr:hover td{background:var(--panel2)} .md-box code{background:var(--panel2);border:1px solid var(--border);border-radius:4px;padding:1px 5px;font-family:ui-monospace,Consolas,monospace;font-size:12px} .md-box pre.md-code code{background:none;border:none;padding:0} .md-box a{color:var(--accent)} -.md-toggle{font-size:11px;float:right} +.md-toggle{font-size:11px;white-space:nowrap} .md-toggle button{border:1px solid var(--border);background:var(--panel2);color:var(--muted);padding:2px 8px;cursor:pointer;font-size:11px;border-radius:5px} .md-toggle button.on{background:var(--accent);color:#fff;border-color:var(--accent)} +.md-title{display:flex;align-items:center;justify-content:space-between;gap:10px} /* V1:tabs / DAG / 告警 / 开关 */ .tabs{display:flex;gap:4px;margin-bottom:16px;border-bottom:1px solid var(--border)}