存款,存款渠道,充值订单

This commit is contained in:
Songsl 2026-04-26 15:34:23 +08:00
parent f5b3faa318
commit 44e43d81f3
14 changed files with 799 additions and 455 deletions

View File

@ -0,0 +1,18 @@
import { request } from '@/api'
// 获取入金渠道列表
export const getChannelList = () => {
return request.get('/channel/getChannelList')
}
// 创建入金渠道列表
export const createChannel = (params: any) => {
return request.post('/channel/createChannel', { ...params })
}
// 编辑入金渠道列表
export const editChannel = (params: any) => {
return request.post('/channel/editChannel', { ...params })
}
// 删除入金渠道列表
export const delChannel = (params: any) => {
return request.post('/channel/delChannel', { ...params })
}

View File

@ -4,3 +4,7 @@ import { request } from '@/api';
export const getRechargeCheckListApi = (params: any) => { export const getRechargeCheckListApi = (params: any) => {
return request.get('/user/rechargeReviewList', { ...params }) return request.get('/user/rechargeReviewList', { ...params })
} }
// 充值审核
export const reviewRechargeOrder = (params: any) => {
return request.post('/user/reviewRechargeOrder', { ...params })
}

View File

@ -0,0 +1,13 @@
import { request } from '@/api'
// 获取充值订单列表
export const getRechargeOrderListApi = (params: any) => {
return request.get('/user/rechargeList', { ...params })
}
// 审核充值订单
export const reviewRechargeOrder = (params: any) => {
return request.post('/user/reviewRechargeOrder', { ...params })
}

View File

@ -1,6 +0,0 @@
import { request } from '@/api';
// 获取充值订单列表
export const getRechargeOrderListApi = (params: any) => {
return request.get('/user/rechargeList', { ...params })
}

View File

@ -30,7 +30,7 @@
show-password show-password
type="password" type="password"
/> />
<!-- 密码输入框 --> <!-- 长文本输入框 -->
<el-input <el-input
v-else-if="item.type === 'textarea'" v-else-if="item.type === 'textarea'"
v-model="formData[item.prop]" v-model="formData[item.prop]"
@ -75,6 +75,8 @@
:max="item.max" :max="item.max"
:placeholder="item.placeholder" :placeholder="item.placeholder"
:disabled="item.disabled" :disabled="item.disabled"
:controls="item.controls ?? true"
:align="item.align || 'left'"
style="width: 100%" style="width: 100%"
/> />
@ -175,6 +177,8 @@ interface FormItem {
clearable?: boolean clearable?: boolean
disabledDate?: (time: Date) => boolean disabledDate?: (time: Date) => boolean
isRange?: boolean isRange?: boolean
controls?: boolean
align?: string
} }
interface Props { interface Props {

View File

@ -1,70 +1,106 @@
<template> <template>
<div class="table-container"> <div class="table-container">
<el-table height="100%" :data="tableData" :row-key="rowKey" :tree-props="treeProps" :border="border" <el-table
:highlight-current-row="highlightCurrentRow" v-bind="$attrs" @row-click="handleRowClick"> ref="tableRef"
<!-- 动态表头列 --> height="100%"
<template v-for="(column, index) in columns" :key="index"> :data="tableData"
<el-table-column v-if="!column.slotName" :prop="column.prop" :label="column.label" :width="column.width" :row-key="rowKey"
:align="column.align || 'left'" :sortable="column.sortable" /> :tree-props="treeProps"
:border="border"
:highlight-current-row="highlightCurrentRow"
v-bind="$attrs"
@row-click="handleRowClick"
>
<!-- 动态表头列 -->
<template v-for="(column, index) in columns" :key="index">
<el-table-column
v-if="column.type === 'index'"
type="index"
:label="column.label"
:width="column.width || 60"
:align="column.align || 'center'"
/>
<el-table-column
v-else-if="!column.slotName"
:prop="column.prop"
:label="column.label"
:width="column.width"
:align="column.align || 'left'"
:sortable="column.sortable"
/>
<!-- 具名插槽列 --> <!-- 具名插槽列 -->
<el-table-column v-else :label="column.label" :width="column.width" :align="column.align || 'left'"> <el-table-column
<template #default="{ row }"> v-else
<slot :name="column.slotName" :row="row" /> :label="column.label"
</template> :width="column.width"
</el-table-column> :align="column.align || 'left'"
</template> >
</el-table> <template #default="{ row }">
</div> <slot :name="column.slotName" :row="row" />
</template>
</el-table-column>
</template>
</el-table>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { TableInstance } from 'element-plus'
// //
interface TableColumn { interface TableColumn {
prop?: string prop?: string
label: string type?: string
width?: string | number label: string
align?: 'left' | 'center' | 'right' width?: string | number
slotName?: string // align?: 'left' | 'center' | 'right'
sortable?: boolean // slotName?: string //
sortable?: boolean //
} }
// //
interface Props { interface Props {
tableData: any[] tableData: any[]
rowKey?: string rowKey?: string
treeProps?: { treeProps?: {
children: string children: string
} }
columns?: TableColumn[], columns?: TableColumn[]
border?: boolean // border?: boolean //
highlightCurrentRow?: boolean // highlightCurrentRow?: boolean //
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
border: true, border: true,
highlightCurrentRow: true, highlightCurrentRow: true,
}) })
// //
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'row-click', row: any): void // (e: 'row-click', row: any): void //
}>() }>()
/** /**
* 处理行点击事件兼容原有逻辑 * 处理行点击事件兼容原有逻辑
*/ */
const handleRowClick = (row: any) => { const handleRowClick = (row: any) => {
// null // null
if (!row) return if (!row) return
emit('row-click', row) emit('row-click', row)
} }
const tableRef = ref<TableInstance>()
//
defineExpose({
clearCurrentRow: () => tableRef.value?.setCurrentRow(),
})
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.table-container { .table-container {
width: 100%; width: 100%;
height: calc(100vh - 180px); height: calc(100vh - 180px);
overflow: hidden; overflow: hidden;
} }
</style> </style>

32
src/utils/handleTime.ts Normal file
View File

@ -0,0 +1,32 @@
/**
*
* @param format 'YYYY-MM-DD HH:mm:ss'
* YYYY - , MM - , DD - , HH - , mm - , ss -
* @returns { start: string, end: string }
*/
export const getRecentSevenDays = (format = 'YYYY-MM-DD HH:mm:ss'):{ start: string, end: string } => {
const now = new Date()
const sevenDaysAgo = new Date(now)
sevenDaysAgo.setDate(now.getDate() - 7)
const formatDateTime = (date: Date) => {
const tokens: Record<string, string> = {
YYYY: String(date.getFullYear()),
MM: String(date.getMonth() + 1).padStart(2, '0'),
DD: String(date.getDate()).padStart(2, '0'),
HH: String(date.getHours()).padStart(2, '0'),
mm: String(date.getMinutes()).padStart(2, '0'),
ss: String(date.getSeconds()).padStart(2, '0'),
}
let result = format
for (const [token, value] of Object.entries(tokens)) {
result = result.replace(token, value)
}
return result
}
return {
start: formatDateTime(sevenDaysAgo),
end: formatDateTime(now),
}
}

View File

@ -1,34 +1,188 @@
<template> <template>
<div class="deposit-channel"> <div class="deposit-channel">
<div class="bar"> <div class="bar">
<el-button type="primary" @click="handleAdd">添加</el-button> <el-button type="primary" @click="handleAdd">添加</el-button>
<el-button type="danger" @click="handleEnable">启用</el-button> <el-button type="primary" @click="handleEdit">编辑</el-button>
<el-button type="danger" @click="handleDisable">禁用</el-button> <el-button type="danger" @click="handleDelete">删除</el-button>
</div>
<DepositChannelTable :table-data="tableTree" :columns="tableColumnsOptions" row-key="id"/>
</div> </div>
<DepositChannelTable
ref="tableRef"
:table-data="tableData"
:columns="tableColumnsOptions"
row-key="id"
@rowClick="handleRowClick"
/>
<el-dialog
v-model="dialogVisible"
:title="dialogTitleType === 1 ? '添加充值渠道' : '编辑充值渠道'"
width="700"
>
<Form
:formItems="formItems"
:formRules="formRules"
:formData="formData"
:optionsMap="optionsMap"
/>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitDetail">确认</el-button>
</div>
</template>
</el-dialog>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
// //
import DepositChannelTable from '@/components/common/Table.vue' import DepositChannelTable from '@/components/common/Table.vue'
import { tableColumns } from './options' import Form from '@/components/common/Form.vue'
import { tableColumns, formItemsData, formRulesData, optionsMapData } from './options'
import {
getChannelList,
createChannel,
editChannel,
delChannel,
} from '@/api/modules/capital/rechangeChanl.ts'
import { ElMessageBox } from 'element-plus'
type ChannelType = {
id?: number
name: string
channel_fee: number | null
type: number | null
api_url: string
api_appid: string
api_key: string
status: number
}
const getTypeText = (type: number | string) => {
return ['', '法币', '数字货币'][type]
}
const getStatusText = (status: number | string) => {
return ['', '正常', '禁用'][status]
}
const initData = async () => {
const res = await getChannelList()
console.log(res)
tableData.value = res.map((item: ChannelType) => ({
...item,
typeStr: getTypeText(item.type),
statusStr: getStatusText(item.status),
})) as ChannelType[]
clearSelection()
}
// //
const tableColumnsOptions = ref(tableColumns) const tableColumnsOptions = ref(tableColumns)
const tableTree = ref([]) const tableData = ref([])
const tableRef = ref()
const dialogVisible = ref(false)
const dialogTitleType = ref(1)
const formData = ref({
name: '',
channel_fee: null,
type: null,
api_url: '',
api_appid: '',
api_key: '',
status: 1,
})
const formItems = ref(formItemsData)
const formRules = ref(formRulesData)
const optionsMap = ref(optionsMapData)
const submitDetail = async () => {
dialogVisible.value = false
if (dialogTitleType.value === 1) {
await createChannel(formData.value)
} else {
await editChannel(formData.value)
}
await initData()
}
let currentRow: any = {}
const handleRowClick = (row: any) => {
console.log('handleRowClick', row)
currentRow = row
}
const resetFormData = () => {
formData.value = {
name: '',
channel_fee: null,
type: null,
api_url: '',
api_appid: '',
api_key: '',
status: 1,
}
}
const handleAdd = () => {
dialogVisible.value = true
dialogTitleType.value = 1
resetFormData()
}
const handleEdit = () => {
if (!currentRow.id) return ElMessage.warning('请先选择要编辑的记录')
dialogVisible.value = true
dialogTitleType.value = 2
formData.value = {
id: currentRow.id,
name: currentRow.name,
channel_fee: currentRow.channel_fee,
type: currentRow.type,
api_url: currentRow.api_url,
api_appid: currentRow.api_appid,
api_key: currentRow.api_key,
status: currentRow.status,
}
}
const handleDelete = () => {
console.log('handleDisable')
ElMessageBox.confirm('确认删除吗?', '提示', {
type: 'warning',
confirmButtonText: '确定',
cancelButtonText: '取消',
})
.then(async () => {
await delChannel({ id: currentRow.id })
await initData()
})
.catch(() => {
console.log('取消删除')
})
}
const clearSelection = () => {
currentRow = {}
nextTick(() => {
tableRef.value?.clearCurrentRow() //
})
}
onMounted(() => {
initData()
})
</script> </script>
<style scoped> <style scoped>
.deposit-channel { .deposit-channel {
.bar { .bar {
margin-bottom: 10px; margin-bottom: 10px;
display: flex; display: flex;
align-items: center; align-items: center;
span { span {
margin-right: 10px; margin-right: 10px;
}
} }
}
} }
</style> </style>

View File

@ -1,9 +1,60 @@
export const tableColumns = [ export const tableColumns = [
//序号 //序号
{ label: '序号', prop: 'index' }, { label: '序号', type: 'index', width: 60, align: 'center' },
//存款渠道名称 //存款渠道名称
{ label: '存款渠道名称', prop: 'depositChannelName' }, { label: '渠道名称', prop: 'name' },
//状态 //存款渠道费用
{ label: '状态', slotName: 'status' }, { label: '渠道费用', prop: 'channel_fee' },
//存款渠道类型
] { label: '类型', prop: 'typeStr' },
// 接口地址
{ label: '接口地址', prop: 'api_url' },
// 接口appid
{ label: '接口appid', prop: 'api_appid' },
{ label: '状态', prop: 'statusStr' },
]
export const formItemsData = [
{ label: '渠道名称', prop: 'name', type: 'input', placeholder: '请输入渠道名称' },
{
label: '渠道费用',
prop: 'channel_fee',
type: 'number',
min: 0,
max: 999999,
controls: false,
placeholder: '请输入渠道费用',
},
{
label: '类型',
prop: 'type',
type: 'select',
placeholder: '请选择类型',
}, // 类型:1法币,2数字货币
{ label: '接口地址', prop: 'api_url', type: 'input', placeholder: '请输入接口地址' },
{ label: '接口appid', prop: 'api_appid', type: 'input', placeholder: '请输入接口appid' },
{ label: '接口密钥', prop: 'api_key', type: 'input', placeholder: '请输入接口密钥' },
{ label: '状态', prop: 'status', type: 'radio' },
]
export const formRulesData = {
name: [{ required: true, message: '请输入渠道名称', trigger: ['change'] }],
channel_fee: [{ required: true, message: '请输入渠道费用', trigger: ['change'] }],
type: [{ required: true, message: '请选择类型', trigger: ['change'] }],
api_url: [{ required: true, message: '请输入接口地址', trigger: ['change'] }],
api_appid: [{ required: true, message: '请输入接口appid', trigger: ['change'] }],
api_key: [{ required: true, message: '请输入接口密钥', trigger: ['change'] }],
status: [{ required: true, message: '请选择状态', trigger: ['change'] }],
}
export const optionsMapData = {
type: [
{ label: '法币', value: 1 },
{ label: '数字货币', value: 2 },
],
status: [
{ label: '启用', value: 1 },
{ label: '禁用', value: 2 },
],
}

View File

@ -1,36 +1,59 @@
<template> <template>
<div class="rechange-check"> <div class="rechange-check">
<!-- 搜索区域默认把当天 0 点到当前时间写入时间输入框 --> <!-- 搜索区域默认把当天 0 点到当前时间写入时间输入框 -->
<RechangeSearch :search-form="searchForm" :search-options="searchOptions" @search="handleSearch" @reset="handleReset"> <RechangeSearch
<template #button> :search-form="searchForm"
<!-- 审核动作先保留按钮交互后续可直接接接口 --> :search-options="searchOptions"
<el-button type="danger" plain @click="handleReject">驳回</el-button> @search="handleSearch"
<el-button type="success" @click="handleApprove">通过</el-button> @reset="handleReset"
<el-button type="primary" plain @click="handleExport">导出</el-button> >
</template> <template #button>
</RechangeSearch> <!-- 审核动作先保留按钮交互后续可直接接接口 -->
<el-button type="danger" plain @click="handleReject">驳回</el-button>
<el-button type="success" @click="handleApprove">通过</el-button>
<el-button type="primary" plain @click="handleExport">导出</el-button>
</template>
</RechangeSearch>
<!-- 列表区域改为真实接口数据仍保留勾选列供后续审核动作使用 --> <!-- 列表区域改为真实接口数据仍保留勾选列供后续审核动作使用 -->
<div class="table-wrapper"> <div class="table-wrapper">
<el-table v-loading="loading" :data="tableData" border height="100%" row-key="id" <el-table
@selection-change="handleSelectionChange"> ref="tableRef"
<el-table-column type="selection" width="55" align="center" /> v-loading="loading"
:data="tableData"
border
height="100%"
row-key="id"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="序号" width="70" align="center"> <el-table-column label="序号" width="70" align="center">
<template #default="scope"> <template #default="scope">
{{ (currentPage - 1) * pageSize + scope.$index + 1 }} {{ (currentPage - 1) * pageSize + scope.$index + 1 }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column v-for="column in tableColumns" :key="column.prop" :prop="column.prop" :label="column.label" <el-table-column
:min-width="column.width" :align="column.align || 'center'" show-overflow-tooltip /> v-for="column in tableColumns"
</el-table> :key="column.prop"
</div> :prop="column.prop"
:label="column.label"
<!-- 分页区域使用接口返回的 total / current_page / per_page --> :min-width="column.width"
<RechangePagination v-model="currentPage" v-model:pageSizeValue="pageSize" :total="total" :align="column.align || 'center'"
@pagination-change="handlePaginationChange" /> show-overflow-tooltip
/>
</el-table>
</div> </div>
<!-- 分页区域使用接口返回的 total / current_page / per_page -->
<RechangePagination
v-model="currentPage"
v-model:pageSizeValue="pageSize"
:total="total"
@pagination-change="handlePaginationChange"
/>
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -39,30 +62,31 @@ import { onMounted, ref } from 'vue'
import RechangeSearch from '@/components/common/Search.vue' import RechangeSearch from '@/components/common/Search.vue'
import RechangePagination from '@/components/common/Pagination.vue' import RechangePagination from '@/components/common/Pagination.vue'
import { exportToExcel } from '@/utils/exportToExcel' import { exportToExcel } from '@/utils/exportToExcel'
import { getRechargeCheckListApi } from '@/api/modules/system/rechargeCheck' import { getRechargeCheckListApi, reviewRechargeOrder } from '@/api/modules/capital/rechargeCheck'
import { rechangeCheckSearch, rechangeCheckTableColumns } from './options' import { rechangeCheckSearch, rechangeCheckTableColumns } from './options'
import { getRecentSevenDays } from '@/utils/handleTime.ts'
// `rechargeOrder` // `rechargeOrder`
interface TableRow { interface TableRow {
id: number id: number
orderNo: string orderNo: string
userName: string userName: string
currency: string currency: string
amount: string amount: string
realCurrency: string realCurrency: string
realAmount: string realAmount: string
rate: string rate: string
status: string status: string
statusText: string statusText: string
rechangeTime: string rechangeTime: string
} }
// //
interface SearchFormData { interface SearchFormData {
orderNo: string orderNo: string
userName: string userName: string
startTime: string startTime: string
endTime: string endTime: string
} }
// //
@ -79,194 +103,191 @@ const total = ref(0)
const currentPage = ref(1) const currentPage = ref(1)
const pageSize = ref(10) const pageSize = ref(10)
// / const initTime = getRecentSevenDays()
const padZero = (value: number) => String(value).padStart(2, '0')
const formatDateTime = (date: Date) => {
const year = date.getFullYear()
const month = padZero(date.getMonth() + 1)
const day = padZero(date.getDate())
const hours = padZero(date.getHours())
const minutes = padZero(date.getMinutes())
const seconds = padZero(date.getSeconds())
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
const getTodayStartTime = () => {
const now = new Date()
now.setHours(0, 0, 0, 0)
return formatDateTime(now)
}
const getCurrentTime = () => formatDateTime(new Date())
// 0 // 0
const createDefaultSearchForm = (): SearchFormData => ({ const createDefaultSearchForm = (): SearchFormData => ({
orderNo: '', orderNo: '',
userName: '', userName: '',
startTime: getTodayStartTime(), startTime: initTime.start,
endTime: getCurrentTime(), endTime: initTime.end,
}) })
const searchForm = ref<SearchFormData>(createDefaultSearchForm()) const searchForm = ref<SearchFormData>(createDefaultSearchForm())
// //
const getRechargeStatusText = (status: string | number | null | undefined) => { const getRechargeStatusText = (status: string | number | null | undefined) => {
const statusMap: Record<string, string> = { const statusMap: Record<string, string> = {
'1': '未审核', '1': '未审核',
'2': '未付款', '2': '未付款',
'3': '已付款', '3': '已付款',
'4': '驳回', '4': '驳回',
} }
return statusMap[String(status ?? '')] || '--' return statusMap[String(status ?? '')] || '--'
} }
// page / page_size / start_time / end_time null // page / page_size / start_time / end_time null
const getList = async () => { const getList = async () => {
loading.value = true loading.value = true
selectedRows.value = [] selectedRows.value = []
const params = { const params = {
page: currentPage.value, page: currentPage.value,
page_size: pageSize.value, page_size: pageSize.value,
trade_id: searchForm.value.orderNo || null, trade_id: searchForm.value.orderNo || null,
start_time: searchForm.value.startTime || getTodayStartTime(), start_time: searchForm.value.startTime || initTime.start,
end_time: searchForm.value.endTime || getCurrentTime(), end_time: searchForm.value.endTime || initTime.end,
user_name: searchForm.value.userName || null, user_name: searchForm.value.userName || null,
} }
try { try {
const data: any = await getRechargeCheckListApi(params) const data: any = await getRechargeCheckListApi(params)
const list = Array.isArray(data?.data) ? data.data : [] const list = Array.isArray(data?.data) ? data.data : []
tableData.value = list.map((item: any, index: number) => ({ tableData.value = list.map((item: any, index: number) => ({
id: Number(item.id ?? index + 1), id: Number(item.id ?? index + 1),
orderNo: String(item.trade_id ?? '--'), orderNo: String(item.trade_id ?? '--'),
userName: item.user?.username ?? '--', userName: item.user?.username ?? '--',
currency: item.recharge_currency?.symbol ?? '--', currency: item.recharge_currency?.symbol ?? '--',
amount: String(item.recharge_amount ?? '--'), amount: String(item.recharge_amount ?? '--'),
realCurrency: item.real_currency?.symbol ?? '--', realCurrency: item.real_currency?.symbol ?? '--',
realAmount: String(item.real_amount ?? '--'), realAmount: String(item.real_amount ?? '--'),
rate: String(item.rate ?? '--'), rate: String(item.rate ?? '--'),
status: String(item.status ?? ''), status: String(item.status ?? ''),
statusText: getRechargeStatusText(item.status), statusText: getRechargeStatusText(item.status),
rechangeTime: item.created_at ?? '--', rechangeTime: item.created_at ?? '--',
})) }))
total.value = Number(data?.total ?? list.length) total.value = Number(data?.total ?? list.length)
currentPage.value = Number(data?.current_page ?? currentPage.value) currentPage.value = Number(data?.current_page ?? currentPage.value)
pageSize.value = Number(data?.per_page ?? pageSize.value) pageSize.value = Number(data?.per_page ?? pageSize.value)
} catch (error) { } catch (error) {
tableData.value = [] tableData.value = []
total.value = 0 total.value = 0
console.error('获取充值审核列表失败', error) console.error('获取充值审核列表失败', error)
} finally { } finally {
loading.value = false loading.value = false
} }
} }
// //
onMounted(() => { onMounted(() => {
getList() getList()
}) })
// //
const handleSearch = () => { const handleSearch = () => {
currentPage.value = 1 currentPage.value = 1
getList() getList()
} }
// //
const handleReset = () => { const handleReset = () => {
searchForm.value = createDefaultSearchForm() searchForm.value = createDefaultSearchForm()
currentPage.value = 1 currentPage.value = 1
pageSize.value = 10 pageSize.value = 10
getList() getList()
} }
const tableRef = ref()
// //
const handleSelectionChange = (rows: TableRow[]) => { const handleSelectionChange = (rows: TableRow[]) => {
if (rows.length > 1) {
// 1
const lastRow = rows[rows.length - 1]
//
tableRef.value?.clearSelection()
//
tableRef.value?.toggleRowSelection(lastRow, true)
selectedRows.value = [lastRow]
} else {
selectedRows.value = rows selectedRows.value = rows
}
} }
// //
const handleApprove = () => { const handleApprove = () => {
if (!selectedRows.value.length) { if (!selectedRows.value.length) {
ElMessage.warning('请先选择要通过的记录') ElMessage.warning('请先选择要通过的记录')
return return
} }
ElMessage.success('审核通过接口待接入') console.log(selectedRows.value)
reviewRechargeOrder({ id: selectedRows.value[0].id, status: '3' })
ElMessage.success('审核通过')
getList()
} }
// //
const handleReject = () => { const handleReject = () => {
if (!selectedRows.value.length) { if (!selectedRows.value.length) {
ElMessage.warning('请先选择要驳回的记录') ElMessage.warning('请先选择要驳回的记录')
return return
} }
ElMessage.success('审核驳回接口待接入') //:1,2,3,4
reviewRechargeOrder({ id: selectedRows.value[0].id, status: '4' })
ElMessage.success('审核驳回')
getList()
} }
// //
const handleExport = async () => { const handleExport = async () => {
await exportToExcel({ await exportToExcel({
tableData: tableData.value, tableData: tableData.value,
columns: tableColumns.value, columns: tableColumns.value,
title: '导出充值审核', title: '导出充值审核',
defaultFileNamePrefix: '充值审核', defaultFileNamePrefix: '充值审核',
}) })
} }
// //
const handlePaginationChange = (page: { currentPage: number; pageSize: number }) => { const handlePaginationChange = (page: { currentPage: number; pageSize: number }) => {
currentPage.value = page.currentPage currentPage.value = page.currentPage
pageSize.value = page.pageSize pageSize.value = page.pageSize
getList() getList()
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.rechange-check { .rechange-check {
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-width: 0; min-width: 0;
padding-bottom: 70px; padding-bottom: 70px;
box-sizing: border-box; box-sizing: border-box;
// //
:deep(.search-container .el-form) { :deep(.search-container .el-form) {
flex-wrap: wrap; flex-wrap: wrap;
align-items: center; align-items: center;
} }
:deep(.search-container .el-form-item) { :deep(.search-container .el-form-item) {
flex: 0 0 auto; flex: 0 0 auto;
margin-bottom: 8px; margin-bottom: 8px;
} }
:deep(.search-container .el-input), :deep(.search-container .el-input),
:deep(.search-container .el-select), :deep(.search-container .el-select),
:deep(.search-container .el-date-editor) { :deep(.search-container .el-date-editor) {
width: 100%; width: 100%;
} }
// //
.table-wrapper { .table-wrapper {
flex: 1; flex: 1;
min-height: 0; min-height: 0;
width: 100%; width: 100%;
background: #fff; background: #fff;
border-radius: 6px; border-radius: 6px;
overflow: hidden; overflow: hidden;
} }
:deep(.el-table), :deep(.el-table),
:deep(.el-table__inner-wrapper), :deep(.el-table__inner-wrapper),
:deep(.el-scrollbar) { :deep(.el-scrollbar) {
width: 100%; width: 100%;
} }
} }
</style> </style>

View File

@ -1,37 +1,39 @@
// 搜索区域配置:按充值审核接口的实际查询参数保留订单号、用户名、开始时间、结束时间。 // 搜索区域配置:按充值审核接口的实际查询参数保留订单号、用户名、开始时间、结束时间。
export const rechangeCheckSearch = [ export const rechangeCheckSearch = [
{ {
prop: 'orderNo', prop: 'orderNo',
label: '订单号', label: '订单号',
type: 'input' as const, type: 'input' as const,
placeholder: '请输入流水号', placeholder: '请输入流水号',
width: '220px', width: '220px',
}, },
{ {
prop: 'userName', prop: 'userName',
label: '用户名称', label: '用户名称',
type: 'input' as const, type: 'input' as const,
placeholder: '请输入用户名', placeholder: '请输入用户名',
width: '200px', width: '200px',
}, },
{ {
prop: 'startTime', prop: 'startTime',
label: '开始时间', label: '开始时间',
type: 'date' as const, type: 'date' as const,
dateType: 'datetime' as const, dateType: 'datetime' as const,
valueFormat: 'YYYY-MM-DD HH:mm:ss', valueFormat: 'YYYY-MM-DD HH:mm:ss',
placeholder: '请选择开始时间', placeholder: '请选择开始时间',
width: '220px', clearable: false,
}, width: '260px',
{ },
prop: 'endTime', {
label: '结束时间', prop: 'endTime',
type: 'date' as const, label: '结束时间',
dateType: 'datetime' as const, type: 'date' as const,
valueFormat: 'YYYY-MM-DD HH:mm:ss', dateType: 'datetime' as const,
placeholder: '请选择结束时间', valueFormat: 'YYYY-MM-DD HH:mm:ss',
width: '220px', placeholder: '请选择结束时间',
}, clearable: false,
width: '260px',
},
] ]
// 表格列配置:返回数据与 `rechargeOrder` 一致,因此按同一份结构展示。 // 表格列配置:返回数据与 `rechargeOrder` 一致,因此按同一份结构展示。

View File

@ -79,7 +79,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, nextTick } from 'vue' import { ref, onMounted } from 'vue'
import { import {
getFeeSettingList, getFeeSettingList,
getFeeSettingDetail, getFeeSettingDetail,
@ -90,8 +90,6 @@ import {
import Form from '@/components/common/Form.vue' import Form from '@/components/common/Form.vue'
import { formDataList, formRulesList } from './option.ts' import { formDataList, formRulesList } from './option.ts'
const title = ref('手续费模板')
const buttons = ref([{ label: '新增', type: 'primary' as const, key: 'add' }])
const dialogVisible = ref(false) const dialogVisible = ref(false)
const dialogTitleType = ref(1) const dialogTitleType = ref(1)

View File

@ -1,16 +1,38 @@
<template> <template>
<div class="order-rechange-page"> <div class="order-rechange-page">
<!-- 搜索区域按接口参数保留订单号用户名称状态开始/结束时间 --> <!-- 搜索区域按接口参数保留订单号用户名称状态开始/结束时间 -->
<OrderRechangeSearch :search-form="searchForm" :search-options="searchOptions" @search="handleSearch" <OrderRechangeSearch
@reset="handleReset" /> :search-form="searchForm"
:search-options="searchOptions"
@search="handleSearch"
@reset="handleReset"
></OrderRechangeSearch>
<!-- 列表区域改为真实接口数据保留原有表格结构 --> <!-- 列表区域改为真实接口数据保留原有表格结构 -->
<OrderRechangeTable :table-data="tableTree" :columns="tableColumnsOptions" :highlight-current-row="false" /> <OrderRechangeTable
:table-data="tableTree"
:columns="tableColumnsOptions"
:highlight-current-row="false"
>
<!-- 操作列插槽 -->
<template #operation="{ row }">
<el-button type="primary" :disabled="row.status !== '1'" @click="handleAudit(row, 3)"
>审核通过</el-button
>
<el-button type="danger" :disabled="row.status !== '1'" @click="handleAudit(row, 4)"
>审核驳回</el-button
>
</template>
</OrderRechangeTable>
<!-- 分页区域使用接口返回的 total / current_page / per_page --> <!-- 分页区域使用接口返回的 total / current_page / per_page -->
<OrderRechangePagination v-model="currentPage" v-model:pageSizeValue="pageSize" :total="total" <OrderRechangePagination
@pagination-change="handlePaginationChange" /> v-model="currentPage"
</div> v-model:pageSizeValue="pageSize"
:total="total"
@pagination-change="handlePaginationChange"
/>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -18,29 +40,32 @@ import { onMounted, ref } from 'vue'
import OrderRechangeSearch from '@/components/common/Search.vue' import OrderRechangeSearch from '@/components/common/Search.vue'
import OrderRechangeTable from '@/components/common/Table.vue' import OrderRechangeTable from '@/components/common/Table.vue'
import OrderRechangePagination from '@/components/common/Pagination.vue' import OrderRechangePagination from '@/components/common/Pagination.vue'
import { getRechargeOrderListApi } from '@/api/modules/system/rechargeOrder' import { getRechargeOrderListApi, reviewRechargeOrder } from '@/api/modules/order/rechange.ts'
import { search, tableColumns } from './options' import { search, tableColumns } from './options'
import { getRecentSevenDays } from '@/utils/handleTime.ts'
// //
interface RechangeOrderItem { interface RechangeOrderItem {
id: number id: number
orderNo: string orderNo: string
userName: string userName: string
currency: string currency: string
amount: string amount: string
status: string status: string
statusText: string statusText: string
rechangeTime: string rechangeTime: string
rechangeAccount: string rechangeAccount: string
} }
const initTime = getRecentSevenDays('YYYY-MM-DD')
// //
interface SearchFormData { interface SearchFormData {
orderNo: string trade_id: string
userName: string user_name: string
status: string status: string
startTime: string start_time: string
endTime: string end_time: string
} }
// //
@ -48,11 +73,11 @@ const searchOptions = ref(search)
// Search // Search
const createDefaultSearchForm = (): SearchFormData => ({ const createDefaultSearchForm = (): SearchFormData => ({
orderNo: '', trade_id: '',
userName: '', user_name: '',
status: '', status: '',
startTime: '', start_time: initTime.start,
endTime: '', end_time: initTime.end,
}) })
const searchForm = ref<SearchFormData>(createDefaultSearchForm()) const searchForm = ref<SearchFormData>(createDefaultSearchForm())
@ -68,133 +93,125 @@ const total = ref(0)
// 1/2/3/4 // 1/2/3/4
const getRechargeStatusText = (status: string | number | null | undefined) => { const getRechargeStatusText = (status: string | number | null | undefined) => {
const statusMap: Record<string, string> = { const statusMap: Record<string, string> = {
'1': '未审核', '1': '未审核',
'2': '未付款', '2': '未付款',
'3': '已付款', '3': '已付款',
'4': '驳回', '4': '驳回',
} }
return statusMap[String(status ?? '')] || '--' return statusMap[String(status ?? '')] || '--'
} }
// `YYYY-MM-DD HH:mm:ss`
const padZero = (value: number) => String(value).padStart(2, '0')
const formatDateTime = (date: Date) => {
const year = date.getFullYear()
const month = padZero(date.getMonth() + 1)
const day = padZero(date.getDate())
const hours = padZero(date.getHours())
const minutes = padZero(date.getMinutes())
const seconds = padZero(date.getSeconds())
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
const getTodayStartTime = () => {
const now = new Date()
now.setHours(0, 0, 0, 0)
return formatDateTime(now)
}
const getCurrentTime = () => formatDateTime(new Date())
// //
const getRechargeOrderList = async () => { const getRechargeOrderList = async () => {
const params = { const params = {
page: currentPage.value, page: currentPage.value,
page_size: pageSize.value, page_size: pageSize.value,
trade_id: searchForm.value.orderNo || null, trade_id: searchForm.value.trade_id || null,
start_time: searchForm.value.startTime ? `${searchForm.value.startTime} 00:00:00` : getTodayStartTime(), start_time: searchForm.value.start_time
end_time: searchForm.value.endTime ? `${searchForm.value.endTime} 23:59:59` : getCurrentTime(), ? `${searchForm.value.start_time} 00:00:00`
user_name: searchForm.value.userName || null, : initTime.start,
status: searchForm.value.status || null, end_time: searchForm.value.end_time ? `${searchForm.value.end_time} 23:59:59` : initTime.end,
} user_name: searchForm.value.user_name || null,
status: searchForm.value.status || null,
}
try { try {
const data: any = await getRechargeOrderListApi(params) const data: any = await getRechargeOrderListApi(params)
const list = Array.isArray(data?.data) ? data.data : [] const list = Array.isArray(data?.data) ? data.data : []
tableTree.value = list.map((item: any, index: number) => ({ tableTree.value = list.map((item: any, index: number) => ({
id: Number(item.id ?? index + 1), id: Number(item.id ?? index + 1),
// trade_id // trade_id
orderNo: String(item.trade_id ?? '--'), orderNo: String(item.trade_id ?? '--'),
// user.username // user.username
userName: item.user?.username ?? '--', userName: item.user?.username ?? '--',
// recharge_currency.symbol // recharge_currency.symbol
currency: item.recharge_currency?.symbol ?? '--', currency: item.recharge_currency?.symbol ?? '--',
// recharge_amount // recharge_amount
amount: String(item.recharge_amount ?? '--'), amount: String(item.recharge_amount ?? '--'),
status: String(item.status ?? ''), status: String(item.status ?? ''),
statusText: getRechargeStatusText(item.status), statusText: getRechargeStatusText(item.status),
// created_at // created_at
rechangeTime: item.created_at ?? '--', rechangeTime: item.created_at ?? '--',
// user.username // user.username
rechangeAccount: item.user?.username ?? '--', rechangeAccount: item.user?.username ?? '--',
})) }))
total.value = Number(data?.total ?? list.length) total.value = Number(data?.total ?? list.length)
currentPage.value = Number(data?.current_page ?? currentPage.value) currentPage.value = Number(data?.current_page ?? currentPage.value)
pageSize.value = Number(data?.per_page ?? pageSize.value) pageSize.value = Number(data?.per_page ?? pageSize.value)
} catch (error) { } catch (error) {
tableTree.value = [] tableTree.value = []
total.value = 0 total.value = 0
console.error('获取充值订单列表失败', error) console.error('获取充值订单列表失败', error)
} }
} }
// //
onMounted(() => { onMounted(() => {
getRechargeOrderList() getRechargeOrderList()
}) })
// //
const handleSearch = () => { const handleSearch = () => {
currentPage.value = 1 currentPage.value = 1
getRechargeOrderList() getRechargeOrderList()
} }
// //
const handleReset = () => { const handleReset = () => {
searchForm.value = createDefaultSearchForm() searchForm.value = createDefaultSearchForm()
currentPage.value = 1 currentPage.value = 1
pageSize.value = 10 pageSize.value = 10
getRechargeOrderList() getRechargeOrderList()
} }
// //
const handlePaginationChange = (page: { currentPage: number; pageSize: number }) => { const handlePaginationChange = (page: { currentPage: number; pageSize: number }) => {
currentPage.value = page.currentPage currentPage.value = page.currentPage
pageSize.value = page.pageSize pageSize.value = page.pageSize
getRechargeOrderList() getRechargeOrderList()
}
// 3
const handleAudit = async (row: RechangeOrderItem, status: number) => {
try {
await reviewRechargeOrder({ id: row.id, status: status })
ElMessage.success('审核通过')
await getRechargeOrderList()
} catch (error) {
ElMessage.error('审核失败')
console.error('审核失败', error)
}
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.order-rechange-page { .order-rechange-page {
min-height: 100%; min-height: 100%;
// //
:deep(.search-container .el-form) { :deep(.search-container .el-form) {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
align-items: center; align-items: center;
} }
:deep(.search-container .el-form-item) { :deep(.search-container .el-form-item) {
flex: 0 0 auto; flex: 0 0 auto;
margin-bottom: 8px; margin-bottom: 8px;
} }
:deep(.search-container .el-input), :deep(.search-container .el-input),
:deep(.search-container .el-select), :deep(.search-container .el-select),
:deep(.search-container .el-date-editor) { :deep(.search-container .el-date-editor) {
width: 100%; width: 100%;
} }
:deep(.table-container) { :deep(.table-container) {
height: calc(100vh - 220px); height: calc(100vh - 220px);
} }
} }
</style> </style>

View File

@ -1,60 +1,60 @@
// 充值订单搜索项:按接口参数保留订单号、用户名称、状态、结束时间、开始时间。 // 充值订单搜索项:按接口参数保留订单号、用户名称、状态、结束时间、开始时间。
export const search = [ export const search = [
{ {
prop: 'orderNo', prop: 'trade_id',
label: '订单号', label: '订单号',
type: 'input' as const, type: 'input' as const,
placeholder: '请输入流水号', placeholder: '请输入流水号',
width: '210px', width: '210px',
}, },
{ {
prop: 'userName', prop: 'user_name',
label: '用户名称', label: '用户名称',
type: 'input' as const, type: 'input' as const,
placeholder: '请输入用户名', placeholder: '请输入用户名',
width: '190px', width: '190px',
}, },
{ {
prop: 'status', prop: 'status',
label: '状态', label: '状态',
type: 'select' as const, type: 'select' as const,
placeholder: '请选择状态', placeholder: '请选择状态',
width: '140px', width: '140px',
options: [ options: [
{ label: '未审核', value: '1' }, { label: '未审核', value: '1' },
{ label: '未付款', value: '2' }, { label: '未付款', value: '2' },
{ label: '已付款', value: '3' }, { label: '已付款', value: '3' },
{ label: '驳回', value: '4' }, { label: '驳回', value: '4' },
], ],
}, },
{ {
prop: 'startTime', prop: 'start_time',
label: '开始时间', label: '开始时间',
type: 'date' as const, type: 'date' as const,
dateType: 'date' as const, dateType: 'date' as const,
valueFormat: 'YYYY-MM-DD', valueFormat: 'YYYY-MM-DD',
placeholder: '请选择开始时间', placeholder: '请选择开始时间',
width: '150px', width: '260px',
}, },
{ {
prop: 'endTime', prop: 'end_time',
label: '结束时间', label: '结束时间',
type: 'date' as const, type: 'date' as const,
dateType: 'date' as const, dateType: 'date' as const,
valueFormat: 'YYYY-MM-DD', valueFormat: 'YYYY-MM-DD',
placeholder: '请选择结束时间', placeholder: '请选择结束时间',
width: '150px', width: '260px',
}, },
] ]
// 充值订单表格列:对应接口数据映射后的列表字段。 // 充值订单表格列:对应接口数据映射后的列表字段。
export const tableColumns = [ export const tableColumns = [
{ prop: 'orderNo', label: '订单号', align: 'center' as const }, { prop: 'orderNo', label: '订单号', align: 'center' as const },
{ prop: 'currency', label: '充值币种', align: 'center' as const }, { prop: 'currency', label: '充值币种', align: 'center' as const },
{ prop: 'amount', label: '金额', align: 'center' as const }, { prop: 'amount', label: '金额', align: 'center' as const },
{ prop: 'statusText', label: '状态', align: 'center' as const }, { prop: 'statusText', label: '状态', align: 'center' as const },
{ prop: 'rechangeTime', label: '充值时间', align: 'center' as const }, { prop: 'rechangeTime', label: '充值时间', align: 'center' as const },
{ prop: 'rechangeAccount', label: '充值账号', align: 'center' as const }, { prop: 'rechangeAccount', label: '充值账号', align: 'center' as const },
{ label: '操作', width: '240px', align: 'center' as const, slotName: 'operation' },
] ]