feat(points): 支持后台上传积分商品图片
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user