1.结算记录中新增结算信息

2.优化渠道页面样式
This commit is contained in:
2026-05-30 14:58:17 +08:00
parent 1cdd597879
commit 7ab3db121c
13 changed files with 297 additions and 61 deletions

View File

@@ -32,5 +32,59 @@ class CommissionRecord extends Backend
$this->model = new \app\common\model\AgentCommissionRecord();
return null;
}
protected function _index(): Response
{
if ($this->request && $this->request->get('select')) {
return $this->select($this->request);
}
list($where, $alias, $limit, $order) = $this->queryBuilder();
$res = $this->model
->field($this->indexField)
->withJoin($this->withJoinTable, $this->withJoinType)
->with($this->withJoinTable)
->alias($alias)
->where($where)
->order($order)
->paginate($limit);
$list = $this->enrichCommissionRecordList($res->items());
return $this->success('', [
'list' => $list,
'total' => $res->total(),
'remark' => get_route_remark(),
]);
}
/**
* @param array<int, mixed> $items
* @return array<int, mixed>
*/
private function enrichCommissionRecordList(array $items): array
{
if ($items === []) {
return $items;
}
$out = [];
foreach ($items as $item) {
$row = is_array($item) ? $item : (method_exists($item, 'toArray') ? $item->toArray() : []);
$gross = strval($row['commission_amount'] ?? '0.00');
$fee = strval($row['handling_fee'] ?? '0.00');
$net = strval($row['net_commission_amount'] ?? '0.00');
if (bccomp($net, '0', 2) <= 0 && (bccomp($gross, '0', 2) > 0 || bccomp($fee, '0', 2) > 0)) {
$net = bcsub($gross, $fee, 2);
if (bccomp($net, '0', 2) < 0) {
$net = '0.00';
}
$row['net_commission_amount'] = $net;
}
$out[] = $row;
}
return $out;
}
}

View File

@@ -121,6 +121,7 @@ return [
'periodSettings' => 'Period settings',
'manualSettle' => 'Manual settle',
'batchSettlePending' => 'Batch settle pending channels',
'viewCommissionRecords' => 'View commission records',
'viewDividendRecords' => 'View paid dividend records',
'viewDirectBetRecords' => 'View direct bet records',
'viewSettlementBetRecords' => 'View settlement-scope bets',

View File

@@ -53,6 +53,7 @@ return [
'periodSettings' => '期号设置',
'manualSettle' => '手动结算',
'batchSettlePending' => '批量结算待结算渠道',
'viewCommissionRecords' => '查看代理佣金记录',
'viewDividendRecords' => '查看已分红记录',
'viewDirectBetRecords' => '查看直属投注记录',
'viewSettlementBetRecords' => '查看总投注金额',

View File

@@ -15,8 +15,13 @@ class AgentCommissionRecord extends Model
'update_time' => 'integer',
'settled_at' => 'integer',
'commission_rate' => 'string',
'share_rate' => 'string',
'calc_base_amount' => 'string',
'commission_amount' => 'string',
'commission_share_percent' => 'string',
'handling_fee' => 'string',
'handling_fee_rate' => 'string',
'net_commission_amount' => 'string',
'status' => 'integer',
];

View File

@@ -194,6 +194,14 @@ class AdminCommissionDistributionService
if ($nodes === []) {
return [];
}
$shareRateByAdmin = [];
foreach ($nodes as $node) {
$nodeAdminId = intval($node['admin_id'] ?? 0);
if ($nodeAdminId <= 0) {
continue;
}
$shareRateByAdmin[$nodeAdminId] = strval($node['share_rate'] ?? '0.00');
}
$defaultRate = self::normalizeHandlingFeeRatePercent($defaultHandlingFeeRate);
$merged = [];
foreach ($nodes as $node) {
@@ -223,6 +231,7 @@ class AdminCommissionDistributionService
'commission_amount' => $gross,
'commission_rate' => $effectiveRate,
'calc_base_amount' => $settlementBase,
'share_rate' => $shareRateByAdmin[$adminId] ?? '0.00',
'commission_share_percent' => self::calcCommissionSharePercent($gross, $totalCommission),
'handling_fee_rate' => $feeRate,
'handling_fee' => $feeAmount,

View File

@@ -73,7 +73,6 @@ class ChannelSettlementService
if ($adminId <= 0) {
continue;
}
unset($row['net_commission_amount']);
$row['status'] = 1;
$row['settled_at'] = $now;
$row['remark'] = strval($row['remark'] ?? '') . ' | 超管结算直接发放';
@@ -370,9 +369,12 @@ class ChannelSettlementService
'channel_id' => $channelId,
'admin_id' => $adminId,
'commission_rate' => strval($dist['commission_rate'] ?? '0.0000'),
'share_rate' => strval($dist['share_rate'] ?? '0.00'),
'calc_base_amount' => strval($dist['calc_base_amount'] ?? '0.00'),
'commission_amount' => $amount,
'commission_share_percent' => strval($dist['commission_share_percent'] ?? '0.00'),
'handling_fee' => strval($dist['handling_fee'] ?? '0.00'),
'handling_fee_rate' => strval($dist['handling_fee_rate'] ?? '0.00'),
'net_commission_amount' => strval($dist['net_commission_amount'] ?? '0.00'),
'status' => 0,
'settled_at' => null,

View File

@@ -7,11 +7,16 @@ export default {
channel_name: 'Channel',
admin_id: 'Agent admin',
admin_username: 'Agent username',
commission_rate: 'Commission rate',
calc_base_amount: 'Calculation base amount',
commission_amount: 'Commission amount (gross)',
handling_fee: 'Handling fee amount',
calc_base_amount: 'Settlement base',
share_rate: 'Share rate',
commission_rate: 'Effective rate',
commission_amount: 'Commission (gross)',
commission_share_percent: 'Share of total',
handling_fee_rate: 'Handling fee rate',
handling_fee: 'Handling fee',
net_commission_amount: 'Net commission',
filter_by_settlement_no: 'Filtered by settlement no.: {no}',
reset_settlement_filter: 'Reset filter',
status: 'Status',
'status 0': 'Pending',
'status 1': 'Paid',

View File

@@ -15,5 +15,6 @@ export default {
remark: 'Remark',
create_time: 'Created',
update_time: 'Updated',
view_commission_records: 'View commission records',
}

View File

@@ -7,11 +7,16 @@ export default {
channel_name: '渠道名称',
admin_id: '代理管理员',
admin_username: '代理账号',
commission_rate: '佣金比例',
calc_base_amount: '结算基数',
commission_amount: '佣金金额(费前)',
handling_fee: '手续费金额',
share_rate: '分配比例',
commission_rate: '有效比例',
commission_amount: '佣金(费前)',
commission_share_percent: '占比',
handling_fee_rate: '手续费比例',
handling_fee: '手续费',
net_commission_amount: '实发佣金',
filter_by_settlement_no: '当前筛选结算周期号:{no}',
reset_settlement_filter: '重置筛选',
status: '状态',
'status 0': '待发放',
'status 1': '已发放',

View File

@@ -15,5 +15,6 @@ export default {
remark: '备注',
create_time: '创建时间',
update_time: '更新时间',
view_commission_records: '查看代理佣金记录',
}

View File

@@ -2,6 +2,21 @@
<div class="default-main ba-table-box">
<el-alert class="ba-table-alert" v-if="baTable.table.remark" :title="baTable.table.remark" type="info" show-icon />
<el-alert
v-if="settlementNoFilter"
class="commission-record-filter-alert"
type="warning"
:closable="false"
show-icon
>
<template #title>
{{ t('agent.commissionRecord.filter_by_settlement_no', { no: settlementNoFilter }) }}
<el-button link type="primary" class="commission-record-filter-reset" @click="resetSettlementNoFilter">
{{ t('agent.commissionRecord.reset_settlement_filter') }}
</el-button>
</template>
</el-alert>
<TableHeader
:buttons="['refresh', 'add', 'edit', 'delete', 'comSearch', 'quickSearch', 'columnDisplay']"
:quick-search-placeholder="t('Quick search placeholder', { fields: t('agent.commissionRecord.quick Search Fields') })"
@@ -13,7 +28,8 @@
</template>
<script setup lang="ts">
import { onMounted, provide, useTemplateRef } from 'vue'
import { computed, onMounted, provide, useTemplateRef } from 'vue'
import { useRoute } from 'vue-router'
import { useI18n } from 'vue-i18n'
import PopupForm from './popupForm.vue'
import { baTableApi } from '/@/api/common'
@@ -21,12 +37,16 @@ import { defaultOptButtons } from '/@/components/table'
import TableHeader from '/@/components/table/header/index.vue'
import Table from '/@/components/table/index.vue'
import baTableClass from '/@/utils/baTable'
import { routePush } from '/@/utils/router'
defineOptions({
name: 'agent/commissionRecord',
})
const SETTLEMENT_NO_PROP = 'settlementPeriod.settlement_no'
const { t } = useI18n()
const route = useRoute()
const tableRef = useTemplateRef('tableRef')
const optButtons: OptButton[] = defaultOptButtons(['edit', 'delete'])
@@ -41,6 +61,33 @@ function formatAmount2(_row: anyObj, _column: any, cellValue: unknown) {
return n.toFixed(2)
}
function formatPercent(_row: anyObj, _column: any, cellValue: unknown) {
if (cellValue === null || cellValue === undefined || cellValue === '') {
return '-'
}
const n = Number(cellValue)
if (!Number.isFinite(n)) {
return String(cellValue)
}
return `${n.toFixed(2)}%`
}
function formatNetCommission(row: anyObj) {
const preset = row?.net_commission_amount
if (preset !== null && preset !== undefined && preset !== '') {
const n = Number(preset)
if (Number.isFinite(n)) {
return n.toFixed(2)
}
}
const gross = Number(row?.commission_amount ?? 0)
const fee = Number(row?.handling_fee ?? 0)
if (!Number.isFinite(gross) || !Number.isFinite(fee)) {
return '-'
}
return Math.max(0, gross - fee).toFixed(2)
}
const baTable = new baTableClass(
new baTableApi('/admin/agent.CommissionRecord/'),
{
@@ -58,7 +105,7 @@ const baTable = new baTableClass(
},
{
label: t('agent.commissionRecord.settlement_period_no'),
prop: 'settlementPeriod.settlement_no',
prop: SETTLEMENT_NO_PROP,
align: 'center',
minWidth: 170,
operator: 'LIKE',
@@ -86,36 +133,70 @@ const baTable = new baTableClass(
render: 'tags',
},
{
label: t('agent.commissionRecord.commission_rate'),
prop: 'commission_rate',
label: t('agent.commissionRecord.calc_base_amount'),
prop: 'calc_base_amount',
align: 'center',
minWidth: 100,
operator: 'RANGE',
formatter: formatAmount2,
},
{
label: t('agent.commissionRecord.share_rate'),
prop: 'share_rate',
align: 'center',
minWidth: 96,
operator: 'RANGE',
formatter: formatPercent,
},
{
label: t('agent.commissionRecord.commission_amount'),
prop: 'commission_amount',
align: 'center',
minWidth: 110,
operator: 'RANGE',
formatter: formatAmount2,
},
{
label: t('agent.commissionRecord.calc_base_amount'),
prop: 'calc_base_amount',
label: t('agent.commissionRecord.commission_share_percent'),
prop: 'commission_share_percent',
align: 'center',
minWidth: 120,
minWidth: 88,
operator: 'RANGE',
formatter: formatAmount2,
formatter: formatPercent,
},
{
label: t('agent.commissionRecord.commission_amount'),
prop: 'commission_amount',
label: t('agent.commissionRecord.handling_fee_rate'),
prop: 'handling_fee_rate',
align: 'center',
minWidth: 120,
minWidth: 96,
operator: 'RANGE',
formatter: formatAmount2,
formatter: formatPercent,
show: false,
},
{
label: t('agent.commissionRecord.handling_fee'),
prop: 'handling_fee',
align: 'center',
minWidth: 96,
operator: 'RANGE',
formatter: formatAmount2,
},
{
label: t('agent.commissionRecord.net_commission_amount'),
prop: 'net_commission_amount',
align: 'center',
minWidth: 96,
operator: 'RANGE',
formatter: (row: anyObj) => formatNetCommission(row),
},
{
label: t('agent.commissionRecord.commission_rate'),
prop: 'commission_rate',
align: 'center',
minWidth: 100,
operator: 'RANGE',
formatter: formatAmount2,
show: false,
},
{
label: t('agent.commissionRecord.status'),
@@ -162,6 +243,7 @@ const baTable = new baTableClass(
width: 170,
sortable: 'custom',
timeFormat: 'yyyy-mm-dd hh:MM:ss',
show: false,
},
{
label: t('agent.commissionRecord.update_time'),
@@ -173,6 +255,7 @@ const baTable = new baTableClass(
width: 170,
sortable: 'custom',
timeFormat: 'yyyy-mm-dd hh:MM:ss',
show: false,
},
{ label: t('Operate'), align: 'center', minWidth: 80, render: 'buttons', buttons: optButtons, operator: false, fixed: 'right' },
],
@@ -182,6 +265,24 @@ const baTable = new baTableClass(
}
)
const settlementNoFilter = computed(() => {
const fromQuery = route.query[SETTLEMENT_NO_PROP]
if (typeof fromQuery === 'string' && fromQuery.trim() !== '') {
return fromQuery.trim()
}
const fromForm = baTable.comSearch.form[SETTLEMENT_NO_PROP]
return typeof fromForm === 'string' && fromForm.trim() !== '' ? fromForm.trim() : ''
})
const resetSettlementNoFilter = () => {
baTable.comSearch.form[SETTLEMENT_NO_PROP] = ''
baTable.setFilterSearchData(baTable.getComSearchData(), 'cover')
if (route.query[SETTLEMENT_NO_PROP]) {
void routePush({ path: route.path, query: {} })
}
baTable.onTableHeaderAction('refresh', { event: 'reset-settlement-filter' })
}
provide('baTable', baTable)
onMounted(() => {
@@ -194,4 +295,13 @@ onMounted(() => {
})
</script>
<style scoped lang="scss"></style>
<style scoped lang="scss">
.commission-record-filter-alert {
margin-bottom: 10px;
}
.commission-record-filter-reset {
margin-left: 8px;
vertical-align: baseline;
}
</style>

View File

@@ -21,14 +21,43 @@ import { defaultOptButtons } from '/@/components/table'
import TableHeader from '/@/components/table/header/index.vue'
import Table from '/@/components/table/index.vue'
import baTableClass from '/@/utils/baTable'
import { auth } from '/@/utils/common'
import { routePush } from '/@/utils/router'
defineOptions({
name: 'agent/settlementPeriod',
})
const SETTLEMENT_NO_PROP = 'settlementPeriod.settlement_no'
const { t } = useI18n()
const tableRef = useTemplateRef('tableRef')
const optButtons: OptButton[] = defaultOptButtons(['edit', 'delete'])
const optButtons: OptButton[] = [
{
render: 'tipButton',
name: 'viewCommissionRecords',
title: 'agent.settlementPeriod.view_commission_records',
text: '',
type: 'primary',
icon: 'fa fa-list-alt',
class: 'table-row-view-commission-records',
disabledTip: false,
display: () => auth('viewCommissionRecords'),
click: (row: TableRow) => {
const settlementNo = String(row?.settlement_no ?? '').trim()
if (settlementNo === '') {
return
}
void routePush({
path: '/admin/agent/commissionRecord',
query: {
[SETTLEMENT_NO_PROP]: settlementNo,
},
})
},
},
...defaultOptButtons(['edit', 'delete']),
]
function formatAmount2(_row: anyObj, _column: any, cellValue: unknown) {
if (cellValue === null || cellValue === undefined || cellValue === '') {
@@ -149,7 +178,7 @@ const baTable = new baTableClass(
sortable: 'custom',
timeFormat: 'yyyy-mm-dd hh:MM:ss',
},
{ label: t('Operate'), align: 'center', minWidth: 80, render: 'buttons', buttons: optButtons, operator: false, fixed: 'right' },
{ label: t('Operate'), align: 'center', minWidth: 120, render: 'buttons', buttons: optButtons, operator: false, fixed: 'right' },
],
},
{

View File

@@ -71,6 +71,7 @@
:model="manualSettle.form"
:label-width="manualSettleFormLabelWidth"
:label-position="manualSettleFormLabelPosition"
size="small"
class="manual-settle-form"
>
<el-form-item :label="t('channel.manual_settle_settlement_no')">
@@ -180,17 +181,13 @@
{{ t('channel.manual_settle_split_scroll_tip') }}
</p>
</div>
<el-alert
class="manual-settle-calc-alert"
:title="t('channel.manual_settle_calc_title')"
type="info"
:closable="false"
show-icon
>
<ul class="manual-settle-calc-list">
<li v-for="(line, idx) in manualSettleCalcDescLines" :key="idx">{{ line }}</li>
</ul>
</el-alert>
<el-collapse v-model="manualSettleCalcCollapse" class="manual-settle-calc-collapse">
<el-collapse-item :title="t('channel.manual_settle_calc_title')" name="calc">
<ul class="manual-settle-calc-list">
<li v-for="(line, idx) in manualSettleCalcDescLines" :key="idx">{{ line }}</li>
</ul>
</el-collapse-item>
</el-collapse>
</div>
</el-form-item>
<el-form-item :label="t('channel.manual_settle_remark')" class="manual-settle-form-item-full">
@@ -441,7 +438,8 @@ const { t } = useI18n()
const MANUAL_SETTLE_MOBILE_BREAKPOINT = 768
const manualSettleViewportMobile = ref(typeof window !== 'undefined' ? window.innerWidth <= MANUAL_SETTLE_MOBILE_BREAKPOINT : false)
const manualSettleDialogWidth = computed(() => (manualSettleViewportMobile.value ? '96%' : '860px'))
const manualSettleCalcCollapse = ref<string[]>([])
const manualSettleDialogWidth = computed(() => (manualSettleViewportMobile.value ? '96%' : '740px'))
const manualSettleFormLabelPosition = computed(() => (manualSettleViewportMobile.value ? 'top' : 'right'))
const manualSettleFormLabelWidth = computed(() => (manualSettleViewportMobile.value ? 'auto' : '140px'))
const manualSettleTableLayout = computed(() => 'fixed')
@@ -1484,9 +1482,9 @@ onUnmounted(() => {
display: flex !important;
flex-direction: column;
width: 92% !important;
max-width: 860px;
max-height: 92vh;
margin: 4vh auto !important;
max-width: 740px;
max-height: 86vh;
margin: 7vh auto !important;
padding-bottom: 0;
overflow: hidden;
}
@@ -1501,12 +1499,12 @@ onUnmounted(() => {
flex: 1 1 auto;
min-height: 0;
height: auto !important;
max-height: calc(92vh - 120px);
max-height: calc(86vh - 108px);
overflow-y: auto !important;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
overscroll-behavior: contain;
padding: 12px 16px 16px;
padding: 10px 14px 12px;
}
:deep(.manual-settle-dialog .el-dialog__footer) {
@@ -1528,14 +1526,15 @@ onUnmounted(() => {
.manual-settle-form {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
column-gap: 16px;
grid-template-columns: repeat(3, minmax(0, 1fr));
column-gap: 12px;
row-gap: 0;
min-width: 0;
max-width: 100%;
}
.manual-settle-form :deep(.el-form-item) {
margin-bottom: 12px;
margin-bottom: 8px;
}
.manual-settle-form-item-full {
@@ -1648,15 +1647,35 @@ onUnmounted(() => {
word-break: keep-all;
}
.manual-settle-calc-alert {
margin-top: 12px;
.manual-settle-calc-collapse {
margin-top: 8px;
border: none;
}
.manual-settle-calc-collapse :deep(.el-collapse-item__header) {
height: 34px;
line-height: 34px;
font-size: 13px;
font-weight: 500;
border-bottom: none;
background: var(--el-fill-color-light);
border-radius: 4px;
padding: 0 10px;
}
.manual-settle-calc-collapse :deep(.el-collapse-item__wrap) {
border-bottom: none;
}
.manual-settle-calc-collapse :deep(.el-collapse-item__content) {
padding: 8px 10px 2px;
}
.manual-settle-calc-list {
margin: 0;
padding-left: 18px;
line-height: 1.6;
font-size: 13px;
line-height: 1.5;
font-size: 12px;
}
.manual-settle-calc-list li + li {
@@ -1726,7 +1745,7 @@ onUnmounted(() => {
}
.manual-settle-form {
grid-template-columns: 1fr;
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@@ -1751,6 +1770,10 @@ onUnmounted(() => {
padding: 8px 10px 10px;
}
.manual-settle-form {
grid-template-columns: 1fr;
}
.manual-settle-form :deep(.el-form-item) {
margin-bottom: 10px;
}
@@ -1778,22 +1801,12 @@ onUnmounted(() => {
max-width: 100%;
}
.manual-settle-calc-alert {
margin-top: 10px;
}
.manual-settle-calc-alert :deep(.el-alert__title) {
font-size: 13px;
line-height: 1.4;
}
.manual-settle-calc-alert :deep(.el-alert__content) {
max-height: none;
overflow: visible;
.manual-settle-calc-collapse :deep(.el-collapse-item__header) {
font-size: 12px;
}
.manual-settle-calc-list {
font-size: 12px;
font-size: 11px;
padding-left: 16px;
}