钱包管理模块

This commit is contained in:
Songsl 2026-06-14 15:43:02 +08:00
parent a3ef32d035
commit 6b49a037e0
5 changed files with 518 additions and 0 deletions

View File

@ -0,0 +1,36 @@
import { request } from '@/api'
// 提现订单列表
export const getWithdrawOrderList = (params: {
page: number
page_size: number
start_time?: string
end_time?: string
status?: string
}) => {
return request.get('/upay/withdrawOrderList', params as any)
}
// 刷新地址余额
export const balanceRefresh = (params: { address?: string, addressType?: string }) => {
return request.post('/upay/balanceRefresh', params)
}
// 提现地址列表
export const getAddressList = (params: {
page: number
page_size: number
address?: string
addressType?: string
}) => {
return request.post('/upay/addressList', params as any)
}
// 提现
export const withdrawAction = (params: {
from: string
to: string
amount: string
}) => {
return request.post('/upay/withdrawAction', params)
}

View File

@ -0,0 +1,228 @@
<template>
<div class="address-list-page table_form-container">
<!-- 搜索 -->
<Search
:search-form="searchForm"
:search-options="searchOptions"
:button-loading="loading"
@search="handleSearch"
@reset="handleReset"
>
</Search>
<!-- 表格 -->
<Table
ref="tableRef"
:table-data="tableData"
:columns="tableColumnsOptions"
row-key="address"
:loading="loading"
@rowClick="handleRowClick"
>
<template #action="{ row }">
<el-button type="primary" size="small" @click.stop="openWithdrawDialog(row)">提现</el-button>
<el-button size="small" @click.stop="handleRefresh(row)">刷新</el-button>
</template>
</Table>
<!-- 分页 -->
<Pagination
v-model="queryParams.page"
v-model:pageSizeValue="queryParams.page_size"
:total="total"
:layout="'prev, pager, next'"
@pagination-change="getList"
/>
<!-- 提现弹窗 -->
<el-dialog v-model="withdrawDialogVisible" title="提现" width="600px">
<el-form :model="withdrawForm" label-width="80px">
<el-form-item label="来源地址">
<el-input v-model="withdrawForm.from" disabled />
</el-form-item>
<el-form-item label="目标地址" required>
<el-input v-model="withdrawForm.to" placeholder="请输入目标地址" />
</el-form-item>
<el-form-item label="金额" required>
<el-input v-model="withdrawForm.amount" placeholder="请输入金额" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="withdrawDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="withdrawLoading" @click="handleWithdraw">确认</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import Search from '@/components/common/Search.vue'
import Table from '@/components/common/Table.vue'
import Pagination from '@/components/common/Pagination.vue'
import { usePageLoading } from '@/composables/useLoading'
import { tableColumns, searchOptions } from './options'
import { getAddressList, balanceRefresh, withdrawAction } from '@/api/modules/wallet/wallet'
defineOptions({
name: 'AddressList'
})
interface AddressRow {
address: string
trxBalance: string
usdtBalance: string
addressType: string
createdAt: string
}
const tableRef = ref()
const tableData = ref<AddressRow[]>([])
const selectedRow = ref<AddressRow | null>(null)
const { loading, startLoading, stopLoading } = usePageLoading()
//
const withdrawDialogVisible = ref(false)
const withdrawLoading = ref(false)
const withdrawForm = reactive({
from: '',
to: '',
amount: '',
})
//
const queryParams = reactive({
page: 1,
page_size: 20,
})
const total = ref(0)
//
const searchForm = ref({
addressType: '',
address: '',
})
//
const tableColumnsOptions = tableColumns
//
const getList = async () => {
startLoading()
try {
const res = await getAddressList({
address: searchForm.value.address || '',
addressType: searchForm.value.addressType || '',
page: queryParams.page,
page_size: queryParams.page_size,
})
if (res) {
tableData.value = res?.address_list?.data?.list || []
if (res?.address_list?.data?.list?.length === 20) {
total.value = queryParams.page * queryParams.page_size
}else{
total.value = queryParams.page * queryParams.page_size - 1
}
console.log("total.value", total.value)
}
} catch (error) {
console.error('获取列表失败', error)
} finally {
stopLoading()
}
}
//
const handleSearch = () => {
queryParams.page = 1
getList()
}
//
const handleReset = () => {
searchForm.value = {
addressType: '',
address: '',
}
queryParams.page = 1
getList()
}
//
const handleRowClick = (row: AddressRow) => {
selectedRow.value = row
}
//
const openWithdrawDialog = (row?: AddressRow) => {
const targetRow = row || selectedRow.value
if (!targetRow) {
ElMessage.warning('请选择一条记录')
return
}
withdrawForm.from = targetRow.address
withdrawForm.to = ''
withdrawForm.amount = ''
withdrawDialogVisible.value = true
}
//
const handleWithdraw = async () => {
if (!withdrawForm.to) {
ElMessage.warning('请输入目标地址')
return
}
if (!withdrawForm.amount) {
ElMessage.warning('请输入金额')
return
}
withdrawLoading.value = true
try {
await withdrawAction({
from: withdrawForm.from,
to: withdrawForm.to,
amount: withdrawForm.amount,
})
ElMessage.success('提现成功')
withdrawDialogVisible.value = false
getList()
} catch (error) {
console.error('提现失败', error)
} finally {
withdrawLoading.value = false
}
}
//
const handleRefresh = async (row?: AddressRow) => {
const targetRow = row || selectedRow.value
if (!targetRow) {
ElMessage.warning('请选择一条记录')
return
}
try {
const res = await balanceRefresh({ address: targetRow.address, addressType: targetRow.addressType })
console.log(res)
if (res) {
ElMessage.success('刷新成功')
const index = tableData.value.findIndex(item => item.address === targetRow.address)
if (index !== -1) {
tableData.value[index]!.trxBalance = res.data;
}
}
} catch (error) {
console.error('刷新失败', error)
}
}
onMounted(() => {
getList()
})
</script>
<style scoped lang="scss">
.address-list-page {
}
</style>

View File

@ -0,0 +1,40 @@
// 提现地址列表页面表格列配置
export const tableColumns = [
// 序号
{ label: '序号', type: 'index', width: 60, align: 'center' as const },
// 地址
{ label: '地址', prop: 'address', width: 400, align: 'center' as const, copy: true },
// TRX余额
{ label: 'TRX余额', prop: 'trxBalance', align: 'center' as const },
// USDT余额
{ label: 'USDT余额', prop: 'usdtBalance', align: 'center' as const },
// 地址类型
{ label: '地址类型', prop: 'addressType', align: 'center' as const },
// 创建时间
{ label: '创建时间', prop: 'createdAt', align: 'center' as const },
// 操作
{ label: '操作', prop: 'action', width: 160, slotName: 'action' as const },
]
// 搜索配置
export const searchOptions = [
{
prop: 'addressType',
label: '钱包类型',
type: 'select' as const,
placeholder: '请选择钱包类型',
width: '220px',
options: [
{ label: 'TRX', value: 'TRX' },
{ label: 'ETH', value: 'ETH' },
{ label: 'BTC', value: 'BTC' },
],
},
{
prop: 'address',
label: '钱包地址',
type: 'input' as const,
placeholder: '请输入钱包地址',
width: '220px',
},
]

View File

@ -0,0 +1,144 @@
<template>
<div class="withdraw-order-list-page table_form-container">
<!-- 搜索 -->
<Search :search-form="searchForm" :search-options="searchOptions" :button-loading="loading"
@search="handleSearch" @reset="handleReset" />
<!-- 表格 -->
<Table ref="tableRef" :table-data="tableData" :columns="tableColumnsOptions" row-key="id" :loading="loading"
@rowClick="handleRowClick" />
<!-- 分页 -->
<Pagination v-model="queryParams.page" v-model:pageSizeValue="queryParams.page_size" :total="total"
:layout="'prev, pager, next'" @pagination-change="getList" />
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import Search from '@/components/common/Search.vue'
import Table from '@/components/common/Table.vue'
import Pagination from '@/components/common/Pagination.vue'
import { usePageLoading } from '@/composables/useLoading'
import { tableColumns, searchOptions } from './options'
import { getLast7DaysRange } from '@/utils/timeUtils'
import { getWithdrawOrderList } from '@/api/modules/wallet/wallet'
defineOptions({
name: 'WithdrawOrderList'
})
interface WithdrawOrderRow {
id: number
user_id: number
order_n: string
notify_n: string | null
from: string
to: string
amount: string
addressType: string | null
balance: string | null
status: number
create_time: number
success_time: number | null
tx_id: string | null
created_at: string
updated_at: string
}
const tableRef = ref()
const tableData = ref<WithdrawOrderRow[]>([])
const { loading, startLoading, stopLoading } = usePageLoading()
//
const queryParams = reactive({
page: 1,
page_size: 20,
})
const total = ref(0)
//
const getDefaultTimeRange = () => {
const range = getLast7DaysRange()
const formatDate = (d: Date): string => {
const year = d.getFullYear()
const month = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
const hours = String(d.getHours()).padStart(2, '0')
const minutes = String(d.getMinutes()).padStart(2, '0')
const seconds = String(d.getSeconds()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
return {
start_time: formatDate(range.startTime),
end_time: formatDate(range.endTime),
}
}
//
const searchForm = ref({
status: '',
start_time: '',
end_time: '',
})
//
const tableColumnsOptions = tableColumns
//
const getList = async () => {
startLoading()
try {
const res = await getWithdrawOrderList({
status: searchForm.value.status || '',
start_time: searchForm.value.start_time || '',
end_time: searchForm.value.end_time || '',
page: queryParams.page || 1,
page_size: queryParams.page_size || 20,
})
if (res) {
tableData.value = res?.data || []
total.value = res?.total || 0
}
} catch (error) {
console.error('获取列表失败', error)
} finally {
stopLoading()
}
}
//
const handleSearch = () => {
queryParams.page = 1
getList()
}
//
const handleReset = () => {
const range = getDefaultTimeRange()
searchForm.value = {
status: '',
start_time: range.start_time,
end_time: range.end_time,
}
queryParams.page = 1
getList()
}
//
const handleRowClick = (row: WithdrawOrderRow) => {
console.log('选中行', row)
}
onMounted(() => {
//
const range = getDefaultTimeRange()
searchForm.value.start_time = range.start_time
searchForm.value.end_time = range.end_time
getList()
})
</script>
<style scoped lang="scss">
.withdraw-order-list-page {}
</style>

View File

@ -0,0 +1,70 @@
// 状态选项
const statusOptions = [
{ label: '未完成', value: 1 },
{ label: '成功', value: 2 },
{ label: '失败', value: 3 },
]
// 提现订单列表页面表格列配置
export const tableColumns = [
// 序号
{ label: '序号', type: 'index', width: 60, align: 'center' as const },
// 用户ID
// { label: '用户ID', prop: 'user_id' },
// 订单号
{ label: '订单号', prop: 'order_n', width: 300, align: 'center' as const, copy: true },
// 通知号
{ label: '通知号', prop: 'notify_n', width: 200, align: 'center' as const },
// 来源地址
{ label: '来源地址', prop: 'from', width: 400, align: 'center' as const, copy: true },
// 目标地址
{ label: '目标地址', prop: 'to', width: 400, align: 'center' as const, copy: true },
// 金额
{ label: '金额', prop: 'amount', width: 160, align: 'center' as const },
// 地址类型
{ label: '地址类型', prop: 'addressType', width: 100, align: 'center' as const },
// 余额
{ label: '余额', prop: 'balance', width: 160, align: 'center' as const },
// 状态
{ label: '状态', prop: 'status', width: 100, align: 'center' as const, options: statusOptions },
// 创建时间
{ label: '创建时间', prop: 'create_time', width: 200, align: 'center' as const },
// 成功时间
{ label: '成功时间', prop: 'success_time', width: 200, align: 'center' as const },
// 交易ID
{ label: '交易ID', prop: 'tx_id', width: 400, align: 'center' as const },
]
// 搜索配置
export const searchOptions = [
{
prop: 'status',
label: '状态',
type: 'select' as const,
placeholder: '请选择状态',
width: '160px',
options: statusOptions,
},
{
prop: 'start_time',
label: '开始时间',
type: 'date' as const,
dateType: 'datetime' as const,
placeholder: '开始时间',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
format: 'YYYY-MM-DD',
width: '220px',
defaultTime: 'start' as const,
},
{
prop: 'end_time',
label: '结束时间',
type: 'date' as const,
dateType: 'datetime' as const,
placeholder: '结束时间',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
format: 'YYYY-MM-DD',
width: '220px',
defaultTime: 'end' as const,
},
]