新增: 资料导入增强+剪贴板+框选多选+未保存自动暂存+图片占位
This commit is contained in:
@@ -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; }
|
||||
|
||||
Reference in New Issue
Block a user