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

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

View File

@ -1,70 +1,106 @@
<template>
<div class="table-container">
<el-table height="100%" :data="tableData" :row-key="rowKey" :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.slotName" :prop="column.prop" :label="column.label" :width="column.width"
:align="column.align || 'left'" :sortable="column.sortable" />
<div class="table-container">
<el-table
ref="tableRef"
height="100%"
:data="tableData"
:row-key="rowKey"
: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'">
<template #default="{ row }">
<slot :name="column.slotName" :row="row" />
</template>
</el-table-column>
</template>
</el-table>
</div>
<!-- 具名插槽列 -->
<el-table-column
v-else
:label="column.label"
:width="column.width"
:align="column.align || 'left'"
>
<template #default="{ row }">
<slot :name="column.slotName" :row="row" />
</template>
</el-table-column>
</template>
</el-table>
</div>
</template>
<script setup lang="ts">
import type { TableInstance } from 'element-plus'
//
interface TableColumn {
prop?: string
label: string
width?: string | number
align?: 'left' | 'center' | 'right'
slotName?: string //
sortable?: boolean //
prop?: string
type?: string
label: string
width?: string | number
align?: 'left' | 'center' | 'right'
slotName?: string //
sortable?: boolean //
}
//
interface Props {
tableData: any[]
rowKey?: string
treeProps?: {
children: string
}
columns?: TableColumn[],
border?: boolean //
highlightCurrentRow?: boolean //
tableData: any[]
rowKey?: string
treeProps?: {
children: string
}
columns?: TableColumn[]
border?: boolean //
highlightCurrentRow?: boolean //
}
const props = withDefaults(defineProps<Props>(), {
border: true,
highlightCurrentRow: true,
border: true,
highlightCurrentRow: true,
})
//
const emit = defineEmits<{
(e: 'row-click', row: any): void //
(e: 'row-click', row: any): void //
}>()
/**
* 处理行点击事件兼容原有逻辑
*/
const handleRowClick = (row: any) => {
// null
if (!row) return
emit('row-click', row)
// null
if (!row) return
emit('row-click', row)
}
const tableRef = ref<TableInstance>()
//
defineExpose({
clearCurrentRow: () => tableRef.value?.setCurrentRow(),
})
</script>
<style scoped lang="scss">
.table-container {
width: 100%;
height: calc(100vh - 180px);
overflow: hidden;
width: 100%;
height: calc(100vh - 180px);
overflow: hidden;
}
</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>
<div class="deposit-channel">
<div class="bar">
<el-button type="primary" @click="handleAdd">添加</el-button>
<el-button type="danger" @click="handleEnable">启用</el-button>
<el-button type="danger" @click="handleDisable">禁用</el-button>
</div>
<DepositChannelTable :table-data="tableTree" :columns="tableColumnsOptions" row-key="id"/>
<div class="deposit-channel">
<div class="bar">
<el-button type="primary" @click="handleAdd">添加</el-button>
<el-button type="primary" @click="handleEdit">编辑</el-button>
<el-button type="danger" @click="handleDelete">删除</el-button>
</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>
<script setup lang="ts">
//
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 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>
<style scoped>
.deposit-channel {
.bar {
margin-bottom: 10px;
display: flex;
align-items: center;
.bar {
margin-bottom: 10px;
display: flex;
align-items: center;
span {
margin-right: 10px;
}
span {
margin-right: 10px;
}
}
}
</style>

View File

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

View File

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

View File

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

View File

@ -1,16 +1,38 @@
<template>
<div class="order-rechange-page">
<!-- 搜索区域按接口参数保留订单号用户名称状态开始/结束时间 -->
<OrderRechangeSearch :search-form="searchForm" :search-options="searchOptions" @search="handleSearch"
@reset="handleReset" />
<div class="order-rechange-page">
<!-- 搜索区域按接口参数保留订单号用户名称状态开始/结束时间 -->
<OrderRechangeSearch
: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 -->
<OrderRechangePagination v-model="currentPage" v-model:pageSizeValue="pageSize" :total="total"
@pagination-change="handlePaginationChange" />
</div>
<!-- 分页区域使用接口返回的 total / current_page / per_page -->
<OrderRechangePagination
v-model="currentPage"
v-model:pageSizeValue="pageSize"
:total="total"
@pagination-change="handlePaginationChange"
/>
</div>
</template>
<script setup lang="ts">
@ -18,29 +40,32 @@ import { onMounted, ref } from 'vue'
import OrderRechangeSearch from '@/components/common/Search.vue'
import OrderRechangeTable from '@/components/common/Table.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 { getRecentSevenDays } from '@/utils/handleTime.ts'
//
interface RechangeOrderItem {
id: number
orderNo: string
userName: string
currency: string
amount: string
status: string
statusText: string
rechangeTime: string
rechangeAccount: string
id: number
orderNo: string
userName: string
currency: string
amount: string
status: string
statusText: string
rechangeTime: string
rechangeAccount: string
}
const initTime = getRecentSevenDays('YYYY-MM-DD')
//
interface SearchFormData {
orderNo: string
userName: string
status: string
startTime: string
endTime: string
trade_id: string
user_name: string
status: string
start_time: string
end_time: string
}
//
@ -48,11 +73,11 @@ const searchOptions = ref(search)
// Search
const createDefaultSearchForm = (): SearchFormData => ({
orderNo: '',
userName: '',
status: '',
startTime: '',
endTime: '',
trade_id: '',
user_name: '',
status: '',
start_time: initTime.start,
end_time: initTime.end,
})
const searchForm = ref<SearchFormData>(createDefaultSearchForm())
@ -68,133 +93,125 @@ const total = ref(0)
// 1/2/3/4
const getRechargeStatusText = (status: string | number | null | undefined) => {
const statusMap: Record<string, string> = {
'1': '未审核',
'2': '未付款',
'3': '已付款',
'4': '驳回',
}
const statusMap: Record<string, string> = {
'1': '未审核',
'2': '未付款',
'3': '已付款',
'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 params = {
page: currentPage.value,
page_size: pageSize.value,
trade_id: searchForm.value.orderNo || null,
start_time: searchForm.value.startTime ? `${searchForm.value.startTime} 00:00:00` : getTodayStartTime(),
end_time: searchForm.value.endTime ? `${searchForm.value.endTime} 23:59:59` : getCurrentTime(),
user_name: searchForm.value.userName || null,
status: searchForm.value.status || null,
}
const params = {
page: currentPage.value,
page_size: pageSize.value,
trade_id: searchForm.value.trade_id || null,
start_time: searchForm.value.start_time
? `${searchForm.value.start_time} 00:00:00`
: initTime.start,
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 {
const data: any = await getRechargeOrderListApi(params)
const list = Array.isArray(data?.data) ? data.data : []
try {
const data: any = await getRechargeOrderListApi(params)
const list = Array.isArray(data?.data) ? data.data : []
tableTree.value = list.map((item: any, index: number) => ({
id: Number(item.id ?? index + 1),
// trade_id
orderNo: String(item.trade_id ?? '--'),
// user.username
userName: item.user?.username ?? '--',
// recharge_currency.symbol
currency: item.recharge_currency?.symbol ?? '--',
// recharge_amount
amount: String(item.recharge_amount ?? '--'),
status: String(item.status ?? ''),
statusText: getRechargeStatusText(item.status),
// created_at
rechangeTime: item.created_at ?? '--',
// user.username
rechangeAccount: item.user?.username ?? '--',
}))
tableTree.value = list.map((item: any, index: number) => ({
id: Number(item.id ?? index + 1),
// trade_id
orderNo: String(item.trade_id ?? '--'),
// user.username
userName: item.user?.username ?? '--',
// recharge_currency.symbol
currency: item.recharge_currency?.symbol ?? '--',
// recharge_amount
amount: String(item.recharge_amount ?? '--'),
status: String(item.status ?? ''),
statusText: getRechargeStatusText(item.status),
// created_at
rechangeTime: item.created_at ?? '--',
// user.username
rechangeAccount: item.user?.username ?? '--',
}))
total.value = Number(data?.total ?? list.length)
currentPage.value = Number(data?.current_page ?? currentPage.value)
pageSize.value = Number(data?.per_page ?? pageSize.value)
} catch (error) {
tableTree.value = []
total.value = 0
console.error('获取充值订单列表失败', error)
}
total.value = Number(data?.total ?? list.length)
currentPage.value = Number(data?.current_page ?? currentPage.value)
pageSize.value = Number(data?.per_page ?? pageSize.value)
} catch (error) {
tableTree.value = []
total.value = 0
console.error('获取充值订单列表失败', error)
}
}
//
onMounted(() => {
getRechargeOrderList()
getRechargeOrderList()
})
//
const handleSearch = () => {
currentPage.value = 1
getRechargeOrderList()
currentPage.value = 1
getRechargeOrderList()
}
//
const handleReset = () => {
searchForm.value = createDefaultSearchForm()
currentPage.value = 1
pageSize.value = 10
getRechargeOrderList()
searchForm.value = createDefaultSearchForm()
currentPage.value = 1
pageSize.value = 10
getRechargeOrderList()
}
//
const handlePaginationChange = (page: { currentPage: number; pageSize: number }) => {
currentPage.value = page.currentPage
pageSize.value = page.pageSize
getRechargeOrderList()
currentPage.value = page.currentPage
pageSize.value = page.pageSize
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>
<style scoped lang="scss">
.order-rechange-page {
min-height: 100%;
min-height: 100%;
//
:deep(.search-container .el-form) {
display: flex;
flex-wrap: wrap;
align-items: center;
}
//
:deep(.search-container .el-form) {
display: flex;
flex-wrap: wrap;
align-items: center;
}
:deep(.search-container .el-form-item) {
flex: 0 0 auto;
margin-bottom: 8px;
}
:deep(.search-container .el-form-item) {
flex: 0 0 auto;
margin-bottom: 8px;
}
:deep(.search-container .el-input),
:deep(.search-container .el-select),
:deep(.search-container .el-date-editor) {
width: 100%;
}
:deep(.search-container .el-input),
:deep(.search-container .el-select),
:deep(.search-container .el-date-editor) {
width: 100%;
}
:deep(.table-container) {
height: calc(100vh - 220px);
}
:deep(.table-container) {
height: calc(100vh - 220px);
}
}
</style>

View File

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