feat: 转盘后台改为商品/奖池/控奖,履约商品下拉选择

去掉手填购票商品 ID,运营按名称价格创建转盘商品并配置奖池与控奖。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-07-22 20:40:03 +08:00
parent 772d142a40
commit 33def56c31

View File

@@ -5,68 +5,118 @@
:closable="false"
show-icon
class="tip"
title="转盘活动按当前俱乐部隔离"
description="切换顶部俱乐部后,此处活动/奖品只属于该俱乐部。任意俱乐部都可配置;小程序端接好转盘接口即可使用。"
title="转盘 = 一种商品"
description="在此新建转盘商品(名称/价格/类型),再配奖池概率与库存。控奖可指定某用户必中某奖。按顶部俱乐部隔离,不影响其他俱乐部。"
/>
<div class="toolbar">
<el-button type="primary" @click="openHuodong()">新建转盘活动</el-button>
<el-button @click="loadHuodong">刷新</el-button>
<el-button type="primary" @click="openCreate()">新建转盘商品</el-button>
<el-button @click="reloadAll">刷新</el-button>
</div>
<el-table :data="huodongList" v-loading="loading" border stripe>
<el-table-column prop="id" label="ID" width="70" />
<el-table-column prop="mingzi" label="活动名" min-width="140" />
<el-table-column prop="shangpin_id" label="购票商品ID" width="120" />
<el-table-column prop="biaoqian" label="商品名称" min-width="140" />
<el-table-column prop="jiage" label="购票" width="100" />
<el-table-column label="启用" width="90">
<template #default="{ row }">
<el-tag :type="row.qiyong ? 'success' : 'info'">{{ row.qiyong ? '启用' : '停用' }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="beizhu" label="备注" min-width="120" />
<el-table-column label="操作" width="280" fixed="right">
<el-table-column label="操作" width="320" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="openHuodong(row)">编辑</el-button>
<el-button link type="primary" @click="openJiangpin(row)">/概率</el-button>
<el-button link type="primary" @click="openEdit(row)">编辑</el-button>
<el-button link type="primary" @click="openJiangpin(row)"></el-button>
<el-button link type="primary" @click="openKongjiang(row)">控奖</el-button>
<el-button link type="warning" @click="toggleHuodong(row)">{{ row.qiyong ? '停用' : '启用' }}</el-button>
</template>
</el-table-column>
</el-table>
<!-- 活动弹窗 -->
<el-dialog v-model="hdVisible" :title="hdForm.id ? '编辑活动' : '新建活动'" width="480px">
<!-- 新建转盘商品 -->
<el-dialog v-model="createVisible" title="新建转盘商品" width="520px">
<el-form label-width="110px">
<el-form-item label="活动名称">
<el-input v-model="hdForm.mingzi" maxlength="64" />
<el-form-item label="商品名称" required>
<el-input v-model="createForm.biaoqian" maxlength="45" placeholder="用户看到的购票商品名" />
</el-form-item>
<el-form-item label="购票商品ID">
<el-input-number v-model="hdForm.shangpin_id" :min="1" controls-position="right" />
<div class="form-tip">请填本俱乐部商品 ID保存后会标记该商品为转盘购票商品</div>
<el-form-item label="购票价格" required>
<el-input-number v-model="createForm.jiage" :min="0.01" :step="1" :precision="2" controls-position="right" />
</el-form-item>
<el-form-item label="商品类型" required>
<el-select v-model="createForm.leixing_id" filterable placeholder="选择类型" style="width: 100%">
<el-option v-for="t in leixingList" :key="t.id" :label="t.jieshao" :value="t.id" />
</el-select>
</el-form-item>
<el-form-item label="所属专区">
<el-select v-model="createForm.zhuanqu_id" clearable filterable placeholder="可选" style="width: 100%">
<el-option
v-for="z in zhuanquFiltered"
:key="z.id"
:label="z.mingzi"
:value="z.id"
/>
</el-select>
</el-form-item>
<el-form-item label="商品图URL">
<el-input v-model="createForm.tupian_url" placeholder="相对路径或完整 URL" />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="hdForm.beizhu" />
</el-form-item>
<el-form-item label="启用">
<el-switch v-model="hdForm.qiyong" />
<el-input v-model="createForm.beizhu" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="hdVisible = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="saveHuodong">保存</el-button>
<el-button @click="createVisible = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="saveCreate">创建</el-button>
</template>
</el-dialog>
<!-- 奖品弹窗 -->
<el-dialog v-model="jpVisible" :title="'奖品配置 - ' + (currentHd?.mingzi || '')" width="720px">
<!-- 编辑 -->
<el-dialog v-model="editVisible" title="编辑转盘商品" width="520px">
<el-form label-width="110px">
<el-form-item label="商品名称">
<el-input v-model="editForm.biaoqian" maxlength="45" />
</el-form-item>
<el-form-item label="展示名">
<el-input v-model="editForm.mingzi" maxlength="64" />
</el-form-item>
<el-form-item label="购票价格">
<el-input-number v-model="editForm.jiage" :min="0.01" :step="1" :precision="2" controls-position="right" />
</el-form-item>
<el-form-item label="商品图URL">
<el-input v-model="editForm.tupian_url" />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="editForm.beizhu" />
</el-form-item>
<el-form-item label="启用">
<el-switch v-model="editForm.qiyong" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="editVisible = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="saveEdit">保存</el-button>
</template>
</el-dialog>
<!-- 奖池 -->
<el-dialog v-model="jpVisible" :title="'奖池 - ' + (currentHd?.biaoqian || currentHd?.mingzi || '')" width="860px">
<div class="toolbar">
<el-button type="primary" size="small" @click="openJpEdit()">添加奖品</el-button>
<el-button size="small" @click="loadJiangpin">刷新</el-button>
</div>
<el-table :data="jiangpinList" v-loading="jpLoading" border size="small">
<el-table-column prop="paixu" label="格子序" width="80" />
<el-table-column prop="mingzi" label="名称" min-width="120" />
<el-table-column prop="quanzhong" label="权重" width="80" />
<el-table-column prop="shangpin_id" label="履约商品ID" width="110" />
<el-table-column prop="paixu" label="格子序" width="70" />
<el-table-column prop="mingzi" label="名称" min-width="110" />
<el-table-column prop="quanzhong" label="权重" width="70" />
<el-table-column label="库存" width="90">
<template #default="{ row }">{{ row.kucun === -1 ? '不限' : row.kucun }}</template>
</el-table-column>
<el-table-column label="履约商品" min-width="140">
<template #default="{ row }">
{{ row.shi_xiexie ? '—' : (row.shangpin_biaoqian || (row.shangpin_id ? '#' + row.shangpin_id : '未绑')) }}
</template>
</el-table-column>
<el-table-column label="谢谢参与" width="90">
<template #default="{ row }">{{ row.shi_xiexie ? '是' : '否' }}</template>
</el-table-column>
@@ -79,47 +129,146 @@
</el-table>
</el-dialog>
<el-dialog v-model="jpEditVisible" :title="jpForm.id ? '编辑奖品' : '添加奖品'" width="480px">
<el-dialog v-model="jpEditVisible" :title="jpForm.id ? '编辑奖品' : '添加奖品'" width="520px">
<el-form label-width="110px">
<el-form-item label="名称"><el-input v-model="jpForm.mingzi" /></el-form-item>
<el-form-item label="格子序号"><el-input-number v-model="jpForm.paixu" :min="0" :max="20" /></el-form-item>
<el-form-item label="格子序号"><el-input-number v-model="jpForm.paixu" :min="0" :max="50" /></el-form-item>
<el-form-item label="中奖权重"><el-input-number v-model="jpForm.quanzhong" :min="0" /></el-form-item>
<el-form-item label="履约商品ID">
<el-input-number v-model="jpForm.shangpin_id" :min="0" />
<div class="form-tip">中奖后生成抢单池订单的商品谢谢参与可留空</div>
<el-form-item label="库存">
<el-input-number v-model="jpForm.kucun" :min="-1" />
<div class="form-tip">-1 = 不限0 = 普通抽奖抽不中控奖仍可强制</div>
</el-form-item>
<el-form-item label="谢谢参与"><el-switch v-model="jpForm.shi_xiexie" /></el-form-item>
<el-form-item v-if="!jpForm.shi_xiexie" label="履约商品">
<el-select
v-model="jpForm.shangpin_id"
clearable
filterable
placeholder="中奖后进抢池的商品(可选)"
style="width: 100%"
>
<el-option
v-for="g in goodsList"
:key="g.id"
:label="`${g.biaoqian}(¥${g.jiage}`"
:value="g.id"
/>
</el-select>
</el-form-item>
<el-form-item label="图标URL"><el-input v-model="jpForm.tupian_url" placeholder="相对路径或完整URL" /></el-form-item>
<el-form-item label="谢谢参与"><el-switch v-model="jpForm.shi_xiexie" /></el-form-item>
</el-form>
<template #footer>
<el-button @click="jpEditVisible = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="saveJiangpin">保存</el-button>
</template>
</el-dialog>
<!-- 控奖 -->
<el-dialog v-model="kjVisible" :title="'控奖 - ' + (currentHd?.biaoqian || '')" width="760px">
<div class="toolbar">
<el-button type="primary" size="small" @click="openKjEdit()">指定必中</el-button>
<el-button size="small" @click="loadKongjiang">刷新</el-button>
</div>
<el-table :data="kongjiangList" v-loading="kjLoading" border size="small">
<el-table-column prop="yonghuid" label="用户UID" min-width="120" />
<el-table-column prop="jiangpin_mingzi" label="必中奖品" min-width="120" />
<el-table-column prop="cishu" label="剩余次数" width="90" />
<el-table-column label="启用" width="80">
<template #default="{ row }">{{ row.qiyong ? '是' : '否' }}</template>
</el-table-column>
<el-table-column prop="beizhu" label="备注" min-width="100" />
<el-table-column label="操作" width="140">
<template #default="{ row }">
<el-button link type="primary" @click="openKjEdit(row)">编辑</el-button>
<el-button link type="danger" @click="delKongjiang(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-dialog>
<el-dialog v-model="kjEditVisible" :title="kjForm.id ? '编辑控奖' : '指定必中'" width="480px">
<el-form label-width="110px">
<el-form-item label="用户UID" required>
<el-input v-model="kjForm.yonghuid" placeholder="小程序用户 UID" />
</el-form-item>
<el-form-item label="必中奖品" required>
<el-select v-model="kjForm.jiangpin_id" filterable style="width: 100%">
<el-option v-for="p in jiangpinList" :key="p.id" :label="p.mingzi" :value="p.id" />
</el-select>
</el-form-item>
<el-form-item label="次数">
<el-input-number v-model="kjForm.cishu" :min="1" />
</el-form-item>
<el-form-item label="启用"><el-switch v-model="kjForm.qiyong" /></el-form-item>
<el-form-item label="备注"><el-input v-model="kjForm.beizhu" /></el-form-item>
</el-form>
<template #footer>
<el-button @click="kjEditVisible = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="saveKongjiang">保存</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ref, reactive, computed, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import request from '@/utils/request'
const loading = ref(false)
const jpLoading = ref(false)
const kjLoading = ref(false)
const saving = ref(false)
const huodongList = ref([])
const jiangpinList = ref([])
const kongjiangList = ref([])
const leixingList = ref([])
const zhuanquList = ref([])
const goodsList = ref([])
const currentHd = ref(null)
const hdVisible = ref(false)
const hdForm = reactive({ id: null, mingzi: '幸运转盘', shangpin_id: null, beizhu: '', qiyong: true })
const createVisible = ref(false)
const createForm = reactive({
biaoqian: '幸运转盘',
jiage: 9.9,
leixing_id: null,
zhuanqu_id: null,
tupian_url: '',
beizhu: '',
})
const editVisible = ref(false)
const editForm = reactive({
id: null, biaoqian: '', mingzi: '', jiage: 0, tupian_url: '', beizhu: '', qiyong: true,
})
const jpVisible = ref(false)
const jpEditVisible = ref(false)
const jpForm = reactive({
id: null, mingzi: '', paixu: 0, quanzhong: 1, shangpin_id: null, tupian_url: '', shi_xiexie: false
id: null, mingzi: '', paixu: 0, quanzhong: 1, kucun: -1,
shangpin_id: null, tupian_url: '', shi_xiexie: false,
})
const kjVisible = ref(false)
const kjEditVisible = ref(false)
const kjForm = reactive({
id: null, yonghuid: '', jiangpin_id: null, cishu: 1, qiyong: true, beizhu: '',
})
const zhuanquFiltered = computed(() => {
if (!createForm.leixing_id) return zhuanquList.value
return zhuanquList.value.filter((z) => !z.leixing_id || z.leixing_id === createForm.leixing_id)
})
async function loadMeta() {
const res = await request.post('/shangpin/adzhuanpanhd', { action: 'meta' })
if (res.code === 0) {
leixingList.value = res.data?.leixing || []
zhuanquList.value = res.data?.zhuanqu || []
goodsList.value = res.data?.goods || []
}
}
async function loadHuodong() {
loading.value = true
try {
@@ -131,39 +280,78 @@ async function loadHuodong() {
}
}
function openHuodong(row) {
if (row) {
Object.assign(hdForm, {
id: row.id,
mingzi: row.mingzi,
shangpin_id: row.shangpin_id,
beizhu: row.beizhu || '',
qiyong: !!row.qiyong
})
} else {
Object.assign(hdForm, { id: null, mingzi: '幸运转盘', shangpin_id: null, beizhu: '', qiyong: true })
}
hdVisible.value = true
async function reloadAll() {
await loadMeta()
await loadHuodong()
}
async function saveHuodong() {
if (!hdForm.shangpin_id) {
ElMessage.warning('请填写购票商品ID')
function openCreate() {
Object.assign(createForm, {
biaoqian: '幸运转盘',
jiage: 9.9,
leixing_id: leixingList.value[0]?.id || null,
zhuanqu_id: null,
tupian_url: '',
beizhu: '',
})
createVisible.value = true
}
async function saveCreate() {
if (!createForm.biaoqian) {
ElMessage.warning('请填写商品名称')
return
}
if (!createForm.leixing_id) {
ElMessage.warning('请选择商品类型')
return
}
saving.value = true
try {
const res = await request.post('/shangpin/adzhuanpanhd', {
action: 'create_product',
...createForm,
mingzi: createForm.biaoqian,
})
if (res.code === 0) {
ElMessage.success('已创建转盘商品')
createVisible.value = false
await loadHuodong()
} else ElMessage.error(res.msg || '创建失败')
} finally {
saving.value = false
}
}
function openEdit(row) {
Object.assign(editForm, {
id: row.id,
biaoqian: row.biaoqian || row.mingzi,
mingzi: row.mingzi,
jiage: Number(row.jiage) || 0,
tupian_url: row.tupian_url || '',
beizhu: row.beizhu || '',
qiyong: !!row.qiyong,
})
editVisible.value = true
}
async function saveEdit() {
saving.value = true
try {
const res = await request.post('/shangpin/adzhuanpanhd', {
action: 'save',
id: hdForm.id,
mingzi: hdForm.mingzi,
shangpin_id: hdForm.shangpin_id,
beizhu: hdForm.beizhu,
qiyong: hdForm.qiyong
id: editForm.id,
biaoqian: editForm.biaoqian,
mingzi: editForm.mingzi,
jiage: editForm.jiage,
tupian_url: editForm.tupian_url,
beizhu: editForm.beizhu,
qiyong: editForm.qiyong,
})
if (res.code === 0) {
ElMessage.success('已保存')
hdVisible.value = false
editVisible.value = false
loadHuodong()
} else ElMessage.error(res.msg || '保存失败')
} finally {
@@ -182,6 +370,7 @@ async function toggleHuodong(row) {
async function openJiangpin(row) {
currentHd.value = row
jpVisible.value = true
await loadMeta()
await loadJiangpin()
}
@@ -191,7 +380,7 @@ async function loadJiangpin() {
try {
const res = await request.post('/shangpin/adzhuanpanjp', {
action: 'list',
huodong_id: currentHd.value.id
huodong_id: currentHd.value.id,
})
if (res.code === 0) jiangpinList.value = res.data || []
else ElMessage.error(res.msg || '加载失败')
@@ -207,14 +396,21 @@ function openJpEdit(row) {
mingzi: row.mingzi,
paixu: row.paixu,
quanzhong: row.quanzhong,
kucun: row.kucun == null ? -1 : row.kucun,
shangpin_id: row.shangpin_id,
tupian_url: row.tupian_url || '',
shi_xiexie: !!row.shi_xiexie
shi_xiexie: !!row.shi_xiexie,
})
} else {
Object.assign(jpForm, {
id: null, mingzi: '', paixu: jiangpinList.value.length, quanzhong: 1,
shangpin_id: null, tupian_url: '', shi_xiexie: false
id: null,
mingzi: '',
paixu: jiangpinList.value.length,
quanzhong: 1,
kucun: -1,
shangpin_id: null,
tupian_url: '',
shi_xiexie: false,
})
}
jpEditVisible.value = true
@@ -231,9 +427,10 @@ async function saveJiangpin() {
mingzi: jpForm.mingzi,
paixu: jpForm.paixu,
quanzhong: jpForm.quanzhong,
shangpin_id: jpForm.shangpin_id || null,
kucun: jpForm.kucun,
shangpin_id: jpForm.shi_xiexie ? null : (jpForm.shangpin_id || null),
tupian_url: jpForm.tupian_url,
shi_xiexie: jpForm.shi_xiexie
shi_xiexie: jpForm.shi_xiexie,
})
if (res.code === 0) {
ElMessage.success('已保存')
@@ -250,7 +447,7 @@ async function delJiangpin(row) {
const res = await request.post('/shangpin/adzhuanpanjp', {
action: 'delete',
huodong_id: currentHd.value.id,
id: row.id
id: row.id,
})
if (res.code === 0) {
ElMessage.success('已删除')
@@ -258,7 +455,93 @@ async function delJiangpin(row) {
} else ElMessage.error(res.msg || '删除失败')
}
onMounted(loadHuodong)
async function openKongjiang(row) {
currentHd.value = row
kjVisible.value = true
await loadJiangpin()
await loadKongjiang()
}
async function loadKongjiang() {
if (!currentHd.value) return
kjLoading.value = true
try {
const res = await request.post('/shangpin/adzhuanpankj', {
action: 'list',
huodong_id: currentHd.value.id,
})
if (res.code === 0) kongjiangList.value = res.data || []
else ElMessage.error(res.msg || '加载失败')
} finally {
kjLoading.value = false
}
}
function openKjEdit(row) {
if (row) {
Object.assign(kjForm, {
id: row.id,
yonghuid: row.yonghuid,
jiangpin_id: row.jiangpin_id,
cishu: row.cishu,
qiyong: !!row.qiyong,
beizhu: row.beizhu || '',
})
} else {
Object.assign(kjForm, {
id: null,
yonghuid: '',
jiangpin_id: jiangpinList.value[0]?.id || null,
cishu: 1,
qiyong: true,
beizhu: '',
})
}
kjEditVisible.value = true
}
async function saveKongjiang() {
if (!currentHd.value) return
if (!kjForm.yonghuid || !kjForm.jiangpin_id) {
ElMessage.warning('请填写用户与奖品')
return
}
saving.value = true
try {
const res = await request.post('/shangpin/adzhuanpankj', {
action: 'save',
huodong_id: currentHd.value.id,
id: kjForm.id,
yonghuid: kjForm.yonghuid,
jiangpin_id: kjForm.jiangpin_id,
cishu: kjForm.cishu,
qiyong: kjForm.qiyong,
beizhu: kjForm.beizhu,
})
if (res.code === 0) {
ElMessage.success('已保存')
kjEditVisible.value = false
loadKongjiang()
} else ElMessage.error(res.msg || '保存失败')
} finally {
saving.value = false
}
}
async function delKongjiang(row) {
await ElMessageBox.confirm('确认删除该控奖?', '提示')
const res = await request.post('/shangpin/adzhuanpankj', {
action: 'delete',
huodong_id: currentHd.value.id,
id: row.id,
})
if (res.code === 0) {
ElMessage.success('已删除')
loadKongjiang()
} else ElMessage.error(res.msg || '删除失败')
}
onMounted(reloadAll)
</script>
<style scoped>