up
This commit is contained in:
@@ -58,8 +58,30 @@
|
||||
<el-form-item label="标题">
|
||||
<el-input v-model="form.title" maxlength="100" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item label="封面图 URL">
|
||||
<el-input v-model="form.cover_url" placeholder="https://..." />
|
||||
<el-form-item label="封面图">
|
||||
<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 label="分类">
|
||||
<el-select v-model="form.category_id" placeholder="请选择" style="width: 100%">
|
||||
@@ -93,11 +115,38 @@ const categories = ref([])
|
||||
const dialogVisible = ref(false)
|
||||
const editing = ref(null)
|
||||
const submitting = ref(false)
|
||||
const coverUploading = ref(false)
|
||||
|
||||
const form = reactive({
|
||||
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 () => {
|
||||
load()
|
||||
try {
|
||||
@@ -181,4 +230,15 @@ async function del(row) {
|
||||
.header { display: flex; justify-content: space-between; align-items: center; }
|
||||
.title-col { display: flex; align-items: center; gap: 8px; }
|
||||
.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>
|
||||
|
||||
Binary file not shown.
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user