<script setup lang="ts">
|
import { computed, onMounted, reactive, ref } from 'vue'
|
import { ArrowLeft, Download, Plus, Refresh, Search } from '@element-plus/icons-vue'
|
import { ElMessage } from 'element-plus'
|
import type { UploadFile } from 'element-plus'
|
import {
|
createTicketPackage,
|
generateTicketPackageCodes,
|
getTicketPackageClaims,
|
getTicketPackageCodes,
|
getTicketPackageMerchantOptions,
|
getTicketPackages,
|
importTicketPackageCodes,
|
updateTicketPackage,
|
updateTicketPackageStatus,
|
uploadAdminImage,
|
voidTicketPackageCode,
|
} from '../api/admin'
|
import { TALENT_CARD_LEVELS } from '../utils/cardLevels'
|
import { downloadExcel } from '../utils/excel'
|
|
type ViewMode = 'list' | 'claims' | 'form' | 'codes'
|
|
const loading = ref(false)
|
const saving = ref(false)
|
const mode = ref<ViewMode>('list')
|
const rows = ref<Record<string, any>[]>([])
|
const merchants = ref<Record<string, any>[]>([])
|
const codeDialogVisible = ref(false)
|
const generateDialogVisible = ref(false)
|
const merchantDialogVisible = ref(false)
|
const editingId = ref<number | string | null>(null)
|
const activePackage = ref<Record<string, any> | null>(null)
|
const selectedPackage = ref<Record<string, any> | null>(null)
|
|
// 电影票只面向三种人才卡开放,筛选与票包权益都不出现"普通游客"。
|
const ticketRuleLevels = TALENT_CARD_LEVELS.filter(level => level !== '普通游客')
|
const resetCycles = ['每天', '每周', '每月', '每年']
|
const packageStatuses = [{ label: '全部', value: '' }, { label: '正常', value: 1 }, { label: '禁用', value: 0 }]
|
const codeStatuses = [
|
{ label: '全部', value: '' },
|
{ label: '可发放', value: 'AVAILABLE' },
|
{ label: '锁定', value: 'LOCKED' },
|
{ label: '已发放', value: 'ISSUED' },
|
{ label: '已使用', value: 'USED' },
|
{ label: '作废', value: 'VOID' },
|
]
|
|
const packageFilters = reactive({
|
keyword: '',
|
merchantKeyword: '',
|
status: '' as string | number,
|
})
|
|
const claimFilters = reactive({
|
talentKeyword: '',
|
cardLevel: '',
|
claimedRange: [] as string[],
|
})
|
|
const codeFilters = reactive({
|
keyword: '',
|
packageKeyword: '',
|
merchantKeyword: '',
|
status: '',
|
})
|
|
const pagination = reactive({
|
page: 1,
|
size: 10,
|
total: 0,
|
})
|
|
const form = reactive({
|
packageName: '',
|
totalQuantity: 1,
|
merchantIds: [] as Array<number | string>,
|
validStart: '',
|
validEnd: '',
|
coverUrl: '',
|
appName: '',
|
appJumpUrl: '',
|
usageText: '',
|
status: 1,
|
sortNo: 0,
|
levelRules: ticketRuleLevels.map(level => ({ talentLevel: level, enabled: 1, claimableCount: 1, resetCycle: '每天' })),
|
})
|
|
const codeForm = reactive({ batchNo: '', codesText: '', remark: '' })
|
const generateForm = reactive({ batchNo: '', codePrefix: 'EX', codeCount: 100, remark: '' })
|
|
const title = computed(() => {
|
if (mode.value === 'claims') return '票包领取记录'
|
if (mode.value === 'form') return '票包信息'
|
if (mode.value === 'codes') return '兑换码码池'
|
return '查询表格'
|
})
|
const selectedMerchantNames = computed(() => {
|
const selected = new Set(form.merchantIds.map(String))
|
return merchants.value
|
.filter(item => selected.has(String(item.id)))
|
.map(item => item.merchantName)
|
.join('、')
|
})
|
|
function text(value: unknown) {
|
return value === undefined || value === null || value === '' ? '-' : String(value)
|
}
|
|
function statusText(status: unknown) {
|
return Number(status) === 0 ? '禁用' : '正常'
|
}
|
|
function codeStatusText(row: Record<string, any>) {
|
return row.statusName || row.codeStatusName || text(row.status)
|
}
|
|
function isVoidedCode(row: Record<string, any>) {
|
const status = String(row.status || row.codeStatus || '').trim().toUpperCase()
|
const statusName = String(row.statusName || row.codeStatusName || '').trim()
|
return status === 'VOID' || statusName === '作废' || statusName === '已作废'
|
}
|
|
function formatTime(value: unknown) {
|
return value ? String(value).replace('T', ' ') : '-'
|
}
|
|
function rowId(row: Record<string, any>) {
|
return row.packageId || row.id
|
}
|
|
function packageParams() {
|
return {
|
page: pagination.page,
|
size: pagination.size,
|
keyword: packageFilters.keyword || undefined,
|
merchantKeyword: packageFilters.merchantKeyword || undefined,
|
status: packageFilters.status === '' ? undefined : packageFilters.status,
|
}
|
}
|
|
function claimParams() {
|
return {
|
page: pagination.page,
|
size: pagination.size,
|
packageId: rowId(selectedPackage.value || {}) || undefined,
|
talentKeyword: claimFilters.talentKeyword || undefined,
|
cardLevel: claimFilters.cardLevel || undefined,
|
claimedStart: claimFilters.claimedRange?.[0],
|
claimedEnd: claimFilters.claimedRange?.[1],
|
}
|
}
|
|
function codeParams() {
|
return {
|
page: pagination.page,
|
size: pagination.size,
|
packageId: rowId(selectedPackage.value || {}) || undefined,
|
keyword: codeFilters.keyword || undefined,
|
packageKeyword: codeFilters.packageKeyword || undefined,
|
merchantKeyword: codeFilters.merchantKeyword || undefined,
|
status: codeFilters.status || undefined,
|
}
|
}
|
|
async function loadData() {
|
if (mode.value === 'form') return
|
loading.value = true
|
try {
|
let result
|
if (mode.value === 'claims') result = await getTicketPackageClaims(claimParams())
|
else if (mode.value === 'codes') result = await getTicketPackageCodes(codeParams())
|
else result = await getTicketPackages(packageParams())
|
rows.value = result.records || []
|
pagination.total = result.total || 0
|
} finally {
|
loading.value = false
|
}
|
}
|
|
async function loadMerchants() {
|
try {
|
// 走票包自己的只读接口:票包运营账号不一定有「商家查询」权限,用原来的商家列表接口会整页报错。
|
merchants.value = (await getTicketPackageMerchantOptions()) || []
|
} catch {
|
merchants.value = []
|
}
|
}
|
|
function search() {
|
pagination.page = 1
|
loadData()
|
}
|
|
function resetListFilters() {
|
Object.assign(packageFilters, { keyword: '', merchantKeyword: '', status: '' })
|
search()
|
}
|
|
function resetClaimFilters() {
|
Object.assign(claimFilters, { talentKeyword: '', cardLevel: '', claimedRange: [] })
|
search()
|
}
|
|
function resetCodeFilters() {
|
Object.assign(codeFilters, { keyword: '', packageKeyword: '', merchantKeyword: '', status: '' })
|
search()
|
}
|
|
function setMode(next: ViewMode, row?: Record<string, any>) {
|
mode.value = next
|
selectedPackage.value = row || selectedPackage.value
|
pagination.page = 1
|
pagination.total = 0
|
rows.value = []
|
if (next === 'form') {
|
resetForm(row)
|
return
|
}
|
loadData()
|
}
|
|
function backToList() {
|
mode.value = 'list'
|
selectedPackage.value = null
|
editingId.value = null
|
pagination.page = 1
|
loadData()
|
}
|
|
function resetForm(row?: Record<string, any>) {
|
editingId.value = row ? rowId(row) : null
|
Object.assign(form, {
|
packageName: row?.packageName || '',
|
totalQuantity: Number(row?.totalQuantity || row?.totalCount || row?.totalCodes || 1),
|
merchantIds: Array.isArray(row?.merchantIds) && row.merchantIds.length ? row.merchantIds.map(String) : row?.merchantId ? [String(row.merchantId)] : [],
|
validStart: row?.validStart || '',
|
validEnd: row?.validEnd || '',
|
coverUrl: row?.coverUrl || '',
|
appName: row?.appName || '',
|
appJumpUrl: row?.appJumpUrl || '',
|
usageText: row?.usageText || '',
|
status: Number(row?.status ?? 1),
|
sortNo: Number(row?.sortNo || 0),
|
})
|
const existingRules = Array.isArray(row?.levelRules) ? row.levelRules : []
|
form.levelRules = ticketRuleLevels.map(level => {
|
const found = existingRules.find((item: Record<string, any>) => item.talentLevel === level)
|
return {
|
talentLevel: level,
|
enabled: Number(found?.enabled ?? 1),
|
claimableCount: Number(found?.claimableCount) > 0 ? Number(found?.claimableCount) : 1,
|
resetCycle: found?.resetCycle || '每天',
|
}
|
})
|
}
|
|
function openCreate() {
|
selectedPackage.value = null
|
setMode('form')
|
}
|
|
function openEdit(row: Record<string, any>) {
|
selectedPackage.value = row
|
setMode('form', row)
|
}
|
|
async function uploadCover(file: UploadFile) {
|
if (!file.raw) return
|
const result = await uploadAdminImage(file.raw)
|
form.coverUrl = result.url
|
ElMessage.success('封面图上传成功')
|
}
|
|
async function save() {
|
if (!form.packageName.trim()) {
|
ElMessage.warning('请输入票包名称')
|
return
|
}
|
const totalQuantity = Number(form.totalQuantity)
|
if (!Number.isInteger(totalQuantity) || totalQuantity < 1) {
|
ElMessage.warning('数量必须为正整数')
|
return
|
}
|
const invalidLevelRule = form.levelRules.find(rule => {
|
const claimableCount = Number(rule.claimableCount)
|
return !Number.isInteger(claimableCount) || claimableCount < 1
|
})
|
if (invalidLevelRule) {
|
ElMessage.warning(`${invalidLevelRule.talentLevel}可领数量必须为正整数`)
|
return
|
}
|
saving.value = true
|
try {
|
const levelRules = form.levelRules.map(rule => ({
|
talentLevel: rule.talentLevel,
|
enabled: rule.enabled,
|
claimableCount: Number(rule.claimableCount),
|
resetCycle: rule.resetCycle || '每天',
|
}))
|
const payload = {
|
packageName: form.packageName.trim(),
|
totalQuantity,
|
merchantId: form.merchantIds[0] ? Number(form.merchantIds[0]) : undefined,
|
merchantIds: form.merchantIds.map(item => Number(item)).filter(Boolean),
|
talentLevels: levelRules.filter(rule => Number(rule.enabled) === 1).map(rule => rule.talentLevel),
|
perUserLimit: Math.max(...levelRules.filter(rule => Number(rule.enabled) === 1).map(rule => rule.claimableCount), 1),
|
claimPrice: 0,
|
validStart: form.validStart || undefined,
|
validEnd: form.validEnd || undefined,
|
coverUrl: form.coverUrl || undefined,
|
appName: form.appName || undefined,
|
appJumpUrl: form.appJumpUrl || undefined,
|
usageText: form.usageText || undefined,
|
status: Number(form.status),
|
sortNo: Number(form.sortNo || 0),
|
levelRules,
|
}
|
if (editingId.value) await updateTicketPackage(editingId.value, payload)
|
else await createTicketPackage(payload)
|
ElMessage.success('保存成功')
|
backToList()
|
} finally {
|
saving.value = false
|
}
|
}
|
|
async function toggleStatus(row: Record<string, any>) {
|
await updateTicketPackageStatus(rowId(row), Number(row.status) === 1 ? '0' : '1')
|
ElMessage.success('状态已更新')
|
await loadData()
|
}
|
|
function openImport(row: Record<string, any>) {
|
activePackage.value = row
|
Object.assign(codeForm, { batchNo: '', codesText: '', remark: '' })
|
codeDialogVisible.value = true
|
}
|
|
function openGenerate(row: Record<string, any>) {
|
activePackage.value = row
|
Object.assign(generateForm, { batchNo: '', codePrefix: 'EX', codeCount: 100, remark: '' })
|
generateDialogVisible.value = true
|
}
|
|
async function importCodes() {
|
if (!activePackage.value) return
|
const codes = codeForm.codesText.split(/[\r\n,,]+/).map(item => item.trim()).filter(Boolean)
|
if (!codes.length) {
|
ElMessage.warning('请输入兑换码')
|
return
|
}
|
await importTicketPackageCodes(rowId(activePackage.value), { batchNo: codeForm.batchNo || undefined, codes, remark: codeForm.remark })
|
ElMessage.success('已导入 ' + codes.length + ' 个兑换码')
|
codeDialogVisible.value = false
|
await loadData()
|
}
|
|
async function generateCodes() {
|
if (!activePackage.value) return
|
const codeCount = Number(generateForm.codeCount || 0)
|
if (codeCount < 1) {
|
ElMessage.warning('请输入生成数量')
|
return
|
}
|
const result = await generateTicketPackageCodes(rowId(activePackage.value), {
|
batchNo: generateForm.batchNo || undefined,
|
codePrefix: generateForm.codePrefix || undefined,
|
codeCount,
|
remark: generateForm.remark,
|
})
|
ElMessage.success('已生成 ' + (result.importedCount || codeCount) + ' 个兑换码')
|
generateDialogVisible.value = false
|
await loadData()
|
}
|
|
async function voidCode(row: Record<string, any>) {
|
if (isVoidedCode(row)) return
|
await voidTicketPackageCode(row.codeId || row.id, '后台作废')
|
ElMessage.success('已作废')
|
await loadData()
|
}
|
|
async function download() {
|
if (mode.value === 'claims') {
|
const result = await getTicketPackageClaims({ ...claimParams(), page: 1, size: 1000 })
|
downloadExcel('票包领取记录', [
|
{ title: '领取编号', value: row => text(row.claimNo || row.id) },
|
{ title: '票包名称', value: row => text(row.packageName) },
|
{ title: '适用商家', value: row => text(row.merchantNames || row.merchantName) },
|
{ title: '持卡人名称', value: row => text(row.talentName) },
|
{ title: '持卡人电话', value: row => text(row.talentMobile) },
|
{ title: '人才等级', value: row => text(row.cardLevel) },
|
{ title: '领取时间', value: row => formatTime(row.claimedAt || row.createdAt) },
|
], result.records || [])
|
return
|
}
|
const result = await getTicketPackages({ ...packageParams(), page: 1, size: 1000 })
|
downloadExcel('电影票票包列表', [
|
{ title: '票包编号', value: row => text(row.packageNo || row.id) },
|
{ title: '票包名称', value: row => text(row.packageName) },
|
{ title: '适用商家', value: row => text(row.merchantNames || row.merchantName) },
|
{ title: '数量', value: row => text(row.totalQuantity) },
|
{ title: '领取数量', value: row => text(row.claimedCount) },
|
{ title: '剩余', value: row => text(row.remainingCount) },
|
{ title: '状态', value: row => statusText(row.status) },
|
], result.records || [])
|
}
|
|
onMounted(() => {
|
loadMerchants()
|
loadData()
|
})
|
</script>
|
|
<template>
|
<div class="page-stack prototype-page movie-ticket-package-page">
|
<section class="panel">
|
<div class="panel-header movie-ticket-panel-header">
|
<h3>{{ title }}</h3>
|
<el-button v-if="mode !== 'list'" :icon="ArrowLeft" @click="backToList">返回</el-button>
|
</div>
|
|
<template v-if="mode === 'list'">
|
<el-form class="movie-ticket-package-filter-form movie-ticket-list-filter-form" :model="packageFilters" label-width="72px">
|
<el-form-item label="票包">
|
<el-input v-model="packageFilters.keyword" placeholder="票包名称、票包编号" clearable />
|
</el-form-item>
|
<el-form-item label="适用商家">
|
<el-input v-model="packageFilters.merchantKeyword" placeholder="商家名称" clearable />
|
</el-form-item>
|
<el-form-item label="状态">
|
<el-select v-model="packageFilters.status" placeholder="全部" clearable>
|
<el-option v-for="item in packageStatuses" :key="String(item.value)" :label="item.label" :value="item.value" />
|
</el-select>
|
</el-form-item>
|
<div class="movie-ticket-package-filter-actions">
|
<el-button type="primary" :icon="Search" @click="search">查询</el-button>
|
<el-button :icon="Refresh" @click="resetListFilters">重置</el-button>
|
</div>
|
</el-form>
|
|
<div class="movie-ticket-package-tools">
|
<el-button type="primary" :icon="Plus" @click="openCreate">新增</el-button>
|
<el-button v-permission="'admin.movie-tickets.export'" :icon="Download" @click="download">下载</el-button>
|
</div>
|
|
<el-table v-loading="loading" :data="rows" border class="movie-ticket-package-table" empty-text="暂无票包">
|
<el-table-column prop="packageNo" label="票包编号" min-width="110" align="center">
|
<template #default="{ row }">{{ text(row.packageNo || row.id) }}</template>
|
</el-table-column>
|
<el-table-column prop="packageName" label="票包名称" min-width="150" align="center" />
|
<el-table-column prop="merchantNames" label="适用商家" min-width="190" align="center" show-overflow-tooltip>
|
<template #default="{ row }">{{ text(row.merchantNames || row.merchantName) }}</template>
|
</el-table-column>
|
<el-table-column prop="totalQuantity" label="数量" min-width="90" align="center" />
|
<el-table-column prop="claimedCount" label="领取数量" min-width="100" align="center" />
|
<el-table-column prop="remainingCount" label="剩余" min-width="90" align="center" />
|
<el-table-column label="状态" min-width="90" align="center">
|
<template #default="{ row }">{{ statusText(row.status) }}</template>
|
</el-table-column>
|
<el-table-column label="操作" width="220" fixed="right" align="center">
|
<template #default="{ row }">
|
<el-button link type="primary" @click="toggleStatus(row)">{{ Number(row.status) === 1 ? '禁用' : '启用' }}</el-button>
|
<el-button link type="primary" @click="openEdit(row)">查看</el-button>
|
<el-button link type="primary" @click="setMode('codes', row)">码池</el-button>
|
<el-button link type="primary" @click="setMode('claims', row)">领取记录</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
</template>
|
|
<template v-else-if="mode === 'claims'">
|
<el-form class="movie-ticket-record-filter-form" :model="claimFilters" label-width="72px">
|
<el-form-item label="持卡人">
|
<el-input v-model="claimFilters.talentKeyword" placeholder="持卡人姓名、手机号、卡号" clearable />
|
</el-form-item>
|
<el-form-item label="卡片等级">
|
<el-select v-model="claimFilters.cardLevel" placeholder="全部" clearable>
|
<el-option label="全部" value="" />
|
<el-option v-for="level in ticketRuleLevels" :key="level" :label="level" :value="level" />
|
</el-select>
|
</el-form-item>
|
<el-form-item label="领取时间">
|
<el-date-picker v-model="claimFilters.claimedRange" type="daterange" start-placeholder="开始日期" end-placeholder="结束日期" range-separator="至" value-format="YYYY-MM-DD" />
|
</el-form-item>
|
<div class="movie-ticket-record-filter-actions">
|
<el-button type="primary" :icon="Search" @click="search">查询</el-button>
|
<el-button :icon="Refresh" @click="resetClaimFilters">重置</el-button>
|
</div>
|
</el-form>
|
|
<div class="movie-ticket-record-tools">
|
<el-button v-permission="'admin.movie-tickets.export'" :icon="Download" @click="download">下载</el-button>
|
</div>
|
|
<el-table v-loading="loading" :data="rows" border class="movie-ticket-record-table" empty-text="暂无领取记录">
|
<el-table-column prop="claimNo" label="领取编号" min-width="130" align="center" />
|
<el-table-column prop="packageName" label="票包名称" min-width="150" align="center" />
|
<el-table-column prop="merchantNames" label="适用商家" min-width="190" align="center" show-overflow-tooltip>
|
<template #default="{ row }">{{ text(row.merchantNames || row.merchantName) }}</template>
|
</el-table-column>
|
<el-table-column prop="talentName" label="持卡人名称" min-width="120" align="center" />
|
<el-table-column prop="talentMobile" label="持卡人电话" min-width="130" align="center" />
|
<el-table-column prop="cardLevel" label="人才等级" min-width="120" align="center" />
|
<el-table-column label="领取时间" min-width="160" align="center">
|
<template #default="{ row }">{{ formatTime(row.claimedAt || row.createdAt) }}</template>
|
</el-table-column>
|
</el-table>
|
</template>
|
|
<template v-else-if="mode === 'codes'">
|
<div class="movie-ticket-code-pool-filters">
|
<el-form class="movie-ticket-package-filter-form movie-ticket-code-pool-filter-form" :model="codeFilters" label-width="72px">
|
<el-form-item label="兑换码">
|
<el-input v-model="codeFilters.keyword" placeholder="码ID、兑换码" clearable />
|
</el-form-item>
|
<el-form-item label="票包">
|
<el-input v-model="codeFilters.packageKeyword" placeholder="票包名称、编号" clearable />
|
</el-form-item>
|
<el-form-item label="适用商家">
|
<el-input v-model="codeFilters.merchantKeyword" placeholder="商家名称" clearable />
|
</el-form-item>
|
<el-form-item label="状态">
|
<el-select v-model="codeFilters.status" placeholder="全部" clearable>
|
<el-option v-for="item in codeStatuses" :key="item.value" :label="item.label" :value="item.value" />
|
</el-select>
|
</el-form-item>
|
<div class="movie-ticket-package-filter-actions">
|
<el-button type="primary" :icon="Search" @click="search">查询</el-button>
|
<el-button :icon="Refresh" @click="resetCodeFilters">重置</el-button>
|
</div>
|
</el-form>
|
</div>
|
<div class="movie-ticket-package-tools movie-ticket-code-pool-tools">
|
<el-button v-permission="'admin.movie-tickets.import'" type="primary" @click="openImport(selectedPackage || {})">导入码</el-button>
|
<el-button @click="openGenerate(selectedPackage || {})">批量生成</el-button>
|
</div>
|
<el-table v-loading="loading" :data="rows" border class="movie-ticket-package-table" empty-text="暂无兑换码">
|
<el-table-column label="码ID" min-width="90" align="center"><template #default="{ row }">{{ text(row.codeId || row.id) }}</template></el-table-column>
|
<el-table-column prop="packageName" label="票包" min-width="150" align="center" />
|
<el-table-column prop="exchangeCode" label="兑换码" min-width="180" align="center" />
|
<el-table-column prop="merchantNames" label="适用商家" min-width="180" align="center" show-overflow-tooltip />
|
<el-table-column label="状态" min-width="100" align="center"><template #default="{ row }">{{ codeStatusText(row) }}</template></el-table-column>
|
<el-table-column prop="lockedTalentName" label="锁定/领取人" min-width="130" align="center" />
|
<el-table-column label="更新时间" min-width="160" align="center"><template #default="{ row }">{{ formatTime(row.updatedAt) }}</template></el-table-column>
|
<el-table-column label="操作" width="100" fixed="right" align="center">
|
<template #default="{ row }">
|
<el-button
|
link
|
:type="isVoidedCode(row) ? 'info' : 'danger'"
|
:disabled="isVoidedCode(row)"
|
@click="voidCode(row)"
|
>作废</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
</template>
|
|
<template v-else>
|
<div class="movie-ticket-create-panel">
|
<h3>票包信息</h3>
|
<el-form class="movie-ticket-create-form" :model="form" label-width="72px">
|
<div class="movie-ticket-create-row">
|
<span>票包名称</span>
|
<el-input v-model="form.packageName" />
|
<span>数量</span>
|
<el-input-number v-model="form.totalQuantity" :min="1" :step="1" :precision="0" controls-position="right" />
|
<el-button type="primary" @click="merchantDialogVisible = true">可用商家</el-button>
|
</div>
|
<div v-if="selectedMerchantNames" class="movie-ticket-selected-merchants">已选商家:{{ selectedMerchantNames }}</div>
|
<div class="movie-ticket-valid-row">
|
<span>有效期</span>
|
<el-date-picker v-model="form.validStart" value-format="YYYY-MM-DD" placeholder="开始日期" />
|
<span>至</span>
|
<el-date-picker v-model="form.validEnd" value-format="YYYY-MM-DD" placeholder="结束日期" />
|
</div>
|
<div class="movie-ticket-cover-row">
|
<span>封面图</span>
|
<el-upload class="movie-ticket-cover-upload" accept="image/*" :auto-upload="false" :show-file-list="false" :on-change="uploadCover">
|
<el-image v-if="form.coverUrl" class="movie-ticket-cover-preview" :src="form.coverUrl" fit="cover" />
|
<div v-else class="movie-ticket-cover-placeholder"><span>+</span><small>上传图片</small></div>
|
</el-upload>
|
</div>
|
<!-- 提示放在行外:封面图那一行只有 58+160px,放里面会被挤成"一字一行"。 -->
|
<div class="admin-image-size-tip">推荐尺寸 150 × 150 px(小程序电影券列表缩略图,1:1 方图)</div>
|
</el-form>
|
|
<h3>人才权益</h3>
|
<div class="movie-ticket-benefit-list">
|
<div v-for="rule in form.levelRules" :key="rule.talentLevel" class="movie-ticket-benefit-row">
|
<strong>{{ rule.talentLevel }}</strong>
|
<el-switch v-model="rule.enabled" :active-value="1" :inactive-value="0" />
|
<span>可领数量</span>
|
<el-input-number v-model="rule.claimableCount" :min="1" :step="1" :precision="0" controls-position="right" placeholder="请输入" />
|
<span>张</span>
|
<span>重置周期</span>
|
<el-select v-model="rule.resetCycle">
|
<el-option v-for="cycle in resetCycles" :key="cycle" :label="cycle" :value="cycle" />
|
</el-select>
|
</div>
|
</div>
|
</div>
|
<div class="movie-ticket-form-actions">
|
<el-button type="primary" :loading="saving" @click="save">保存</el-button>
|
</div>
|
</template>
|
|
<div v-if="mode !== 'form'" class="pagination-row">
|
<el-pagination v-model:current-page="pagination.page" v-model:page-size="pagination.size" background layout="total, sizes, prev, pager, next" :page-sizes="[10, 20, 50]" :pager-count="5" :total="pagination.total" @change="loadData" />
|
</div>
|
</section>
|
|
<el-dialog v-model="codeDialogVisible" title="导入兑换码" width="600px">
|
<el-form :model="codeForm" label-width="90px">
|
<el-form-item label="批次号"><el-input v-model="codeForm.batchNo" /></el-form-item>
|
<el-form-item label="兑换码"><el-input v-model="codeForm.codesText" type="textarea" :rows="8" placeholder="一行一个,或逗号分隔" /></el-form-item>
|
<el-form-item label="备注"><el-input v-model="codeForm.remark" /></el-form-item>
|
</el-form>
|
<template #footer><el-button @click="codeDialogVisible = false">取消</el-button><el-button type="primary" @click="importCodes">导入</el-button></template>
|
</el-dialog>
|
|
<el-dialog v-model="generateDialogVisible" title="批量生成兑换码" width="520px">
|
<el-form :model="generateForm" label-width="90px">
|
<el-form-item label="批次号"><el-input v-model="generateForm.batchNo" placeholder="不填自动生成" /></el-form-item>
|
<el-form-item label="码前缀"><el-input v-model="generateForm.codePrefix" placeholder="默认 EX" /></el-form-item>
|
<el-form-item label="生成数量"><el-input-number v-model="generateForm.codeCount" :min="1" :max="5000" controls-position="right" /></el-form-item>
|
<el-form-item label="备注"><el-input v-model="generateForm.remark" /></el-form-item>
|
</el-form>
|
<template #footer><el-button @click="generateDialogVisible = false">取消</el-button><el-button type="primary" @click="generateCodes">生成</el-button></template>
|
</el-dialog>
|
|
<el-dialog v-model="merchantDialogVisible" title="可用商家" width="560px">
|
<el-select v-model="form.merchantIds" class="movie-ticket-merchant-select" multiple filterable clearable placeholder="请选择适用商家">
|
<el-option v-for="merchant in merchants" :key="merchant.id" :label="merchant.merchantName" :value="String(merchant.id)" />
|
</el-select>
|
<template #footer>
|
<el-button @click="merchantDialogVisible = false">取消</el-button>
|
<el-button type="primary" @click="merchantDialogVisible = false">确定</el-button>
|
</template>
|
</el-dialog>
|
|
</div>
|
</template>
|