feat: 接单池卡背景与充值页大背景始终显示上传入口

未上传显示空占位与「上传背景」;会员/保证金页大图单独成组,不依赖接口是否已下发新 key。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-07-25 20:52:14 +08:00
parent a57992786c
commit ee9c3c2381

View File

@@ -140,12 +140,19 @@
</el-card>
<el-card class="section-card" shadow="never">
<template #header><strong>打手中心-会员/保证金充值页图标</strong></template>
<template #header><strong>接单池-订单卡背景</strong></template>
<p class="hint">平台 / 优质商家 / 普通商家三类抢单卡片背景。未上传时小程序保持原白卡;此处始终可直接上传。</p>
<div class="icon-grid">
<div v-for="item in fighterRechargeIcons" :key="item.icon_key" class="icon-cell">
<div v-for="item in grabCardBgIcons" :key="item.icon_key" class="icon-cell">
<div class="icon-label">{{ item.label }}</div>
<div class="icon-key">{{ item.icon_key }}</div>
<el-image :src="fullUrl(item.image_path)" fit="contain" class="icon-preview" />
<el-image
v-if="previewUrl(item)"
:src="previewUrl(item)"
fit="contain"
class="poster-preview"
/>
<div v-else class="empty-preview">暂无图片</div>
<el-upload
action="#"
:auto-upload="false"
@@ -153,7 +160,66 @@
:on-change="(f) => uploadIcon(f, item.icon_key)"
accept="image/jpeg,image/png,image/jpg,image/webp"
>
<el-button size="small" type="primary" :loading="uploadingKey === item.icon_key">更换</el-button>
<el-button size="small" type="primary" :loading="uploadingKey === item.icon_key">
{{ item.is_uploaded ? '更换背景' : '上传背景' }}
</el-button>
</el-upload>
</div>
</div>
</el-card>
<el-card class="section-card" shadow="never">
<template #header><strong>打手「我的」- 会员/保证金充值页大背景</strong></template>
<p class="hint">会员充值页顶图、保证金页顶图与右侧装饰。未上传时小程序用默认样式;此处始终可直接上传。</p>
<div class="icon-grid">
<div v-for="item in fighterPageBgIcons" :key="item.icon_key" class="icon-cell">
<div class="icon-label">{{ item.label }}</div>
<div class="icon-key">{{ item.icon_key }}</div>
<el-image
v-if="previewUrl(item)"
:src="previewUrl(item)"
fit="contain"
class="poster-preview"
/>
<div v-else class="empty-preview">暂无图片</div>
<el-upload
action="#"
:auto-upload="false"
:show-file-list="false"
:on-change="(f) => uploadIcon(f, item.icon_key)"
accept="image/jpeg,image/png,image/jpg,image/webp"
>
<el-button size="small" type="primary" :loading="uploadingKey === item.icon_key">
{{ item.is_uploaded ? '更换背景' : '上传背景' }}
</el-button>
</el-upload>
</div>
</div>
</el-card>
<el-card class="section-card" shadow="never">
<template #header><strong>打手中心-会员/保证金充值页图标</strong></template>
<div class="icon-grid">
<div v-for="item in fighterRechargeIcons" :key="item.icon_key" class="icon-cell">
<div class="icon-label">{{ item.label }}</div>
<div class="icon-key">{{ item.icon_key }}</div>
<el-image
v-if="previewUrl(item)"
:src="previewUrl(item)"
fit="contain"
class="icon-preview"
/>
<div v-else class="empty-preview empty-preview-sm">暂无图片</div>
<el-upload
action="#"
:auto-upload="false"
:show-file-list="false"
:on-change="(f) => uploadIcon(f, item.icon_key)"
accept="image/jpeg,image/png,image/jpg,image/webp"
>
<el-button size="small" type="primary" :loading="uploadingKey === item.icon_key">
{{ item.is_uploaded ? '更换' : '上传' }}
</el-button>
</el-upload>
</div>
</div>
@@ -235,6 +301,23 @@ const CUSER_MINE_KEYS = new Set([
'cuser_mine_invitees',
])
const COMMON_KEYS = new Set(['mine_pindao', 'icon_refresh'])
const GRAB_CARD_BG_META = [
{ icon_key: 'grab_card_pingtai_bg', label: '接单池-平台订单卡背景' },
{ icon_key: 'grab_card_youzhi_bg', label: '接单池-优质商家卡背景' },
{ icon_key: 'grab_card_shangjia_bg', label: '接单池-普通商家卡背景' },
]
const GRAB_CARD_BG_KEYS = new Set(GRAB_CARD_BG_META.map((i) => i.icon_key))
/** 充值页大背景(未上传也不下发),单独成组方便运营上传 */
const FIGHTER_PAGE_BG_META = [
{ icon_key: 'fighter_recharge_vip_bg', label: '打手会员充值页-顶部背景图' },
{ icon_key: 'fighter_deposit_bg', label: '打手保证金页-顶部背景图' },
{ icon_key: 'fighter_deposit_hero', label: '打手保证金页-卡片右侧装饰图' },
]
const FIGHTER_PAGE_BG_KEYS = new Set(FIGHTER_PAGE_BG_META.map((i) => i.icon_key))
const CONFIG_ONLY_PREVIEW_KEYS = new Set([
...GRAB_CARD_BG_KEYS,
...FIGHTER_PAGE_BG_KEYS,
])
const FIGHTER_BANNER_KEYS = new Set(['fighter_recharge_member', 'fighter_recharge_deposit'])
const FIGHTER_MINE_PAGE_KEYS = new Set([
'fighter_order_pending', 'fighter_order_settling', 'fighter_order_done', 'fighter_order_closed',
@@ -244,9 +327,28 @@ const FIGHTER_MINE_PAGE_KEYS = new Set([
'fighter_complaint_record', 'fighter_mine_punish',
'fighter_mine_copy', 'fighter_mine_edit', 'fighter_mine_qiepian', 'fighter_mine_penalty_tip',
])
function mergeFixedIcons(metaList, fromApi) {
const map = new Map((fromApi || []).map((i) => [i.icon_key, i]))
return metaList.map((meta) => {
const row = map.get(meta.icon_key)
return {
icon_key: meta.icon_key,
label: row?.label || meta.label,
image_path: row?.image_path || '',
is_uploaded: !!row?.is_uploaded,
id: row?.id ?? null,
description: row?.description || '',
is_active: row?.is_active ?? true,
}
})
}
const posterIcons = computed(() => icons.value.filter((i) => POSTER_KEYS.has(i.icon_key)))
const cuserMineIcons = computed(() => icons.value.filter((i) => CUSER_MINE_KEYS.has(i.icon_key)))
const merchantIcons = computed(() => icons.value.filter((i) => i.icon_key.startsWith('merchant_')))
const grabCardBgIcons = computed(() => mergeFixedIcons(GRAB_CARD_BG_META, icons.value))
const fighterPageBgIcons = computed(() => mergeFixedIcons(FIGHTER_PAGE_BG_META, icons.value))
const fighterBannerIcons = computed(() => icons.value.filter((i) => FIGHTER_BANNER_KEYS.has(i.icon_key)))
const fighterMinePageIcons = computed(() => icons.value.filter((i) => FIGHTER_MINE_PAGE_KEYS.has(i.icon_key)))
const fighterMoreFuncIcons = computed(() => icons.value.filter((i) => {
@@ -255,7 +357,7 @@ const fighterMoreFuncIcons = computed(() => icons.value.filter((i) => {
}))
const fighterRechargeIcons = computed(() => icons.value.filter((i) => {
const k = i.icon_key
if (FIGHTER_BANNER_KEYS.has(k)) return false
if (FIGHTER_BANNER_KEYS.has(k) || FIGHTER_PAGE_BG_KEYS.has(k)) return false
return k.startsWith('fighter_recharge_') || k.startsWith('fighter_deposit_')
}))
const commonIcons = computed(() => icons.value.filter((i) => COMMON_KEYS.has(i.icon_key)))
@@ -273,6 +375,13 @@ function fullUrl(path) {
return base + (path.startsWith('/') ? path : path)
}
/** 未实际上传的配置型大图不展示默认路径(会 404 */
function previewUrl(item) {
if (!item) return ''
if (CONFIG_ONLY_PREVIEW_KEYS.has(item.icon_key) && !item.is_uploaded) return ''
return fullUrl(item.image_path)
}
async function loadIcons() {
const res = await request.post(JITUAN_API.miniappIconList, { phone: username.value })
if (res.code === 0) {
@@ -399,6 +508,26 @@ onMounted(async () => {
.icon-key { font-size: 11px; color: #999; margin-bottom: 8px; }
.icon-preview { width: 64px; height: 64px; margin: 8px auto; }
.poster-preview { width: 100%; max-width: 200px; height: 120px; margin: 8px auto; border-radius: 6px; }
.empty-preview {
width: 100%;
max-width: 200px;
height: 120px;
margin: 8px auto;
border-radius: 6px;
border: 1px dashed #d0d7de;
background: #f6f8fa;
color: #8b949e;
display: flex;
align-items: center;
justify-content: center;
font-size: 13px;
}
.empty-preview-sm {
width: 64px;
height: 64px;
max-width: 64px;
font-size: 11px;
}
.lunbo-grid {
display: flex;
flex-wrap: wrap;