This commit is contained in:
lzcode
2026-10-16 14:01:02 +08:00
parent 8fc899d0a9
commit 24d63e6702
3 changed files with 104 additions and 2 deletions
+62 -2
View File
@@ -58,8 +58,30 @@
<el-form-item label="标题"> <el-form-item label="标题">
<el-input v-model="form.title" maxlength="100" show-word-limit /> <el-input v-model="form.title" maxlength="100" show-word-limit />
</el-form-item> </el-form-item>
<el-form-item label="封面图 URL"> <el-form-item label="封面图">
<el-input v-model="form.cover_url" placeholder="https://..." /> <div class="cover-upload">
<el-upload
:show-file-list="false"
:auto-upload="false"
accept="image/jpeg,image/png,image/webp,image/gif"
:on-change="(f) => uploadCover(f)"
>
<div v-if="form.cover_url" class="cover-preview">
<img :src="form.cover_url" />
<span v-if="coverUploading" class="mask">上传中…</span>
</div>
<el-button v-else plain :loading="coverUploading">选择图片</el-button>
</el-upload>
<el-button
v-if="form.cover_url"
link type="danger"
style="margin-left: 12px"
@click="form.cover_url = ''"
>移除</el-button>
</div>
</el-form-item>
<el-form-item label="封面 URL">
<el-input v-model="form.cover_url" placeholder="可直接粘贴或通过上方选择上传" />
</el-form-item> </el-form-item>
<el-form-item label="分类"> <el-form-item label="分类">
<el-select v-model="form.category_id" placeholder="请选择" style="width: 100%"> <el-select v-model="form.category_id" placeholder="请选择" style="width: 100%">
@@ -93,11 +115,38 @@ const categories = ref([])
const dialogVisible = ref(false) const dialogVisible = ref(false)
const editing = ref(null) const editing = ref(null)
const submitting = ref(false) const submitting = ref(false)
const coverUploading = ref(false)
const form = reactive({ const form = reactive({
title: '', cover_url: '', description: '', category_id: 0, title: '', cover_url: '', description: '', category_id: 0,
}) })
async function uploadCover(file) {
if (!file || !file.raw) return
const allowed = ['image/jpeg', 'image/png', 'image/webp', 'image/gif']
if (!allowed.includes(file.raw.type)) {
return ElMessage.error('仅支持 jpg/png/webp/gif')
}
if (file.raw.size > 5 * 1024 * 1024) {
return ElMessage.error('图片不能超过 5MB')
}
coverUploading.value = true
try {
const fd = new FormData()
fd.append('file', file.raw)
const r = await request({
url: '/admin/upload/image', method: 'post', data: fd,
headers: { 'Content-Type': 'multipart/form-data' },
})
form.cover_url = r.data.url
ElMessage.success('封面上传成功')
} catch (e) {
ElMessage.error(e?.message || '上传失败')
} finally {
coverUploading.value = false
}
}
onMounted(async () => { onMounted(async () => {
load() load()
try { try {
@@ -181,4 +230,15 @@ async function del(row) {
.header { display: flex; justify-content: space-between; align-items: center; } .header { display: flex; justify-content: space-between; align-items: center; }
.title-col { display: flex; align-items: center; gap: 8px; } .title-col { display: flex; align-items: center; gap: 8px; }
.cover { width: 40px; height: 40px; border-radius: 4px; object-fit: cover; } .cover { width: 40px; height: 40px; border-radius: 4px; object-fit: cover; }
.cover-upload { display: flex; align-items: center; }
.cover-preview {
width: 120px; height: 70px; border-radius: 6px; overflow: hidden;
border: 1px dashed #dcdfe6; cursor: pointer; position: relative;
display: flex; align-items: center; justify-content: center;
}
.cover-preview img { width: 100%; height: 100%; object-fit: cover; }
.cover-preview .mask {
position: absolute; inset: 0; background: rgba(0,0,0,.6);
color: #fff; font-size: 12px; display: flex; align-items: center; justify-content: center;
}
</style> </style>
Binary file not shown.
+42
View File
@@ -174,6 +174,48 @@ async def upload_video_file(request: Request, token: str = "",
}) })
@router.post("/upload/image")
async def upload_admin_image(request: Request,
admin: AdminIdentity = Depends(require_super)):
"""admin 通用图片上传:封面/头像/系列图等。存到 runtime/uploads/images/{YYYYMMDD}/,
返回 h5 域名可直接访问的完整 URL。"""
from fastapi import UploadFile, HTTPException
from datetime import datetime
from pathlib import Path
from uuid import uuid4
form = await request.form()
files = [v for v in form.values() if hasattr(v, "filename") and v.filename]
if not files:
raise HTTPException(400, "缺少 file 字段")
upload: UploadFile = files[0]
allowed = {".jpg", ".jpeg", ".png", ".webp", ".gif"}
suffix = Path(upload.filename or "").suffix.lower()
if suffix not in allowed:
raise HTTPException(400, f"仅支持 {','.join(sorted(allowed))}")
PROJECT_ROOT = Path("/root/project/video")
day = datetime.now().strftime("%Y%m%d")
out_dir = PROJECT_ROOT / "runtime" / "uploads" / "images" / day
out_dir.mkdir(parents=True, exist_ok=True)
file_name = f"{uuid4().hex}{suffix}"
file_path = out_dir / file_name
size = 0
with open(file_path, "wb") as f:
while True:
chunk = await upload.read(1024 * 1024)
if not chunk:
break
size += len(chunk)
f.write(chunk)
await upload.close()
url = f"https://h5.256kb.top/uploads/images/{day}/{file_name}"
return ok({"url": url, "file_size": size})
# ---------- 用户 / 积分 ---------- # ---------- 用户 / 积分 ----------
@router.get("/users") @router.get("/users")