fix(blog): 后台博客图片支持本地上传 + 封面图上传错误信息暴露
正文编辑器: - 新增"图片"按钮: 点击弹文件选择 -> 调 /api/admin/upload -> 自动插入 <img src=/uploads/...> - 保留"图片URL"按钮兼容外链/已有路径(原来唯一的 prompt 路径) - 上传中按钮 disabled, 失败用 naive-ui message 提示 封面图 ImageUpload: - 改用 useAdmin().adminFetch 替代裸 \$fetch + useState 取 token 的双轨写法 - 错误信息从单一字符串"上传失败"改为 e.data?.message || e.message || HTTP statusCode, 让线上"上传失败"自暴露真实根因 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
这个提交包含在:
父节点
9a0afb7cce
当前提交
b5c13d5f24
@ -90,11 +90,9 @@ async function uploadFile(file) {
|
|||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append('file', file)
|
formData.append('file', file)
|
||||||
|
|
||||||
const token = useState('admin_token')
|
const result = await adminFetch('/api/admin/upload', {
|
||||||
const result = await $fetch('/api/admin/upload', {
|
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData,
|
body: formData,
|
||||||
headers: { Authorization: `Bearer ${token.value}` },
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if (result.files?.length) {
|
if (result.files?.length) {
|
||||||
@ -102,7 +100,7 @@ async function uploadFile(file) {
|
|||||||
imgError.value = false
|
imgError.value = false
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
uploadError.value = e.data?.message || '上传失败'
|
uploadError.value = e?.data?.message || e?.message || `上传失败 (HTTP ${e?.statusCode ?? '?'})`
|
||||||
} finally {
|
} finally {
|
||||||
uploading.value = false
|
uploading.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,7 +14,8 @@
|
|||||||
<button type="button" :class="btn(editor.isActive('blockquote'))" @click="editor.chain().focus().toggleBlockquote().run()" title="引用">" 引用</button>
|
<button type="button" :class="btn(editor.isActive('blockquote'))" @click="editor.chain().focus().toggleBlockquote().run()" title="引用">" 引用</button>
|
||||||
<span class="divider" />
|
<span class="divider" />
|
||||||
<button type="button" :class="btn(editor.isActive('link'))" @click="setLink" title="链接">🔗 链接</button>
|
<button type="button" :class="btn(editor.isActive('link'))" @click="setLink" title="链接">🔗 链接</button>
|
||||||
<button type="button" class="toolbar-btn" @click="addImage" title="插入图片">🖼 图片</button>
|
<button type="button" class="toolbar-btn" :disabled="uploading" @click="addImage" :title="uploading ? '上传中...' : '插入图片'">🖼 {{ uploading ? '上传中' : '图片' }}</button>
|
||||||
|
<button type="button" class="toolbar-btn" @click="addImageByUrl" title="插入网络图片URL">🌐 图片URL</button>
|
||||||
<span class="divider" />
|
<span class="divider" />
|
||||||
<button type="button" class="toolbar-btn" @click="editor.chain().focus().undo().run()" title="撤销">↶</button>
|
<button type="button" class="toolbar-btn" @click="editor.chain().focus().undo().run()" title="撤销">↶</button>
|
||||||
<button type="button" class="toolbar-btn" @click="editor.chain().focus().redo().run()" title="重做">↷</button>
|
<button type="button" class="toolbar-btn" @click="editor.chain().focus().redo().run()" title="重做">↷</button>
|
||||||
@ -22,6 +23,8 @@
|
|||||||
<button type="button" class="toolbar-btn" :class="{ active: showHtml }" @click="showHtml = !showHtml" title="源代码视图"></> HTML</button>
|
<button type="button" class="toolbar-btn" :class="{ active: showHtml }" @click="showHtml = !showHtml" title="源代码视图"></> HTML</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<input ref="fileInput" type="file" accept="image/jpeg,image/png,image/webp,image/gif,image/svg+xml" style="display: none" @change="handleFileChange" />
|
||||||
|
|
||||||
<EditorContent v-show="!showHtml" :editor="editor" class="rich-editor-body" />
|
<EditorContent v-show="!showHtml" :editor="editor" class="rich-editor-body" />
|
||||||
<textarea
|
<textarea
|
||||||
v-show="showHtml"
|
v-show="showHtml"
|
||||||
@ -39,6 +42,7 @@ import StarterKit from '@tiptap/starter-kit'
|
|||||||
import Link from '@tiptap/extension-link'
|
import Link from '@tiptap/extension-link'
|
||||||
import Image from '@tiptap/extension-image'
|
import Image from '@tiptap/extension-image'
|
||||||
import { watch, ref, onBeforeUnmount } from 'vue'
|
import { watch, ref, onBeforeUnmount } from 'vue'
|
||||||
|
import { useMessage } from 'naive-ui'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: { type: String, default: '' },
|
modelValue: { type: String, default: '' },
|
||||||
@ -47,6 +51,10 @@ const props = defineProps({
|
|||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
const showHtml = ref(false)
|
const showHtml = ref(false)
|
||||||
|
const fileInput = ref(null)
|
||||||
|
const uploading = ref(false)
|
||||||
|
const message = useMessage()
|
||||||
|
const { adminFetch } = useAdmin()
|
||||||
|
|
||||||
const editor = useEditor({
|
const editor = useEditor({
|
||||||
content: props.modelValue,
|
content: props.modelValue,
|
||||||
@ -79,8 +87,35 @@ function setLink() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function addImage() {
|
function addImage() {
|
||||||
const url = window.prompt('图片 URL(可填 /uploads/ 或 /images/ 路径)')
|
if (uploading.value) return
|
||||||
if (url) editor.value.chain().focus().setImage({ src: url }).run()
|
fileInput.value?.click()
|
||||||
|
}
|
||||||
|
|
||||||
|
function addImageByUrl() {
|
||||||
|
const url = window.prompt('图片 URL(可填 /uploads/ 或 /images/ 路径,或外部 https:// 链接)')
|
||||||
|
if (url) editor.value.chain().focus().setImage({ src: url.trim() }).run()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleFileChange(e) {
|
||||||
|
const file = e.target.files?.[0]
|
||||||
|
e.target.value = ''
|
||||||
|
if (!file) return
|
||||||
|
if (file.size > 10 * 1024 * 1024) { message.error('文件过大,最大 10MB'); return }
|
||||||
|
|
||||||
|
uploading.value = true
|
||||||
|
try {
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', file)
|
||||||
|
const result = await adminFetch('/api/admin/upload', { method: 'POST', body: formData })
|
||||||
|
const url = result?.files?.[0]?.url
|
||||||
|
if (!url) throw new Error('上传响应缺少 url 字段')
|
||||||
|
editor.value.chain().focus().setImage({ src: url }).run()
|
||||||
|
message.success('图片已插入')
|
||||||
|
} catch (err) {
|
||||||
|
message.error(err?.data?.message || err?.message || `上传失败 (HTTP ${err?.statusCode ?? '?'})`)
|
||||||
|
} finally {
|
||||||
|
uploading.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
正在加载...
x
在新工单中引用
屏蔽一个用户