卡密 会员
This commit is contained in:
parent
2b2a4dd8d7
commit
68fbee5b68
20
src/api/modules/cards.js
Normal file
20
src/api/modules/cards.js
Normal 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 }
|
||||
20
src/api/modules/settledMemberRecord.js
Normal file
20
src/api/modules/settledMemberRecord.js
Normal 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 }
|
||||
@ -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>
|
||||
|
||||
@ -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: '卡密列表' }
|
||||
}
|
||||
]
|
||||
}]
|
||||
|
||||
@ -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
|
||||
709
src/views/SettledConfig/MemberRecord.vue
Normal file
709
src/views/SettledConfig/MemberRecord.vue
Normal 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>
|
||||
385
src/views/Tools/Cards/List.vue
Normal file
385
src/views/Tools/Cards/List.vue
Normal 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>
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
Loading…
Reference in New Issue
Block a user