<script setup lang="ts">
|
import { onMounted, ref } from 'vue'
|
import { Picture } from '@element-plus/icons-vue'
|
import { ElMessage } from 'element-plus'
|
import { getEquityImageCategories, saveEquityImageCategory, uploadAdminImage } from '../api/admin'
|
import type { EquityImageCategory, EquityImageItem } from '../types/admin'
|
|
const activeTab = ref('gift')
|
const loading = ref(false)
|
const saving = ref(false)
|
const uploadingImageId = ref<number | string | null>(null)
|
const supportedCategoryKeys = new Set(['premium', 'food', 'home', 'transport', 'city', 'shopping', 'entertainment'])
|
const categoryOrder = ['premium', 'food', 'home', 'transport', 'city', 'shopping', 'entertainment']
|
|
function resolveImageUrl(url?: string) {
|
if (!url || /^https?:\/\//i.test(url)) return url || ''
|
const apiBaseUrl = import.meta.env.VITE_API_BASE_URL || ''
|
return apiBaseUrl ? `${apiBaseUrl.replace(/\/$/, '')}${url}` : url
|
}
|
|
function normalizeConfig(config: EquityImageCategory): EquityImageCategory {
|
const images = config.images?.length ? config.images : [{ id: Date.now(), name: '', imageUrl: '', enabled: true }]
|
return {
|
...config,
|
images: images.map((item) => ({
|
...item,
|
enabled: item.enabled === true || item.enabled === 1,
|
})),
|
}
|
}
|
|
/**
|
* 每个分类的顶部大图在小程序里高度并不一样:舒心安居是 590rpx,其余分类都是 730rpx
|
* (见 rencaika/pages/stay-benefits 与 premium-benefits/food-benefits/transport-benefits/
|
* travel-benefits/shopping-benefits/entertainment-benefits 的 .hero 高度)。
|
* 推荐尺寸必须跟着实际展示走,否则商户按提示传图仍会被裁掉。
|
*/
|
function imageSizeTip(categoryKey: string) {
|
if (categoryKey === 'home' || categoryKey === 'stay') {
|
return '推荐尺寸 750 × 590 px(宽高比约 1.27:1,与舒心安居页顶部大图一致)'
|
}
|
return '推荐尺寸 750 × 730 px(宽高比约 1:1,与小程序顶部大图一致)'
|
}
|
|
const configs = ref<EquityImageCategory[]>([
|
{
|
categoryKey: 'gift',
|
categoryLabel: '臻享礼遇',
|
intro: '',
|
images: [{ id: 1, name: '', imageUrl: '' }],
|
},
|
{
|
categoryKey: 'food',
|
categoryLabel: '暖心食享',
|
intro: '',
|
images: [{ id: 1, name: '', imageUrl: '' }],
|
},
|
{
|
categoryKey: 'home',
|
categoryLabel: '舒心安居',
|
intro: '',
|
images: [{ id: 1, name: '', imageUrl: '' }],
|
},
|
{
|
categoryKey: 'travel',
|
categoryLabel: '畅行无忧',
|
intro: '',
|
images: [{ id: 1, name: '', imageUrl: '' }],
|
},
|
{
|
categoryKey: 'city',
|
categoryLabel: '悦览花城',
|
intro: '',
|
images: [{ id: 1, name: '', imageUrl: '' }],
|
},
|
{
|
categoryKey: 'goods',
|
categoryLabel: '惠享好物',
|
intro: '',
|
images: [{ id: 1, name: '', imageUrl: '' }],
|
},
|
{
|
categoryKey: 'life',
|
categoryLabel: '怡乐生活',
|
intro: '',
|
images: [{ id: 1, name: '', imageUrl: '' }],
|
},
|
])
|
|
function currentConfig() {
|
return configs.value.find((item) => item.categoryKey === activeTab.value) || configs.value[0]
|
}
|
|
function addImage() {
|
const config = currentConfig()
|
config.images.push({
|
id: Date.now(),
|
name: '',
|
imageUrl: '',
|
enabled: true,
|
})
|
}
|
|
function removeImage(index: number) {
|
const images = currentConfig().images
|
if (images.length <= 1) return
|
images.splice(index, 1)
|
}
|
|
async function uploadImage(item: EquityImageItem) {
|
const input = document.createElement('input')
|
input.type = 'file'
|
input.accept = 'image/jpeg,image/png,image/webp'
|
input.onchange = async () => {
|
const file = input.files?.[0]
|
input.remove()
|
if (!file) return
|
uploadingImageId.value = item.id ?? null
|
try {
|
const result = await uploadAdminImage(file)
|
item.imageUrl = result.url
|
ElMessage.success('图片已上传,请保存配置')
|
} finally {
|
uploadingImageId.value = null
|
}
|
}
|
input.click()
|
}
|
|
async function loadData() {
|
loading.value = true
|
try {
|
const result = await getEquityImageCategories()
|
configs.value = result
|
.filter((item) => supportedCategoryKeys.has(item.categoryKey))
|
.sort((left, right) => categoryOrder.indexOf(left.categoryKey) - categoryOrder.indexOf(right.categoryKey))
|
.map(normalizeConfig)
|
if (!configs.value.some((item) => item.categoryKey === activeTab.value)) {
|
activeTab.value = configs.value[0]?.categoryKey != null ? String(configs.value[0].categoryKey) : ''
|
}
|
} finally {
|
loading.value = false
|
}
|
}
|
|
async function saveConfig() {
|
saving.value = true
|
try {
|
const saved = await saveEquityImageCategory(currentConfig())
|
const index = configs.value.findIndex((item) => item.categoryKey === saved.categoryKey)
|
if (index >= 0) {
|
configs.value[index] = normalizeConfig(saved)
|
}
|
} finally {
|
saving.value = false
|
}
|
ElMessage.success('权益图片配置已保存')
|
}
|
|
onMounted(loadData)
|
</script>
|
|
<template>
|
<div v-loading="loading" class="equity-image-page">
|
<el-radio-group v-model="activeTab" class="equity-image-tabs">
|
<el-radio-button v-for="item in configs" :key="item.categoryKey" :label="item.categoryKey">
|
{{ item.categoryLabel }}
|
</el-radio-button>
|
</el-radio-group>
|
|
<section class="equity-image-panel">
|
<div class="equity-intro-block">
|
<label>介绍文案</label>
|
<el-input
|
v-model="currentConfig().intro"
|
type="textarea"
|
:rows="4"
|
resize="none"
|
placeholder="请输入"
|
/>
|
</div>
|
|
<div v-for="(item, index) in currentConfig().images" :key="item.id" class="equity-image-block">
|
<h3>图片{{ index + 1 }}</h3>
|
|
<div class="equity-name-row">
|
<label>名称</label>
|
<el-input v-model="item.name" placeholder="请输入" />
|
</div>
|
|
<div class="equity-name-row">
|
<label>是否启用</label>
|
<el-switch v-model="item.enabled" />
|
</div>
|
|
<div class="equity-upload-row">
|
<label>图片</label>
|
<div class="equity-upload-preview">
|
<img v-if="item.imageUrl" :src="resolveImageUrl(item.imageUrl)" alt="权益图片" />
|
<el-icon v-else><Picture /></el-icon>
|
</div>
|
<el-button type="primary" :loading="uploadingImageId === item.id" @click="uploadImage(item)">上传</el-button>
|
<el-button v-if="currentConfig().images.length > 1" @click="removeImage(index)">移除</el-button>
|
<span class="equity-upload-tip">{{ imageSizeTip(currentConfig().categoryKey) }}</span>
|
</div>
|
</div>
|
|
<el-button type="primary" class="equity-add-button" @click="addImage">新增</el-button>
|
</section>
|
|
<el-button type="primary" class="equity-save-button" :loading="saving" @click="saveConfig">保存</el-button>
|
</div>
|
</template>
|