Files
Vue3/src/views/product/Turntable.vue
XingQue 33def56c31 feat: 转盘后台改为商品/奖池/控奖,履约商品下拉选择
去掉手填购票商品 ID,运营按名称价格创建转盘商品并配置奖池与控奖。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-22 20:40:03 +08:00

553 lines
18 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="zhuanpan-page">
<el-alert
type="info"
:closable="false"
show-icon
class="tip"
title="转盘 = 一种商品"
description="在此新建转盘商品(名称/价格/类型),再配奖池概率与库存。控奖可指定某用户必中某奖。按顶部俱乐部隔离,不影响其他俱乐部。"
/>
<div class="toolbar">
<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="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="320" fixed="right">
<template #default="{ row }">
<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="createVisible" title="新建转盘商品" width="520px">
<el-form label-width="110px">
<el-form-item label="商品名称" required>
<el-input v-model="createForm.biaoqian" maxlength="45" placeholder="用户看到的购票商品名" />
</el-form-item>
<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="createForm.beizhu" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="createVisible = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="saveCreate">创建</el-button>
</template>
</el-dialog>
<!-- 编辑 -->
<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="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>
<el-table-column label="操作" width="140">
<template #default="{ row }">
<el-button link type="primary" @click="openJpEdit(row)">编辑</el-button>
<el-button link type="danger" @click="delJiangpin(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-dialog>
<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="50" /></el-form-item>
<el-form-item label="中奖权重"><el-input-number v-model="jpForm.quanzhong" :min="0" /></el-form-item>
<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>
<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, 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 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, 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 {
const res = await request.post('/shangpin/adzhuanpanhd', { action: 'list' })
if (res.code === 0) huodongList.value = res.data || []
else ElMessage.error(res.msg || '加载失败')
} finally {
loading.value = false
}
}
async function reloadAll() {
await loadMeta()
await loadHuodong()
}
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: 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('已保存')
editVisible.value = false
loadHuodong()
} else ElMessage.error(res.msg || '保存失败')
} finally {
saving.value = false
}
}
async function toggleHuodong(row) {
const res = await request.post('/shangpin/adzhuanpanhd', { action: 'toggle', id: row.id })
if (res.code === 0) {
ElMessage.success('已更新')
loadHuodong()
} else ElMessage.error(res.msg || '操作失败')
}
async function openJiangpin(row) {
currentHd.value = row
jpVisible.value = true
await loadMeta()
await loadJiangpin()
}
async function loadJiangpin() {
if (!currentHd.value) return
jpLoading.value = true
try {
const res = await request.post('/shangpin/adzhuanpanjp', {
action: 'list',
huodong_id: currentHd.value.id,
})
if (res.code === 0) jiangpinList.value = res.data || []
else ElMessage.error(res.msg || '加载失败')
} finally {
jpLoading.value = false
}
}
function openJpEdit(row) {
if (row) {
Object.assign(jpForm, {
id: row.id,
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,
})
} else {
Object.assign(jpForm, {
id: null,
mingzi: '',
paixu: jiangpinList.value.length,
quanzhong: 1,
kucun: -1,
shangpin_id: null,
tupian_url: '',
shi_xiexie: false,
})
}
jpEditVisible.value = true
}
async function saveJiangpin() {
if (!currentHd.value) return
saving.value = true
try {
const res = await request.post('/shangpin/adzhuanpanjp', {
action: 'save',
huodong_id: currentHd.value.id,
id: jpForm.id,
mingzi: jpForm.mingzi,
paixu: jpForm.paixu,
quanzhong: jpForm.quanzhong,
kucun: jpForm.kucun,
shangpin_id: jpForm.shi_xiexie ? null : (jpForm.shangpin_id || null),
tupian_url: jpForm.tupian_url,
shi_xiexie: jpForm.shi_xiexie,
})
if (res.code === 0) {
ElMessage.success('已保存')
jpEditVisible.value = false
loadJiangpin()
} else ElMessage.error(res.msg || '保存失败')
} finally {
saving.value = false
}
}
async function delJiangpin(row) {
await ElMessageBox.confirm('确认删除该奖品?', '提示')
const res = await request.post('/shangpin/adzhuanpanjp', {
action: 'delete',
huodong_id: currentHd.value.id,
id: row.id,
})
if (res.code === 0) {
ElMessage.success('已删除')
loadJiangpin()
} else ElMessage.error(res.msg || '删除失败')
}
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>
.zhuanpan-page { padding: 8px; }
.tip { margin-bottom: 12px; }
.toolbar { margin-bottom: 12px; display: flex; gap: 8px; }
.form-tip { color: #909399; font-size: 12px; line-height: 1.4; margin-top: 4px; }
</style>