chore: 初始提交 - 移除所有硬编码凭据,统一从环境变量读取
This commit is contained in:
@@ -0,0 +1,535 @@
|
||||
// UI 渲染与交互
|
||||
const UI = (function () {
|
||||
const gridEl = () => document.getElementById('calendar-grid');
|
||||
const monthLabelEl = () => document.getElementById('currentMonth');
|
||||
const dayEventsEl = () => document.getElementById('dayEvents');
|
||||
|
||||
const WEEKDAYS = ['日', '一', '二', '三', '四', '五', '六'];
|
||||
|
||||
function isMobile() {
|
||||
try {
|
||||
return window.matchMedia && window.matchMedia('(max-width: 768px)').matches || (navigator && navigator.maxTouchPoints > 0);
|
||||
} catch (_) { return false; }
|
||||
}
|
||||
|
||||
// 创建详细悬停提示框
|
||||
function createEventTooltip(event) {
|
||||
let recurText = '';
|
||||
try {
|
||||
if (typeof parseRecurrenceStr === 'function') {
|
||||
const rec = parseRecurrenceStr(event.recurrence || '');
|
||||
if (rec && rec.type && rec.type !== 'NONE') {
|
||||
const parts = [];
|
||||
if (rec.type === 'DAILY') {
|
||||
parts.push(rec.interval > 1 ? `每 ${rec.interval} 天` : '每天');
|
||||
} else if (rec.type === 'WEEKLY') {
|
||||
parts.push(rec.interval > 1 ? `每 ${rec.interval} 周` : '每周');
|
||||
if (Array.isArray(rec.by) && rec.by.length) {
|
||||
const map = ['一','二','三','四','五','六','日'];
|
||||
const names = rec.by.map(n => map[(n - 1 + 7) % 7]).map(ch => '周' + ch);
|
||||
parts.push(names.join('、'));
|
||||
}
|
||||
}
|
||||
recurText = parts.join(',');
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
const tooltip = document.createElement('div');
|
||||
tooltip.className = 'event-tooltip';
|
||||
tooltip.innerHTML = `
|
||||
<div class="tooltip-header">
|
||||
<strong>${escapeHtml(event.title || '无标题')}</strong>
|
||||
${event.completed ? '<span class="badge bg-success ms-2">已完成</span>' : ''}
|
||||
</div>
|
||||
<div class="tooltip-content">
|
||||
<table class="tooltip-table">
|
||||
<tr><th>开始时间:</th><td>${formatDateTime(event.start)}</td></tr>
|
||||
${event.end ? `<tr><th>结束时间:</th><td>${formatDateTime(event.end)}</td></tr>` : ''}
|
||||
${event.content ? `<tr><th>内容:</th><td>${escapeHtmlWithLinks(event.content)}</td></tr>` : ''}
|
||||
${event.remindAt ? `<tr><th>提醒时间:</th><td>${formatDateTime(event.remindAt)}</td></tr>` : ''}
|
||||
${recurText ? `<tr><th>循环:</th><td>${recurText}</td></tr>` : ''}
|
||||
<tr><th>状态:</th><td>${event.completed ? '已完成' : '进行中'}</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function escapeHtmlWithLinks(text) {
|
||||
const raw = String(text || '');
|
||||
const escaped = escapeHtml(raw);
|
||||
const linkPattern = /((?:https?:\/\/|\/tools\/)[^\s<]+)/g;
|
||||
return escaped.replace(linkPattern, (m) => {
|
||||
const href = m.startsWith('/tools/') ? m : m;
|
||||
return `<a href="${href}" target="_blank" rel="noopener noreferrer">${m}</a>`;
|
||||
}).replace(/\n/g, '<br>');
|
||||
}
|
||||
|
||||
function formatDateTime(dateString) {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
}
|
||||
|
||||
function renderWeekdayHeader() {
|
||||
const head = document.createElement('div');
|
||||
head.className = 'calendar-weekday';
|
||||
WEEKDAYS.forEach(w => {
|
||||
const d = document.createElement('div');
|
||||
d.textContent = w;
|
||||
head.appendChild(d);
|
||||
});
|
||||
return head;
|
||||
}
|
||||
|
||||
function monthStart(year, month) { return new Date(Date.UTC(year, month - 1, 1)); }
|
||||
function monthEnd(year, month) { return new Date(Date.UTC(year, month, 0)); }
|
||||
|
||||
function loadEventIntoForm(e) {
|
||||
if (!e) return;
|
||||
const form = document.getElementById('eventForm');
|
||||
if (!form) return;
|
||||
form.eventLoaded = e;
|
||||
const idInput = document.getElementById('eventId');
|
||||
if (idInput) idInput.value = e.id || '';
|
||||
const titleInput = document.getElementById('title');
|
||||
if (titleInput) titleInput.value = e.title || '';
|
||||
const contentInput = document.getElementById('content');
|
||||
if (contentInput) contentInput.value = e.content || '';
|
||||
const completedInput = document.getElementById('completedFlag');
|
||||
if (completedInput) completedInput.checked = !!e.completed;
|
||||
|
||||
const startInput = document.getElementById('start');
|
||||
if (startInput && e.start) startInput.value = toInputValue(e.start);
|
||||
const endInput = document.getElementById('end');
|
||||
if (endInput && e.end) endInput.value = toInputValue(e.end);
|
||||
|
||||
const typeSel = document.getElementById('recurrenceType');
|
||||
const intervalInput = document.getElementById('recurrenceInterval');
|
||||
const weekdayInputs = Array.from(document.querySelectorAll('#weekdayGroup [data-weekday]'));
|
||||
const recStr = String(e.recurrence || '');
|
||||
if (typeSel) {
|
||||
typeSel.value = recStr.startsWith('DAILY') ? 'DAILY' : (recStr.startsWith('WEEKLY') ? 'WEEKLY' : 'NONE');
|
||||
}
|
||||
if (intervalInput) {
|
||||
const m = /interval=(\d+)/.exec(recStr);
|
||||
intervalInput.value = m ? Math.max(1, parseInt(m[1], 10)) : 1;
|
||||
}
|
||||
const by = (() => {
|
||||
const m = /by=([0-9,]+)/.exec(recStr);
|
||||
return m ? m[1].split(',').map(s => parseInt(s, 10)) : [];
|
||||
})();
|
||||
weekdayInputs.forEach(el => {
|
||||
const v = parseInt(el.getAttribute('data-weekday'), 10);
|
||||
el.checked = by.includes(v);
|
||||
});
|
||||
|
||||
const enableReminderInput = document.getElementById('enableReminder');
|
||||
const hasRemind = !!e.remindAt;
|
||||
if (enableReminderInput) enableReminderInput.checked = hasRemind;
|
||||
const dt = document.getElementById('remindDateTime');
|
||||
const tm = document.getElementById('remindTime');
|
||||
const typeVal = typeSel ? typeSel.value : 'NONE';
|
||||
if (typeVal === 'NONE') {
|
||||
if (dt) {
|
||||
dt.classList.remove('d-none');
|
||||
dt.value = e.remindAt ? toInputValue(e.remindAt) : '';
|
||||
}
|
||||
if (tm) tm.classList.add('d-none');
|
||||
} else {
|
||||
if (dt) dt.classList.add('d-none');
|
||||
if (tm) {
|
||||
tm.classList.remove('d-none');
|
||||
tm.value = e.remindAt ? toTimeInputValue(e.remindAt) : '';
|
||||
}
|
||||
}
|
||||
|
||||
const btnDelete = document.getElementById('btnDelete');
|
||||
if (btnDelete) btnDelete.classList.remove('d-none');
|
||||
if (isMobile()) {
|
||||
openEventModal(e);
|
||||
}
|
||||
}
|
||||
|
||||
// 在 UI.renderCalendar 的事件 pill 点击中,改为填充统一表单字段与解析循环
|
||||
function renderCalendar(year, month, events) {
|
||||
const grid = gridEl();
|
||||
grid.innerHTML = '';
|
||||
// 清理旧的星期头部,避免重复累加
|
||||
Array.from(grid.parentElement.querySelectorAll('.calendar-weekday')).forEach(el => el.remove());
|
||||
const head = renderWeekdayHeader();
|
||||
grid.parentElement.insertBefore(head, grid);
|
||||
|
||||
// 性能优化:建立日期索引,避免在循环中重复遍历所有事件
|
||||
const eventsByDate = new Map();
|
||||
// 辅助:生成本地日期 Key YYYY-MM-DD
|
||||
const toKey = (y, m, d) => `${y}-${String(m).padStart(2,'0')}-${String(d).padStart(2,'0')}`;
|
||||
|
||||
// 确定当月范围(本地时间)
|
||||
const monthStart = new Date(year, month - 1, 1, 0, 0, 0, 0);
|
||||
const monthEnd = new Date(year, month, 0, 23, 59, 59, 999);
|
||||
const overlapsDay = (e, dateObj) => {
|
||||
try {
|
||||
if (!e || e.deleted || !e.start) return false;
|
||||
const s = new Date(e.start);
|
||||
const ep = e.end ? new Date(e.end) : new Date(e.start);
|
||||
const ds = new Date(dateObj.getFullYear(), dateObj.getMonth(), dateObj.getDate(), 0, 0, 0, 0);
|
||||
const de = new Date(dateObj.getFullYear(), dateObj.getMonth(), dateObj.getDate(), 23, 59, 59, 999);
|
||||
return s <= de && ep >= ds;
|
||||
} catch (_) { return false; }
|
||||
};
|
||||
|
||||
events.forEach(e => {
|
||||
if (!e || e.deleted || !e.start) return;
|
||||
try {
|
||||
const s = new Date(e.start);
|
||||
const end = e.end ? new Date(e.end) : new Date(e.start);
|
||||
|
||||
// 计算事件涉及的日期范围(截断到当月)
|
||||
// 转换为本地日期的 0 点进行比较
|
||||
const sDay = new Date(s.getFullYear(), s.getMonth(), s.getDate());
|
||||
const eDay = new Date(end.getFullYear(), end.getMonth(), end.getDate());
|
||||
|
||||
// 如果事件范围与当月无交集,跳过
|
||||
if (eDay < monthStart || sDay > monthEnd) return;
|
||||
|
||||
const iterStart = sDay < monthStart ? monthStart : sDay;
|
||||
const iterEnd = eDay > monthEnd ? monthEnd : eDay;
|
||||
|
||||
// 遍历涉及的每一天
|
||||
for (let d = new Date(iterStart); d <= iterEnd; d.setDate(d.getDate() + 1)) {
|
||||
if (!overlapsDay(e, d)) continue;
|
||||
const key = toKey(d.getFullYear(), d.getMonth() + 1, d.getDate());
|
||||
if (!eventsByDate.has(key)) eventsByDate.set(key, []);
|
||||
eventsByDate.get(key).push(e);
|
||||
}
|
||||
} catch (_) {}
|
||||
});
|
||||
|
||||
const start = new Date(Date.UTC(year, month - 1, 1));
|
||||
const end = new Date(Date.UTC(year, month, 0));
|
||||
const firstWeekday = start.getUTCDay();
|
||||
const daysInMonth = end.getUTCDate();
|
||||
|
||||
// 补齐上月空白格
|
||||
for (let i = 0; i < firstWeekday; i++) {
|
||||
const cell = document.createElement('div');
|
||||
cell.className = 'calendar-cell is-outside';
|
||||
cell.innerHTML = `<div class="cell-header"><span></span></div>`;
|
||||
grid.appendChild(cell);
|
||||
}
|
||||
|
||||
const today = new Date();
|
||||
const isToday = (d) => d.getFullYear() === today.getFullYear() && d.getMonth() === today.getMonth() && d.getDate() === today.getDate();
|
||||
|
||||
const frag = document.createDocumentFragment();
|
||||
for (let day = 1; day <= daysInMonth; day++) {
|
||||
const date = new Date(year, month - 1, day); // 本地时间对象用于 isToday 判断
|
||||
const cell = document.createElement('div');
|
||||
cell.className = 'calendar-cell' + (isToday(date) ? ' is-today' : '');
|
||||
const header = document.createElement('div');
|
||||
header.className = 'cell-header';
|
||||
header.innerHTML = `<span>${day}</span>`;
|
||||
const body = document.createElement('div');
|
||||
body.className = 'events';
|
||||
|
||||
const key = toKey(year, month, day);
|
||||
const esRaw = eventsByDate.get(key) || [];
|
||||
|
||||
const es = (function dedupDay(list) {
|
||||
if (list.length <= 1) return list;
|
||||
const seenDisplay = new Set();
|
||||
const seenId = new Set();
|
||||
return list.filter(e => {
|
||||
// 优化:减少 Date 对象创建
|
||||
const sStr = e.start || '';
|
||||
// 简单提取时分,假设 ISO 格式
|
||||
let hh = '00', mm = '00';
|
||||
if (sStr.length >= 16) {
|
||||
const tPart = new Date(sStr);
|
||||
hh = String(tPart.getHours()).padStart(2, '0');
|
||||
mm = String(tPart.getMinutes()).padStart(2, '0');
|
||||
}
|
||||
const title = (e.title || '').trim();
|
||||
const done = e.completed ? '1' : '0';
|
||||
const dk = `${title}|${hh}:${mm}|${done}`;
|
||||
|
||||
if (seenDisplay.has(dk)) return false;
|
||||
|
||||
if (e.id) {
|
||||
if (seenId.has(e.id)) return false;
|
||||
seenId.add(e.id);
|
||||
}
|
||||
seenDisplay.add(dk);
|
||||
return true;
|
||||
});
|
||||
})(esRaw);
|
||||
|
||||
const MAX_PER_DAY = 50;
|
||||
const visibleEs = es.slice(0, MAX_PER_DAY);
|
||||
|
||||
visibleEs.forEach(e => {
|
||||
const pill = document.createElement('div');
|
||||
pill.className = 'event-pill' + (e.completed ? ' is-done' : '');
|
||||
pill.textContent = e.title;
|
||||
if (!isMobile()) {
|
||||
pill.addEventListener('mouseenter', () => {
|
||||
const tooltip = createEventTooltip(e);
|
||||
document.body.appendChild(tooltip);
|
||||
const rect = pill.getBoundingClientRect();
|
||||
tooltip.style.position = 'fixed';
|
||||
tooltip.style.left = rect.right + 10 + 'px';
|
||||
tooltip.style.top = rect.top + 'px';
|
||||
tooltip.style.zIndex = '1000';
|
||||
pill._currentTooltip = tooltip;
|
||||
});
|
||||
pill.addEventListener('mouseleave', () => {
|
||||
if (pill._currentTooltip) {
|
||||
document.body.removeChild(pill._currentTooltip);
|
||||
pill._currentTooltip = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pill.addEventListener('click', () => {
|
||||
loadEventIntoForm(e);
|
||||
});
|
||||
body.appendChild(pill);
|
||||
});
|
||||
|
||||
if (es.length > MAX_PER_DAY) {
|
||||
const more = document.createElement('div');
|
||||
more.className = 'event-pill more';
|
||||
more.textContent = `还有 ${es.length - MAX_PER_DAY} 项...`;
|
||||
more.style.backgroundColor = '#f0f0f0';
|
||||
more.style.color = '#888';
|
||||
more.style.textAlign = 'center';
|
||||
more.style.cursor = 'pointer';
|
||||
more.style.fontSize = '0.85em';
|
||||
body.appendChild(more);
|
||||
}
|
||||
|
||||
cell.appendChild(header);
|
||||
cell.appendChild(body);
|
||||
cell.addEventListener('click', (ev) => {
|
||||
const isPill = ev.target.closest('.event-pill');
|
||||
if (!isPill) renderDayList(date, events);
|
||||
});
|
||||
frag.appendChild(cell);
|
||||
}
|
||||
|
||||
grid.appendChild(frag);
|
||||
monthLabelEl().textContent = `${year}年${month}月`;
|
||||
}
|
||||
|
||||
function renderDayList(date, events) {
|
||||
const list = dayEventsEl();
|
||||
list.innerHTML = '';
|
||||
function overlapsDay(e, dateObj) {
|
||||
try {
|
||||
if (!e || e.deleted || !e.start) return false;
|
||||
const s = new Date(e.start);
|
||||
const ep = e.end ? new Date(e.end) : new Date(e.start);
|
||||
const ds = new Date(dateObj.getFullYear(), dateObj.getMonth(), dateObj.getDate(), 0, 0, 0, 0);
|
||||
const de = new Date(dateObj.getFullYear(), dateObj.getMonth(), dateObj.getDate(), 23, 59, 59, 999);
|
||||
return s <= de && ep >= ds;
|
||||
} catch (_) { return false; }
|
||||
}
|
||||
const esRaw = events.filter(e => overlapsDay(e, date) && !e.deleted);
|
||||
const es = (function dedupDay(list) {
|
||||
const seenId = new Set();
|
||||
const seenDisplay = new Set();
|
||||
function displayKey(e) {
|
||||
const d = e.start ? new Date(e.start) : null;
|
||||
const hh = d ? String(d.getHours()).padStart(2, '0') : '00';
|
||||
const mm = d ? String(d.getMinutes()).padStart(2, '0') : '00';
|
||||
const title = (e.title || '').trim();
|
||||
const done = e.completed ? '1' : '0';
|
||||
return `${title}|${hh}:${mm}|${done}`;
|
||||
}
|
||||
return list.filter(e => {
|
||||
const dk = displayKey(e);
|
||||
if (seenDisplay.has(dk)) return false;
|
||||
|
||||
if (e.id) {
|
||||
if (seenId.has(e.id)) return false;
|
||||
seenId.add(e.id);
|
||||
}
|
||||
|
||||
seenDisplay.add(dk);
|
||||
return true;
|
||||
});
|
||||
})(esRaw);
|
||||
if (!es.length) {
|
||||
const li = document.createElement('li');
|
||||
li.className = 'list-group-item text-muted small';
|
||||
li.textContent = '当天暂无事件';
|
||||
list.appendChild(li);
|
||||
return;
|
||||
}
|
||||
es.forEach(e => {
|
||||
const li = document.createElement('li');
|
||||
li.className = 'list-group-item';
|
||||
if (e.completed) li.classList.add('is-done');
|
||||
const wrap = document.createElement('div');
|
||||
wrap.className = 'event-list-content';
|
||||
const titleEl = document.createElement('p');
|
||||
titleEl.className = 'event-list-title';
|
||||
titleEl.textContent = e.title || '';
|
||||
const timeEl = document.createElement('p');
|
||||
timeEl.className = 'event-list-time';
|
||||
const s = e.start ? new Date(e.start) : null;
|
||||
const ed = e.end ? new Date(e.end) : null;
|
||||
const pad = (n) => String(n).padStart(2, '0');
|
||||
const fmt = (d) => d ? `${pad(d.getHours())}:${pad(d.getMinutes())}` : '';
|
||||
const sTxt = fmt(s);
|
||||
const eTxt = fmt(ed);
|
||||
timeEl.textContent = eTxt ? `${sTxt} - ${eTxt}` : sTxt;
|
||||
const descEl = document.createElement('p');
|
||||
descEl.className = 'event-list-desc';
|
||||
descEl.innerHTML = escapeHtmlWithLinks(e.content || '');
|
||||
wrap.appendChild(titleEl);
|
||||
wrap.appendChild(timeEl);
|
||||
if (descEl.textContent) wrap.appendChild(descEl);
|
||||
li.appendChild(wrap);
|
||||
li.addEventListener('click', () => {
|
||||
loadEventIntoForm(e);
|
||||
});
|
||||
list.appendChild(li);
|
||||
});
|
||||
}
|
||||
|
||||
function toInputValue(iso) {
|
||||
const d = new Date(iso);
|
||||
const q = Math.round(d.getMinutes() / 15) * 15;
|
||||
d.setMinutes(Math.min(45, Math.max(0, q)), 0, 0);
|
||||
const pad = (n) => String(n).padStart(2, '0');
|
||||
const yyyy = d.getFullYear();
|
||||
const MM = pad(d.getMonth() + 1);
|
||||
const dd = pad(d.getDate());
|
||||
const hh = pad(d.getHours());
|
||||
const mm = pad(d.getMinutes());
|
||||
return `${yyyy}-${MM}-${dd}T${hh}:${mm}`;
|
||||
}
|
||||
function toTimeInputValue(iso) {
|
||||
const d = new Date(iso);
|
||||
const q = Math.round(d.getMinutes() / 15) * 15;
|
||||
d.setMinutes(Math.min(45, Math.max(0, q)), 0, 0);
|
||||
const pad = (n) => String(n).padStart(2, '0');
|
||||
return `${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
||||
}
|
||||
|
||||
function openEventModal(event) {
|
||||
const backdrop = document.createElement('div');
|
||||
backdrop.className = 'event-modal-backdrop';
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'event-modal';
|
||||
const tableRows = `
|
||||
<tr><th>开始时间:</th><td>${formatDateTime(event.start)}</td></tr>
|
||||
${event.end ? `<tr><th>结束时间:</th><td>${formatDateTime(event.end)}</td></tr>` : ''}
|
||||
${event.content ? `<tr><th>内容:</th><td>${escapeHtml(event.content)}</td></tr>` : ''}
|
||||
${event.remindAt ? `<tr><th>提醒时间:</th><td>${formatDateTime(event.remindAt)}</td></tr>` : ''}
|
||||
<tr><th>状态:</th><td>${event.completed ? '已完成' : '进行中'}</td></tr>
|
||||
`;
|
||||
modal.innerHTML = `
|
||||
<div class="modal-header">
|
||||
<div class="modal-title">${escapeHtml(event.title || '无标题')}</div>
|
||||
<button type="button" class="modal-close" aria-label="关闭">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<table class="tooltip-table">${tableRows}</table>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(backdrop);
|
||||
document.body.appendChild(modal);
|
||||
const close = () => { modal.remove(); backdrop.remove(); };
|
||||
modal.querySelector('.modal-close').addEventListener('click', close);
|
||||
backdrop.addEventListener('click', close);
|
||||
}
|
||||
|
||||
function showGlobalModal(message) {
|
||||
let backdrop = document.getElementById('globalModalBackdrop');
|
||||
if (!backdrop) {
|
||||
backdrop = document.createElement('div');
|
||||
backdrop.id = 'globalModalBackdrop';
|
||||
backdrop.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.35);display:flex;align-items:center;justify-content:center;z-index:2000;';
|
||||
const box = document.createElement('div');
|
||||
box.id = 'globalModalBox';
|
||||
box.style.cssText = 'min-width:260px;max-width:90vw;padding:16px 20px;border-radius:10px;background:#1f2937;color:#fff;box-shadow:0 10px 25px rgba(0,0,0,.35);text-align:center;';
|
||||
const spinner = document.createElement('div');
|
||||
spinner.className = 'spinner-border text-light';
|
||||
spinner.setAttribute('role', 'status');
|
||||
spinner.style.cssText = 'width:2rem;height:2rem;';
|
||||
const msg = document.createElement('div');
|
||||
msg.id = 'globalModalMessage';
|
||||
msg.style.cssText = 'margin-top:10px;font-size:14px;';
|
||||
msg.textContent = message || '正在处理...';
|
||||
const logPanel = document.createElement('div');
|
||||
logPanel.id = 'globalModalLog';
|
||||
logPanel.style.cssText = 'margin-top:10px;padding:10px;background:#374151;border-radius:5px;text-align:left;font-size:12px;max-height:200px;overflow-y:auto;white-space:pre-wrap;';
|
||||
const copyBtn = document.createElement('button');
|
||||
copyBtn.id = 'globalModalCopy';
|
||||
copyBtn.style.cssText = 'margin-top:10px;padding:5px 10px;background:#4b5563;border:none;border-radius:5px;color:#fff;cursor:pointer;';
|
||||
copyBtn.textContent = '复制日志';
|
||||
copyBtn.addEventListener('click', () => {
|
||||
try {
|
||||
const text = logPanel.textContent || '';
|
||||
if (!text) return;
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(text).catch(() => {});
|
||||
}
|
||||
} catch (_) {}
|
||||
});
|
||||
box.appendChild(spinner);
|
||||
box.appendChild(msg);
|
||||
box.appendChild(logPanel);
|
||||
box.appendChild(copyBtn);
|
||||
backdrop.appendChild(box);
|
||||
document.body.appendChild(backdrop);
|
||||
} else {
|
||||
const msg = document.getElementById('globalModalMessage');
|
||||
if (msg) msg.textContent = message || '正在处理...';
|
||||
const logPanel = document.getElementById('globalModalLog');
|
||||
if (logPanel) logPanel.textContent = '';
|
||||
}
|
||||
}
|
||||
function appendGlobalModalLog(text) {
|
||||
const logPanel = document.getElementById('globalModalLog');
|
||||
if (!logPanel) return;
|
||||
const line = new Date().toLocaleTimeString() + ' - ' + text;
|
||||
logPanel.textContent = (logPanel.textContent || '') + line + '\n';
|
||||
logPanel.scrollTop = logPanel.scrollHeight;
|
||||
}
|
||||
function hideGlobalModal() {
|
||||
const backdrop = document.getElementById('globalModalBackdrop');
|
||||
if (backdrop && backdrop.parentNode) backdrop.parentNode.removeChild(backdrop);
|
||||
}
|
||||
function updateGlobalModalMessage(message) {
|
||||
const msg = document.getElementById('globalModalMessage');
|
||||
if (msg) msg.textContent = message || '';
|
||||
}
|
||||
function completeGlobalModal(message) {
|
||||
try {
|
||||
const box = document.getElementById('globalModalBox');
|
||||
const spinner = box ? box.querySelector('.spinner-border') : null;
|
||||
if (spinner && spinner.parentNode) spinner.parentNode.removeChild(spinner);
|
||||
} catch (_) {}
|
||||
updateGlobalModalMessage(message || '完成');
|
||||
setTimeout(() => { try { hideGlobalModal(); } catch (_) {} }, 1000);
|
||||
}
|
||||
|
||||
return { renderCalendar, renderDayList, toInputValue, showGlobalModal, appendGlobalModalLog, hideGlobalModal, updateGlobalModalMessage, completeGlobalModal };
|
||||
})();
|
||||
|
||||
window.UI = UI;
|
||||
Reference in New Issue
Block a user