async function api(path, options = {}) { const config = { method: options.method || 'GET', headers: {}, credentials: 'same-origin' }; if (options.json !== undefined) { config.headers['Content-Type'] = 'application/json'; config.body = JSON.stringify(options.json); } else if (options.body) { config.body = options.body; } const response = await fetch(`/api/${path}`, config); let data; try { data = await response.json(); } catch (_) { data = { ok: false, message: '服务器响应异常,请刷新后重试' }; } if (!response.ok && !data.message) data.message = `请求失败(${response.status})`; return data; } function toast(message, isError = false) { let element = document.querySelector('#toast'); if (!element) { element = document.createElement('div'); element.id = 'toast'; element.className = 'toast'; document.body.appendChild(element); } element.textContent = message; element.classList.toggle('error', Boolean(isError)); element.classList.add('show'); clearTimeout(element.timer); element.timer = setTimeout(() => element.classList.remove('show'), 3800); } function text(value) { return String(value ?? '').replace(/[&<>"']/g, ch => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[ch])); } function requireConfirm(message) { return window.confirm(message); } function setButtonsDisabled(root, disabled) { root.querySelectorAll('button[data-working]').forEach(button => { button.disabled = disabled || button.dataset.alwaysDisabled === '1'; }); }