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

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
+64 -7
View File
@@ -5,7 +5,8 @@
import { computed, watch, ref } from 'vue'
import { store } from '../../core/store'
import { elementTypes } from '../../core/sample'
import { generateImage, isImageConfigured } from '../../core/ai'
import { generateImage, isImageConfigured, checkImageQuota } from '../../core/ai'
import { readAsDataURL } from '../../core/importer'
import { markdownToSegments, segmentsToPlain, hasFormatting } from '../../core/richtext'
import AddGrid from './AddGrid.vue'
import type { ElementType, ChartType } from '../../core/types'
@@ -23,6 +24,7 @@ const CHART_TYPES: Array<{ k: ChartType; label: string; icon: string }> = [
const selected = computed(() => store.getSelected())
const slide = computed(() => store.currentSlide.value)
const multiCount = computed(() => store.getSelection().length)
/** 临时输入态(v-model 绑定) */
const content = ref('')
@@ -80,6 +82,16 @@ function onAdd(type: ElementType) {
store.addElement(type)
}
/* 多选批量操作 */
function onMultiCopy() {
const sel = store.getSelection()
if (sel.length) store.copyElements(sel)
}
function onMultiDel() {
const sel = store.getSelection()
if (sel.length) store.delElements(sel)
}
function onContentInput() {
if (!selected.value) return
store.updateElement(selected.value.id, { content: content.value })
@@ -161,7 +173,7 @@ function applyMarkdown() {
if (!el) return
const segs = markdownToSegments(content.value)
if (segs.length && hasFormatting(segs)) {
store.updateElement(el.id, { segments: segs } as any)
store.updateElement(el.id, { segments: segs })
}
}
@@ -169,10 +181,10 @@ function applyMarkdown() {
function clearRich() {
const el = selected.value
if (!el) return
store.updateElement(el.id, { segments: undefined } as any)
store.updateElement(el.id, { segments: undefined })
}
/* ---------- AI 配图 ---------- */
/* ---------- AI 配图 / 本地换图 ---------- */
async function onAiImage() {
if (imgBusy.value) return
const el = selected.value
@@ -184,6 +196,8 @@ async function onAiImage() {
imgAbort = new AbortController()
try {
const r = await generateImage({ prompt: promptText, signal: imgAbort.signal })
const quotaErr = checkImageQuota(r.url)
if (quotaErr) { alert(quotaErr); return }
store.updateElement(el.id, { content: r.url })
} catch (e: any) {
if (e?.name !== 'AbortError') alert('配图失败:' + (e?.message || String(e)))
@@ -191,6 +205,28 @@ async function onAiImage() {
imgBusy.value = false; imgAbort = null
}
}
/** 本地上传图片替换当前图片元素内容 */
function onLocalImage() {
const el = selected.value
if (!el || el.type !== 'image') return
const input = document.createElement('input')
input.type = 'file'
input.accept = 'image/*'
input.onchange = async () => {
const file = input.files?.[0]
if (!file) return
try {
const dataUrl = await readAsDataURL(file)
const quotaErr = checkImageQuota(dataUrl)
if (quotaErr) { alert(quotaErr); return }
store.updateElement(el.id, { content: dataUrl })
} catch (e: any) {
alert('读取图片失败:' + (e?.message || String(e)))
}
}
input.click()
}
</script>
<template>
@@ -201,6 +237,24 @@ async function onAiImage() {
<AddGrid @add="onAdd" />
</section>
<!-- 多选态批量操作 -->
<section v-else-if="multiCount > 1" class="prop-section" id="propMulti">
<h4 class="prop-title">多选 · <span>{{ multiCount }} 个元素</span></h4>
<div class="prop-row">
<label>批量操作</label>
<div class="seg">
<button @click="onMultiCopy" title="Ctrl+C"> 复制</button>
<button class="danger" @click="onMultiDel" title="Delete">🗑 删除</button>
</div>
</div>
<div class="prop-row">
<label>提示</label>
<div style="font-size:12px;color:var(--ui-muted);line-height:1.6">
拖动整组移动 · Shift+点击加选/减选<br />空白处拖动框选 · Esc 取消多选
</div>
</div>
</section>
<!-- 选中元素 -->
<section v-else class="prop-section" id="propElement">
<h4 class="prop-title">元素 · <span>{{ typeLabel }}</span></h4>
@@ -321,10 +375,13 @@ async function onAiImage() {
</div>
</div>
<!-- AI 配图仅图片元素 -->
<!-- 图片来源仅图片元素 -->
<div v-if="selected.type === 'image'" class="prop-row">
<label>AI 配图</label>
<button class="btn" :disabled="imgBusy" @click="onAiImage">{{ imgBusy ? '生成中' : '🎨 AI 配图' }}</button>
<label>图片来源</label>
<div class="seg">
<button @click="onLocalImage" title="从本地选择图片">📁 本地图片</button>
<button :disabled="imgBusy" @click="onAiImage">{{ imgBusy ? '生成中' : '🎨 AI 配图' }}</button>
</div>
</div>
<!-- 层级 -->