feat: 会员管理支持总会员创建、上架包含子会员与装饰图上传

- 集团新建时可勾选总会员
- 俱乐部上架/编辑时多选包含子会员
- 装饰图上传提示与编辑模式说明

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-07-05 18:12:59 +08:00
parent 021d00e932
commit 16b04e3905

View File

@@ -47,7 +47,8 @@
<div class="card-header">
<div class="member-name-wrap">
<div class="member-name">{{ member.jieshao }}</div>
<el-tag v-if="member.trial_enabled" size="small" type="warning" class="trial-tag">体验可售</el-tag>
<el-tag v-if="member.is_bundle" size="small" type="success" class="trial-tag">总会员</el-tag>
<el-tag v-if="member.trial_enabled && !member.is_bundle" size="small" type="warning" class="trial-tag">体验可售</el-tag>
</div>
<div class="member-price">¥{{ formatMoney(member.jiage) }}<span class="days-hint">/{{ member.formal_days || 30 }}</span></div>
<div
@@ -113,10 +114,10 @@
<el-form-item label="组长分成(元)" prop="zuzhangfc">
<el-input-number v-model="currentMember.zuzhangfc" :min="0" :precision="2" :step="1" :disabled="!isEditMode" style="width: 100%" />
</el-form-item>
<el-form-item label="开启体验会员">
<el-form-item label="开启体验会员" v-if="!currentMember.is_bundle">
<el-switch v-model="currentMember.trial_enabled" :disabled="!isEditMode" />
</el-form-item>
<template v-if="currentMember.trial_enabled">
<template v-if="currentMember.trial_enabled && !currentMember.is_bundle">
<el-form-item label="体验价格(元)" prop="trial_price">
<el-input-number v-model="currentMember.trial_price" :min="0.01" :precision="2" :step="1" :disabled="!isEditMode" style="width: 100%" />
</el-form-item>
@@ -133,7 +134,33 @@
<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-alert
v-if="isAllClubScope"
type="warning"
:closable="false"
show-icon
title="装饰图需在具体俱乐部下配置"
description="请先在顶栏切换到具体俱乐部,再点「编辑」上传充值页卡片装饰图/背景图。"
class="member-card-scope-tip"
/>
<template v-else>
<el-form-item v-if="currentMember.is_bundle" label="包含的子会员">
<el-select
v-model="currentMember.included_huiyuan_ids"
multiple
filterable
:disabled="!isEditMode"
placeholder="选择本俱乐部已上架的普通会员"
style="width: 100%"
>
<el-option
v-for="opt in clubSubMemberOptions"
:key="opt.huiyuan_id"
:label="`${opt.jieshao} (${opt.huiyuan_id})`"
:value="opt.huiyuan_id"
/>
</el-select>
</el-form-item>
<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" />
@@ -147,6 +174,7 @@
>
<el-button size="small" type="primary" :loading="cardUploadField === 'card_image'">上传装饰图</el-button>
</el-upload>
<span v-else-if="!currentMember.card_image" class="member-card-hint">点击「编辑」后可上传</span>
</div>
</el-form-item>
<el-form-item label="充值页卡片背景图">
@@ -162,6 +190,7 @@
>
<el-button size="small" type="primary" :loading="cardUploadField === 'card_bg'">上传背景图</el-button>
</el-upload>
<span v-else-if="!currentMember.card_bg" class="member-card-hint">点击「编辑」后可上传</span>
</div>
</el-form-item>
</template>
@@ -201,6 +230,10 @@
<el-form-item label="详细规则介绍" prop="jtjieshao">
<el-input v-model="addForm.jtjieshao" type="textarea" rows="4" placeholder="选填" />
</el-form-item>
<el-form-item label="总会员(包含式)">
<el-switch v-model="addForm.is_bundle" />
<span class="form-hint-inline">开启后可在俱乐部配置包含哪些子会员类型,无体验版</span>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="addDialogVisible = false">取消</el-button>
@@ -223,7 +256,10 @@
class="catalog-item"
@click="openEnableDialog(item)"
>
<div class="catalog-name">{{ item.jieshao }}</div>
<div class="catalog-name">
{{ item.jieshao }}
<el-tag v-if="item.is_bundle" size="small" type="success" style="margin-left:8px">总会员</el-tag>
</div>
<div class="catalog-id">ID: {{ item.huiyuan_id }}</div>
</div>
</div>
@@ -245,6 +281,15 @@
:title="enableTarget.jieshao"
:description="`会员 ID${enableTarget.huiyuan_id}`"
/>
<el-alert
v-if="enableTarget.is_bundle"
type="success"
:closable="false"
show-icon
class="enable-tip"
title="总会员包含式"
description="无体验版请配置价格/天数并选择本俱乐部已上架的普通会员作为包含项"
/>
<el-form :model="enableForm" :rules="enableRules" ref="enableFormRef" label-width="130px">
<el-form-item label="正式价格()" prop="jiage">
<el-input-number v-model="enableForm.jiage" :min="0.01" :precision="2" :step="1" style="width: 100%" />
@@ -258,10 +303,26 @@
<el-form-item label="组长分成()" prop="zuzhangfc">
<el-input-number v-model="enableForm.zuzhangfc" :min="0" :precision="2" :step="1" style="width: 100%" />
</el-form-item>
<el-form-item label="开启体验会员">
<el-form-item v-if="enableTarget.is_bundle" label="包含的子会员" prop="included_huiyuan_ids">
<el-select
v-model="enableForm.included_huiyuan_ids"
multiple
filterable
placeholder="选择本俱乐部已上架的普通会员至少一个"
style="width: 100%"
>
<el-option
v-for="opt in enableSubMemberOptions"
:key="opt.huiyuan_id"
:label="`${opt.jieshao} (${opt.huiyuan_id})`"
:value="opt.huiyuan_id"
/>
</el-select>
</el-form-item>
<el-form-item v-if="!enableTarget.is_bundle" label="开启体验会员">
<el-switch v-model="enableForm.trial_enabled" />
</el-form-item>
<template v-if="enableForm.trial_enabled">
<template v-if="enableForm.trial_enabled && !enableTarget.is_bundle">
<el-form-item label="体验价格()" prop="trial_price">
<el-input-number v-model="enableForm.trial_price" :min="0.01" :precision="2" :step="1" style="width: 100%" />
</el-form-item>
@@ -300,6 +361,16 @@ const memberList = ref([])
const priceNote = ref('')
const clubId = ref('xq')
const isAllClubScope = computed(() => getAdminClubScope() === 'all')
const clubSubMemberOptions = computed(() =>
(memberList.value || []).filter(
(m) => !m.is_bundle && m.huiyuan_id !== currentMember.value?.huiyuan_id,
),
)
const enableSubMemberOptions = computed(() =>
(memberList.value || []).filter(
(m) => !m.is_bundle && m.huiyuan_id !== enableTarget.value?.huiyuan_id,
),
)
const detailDialogVisible = ref(false)
const isEditMode = ref(false)
@@ -371,7 +442,8 @@ function defaultClubPriceForm() {
trial_price: 0,
trial_days: 7,
trial_guanshifc: 0,
trial_zuzhangfc: 0
trial_zuzhangfc: 0,
included_huiyuan_ids: [],
}
}
@@ -389,6 +461,8 @@ function normalizeMember(member) {
trial_zuzhangfc: Number(member.trial_zuzhangfc || 0),
card_image: member.card_image || '',
card_bg: member.card_bg || '',
is_bundle: Boolean(member.is_bundle),
included_huiyuan_ids: Array.isArray(member.included_huiyuan_ids) ? [...member.included_huiyuan_ids] : [],
}
}
@@ -502,6 +576,10 @@ const saveMember = async () => {
if (!validatePriceSplit(m.jiage, m.guanshifc, m.zuzhangfc, m.trial_enabled, m.trial_price, m.trial_guanshifc, m.trial_zuzhangfc)) {
return
}
if (m.is_bundle && (!m.included_huiyuan_ids || m.included_huiyuan_ids.length === 0)) {
ElMessage.error('总会员请至少选择一个包含的子会员')
return
}
try {
await ElMessageBox.confirm('确定保存修改吗?', '确认', {
confirmButtonText: '确认',
@@ -523,6 +601,7 @@ const saveMember = async () => {
jtjieshao: m.jtjieshao || '',
card_image: m.card_image || '',
card_bg: m.card_bg || '',
included_huiyuan_ids: m.is_bundle ? (m.included_huiyuan_ids || []) : undefined,
...buildTrialPayload(m)
})
if (res.code === 0) {
@@ -557,7 +636,8 @@ const openAddDialog = () => {
jiage: 0,
guanshifc: 0,
zuzhangfc: 0,
jtjieshao: ''
jtjieshao: '',
is_bundle: false,
}
addDialogVisible.value = true
}
@@ -594,7 +674,8 @@ const submitAdd = async () => {
jiage: addForm.value.jiage,
guanshifc: addForm.value.guanshifc,
zuzhangfc: addForm.value.zuzhangfc,
jtjieshao: addForm.value.jtjieshao || ''
jtjieshao: addForm.value.jtjieshao || '',
is_bundle: addForm.value.is_bundle,
})
if (res.code === 0) {
ElMessage.success('创建成功')
@@ -640,8 +721,11 @@ const closeCatalogDialog = () => {
}
const openEnableDialog = (item) => {
enableTarget.value = item
enableTarget.value = { ...item, is_bundle: Boolean(item.is_bundle) }
enableForm.value = defaultClubPriceForm()
if (item.is_bundle) {
enableForm.value.trial_enabled = false
}
enableDialogVisible.value = true
catalogDialogVisible.value = false
}
@@ -666,16 +750,32 @@ const submitEnable = async () => {
if (!validatePriceSplit(f.jiage, f.guanshifc, f.zuzhangfc, f.trial_enabled, f.trial_price, f.trial_guanshifc, f.trial_zuzhangfc)) {
return
}
if (enableTarget.value.is_bundle) {
if (!f.included_huiyuan_ids || f.included_huiyuan_ids.length === 0) {
ElMessage.error('总会员请至少选择一个包含的子会员')
return
}
if (enableSubMemberOptions.value.length === 0) {
ElMessage.error('请先将至少一个普通会员上架,再配置总会员')
return
}
}
enabling.value = true
try {
const res = await request.post(JITUAN_API.memberEnable, {
const payload = {
username: username.value,
huiyuan_id: enableTarget.value.huiyuan_id,
jiage: f.jiage,
formal_days: f.formal_days,
guanshifc: f.guanshifc,
zuzhangfc: f.zuzhangfc,
...buildTrialPayload(f)
})
...buildTrialPayload(f),
}
if (enableTarget.value.is_bundle) {
payload.trial_enabled = false
payload.included_huiyuan_ids = f.included_huiyuan_ids
}
const res = await request.post(JITUAN_API.memberEnable, payload)
if (res.code === 0) {
ElMessage.success('上架成功')
enableDialogVisible.value = false
@@ -860,6 +960,18 @@ onMounted(() => {
border-radius: 8px;
background: #0d2137;
}
.member-card-hint {
font-size: 12px;
color: #8ab3cf;
}
.member-card-scope-tip {
margin-bottom: 12px;
}
.form-hint-inline {
margin-left: 8px;
font-size: 12px;
color: #8ab3cf;
}
.no-permission {
text-align: center;
padding: 80px 20px;