新增: OSS 资产库与桌面本地文件读取,交互与云同步增强

This commit is contained in:
lxy
2026-08-27 22:22:04 +08:00
parent e839c62bba
commit daaccb8ad7
21 changed files with 1388 additions and 38 deletions
+34 -12
View File
@@ -14,6 +14,7 @@ import {
type FileEntry, type ImportReport,
describeReport
} from '../../core/importer'
import { putAsset, isOssEnabled } from '../../core/assets'
const props = withDefaults(defineProps<{ visible: boolean }>(), { visible: false })
const emit = defineEmits<{
@@ -65,7 +66,7 @@ function openFilePicker() {
fileInput.type = 'file'
fileInput.multiple = true
fileInput.accept = ACCEPT_ATTR
fileInput.onchange = () => handleFiles(fileInput!.files)
fileInput.onchange = () => handleFiles(fileInput!.files ? Array.from(fileInput!.files) : null)
}
fileInput.value = ''
fileInput.click()
@@ -76,7 +77,7 @@ function openDirPicker() {
dirInput = document.createElement('input')
dirInput.type = 'file'
;(dirInput as any).webkitdirectory = true
dirInput.onchange = () => handleFiles(dirInput!.files)
dirInput.onchange = () => handleFiles(dirInput!.files ? Array.from(dirInput!.files) : null)
}
dirInput.value = ''
dirInput.click()
@@ -98,17 +99,17 @@ function onDropzoneDrop(e: DragEvent) {
e.preventDefault()
dragOver.value = false
const fl = e.dataTransfer?.files
if (fl && fl.length > 0) void handleFiles(fl)
if (fl && fl.length > 0) void handleFiles(Array.from(fl))
}
/** 供父组件预填拖入的文件(全局拖放 → 打开弹窗并直接进入预览态) */
function acceptDroppedFiles(fl: FileList) {
function acceptDroppedFiles(fl: File[]) {
void handleFiles(fl)
}
defineExpose({ acceptDroppedFiles })
async function handleFiles(fl: FileList | null) {
async function handleFiles(fl: File[] | null) {
if (!fl || fl.length === 0) return
loading.value = true
loaded.value = false
@@ -167,7 +168,7 @@ async function runAiAnalysis() {
}
/* ---------- 执行导入 ---------- */
function doImport() {
async function doImport() {
const images = imageEntries.value.filter(e => e.data)
const videos = videoEntries.value.filter(e => e.data)
const docs = docEntries.value.filter(e => e.slides && e.slides.length > 0)
@@ -188,20 +189,40 @@ function doImport() {
const batch = store.beginBatch()
// 图片体积闸门:localStorage 约 5MB 字符上限,超限导入后刷新会丢图
if (images.length > 0) {
// (OSS 启用时走资产库引用,不占 localStorage,跳过闸门)
if (images.length > 0 && !isOssEnabled()) {
const imgChars = images.reduce((s, e) => s + (e.data?.length || 0), 0)
let deckChars = 0
try { deckChars = JSON.stringify(store.getDeck()).length } catch (e) { /* ignore */ }
const QUOTA_CHARS = 4_500_000
if (deckChars + imgChars > QUOTA_CHARS) {
emit('toast', '图片过大:导入后约 ' + ((deckChars + imgChars) / 1048576).toFixed(1) + 'MB,超本地存储上限(约 5MB),刷新可能丢失。请压缩图片、减少数量,或先清理文库/旧图')
emit('toast', '图片过大:导入后约 ' + ((deckChars + imgChars) / 1048576).toFixed(1) + 'MB,超本地存储上限(约 5MB),刷新可能丢失。请压缩图片、减少数量,或在 ☁ 云存储中启用 OSS')
return
}
}
// 1. 图片 → 当前幻灯片
// 1. 图片 → 当前幻灯片OSS 启用时 content 存资产引用,绕过 5MB 墙)
if (images.length > 0) {
if (images.length === 1) {
if (isOssEnabled()) {
if (images.length === 1) {
const ref = await putAsset(images[0].file)
const el = fileToImageElement(images[0].file, ref)
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 = await Promise.all(images.map(async f => {
const ref = await putAsset(f.file)
return fileToImageElement(f.file, ref)
}))
// 复用网格布局(dataUrl 传空串,只取行列坐标/尺寸)
const grid = imagesToSlideElements(images.map(f => ({ file: f.file, dataUrl: '' })))
for (let i = 0; i < els.length; i++) {
els[i].x = grid[i].x; els[i].y = grid[i].y; els[i].w = grid[i].w; els[i].h = grid[i].h
store.addElement('image', { content: els[i].content, x: els[i].x, y: els[i].y, w: els[i].w, h: els[i].h, style: els[i].style }, batch)
insertedImages++
}
}
} else 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 }, batch)
insertedImages = 1
@@ -214,10 +235,11 @@ function doImport() {
}
}
// 1.5 视频 → 当前幻灯片(每个视频一页,元素铺满合理区域)
// 1.5 视频 → 当前幻灯片(OSS 启用时走资产库;每个视频一页,元素铺满合理区域)
if (videos.length > 0) {
for (const v of videos) {
const el = fileToVideoElement(v.file, v.data!)
const content = isOssEnabled() ? await putAsset(v.file) : v.data!
const el = fileToVideoElement(v.file, content)
if (videos.length === 1) {
store.addElement('video', { content: el.content, x: el.x, y: el.y, w: el.w, h: el.h, style: el.style }, batch)
insertedImages++