= {};
+ for (const k of otherKeys) rest[k] = (detail as any)[k];
+ parts.push(`\nExtra: ${formatJson(rest)}`);
+ }
+ return parts.join('\n') || String(detail);
+};
+
const formatJson = (val: any) => {
try {
return JSON.stringify(val, null, 2);
@@ -275,6 +312,108 @@ const writeToClipboard = async (text: string) => {
}
};
+// 打印/导出图片:先展开所有条目(优先展开有图片的),然后等待图片加载完再打印
+const printLogs = () => {
+ // 1. 展开所有带 detail 的条目
+ for (const e of logStore.logs) {
+ if (e.detail !== undefined) forcedExpanded.value.add(e.id);
+ }
+ forcedCollapsed.value.clear();
+ expandedAll.value = true;
+
+ // 2. 等 Vue 渲染完,再给图片 1.5 秒加载时间,然后触发打印
+ nextTick(() => {
+ setTimeout(() => {
+ window.print();
+ }, 1500);
+ });
+};
+
+// 导出完整 HTML(含图片渲染),保存为文件后双击打开即可打印/截图
+const exportHtml = () => {
+ const rowsHtml = logStore.logs.map((e) => {
+ const levelCls = e.level;
+ let detailHtml = '';
+ if (e.detail !== undefined) {
+ if (isImageUrl(e.detail)) {
+ detailHtml = `
+
+
})
+
${String(e.detail)}
+
`;
+ } else if (isErrorDetail(e.detail)) {
+ detailHtml = `${escapeHtml(formatErrorDetail(e.detail))} `;
+ } else if (typeof e.detail === 'string' && (e.detail.startsWith('http:') || e.detail.startsWith('https:'))) {
+ detailHtml = ``;
+ } else if (typeof e.detail === 'string') {
+ detailHtml = ``;
+ } else if (typeof e.detail === 'object' && e.detail !== null) {
+ detailHtml = `${escapeHtml(formatJson(e.detail))} `;
+ } else {
+ detailHtml = `${escapeHtml(formatDetailForText(e.detail))} `;
+ }
+ }
+ return `
+
+ ${e.time}
+ ${e.level.toUpperCase()}
+ [${e.module}]
+ ${escapeHtml(e.message)}
+ ${detailHtml}
+
`;
+ }).join('');
+
+ const html = `
+
+
+
+QZ Music 运行日志 - 导出 ${new Date().toISOString()}
+
+
+
+QZ Music 运行日志
+共 ${logStore.logs.length} 条 · 导出时间 ${new Date().toLocaleString()}
+${rowsHtml}
+
+`;
+
+ const blob = new Blob([html], { type: 'text/html;charset=utf-8' });
+ downloadBlob(blob, `qzmusic-logs-${tsFileName()}.html`);
+};
+
+// HTML 转义
+const escapeHtml = (s: string) => String(s)
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''');
+
const onClear = () => {
if (confirm('确定清空所有日志?')) {
logStore.clear();
@@ -607,4 +746,15 @@ onMounted(() => {
background: var(--color-bg-tertiary);
border-color: var(--color-text-muted);
}
+
+/* 打印时隐藏工具栏和按钮,图片完整展开 */
+@media print {
+ .action-group, .filter-group, .toolbar, .detail-actions, .mini-btn { display: none !important; }
+ .log-view { padding: 0; }
+ .log-header { border: none; background: transparent; padding: 0 0 12px 0; }
+ .log-body { background: transparent; border: none; padding: 0; }
+ .log-row { break-inside: avoid; page-break-inside: avoid; background: transparent !important; border-bottom: 1px solid #ddd; }
+ .row-detail { break-inside: avoid; background: #f5f5f5 !important; }
+ .detail-image { max-width: 100%; max-height: none; }
+}