🎨 新增兑换码功能
This commit is contained in:
80
src/api/cdk/index.js
Normal file
80
src/api/cdk/index.js
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import service from '@/utils/request'
|
||||||
|
|
||||||
|
|
||||||
|
// 获取兑换码库列表
|
||||||
|
export const mkList = (params) => {
|
||||||
|
return service({
|
||||||
|
url: '/cdk/mk/list',
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增兑换码库
|
||||||
|
export const addMk = (data) => {
|
||||||
|
return service({
|
||||||
|
url: '/cdk/mk',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑兑换码库
|
||||||
|
export const editMk = (data) => {
|
||||||
|
return service({
|
||||||
|
url: '/cdk/mk',
|
||||||
|
method: 'put',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 删除兑换码库
|
||||||
|
export const delMk = (data) => {
|
||||||
|
return service({
|
||||||
|
url: '/cdk/mk',
|
||||||
|
method: 'delete',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取兑换码库详情
|
||||||
|
export const mkDetail = (params) => {
|
||||||
|
return service({
|
||||||
|
url: '/cdk/mk',
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// =======================CDK相关===========================
|
||||||
|
|
||||||
|
// 获取兑换码列表
|
||||||
|
export const cdkList = (params) => {
|
||||||
|
return service({
|
||||||
|
url: '/cdk/list',
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 新增兑换码
|
||||||
|
export const addCdk = (data) => {
|
||||||
|
return service({
|
||||||
|
url: '/cdk/generate',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 作废兑换码
|
||||||
|
export const delCdk = (data) => {
|
||||||
|
return service({
|
||||||
|
url: '/cdk',
|
||||||
|
method: 'delete',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@@ -5,6 +5,7 @@
|
|||||||
"/src/view/bot/bot/botForm.vue": "BotForm",
|
"/src/view/bot/bot/botForm.vue": "BotForm",
|
||||||
"/src/view/category/category/category.vue": "Category",
|
"/src/view/category/category/category.vue": "Category",
|
||||||
"/src/view/category/category/categoryForm.vue": "CategoryForm",
|
"/src/view/category/category/categoryForm.vue": "CategoryForm",
|
||||||
|
"/src/view/cdk/index.vue": "CdkManagement",
|
||||||
"/src/view/dashboard/components/banner.vue": "Banner",
|
"/src/view/dashboard/components/banner.vue": "Banner",
|
||||||
"/src/view/dashboard/components/card.vue": "Card",
|
"/src/view/dashboard/components/card.vue": "Card",
|
||||||
"/src/view/dashboard/components/charts-content-numbers.vue": "ChartsContentNumbers",
|
"/src/view/dashboard/components/charts-content-numbers.vue": "ChartsContentNumbers",
|
||||||
@@ -25,6 +26,7 @@
|
|||||||
"/src/view/goods/article/edit.vue": "Edit",
|
"/src/view/goods/article/edit.vue": "Edit",
|
||||||
"/src/view/goods/article/index.vue": "Index",
|
"/src/view/goods/article/index.vue": "Index",
|
||||||
"/src/view/goods/index.vue": "goods",
|
"/src/view/goods/index.vue": "goods",
|
||||||
|
"/src/view/goods/teacher_vip/index.vue": "Index",
|
||||||
"/src/view/goods/vip/index.vue": "VipList",
|
"/src/view/goods/vip/index.vue": "VipList",
|
||||||
"/src/view/init/index.vue": "Init",
|
"/src/view/init/index.vue": "Init",
|
||||||
"/src/view/layout/aside/asideComponent/asyncSubmenu.vue": "AsyncSubmenu",
|
"/src/view/layout/aside/asideComponent/asyncSubmenu.vue": "AsyncSubmenu",
|
||||||
|
661
src/view/cdk/cdkManage.vue
Normal file
661
src/view/cdk/cdkManage.vue
Normal file
@@ -0,0 +1,661 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<!-- 页面头部 -->
|
||||||
|
<div class="page-header">
|
||||||
|
<div class="header-left">
|
||||||
|
<el-button @click="goBack" type="text" class="back-btn">
|
||||||
|
<el-icon><ArrowLeft /></el-icon>
|
||||||
|
返回兑换码库管理
|
||||||
|
</el-button>
|
||||||
|
<h2>{{ libraryInfo.codeName }} - 兑换码管理</h2>
|
||||||
|
</div>
|
||||||
|
<div class="header-right">
|
||||||
|
<el-button type="primary" @click="handleGenerateCdk">生成兑换码</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 搜索区域 -->
|
||||||
|
<div class="gva-search-box">
|
||||||
|
<el-form :inline="true" :model="searchForm" class="demo-form-inline">
|
||||||
|
<el-form-item label="兑换码">
|
||||||
|
<el-input v-model="searchForm.code" placeholder="请输入兑换码" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态">
|
||||||
|
<el-select v-model="searchForm.status" placeholder="请选择状态" clearable>
|
||||||
|
<el-option label="未使用" :value="1" />
|
||||||
|
<el-option label="已使用" :value="2" />
|
||||||
|
<el-option label="已作废" :value="3" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="使用者">
|
||||||
|
<el-input v-model="searchForm.useName" placeholder="请输入使用者名称" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="searchData">查询</el-button>
|
||||||
|
<el-button @click="resetData">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 统计信息 -->
|
||||||
|
<div class="stats-box">
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="6">
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-value">{{ stats.total }}</div>
|
||||||
|
<div class="stat-label">总数量</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<div class="stat-item unused">
|
||||||
|
<div class="stat-value">{{ stats.unused }}</div>
|
||||||
|
<div class="stat-label">未使用</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<div class="stat-item used">
|
||||||
|
<div class="stat-value">{{ stats.used }}</div>
|
||||||
|
<div class="stat-label">已使用</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<div class="stat-item invalid">
|
||||||
|
<div class="stat-value">{{ stats.invalid }}</div>
|
||||||
|
<div class="stat-label">已作废</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 表格区域 -->
|
||||||
|
<div class="gva-table-box">
|
||||||
|
<el-table
|
||||||
|
:data="tableData"
|
||||||
|
v-loading="tableLoading"
|
||||||
|
style="width: 100%"
|
||||||
|
class="gva-table"
|
||||||
|
stripe
|
||||||
|
>
|
||||||
|
<el-table-column prop="ID" label="ID" width="80" />
|
||||||
|
<el-table-column prop="code" label="兑换码" min-width="180">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="code-cell">
|
||||||
|
<span>{{ row.code }}</span>
|
||||||
|
<el-button
|
||||||
|
type="text"
|
||||||
|
@click="copyCode(row.code)"
|
||||||
|
class="copy-btn"
|
||||||
|
title="复制兑换码"
|
||||||
|
>
|
||||||
|
<el-icon><CopyDocument /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="status" label="状态" width="100">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="getStatusType(row.status)">
|
||||||
|
{{ getStatusText(row.status) }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="validDay" label="有效期(天)" width="120" />
|
||||||
|
<el-table-column prop="expireAt" label="到期时间" width="180">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span :class="{ 'text-danger': isExpiringSoon(row.expireAt), 'text-expired': isExpired(row.expireAt) }">
|
||||||
|
{{ formatDate(row.expireAt) }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="useName" label="使用者" width="120">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ row.useName || '-' }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="useAt" label="使用时间" width="180">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ row.useAt ? formatDate(row.useAt) : '-' }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="CreatedAt" label="创建时间" width="180">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ formatDate(row.CreatedAt) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="120" fixed="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button
|
||||||
|
type="text"
|
||||||
|
@click="handleDelete(row)"
|
||||||
|
style="color: #f56c6c"
|
||||||
|
:disabled="row.status !== 1"
|
||||||
|
>
|
||||||
|
作废
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="queryParams.page"
|
||||||
|
v-model:page-size="queryParams.pageSize"
|
||||||
|
:page-sizes="[10, 20, 50, 100]"
|
||||||
|
:total="total"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
@size-change="handleSizeChange"
|
||||||
|
@current-change="handleCurrentChange"
|
||||||
|
class="gva-pagination"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 生成兑换码弹窗 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="generateDialogVisible"
|
||||||
|
title="生成兑换码"
|
||||||
|
width="500px"
|
||||||
|
:before-close="handleGenerateClose"
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
ref="generateFormRef"
|
||||||
|
:model="generateForm"
|
||||||
|
:rules="getDynamicRules()"
|
||||||
|
label-width="120px"
|
||||||
|
>
|
||||||
|
<el-form-item label="兑换码库">
|
||||||
|
<el-input v-model="libraryInfo.codeName" disabled />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="生成数量" prop="number">
|
||||||
|
<el-input-number
|
||||||
|
v-model="generateForm.number"
|
||||||
|
:min="1"
|
||||||
|
:max="1000"
|
||||||
|
placeholder="请输入生成数量"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="有效期" prop="validityType">
|
||||||
|
<el-radio-group v-model="generateForm.validityType" @change="handleValidityTypeChange">
|
||||||
|
<el-radio :label="'permanent'">永久有效</el-radio>
|
||||||
|
<el-radio :label="'custom'">自定义天数</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
<div v-if="generateForm.validityType === 'custom'" style="margin-top: 10px;">
|
||||||
|
<el-input-number
|
||||||
|
v-model="generateForm.customDays"
|
||||||
|
:min="1"
|
||||||
|
:max="3650"
|
||||||
|
placeholder="请输入有效期天数"
|
||||||
|
style="width: 200px"
|
||||||
|
/>
|
||||||
|
<span style="margin-left: 8px;">天</span>
|
||||||
|
</div>
|
||||||
|
<div style="font-size: 12px; color: #999; margin-top: 5px;">
|
||||||
|
{{ generateForm.validityType === 'permanent' ? '兑换码永久有效,不会过期' : '兑换码的有效期,超过此期限将无法使用' }}
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="handleGenerateClose">取消</el-button>
|
||||||
|
<el-button type="primary" @click="submitGenerate">确定</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { cdkList, addCdk, delCdk } from '@/api/cdk/index.js'
|
||||||
|
import { ref, onMounted, computed, watch } from 'vue'
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { ArrowLeft, CopyDocument } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'CdkManage'
|
||||||
|
})
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
// 兑换码库信息
|
||||||
|
const libraryInfo = ref({
|
||||||
|
ID: '',
|
||||||
|
codeName: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表格相关数据
|
||||||
|
const tableLoading = ref(false)
|
||||||
|
const tableData = ref([])
|
||||||
|
const queryParams = ref({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
eid: 0
|
||||||
|
})
|
||||||
|
const total = ref(0)
|
||||||
|
|
||||||
|
// 搜索表单
|
||||||
|
const searchForm = ref({
|
||||||
|
code: '',
|
||||||
|
status: '',
|
||||||
|
useName: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 统计数据
|
||||||
|
const stats = computed(() => {
|
||||||
|
const unused = tableData.value.filter(item => item.status === 1).length
|
||||||
|
const used = tableData.value.filter(item => item.status === 2).length
|
||||||
|
const invalid = tableData.value.filter(item => item.status === 3).length
|
||||||
|
|
||||||
|
return {
|
||||||
|
total: total.value,
|
||||||
|
unused,
|
||||||
|
used,
|
||||||
|
invalid
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 生成兑换码弹窗
|
||||||
|
const generateDialogVisible = ref(false)
|
||||||
|
const generateFormRef = ref(null)
|
||||||
|
const generateForm = ref({
|
||||||
|
eid: 0,
|
||||||
|
number: 1,
|
||||||
|
expirer: 30,
|
||||||
|
validityType: 'custom', // 'permanent' 或 'custom'
|
||||||
|
customDays: 30
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表单验证规则
|
||||||
|
const generateRules = {
|
||||||
|
number: [
|
||||||
|
{ required: true, message: '请输入生成数量', trigger: 'blur' },
|
||||||
|
{ type: 'number', min: 1, max: 1000, message: '生成数量必须在1-1000之间', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
validityType: [
|
||||||
|
{ required: true, message: '请选择有效期类型', trigger: 'change' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 动态验证规则
|
||||||
|
const getDynamicRules = () => {
|
||||||
|
const rules = { ...generateRules }
|
||||||
|
if (generateForm.value.validityType === 'custom') {
|
||||||
|
rules.customDays = [
|
||||||
|
{ required: true, message: '请输入有效期天数', trigger: 'blur' },
|
||||||
|
{ type: 'number', min: 1, max: 3650, message: '有效期必须在1-3650天之间', trigger: 'blur' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
return rules
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化日期
|
||||||
|
const formatDate = (dateStr) => {
|
||||||
|
if (!dateStr) return '-'
|
||||||
|
return new Date(dateStr).toLocaleString('zh-CN')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取状态文本
|
||||||
|
const getStatusText = (status) => {
|
||||||
|
const statusMap = {
|
||||||
|
1: '未使用',
|
||||||
|
2: '已使用',
|
||||||
|
3: '已作废'
|
||||||
|
}
|
||||||
|
return statusMap[status] || '未知'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取状态类型
|
||||||
|
const getStatusType = (status) => {
|
||||||
|
const typeMap = {
|
||||||
|
1: 'success',
|
||||||
|
2: 'warning',
|
||||||
|
3: 'danger'
|
||||||
|
}
|
||||||
|
return typeMap[status] || 'info'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否即将过期(7天内)
|
||||||
|
const isExpiringSoon = (expireTime) => {
|
||||||
|
if (!expireTime) return false
|
||||||
|
const expireDate = new Date(expireTime)
|
||||||
|
const now = new Date()
|
||||||
|
const diffTime = expireDate.getTime() - now.getTime()
|
||||||
|
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
|
||||||
|
return diffDays <= 7 && diffDays > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否已过期
|
||||||
|
const isExpired = (expireTime) => {
|
||||||
|
if (!expireTime) return false
|
||||||
|
const expireDate = new Date(expireTime)
|
||||||
|
const now = new Date()
|
||||||
|
return expireDate.getTime() < now.getTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制兑换码
|
||||||
|
const copyCode = async (code) => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(code)
|
||||||
|
ElMessage.success('兑换码已复制到剪贴板')
|
||||||
|
} catch (error) {
|
||||||
|
// 降级方案
|
||||||
|
const textArea = document.createElement('textarea')
|
||||||
|
textArea.value = code
|
||||||
|
document.body.appendChild(textArea)
|
||||||
|
textArea.select()
|
||||||
|
document.execCommand('copy')
|
||||||
|
document.body.removeChild(textArea)
|
||||||
|
ElMessage.success('兑换码已复制到剪贴板')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索数据
|
||||||
|
const searchData = () => {
|
||||||
|
queryParams.value.page = 1
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置搜索
|
||||||
|
const resetData = () => {
|
||||||
|
searchForm.value = {
|
||||||
|
code: '',
|
||||||
|
status: '',
|
||||||
|
useName: ''
|
||||||
|
}
|
||||||
|
queryParams.value.page = 1
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页处理
|
||||||
|
const handleSizeChange = (size) => {
|
||||||
|
queryParams.value.pageSize = size
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCurrentChange = (page) => {
|
||||||
|
queryParams.value.page = page
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取兑换码列表
|
||||||
|
async function getList() {
|
||||||
|
tableLoading.value = true
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
...queryParams.value,
|
||||||
|
...searchForm.value
|
||||||
|
}
|
||||||
|
const res = await cdkList(params)
|
||||||
|
if (res.code === 0) {
|
||||||
|
tableData.value = res.data.list || []
|
||||||
|
total.value = res.data.total || 0
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.msg || '获取数据失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取兑换码列表失败:', error)
|
||||||
|
ElMessage.error('获取数据失败')
|
||||||
|
} finally {
|
||||||
|
tableLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理有效期类型变化
|
||||||
|
const handleValidityTypeChange = (type) => {
|
||||||
|
if (type === 'permanent') {
|
||||||
|
generateForm.value.expirer = 0
|
||||||
|
} else {
|
||||||
|
generateForm.value.expirer = generateForm.value.customDays || 30
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成兑换码
|
||||||
|
const handleGenerateCdk = () => {
|
||||||
|
generateForm.value = {
|
||||||
|
eid: Number(libraryInfo.value.ID),
|
||||||
|
number: 1,
|
||||||
|
expirer: 30,
|
||||||
|
validityType: 'custom',
|
||||||
|
customDays: 30
|
||||||
|
}
|
||||||
|
generateDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭生成弹窗
|
||||||
|
const handleGenerateClose = () => {
|
||||||
|
generateDialogVisible.value = false
|
||||||
|
generateFormRef.value?.resetFields()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交生成
|
||||||
|
const submitGenerate = async () => {
|
||||||
|
if (!generateFormRef.value) return
|
||||||
|
|
||||||
|
// 动态设置验证规则
|
||||||
|
generateFormRef.value.clearValidate()
|
||||||
|
|
||||||
|
// 根据选择的有效期类型设置expirer值
|
||||||
|
if (generateForm.value.validityType === 'permanent') {
|
||||||
|
generateForm.value.expirer = 0
|
||||||
|
} else {
|
||||||
|
generateForm.value.expirer = generateForm.value.customDays
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证表单
|
||||||
|
let isValid = true
|
||||||
|
|
||||||
|
// 验证生成数量
|
||||||
|
if (!generateForm.value.number || generateForm.value.number < 1 || generateForm.value.number > 1000) {
|
||||||
|
ElMessage.error('生成数量必须在1-1000之间')
|
||||||
|
isValid = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证自定义天数
|
||||||
|
if (generateForm.value.validityType === 'custom') {
|
||||||
|
if (!generateForm.value.customDays || generateForm.value.customDays < 1 || generateForm.value.customDays > 3650) {
|
||||||
|
ElMessage.error('有效期必须在1-3650天之间')
|
||||||
|
isValid = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isValid) {
|
||||||
|
try {
|
||||||
|
// 准备提交的数据
|
||||||
|
const submitData = {
|
||||||
|
eid: generateForm.value.eid,
|
||||||
|
number: generateForm.value.number,
|
||||||
|
expirer: generateForm.value.expirer
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await addCdk(submitData)
|
||||||
|
if (res.code === 0) {
|
||||||
|
ElMessage.success('生成成功')
|
||||||
|
handleGenerateClose()
|
||||||
|
getList()
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.msg || '生成失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error('生成失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 作废兑换码
|
||||||
|
const handleDelete = async (row) => {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm('确定要作废这个兑换码吗?', '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
|
||||||
|
const res = await delCdk({ ID: row.ID })
|
||||||
|
if (res.code === 0) {
|
||||||
|
ElMessage.success('作废成功')
|
||||||
|
getList()
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.msg || '作废失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 用户取消作废
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回上一页
|
||||||
|
const goBack = () => {
|
||||||
|
router.go(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面初始化
|
||||||
|
onMounted(() => {
|
||||||
|
// 从路由参数获取兑换码库信息
|
||||||
|
const { id, name } = route.query
|
||||||
|
if (id && name) {
|
||||||
|
libraryInfo.value = {
|
||||||
|
ID: id,
|
||||||
|
codeName: decodeURIComponent(name)
|
||||||
|
}
|
||||||
|
queryParams.value.eid = id
|
||||||
|
getList()
|
||||||
|
} else {
|
||||||
|
ElMessage.error('缺少兑换码库信息')
|
||||||
|
goBack()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.page-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
.header-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.back-btn {
|
||||||
|
margin-right: 15px;
|
||||||
|
color: #409eff;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #66b1ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.gva-search-box {
|
||||||
|
padding: 20px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-box {
|
||||||
|
padding: 20px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
|
||||||
|
&.unused {
|
||||||
|
background: #f0f9ff;
|
||||||
|
color: #0369a1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.used {
|
||||||
|
background: #fefce8;
|
||||||
|
color: #a16207;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.invalid {
|
||||||
|
background: #fef2f2;
|
||||||
|
color: #dc2626;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.gva-table-box {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gva-table {
|
||||||
|
:deep(.el-table__header) {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__row:hover > td) {
|
||||||
|
background-color: #f5f7fa !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.gva-pagination {
|
||||||
|
margin-top: 20px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-form-inline {
|
||||||
|
.el-form-item {
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-cell {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.copy-btn {
|
||||||
|
margin-left: 8px;
|
||||||
|
padding: 4px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-danger {
|
||||||
|
color: #f56c6c;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-expired {
|
||||||
|
color: #909399;
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
</style>
|
747
src/view/cdk/index.vue
Normal file
747
src/view/cdk/index.vue
Normal file
@@ -0,0 +1,747 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="cdk-library-management">
|
||||||
|
<!-- 兑换码库管理 -->
|
||||||
|
<div class="gva-search-box">
|
||||||
|
<el-form :inline="true" :model="mkSearchForm" class="demo-form-inline">
|
||||||
|
<el-form-item label="兑换码库名称">
|
||||||
|
<el-input v-model="mkSearchForm.codeName" placeholder="请输入兑换码库名称" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="searchMkData">查询</el-button>
|
||||||
|
<el-button @click="resetMkData">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="gva-table-box">
|
||||||
|
<div class="gva-btn-list">
|
||||||
|
<el-button type="primary" @click="handleAddMk">新增兑换码库</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-table
|
||||||
|
:data="mkTableData"
|
||||||
|
v-loading="mkTableLoading"
|
||||||
|
style="width: 100%"
|
||||||
|
class="gva-table"
|
||||||
|
stripe
|
||||||
|
>
|
||||||
|
<el-table-column prop="ID" label="ID" width="80" />
|
||||||
|
<el-table-column prop="codeName" label="兑换码库名称" min-width="150" />
|
||||||
|
<el-table-column prop="type" label="类型" width="100">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ getTypeText(row.type) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="item" label="关联项目" width="150">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ getItemDisplayName(row) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="num" label="兑换码数量" width="120" />
|
||||||
|
<el-table-column prop="no" label="已使用数量" width="120" />
|
||||||
|
<el-table-column label="使用率" width="100">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ getUsageRate(row.no, row.num) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="CreatedAt" label="创建时间" width="180">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ formatDate(row.CreatedAt) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="180" fixed="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button type="text" @click="handleEditMk(row)">编辑</el-button>
|
||||||
|
<el-button type="text" @click="handleManageCdk(row)">兑换码管理</el-button>
|
||||||
|
<el-button type="text" @click="handleDeleteMk(row)" style="color: #f56c6c">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="mkQueryParams.page"
|
||||||
|
v-model:page-size="mkQueryParams.pageSize"
|
||||||
|
:page-sizes="[10, 20, 50, 100]"
|
||||||
|
:total="mkTotal"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
@size-change="handleMkSizeChange"
|
||||||
|
@current-change="handleMkCurrentChange"
|
||||||
|
class="gva-pagination"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 新增/编辑兑换码库弹窗 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="mkDialogVisible"
|
||||||
|
:title="mkDialogTitle"
|
||||||
|
width="500px"
|
||||||
|
:before-close="handleMkClose"
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
ref="mkFormRef"
|
||||||
|
:model="mkForm"
|
||||||
|
:rules="mkRules"
|
||||||
|
label-width="120px"
|
||||||
|
>
|
||||||
|
<el-form-item label="兑换码库名称" prop="codeName">
|
||||||
|
<el-input v-model="mkForm.codeName" placeholder="请输入兑换码库名称" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="类型" prop="type">
|
||||||
|
<el-select v-model="mkForm.type" placeholder="请选择类型" style="width: 100%" @change="handleTypeChange">
|
||||||
|
<el-option label="VIP" :value="1" />
|
||||||
|
<el-option label="讲师VIP" :value="2" />
|
||||||
|
<el-option label="课程" :value="3" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="getItemLabel(mkForm.type)" prop="item">
|
||||||
|
<el-button type="primary" @click="handleSelectItem" style="width: 100%">
|
||||||
|
{{ selectedItemName || '请选择' + getItemLabel(mkForm.type) }}
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="handleMkClose">取消</el-button>
|
||||||
|
<el-button type="primary" @click="submitMk">确定</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 选择项目弹窗 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="selectItemDialogVisible"
|
||||||
|
:title="'选择' + getItemLabel(mkForm.type)"
|
||||||
|
width="900px"
|
||||||
|
:before-close="handleSelectItemClose"
|
||||||
|
>
|
||||||
|
<!-- 搜索框 -->
|
||||||
|
<div style="margin-bottom: 15px;">
|
||||||
|
<el-input
|
||||||
|
v-model="itemSearchKeyword"
|
||||||
|
placeholder="请输入关键词搜索"
|
||||||
|
clearable
|
||||||
|
@input="handleItemSearch"
|
||||||
|
style="width: 300px;"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<el-icon><Search /></el-icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-table
|
||||||
|
:data="filteredItemOptions"
|
||||||
|
v-loading="itemOptionsLoading"
|
||||||
|
style="width: 100%"
|
||||||
|
@row-click="handleItemRowClick"
|
||||||
|
highlight-current-row
|
||||||
|
>
|
||||||
|
<el-table-column prop="ID" label="ID" width="80" />
|
||||||
|
<el-table-column :prop="getItemNameProp(mkForm.type)" :label="getItemLabel(mkForm.type) + '名称'" min-width="200" />
|
||||||
|
|
||||||
|
<!-- VIP套餐相关列 -->
|
||||||
|
<template v-if="mkForm.type === 1">
|
||||||
|
<el-table-column prop="price" label="价格" width="100">
|
||||||
|
<template #default="{ row }">
|
||||||
|
¥{{ row.price }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="duration" label="时长(天)" width="100" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 讲师VIP相关列 -->
|
||||||
|
<template v-if="mkForm.type === 2">
|
||||||
|
<el-table-column prop="price" label="价格" width="100">
|
||||||
|
<template #default="{ row }">
|
||||||
|
¥{{ row.price }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="teacher_name" label="讲师" width="120" />
|
||||||
|
<el-table-column prop="desc" label="描述" width="150" show-overflow-tooltip />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 课程相关列 -->
|
||||||
|
<template v-if="mkForm.type === 3">
|
||||||
|
<el-table-column prop="price" label="价格" width="100">
|
||||||
|
<template #default="{ row }">
|
||||||
|
¥{{ row.price }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="teacherName" label="讲师" width="120" />
|
||||||
|
<el-table-column prop="isFree" label="是否免费" width="100">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="row.isFree === 1 ? 'success' : 'info'">
|
||||||
|
{{ row.isFree === 1 ? '免费' : '付费' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="status" label="状态" width="100">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="row.status === 1 ? 'success' : 'danger'">
|
||||||
|
{{ row.status === 1 ? '上架' : '下架' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</template>
|
||||||
|
</el-table>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="handleSelectItemClose">取消</el-button>
|
||||||
|
<el-button type="primary" @click="confirmSelectItem" :disabled="!selectedItem">确定</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {
|
||||||
|
mkList,
|
||||||
|
addMk,
|
||||||
|
editMk,
|
||||||
|
delMk
|
||||||
|
} from '@/api/cdk/index.js'
|
||||||
|
import { list as vipList } from '@/api/goods/vip.js'
|
||||||
|
import { list as teacherVipList } from '@/api/goods/teacherVip.js'
|
||||||
|
import { list as goodsList } from '@/api/goods/index.js'
|
||||||
|
import { ref, onMounted, computed } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { Search } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'CdkLibraryManagement'
|
||||||
|
})
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
// 格式化日期
|
||||||
|
const formatDate = (dateStr) => {
|
||||||
|
if (!dateStr) return '-'
|
||||||
|
return new Date(dateStr).toLocaleString('zh-CN')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兑换码库相关数据
|
||||||
|
const mkTableLoading = ref(false)
|
||||||
|
const mkTableData = ref([])
|
||||||
|
const mkQueryParams = ref({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 10
|
||||||
|
})
|
||||||
|
const mkTotal = ref(0)
|
||||||
|
const mkSearchForm = ref({
|
||||||
|
codeName: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 兑换码库弹窗相关
|
||||||
|
const mkDialogVisible = ref(false)
|
||||||
|
const mkFormRef = ref(null)
|
||||||
|
const mkForm = ref({
|
||||||
|
ID: '',
|
||||||
|
codeName: '',
|
||||||
|
type: 1,
|
||||||
|
item: ''
|
||||||
|
})
|
||||||
|
const mkDialogTitle = computed(() => mkForm.value.ID ? '编辑兑换码库' : '新增兑换码库')
|
||||||
|
|
||||||
|
// 选择项目相关
|
||||||
|
const selectItemDialogVisible = ref(false)
|
||||||
|
const itemOptions = ref([])
|
||||||
|
const itemOptionsLoading = ref(false)
|
||||||
|
const selectedItem = ref(null)
|
||||||
|
const selectedItemName = ref('')
|
||||||
|
const itemSearchKeyword = ref('')
|
||||||
|
|
||||||
|
// 过滤后的项目选项
|
||||||
|
const filteredItemOptions = computed(() => {
|
||||||
|
if (!itemSearchKeyword.value) {
|
||||||
|
return itemOptions.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyword = itemSearchKeyword.value.toLowerCase()
|
||||||
|
return itemOptions.value.filter(item => {
|
||||||
|
const nameProp = getItemNameProp(mkForm.value.type)
|
||||||
|
const name = item[nameProp]?.toLowerCase() || ''
|
||||||
|
const teacherName = (item.teacher_name || item.teacherName || '').toLowerCase()
|
||||||
|
const desc = (item.desc || '').toLowerCase()
|
||||||
|
|
||||||
|
return name.includes(keyword) ||
|
||||||
|
teacherName.includes(keyword) ||
|
||||||
|
desc.includes(keyword) ||
|
||||||
|
item.ID.toString().includes(keyword)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 项目名称缓存
|
||||||
|
const itemNameCache = ref({})
|
||||||
|
|
||||||
|
// 表单验证规则
|
||||||
|
const mkRules = {
|
||||||
|
codeName: [
|
||||||
|
{ required: true, message: '请输入兑换码库名称', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
type: [
|
||||||
|
{ required: true, message: '请选择类型', trigger: 'change' }
|
||||||
|
],
|
||||||
|
item: [
|
||||||
|
{ required: true, message: '请选择对应的项目', trigger: 'change' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 获取类型文本
|
||||||
|
const getTypeText = (type) => {
|
||||||
|
const typeMap = {
|
||||||
|
1: 'VIP',
|
||||||
|
2: '讲师VIP',
|
||||||
|
3: '课程'
|
||||||
|
}
|
||||||
|
return typeMap[type] || '未知'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取使用率
|
||||||
|
const getUsageRate = (used, total) => {
|
||||||
|
if (!total || total === 0) return '0%'
|
||||||
|
const rate = ((used || 0) / total * 100).toFixed(1)
|
||||||
|
return `${rate}%`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取项目标签
|
||||||
|
const getItemLabel = (type) => {
|
||||||
|
const labelMap = {
|
||||||
|
1: 'VIP套餐',
|
||||||
|
2: '讲师VIP',
|
||||||
|
3: '课程'
|
||||||
|
}
|
||||||
|
return labelMap[type] || '项目'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取项目名称属性
|
||||||
|
const getItemNameProp = (type) => {
|
||||||
|
const propMap = {
|
||||||
|
1: 'name',
|
||||||
|
2: 'title',
|
||||||
|
3: 'title'
|
||||||
|
}
|
||||||
|
return propMap[type] || 'name'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理类型变化
|
||||||
|
const handleTypeChange = (type) => {
|
||||||
|
// 清空已选择的项目
|
||||||
|
mkForm.value.item = ''
|
||||||
|
selectedItem.value = null
|
||||||
|
selectedItemName.value = ''
|
||||||
|
console.log('类型变更为:', getTypeText(type))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理选择项目
|
||||||
|
const handleSelectItem = async () => {
|
||||||
|
if (!mkForm.value.type) {
|
||||||
|
ElMessage.warning('请先选择类型')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await loadItemOptions()
|
||||||
|
selectItemDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载项目选项
|
||||||
|
const loadItemOptions = async () => {
|
||||||
|
itemOptionsLoading.value = true
|
||||||
|
try {
|
||||||
|
let res
|
||||||
|
const params = { page: 1, pageSize: 1000 }
|
||||||
|
|
||||||
|
switch (mkForm.value.type) {
|
||||||
|
case 1: // VIP
|
||||||
|
res = await vipList(params)
|
||||||
|
break
|
||||||
|
case 2: // 讲师VIP
|
||||||
|
res = await teacherVipList(params)
|
||||||
|
break
|
||||||
|
case 3: // 课程
|
||||||
|
res = await goodsList(params)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
itemOptions.value = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.code === 0) {
|
||||||
|
itemOptions.value = res.data.list || []
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.msg || '获取数据失败')
|
||||||
|
itemOptions.value = []
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载项目选项失败:', error)
|
||||||
|
ElMessage.error('加载数据失败')
|
||||||
|
itemOptions.value = []
|
||||||
|
} finally {
|
||||||
|
itemOptionsLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理项目行点击
|
||||||
|
const handleItemRowClick = (row) => {
|
||||||
|
selectedItem.value = row
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认选择项目
|
||||||
|
const confirmSelectItem = () => {
|
||||||
|
if (!selectedItem.value) {
|
||||||
|
ElMessage.warning('请选择一个项目')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mkForm.value.item = selectedItem.value.ID
|
||||||
|
const nameProp = getItemNameProp(mkForm.value.type)
|
||||||
|
selectedItemName.value = selectedItem.value[nameProp]
|
||||||
|
|
||||||
|
handleSelectItemClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭选择项目弹窗
|
||||||
|
const handleSelectItemClose = () => {
|
||||||
|
selectItemDialogVisible.value = false
|
||||||
|
selectedItem.value = null
|
||||||
|
itemSearchKeyword.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理项目搜索
|
||||||
|
const handleItemSearch = () => {
|
||||||
|
// 搜索逻辑已在 computed 中处理
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取项目显示名称
|
||||||
|
const getItemDisplayName = (row) => {
|
||||||
|
const cacheKey = `${row.type}_${row.item}`
|
||||||
|
if (itemNameCache.value[cacheKey]) {
|
||||||
|
return itemNameCache.value[cacheKey]
|
||||||
|
}
|
||||||
|
return `ID: ${row.item}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载项目名称缓存
|
||||||
|
const loadItemNameCache = async () => {
|
||||||
|
try {
|
||||||
|
// 获取所有类型的数据并缓存名称
|
||||||
|
const [vipRes, teacherVipRes, goodsRes] = await Promise.all([
|
||||||
|
vipList({ page: 1, pageSize: 1000 }),
|
||||||
|
teacherVipList({ page: 1, pageSize: 1000 }),
|
||||||
|
goodsList({ page: 1, pageSize: 1000 })
|
||||||
|
])
|
||||||
|
|
||||||
|
// 缓存VIP名称
|
||||||
|
if (vipRes.code === 0 && vipRes.data.list) {
|
||||||
|
vipRes.data.list.forEach(item => {
|
||||||
|
itemNameCache.value[`1_${item.ID}`] = item.name
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 缓存讲师VIP名称
|
||||||
|
if (teacherVipRes.code === 0 && teacherVipRes.data.list) {
|
||||||
|
teacherVipRes.data.list.forEach(item => {
|
||||||
|
itemNameCache.value[`2_${item.ID}`] = item.title
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 缓存课程名称
|
||||||
|
if (goodsRes.code === 0 && goodsRes.data.list) {
|
||||||
|
goodsRes.data.list.forEach(item => {
|
||||||
|
itemNameCache.value[`3_${item.ID}`] = item.title
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载项目名称缓存失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 兑换码库相关方法
|
||||||
|
const searchMkData = () => {
|
||||||
|
mkQueryParams.value.page = 1
|
||||||
|
getMkList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetMkData = () => {
|
||||||
|
mkSearchForm.value = {
|
||||||
|
codeName: ''
|
||||||
|
}
|
||||||
|
mkQueryParams.value = {
|
||||||
|
page: 1,
|
||||||
|
pageSize: 10
|
||||||
|
}
|
||||||
|
getMkList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMkSizeChange = (size) => {
|
||||||
|
mkQueryParams.value.pageSize = size
|
||||||
|
getMkList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMkCurrentChange = (page) => {
|
||||||
|
mkQueryParams.value.page = page
|
||||||
|
getMkList()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getMkList() {
|
||||||
|
mkTableLoading.value = true
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
...mkQueryParams.value,
|
||||||
|
...mkSearchForm.value
|
||||||
|
}
|
||||||
|
const res = await mkList(params)
|
||||||
|
if (res.code === 0) {
|
||||||
|
mkTableData.value = res.data.list || []
|
||||||
|
mkTotal.value = res.data.total || 0
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
mkTableLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取兑换码库选项
|
||||||
|
async function getMkOptions() {
|
||||||
|
try {
|
||||||
|
const res = await mkList({ page: 1, pageSize: 1000 })
|
||||||
|
if (res.code === 0) {
|
||||||
|
mkOptions.value = res.data.list || []
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取兑换码库选项失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleAddMk = () => {
|
||||||
|
mkForm.value = {
|
||||||
|
ID: '',
|
||||||
|
codeName: '',
|
||||||
|
type: 1,
|
||||||
|
item: ''
|
||||||
|
}
|
||||||
|
selectedItemName.value = ''
|
||||||
|
selectedItem.value = null
|
||||||
|
mkDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleEditMk = async (row) => {
|
||||||
|
mkForm.value = {
|
||||||
|
ID: row.ID,
|
||||||
|
codeName: row.codeName,
|
||||||
|
type: row.type,
|
||||||
|
item: row.item
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取已选择项目的名称
|
||||||
|
await loadItemOptions()
|
||||||
|
const nameProp = getItemNameProp(row.type)
|
||||||
|
const selectedItemData = itemOptions.value.find(item => item.ID === row.item)
|
||||||
|
selectedItemName.value = selectedItemData ? selectedItemData[nameProp] : `ID: ${row.item}`
|
||||||
|
|
||||||
|
mkDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleManageCdk = (row) => {
|
||||||
|
// 跳转到兑换码管理页面,传递兑换码库ID和名称
|
||||||
|
router.push({
|
||||||
|
path: 'cdkManage',
|
||||||
|
query: {
|
||||||
|
id: row.ID,
|
||||||
|
name: encodeURIComponent(row.codeName)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const handleDeleteMk = async (row) => {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm('确定要删除这个兑换码库吗?', '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
|
||||||
|
const res = await delMk({ ID: row.ID })
|
||||||
|
if (res.code === 0) {
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
getMkList()
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.msg || '删除失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 用户取消删除
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMkClose = () => {
|
||||||
|
mkDialogVisible.value = false
|
||||||
|
mkFormRef.value?.resetFields()
|
||||||
|
selectedItemName.value = ''
|
||||||
|
selectedItem.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitMk = async () => {
|
||||||
|
if (!mkFormRef.value) return
|
||||||
|
await mkFormRef.value.validate(async (valid) => {
|
||||||
|
if (valid) {
|
||||||
|
try {
|
||||||
|
const isEdit = !!mkForm.value.ID
|
||||||
|
const res = isEdit ? await editMk(mkForm.value) : await addMk(mkForm.value)
|
||||||
|
|
||||||
|
if (res.code === 0) {
|
||||||
|
ElMessage.success(isEdit ? '编辑成功' : '新增成功')
|
||||||
|
handleMkClose()
|
||||||
|
getMkList()
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.msg || (isEdit ? '编辑失败' : '新增失败'))
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error('操作失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兑换码相关方法
|
||||||
|
const searchCdkData = () => {
|
||||||
|
cdkQueryParams.value.page = 1
|
||||||
|
getCdkList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetCdkData = () => {
|
||||||
|
cdkSearchForm.value = {
|
||||||
|
code: '',
|
||||||
|
mkId: '',
|
||||||
|
status: ''
|
||||||
|
}
|
||||||
|
cdkQueryParams.value = {
|
||||||
|
page: 1,
|
||||||
|
pageSize: 10
|
||||||
|
}
|
||||||
|
getCdkList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCdkSizeChange = (size) => {
|
||||||
|
cdkQueryParams.value.pageSize = size
|
||||||
|
getCdkList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCdkCurrentChange = (page) => {
|
||||||
|
cdkQueryParams.value.page = page
|
||||||
|
getCdkList()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getCdkList() {
|
||||||
|
cdkTableLoading.value = true
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
...cdkQueryParams.value,
|
||||||
|
...cdkSearchForm.value
|
||||||
|
}
|
||||||
|
const res = await cdkList(params)
|
||||||
|
if (res.code === 0) {
|
||||||
|
cdkTableData.value = res.data.list || []
|
||||||
|
cdkTotal.value = res.data.total || 0
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
cdkTableLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDeleteCdk = async (row) => {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm('确定要作废这个兑换码吗?', '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
|
||||||
|
const res = await delCdk({ ID: row.ID })
|
||||||
|
if (res.code === 0) {
|
||||||
|
ElMessage.success('作废成功')
|
||||||
|
getCdkList()
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.msg || '作废失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 用户取消作废
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 页面初始化
|
||||||
|
onMounted(() => {
|
||||||
|
loadItemNameCache()
|
||||||
|
getMkList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.cdk-library-management {
|
||||||
|
// 兑换码库管理样式
|
||||||
|
}
|
||||||
|
|
||||||
|
.gva-search-box {
|
||||||
|
padding: 20px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gva-table-box {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
.gva-btn-list {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.gva-table {
|
||||||
|
:deep(.el-table__header) {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__row:hover > td) {
|
||||||
|
background-color: #f5f7fa !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.gva-pagination {
|
||||||
|
margin-top: 20px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-form-inline {
|
||||||
|
.el-form-item {
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-danger {
|
||||||
|
color: #f56c6c;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
Reference in New Issue
Block a user