| | |
| | | <script setup lang="ts"> |
| | | import { onMounted, ref } from 'vue' |
| | | import { computed, onMounted, reactive, ref } from 'vue' |
| | | import { useRouter } from 'vue-router' |
| | | import { Download, Plus, Refresh, Search } from '@element-plus/icons-vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { getMovieTicketRules, saveMovieTicketRules } from '../api/admin' |
| | | import type { MovieTicketRule } from '../types/admin' |
| | | import { downloadExcel } from '../utils/excel' |
| | | |
| | | const resetCycles = ['每天', '每周', '每月', '每季', '每年'] |
| | | interface MovieTicketPackageRow { |
| | | id: number | string |
| | | packageNo: string |
| | | packageName: string |
| | | merchants: string |
| | | totalCount: number |
| | | claimedCount: number |
| | | remainCount: number |
| | | status: '正常' | '禁用' |
| | | talentKeyword?: string |
| | | cardLevel?: string |
| | | orderDate?: string |
| | | } |
| | | interface MovieTicketBenefitForm { |
| | | level: string |
| | | enabled: boolean |
| | | claimableCount: string |
| | | resetCycle: string |
| | | } |
| | | |
| | | const router = useRouter() |
| | | const loading = ref(false) |
| | | const saving = ref(false) |
| | | const pageMode = ref<'list' | 'create' | 'view'>('list') |
| | | |
| | | const rules = ref<MovieTicketRule[]>([ |
| | | { |
| | | level: '卓越人才卡', |
| | | enabled: true, |
| | | claimableCount: '', |
| | | resetCycle: '每月', |
| | | }, |
| | | { |
| | | level: '杰出人才卡', |
| | | enabled: true, |
| | | claimableCount: '', |
| | | resetCycle: '每月', |
| | | }, |
| | | { |
| | | level: '菁英人才卡', |
| | | enabled: true, |
| | | claimableCount: '', |
| | | resetCycle: '每月', |
| | | }, |
| | | const cardLevels = ['卓越人才卡', '杰出人才卡', '菁英人才卡', '青年人才卡'] |
| | | const statusOptions = ['正常', '禁用'] |
| | | const resetCycles = ['每天', '每周', '每月', '每季', '每年'] |
| | | |
| | | const filters = reactive({ |
| | | talentKeyword: '', |
| | | cardLevel: '', |
| | | orderTime: [] as string[], |
| | | status: '', |
| | | }) |
| | | |
| | | const pagination = reactive({ |
| | | page: 1, |
| | | size: 10, |
| | | total: 0, |
| | | }) |
| | | |
| | | const movieTicketPackageSource = ref<MovieTicketPackageRow[]>([ |
| | | { |
| | | id: 1, |
| | | packageNo: '123464561', |
| | | packageName: '赵四', |
| | | merchants: 'XXX电影院、XXXX电影院', |
| | | totalCount: 12354, |
| | | claimedCount: 25, |
| | | remainCount: 1564, |
| | | status: '正常', |
| | | talentKeyword: '赵四 13800000000 TC0001', |
| | | cardLevel: '卓越人才卡', |
| | | orderDate: '2026-06-18', |
| | | }, |
| | | ]) |
| | | |
| | | function normalizeRule(rule: MovieTicketRule): MovieTicketRule { |
| | | return { |
| | | ...rule, |
| | | enabled: rule.enabled === true || rule.enabled === 1, |
| | | claimableCount: |
| | | rule.claimableCount === '' || rule.claimableCount === undefined ? undefined : Number(rule.claimableCount), |
| | | resetCycle: rule.resetCycle || '每月', |
| | | } |
| | | const createForm = reactive({ |
| | | packageName: '', |
| | | totalCount: '', |
| | | }) |
| | | |
| | | const benefitForm = ref<MovieTicketBenefitForm[]>(defaultBenefitForm()) |
| | | const isReadonlyMode = computed(() => pageMode.value === 'view') |
| | | |
| | | function defaultBenefitForm(): MovieTicketBenefitForm[] { |
| | | return [ |
| | | { level: '卓越人才卡', enabled: true, claimableCount: '', resetCycle: '每天' }, |
| | | { level: '菁英人才卡', enabled: true, claimableCount: '', resetCycle: '每天' }, |
| | | { level: '杰出人才卡', enabled: true, claimableCount: '', resetCycle: '每天' }, |
| | | ] |
| | | } |
| | | |
| | | async function loadRules() { |
| | | loading.value = true |
| | | try { |
| | | const result = await getMovieTicketRules() |
| | | if (result.length) { |
| | | rules.value = result.map(normalizeRule) |
| | | const filteredRows = computed(() => { |
| | | return movieTicketPackageSource.value.filter((item) => { |
| | | const keywordMatched = !filters.talentKeyword |
| | | || `${item.talentKeyword || ''} ${item.packageName} ${item.packageNo}`.includes(filters.talentKeyword.trim()) |
| | | const levelMatched = !filters.cardLevel || item.cardLevel === filters.cardLevel |
| | | const statusMatched = !filters.status || item.status === filters.status |
| | | const [startDate, endDate] = filters.orderTime |
| | | const timeMatched = !startDate || !endDate || !item.orderDate || (item.orderDate >= startDate && item.orderDate <= endDate) |
| | | return keywordMatched && levelMatched && statusMatched && timeMatched |
| | | }) |
| | | }) |
| | | |
| | | const pagedRows = computed(() => { |
| | | const start = (pagination.page - 1) * pagination.size |
| | | return filteredRows.value.slice(start, start + pagination.size) |
| | | }) |
| | | |
| | | function loadData() { |
| | | loading.value = true |
| | | try { |
| | | pagination.total = filteredRows.value.length |
| | | } finally { |
| | | loading.value = false |
| | | } |
| | | } finally { |
| | | loading.value = false |
| | | } |
| | | } |
| | | |
| | | async function saveRules() { |
| | | saving.value = true |
| | | try { |
| | | rules.value = (await saveMovieTicketRules(rules.value.map(normalizeRule))).map(normalizeRule) |
| | | } finally { |
| | | saving.value = false |
| | | } |
| | | ElMessage.success('电影票规则已保存') |
| | | function search() { |
| | | pagination.page = 1 |
| | | loadData() |
| | | } |
| | | |
| | | onMounted(loadRules) |
| | | function resetFilters() { |
| | | filters.talentKeyword = '' |
| | | filters.cardLevel = '' |
| | | filters.orderTime = [] |
| | | filters.status = '' |
| | | pagination.page = 1 |
| | | loadData() |
| | | } |
| | | |
| | | function openCreate() { |
| | | pageMode.value = 'create' |
| | | resetCreateForm() |
| | | } |
| | | |
| | | function resetCreateForm() { |
| | | createForm.packageName = '' |
| | | createForm.totalCount = '' |
| | | benefitForm.value = defaultBenefitForm() |
| | | } |
| | | |
| | | function openAvailableMerchants() { |
| | | ElMessage.info('可用商家配置暂未接入') |
| | | } |
| | | |
| | | function fillViewForm(row: MovieTicketPackageRow) { |
| | | createForm.packageName = row.packageName |
| | | createForm.totalCount = String(row.totalCount) |
| | | benefitForm.value = defaultBenefitForm().map((item) => ({ |
| | | ...item, |
| | | enabled: item.level === row.cardLevel, |
| | | claimableCount: item.level === row.cardLevel ? String(row.remainCount) : '', |
| | | })) |
| | | } |
| | | |
| | | function saveCreate() { |
| | | if (!createForm.packageName.trim()) { |
| | | ElMessage.warning('请输入票包名称') |
| | | return |
| | | } |
| | | if (!createForm.totalCount.trim()) { |
| | | ElMessage.warning('请输入数量') |
| | | return |
| | | } |
| | | const totalCount = Number(createForm.totalCount) |
| | | if (!Number.isFinite(totalCount) || totalCount <= 0) { |
| | | ElMessage.warning('数量需为大于 0 的数字') |
| | | return |
| | | } |
| | | const invalidBenefit = benefitForm.value.find((item) => item.enabled && !item.claimableCount.trim()) |
| | | if (invalidBenefit) { |
| | | ElMessage.warning(`请输入${invalidBenefit.level}可领数量`) |
| | | return |
| | | } |
| | | |
| | | movieTicketPackageSource.value.unshift({ |
| | | id: Date.now(), |
| | | packageNo: String(Date.now()), |
| | | packageName: createForm.packageName.trim(), |
| | | merchants: '待配置', |
| | | totalCount, |
| | | claimedCount: 0, |
| | | remainCount: totalCount, |
| | | status: '正常', |
| | | talentKeyword: '', |
| | | cardLevel: benefitForm.value.find((item) => item.enabled)?.level || '', |
| | | orderDate: new Date().toISOString().slice(0, 10), |
| | | }) |
| | | pageMode.value = 'list' |
| | | pagination.page = 1 |
| | | loadData() |
| | | ElMessage.success('票包已新增') |
| | | } |
| | | |
| | | function toggleStatus(row: MovieTicketPackageRow) { |
| | | row.status = row.status === '正常' ? '禁用' : '正常' |
| | | ElMessage.success(`已${row.status === '正常' ? '启用' : '禁用'}该票包`) |
| | | loadData() |
| | | } |
| | | |
| | | function viewDetail(row: MovieTicketPackageRow) { |
| | | fillViewForm(row) |
| | | pageMode.value = 'view' |
| | | } |
| | | |
| | | function openReceiveRecords() { |
| | | router.push('/movie-ticket-records') |
| | | } |
| | | |
| | | function backToList() { |
| | | pageMode.value = 'list' |
| | | } |
| | | |
| | | function download() { |
| | | downloadExcel( |
| | | '电影票管理', |
| | | [ |
| | | { title: '票包编号', value: row => row.packageNo }, |
| | | { title: '票包名称', value: row => row.packageName }, |
| | | { title: '适用商家', value: row => row.merchants }, |
| | | { title: '数量', value: row => row.totalCount }, |
| | | { title: '领取数量', value: row => row.claimedCount }, |
| | | { title: '剩余', value: row => row.remainCount }, |
| | | { title: '状态', value: row => row.status }, |
| | | ], |
| | | filteredRows.value, |
| | | ) |
| | | ElMessage.success(`已导出 ${filteredRows.value.length} 条电影票数据`) |
| | | } |
| | | |
| | | onMounted(loadData) |
| | | </script> |
| | | |
| | | <template> |
| | | <div v-loading="loading" class="movie-ticket-config-page"> |
| | | <section class="movie-ticket-config-panel"> |
| | | <h3>人才权益</h3> |
| | | <div v-if="pageMode === 'list'" class="page-stack prototype-page movie-ticket-package-page"> |
| | | <section class="panel"> |
| | | <div class="panel-header"> |
| | | <h3>查询表格</h3> |
| | | </div> |
| | | |
| | | <div class="movie-ticket-rule-list"> |
| | | <div v-for="rule in rules" :key="rule.level" class="movie-ticket-rule-row"> |
| | | <strong>{{ rule.level }}</strong> |
| | | <el-switch v-model="rule.enabled" /> |
| | | <span>可领数量</span> |
| | | <el-input v-model="rule.claimableCount" placeholder="请输入" /> |
| | | <span>张</span> |
| | | <span class="reset-label">重置周期</span> |
| | | <el-select v-model="rule.resetCycle"> |
| | | <el-option v-for="cycle in resetCycles" :key="cycle" :label="cycle" :value="cycle" /> |
| | | </el-select> |
| | | </div> |
| | | </div> |
| | | </section> |
| | | <el-form class="movie-ticket-package-filter-form" :model="filters" label-width="72px"> |
| | | <el-form-item label="持卡人"> |
| | | <el-input v-model="filters.talentKeyword" placeholder="持卡人姓名、手机号、卡号" clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="卡片等级"> |
| | | <el-select v-model="filters.cardLevel" placeholder="全部" clearable> |
| | | <el-option label="全部" value="" /> |
| | | <el-option v-for="level in cardLevels" :key="level" :label="level" :value="level" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="订单时间"> |
| | | <el-date-picker |
| | | v-model="filters.orderTime" |
| | | type="daterange" |
| | | start-placeholder="开始日期" |
| | | end-placeholder="结束日期" |
| | | range-separator="至" |
| | | value-format="YYYY-MM-DD" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="状态"> |
| | | <el-select v-model="filters.status" placeholder="全部" clearable> |
| | | <el-option label="全部" value="" /> |
| | | <el-option v-for="status in statusOptions" :key="status" :label="status" :value="status" /> |
| | | </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="resetFilters">重置</el-button> |
| | | </div> |
| | | </el-form> |
| | | |
| | | <el-button type="primary" class="movie-ticket-save-button" :loading="saving" @click="saveRules">保存</el-button> |
| | | </div> |
| | | <div class="movie-ticket-package-tools"> |
| | | <el-button type="primary" :icon="Plus" @click="openCreate">新增</el-button> |
| | | <el-button :icon="Download" @click="download">下载</el-button> |
| | | </div> |
| | | |
| | | <el-table |
| | | v-loading="loading" |
| | | :data="pagedRows" |
| | | border |
| | | class="movie-ticket-package-table" |
| | | empty-text="暂无电影票数据" |
| | | > |
| | | <el-table-column prop="packageNo" label="票包编号" min-width="120" align="center" /> |
| | | <el-table-column prop="packageName" label="票包名称" min-width="140" align="center" /> |
| | | <el-table-column prop="merchants" label="适用商家" min-width="150" align="center" show-overflow-tooltip /> |
| | | <el-table-column prop="totalCount" label="数量" min-width="90" align="center" /> |
| | | <el-table-column prop="claimedCount" label="领取数量" min-width="110" align="center" /> |
| | | <el-table-column prop="remainCount" label="剩余" min-width="90" align="center" /> |
| | | <el-table-column prop="status" label="状态" min-width="90" align="center" /> |
| | | <el-table-column label="操作" min-width="160" align="center"> |
| | | <template #default="{ row }"> |
| | | <el-button link type="primary" @click="toggleStatus(row)">{{ row.status === '正常' ? '禁用' : '启用' }}</el-button> |
| | | <el-button link type="primary" @click="viewDetail(row)">查看</el-button> |
| | | <el-button link type="primary" @click="openReceiveRecords">领取记录</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <div class="pagination-row"> |
| | | <el-pagination |
| | | v-model:current-page="pagination.page" |
| | | v-model:page-size="pagination.size" |
| | | background |
| | | layout="total, prev, pager, next" |
| | | :pager-count="5" |
| | | :total="pagination.total" |
| | | @change="loadData" |
| | | /> |
| | | </div> |
| | | </section> |
| | | </div> |
| | | |
| | | <div v-else class="page-stack prototype-page movie-ticket-create-page"> |
| | | <section class="panel movie-ticket-create-panel"> |
| | | <h3>票包信息</h3> |
| | | |
| | | <div class="movie-ticket-create-form"> |
| | | <div class="movie-ticket-create-row"> |
| | | <label>票包名称</label> |
| | | <el-input v-if="!isReadonlyMode" v-model="createForm.packageName" placeholder="攀枝花万达" /> |
| | | <span v-else>{{ createForm.packageName || '-' }}</span> |
| | | <label>数量</label> |
| | | <el-input v-if="!isReadonlyMode" v-model="createForm.totalCount" placeholder="888" /> |
| | | <span v-else>{{ createForm.totalCount || '-' }}</span> |
| | | <el-button v-if="!isReadonlyMode" type="primary" @click="openAvailableMerchants">可用商家</el-button> |
| | | <span v-else>可用商家</span> |
| | | </div> |
| | | </div> |
| | | |
| | | <h3>人才权益</h3> |
| | | |
| | | <div class="movie-ticket-benefit-list"> |
| | | <div v-for="item in benefitForm" :key="item.level" class="movie-ticket-benefit-row"> |
| | | <strong>{{ item.level }}</strong> |
| | | <el-switch v-if="!isReadonlyMode" v-model="item.enabled" /> |
| | | <span v-else>{{ item.enabled ? '开启' : '关闭' }}</span> |
| | | <span>可领数量</span> |
| | | <el-input v-if="!isReadonlyMode" v-model="item.claimableCount" placeholder="请输入" /> |
| | | <span v-else>{{ item.claimableCount || '-' }}</span> |
| | | <span>张</span> |
| | | <span>重置周期</span> |
| | | <el-select v-if="!isReadonlyMode" v-model="item.resetCycle"> |
| | | <el-option v-for="cycle in resetCycles" :key="cycle" :label="cycle" :value="cycle" /> |
| | | </el-select> |
| | | <span v-else>{{ item.resetCycle }}</span> |
| | | </div> |
| | | </div> |
| | | </section> |
| | | |
| | | <template v-if="isReadonlyMode"> |
| | | <el-button class="movie-ticket-save-button" @click="backToList">返回</el-button> |
| | | </template> |
| | | <el-button v-else type="primary" class="movie-ticket-save-button" @click="saveCreate">保存</el-button> |
| | | </div> |
| | | </template> |