feat: 打手中心充值横幅与全页图标后台分组配置

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-07-05 17:36:32 +08:00
parent 6ed29939cb
commit 021d00e932
3 changed files with 209 additions and 4 deletions

View File

@@ -296,6 +296,7 @@ export const JITUAN_API = {
penaltyStats: '/jituan/houtai/htglyhqcfsltj',
penaltyList: '/jituan/houtai/hthqfklb',
penaltyAction: '/jituan/houtai/glyclfk',
penaltyPlatformAudit: '/jituan/houtai/glyptshfk',
penaltyCreate: '/jituan/houtai/htfksc',
kefuCfgl: '/jituan/houtai/kefu-cfgl',
adminAssignments: '/jituan/houtai/admin-assignments',
@@ -306,6 +307,7 @@ export const JITUAN_API = {
memberCatalog: '/jituan/houtai/hthycatalog',
memberEnable: '/jituan/houtai/hthysj',
memberDisable: '/jituan/houtai/hthyxj',
memberCardUpload: '/jituan/houtai/hy-card-upload',
identityTagList: '/jituan/houtai/identity-tag/list',
identityTagManage: '/jituan/houtai/identity-tag/manage',
identityTagBindList: '/jituan/houtai/identity-tag/bind-list',

View File

@@ -133,6 +133,38 @@
<el-form-item label="详细规则介绍" prop="jtjieshao">
<el-input v-model="currentMember.jtjieshao" type="textarea" rows="4" :disabled="!isEditMode" />
</el-form-item>
<template v-if="!isAllClubScope">
<el-form-item label="充值页卡片装饰图">
<div class="member-card-upload">
<el-image v-if="currentMember.card_image" :src="memberCardUrl(currentMember.card_image)" fit="contain" class="member-card-preview" />
<el-upload
v-if="isEditMode"
action="#"
:auto-upload="false"
:show-file-list="false"
:on-change="(f) => uploadMemberCard(f, 'card_image')"
accept="image/jpeg,image/png,image/jpg,image/webp"
>
<el-button size="small" type="primary" :loading="cardUploadField === 'card_image'">上传装饰图</el-button>
</el-upload>
</div>
</el-form-item>
<el-form-item label="充值页卡片背景图">
<div class="member-card-upload">
<el-image v-if="currentMember.card_bg" :src="memberCardUrl(currentMember.card_bg)" fit="contain" class="member-card-preview" />
<el-upload
v-if="isEditMode"
action="#"
:auto-upload="false"
:show-file-list="false"
:on-change="(f) => uploadMemberCard(f, 'card_bg')"
accept="image/jpeg,image/png,image/jpg,image/webp"
>
<el-button size="small" type="primary" :loading="cardUploadField === 'card_bg'">上传背景图</el-button>
</el-upload>
</div>
</el-form-item>
</template>
<el-form-item label="购买次数" v-if="!isEditMode">
<el-input :value="currentMember.goumai_cishu || 0" disabled />
</el-form-item>
@@ -274,6 +306,39 @@ const isEditMode = ref(false)
const currentMember = ref({})
const detailFormRef = ref(null)
const saving = ref(false)
const cardUploadField = ref('')
function memberCardUrl(rel) {
if (!rel) return ''
if (rel.startsWith('http')) return rel
const base = window.$ossURL || ''
return base + rel
}
async function uploadMemberCard(file, field) {
if (!file?.raw || !currentMember.value?.huiyuan_id) return
cardUploadField.value = field
try {
const fd = new FormData()
fd.append('username', username.value)
fd.append('huiyuan_id', currentMember.value.huiyuan_id)
fd.append('field', field)
fd.append('file', file.raw)
const res = await request.post(JITUAN_API.memberCardUpload, fd, {
headers: { 'Content-Type': 'multipart/form-data' },
})
if (res.code === 0) {
currentMember.value[field] = res.data?.[field] || ''
ElMessage.success('上传成功')
} else {
ElMessage.error(res.msg || '上传失败')
}
} catch (e) {
ElMessage.error('上传失败')
} finally {
cardUploadField.value = ''
}
}
const addDialogVisible = ref(false)
const addFormRef = ref(null)
@@ -321,7 +386,9 @@ function normalizeMember(member) {
trial_price: Number(member.trial_price || 0),
trial_days: Number(member.trial_days || 0),
trial_guanshifc: Number(member.trial_guanshifc || 0),
trial_zuzhangfc: Number(member.trial_zuzhangfc || 0)
trial_zuzhangfc: Number(member.trial_zuzhangfc || 0),
card_image: member.card_image || '',
card_bg: member.card_bg || '',
}
}
@@ -454,6 +521,8 @@ const saveMember = async () => {
guanshifc: m.guanshifc,
zuzhangfc: m.zuzhangfc,
jtjieshao: m.jtjieshao || '',
card_image: m.card_image || '',
card_bg: m.card_bg || '',
...buildTrialPayload(m)
})
if (res.code === 0) {
@@ -779,6 +848,18 @@ onMounted(() => {
color: #8ab3cf;
margin-top: 4px;
}
.member-card-upload {
display: flex;
flex-direction: column;
gap: 10px;
align-items: flex-start;
}
.member-card-preview {
width: 160px;
height: 100px;
border-radius: 8px;
background: #0d2137;
}
.no-permission {
text-align: center;
padding: 80px 20px;

View File

@@ -36,9 +36,111 @@
</el-card>
<el-card class="section-card" shadow="never">
<template #header><strong>功能图标(按俱乐部)</strong></template>
<template #header><strong>商家端图标</strong></template>
<div class="icon-grid">
<div v-for="item in funcIcons" :key="item.icon_key" class="icon-cell">
<div v-for="item in merchantIcons" :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-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">更换</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 fighterBannerIcons" :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="poster-preview" />
<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">更换横幅</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 fighterMinePageIcons" :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-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">更换</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 fighterMoreFuncIcons" :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-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">更换</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 :src="fullUrl(item.image_path)" fit="contain" class="icon-preview" />
<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">更换</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 commonIcons" :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" />
@@ -105,8 +207,28 @@ const REQUIRED_PERMISSION = '8080a'
const hasPermission = ref(true)
const icons = ref([])
const POSTER_KEYS = new Set(['guanshi_poster_bg', 'zuzhang_poster_bg'])
const COMMON_KEYS = new Set(['mine_pindao', 'icon_refresh'])
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',
'fighter_trade_commission', 'fighter_trade_withdraw', 'fighter_trade_settle', 'fighter_trade_deduct', 'fighter_trade_recharge',
'fighter_invite_subordinate', 'fighter_invite_poster', 'fighter_invite_code',
'fighter_kuaishou_cash', 'fighter_mine_copy', 'fighter_mine_edit', 'fighter_mine_qiepian', 'fighter_mine_penalty_tip',
])
const posterIcons = computed(() => icons.value.filter((i) => POSTER_KEYS.has(i.icon_key)))
const funcIcons = computed(() => icons.value.filter((i) => !POSTER_KEYS.has(i.icon_key)))
const merchantIcons = computed(() => icons.value.filter((i) => i.icon_key.startsWith('merchant_')))
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) => {
const k = i.icon_key
return k.startsWith('fighter_mine_') && !FIGHTER_MINE_PAGE_KEYS.has(k)
}))
const fighterRechargeIcons = computed(() => icons.value.filter((i) => {
const k = i.icon_key
if (FIGHTER_BANNER_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)))
const uploadingKey = ref('')
const pindao = ref({ channel_no: '', title: '频道' })
const pindaoImages = ref([])