313 lines
11 KiB
Vue
313 lines
11 KiB
Vue
<template>
|
||
<div class="mod-card-pool">
|
||
<!-- 卡池统计 -->
|
||
<el-row :gutter="16" class="stats-row">
|
||
<el-col :span="6">
|
||
<el-card shadow="hover">
|
||
<div class="stat-card">
|
||
<div class="stat-value">{{ stats.total }}</div>
|
||
<div class="stat-label">总卡数</div>
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-card shadow="hover">
|
||
<div class="stat-card">
|
||
<div class="stat-value" style="color: #67c23a;">{{ stats.available }}</div>
|
||
<div class="stat-label">可用(UNUSED)</div>
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-card shadow="hover">
|
||
<div class="stat-card">
|
||
<div class="stat-value" style="color: #e6a23c;">{{ stats.reserved }}</div>
|
||
<div class="stat-label">已预留(RESERVED)</div>
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-card shadow="hover">
|
||
<div class="stat-card">
|
||
<div class="stat-value" style="color: #409eff;">{{ stats.used }}</div>
|
||
<div class="stat-label">已使用(USED)</div>
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<!-- 搜索 + 操作 -->
|
||
<el-form :inline="true" :model="dataForm" @keyup.enter="getDataList()">
|
||
<el-form-item>
|
||
<el-input v-model="dataForm.cardCode" placeholder="卡密/激活码" clearable></el-input>
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-input v-model="dataForm.orderNo" placeholder="关联订单号" clearable></el-input>
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-select v-model="dataForm.status" placeholder="卡状态" clearable>
|
||
<el-option label="未使用" value="UNUSED"></el-option>
|
||
<el-option label="已预留" value="RESERVED"></el-option>
|
||
<el-option label="已使用" value="USED"></el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<org-tree-select v-model="dataForm.partnerId" placeholder="合作商" width="160px" />
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button @click="getDataList()">查询</el-button>
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button type="primary" @click="importVisible = true">导入卡密</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
|
||
<!-- 列表 -->
|
||
<div class="table-page-wrap">
|
||
<el-table :data="dataList" border v-loading="dataListLoading" style="width: 100%;">
|
||
<el-table-column type="index" header-align="center" align="center" width="60" label="序号" />
|
||
<el-table-column prop="cardCode" header-align="center" align="center" min-width="160" label="卡密(激活码)" show-overflow-tooltip />
|
||
<el-table-column prop="thirdCardNo" header-align="center" align="center" min-width="180" label="第三方卡号" show-overflow-tooltip />
|
||
<el-table-column prop="thirdPartyRef" header-align="center" align="center" min-width="180" label="第三方引用(UID)" show-overflow-tooltip />
|
||
<el-table-column prop="partnerId" header-align="center" align="center" width="100" label="合作商" />
|
||
<el-table-column prop="status" header-align="center" align="center" width="100" label="卡状态">
|
||
<template #default="{ row }">
|
||
<el-tag v-if="row.status === 'UNUSED'" size="small" type="success">未使用</el-tag>
|
||
<el-tag v-else-if="row.status === 'RESERVED'" size="small" type="warning">已预留</el-tag>
|
||
<el-tag v-else-if="row.status === 'USED'" size="small" type="primary">已使用</el-tag>
|
||
<el-tag v-else size="small">{{ row.status }}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="activateStatus" header-align="center" align="center" width="100" label="激活状态">
|
||
<template #default="{ row }">
|
||
<el-tag v-if="row.activateStatus === 'PENDING'" size="small" type="info">待激活</el-tag>
|
||
<el-tag v-else-if="row.activateStatus === 'SUCCESS'" size="small" type="success">已激活</el-tag>
|
||
<el-tag v-else-if="row.activateStatus === 'FAILED'" size="small" type="danger">激活失败</el-tag>
|
||
<el-tag v-else size="small">{{ row.activateStatus || '-' }}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="deviceId" header-align="center" align="center" width="90" label="设备" />
|
||
<el-table-column prop="orderNo" header-align="center" align="center" min-width="160" label="关联订单号" show-overflow-tooltip />
|
||
<el-table-column prop="expireAt" header-align="center" align="center" width="170" label="到期时间">
|
||
<template #default="{ row }">{{ row.expireAt || '-' }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="activatedAt" header-align="center" align="center" width="170" label="激活时间">
|
||
<template #default="{ row }">{{ row.activatedAt || '-' }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="createdAt" header-align="center" align="center" width="170" label="创建时间" />
|
||
</el-table>
|
||
<el-pagination
|
||
@size-change="sizeChangeHandle"
|
||
@current-change="currentChangeHandle"
|
||
:current-page="pageIndex"
|
||
:page-sizes="[10, 20, 50, 100]"
|
||
:page-size="pageSize"
|
||
:total="totalPage"
|
||
layout="total, sizes, prev, pager, next, jumper">
|
||
</el-pagination>
|
||
</div>
|
||
|
||
<!-- 导入卡密弹窗 -->
|
||
<el-dialog v-model="importVisible" title="导入天卡卡密" width="600px">
|
||
<el-form :model="importForm" :rules="importRules" ref="importFormRef" label-width="100px">
|
||
<el-form-item label="合作商" prop="partnerId">
|
||
<org-tree-select v-model="importForm.partnerId" placeholder="请选择合作商" />
|
||
</el-form-item>
|
||
<el-form-item label="卡密列表" prop="cardCodesText">
|
||
<el-input
|
||
v-model="importForm.cardCodesText"
|
||
type="textarea"
|
||
:rows="10"
|
||
placeholder="每行一个卡密/激活码,例如: XXXX-XXXX-XXXX YYYY-YYYY-YYYY" />
|
||
<div style="font-size: 12px; color: #999; margin-top: 4px;">
|
||
每行一个橘视厅激活码,系统会自动生成第三方卡号和引用标识
|
||
</div>
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button @click="importVisible = false">取消</el-button>
|
||
<el-button type="primary" :loading="importLoading" @click="submitImport">开始导入</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { reactive, ref, onMounted } from 'vue'
|
||
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
|
||
import {
|
||
getDayCardPoolList,
|
||
getDayCardPoolStats,
|
||
importDayCardPool,
|
||
type DayCardPoolItem,
|
||
type DayCardPoolStats
|
||
} from '@/api/manage/daycard'
|
||
|
||
interface DataForm {
|
||
cardCode: string
|
||
orderNo: string
|
||
status: string
|
||
partnerId: string
|
||
}
|
||
|
||
const dataForm = reactive<DataForm>({
|
||
cardCode: '',
|
||
orderNo: '',
|
||
status: '',
|
||
partnerId: ''
|
||
})
|
||
|
||
const dataList = ref<DayCardPoolItem[]>([])
|
||
const pageIndex = ref(1)
|
||
const pageSize = ref(10)
|
||
const totalPage = ref(0)
|
||
const dataListLoading = ref(false)
|
||
|
||
const stats = reactive<DayCardPoolStats>({
|
||
total: 0,
|
||
available: 0,
|
||
reserved: 0,
|
||
used: 0
|
||
})
|
||
|
||
// 导入弹窗
|
||
const importVisible = ref(false)
|
||
const importLoading = ref(false)
|
||
const importFormRef = ref<FormInstance>()
|
||
|
||
interface ImportForm {
|
||
partnerId: number | null
|
||
cardCodesText: string
|
||
}
|
||
|
||
const importForm = reactive<ImportForm>({
|
||
partnerId: 1,
|
||
cardCodesText: ''
|
||
})
|
||
|
||
const importRules: FormRules = {
|
||
partnerId: [{ required: true, message: '请输入合作商ID', trigger: 'blur' }],
|
||
cardCodesText: [{ required: true, message: '请输入卡密', trigger: 'blur' }]
|
||
}
|
||
|
||
async function loadStats (): Promise<void> {
|
||
try {
|
||
const res = await getDayCardPoolStats()
|
||
if (res && res.code === 0 && res.data?.stats) {
|
||
stats.total = res.data.stats.total || 0
|
||
stats.available = res.data.stats.available || 0
|
||
stats.reserved = res.data.stats.reserved || 0
|
||
stats.used = res.data.stats.used || 0
|
||
}
|
||
} catch {
|
||
// 静默
|
||
}
|
||
}
|
||
|
||
async function getDataList (): Promise<void> {
|
||
dataListLoading.value = true
|
||
try {
|
||
const params: Record<string, unknown> = {
|
||
page: pageIndex.value,
|
||
limit: pageSize.value
|
||
}
|
||
if (dataForm.cardCode) params.cardCode = dataForm.cardCode
|
||
if (dataForm.orderNo) params.orderNo = dataForm.orderNo
|
||
if (dataForm.status) params.status = dataForm.status
|
||
if (dataForm.partnerId) params.partnerId = dataForm.partnerId
|
||
|
||
const res = await getDayCardPoolList(params as any)
|
||
if (res && res.code === 0) {
|
||
dataList.value = res.data?.page?.list || []
|
||
totalPage.value = res.data?.page?.totalCount || 0
|
||
} else {
|
||
dataList.value = []
|
||
totalPage.value = 0
|
||
if (res?.msg) ElMessage.error(res.msg)
|
||
}
|
||
} catch (e: any) {
|
||
dataList.value = []
|
||
totalPage.value = 0
|
||
ElMessage.error(e?.message || '请求异常')
|
||
} finally {
|
||
dataListLoading.value = false
|
||
}
|
||
}
|
||
|
||
function sizeChangeHandle (val: number): void {
|
||
pageSize.value = val
|
||
pageIndex.value = 1
|
||
getDataList()
|
||
}
|
||
|
||
function currentChangeHandle (val: number): void {
|
||
pageIndex.value = val
|
||
getDataList()
|
||
}
|
||
|
||
async function submitImport (): Promise<void> {
|
||
if (!importFormRef.value) return
|
||
await importFormRef.value.validate(async (valid) => {
|
||
if (!valid) return
|
||
const cardCodes = importForm.cardCodesText
|
||
.split('\n')
|
||
.map(s => s.trim())
|
||
.filter(s => s.length > 0)
|
||
if (cardCodes.length === 0) {
|
||
ElMessage.warning('请输入至少一个卡密')
|
||
return
|
||
}
|
||
importLoading.value = true
|
||
try {
|
||
const res = await importDayCardPool({
|
||
partnerId: importForm.partnerId ?? 0,
|
||
cardCodes
|
||
})
|
||
if (res && res.code === 0) {
|
||
const successCount = (res as any).data?.successCount ?? 0
|
||
const totalCount = (res as any).data?.totalCount ?? cardCodes.length
|
||
ElMessage.success(`导入完成:成功 ${successCount} / 总计 ${totalCount}`)
|
||
importVisible.value = false
|
||
importForm.cardCodesText = ''
|
||
getDataList()
|
||
loadStats()
|
||
} else {
|
||
ElMessage.error(res?.msg || '导入失败')
|
||
}
|
||
} catch (e: any) {
|
||
ElMessage.error(e?.message || '导入异常')
|
||
} finally {
|
||
importLoading.value = false
|
||
}
|
||
})
|
||
}
|
||
|
||
onMounted(() => {
|
||
getDataList()
|
||
loadStats()
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.mod-card-pool {
|
||
padding: 10px;
|
||
}
|
||
.stats-row {
|
||
margin-bottom: 16px;
|
||
}
|
||
.stat-card {
|
||
text-align: center;
|
||
padding: 10px 0;
|
||
}
|
||
.stat-value {
|
||
font-size: 28px;
|
||
font-weight: bold;
|
||
color: #303133;
|
||
}
|
||
.stat-label {
|
||
font-size: 13px;
|
||
color: #909399;
|
||
margin-top: 4px;
|
||
}
|
||
</style>
|