feat: restore learning feedback and knowledge capabilities
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
import type { SummaryCardResponse } from '@/types/api';
|
||||
|
||||
const WIDTH = 1080;
|
||||
const PADDING = 84;
|
||||
const CONTENT_WIDTH = WIDTH - PADDING * 2;
|
||||
|
||||
const safeFileName = (value: string) =>
|
||||
(value || 'SOP总结卡').replace(/[\\/:*?"<>|\r\n]+/g, '-').slice(0, 48);
|
||||
|
||||
const wrapText = (context: CanvasRenderingContext2D, text: string, maxWidth: number) => {
|
||||
const lines: string[] = [];
|
||||
let current = '';
|
||||
for (const char of String(text || '').trim()) {
|
||||
const next = current + char;
|
||||
if (current && context.measureText(next).width > maxWidth) {
|
||||
lines.push(current);
|
||||
current = char;
|
||||
} else {
|
||||
current = next;
|
||||
}
|
||||
}
|
||||
if (current) lines.push(current);
|
||||
return lines.length ? lines : [''];
|
||||
};
|
||||
|
||||
export const downloadSummaryCardImage = async (card: SummaryCardResponse) => {
|
||||
if (typeof document === 'undefined' || typeof URL === 'undefined') {
|
||||
throw new Error('当前终端暂不支持下载图片');
|
||||
}
|
||||
if (document.fonts?.ready) await document.fonts.ready;
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = WIDTH;
|
||||
canvas.height = 1;
|
||||
const measureContext = canvas.getContext('2d');
|
||||
if (!measureContext) throw new Error('图片生成失败');
|
||||
|
||||
measureContext.font = '800 56px system-ui, sans-serif';
|
||||
const titleLines = wrapText(measureContext, card.title || '工作总结卡', CONTENT_WIDTH);
|
||||
const sectionRows = [
|
||||
['核心步骤', (card.steps || []).map((item) => `${item.label}:${item.desc}`)],
|
||||
['推荐话术', card.scripts || []],
|
||||
['常见异议与回应', (card.objections || []).map((item) => `${item.concern};回应:${item.response}`)],
|
||||
['师傅提醒', card.reminders || []]
|
||||
] as const;
|
||||
measureContext.font = '400 28px system-ui, sans-serif';
|
||||
const sections = sectionRows
|
||||
.filter(([, rows]) => rows.length)
|
||||
.map(([title, rows]) => ({
|
||||
title,
|
||||
rows: rows.map((row) => wrapText(measureContext, row, CONTENT_WIDTH - 64))
|
||||
}));
|
||||
|
||||
let measuredY = 118 + 80 + titleLines.length * 76 + 14 + 70;
|
||||
sections.forEach((section) => {
|
||||
measuredY += 48;
|
||||
section.rows.forEach((lines) => {
|
||||
measuredY += Math.max(54, lines.length * 44 + 18);
|
||||
});
|
||||
measuredY += 36;
|
||||
});
|
||||
const height = Math.max(720, measuredY + 100);
|
||||
canvas.height = height;
|
||||
const context = canvas.getContext('2d');
|
||||
if (!context) throw new Error('图片生成失败');
|
||||
|
||||
context.fillStyle = '#f7f9fc';
|
||||
context.fillRect(0, 0, WIDTH, height);
|
||||
context.fillStyle = '#ffffff';
|
||||
context.fillRect(36, 36, WIDTH - 72, height - 72);
|
||||
|
||||
let y = 118;
|
||||
context.fillStyle = '#e60012';
|
||||
context.font = '700 28px system-ui, sans-serif';
|
||||
context.fillText('银城数字师傅 · SOP 总结卡', PADDING, y);
|
||||
y += 80;
|
||||
context.fillStyle = '#172033';
|
||||
context.font = '800 56px system-ui, sans-serif';
|
||||
titleLines.forEach((line) => {
|
||||
context.fillText(line, PADDING, y);
|
||||
y += 76;
|
||||
});
|
||||
y += 14;
|
||||
context.fillStyle = '#e60012';
|
||||
context.fillRect(PADDING, y, 88, 8);
|
||||
y += 70;
|
||||
|
||||
sections.forEach((section) => {
|
||||
context.fillStyle = '#172033';
|
||||
context.font = '800 34px system-ui, sans-serif';
|
||||
context.fillText(section.title, PADDING, y);
|
||||
y += 48;
|
||||
section.rows.forEach((lines, index) => {
|
||||
context.fillStyle = '#fff0f1';
|
||||
context.beginPath();
|
||||
context.arc(PADDING + 18, y + 19, 18, 0, Math.PI * 2);
|
||||
context.fill();
|
||||
context.fillStyle = '#e60012';
|
||||
context.font = '800 20px system-ui, sans-serif';
|
||||
context.textAlign = 'center';
|
||||
context.fillText(String(index + 1), PADDING + 18, y + 27);
|
||||
context.textAlign = 'left';
|
||||
context.fillStyle = '#3f4a5c';
|
||||
context.font = '400 28px system-ui, sans-serif';
|
||||
lines.forEach((line, lineIndex) => context.fillText(line, PADDING + 56, y + 28 + lineIndex * 44));
|
||||
y += Math.max(54, lines.length * 44 + 18);
|
||||
});
|
||||
y += 36;
|
||||
});
|
||||
|
||||
context.fillStyle = '#8a94a5';
|
||||
context.font = '400 22px system-ui, sans-serif';
|
||||
context.fillText('内容由数字师傅基于已授权 SOP 生成,请结合现场情况执行。', PADDING, y + 10);
|
||||
|
||||
const blob = await new Promise<Blob>((resolve, reject) => {
|
||||
canvas.toBlob((value) => value ? resolve(value) : reject(new Error('图片生成失败')), 'image/png');
|
||||
});
|
||||
canvas.width = 1;
|
||||
canvas.height = 1;
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `${safeFileName(card.title)}.png`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
window.setTimeout(() => URL.revokeObjectURL(url), 1000);
|
||||
};
|
||||
Reference in New Issue
Block a user