新增: 资料导入增强+剪贴板+框选多选+未保存自动暂存+图片占位

This commit is contained in:
lxy
2026-08-24 02:13:15 +08:00
parent cc804f0749
commit 0907a104cf
10 changed files with 788 additions and 88 deletions
+53 -7
View File
@@ -9,6 +9,7 @@ import {
scanFiles, readEntries,
aiAnalyzeDocument,
fileToImageElement, imagesToSlideElements,
ACCEPT_ATTR,
type FileEntry, type ImportReport,
describeReport
} from '../../core/importer'
@@ -59,7 +60,7 @@ function openFilePicker() {
fileInput = document.createElement('input')
fileInput.type = 'file'
fileInput.multiple = true
fileInput.accept = '.png,.jpg,.jpeg,.gif,.webp,.svg,.md,.markdown,.txt,.text,.pdf,.docx,.doc'
fileInput.accept = ACCEPT_ATTR
fileInput.onchange = () => handleFiles(fileInput!.files)
}
fileInput.value = ''
@@ -77,6 +78,32 @@ function openDirPicker() {
dirInput.click()
}
/* ---------- 拖放导入(两个 dropzone 通用) ---------- */
const dragOver = ref(false)
function onDropzoneDragOver(e: DragEvent) {
if (!e.dataTransfer?.types.includes('Files')) return
e.preventDefault()
e.dataTransfer.dropEffect = 'copy'
dragOver.value = true
}
function onDropzoneDragLeave() {
dragOver.value = false
}
function onDropzoneDrop(e: DragEvent) {
e.preventDefault()
dragOver.value = false
const fl = e.dataTransfer?.files
if (fl && fl.length > 0) void handleFiles(fl)
}
/** 供父组件预填拖入的文件(全局拖放 → 打开弹窗并直接进入预览态) */
function acceptDroppedFiles(fl: FileList) {
void handleFiles(fl)
}
defineExpose({ acceptDroppedFiles })
async function handleFiles(fl: FileList | null) {
if (!fl || fl.length === 0) return
loading.value = true
@@ -152,6 +179,8 @@ function doImport() {
let insertedImages = 0
let insertedSlides = 0
// 整次导入合并为一条 undo 记录
const batch = store.beginBatch()
// 图片体积闸门:localStorage 约 5MB 字符上限,超限导入后刷新会丢图
if (images.length > 0) {
@@ -169,12 +198,12 @@ function doImport() {
if (images.length > 0) {
if (images.length === 1) {
const el = fileToImageElement(images[0].file, images[0].data!)
store.addElement('image', { content: el.content, x: el.x, y: el.y, w: el.w, h: el.h, style: el.style })
store.addElement('image', { content: el.content, x: el.x, y: el.y, w: el.w, h: el.h, style: el.style }, batch)
insertedImages = 1
} else {
const els = imagesToSlideElements(images.map(f => ({ file: f.file, dataUrl: f.data! })))
for (const el of els) {
store.addElement('image', { content: el.content, x: el.x, y: el.y, w: el.w, h: el.h, style: el.style })
store.addElement('image', { content: el.content, x: el.x, y: el.y, w: el.w, h: el.h, style: el.style }, batch)
insertedImages++
}
}
@@ -185,7 +214,7 @@ function doImport() {
for (const d of docs) {
if (d.slides) {
for (const slide of d.slides) {
store.appendSlide(slide)
store.appendSlide(slide, batch)
insertedSlides++
}
}
@@ -234,15 +263,31 @@ function textPreview(data: string, maxLen = 80): string {
<button class="tab" :class="{ active: activeTab === 'dir' }" @click="activeTab = 'dir'">📁 读取目录</button>
</div>
<div v-if="activeTab === 'files'" class="dropzone" @click="openFilePicker">
<div
v-if="activeTab === 'files'"
class="dropzone"
:class="{ 'drag-over': dragOver }"
@click="openFilePicker"
@dragover="onDropzoneDragOver"
@dragleave="onDropzoneDragLeave"
@drop="onDropzoneDrop"
>
<div class="dropzone-icon">📄</div>
<div class="dropzone-text">
<strong>点击选择文件</strong>
<strong>点击选择或拖入文件</strong>
<span class="hint">图片直接插入 · 文档PDF/DOCX/MD/TXT AI 分析生成幻灯片</span>
</div>
</div>
<div v-else class="dropzone" @click="openDirPicker">
<div
v-else
class="dropzone"
:class="{ 'drag-over': dragOver }"
@click="openDirPicker"
@dragover="onDropzoneDragOver"
@dragleave="onDropzoneDragLeave"
@drop="onDropzoneDrop"
>
<div class="dropzone-icon">📁</div>
<div class="dropzone-text">
<strong>点击选择目录</strong>
@@ -350,6 +395,7 @@ function textPreview(data: string, maxLen = 80): string {
background: var(--panel, #f8fafc);
}
.dropzone:hover { border-color: var(--primary, #4f46e5); background: color-mix(in srgb, var(--primary, #4f46e5) 5%, var(--panel, #f8fafc)); }
.dropzone.drag-over { border-color: var(--primary, #4f46e5); background: color-mix(in srgb, var(--primary, #4f46e5) 10%, var(--panel, #f8fafc)); }
.dropzone-icon { font-size: 44px; }
.dropzone-text { display: flex; flex-direction: column; gap: 4px; }
.dropzone-text strong { font-size: 16px; }