init: 橘视厅酒店版会员系统前端

This commit is contained in:
zhaogd
2026-07-26 12:54:46 +08:00
commit 690568861b
243 changed files with 42311 additions and 0 deletions

View File

@@ -0,0 +1,312 @@
<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="每行一个卡密/激活码,例如:&#10;XXXX-XXXX-XXXX&#10;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>