Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 560f6515d2 | |||
| 432e16358f | |||
| 6f9bb5f43b | |||
| 3955dd6049 | |||
| cdec96e852 |
@ -58,7 +58,8 @@ class WeekReportController extends Controller
|
|||||||
$request->validate([
|
$request->validate([
|
||||||
'tasks' => 'required|array|min:1',
|
'tasks' => 'required|array|min:1',
|
||||||
'tasks.*.description' => 'required|string|max:2000',
|
'tasks.*.description' => 'required|string|max:2000',
|
||||||
'tasks.*.image' => 'nullable|string',
|
'tasks.*.images' => 'nullable|array', // 支持多图
|
||||||
|
'tasks.*.images.*' => 'nullable|string', // 每张图片路径
|
||||||
'week_start' => 'nullable|date',
|
'week_start' => 'nullable|date',
|
||||||
'week_end' => 'nullable|date',
|
'week_end' => 'nullable|date',
|
||||||
'author' => 'nullable|string|max:100',
|
'author' => 'nullable|string|max:100',
|
||||||
@ -106,7 +107,10 @@ class WeekReportController extends Controller
|
|||||||
foreach ($tasks as $index => $task) {
|
foreach ($tasks as $index => $task) {
|
||||||
$num = $index + 1;
|
$num = $index + 1;
|
||||||
$desc = trim($task['description']);
|
$desc = trim($task['description']);
|
||||||
$hasImage = !empty($task['image']) ? '(有截图)' : '';
|
// 支持多图:检查 images 数组
|
||||||
|
$images = $task['images'] ?? [];
|
||||||
|
$imageCount = count(array_filter($images));
|
||||||
|
$hasImage = $imageCount > 0 ? "(有{$imageCount}张截图)" : '';
|
||||||
$list[] = "{$num}. {$desc}{$hasImage}";
|
$list[] = "{$num}. {$desc}{$hasImage}";
|
||||||
}
|
}
|
||||||
return implode("\n", $list);
|
return implode("\n", $list);
|
||||||
@ -165,10 +169,12 @@ class WeekReportController extends Controller
|
|||||||
4. 不要使用"高效完成"、"圆满完成"、"取得显著成效"等套话
|
4. 不要使用"高效完成"、"圆满完成"、"取得显著成效"等套话
|
||||||
5. 每个任务用简短的一两句话描述即可,说清楚做了什么
|
5. 每个任务用简短的一两句话描述即可,说清楚做了什么
|
||||||
6. 可以按项目或类型简单分组,但不要过度分类
|
6. 可以按项目或类型简单分组,但不要过度分类
|
||||||
7. 【重要】只有标注了"(有截图)"的任务才添加"[图片占位符-任务X]",其他任务不要添加{$nextWeekRequirement}
|
7. 【重要】只有标注了"(有截图)"的任务才在该任务描述后紧接着添加"[图片占位符-任务X]"(X是原始任务序号),其他任务不要添加{$nextWeekRequirement}
|
||||||
9. 总结部分一两句话概括即可,不要写得太官方
|
9. 【重要】不要添加任何总结、小结、回顾等内容
|
||||||
10. 不要出现具体的完成时间、耗时等信息
|
10. 不要出现具体的完成时间、耗时等信息
|
||||||
11. 整体篇幅适中,不要太长
|
11. 整体篇幅适中,不要太长
|
||||||
|
12. 【重要】图片占位符的序号要与原始任务序号的序号一致,不要出现错位
|
||||||
|
|
||||||
|
|
||||||
直接输出周报内容:
|
直接输出周报内容:
|
||||||
PROMPT;
|
PROMPT;
|
||||||
@ -224,10 +230,11 @@ PROMPT;
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在报告中插入图片
|
* 在报告中插入图片(支持多图)
|
||||||
*/
|
*/
|
||||||
private function insertImagesToReport(string $report, array $tasks): string
|
private function insertImagesToReport(string $report, array $tasks): string
|
||||||
{
|
{
|
||||||
|
// 首先尝试替换AI生成的占位符
|
||||||
foreach ($tasks as $index => $task) {
|
foreach ($tasks as $index => $task) {
|
||||||
$num = $index + 1;
|
$num = $index + 1;
|
||||||
|
|
||||||
@ -241,11 +248,22 @@ PROMPT;
|
|||||||
"【图片占位符{$num}】",
|
"【图片占位符{$num}】",
|
||||||
];
|
];
|
||||||
|
|
||||||
if (!empty($task['image'])) {
|
// 获取该任务的所有图片
|
||||||
$imageUrl = asset('storage/' . $task['image']);
|
$images = $task['images'] ?? [];
|
||||||
$imageMarkdown = "\n\n\n";
|
$validImages = array_filter($images);
|
||||||
|
|
||||||
|
if (!empty($validImages)) {
|
||||||
|
// 构建多图Markdown
|
||||||
|
$imagesMarkdown = "\n\n";
|
||||||
|
foreach ($validImages as $imgIndex => $imagePath) {
|
||||||
|
$imageUrl = asset('storage/' . $imagePath);
|
||||||
|
$imgNum = $imgIndex + 1;
|
||||||
|
$imagesMarkdown .= "\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 替换占位符
|
||||||
foreach ($placeholders as $placeholder) {
|
foreach ($placeholders as $placeholder) {
|
||||||
$report = str_replace($placeholder, $imageMarkdown, $report);
|
$report = str_replace($placeholder, $imagesMarkdown, $report);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 没有图片时移除所有占位符
|
// 没有图片时移除所有占位符
|
||||||
|
|||||||
@ -6,8 +6,8 @@
|
|||||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||||
<title>智能周报生成器</title>
|
<title>智能周报生成器</title>
|
||||||
<link href="https://cdn.bootcdn.net/ajax/libs/remixicon/3.5.0/remixicon.css" rel="stylesheet">
|
<link href="https://cdn.bootcdn.net/ajax/libs/remixicon/3.5.0/remixicon.css" rel="stylesheet">
|
||||||
<script src="https://cdn.bootcdn.net/ajax/libs/marked/9.1.6/marked.min.js"></script>
|
<script src="https://unpkg.com/marked@9.1.6/marked.min.js"></script>
|
||||||
<script src="https://cdn.bootcdn.net/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
|
<script src="https://unpkg.com/html2pdf.js@0.10.1/dist/html2pdf.bundle.min.js"></script>
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
@ -353,24 +353,32 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-preview {
|
.image-preview-list {
|
||||||
position: relative;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-preview img {
|
.image-preview-item {
|
||||||
max-width: 100%;
|
position: relative;
|
||||||
max-height: 150px;
|
display: inline-block;
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-preview .remove-image {
|
.image-preview-item img {
|
||||||
|
max-width: 120px;
|
||||||
|
max-height: 100px;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-preview-item .remove-image {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -8px;
|
top: -8px;
|
||||||
right: -8px;
|
right: -8px;
|
||||||
width: 24px;
|
width: 22px;
|
||||||
height: 24px;
|
height: 22px;
|
||||||
background: var(--danger);
|
background: var(--danger);
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
@ -379,14 +387,26 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-size: 0.9rem;
|
font-size: 0.8rem;
|
||||||
transition: transform 0.2s ease;
|
transition: transform 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-preview .remove-image:hover {
|
.image-preview-item .remove-image:hover {
|
||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.image-count-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
background: var(--primary);
|
||||||
|
color: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
/* 按钮样式 */
|
/* 按钮样式 */
|
||||||
.btn {
|
.btn {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
@ -1140,8 +1160,8 @@
|
|||||||
tasks.push({
|
tasks.push({
|
||||||
id: Date.now() + Math.random(),
|
id: Date.now() + Math.random(),
|
||||||
description: task.description,
|
description: task.description,
|
||||||
image: null,
|
images: [],
|
||||||
imagePath: '',
|
imagePaths: [],
|
||||||
zentaoId: task.id
|
zentaoId: task.id
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -1235,8 +1255,8 @@
|
|||||||
tasks.push({
|
tasks.push({
|
||||||
id: taskId,
|
id: taskId,
|
||||||
description: '',
|
description: '',
|
||||||
image: null,
|
images: [], // 支持多图
|
||||||
imagePath: ''
|
imagePaths: [] // 对应的URL列表
|
||||||
});
|
});
|
||||||
renderTasks();
|
renderTasks();
|
||||||
updateTaskCount();
|
updateTaskCount();
|
||||||
@ -1260,11 +1280,35 @@
|
|||||||
container.innerHTML = '';
|
container.innerHTML = '';
|
||||||
|
|
||||||
tasks.forEach((task, index) => {
|
tasks.forEach((task, index) => {
|
||||||
|
// 生成多图预览HTML
|
||||||
|
let imagesPreviewHtml = '';
|
||||||
|
if (task.imagePaths && task.imagePaths.length > 0) {
|
||||||
|
imagesPreviewHtml = `
|
||||||
|
<div class="image-preview-list">
|
||||||
|
${task.imagePaths.map((url, imgIndex) => `
|
||||||
|
<div class="image-preview-item">
|
||||||
|
<img src="${url}" alt="截图${imgIndex + 1}">
|
||||||
|
<button class="remove-image" onclick="removeImage(${task.id}, ${imgIndex})">
|
||||||
|
<i class="ri-close-line"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`).join('')}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageCountBadge = task.imagePaths && task.imagePaths.length > 0
|
||||||
|
? `<span class="image-count-badge"><i class="ri-image-line"></i>${task.imagePaths.length}</span>`
|
||||||
|
: '';
|
||||||
|
|
||||||
const taskEl = document.createElement('div');
|
const taskEl = document.createElement('div');
|
||||||
taskEl.className = 'task-item';
|
taskEl.className = 'task-item';
|
||||||
taskEl.innerHTML = `
|
taskEl.innerHTML = `
|
||||||
<div class="task-header">
|
<div class="task-header">
|
||||||
<span class="task-number">${index + 1}</span>
|
<div style="display: flex; align-items: center;">
|
||||||
|
<span class="task-number">${index + 1}</span>
|
||||||
|
${imageCountBadge}
|
||||||
|
</div>
|
||||||
<button class="task-delete" onclick="deleteTask(${task.id})" title="删除任务">
|
<button class="task-delete" onclick="deleteTask(${task.id})" title="删除任务">
|
||||||
<i class="ri-delete-bin-line"></i>
|
<i class="ri-delete-bin-line"></i>
|
||||||
</button>
|
</button>
|
||||||
@ -1282,19 +1326,12 @@
|
|||||||
ondragover="handleDragOver(event, this)"
|
ondragover="handleDragOver(event, this)"
|
||||||
ondragleave="handleDragLeave(event, this)"
|
ondragleave="handleDragLeave(event, this)"
|
||||||
ondrop="handleDrop(event, ${task.id}, this)">
|
ondrop="handleDrop(event, ${task.id}, this)">
|
||||||
<input type="file" id="image-input-${task.id}" accept="image/*"
|
<input type="file" id="image-input-${task.id}" accept="image/*" multiple
|
||||||
onchange="handleImageSelect(${task.id}, this)" hidden>
|
onchange="handleImageSelect(${task.id}, this)" hidden>
|
||||||
<i class="ri-image-add-line"></i>
|
<i class="ri-image-add-line"></i>
|
||||||
<span>点击或拖拽上传任务截图(可选)</span>
|
<span>点击或拖拽上传任务截图(支持多张,可选)</span>
|
||||||
</div>
|
</div>
|
||||||
${task.imagePath ? `
|
${imagesPreviewHtml}
|
||||||
<div class="image-preview">
|
|
||||||
<img src="${task.imagePath}" alt="任务截图">
|
|
||||||
<button class="remove-image" onclick="removeImage(${task.id})">
|
|
||||||
<i class="ri-close-line"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
` : ''}
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
container.appendChild(taskEl);
|
container.appendChild(taskEl);
|
||||||
@ -1323,56 +1360,76 @@
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
el.classList.remove('dragover');
|
el.classList.remove('dragover');
|
||||||
const files = e.dataTransfer.files;
|
const files = e.dataTransfer.files;
|
||||||
if (files.length > 0 && files[0].type.startsWith('image/')) {
|
// 支持拖拽多张图片
|
||||||
uploadImage(taskId, files[0]);
|
const imageFiles = Array.from(files).filter(f => f.type.startsWith('image/'));
|
||||||
|
if (imageFiles.length > 0) {
|
||||||
|
uploadImages(taskId, imageFiles);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 选择图片
|
// 选择图片(支持多选)
|
||||||
function handleImageSelect(taskId, input) {
|
function handleImageSelect(taskId, input) {
|
||||||
if (input.files.length > 0) {
|
if (input.files.length > 0) {
|
||||||
uploadImage(taskId, input.files[0]);
|
uploadImages(taskId, Array.from(input.files));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 上传图片
|
// 批量上传图片
|
||||||
async function uploadImage(taskId, file) {
|
async function uploadImages(taskId, files) {
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('image', file);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('/upload', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'X-CSRF-TOKEN': csrfToken
|
|
||||||
},
|
|
||||||
body: formData
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (data.success) {
|
|
||||||
const task = tasks.find(t => t.id === taskId);
|
|
||||||
if (task) {
|
|
||||||
task.image = data.path;
|
|
||||||
task.imagePath = data.url;
|
|
||||||
renderTasks();
|
|
||||||
showToast('图片上传成功', 'success');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
showToast(data.message || '图片上传失败', 'error');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
showToast('图片上传失败: ' + error.message, 'error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 移除图片
|
|
||||||
function removeImage(taskId) {
|
|
||||||
const task = tasks.find(t => t.id === taskId);
|
const task = tasks.find(t => t.id === taskId);
|
||||||
if (task) {
|
if (!task) return;
|
||||||
task.image = null;
|
|
||||||
task.imagePath = '';
|
// 初始化数组(兼容旧数据)
|
||||||
|
if (!task.images) task.images = [];
|
||||||
|
if (!task.imagePaths) task.imagePaths = [];
|
||||||
|
|
||||||
|
let successCount = 0;
|
||||||
|
let failCount = 0;
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('image', file);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/upload', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'X-CSRF-TOKEN': csrfToken
|
||||||
|
},
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
task.images.push(data.path);
|
||||||
|
task.imagePaths.push(data.url);
|
||||||
|
successCount++;
|
||||||
|
} else {
|
||||||
|
failCount++;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
failCount++;
|
||||||
|
console.error('图片上传失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTasks();
|
||||||
|
|
||||||
|
if (successCount > 0) {
|
||||||
|
showToast(`成功上传 ${successCount} 张图片`, 'success');
|
||||||
|
}
|
||||||
|
if (failCount > 0) {
|
||||||
|
showToast(`${failCount} 张图片上传失败`, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除指定索引的图片
|
||||||
|
function removeImage(taskId, imageIndex) {
|
||||||
|
const task = tasks.find(t => t.id === taskId);
|
||||||
|
if (task && task.images && task.imagePaths) {
|
||||||
|
task.images.splice(imageIndex, 1);
|
||||||
|
task.imagePaths.splice(imageIndex, 1);
|
||||||
renderTasks();
|
renderTasks();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1403,7 +1460,7 @@
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
tasks: validTasks.map(t => ({
|
tasks: validTasks.map(t => ({
|
||||||
description: t.description,
|
description: t.description,
|
||||||
image: t.image
|
images: t.images || [] // 支持多图
|
||||||
})),
|
})),
|
||||||
week_start: weekStart,
|
week_start: weekStart,
|
||||||
week_end: weekEnd,
|
week_end: weekEnd,
|
||||||
@ -1435,7 +1492,20 @@
|
|||||||
document.getElementById('preview-placeholder').style.display = 'none';
|
document.getElementById('preview-placeholder').style.display = 'none';
|
||||||
const previewEl = document.getElementById('markdown-preview');
|
const previewEl = document.getElementById('markdown-preview');
|
||||||
previewEl.style.display = 'block';
|
previewEl.style.display = 'block';
|
||||||
previewEl.innerHTML = marked.parse(markdown);
|
|
||||||
|
// 检查 marked 是否可用
|
||||||
|
if (typeof marked !== 'undefined') {
|
||||||
|
previewEl.innerHTML = marked.parse(markdown);
|
||||||
|
} else {
|
||||||
|
// 简单的Markdown转换
|
||||||
|
previewEl.innerHTML = markdown
|
||||||
|
.replace(/^### (.*$)/gim, '<h3>$1</h3>')
|
||||||
|
.replace(/^## (.*$)/gim, '<h2>$1</h2>')
|
||||||
|
.replace(/^# (.*$)/gim, '<h1>$1</h1>')
|
||||||
|
.replace(/\*\*(.*)\*\*/gim, '<strong>$1</strong>')
|
||||||
|
.replace(/\*(.*)\*/gim, '<em>$1</em>')
|
||||||
|
.replace(/\n/gim, '<br>');
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('download-buttons').style.display = 'flex';
|
document.getElementById('download-buttons').style.display = 'flex';
|
||||||
document.getElementById('copy-btn').disabled = false;
|
document.getElementById('copy-btn').disabled = false;
|
||||||
@ -1517,103 +1587,90 @@
|
|||||||
document.querySelector('.loading-text').textContent = '正在生成PDF...';
|
document.querySelector('.loading-text').textContent = '正在生成PDF...';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 创建临时容器用于PDF生成
|
// 转换Markdown为HTML
|
||||||
const tempDiv = document.createElement('div');
|
let htmlContent = '';
|
||||||
tempDiv.id = 'pdf-content';
|
if (typeof marked !== 'undefined') {
|
||||||
tempDiv.innerHTML = `
|
htmlContent = marked.parse(generatedReport);
|
||||||
<div class="pdf-wrapper">
|
} else {
|
||||||
${marked.parse(generatedReport)}
|
htmlContent = generatedReport
|
||||||
</div>
|
.replace(/^### (.*$)/gim, '<h3>$1</h3>')
|
||||||
`;
|
.replace(/^## (.*$)/gim, '<h2>$1</h2>')
|
||||||
tempDiv.style.cssText = `
|
.replace(/^# (.*$)/gim, '<h1>$1</h1>')
|
||||||
position: absolute;
|
.replace(/\*\*(.*)\*\*/gim, '<strong>$1</strong>')
|
||||||
left: -9999px;
|
.replace(/\*(.*)\*/gim, '<em>$1</em>')
|
||||||
top: 0;
|
.replace(/\n/gim, '<br>');
|
||||||
width: 210mm;
|
}
|
||||||
background: white;
|
|
||||||
font-family: "Microsoft YaHei", "SimHei", "PingFang SC", -apple-system, sans-serif;
|
|
||||||
font-size: 12pt;
|
|
||||||
line-height: 1.6;
|
|
||||||
color: #333;
|
|
||||||
`;
|
|
||||||
|
|
||||||
// 添加内联样式
|
// 使用新窗口打印方式生成PDF
|
||||||
const style = document.createElement('style');
|
const printWindow = window.open('', '_blank');
|
||||||
style.textContent = `
|
printWindow.document.write(`
|
||||||
#pdf-content .pdf-wrapper {
|
<!DOCTYPE html>
|
||||||
padding: 15mm 20mm;
|
<html>
|
||||||
box-sizing: border-box;
|
<head>
|
||||||
}
|
<meta charset="UTF-8">
|
||||||
#pdf-content h1 {
|
<title>周报_${document.getElementById('week-start').value}</title>
|
||||||
font-size: 18pt;
|
<style>
|
||||||
color: #1a1a1a;
|
@media print {
|
||||||
border-bottom: 2px solid #4a90d9;
|
body { margin: 0; padding: 20mm; }
|
||||||
padding-bottom: 8px;
|
}
|
||||||
margin: 0 0 15px 0;
|
body {
|
||||||
}
|
font-family: "Microsoft YaHei", "SimHei", "PingFang SC", sans-serif;
|
||||||
#pdf-content h2 {
|
font-size: 12pt;
|
||||||
font-size: 14pt;
|
line-height: 1.8;
|
||||||
color: #2c3e50;
|
color: #333;
|
||||||
margin: 20px 0 10px 0;
|
padding: 20mm;
|
||||||
padding-left: 10px;
|
max-width: 210mm;
|
||||||
border-left: 4px solid #4a90d9;
|
margin: 0 auto;
|
||||||
}
|
background: white;
|
||||||
#pdf-content h3 {
|
}
|
||||||
font-size: 12pt;
|
h1 {
|
||||||
color: #34495e;
|
font-size: 18pt;
|
||||||
margin: 15px 0 8px 0;
|
color: #1a1a1a;
|
||||||
}
|
border-bottom: 2px solid #3b82f6;
|
||||||
#pdf-content p {
|
padding-bottom: 8px;
|
||||||
margin: 0 0 10px 0;
|
margin: 0 0 16px 0;
|
||||||
text-align: justify;
|
}
|
||||||
}
|
h2 {
|
||||||
#pdf-content ul, #pdf-content ol {
|
font-size: 14pt;
|
||||||
margin: 0 0 10px 20px;
|
color: #1e40af;
|
||||||
padding: 0;
|
margin: 20px 0 10px 0;
|
||||||
}
|
padding-left: 10px;
|
||||||
#pdf-content li {
|
border-left: 4px solid #3b82f6;
|
||||||
margin-bottom: 5px;
|
}
|
||||||
}
|
h3 {
|
||||||
#pdf-content img {
|
font-size: 12pt;
|
||||||
max-width: 100%;
|
color: #374151;
|
||||||
height: auto;
|
margin: 16px 0 8px 0;
|
||||||
margin: 10px 0;
|
font-weight: 600;
|
||||||
border: 1px solid #ddd;
|
}
|
||||||
border-radius: 4px;
|
p { margin: 0 0 10px 0; }
|
||||||
}
|
ul, ol { margin: 0 0 10px 20px; padding: 0; }
|
||||||
#pdf-content strong {
|
li { margin-bottom: 5px; }
|
||||||
color: #2c3e50;
|
strong { color: #1e40af; }
|
||||||
}
|
img { max-width: 100%; height: auto; margin: 10px 0; }
|
||||||
#pdf-content blockquote {
|
blockquote {
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
padding: 10px 15px;
|
padding: 10px 15px;
|
||||||
border-left: 4px solid #4a90d9;
|
border-left: 4px solid #3b82f6;
|
||||||
background: #f8f9fa;
|
background: #f0f9ff;
|
||||||
}
|
}
|
||||||
`;
|
</style>
|
||||||
tempDiv.prepend(style);
|
</head>
|
||||||
|
<body>
|
||||||
|
${htmlContent}
|
||||||
|
<script>
|
||||||
|
window.onload = function() {
|
||||||
|
setTimeout(function() {
|
||||||
|
window.print();
|
||||||
|
}, 300);
|
||||||
|
};
|
||||||
|
<\/script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`);
|
||||||
|
printWindow.document.close();
|
||||||
|
|
||||||
document.body.appendChild(tempDiv);
|
showToast('请在打印对话框中选择"另存为PDF"', 'info');
|
||||||
|
|
||||||
const opt = {
|
|
||||||
margin: 0,
|
|
||||||
filename: `周报_${document.getElementById('week-start').value}.pdf`,
|
|
||||||
image: { type: 'jpeg', quality: 0.98 },
|
|
||||||
html2canvas: {
|
|
||||||
scale: 2,
|
|
||||||
useCORS: true,
|
|
||||||
logging: false,
|
|
||||||
width: tempDiv.querySelector('.pdf-wrapper').scrollWidth,
|
|
||||||
windowWidth: tempDiv.querySelector('.pdf-wrapper').scrollWidth
|
|
||||||
},
|
|
||||||
jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' },
|
|
||||||
pagebreak: { mode: ['avoid-all', 'css', 'legacy'] }
|
|
||||||
};
|
|
||||||
|
|
||||||
await html2pdf().set(opt).from(tempDiv.querySelector('.pdf-wrapper')).save();
|
|
||||||
|
|
||||||
document.body.removeChild(tempDiv);
|
|
||||||
showToast('PDF 下载成功', 'success');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showToast('下载失败: ' + error.message, 'error');
|
showToast('下载失败: ' + error.message, 'error');
|
||||||
console.error('PDF生成错误:', error);
|
console.error('PDF生成错误:', error);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user