|
|
@@ -0,0 +1,185 @@
|
|
|
+import { useState } from 'react';
|
|
|
+import { Button } from '@/client/components/ui/button';
|
|
|
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/client/components/ui/card';
|
|
|
+import { Input } from '@/client/components/ui/input';
|
|
|
+import { Label } from '@/client/components/ui/label';
|
|
|
+import { Alert, AlertDescription } from '@/client/components/ui/alert';
|
|
|
+import { FileText, Upload, Download } from 'lucide-react';
|
|
|
+import { toast } from 'sonner';
|
|
|
+import WordViewer from '@/client/admin-shadcn/components/WordViewer';
|
|
|
+
|
|
|
+interface WordFile {
|
|
|
+ id: string;
|
|
|
+ name: string;
|
|
|
+ size: number;
|
|
|
+ url: string;
|
|
|
+ previewUrl?: string;
|
|
|
+}
|
|
|
+
|
|
|
+export default function WordPreview() {
|
|
|
+ const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
|
|
+ const [previewFile, setPreviewFile] = useState<WordFile | null>(null);
|
|
|
+ const [isLoading, setIsLoading] = useState(false);
|
|
|
+ const [previewLoading, setPreviewLoading] = useState(false);
|
|
|
+
|
|
|
+ const handleFileSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
|
+ const file = event.target.files?.[0];
|
|
|
+ if (file) {
|
|
|
+ // 检查文件类型
|
|
|
+ const validTypes = [
|
|
|
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
|
+ 'application/msword'
|
|
|
+ ];
|
|
|
+
|
|
|
+ if (!validTypes.includes(file.type)) {
|
|
|
+ toast.error('请选择有效的Word文件(.docx或.doc)');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ setSelectedFile(file);
|
|
|
+ setPreviewFile(null);
|
|
|
+ toast.success('文件已选择');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handlePreview = async () => {
|
|
|
+ if (!selectedFile) {
|
|
|
+ toast.error('请先选择Word文件');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ setPreviewLoading(true);
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 创建文件预览URL
|
|
|
+ const fileUrl = URL.createObjectURL(selectedFile);
|
|
|
+ const wordFile: WordFile = {
|
|
|
+ id: Date.now().toString(),
|
|
|
+ name: selectedFile.name,
|
|
|
+ size: selectedFile.size,
|
|
|
+ url: fileUrl,
|
|
|
+ previewUrl: fileUrl
|
|
|
+ };
|
|
|
+
|
|
|
+ setPreviewFile(wordFile);
|
|
|
+ toast.success('文件加载成功,正在预览...');
|
|
|
+ } catch (error) {
|
|
|
+ toast.error('文件预览失败,请重试');
|
|
|
+ console.error('Preview error:', error);
|
|
|
+ } finally {
|
|
|
+ setPreviewLoading(false);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleDownload = () => {
|
|
|
+ if (!previewFile) {
|
|
|
+ toast.error('没有可下载的文件');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const link = document.createElement('a');
|
|
|
+ link.href = previewFile.url;
|
|
|
+ link.download = previewFile.name;
|
|
|
+ document.body.appendChild(link);
|
|
|
+ link.click();
|
|
|
+ document.body.removeChild(link);
|
|
|
+ };
|
|
|
+
|
|
|
+ const formatFileSize = (bytes: number) => {
|
|
|
+ if (bytes === 0) return '0 Bytes';
|
|
|
+ const k = 1024;
|
|
|
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
|
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
|
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
|
+ };
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="space-y-6">
|
|
|
+ <div>
|
|
|
+ <h1 className="text-3xl font-bold tracking-tight">Word文档在线预览</h1>
|
|
|
+ <p className="text-muted-foreground">上传并预览Word文档内容</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="grid gap-6 md:grid-cols-2">
|
|
|
+ {/* 文件选择区域 */}
|
|
|
+ <Card>
|
|
|
+ <CardHeader>
|
|
|
+ <CardTitle className="flex items-center gap-2">
|
|
|
+ <Upload className="h-5 w-5" />
|
|
|
+ 选择Word文件
|
|
|
+ </CardTitle>
|
|
|
+ <CardDescription>
|
|
|
+ 支持 .docx 和 .doc 格式的Word文档
|
|
|
+ </CardDescription>
|
|
|
+ </CardHeader>
|
|
|
+ <CardContent className="space-y-4">
|
|
|
+ <div className="grid w-full max-w-sm items-center gap-1.5">
|
|
|
+ <Label htmlFor="word-file">Word文档</Label>
|
|
|
+ <Input
|
|
|
+ id="word-file"
|
|
|
+ type="file"
|
|
|
+ accept=".doc,.docx,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
|
|
+ onChange={handleFileSelect}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {selectedFile && (
|
|
|
+ <Alert>
|
|
|
+ <FileText className="h-4 w-4" />
|
|
|
+ <AlertDescription>
|
|
|
+ <div className="space-y-1">
|
|
|
+ <p><strong>文件名:</strong> {selectedFile.name}</p>
|
|
|
+ <p><strong>大小:</strong> {formatFileSize(selectedFile.size)}</p>
|
|
|
+ <p><strong>类型:</strong> {selectedFile.type}</p>
|
|
|
+ </div>
|
|
|
+ </AlertDescription>
|
|
|
+ </Alert>
|
|
|
+ )}
|
|
|
+
|
|
|
+ <Button
|
|
|
+ onClick={handlePreview}
|
|
|
+ disabled={!selectedFile}
|
|
|
+ className="w-full"
|
|
|
+ >
|
|
|
+ <>
|
|
|
+ <Upload className="h-4 w-4 mr-2" />
|
|
|
+ 开始预览
|
|
|
+ </>
|
|
|
+ </Button>
|
|
|
+ </CardContent>
|
|
|
+ </Card>
|
|
|
+
|
|
|
+ {/* 预览区域 */}
|
|
|
+ <Card>
|
|
|
+ <CardHeader>
|
|
|
+ <CardTitle className="flex items-center gap-2">
|
|
|
+ <FileText className="h-5 w-5" />
|
|
|
+ 文档预览
|
|
|
+ </CardTitle>
|
|
|
+ <CardDescription>
|
|
|
+ {previewFile ? previewFile.name : '请先选择并预览文档'}
|
|
|
+ </CardDescription>
|
|
|
+ </CardHeader>
|
|
|
+ <CardContent>
|
|
|
+ <WordViewer file={selectedFile} />
|
|
|
+ </CardContent>
|
|
|
+ </Card>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* 使用说明 */}
|
|
|
+ <Card>
|
|
|
+ <CardHeader>
|
|
|
+ <CardTitle>使用说明</CardTitle>
|
|
|
+ </CardHeader>
|
|
|
+ <CardContent>
|
|
|
+ <div className="space-y-2 text-sm">
|
|
|
+ <p>• 支持的文件格式:.docx(Word 2007及以上版本)和 .doc(Word 97-2003)</p>
|
|
|
+ <p>• 文件大小限制:建议不超过10MB</p>
|
|
|
+ <p>• 浏览器限制:部分浏览器可能无法完美显示Word文档的复杂格式</p>
|
|
|
+ <p>• 隐私保护:所有文件处理都在本地浏览器完成,不会上传到服务器</p>
|
|
|
+ </div>
|
|
|
+ </CardContent>
|
|
|
+ </Card>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+}
|