feat(points): 支持后台上传积分商品图片

This commit is contained in:
yml2213
2026-11-04 19:28:40 +08:00
parent 27c66548a2
commit 46c682cb78
3 changed files with 112 additions and 4 deletions
@@ -13,6 +13,7 @@ import {
type PointsMallRedemption,
} from '@/features/points/api/pointsMall'
import { useAdminSessionStore } from '@/stores/adminSession'
import { uploadAdminFile } from '@/shared/api/files'
import { readError } from '@/shared/utils/error'
const adminSession = useAdminSessionStore()
@@ -23,6 +24,8 @@ const products = ref<PointsMallProduct[]>([])
const redemptions = ref<PointsMallRedemption[]>([])
const editorVisible = ref(false)
const editingID = ref(0)
const coverUploading = ref(false)
const coverFileInput = ref<HTMLInputElement | null>(null)
const canManage = computed(() => adminSession.hasPermission('points_mall:manage'))
const form = reactive<PointsMallProductPayload>(defaultProduct())
@@ -102,6 +105,27 @@ async function completeRedemption(item: PointsMallRedemption) {
}
}
function triggerCoverUpload() {
coverFileInput.value?.click()
}
async function handleCoverFileChange(event: Event) {
const input = event.target as HTMLInputElement
const file = input.files?.[0]
input.value = ''
if (!file) return
coverUploading.value = true
try {
const uploaded = await uploadAdminFile(file, 'points-mall')
form.cover_url = uploaded.url
ElMessage.success('商品图片上传成功')
} catch (error) {
ElMessage.error(readError(error, '商品图片上传失败'))
} finally {
coverUploading.value = false
}
}
function defaultProduct(): PointsMallProductPayload {
return {
title: '',
@@ -203,8 +227,30 @@ function defaultProduct(): PointsMallProductPayload {
<el-form-item label="商品说明">
<el-input v-model="form.description" type="textarea" :rows="3" />
</el-form-item>
<el-form-item label="封面图片 URL">
<el-input v-model="form.cover_url" />
<el-form-item label="商品封面">
<div class="cover-editor">
<div class="cover-preview">
<img v-if="form.cover_url" :src="form.cover_url" alt="商品封面预览" />
<span v-else>暂无图片</span>
</div>
<div class="cover-actions">
<el-button :loading="coverUploading" @click="triggerCoverUpload">
{{ coverUploading ? '上传中' : '上传本地图片' }}
</el-button>
<el-button v-if="form.cover_url" plain @click="form.cover_url = ''"
>移除图片</el-button
>
<el-input v-model="form.cover_url" placeholder="也可以直接填写图片 URL" />
<small>支持 JPG、PNG、WebP,上传后自动保存到商品资料。</small>
</div>
</div>
<input
ref="coverFileInput"
class="hidden-cover-input"
type="file"
accept="image/jpeg,image/png,image/webp"
@change="handleCoverFileChange"
/>
</el-form-item>
<el-form-item label="兑换后发放说明">
<el-input v-model="form.redemption_instructions" type="textarea" :rows="3" />
@@ -269,9 +315,59 @@ function defaultProduct(): PointsMallProductPayload {
gap: 12px;
}
.cover-editor {
display: grid;
grid-template-columns: 180px minmax(0, 1fr);
gap: 14px;
width: 100%;
}
.cover-preview {
display: grid;
width: 180px;
height: 120px;
place-items: center;
overflow: hidden;
border: 1px dashed #cbd5e1;
border-radius: 10px;
background: #f8fafc;
color: #94a3b8;
font-size: 12px;
}
.cover-preview img {
width: 100%;
height: 100%;
object-fit: cover;
}
.cover-actions {
display: grid;
align-content: start;
gap: 9px;
}
.cover-actions .el-button {
justify-self: start;
margin-left: 0;
}
.cover-actions small {
color: #94a3b8;
font-size: 11px;
}
.hidden-cover-input {
display: none;
}
@media (max-width: 760px) {
.form-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.cover-editor {
grid-template-columns: 1fr;
}
}
</style>