卡密 会员

This commit is contained in:
yuhawu 2025-06-11 14:24:09 +08:00
parent 2b2a4dd8d7
commit 68fbee5b68
8 changed files with 1200 additions and 13 deletions

20
src/api/modules/cards.js Normal file
View File

@ -0,0 +1,20 @@
import instance from '../../utils/axios.js'
const cardsApi = {
// 获取卡密列表(分页)
pageQueryCard: (params) => instance.get('/cards/pageQueryCard', { params }),
// 删除卡密
deleteCard: (id) => instance.post('/cards/delete', null, { params: { id } }),
// 获取卡密
createCardSecret: () => instance.get('http://146.56.227.42:8089/cards/createCardSecret'),
// 批量生成卡密
batchCreateCards: (data) => instance.post('/cards/batchCreate', data),
// 修改卡密状态
updateCardStatus: (data) => instance.put('/cards/updateStatus', data)
}
export { cardsApi }

View File

@ -0,0 +1,20 @@
import instance from '../../utils/axios.js'
const settledMemberRecordApi = {
// 获取入驻会员开通记录列表
getSettledMemberRecordList: (params) => instance.get('/settledMember/record/list', { params }),
// 删除入驻会员开通记录
deleteSettledMemberRecord: (id) => instance.post('/settledMember/record/delete', null, { params: { id } }),
// 新增入驻会员开通记录
addSettledMemberRecord: (data) => instance.post('/settledMember/record/add', data),
// 更新入驻会员开通记录
updateSettledMemberRecord: (data) => instance.put('/settledMember/record/update', data),
// 搜索入驻会员开通记录(分页+条件)
searchSettledMemberRecord: (params) => instance.get('/settledMember/record/search', { params }),
}
export { settledMemberRecordApi }

View File

@ -37,6 +37,10 @@
title: '配置列表',
path: '/settledConfig/list'
},
{
title: '会员开通记录',
path: '/settledConfig/memberRecord'
},
]
},
{
@ -67,6 +71,19 @@
}
]
},
{
title: '工具管理',
path: '/tools',
icon: DocIcon,
children: [{
title: '卡密管理',
path: '/tools/cards',
children: [{
title: '卡密列表',
path: '/tools/cards/list'
}]
}]
},
// ...
])
</script>

View File

@ -18,6 +18,11 @@ const routes = [{
component: () => import('@/views/SettledConfig/List.vue'),
meta: { title: '配置列表' }
},
{
path: '/SettledConfig/memberRecord',
component: () => import('@/views/SettledConfig/MemberRecord.vue'),
meta: { title: '会员开通记录' }
},
{
path: '/user/list',
component: () => import('@/views/User/List.vue'),
@ -27,6 +32,11 @@ const routes = [{
path: '/user/edit',
component: () => import('@/views/User/Edit.vue'),
meta: { title: '新增用户' }
},
{
path: '/tools/cards/list',
component: () => import('@/views/Tools/Cards/List.vue'),
meta: { title: '卡密列表' }
}
]
}]

View File

@ -2,10 +2,36 @@ import axios from 'axios'
// 创建 axios
const instance = axios.create({
baseURL: '/api',
timeout: 5000,
timeout: 30000, // 增加超时时间到30秒
// headers: {
// 'Content-Type': 'multipart/form-data'
// }
})
// 请求拦截器
instance.interceptors.request.use(
config => {
// 在发送请求之前做些什么
return config
},
error => {
// 对请求错误做些什么
console.error('请求错误:', error)
return Promise.reject(error)
}
)
// 响应拦截器
instance.interceptors.response.use(
response => {
// 对响应数据做点什么
return response.data
},
error => {
// 对响应错误做点什么
console.error('响应错误:', error)
return Promise.reject(error)
}
)
export default instance

View File

@ -0,0 +1,709 @@
<template>
<div class="list-container">
<!-- 搜索区域 -->
<div class="search-area">
<el-form :inline="true" :model="searchForm">
<el-form-item label="会员ID">
<el-input v-model="searchForm.userId" placeholder="请输入会员ID" clearable />
</el-form-item>
<el-form-item label="会员名称">
<el-input v-model="searchForm.memberName" placeholder="请输入会员名称" clearable />
</el-form-item>
<el-form-item label="开通时间">
<el-date-picker
v-model="searchForm.timeRange"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期1"
value-format="YYYY-MM-DD"
:shortcuts="dateShortcuts"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">搜索</el-button>
<el-button @click="resetSearch">重置</el-button>
</el-form-item>
</el-form>
</div>
<!-- 操作按钮 -->
<div class="action-bar">
<el-button type="primary" @click="handleAdd">新增</el-button>
<el-button @click="refreshData">刷新</el-button>
</div>
<!-- 数据表格 -->
<el-table
ref="tableRef"
:data="tableData"
border
stripe
style="width: 100%;"
@selection-change="handleSelectionChange"
row-key="id"
v-loading="loading"
>
<el-table-column type="selection" align="center" width="55" />
<el-table-column prop="id" align="center" label="记录ID" width="80" />
<el-table-column prop="userId" align="center" label="用户ID" width="100" />
<el-table-column prop="title" align="center" label="会员名称" width="120" />
<el-table-column prop="settledCostKey" align="center" label="入驻标识" width="150" />
<el-table-column align="center" label="到期时间" width="180">
<template #default="{ row }">
{{ formatDateTime(row.expirationDate) }}
</template>
</el-table-column>
<el-table-column align="center" label="佣金设置" width="200">
<template #default="{ row }">
{{ getKickbackTypeText(row.kickbackType) }}:
{{ formatValue(row.kickbackValue, row.kickbackType) }}
</template>
</el-table-column>
<el-table-column align="center" label="资源占用费" width="200">
<template #default="{ row }">
{{ getResourceCostTypeText(row.resourceCostType) }}:
{{ formatValue(row.resourceCostValue, row.resourceCostType) }}
</template>
</el-table-column>
<el-table-column prop="serviceRate" align="center" label="服务费比例" width="120">
<template #default="{ row }">
{{ (row.serviceRate / 10000).toFixed(2) }}%
</template>
</el-table-column>
<el-table-column prop="price" align="center" label="购买价格(元)" width="120">
<template #default="{ row }">
{{ (row.price / 100).toFixed(2) }}
</template>
</el-table-column>
<el-table-column prop="state" align="center" label="状态" width="100">
<template #default="{ row }">
<el-tag :type="getStatusType(row.state)">{{ getStatusText(row.state) }}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" label="操作" fixed="right" width="220">
<template #default="{ row }">
<el-button size="small" type="primary" @click="handleEdit(row)">编辑</el-button>
<el-button
size="small"
type="danger"
@click="handleDelete(row)"
:disabled="row.state === 0"
>删除</el-button>
<el-button
v-if="row.state === 1"
size="small"
type="warning"
@click="handleChangeStatus(row, 0)"
>停用</el-button>
<el-button
v-if="row.state === 0"
size="small"
type="success"
@click="handleChangeStatus(row, 1)"
>启用</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-container">
<el-pagination
v-model:current-page="pagination.current"
v-model:page-size="pagination.size"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="pagination.total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
<!-- 新增/编辑对话框 -->
<el-dialog
:title="dialogType === 'add' ? '新增入驻会员开通记录' : '编辑入驻会员开通记录'"
v-model="dialogVisible"
width="600px"
:close-on-click-modal="false"
>
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="120px"
label-position="right"
>
<el-form-item label="会员ID" prop="userId">
<el-input v-model="formData.userId" placeholder="请输入会员ID" />
</el-form-item>
<el-form-item label="会员名称" prop="title">
<el-input v-model="formData.title" placeholder="请输入会员名称" maxlength="20" show-word-limit />
</el-form-item>
<el-form-item label="配置选择" prop="settledCostConfigId">
<el-select v-model="formData.settledCostConfigId" placeholder="请选择配置" @change="handleConfigChange">
<el-option
v-for="item in configOptions"
:key="item.id"
:label="item.title"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="开通时间" prop="openTime">
<el-date-picker
v-model="formData.openTime"
type="datetime"
placeholder="选择开通时间"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item label="到期时间" prop="expirationDate">
<el-date-picker
v-model="formData.expirationDate"
type="datetime"
placeholder="选择到期时间"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item label="支付金额(元)" prop="price">
<el-input-number
v-model="formData.price"
:min="0"
:precision="2"
:step="10"
placeholder="请输入支付金额"
/>
</el-form-item>
<el-form-item label="状态" prop="state">
<el-select v-model="formData.state" placeholder="请选择状态">
<el-option label="已停用" :value="0" />
<el-option label="已启用" :value="1" />
</el-select>
</el-form-item>
<el-form-item label="佣金类型" prop="kickbackType">
<el-select v-model="formData.kickbackType" placeholder="请选择佣金类型">
<el-option label="预留" :value="0" />
<el-option label="提点" :value="1" />
<el-option label="固定费用" :value="2" />
</el-select>
</el-form-item>
<el-form-item label="佣金值" prop="kickbackValue">
<el-input-number
v-model="formData.kickbackValue"
:min="0"
:precision="formData.kickbackType === 1 ? 2 : 0"
:step="formData.kickbackType === 1 ? 0.01 : 1"
placeholder="请输入佣金值"
/>
<span class="form-tip">{{ formData.kickbackType === 1 ? '万分比' : '分' }}</span>
</el-form-item>
<el-form-item label="资源占用费类型" prop="resourceCostType">
<el-select v-model="formData.resourceCostType" placeholder="请选择资源占用费类型">
<el-option label="预留" :value="0" />
<el-option label="提点" :value="1" />
<el-option label="固定费用" :value="2" />
</el-select>
</el-form-item>
<el-form-item label="资源占用费值" prop="resourceCostValue">
<el-input-number
v-model="formData.resourceCostValue"
:min="0"
:precision="formData.resourceCostType === 1 ? 2 : 0"
:step="formData.resourceCostType === 1 ? 0.01 : 1"
placeholder="请输入资源占用费值"
/>
<span class="form-tip">{{ formData.resourceCostType === 1 ? '万分比' : '分' }}</span>
</el-form-item>
<el-form-item label="服务费比例" prop="serviceRate">
<el-input-number
v-model="formData.serviceRate"
:min="0"
:precision="2"
:step="0.01"
placeholder="请输入服务费比例"
/>
<span class="form-tip">万分比</span>
</el-form-item>
<el-form-item label="会员限制" prop="constraintJson">
<el-input
v-model="formData.constraintJson"
type="textarea"
:rows="3"
placeholder="请输入会员限制JSON格式数据"
/>
</el-form-item>
<el-form-item label="备注" prop="note">
<el-input
v-model="formData.note"
type="textarea"
:rows="3"
placeholder="请输入备注信息"
/>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { settledMemberRecordApi } from '@/api/modules/settledMemberRecord'
import { settledCostConfigApi } from '@/api/modules/settledCostConfig'
import { ElMessage, ElMessageBox } from 'element-plus'
//
const tableData = ref([])
const loading = ref(false)
const tableRef = ref(null)
//
const multipleSelection = ref([])
//
const searchForm = reactive({
userId: '',
memberName: '',
timeRange: []
})
//
const dateShortcuts = [
{
text: '最近一周',
value: () => {
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
return [start, end]
},
},
{
text: '最近一个月',
value: () => {
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
return [start, end]
},
},
{
text: '最近三个月',
value: () => {
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90)
return [start, end]
},
},
]
//
const pagination = reactive({
current: 1,
size: 10,
total: 0
})
//
const configOptions = ref([])
//
const dialogVisible = ref(false)
const dialogType = ref('add')
const formRef = ref(null)
const submitLoading = ref(false)
//
const formData = reactive({
id: null,
userId: '',
settledCostConfigId: null,
title: '',
settledCostKey: '',
constraintJson: '{}',
expirationDate: '',
kickbackType: 1,
kickbackValue: 0,
resourceCostType: 1,
resourceCostValue: 0,
serviceRate: 0,
price: 0,
state: 1,
note: '',
createdBy: 0,
updatedBy: 0
})
//
const formRules = {
userId: [
{ required: true, message: '请输入用户ID', trigger: 'blur' },
],
settledCostConfigId: [
{ required: true, message: '请选择配置', trigger: 'change' },
],
title: [
{ required: true, message: '请输入标题', trigger: 'blur' },
{ max: 20, message: '标题长度不能超过20个字符', trigger: 'blur' }
],
expirationDate: [
{ required: true, message: '请选择到期时间', trigger: 'change' },
],
kickbackType: [
{ required: true, message: '请选择佣金类型', trigger: 'change' },
],
kickbackValue: [
{ required: true, message: '请输入佣金值', trigger: 'blur' },
],
resourceCostType: [
{ required: true, message: '请选择资源占用费类型', trigger: 'change' },
],
resourceCostValue: [
{ required: true, message: '请输入资源占用费值', trigger: 'blur' },
],
serviceRate: [
{ required: true, message: '请输入服务费比例', trigger: 'blur' },
],
price: [
{ required: true, message: '请输入购买价格', trigger: 'blur' },
],
state: [
{ required: true, message: '请选择状态', trigger: 'change' },
]
}
//
onMounted(() => {
fetchData()
fetchConfigOptions()
})
//
const fetchConfigOptions = async () => {
try {
const res = await settledCostConfigApi.getSettledCostConfigList()
console.log("res",res)
// API使
configOptions.value = res.data || []
} catch (error) {
console.error('获取配置选项失败:', error)
ElMessage.error('获取配置选项失败')
}
}
//
const fetchData = async () => {
loading.value = true
try {
//
const params = {
page: pagination.current,
size: pagination.size,
userId: searchForm.userId,
memberName: searchForm.memberName
}
//
if (searchForm.timeRange && searchForm.timeRange.length === 2) {
params.startTime = searchForm.timeRange[0]
params.endTime = searchForm.timeRange[1]
}
const res = await settledMemberRecordApi.getSettledMemberRecordList(params)
// API使
tableData.value = res.data.records || []
pagination.total = res.data.total || 0
} catch (error) {
console.error('获取数据失败:', error)
ElMessage.error('获取数据失败')
} finally {
loading.value = false
}
}
//
const refreshData = () => {
fetchData()
}
//
const handleSearch = () => {
pagination.current = 1 //
fetchData()
}
//
const resetSearch = () => {
searchForm.userId = ''
searchForm.memberName = ''
searchForm.timeRange = []
pagination.current = 1 //
fetchData()
}
//
const handleSizeChange = (size) => {
pagination.size = size
pagination.current = 1 //
fetchData()
}
//
const handleCurrentChange = (current) => {
pagination.current = current
fetchData()
}
//
const handleSelectionChange = (val) => {
multipleSelection.value = val
}
//
const handleAdd = () => {
resetForm()
dialogType.value = 'add'
dialogVisible.value = true
//
const now = new Date()
formData.openTime = now.toISOString().substring(0, 19).replace('T', ' ')
//
const nextYear = new Date()
nextYear.setFullYear(nextYear.getFullYear() + 1)
formData.expirationDate = nextYear.toISOString().substring(0, 19).replace('T', ' ')
}
//
const handleEdit = (row) => {
resetForm()
dialogType.value = 'edit'
// 使
Object.assign(formData, row)
//
formData.price = row.price / 100
dialogVisible.value = true
}
//
const handleDelete = (row) => {
ElMessageBox.confirm('确定要删除该记录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
await settledMemberRecordApi.deleteSettledMemberRecord(row.id)
ElMessage.success('删除成功')
fetchData()
} catch (error) {
console.error('删除失败:', error)
ElMessage.error('删除失败')
}
}).catch(() => {})
}
//
const handleChangeStatus = async (row, newState) => {
const stateTip = newState === 1 ? '启用' : '停用'
ElMessageBox.confirm(`确定要${stateTip}该记录吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
await settledMemberRecordApi.updateSettledMemberRecord({
id: row.id,
state: newState
})
ElMessage.success(`${stateTip}成功`)
fetchData()
} catch (error) {
console.error(`${stateTip}失败:`, error)
ElMessage.error(`${stateTip}失败`)
}
}).catch(() => {})
}
//
const handleConfigChange = (configId) => {
const selectedConfig = configOptions.value.find(item => item.id === configId)
if (selectedConfig) {
formData.title = selectedConfig.title
formData.settledCostKey = selectedConfig.settledCostKey
formData.price = selectedConfig.price / 100 //
}
}
//
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid) => {
if (valid) {
submitLoading.value = true
try {
// API
const submitData = { ...formData }
//
submitData.price = Math.round(submitData.price * 100)
if (dialogType.value === 'add') {
//
await settledMemberRecordApi.addSettledMemberRecord(submitData)
ElMessage.success('创建成功')
} else {
//
await settledMemberRecordApi.updateSettledMemberRecord(submitData)
ElMessage.success('更新成功')
}
dialogVisible.value = false
fetchData()
} catch (error) {
console.error('提交失败:', error)
ElMessage.error('提交失败: ' + (error.message || '未知错误'))
} finally {
submitLoading.value = false
}
}
})
}
//
const resetForm = () => {
if (formRef.value) {
formRef.value.resetFields()
}
Object.assign(formData, {
id: null,
userId: '',
settledCostConfigId: null,
title: '',
settledCostKey: '',
constraintJson: '{}',
expirationDate: '',
kickbackType: 1,
kickbackValue: 0,
resourceCostType: 1,
resourceCostValue: 0,
serviceRate: 0,
price: 0,
state: 1,
note: '',
createdBy: 0,
updatedBy: 0
})
}
//
const formatDateTime = (timestamp) => {
if (!timestamp) return '-'
const date = new Date(timestamp)
return date.toLocaleString()
}
//
const getStatusType = (state) => {
const map = {
0: 'danger',
1: 'success'
}
return map[state] || 'info'
}
//
const getStatusText = (state) => {
const map = {
0: '已停用',
1: '已启用'
}
return map[state] || '未知'
}
//
const getKickbackTypeText = (type) => {
const map = {
0: '预留',
1: '提点',
2: '固定费用'
}
return map[type] || '未知'
}
//
const getResourceCostTypeText = (type) => {
const map = {
0: '预留',
1: '提点',
2: '固定费用'
}
return map[type] || '未知'
}
//
const formatValue = (value, type) => {
if (type === 1) {
// -
return (value / 10000).toFixed(2) + '%'
} else if (type === 2) {
// -
return (value / 100).toFixed(2) + '元'
}
return value
}
</script>
<style scoped>
.list-container {
padding: 20px;
}
.search-area {
margin-bottom: 20px;
padding: 18px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.action-bar {
margin-bottom: 20px;
display: flex;
justify-content: flex-start;
}
.pagination-container {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
.expand-row-item {
margin: 8px 0;
}
.expand-label {
font-weight: bold;
margin-right: 10px;
}
.form-tip {
margin-left: 8px;
color: #909399;
font-size: 14px;
}
</style>

View File

@ -0,0 +1,385 @@
<template>
<div class="list-container">
<!-- 搜索区域 -->
<div class="search-area">
<el-form :inline="true" :model="searchForm">
<el-form-item label="卡密账号">
<el-input v-model="searchForm.cardId" placeholder="请输入卡密账号" clearable />
</el-form-item>
<el-form-item label="卡密密码">
<el-input v-model="searchForm.cardSecret" placeholder="请输入卡密密码" clearable />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="searchForm.status" placeholder="请选择状态" clearable>
<el-option label="未激活" :value="0" />
<el-option label="未使用" :value="1" />
<el-option label="已使用" :value="2" />
<el-option label="已冻结" :value="3" />
<el-option label="已过期" :value="4" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">搜索</el-button>
<el-button @click="resetSearch">重置</el-button>
</el-form-item>
</el-form>
</div>
<!-- 操作按钮 -->
<div class="action-bar">
<el-button type="primary" @click="handleGetCardSecret">获取卡密</el-button>
<el-button @click="refreshData">刷新</el-button>
</div>
<!-- 数据表格 -->
<el-table
ref="tableRef"
:data="tableData"
border
stripe
style="width: 100%;"
v-loading="loading"
>
<el-table-column type="selection" align="center" width="55" />
<el-table-column prop="cardId" label="卡密账号" min-width="180" />
<el-table-column prop="cardSecret" label="卡密密码" min-width="180" />
<el-table-column prop="cardType" label="卡密类型" min-width="120">
<template #default="{ row }">
{{ getCardTypeText(row.cardType) }}
</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="faceValue" label="面值" width="100">
<template #default="{ row }">
{{ row.faceValue }}
</template>
</el-table-column>
<el-table-column prop="balance" label="当前余额" width="100">
<template #default="{ row }">
{{ row.balance }}
</template>
</el-table-column>
<el-table-column prop="effectiveDays" label="有效期" width="100">
<template #default="{ row }">
{{ row.effectiveDays }}
</template>
</el-table-column>
<el-table-column prop="activateTime" label="激活时间" min-width="160">
<template #default="{ row }">
{{ formatDateTime(row.activateTime) }}
</template>
</el-table-column>
<el-table-column prop="expireTime" label="过期时间" min-width="160">
<template #default="{ row }">
{{ formatDateTime(row.expireTime) }}
</template>
</el-table-column>
<el-table-column prop="useTime" label="使用时间" min-width="160">
<template #default="{ row }">
{{ formatDateTime(row.useTime) }}
</template>
</el-table-column>
<el-table-column prop="memo" label="备注" min-width="120" />
<el-table-column label="操作" fixed="right" width="120">
<template #default="{ row }">
<el-button
size="small"
type="danger"
@click="handleDelete(row)"
:disabled="row.status !== 0"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-container">
<el-pagination
v-model:current-page="pagination.current"
v-model:page-size="pagination.size"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="pagination.total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
<!-- 获取卡密对话框 -->
<el-dialog
title="卡密信息"
v-model="cardSecretDialogVisible"
width="500px"
:close-on-click-modal="false"
>
<div class="card-secret-content">
<p>您的卡密为</p>
<el-input
v-model="cardSecretValue"
type="textarea"
:rows="3"
readonly
/>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="cardSecretDialogVisible = false">关闭</el-button>
<el-button type="primary" @click="handleCopyCardSecret">复制</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { cardsApi } from '@/api/modules/cards'
//
const tableData = ref([])
const loading = ref(false)
const tableRef = ref(null)
//
const searchForm = reactive({
cardId: '',
cardSecret: '',
status: null
})
//
const pagination = reactive({
current: 1,
size: 10,
total: 0
})
//
const cardSecretDialogVisible = ref(false)
const cardSecretValue = ref('')
//
onMounted(() => {
fetchData()
})
//
const fetchData = async () => {
loading.value = true
try {
// 使API
const res = await cardsApi.pageQueryCard({
page: pagination.current,
size: pagination.size,
cardId: searchForm.cardId || undefined,
cardSecret: searchForm.cardSecret || undefined,
status: searchForm.status !== null ? searchForm.status : undefined
})
// response.data使res
if (res.code === 200) {
tableData.value = res.data.records || []
pagination.total = res.data.total || 0
} else {
ElMessage.error(res.message || '获取数据失败')
}
} catch (error) {
console.error('获取数据失败:', error)
//
if (error.code === 'ECONNABORTED') {
ElMessage.error('请求超时,请检查网络连接或联系管理员')
} else if (error.response) {
ElMessage.error(`请求失败: ${error.response.status} ${error.response.statusText}`)
} else if (error.request) {
ElMessage.error('服务器未响应,请稍后再试')
} else {
ElMessage.error(`请求错误: ${error.message}`)
}
//
tableData.value = []
pagination.total = 0
} finally {
loading.value = false
}
}
//
const handleGetCardSecret = async () => {
try {
const res = await cardsApi.createCardSecret()
// createCardSecret使res
if (res.code === 200) {
cardSecretValue.value = res.data
cardSecretDialogVisible.value = true
} else {
ElMessage.error(res.message || '获取卡密失败')
}
} catch (error) {
console.error('获取卡密失败:', error)
ElMessage.error('获取卡密失败: ' + (error.message || '未知错误'))
}
}
//
const handleCopyCardSecret = async () => {
try {
await navigator.clipboard.writeText(cardSecretValue.value)
ElMessage.success('复制成功')
cardSecretDialogVisible.value = false
} catch (error) {
console.error('复制失败:', error)
ElMessage.error('复制失败')
}
}
//
const refreshData = () => {
fetchData()
}
//
const handleSearch = () => {
pagination.current = 1
fetchData()
}
//
const resetSearch = () => {
searchForm.cardId = ''
searchForm.cardSecret = ''
searchForm.status = null
pagination.current = 1
fetchData()
}
//
const handleSizeChange = (size) => {
pagination.size = size
pagination.current = 1
fetchData()
}
//
const handleCurrentChange = (current) => {
pagination.current = current
fetchData()
}
//
const handleDelete = (row) => {
ElMessageBox.confirm('确定要删除该卡密吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const res = await cardsApi.deleteCard(row.id)
if (res.code === 200) {
ElMessage.success('删除成功')
fetchData()
} else {
ElMessage.error(res.message || '删除失败')
}
} catch (error) {
console.error('删除失败:', error)
//
if (error.code === 'ECONNABORTED') {
ElMessage.error('请求超时,请稍后再试')
} else if (error.response) {
ElMessage.error(`删除失败: ${error.response.status} ${error.response.statusText}`)
} else {
ElMessage.error(`删除失败: ${error.message || '未知错误'}`)
}
}
}).catch(() => {
//
})
}
//
const getStatusType = (status) => {
const map = {
0: 'info',
1: 'success',
2: 'warning',
3: 'danger',
4: 'info'
}
return map[status] || 'info'
}
//
const getStatusText = (status) => {
const map = {
0: '未激活',
1: '未使用',
2: '已使用',
3: '已冻结',
4: '已过期'
}
return map[status] || '未知'
}
//
const getCardTypeText = (type) => {
const map = {
'verifyPriceCredential': '使用核价凭证'
}
return map[type] || type
}
//
const formatDateTime = (timestamp) => {
if (!timestamp) return '-'
const date = new Date(timestamp)
return date.toLocaleString()
}
</script>
<style scoped>
.list-container {
padding: 20px;
}
.search-area {
margin-bottom: 20px;
padding: 18px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.action-bar {
margin-bottom: 20px;
display: flex;
justify-content: flex-start;
gap: 10px;
}
.pagination-container {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
.card-secret-content {
text-align: center;
p {
margin-bottom: 15px;
font-size: 16px;
}
}
</style>

View File

@ -41,17 +41,17 @@ export default defineConfig({
__VUE_OPTIONS_API__: JSON.stringify(false),
// 关闭生产环境 devtools可选
__VUE_PROD_DEVTOOLS__: JSON.stringify(false)
}
// server: {
// proxy: {
// '/api': {
// // target: 'http://127.0.0.1:8080',
},
server: {
proxy: {
'/api': {
target: 'http://127.0.0.1:8089',
// target: 'http://146.56.227.42',
// changeOrigin: true,
// rewrite: (path) => path.replace(/^\/api/,''),
// // 如需处理WebSocket
// ws: true
// }
// }
// }
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/,''),
// 如需处理WebSocket
ws: true
}
}
}
})