@@ -0,0 +1,129 @@
< template >
< div >
< div class = "kra-search-box" >
< el-form :model = "query" :inline = "true" @submit.prevent ="search" >
< el -form -item label = "所属平台" > < el-select v-model = "sources" multiple collapse-tags clearable placeholder="选择平台" style="width: 210px" @change="sourceChanged"><el-option v-for="(label, value) in platforms" :key="value" :label="label" :value="value" / > < / el-select > < / el-form-item >
< el-form-item label = "商务账号" > < el-input v-model = "query.busierAccount" clearable placeholder="商务账号" / > < / el-form-item >
< el-form-item label = "玩家ID" > < el-input v-model = "playerID" clearable placeholder="玩家ID" / > < / el-form-item >
< el-form-item v-if = "spend" label="角色名"><el-input v-model="query.gamePlayerName" clearable placeholder="角色名" / > < / el-form-item >
< el-form-item label = "渠道账号" > < el-input v-model = "query.promoteAccount" clearable placeholder="渠道账号(精确匹配)" / > < / el-form-item >
< el-form-item v-if = "spend" label="订单号"><el-input v-model="query.payOrderNumber" clearable placeholder="订单号" / > < / el-form-item >
< el-form-item label = "游戏" > < el-select v-model = "gameNames" multiple collapse-tags clearable filterable remote reserve-keyword :remote-method="loadGames" :loading="gamesLoading" :disabled="!sources.length" placeholder="全部游戏" style="width: 200px" @visible-change="open => open && loadGames('')"><el-option v-for="name in gameOptions" :key="name" :label="name" :value="name" / > < / el-select > < / el-form-item >
< el-form-item v-if = "spend" label="方式"><el-select v-model="query.payWay" clearable placeholder="全部方式" style="width: 140px"><el-option v-for="(label, value) in payWays" :key="value" :label="label" :value="Number(value)" / > < / el-select > < / el-form-item >
< el-form-item v-if = "spend" label="状态"><el-select v-model="query.payStatus" clearable placeholder="全部状态" style="width: 140px"><el-option v-for="(label, value) in statuses" :key="value" :label="label" :value="Number(value)" / > < / el-select > < / el-form-item >
< el-form-item label = "日期" > < el-date-picker v-model = "dates" type="daterange" value-format="YYYY-MM-DD" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" :clearable="false" :shortcuts="shortcuts" style="width: 300px" / > < / el-form-item >
< el-form-item > < el-button type = "primary" icon = "search" native -type = " submit " > 搜索 < / el-button > < / el-form-item >
< / el-form >
< / div >
< div class = "kra-table-box" >
< div class = "kra-btn-list" > < el-button v-if = "canExport" type="warning" plain icon="download" :loading="exporting" @click="download" > 导出 < / el -button > < / div >
< el-alert v-if = "error" class="mb-4" type="error" :closable="false" :title="error" / >
< el-alert v-else-if = "!available" class="mb-4" type="warning" :closable="false" title="明细所需业务数据尚未接入。" / >
< el-table v-loading = "loading" :data="rows" :row-key="row => `${row.sourceDb}:${row.id}`" :row-class-name="rowClass" :default-sort="{ prop: spend ? 'payTime' : 'registerTime', order: 'descending' }" @sort-change="sortChanged" >
< el -table -column prop = "sourceDbLabel" label = "所属平台" min -width = " 100 " / >
< el-table-column v-if = "spend" prop="id" label="ID" min-width="90" / >
< el-table-column prop = "userId" label = "玩家ID" min -width = " 110 " / >
< el-table-column v-if = "!spend" prop="registerTime" label="注册时间" min-width="145" sortable="custom"><template #default="{ row }" > {{ unixTime ( row.registerTime ) }} < / template > < / el -table -column >
< el-table-column v-if = "spend" prop="payOrderNumber" label="订单号" min-width="190" show -overflow -tooltip / >
< el-table-column : prop = "spend ? 'gameName' : 'fgameName'" label = "游戏" min -width = " 150 " show -overflow -tooltip / >
< el-table-column v-if = "spend" prop="serverName" label="区服" min-width="140" show -overflow -tooltip / >
< el-table-column v-if = "spend" prop="gamePlayerName" label="角色" min-width="130" show -overflow -tooltip / >
< el-table-column : prop = "spend ? 'payAmount' : 'totalPayAmount'" : label = "spend ? '实付金额' : '累计充值'" min -width = " 150 " sortable = "custom" > < template # default = "{ row }" > { { channelMoney ( spend ? row . payAmount ? ? '0.00' : row . totalPayAmount ) } } < / template > < / el-table-column >
< el-table-column v-if = "spend" prop="payTime" label="充值时间" min-width="145" sortable="custom"><template #default="{ row }" > {{ unixTime ( row.payTime ) }} < / template > < / el -table -column >
< el-table-column v-if = "spend" label="方式" min-width="100"><template #default="{ row }" > {{ payWays [ row.payWay ] ? ? row.payWay ? ? ' — ' }} < / template > < / el -table -column >
< el-table-column v-if = "spend" label="状态" min-width="110"><template #default="{ row }"><el-tag :type="{ 1: 'success', 2: 'warning', 0: 'danger' }[row.payStatus] || 'info'" > {{ statuses [ row.payStatus ] ? ? row.payStatus ? ? ' — ' }} < / el -tag > < / template > < / el-table-column >
< el-table-column : label = "spend ? 'IP' : '注册IP'" min -width = " 130 " > < template # default = "{ row }" > { { ( spend ? row . spendIp : row . registerIp ) || '—' } } < / template > < / el-table-column >
< el-table-column v-if = "!spend" prop="deviceName" label="机型" min-width="140" show -overflow -tooltip / >
< el-table-column prop = "promoteAccount" label = "渠道" min -width = " 170 " show -overflow -tooltip / >
< el-table-column prop = "busierName" label = "归属商务" min -width = " 190 " show -overflow -tooltip / >
< / el-table >
< p v-if = "spend" class="mt-4 text-right" > 合计 : 成功支付总额 < strong > { { error ? '未获取' : available ? channelMoney ( totalPayAmount ) : '未接入' } } < / strong > < / p >
< div class = "kra-pagination" > < el-pagination v -model :current-page = "query.pageNum" v -model :page-size = "query.pageSize" :total = "total" : page -sizes = " [ 10 , 20 , 50 , 100 ] " layout = "total, sizes, prev, pager, next, jumper" @ size -change = " search " @ current -change = " load " / > < / div >
< / div >
< / div >
< / template >
< script setup >
import { computed , onMounted , reactive , ref } from 'vue'
import { ElMessage } from 'element-plus'
import { useUserStore } from '@/pinia/modules/user'
import { useBtnAuth } from '@/utils/btnAuth'
import { listLedger , exportLedger , listLedgerGames } from '@/api/ledger'
import { channelMoney } from '../channel-display'
const props = defineProps ( { kind : { type : String , required : true } } )
const spend = computed ( ( ) => props . kind === 'spend' )
const user = useUserStore ( ) , buttons = useBtnAuth ( )
const canExport = computed ( ( ) => Number ( user . userInfo . authorityId ) === 888 || ! ! buttons [ ` sw: ${ props . kind } :export ` ] )
const platforms = { lx : '龙翔' , hw : '汇玩' , yq : '游趣' }
const payWays = { 1 : '绑币' , 2 : '平台币' , 3 : '支付宝' , 4 : '微信' , 5 : '金猪' , 6 : '苹果支付' , 11 : '盒子币' , 12 : '龙翔币' , 14 : '抖音小游戏' }
const statuses = { 1 : '成功' , 2 : '处理中' , 0 : '失败' }
const today = ( ) => { const now = new Date ( ) ; return new Date ( now . getFullYear ( ) , now . getMonth ( ) , now . getDate ( ) ) }
const ymd = date => ` ${ date . getFullYear ( ) } - ${ String ( date . getMonth ( ) + 1 ) . padStart ( 2 , '0' ) } - ${ String ( date . getDate ( ) ) . padStart ( 2 , '0' ) } `
const end = today ( ) , begin = today ( ) ; begin . setDate ( begin . getDate ( ) - 29 )
const dates = ref ( [ ymd ( begin ) , ymd ( end ) ] )
const shortcuts = [
{ text : '今日' , value : ( ) => [ today ( ) , today ( ) ] } ,
{ text : '昨日' , value : ( ) => { const d = today ( ) ; d . setDate ( d . getDate ( ) - 1 ) ; return [ d , new Date ( d ) ] } } ,
{ text : '本周' , value : ( ) => { const e = today ( ) , b = today ( ) ; b . setDate ( b . getDate ( ) - ( b . getDay ( ) || 7 ) + 1 ) ; return [ b , e ] } } ,
{ text : '本月' , value : ( ) => { const e = today ( ) ; return [ new Date ( e . getFullYear ( ) , e . getMonth ( ) , 1 ) , e ] } }
]
const sources = ref ( Object . keys ( platforms ) ) , gameNames = ref ( [ ] ) , gameOptions = ref ( [ ] ) , playerID = ref ( '' )
const gamesLoading = ref ( false ) , exporting = ref ( false ) , loading = ref ( false ) , available = ref ( true )
const error = ref ( '' )
const rows = ref ( [ ] ) , total = ref ( 0 ) , totalPayAmount = ref ( '0.00' )
const query = reactive ( { pageNum : 1 , pageSize : 10 , busierAccount : '' , promoteAccount : '' , gamePlayerName : '' , payOrderNumber : '' , payStatus : undefined , payWay : undefined , sortField : spend . value ? 'payTime' : 'registerTime' , sortOrder : 'desc' } )
let listVersion = 0 , gameVersion = 0
const buildQuery = ( ) => {
const player = playerID . value . trim ( )
if ( player && ! /^\d+$/ . test ( player ) ) { ElMessage . warning ( '玩家ID应为整数' ) ; return null }
const result = { ... query , sourceDb : sources . value . join ( ',' ) , gameName : gameNames . value . join ( ',' ) , userId : player || undefined , busierAccount : query . busierAccount . trim ( ) , promoteAccount : query . promoteAccount . trim ( ) , gamePlayerName : query . gamePlayerName . trim ( ) , payOrderNumber : query . payOrderNumber . trim ( ) }
result [ spend . value ? 'beginPayTime' : 'beginRegisterTime' ] = dates . value ? . [ 0 ]
result [ spend . value ? 'endPayTime' : 'endRegisterTime' ] = dates . value ? . [ 1 ]
return result
}
const load = async ( ) => {
const version = ++ listVersion
if ( ! sources . value . length ) { rows . value = [ ] ; total . value = 0 ; totalPayAmount . value = '0.00' ; available . value = true ; loading . value = false ; error . value = '' ; return }
const params = buildQuery ( ) ; if ( ! params ) { loading . value = false ; return }
loading . value = true ; error . value = ''
try {
const res = await listLedger ( props . kind , params )
if ( version !== listVersion ) return
if ( res . code === 0 ) { rows . value = res . data . rows ; total . value = res . data . total ; totalPayAmount . value = res . data . totalPayAmount ; available . value = res . data . available !== false }
else { rows . value = [ ] ; total . value = 0 ; totalPayAmount . value = null ; error . value = res . msg || '明细查询失败' }
} catch { if ( version === listVersion ) { rows . value = [ ] ; total . value = 0 ; totalPayAmount . value = null ; error . value = '明细查询失败,请重试' } } finally { if ( version === listVersion ) loading . value = false }
}
const search = ( ) => { query . pageNum = 1 ; if ( ! sources . value . length ) ElMessage . warning ( '请选择平台' ) ; return load ( ) }
const sortChanged = ( { prop , order } ) => { query . sortField = prop || ( spend . value ? 'payTime' : 'registerTime' ) ; query . sortOrder = order === 'ascending' ? 'asc' : 'desc' ; return search ( ) }
const sourceChanged = ( ) => { ++ gameVersion ; gameNames . value = [ ] ; gameOptions . value = [ ] ; gamesLoading . value = false }
const loadGames = async ( keyword = '' ) => {
const version = ++ gameVersion ; if ( ! sources . value . length ) return
gamesLoading . value = true
try { const res = await listLedgerGames ( { sourceDb : sources . value . join ( ',' ) , keyword , limit : 50 } ) ; if ( version === gameVersion && res . code === 0 ) gameOptions . value = [ ... new Set ( [ ... gameNames . value , ... res . data . map ( row => row . gameName ) . filter ( Boolean ) ] ) ] } finally { if ( version === gameVersion ) gamesLoading . value = false }
}
const unixTime = value => {
if ( value == null ) return '—'
const date = new Date ( Number ( value ) * 1000 )
if ( Number . isNaN ( date . getTime ( ) ) ) return '—'
return ` ${ String ( date . getMonth ( ) + 1 ) . padStart ( 2 , '0' ) } - ${ String ( date . getDate ( ) ) . padStart ( 2 , '0' ) } ${ String ( date . getHours ( ) ) . padStart ( 2 , '0' ) } : ${ String ( date . getMinutes ( ) ) . padStart ( 2 , '0' ) } `
}
const rowClass = ( { row } ) => spend . value && Number ( row . payAmount ) >= 5000 ? 'large-recharge-row' : ''
const download = async ( ) => {
if ( ! sources . value . length ) { ElMessage . warning ( '请选择平台' ) ; return }
const params = buildQuery ( ) ; if ( ! params ) return
exporting . value = true
try {
const result = await exportLedger ( props . kind , params )
const blob = result instanceof Blob ? result : result . data
if ( ! ( blob instanceof Blob ) ) return
if ( blob . type . includes ( 'json' ) ) return
const url = URL . createObjectURL ( blob ) , link = document . createElement ( 'a' )
link . href = url ; link . download = spend . value ? '充值明细.xlsx' : '注册明细.xlsx' ; link . click ( ) ; setTimeout ( ( ) => URL . revokeObjectURL ( url ) , 1000 )
} finally { exporting . value = false }
}
onMounted ( load )
< / script >
< style scoped >
: deep ( . large - recharge - row ) { -- el - table - tr - bg - color : var ( -- el - color - warning - light - 9 ) ; }
< / style >