2026-04-18-20-03-44 - 模板导入导出迁移、Logo替换为可交互占位符
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import Sidebar from '../components/Sidebar';
|
||||
import { Plus, Edit, Trash2, Save, Printer, Undo, Redo, Bold, Italic, Underline, AlignLeft, AlignCenter, AlignRight, Table, Image as ImageIcon, Check, Download } from 'lucide-react';
|
||||
import { Plus, Edit, Trash2, Save, Printer, Undo, Redo, Bold, Italic, Underline, AlignLeft, AlignCenter, AlignRight, Table, Image as ImageIcon, Check, Download, Upload } from 'lucide-react';
|
||||
import { User, Template, FormField, FieldType, DEFAULT_FORM_FIELDS } from '../types';
|
||||
import { defaultReportContent } from '../utils/defaultContent';
|
||||
import { printDocument } from '../utils/print';
|
||||
@@ -16,6 +16,8 @@ export default function TemplateManage() {
|
||||
const [exportModalOpen, setExportModalOpen] = useState(false);
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [formData, setFormData] = useState({ name: '', desc: '' });
|
||||
const [importedContent, setImportedContent] = useState<{content: string; fields: FormField[]} | null>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const [isSaved, setIsSaved] = useState(false);
|
||||
const editorRef = useRef<HTMLDivElement>(null);
|
||||
const savedRangeRef = useRef<Range | null>(null);
|
||||
@@ -128,6 +130,10 @@ export default function TemplateManage() {
|
||||
const template = templates.find(t => t.id === currentTemplateId);
|
||||
if (template) {
|
||||
editorRef.current.innerHTML = template.content;
|
||||
if (template.fields && template.fields.length > 0) {
|
||||
setFormFields(template.fields);
|
||||
storage.set('formFieldsConfig', template.fields);
|
||||
}
|
||||
}
|
||||
setTimeout(() => updatePageHeight(), 0);
|
||||
}
|
||||
@@ -598,6 +604,48 @@ export default function TemplateManage() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleImportFile = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
try {
|
||||
const json = JSON.parse(event.target?.result as string);
|
||||
if (json.type !== 'surclaw_template_package') {
|
||||
alert('无效的模板包文件');
|
||||
return;
|
||||
}
|
||||
setFormData({ name: json.title || '', desc: json.description || '' });
|
||||
setImportedContent({
|
||||
content: json.content || '',
|
||||
fields: Array.isArray(json.fields) ? json.fields : []
|
||||
});
|
||||
} catch {
|
||||
alert('文件解析失败,请检查 JSON 格式');
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
if (e.target) e.target.value = '';
|
||||
};
|
||||
|
||||
const handleExportTemplate = (template: Template) => {
|
||||
const exportData = {
|
||||
version: '1.0',
|
||||
type: 'surclaw_template_package',
|
||||
title: template.name,
|
||||
description: template.desc || '',
|
||||
content: template.content,
|
||||
fields: template.fields || formFields
|
||||
};
|
||||
const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `模板导出-${template.name}.json`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
const handleModalSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
const allTemplates = storage.get<Template[]>('templates', []);
|
||||
@@ -615,14 +663,19 @@ export default function TemplateManage() {
|
||||
id: 'tpl_' + Date.now(),
|
||||
name: formData.name,
|
||||
desc: formData.desc,
|
||||
content: defaultReportContent,
|
||||
content: importedContent?.content || defaultReportContent,
|
||||
createdAt: new Date().toISOString(),
|
||||
author: currentUser?.username || 'admin'
|
||||
author: currentUser?.username || 'admin',
|
||||
fields: importedContent?.fields || formFields
|
||||
};
|
||||
const updated = [...allTemplates, newTpl];
|
||||
setTemplates([...templates, newTpl]);
|
||||
storage.set('templates', updated);
|
||||
setCurrentTemplateId(newTpl.id);
|
||||
if (importedContent?.fields && importedContent.fields.length > 0) {
|
||||
setFormFields(importedContent.fields);
|
||||
storage.set('formFieldsConfig', importedContent.fields);
|
||||
}
|
||||
|
||||
const savedUsers = storage.get<User[]>('users', []);
|
||||
let updatedUsers = savedUsers;
|
||||
@@ -663,6 +716,7 @@ export default function TemplateManage() {
|
||||
}
|
||||
}
|
||||
setIsModalOpen(false);
|
||||
setImportedContent(null);
|
||||
};
|
||||
|
||||
if (!currentUser) return null;
|
||||
@@ -708,6 +762,12 @@ export default function TemplateManage() {
|
||||
>
|
||||
编辑
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); handleExportTemplate(tpl); }}
|
||||
className="px-2 py-1 rounded-md bg-blue-50 text-blue-600 text-[10px] font-bold hover:bg-blue-100 transition-colors"
|
||||
>
|
||||
导出
|
||||
</button>
|
||||
{templates.length > 1 && (
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); handleDeleteTemplate(tpl.id); }}
|
||||
@@ -1291,6 +1351,19 @@ export default function TemplateManage() {
|
||||
<div className="bg-white rounded-2xl p-10 w-full max-w-[500px] shadow-2xl border border-border">
|
||||
<h3 className="text-xl font-bold text-text-main mb-2">{isEditing ? '编辑模板信息' : '新增模板'}</h3>
|
||||
<p className="text-sm text-text-muted mb-8">设置模板的基本名称和描述</p>
|
||||
{!isEditing && (
|
||||
<div className="flex items-center gap-3 mb-6 p-3 bg-slate-50 rounded-xl border border-dashed border-slate-200">
|
||||
<div className="text-xs text-text-muted flex-1">已有模板文件?点击右侧图标导入</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
className="w-8 h-8 bg-accent text-white rounded-lg flex items-center justify-center hover:bg-blue-700 transition-colors shadow-sm"
|
||||
>
|
||||
<Upload size={16} />
|
||||
</button>
|
||||
<input ref={fileInputRef} type="file" accept=".json" className="hidden" onChange={handleImportFile} />
|
||||
</div>
|
||||
)}
|
||||
<form onSubmit={handleModalSubmit} className="space-y-6">
|
||||
<div className="space-y-1.5">
|
||||
<label className="block text-xs font-bold text-text-main uppercase tracking-wider">模板名称 *</label>
|
||||
@@ -1315,7 +1388,7 @@ export default function TemplateManage() {
|
||||
<div className="flex justify-end gap-3 pt-4 border-t border-border">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsModalOpen(false)}
|
||||
onClick={() => { setIsModalOpen(false); setImportedContent(null); }}
|
||||
className="px-6 py-2.5 bg-slate-100 text-text-muted rounded-lg text-sm font-semibold hover:bg-slate-200 transition-colors"
|
||||
>
|
||||
取消
|
||||
|
||||
Reference in New Issue
Block a user