瀏覽代碼

Visualizzatore di PDF

Sviluppo_Carrello_Immagini
Lorenzo Pollutri 2 小時之前
父節點
當前提交
613e4ecb27
共有 2 個檔案被更改,包括 77 行新增10 行删除
  1. +74
    -9
      app/admin/page.tsx
  2. +3
    -1
      package.json

+ 74
- 9
app/admin/page.tsx 查看文件

@@ -4,6 +4,51 @@ import { useState, useEffect } from 'react';
import { Card, Portal, MediaItem } from '@/types';
const isVideoUrl = (url: string) => /\.(mp4|webm|mov|m4v|ogv)(\?|$)/i.test(url);
const isPdfFile = (file: File) =>
file.type === 'application/pdf' || /\.pdf$/i.test(file.name);
async function uploadBlobAsImage(blob: Blob, name: string): Promise<string | null> {
const formData = new FormData();
formData.append('file', new File([blob], name, { type: blob.type || 'image/png' }));
const res = await fetch('/api/upload', { method: 'POST', body: formData });
const data = await res.json();
return data.url || null;
}
async function pdfToImageItems(
file: File,
onProgress: (page: number, total: number) => void
): Promise<MediaItem[]> {
const pdfjs = await import('pdfjs-dist');
// Worker file is copied to /public via the postinstall script
pdfjs.GlobalWorkerOptions.workerSrc = '/pdf.worker.min.mjs';
const arrayBuffer = await file.arrayBuffer();
const pdf = await pdfjs.getDocument({ data: arrayBuffer }).promise;
const baseName = file.name.replace(/\.pdf$/i, '').replace(/[^a-zA-Z0-9-_]/g, '_');
const items: MediaItem[] = [];
for (let i = 1; i <= pdf.numPages; i++) {
onProgress(i, pdf.numPages);
const page = await pdf.getPage(i);
const viewport = page.getViewport({ scale: 1.5 });
const canvas = document.createElement('canvas');
canvas.width = viewport.width;
canvas.height = viewport.height;
const ctx = canvas.getContext('2d');
if (!ctx) continue;
await page.render({ canvasContext: ctx, viewport }).promise;
const blob: Blob = await new Promise((resolve, reject) => {
canvas.toBlob(b => b ? resolve(b) : reject(new Error('toBlob failed')), 'image/png');
});
const url = await uploadBlobAsImage(blob, `${baseName}-page${i}.png`);
if (url) items.push({ url });
}
return items;
}
export default function AdminDashboard() {
const [activeTab, setActiveTab] = useState<'cards' | 'settings'>('cards');
@@ -20,6 +65,7 @@ export default function AdminDashboard() {
// NEW UI STATES: Toast and Confirm Dialog
const [toast, setToast] = useState<string | null>(null);
const [confirmDialog, setConfirmDialog] = useState<{ message: string, onConfirm: () => void } | null>(null);
const [pdfProgress, setPdfProgress] = useState<{ name: string; page: number; total: number } | null>(null);
// Helper to show auto-dismissing toast
const showToast = (message: string) => {
@@ -58,11 +104,25 @@ export default function AdminDashboard() {
const uploaded: MediaItem[] = [];
for (const file of Array.from(files)) {
const formData = new FormData();
formData.append('file', file);
const res = await fetch('/api/upload', { method: 'POST', body: formData });
const data = await res.json();
if (data.url) uploaded.push({ url: data.url });
try {
if (isPdfFile(file)) {
const items = await pdfToImageItems(file, (page, total) =>
setPdfProgress({ name: file.name, page, total })
);
uploaded.push(...items);
setPdfProgress(null);
} else {
const formData = new FormData();
formData.append('file', file);
const res = await fetch('/api/upload', { method: 'POST', body: formData });
const data = await res.json();
if (data.url) uploaded.push({ url: data.url });
}
} catch (err) {
console.error('Upload failed for', file.name, err);
showToast(`Failed to process "${file.name}".`);
setPdfProgress(null);
}
}
setIsEditing(prev => ({
@@ -376,20 +436,25 @@ export default function AdminDashboard() {
)}
</div>
{/* Gallery Media (images + videos) */}
{/* Gallery Media (images + videos + PDFs) */}
<div>
<label className="block text-sm font-semibold text-gray-800 mb-1">
Gallery Media <span className="text-gray-400 font-normal text-xs">(images or videos, shown in detail modal)</span>
Gallery Media <span className="text-gray-400 font-normal text-xs">(images, videos or PDFs — PDF pages become slides)</span>
</label>
<div className="border-2 border-dashed border-gray-300 rounded-lg p-3 hover:bg-gray-50 transition-colors">
<input
type="file"
accept="image/*,video/*"
accept="image/*,video/*,application/pdf,.pdf"
multiple
onChange={handleUploadExtraMedia}
className="block w-full text-sm text-gray-700 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-purple-50 file:text-purple-700 hover:file:bg-purple-100 cursor-pointer"
/>
{uploading['extraMedia'] && <p className="mt-2 text-sm text-purple-600 font-medium">Uploading...</p>}
{uploading['extraMedia'] && !pdfProgress && <p className="mt-2 text-sm text-purple-600 font-medium">Uploading...</p>}
{pdfProgress && (
<p className="mt-2 text-sm text-purple-600 font-medium">
Processing &ldquo;{pdfProgress.name}&rdquo;: page {pdfProgress.page} of {pdfProgress.total}
</p>
)}
</div>
{(isEditing.extraMedia || []).length > 0 && (


+ 3
- 1
package.json 查看文件

@@ -6,10 +6,12 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint"
"lint": "eslint",
"postinstall": "node -e \"require('fs').copyFileSync('node_modules/pdfjs-dist/build/pdf.worker.min.mjs','public/pdf.worker.min.mjs')\""
},
"dependencies": {
"next": "16.2.4",
"pdfjs-dist": "^4.7.76",
"react": "19.2.4",
"react-dom": "19.2.4"
},


Loading…
取消
儲存