后台编辑器支持短文配图:加载时显示、可增删、保存回传
短文的配图(images 字段)此前在后台编辑器里完全不可见——快发盒发的 带图短文一旦进后台重新编辑,站主既看不到图,也无法管理。现在: - 编辑器加载短文时显示配图条(缩略图 + 单张移除 + 补传,最多 4 张, 与快发盒同上限),配图条只在类型为短文时出现 - 保存时短文始终回传当前列表(后端约定 nil = 不修改,空数组 = 清空), 长文不带该字段;自动保存同样回传,不会弄丢图
This commit is contained in:
1 parent
125107d1c9
commit
2917db9e89
1 file changed
+121
-3
@@ -38,9 +38,45 @@ const form = reactive({
|
||||
status: 'draft',
|
||||
published_at: '',
|
||||
reading_minutes: null,
|
||||
override_minutes: false
|
||||
override_minutes: false,
|
||||
images: []
|
||||
})
|
||||
|
||||
// ---------- 短文配图:时间线 Twitter 式网格,最多 4 张 ----------
|
||||
// 后端约定 images 为 nil = 不修改;编辑器对短文始终回传当前列表
|
||||
// (增删都在这里完成),长文不带该字段。
|
||||
const MAX_IMAGES = 4
|
||||
const imgInput = ref(null)
|
||||
const imgUploading = ref(false)
|
||||
|
||||
function pickImages() {
|
||||
imgInput.value?.click()
|
||||
}
|
||||
async function onImagesPick(e) {
|
||||
const files = [...(e.target.files || [])].slice(0, MAX_IMAGES - form.images.length)
|
||||
e.target.value = ''
|
||||
if (!files.length) {
|
||||
toastErr('配图最多 4 张')
|
||||
return
|
||||
}
|
||||
imgUploading.value = true
|
||||
try {
|
||||
const fd = new FormData()
|
||||
for (const f of files) fd.append('file', f)
|
||||
const data = await adminApi.uploadFiles(fd)
|
||||
for (const f of data) form.images.push(f.url)
|
||||
dirty.value = true
|
||||
} catch (e2) {
|
||||
toastErr(e2.message || '图片上传失败')
|
||||
} finally {
|
||||
imgUploading.value = false
|
||||
}
|
||||
}
|
||||
function removeImage(i) {
|
||||
form.images.splice(i, 1)
|
||||
dirty.value = true
|
||||
}
|
||||
|
||||
// ---------- mode: 'wysiwyg' | 'md' ----------
|
||||
const mode = ref('wysiwyg')
|
||||
|
||||
@@ -115,6 +151,7 @@ async function loadData() {
|
||||
form.cover_url = p.cover_url || ''
|
||||
form.content_md = p.content_md || ''
|
||||
form.tags = p.tags ? [...p.tags] : []
|
||||
form.images = p.images ? [...p.images] : []
|
||||
form.status = p.status || 'draft'
|
||||
publishedLocal.value = isoToLocal(p.published_at)
|
||||
if (p.reading_minutes && p.reading_minutes !== estimateMinutes(p.content_md || '')) {
|
||||
@@ -284,6 +321,7 @@ function applySaved(saved) {
|
||||
// 自动摘要/自动封面在服务端补齐的,回写表单让站主看见(清空仍会保留为空)
|
||||
if (typeof saved.summary === 'string') form.summary = saved.summary
|
||||
if (typeof saved.cover_url === 'string') form.cover_url = saved.cover_url
|
||||
if (Array.isArray(saved.images)) form.images = [...saved.images]
|
||||
publishedLocal.value = isoToLocal(saved.published_at)
|
||||
}
|
||||
|
||||
@@ -313,7 +351,10 @@ function buildPayload() {
|
||||
tags: [...form.tags],
|
||||
status: form.status,
|
||||
published_at: localToIso(publishedLocal.value) || new Date().toISOString(),
|
||||
reading_minutes: form.override_minutes && form.reading_minutes ? Number(form.reading_minutes) : null
|
||||
reading_minutes: form.override_minutes && form.reading_minutes ? Number(form.reading_minutes) : null,
|
||||
// 短文配图:始终回传当前列表(后端 nil = 不修改;空数组 = 清空配图)。
|
||||
// 长文不带该字段,undefined 不会进 JSON。
|
||||
...(form.kind === 'short' ? { images: [...form.images] } : {})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -938,6 +979,26 @@ const tbGroups = computed(() => [
|
||||
:placeholder="form.kind === 'short' ? '标题(可留空,仅作归档索引)' : '标题'"
|
||||
/>
|
||||
|
||||
<!-- 短文配图:时间线 Twitter 式网格展示,最多 4 张,可增删 -->
|
||||
<div v-if="form.kind === 'short'" class="note-imgs">
|
||||
<div v-for="(u, i) in form.images" :key="u" class="note-img">
|
||||
<img :src="u" alt="" loading="lazy" />
|
||||
<button type="button" class="rm" title="移除这张图" aria-label="移除这张图" @click="removeImage(i)">✕</button>
|
||||
</div>
|
||||
<button
|
||||
v-if="form.images.length < MAX_IMAGES"
|
||||
type="button"
|
||||
class="note-img add"
|
||||
:disabled="imgUploading"
|
||||
title="添加配图"
|
||||
@click="pickImages"
|
||||
>{{ imgUploading ? '…' : '+' }}</button>
|
||||
<input ref="imgInput" type="file" accept="image/png,image/jpeg,image/webp,image/gif" hidden multiple @change="onImagesPick" />
|
||||
</div>
|
||||
<p v-if="form.kind === 'short'" class="field-hint" style="margin: 4px 0 12px;">
|
||||
配图在时间线以网格展示(最多 4 张),发布前后都能增删;正文里的行内图片不受这里影响。
|
||||
</p>
|
||||
|
||||
<!-- 工具栏:仅 Markdown 模式显示 -->
|
||||
<div v-if="mode === 'md'" class="toolbar">
|
||||
<div v-for="g in tbGroups" :key="g.label" class="tb-group">
|
||||
@@ -1152,4 +1213,61 @@ const tbGroups = computed(() => [
|
||||
</aside>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
</template>
|
||||
<style scoped>
|
||||
/* 短文配图条:与时间线网格同观感(圆角方块 + cover 裁切) */
|
||||
.note-imgs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.note-img {
|
||||
position: relative;
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--admin-line);
|
||||
}
|
||||
.note-img img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
.note-img .rm {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
background: rgba(0, 0, 0, 0.55);
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.note-img .rm:hover {
|
||||
background: rgba(0, 0, 0, 0.75);
|
||||
}
|
||||
.note-img.add {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-style: dashed;
|
||||
background: transparent;
|
||||
color: var(--admin-muted);
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.note-img.add:hover {
|
||||
color: var(--admin-accent);
|
||||
border-color: var(--admin-accent);
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user